Both support the temporary gate-mode arrival-event operator button (Cockpit
side of the button is a separate later step):
- rov_api: new POST /nav/safe_zone_reached endpoint, mirroring the existing
POST /abort pattern exactly (same publisher-creation and thread-safe
publish mechanism). Publishes Bool(true) to /rov/nav/safe_zone_reached,
which failsafe_monitor already subscribes to and was verified consuming
on the bench (bf815cc). This endpoint and its future Cockpit button are
temporary operator scaffolding standing in for navigation's real
GPS/EKF-derived arrival event; the topic and failsafe_monitor's
subscription to it are permanent. /abort and all other endpoints
unchanged.
- cockpit_bridge: additive new data lake variable external/rov-failsafe-state,
reading msg.failsafe_state off the same FailsafeStatus message already
handled in _failsafe_cb, alongside the existing external/rov-failsafe
(assessment_state) and external/rov-failsafe-cause. Lets a widget key a
control on the vehicle being in RETURN_TO_SAFE (value 3, confirmed from
rov_interfaces/msg/FailsafeStatus.msg STATE_* constants) specifically.
Existing rov-failsafe / rov-failsafe-cause derivation unchanged.
Widget work itself (W1 button) is a separate later step, not part of this
commit. W1 widget file and its pre-edit .bak backup are untouched.
Adds the missing exit from RETURN_TO_SAFE for gate-mode (AUV) committed
recovery. Confirmed live on RPi5 12 Jul 2026: with the mode system now
publishing a real gate profile, failsafe_monitor commits HOLD_AND_RECOVER
-> RETURN_TO_SAFE on condition-clear as designed (2a3e577) but then never
leaves, because the gate branch deliberately ignores further
condition-clear so committed recovery runs to completion (DIR-7 addendum,
"In-mission recovery is mode-dependent") — nothing signalled completion.
New subscription: /rov/nav/safe_zone_reached (std_msgs/Bool), same plain
QoS and latch-on-True callback style as the existing flag_manual_abort
subscription. This topic and this subscription are the PERMANENT nav
interface — the navigation subsystem will publish the real arrival event
here (GPS at surface, EKF dead-reckoning underwater); only today's
publisher is temporary bench-test/button scaffolding. failsafe_monitor
only ever consumes this event, never computes position itself.
health_role == HEALTH_GATE, fsm_state == RETURN_TO_SAFE, flag True ->
transition to NORMAL, cause text "Safe zone reached — recovery complete"
(DIR-7's cause-text MUST, via the existing message-prefix mechanism — no
FailsafeStatus.msg change, no cockpit_bridge/W1 change needed). Any other
combination (not in RETURN_TO_SAFE, or advisory mode) is ignored — advisory
keeps its existing, untouched operator-resume mechanism.
Gate's HOLD_AND_RECOVER->RETURN_TO_SAFE commit behaviour and the advisory
branch are both unchanged — this only adds the missing exit.
mode_profile_loader was built and committed 7 Jul 2026 (4e48dc2) but never
added to any launch file, so /rov/mode/profile has had zero publishers at
runtime on every startup since — confirmed 12 Jul 2026 on RPi5 via
`ros2 node list` (no mode node) and `ros2 topic info /rov/mode/profile`
(Publisher count: 0, 2 subscribers waiting). The entire operating-mode
profile system has been dark since it was built; failsafe_monitor's
health_role has been silently running on its gate default the whole time
rather than the actual configured mode.
Adds the Node action (package rov_mission, executable mode_profile_loader)
alongside the other core nodes, before Perception, so failsafe_monitor and
cockpit_bridge — both latched subscribers to its output — wait as little as
possible on a publisher. respawn=True/respawn_delay=5.0 to match the other
core nodes (c099c00, a5e1c99); not on the respawn exclusion list.
mode_profile_loader.py and mode_profiles.yaml are untouched — this closes
the wiring gap only.
Implements the DIR-7 addendum "In-mission recovery is mode-dependent" (11 Jul
2026, docs/design_intent_register.md). failsafe_monitor now subscribes to
/rov/mode/profile (latched) for health_role and branches condition-clear
recovery behaviour on it, replacing the mode-blind unconditional-NORMAL
recovery added in 1b0050d:
- gate (AUV): HOLD_AND_RECOVER commits to RETURN_TO_SAFE on condition-clear
instead of returning to NORMAL; an in-progress RETURN_TO_SAFE is left
alone (recovery runs to completion). Mission resumption stays a separate
deliberate action (DIR-10) — not touched here.
- advisory (ROV): stays in its current recovery state on condition-clear;
only an explicit operator resume (new placeholder topic
/rov/failsafe/operator_resume, pending the real rov_api endpoint) returns
it to NORMAL.
health_role defaults to gate (not advisory) until the first ModeProfile
message arrives, per DIR-7's absence-of-data-is-not-health guidance applied
to mode assumption. Cause text ("Recovering to safe zone" /
"Holding — awaiting operator decision") flows through the existing
message-prefix mechanism, so cockpit_bridge and W1 need no changes.
FailsafeStatus.msg, mission_executor, and mission-resume logic are
out of scope and untouched.
Add ModeProfile.msg (atomic resolved flag set) to rov_interfaces.
Add mode_profile_loader node + config/mode_profiles.yaml to rov_mission:
ROV/AUV profiles plus custom/hybrid Save As, published latched on
/rov/mode/profile, reloadable via /rov/mode/reload service.
cockpit_bridge subscribes and surfaces flags to the Cockpit data lake
as external/rov-*. Single source of truth; widgets read flags, not modes.
Verified end-to-end: ROV and AUV resolve correctly and live reload works.
Refs DIR-1/2 (ROV/AUV duality), DIR-9 (amended: ARM-bounded recording), DIR-10.
- New rov_api package: HTTP-to-ROS2 bridge on port 8081
- GET /health: mission state, recording active, failsafe state
- POST /abort: W3 RETURN TO SAFE, publishes Bool to /rov/mission/abort
- POST /mission/start: MissionCommand START (recording no-go gate enforced)
- POST /mission/stop: MissionCommand ABORT
- Embedded rclpy node in background thread, uvicorn in main thread
- Verified end-to-end: /abort -> failsafe_monitor FSM NORMAL -> HOLD_AND_RECOVER
- Runs as argonaut-api.service (manual-start in dev, same policy as argonaut.service)
- Deferred to Stage 2b: /return_budget, /backup/*, /mission/upload
- Add Range subscription on /ping2/altitude (sensor_msgs/Range)
- Publish rov-altitude to data lake (-1.0 sentinel = NO SONAR)
- Treats range_max (100m) as no-return, same as no-data sentinel
- Add recording_manager node: continuous rosbag2 MCAP recorder,
zstd compression, 500MB bag splitting, 10GB retention policy,
1Hz health monitoring and restart on subprocess exit
- Add /rov/recording/active no-go gate to mission_executor START handler
- Add rov-recording variable to cockpit_bridge (data lake visibility)
- Replace foxglove_mcap ExecuteProcess with recording_manager Node
- Register recording_manager entry point in setup.py
- Confirmed: data: true on /rov/recording/active in full stack
- New node bridges ROV topics to Cockpit via Generic WebSocket on :9001
- Sends variableName=value format (Cockpit v1.18+ protocol)
- Variables land in data lake as external/rov-{failsafe,depth,voltage,heading,ms,mp}
- Subscribes: /rov/failsafe, /rov/depth, /mavros/battery,
/mavros/mavros/compass_hdg, /rov/mission/status
- Added to mission.launch.py and rov_mission entry points
- Verified: Cockpit connects, all 6 external/rov-* vars appear in data lake
- state_estimator was an unimplemented stub (empty callbacks, never
published /rov/state) - removed node, entry point, launch block
- motion_controller + mission_executor: /rov/state -> /odometry/filtered
(the working robot_localization EKF output)
- Eliminates redundant/competing state estimators; single source of truth
- Verified: EKF publishes ~11Hz, both consumers subscribed
- ekf.yaml imu0: /imu/data -> /mavros/mavros/data (the empty /imu/data
topic had no publisher; EKF produced no output)
- Verified: /odometry/filtered now publishes valid fused orientation
with real Pixhawk IMU via MAVROS
- Note: high covariance expected (single budget IMU, untuned, no position
input) - tune after Xsens/DVL added
- depth_node + failsafe_monitor: subscribe to real MAVROS topic
/mavros/mavros/rel_alt (was /mavros/global_position/rel_alt)
- failsafe_monitor: Float32 -> Float64 (resolves duplicate-type error
that dropped the topic from recordings)
- Remove mock_publisher (VM-era fake data, now collides with real MAVROS)
- Verified: /rov/depth now publishes real data; zero duplicate-type errors
- rov_full.launch.py: fcu_url to udp://@<ip>:14550 (was bad local bind); add plugin_allowlist (sys_status,sys_time,command,imu,global_position,setpoint_velocity) to stop duplicate-subscription crash on companion_process_status/debug_value
- foxglove_mcap.launch.py: timestamp bag output dir (was fixed /data/bags/dive, failed on second launch)
- failsafe_monitor/depth_node/state_estimator: qos_profile_sensor_data on MAVROS-sourced subs (MAVROS publishes BEST_EFFORT; default RELIABLE blocked all messages)
- failsafe_monitor: treat battery_percent<0 as UNKNOWN not critical (SITL has no battery; was forcing permanent EMERGENCY_SURFACE)
- New rov_simulation package (dev only — not for vehicle deployment)
- mock_publisher: synthetic MAVROS sensor data with 5 test scenarios
(nominal, low_battery, comms_loss, depth_warning, all_clear)
- dev_stack.launch.py: launches mock_publisher + failsafe_monitor + mission_executor
- Scenarios trigger specific failsafe conditions for end-to-end testing
- Full mission state machine: IDLE/RUNNING/PAUSED/COMPLETE/ABORTED
- Breadcrumb buffer: 1800 entries at 2s interval (1hr coverage)
- Failsafe integration: HOVER=pause, RETURN_TO_SAFE=abort+breadcrumb, EMERGENCY=immediate abort
- Waypoint loading from YAML mission file via LOAD command
- Arrival detection with configurable radius
- Image capture loiter with configurable delay
- Simple proportional navigation toward waypoints (Phase 3+ replaces with full planner)
- Entry point recorded at mission start for safe zone reference
- Updated FailsafeStatus.msg with assessment state, FSM state, thruster_anomaly flag
- Implemented GREEN/AMBER/RED continuous self-assessment
- Dynamic assessment rates: 5Hz/20Hz/50Hz
- Full FSM: NORMAL->HOLD_AND_RECOVER->RETURN_TO_SAFE->EMERGENCY_SURFACE
- Priority-ordered failsafe conditions (8 levels per design spec)
- Tiered battery thresholds: warning/return/critical/emergency
- Conditional comms loss response based on assessment state
- Conditional manual abort response based on assessment state
- All thresholds configurable via failsafe.yaml