LCOV - code coverage report
Current view: top level - nodes - ControlsROS2Wrapper.h Hit Total Coverage
Test: doc-coverage.info Lines: 17 24 70.8 %
Date: 2026-07-16 00:03:02

          Line data    Source code
       1             : #ifndef CONTROLS_NODES_CONTROLSROS2WRAPPER_H
       2             : #define CONTROLS_NODES_CONTROLSROS2WRAPPER_H
       3             : 
       4             : #include <allocator/ThrustAllocator.h>
       5             : #include <utils/Types.h>
       6             : #include <utils/concurrentqueue.h>
       7             : #include <vehicle/Vehicle.h>
       8             : #include <yaml-cpp/yaml.h>
       9             : 
      10             : #include <atomic>
      11             : #include <bb_controls_msgs/action/locomotion.hpp>
      12             : #include <bb_controls_msgs/msg/controller_status.hpp>
      13             : #include <bb_controls_msgs/msg/thrusters.hpp>
      14             : #include <bb_controls_msgs/srv/controller.hpp>
      15             : #include <bb_controls_msgs/srv/encircle_traj.hpp>
      16             : #include <bb_controls_msgs/srv/spline_traj.hpp>
      17             : #include <bb_planner_msgs/srv/get_plan_through_poses.hpp>
      18             : #include <bb_sensor_msgs/msg/batteries_with_id.hpp>
      19             : #include <boost/uuid/uuid.hpp>
      20             : #include <boost/uuid/uuid_generators.hpp>
      21             : #include <boost/uuid/uuid_io.hpp>
      22             : #include <controls/controls_parameters.hpp>
      23             : #include <memory>
      24             : #include <mutex>
      25             : #include <rclcpp_action/rclcpp_action.hpp>
      26             : #include <rclcpp_action/server.hpp>
      27             : #include <std_msgs/msg/bool.hpp>
      28             : #include <string>
      29             : #include <thread>
      30             : 
      31             : /**
      32             :  * @brief ROS2 node that wraps the Vehicle control stack and exposes its interface to the rest of the system
      33             :  *
      34             :  * Manages all ROS publishers, subscribers, services, and actions.  Runs the
      35             :  * 20 Hz control loop (started on receipt of the first odometry message), drains
      36             :  * a lock-free debug queue at 20 Hz on a separate timer, and forwards parameter
      37             :  * updates to the Vehicle and SystemDynamicsBase objects at runtime.
      38             :  *
      39             :  * Services:
      40             :  *   - in/controller        — Enable (stationkeep) or disable the controller
      41             :  *   - in/xyzTraj           — Request a waypoint/spline trajectory
      42             :  *   - in/encircleTraj      — Request a circular arc trajectory
      43             :  *
      44             :  * Actions:
      45             :  *   - in/locomotion/poly — Execute a waypoint trajectory with tolerance-based success checking
      46             :  *
      47             :  * Subscriptions:
      48             :  *   - in/odom_ned   — Vehicle odometry (nav_msgs/Odometry)
      49             :  *   - in/batteries  — Battery voltages
      50             :  *   - in/teleop     — Direct force commands (disables the controller if running, then allocates directly)
      51             :  *
      52             :  * Publications:
      53             :  *   - out/force              — Controller body force (WrenchStamped)
      54             :  *   - out/force/allocated    — Achieved body force after allocation (WrenchStamped)
      55             :  *   - out/trajectory/status  — Trajectory and controller status
      56             :  *   - out/trajectory/viz     — Sampled trajectory poses for RViz
      57             :  *   - out/thrusters/input    — Per-thruster PWM commands
      58             :  *   - out/thrusters/forces   — Per-thruster forces in Newtons
      59             :  */
      60           1 : class ControlsROS2Wrapper : public rclcpp::Node {
      61             : public:
      62             :     /**
      63             :      * @brief Constructs the node, initialises all ROS interfaces, and loads parameters
      64             :      *
      65             :      * @param name              [in] ROS node name
      66             :      * @param robot             [in] Fully constructed Vehicle instance
      67             :      * @param body_frame_tf_name  [in] TF frame ID of the vehicle body frame
      68             :      * @param world_frame_tf_name [in] TF frame ID of the world frame
      69             :      */
      70           1 :     explicit ControlsROS2Wrapper(const std::string& name, std::unique_ptr<Vehicle> robot,
      71             :                                  std::string body_frame_tf_name = "base_link", std::string world_frame_tf_name = "map");
      72             : 
      73           0 :     ~ControlsROS2Wrapper() override;
      74             : 
      75             :     // Non-copyable and non-movable: mutex, atomic, unique_ptr, and thread members
      76           0 :     ControlsROS2Wrapper(const ControlsROS2Wrapper&)            = delete;
      77           0 :     ControlsROS2Wrapper& operator=(const ControlsROS2Wrapper&) = delete;
      78           0 :     ControlsROS2Wrapper(ControlsROS2Wrapper&&)                 = delete;
      79           0 :     ControlsROS2Wrapper& operator=(ControlsROS2Wrapper&&)      = delete;
      80             : 
      81           0 :     using Locomotion           = bb_controls_msgs::action::Locomotion;
      82           0 :     using GoalHandleLocomotion = rclcpp_action::ServerGoalHandle<Locomotion>;
      83             : 
      84             : protected:
      85             :     /** @brief Control loop frequency in Hz */
      86             :     static constexpr int CONTROL_LOOP_RATE = 20;
      87             : 
      88             :     /** @brief Minimum depth value clamp to avoid near-zero depth noise (m) */
      89             :     static constexpr double MIN_DEPTH_CLAMP = 0.00;
      90             : 
      91             :     /** @brief Max plausible vehicle speed for odometry jump rejection (m/s) */
      92             :     static constexpr double MAX_VEL = 1.0;
      93             : 
      94             :     /** @brief Odometry gap (s) beyond which the twist low-pass filter re-initialises */
      95             :     static constexpr double TWIST_LPF_RESET_DT = 0.5;
      96             : 
      97             :     /** @brief Flag for starting control loop **/
      98             :     std::once_flag control_loop_started_;
      99             : 
     100             :     /** @brief TF child_frame_id of the vehicle body frame */
     101             :     const std::string vehicle_frame_id_;
     102             : 
     103             :     /** @brief TF frame_id of the world frame */
     104             :     const std::string world_frame_id_;
     105             : 
     106             :     /** @brief Encapsulated vehicle control stack */
     107             :     std::unique_ptr<Vehicle> auv_;
     108             : 
     109             :     // ROS subscribers
     110             :     rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr world_sub_;
     111             :     rclcpp::Subscription<bb_sensor_msgs::msg::BatteriesWithId>::SharedPtr battery_sub_;
     112             :     rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr teleop_force_sub_;
     113             : 
     114             :     // ROS publishers
     115             :     rclcpp::Publisher<geometry_msgs::msg::WrenchStamped>::SharedPtr pub_controller_force_;
     116             :     rclcpp::Publisher<bb_controls_msgs::msg::ControllerStatus>::SharedPtr pub_status_;
     117             :     rclcpp::Publisher<geometry_msgs::msg::PoseArray>::SharedPtr pub_traj_viz_;
     118             :     rclcpp::Publisher<bb_controls_msgs::msg::Thrusters>::SharedPtr pub_thruster_commands_;
     119             :     rclcpp::Publisher<geometry_msgs::msg::WrenchStamped>::SharedPtr pub_allocator_force_;
     120             :     rclcpp::Publisher<bb_controls_msgs::msg::ThrusterForces>::SharedPtr pub_thruster_forces_;
     121             : 
     122             :     rclcpp::TimerBase::SharedPtr control_loop_timer_;
     123             :     rclcpp::TimerBase::SharedPtr debug_publish_timer_;
     124             : 
     125             :     /** @brief Lock-free queue through which the control loop passes debug messages to the publish timer */
     126             :     moodycamel::ConcurrentQueue<DebugMessage> debug_queue_;
     127             : 
     128             :     rclcpp::Service<bb_controls_msgs::srv::Controller>::SharedPtr controller_srv_;
     129             :     rclcpp::Service<bb_controls_msgs::srv::SplineTraj>::SharedPtr spline_traj_srv_;
     130             :     rclcpp::Service<bb_controls_msgs::srv::EncircleTraj>::SharedPtr encircle_traj_srv_;
     131             : 
     132             :     rclcpp_action::Server<Locomotion>::SharedPtr poly_action_server_;
     133             :     std::mutex action_goal_mutex_;
     134             :     std::shared_ptr<GoalHandleLocomotion> current_action_goal_;
     135             : 
     136             :     /** @brief Signals the action execution thread to exit its loop */
     137             :     std::atomic<bool> shutdown_requested_{false};
     138             : 
     139             :     /** @brief Thread running action execute method; joined before destruction */
     140             :     std::thread action_execute_thread_;
     141             : 
     142             :     /** @brief Declares all parameters, validates updates, and exposes the generated Params struct */
     143             :     std::shared_ptr<controls::ParamListener> param_listener_;
     144             : 
     145             :     /** @brief Local snapshot of ROS parameters, pushed to Vehicle subsystems on change */
     146             :     controls::Params config_;
     147             : 
     148             :     /** @brief ROS timestamp of the previous control loop iteration */
     149             :     double prev_time_ = 0.0;
     150             : 
     151             :     /** @brief Per-axis twist low-pass time constants (s); 0 disables an axis */
     152             :     Array6d twist_lpf_tau_ = Array6d::Zero();
     153             : 
     154             :     /** @brief State of the per-axis first-order low-pass filter on the measured twist */
     155             :     Vector6d twist_lpf_state_ = Vector6d::Zero();
     156             : 
     157             :     /** @brief Whether twist_lpf_state_ holds a valid sample */
     158             :     bool twist_lpf_init_ = false;
     159             : 
     160             :     rclcpp::CallbackGroup::SharedPtr odom_cb_group_, control_loop_cb_group_, services_cb_group_, actions_cb_group_,
     161             :         aux_cb_group_;
     162             : 
     163             :     /**
     164             :      * @brief Executes one iteration of the 20 Hz control loop
     165             :      *
     166             :      * Calls process_pending, get_force, allocate_force, and publishes thruster
     167             :      * commands.  Enqueues debug data for the background publish timer.
     168             :      */
     169           1 :     void control_loop();
     170             : 
     171             :     /**
     172             :      * @brief Publishes a ControllerStatus message from the given status snapshot
     173             :      *
     174             :      * @param status [in] Controller status snapshot to publish
     175             :      */
     176           1 :     void publish_controller_status(const Vehicle::ControllerStatus& status);
     177             : 
     178             :     /**
     179             :      * @brief Drains the debug queue and publishes all pending messages
     180             :      *
     181             :      * Also publishes the current controller status.  Called at 20 Hz by a
     182             :      * separate timer on the aux callback group.
     183             :      */
     184           1 :     void debug_publish_loop();
     185             : 
     186             :     /** @brief Pulls the latest validated params from the ParamListener and applies them on change */
     187           1 :     void refresh_params_if_changed();
     188             : 
     189             :     /**
     190             :      * @brief Applies config_ to the Vehicle's dynamics model, controller gains, and limits
     191             :      */
     192           1 :     void apply_config_to_vehicle();
     193             : 
     194             :     /**
     195             :      * @brief Odometry subscriber callback — updates vehicle state and starts the control loop
     196             :      *
     197             :      * @param m [in] Incoming odometry message
     198             :      */
     199           1 :     void sub_callback_world_position(const nav_msgs::msg::Odometry::ConstSharedPtr& m);
     200             : 
     201             :     /**
     202             :      * @brief Battery subscriber callback — forwards the minimum cell voltage to the Vehicle
     203             :      *
     204             :      * @param msg [in] Incoming battery message containing per-cell voltages
     205             :      */
     206           1 :     void sub_callback_battery_voltage(const bb_sensor_msgs::msg::BatteriesWithId::ConstSharedPtr& msg);
     207             : 
     208             :     /**
     209             :      * @brief Teleop subscriber callback — allocates the raw force directly if the controller is disabled
     210             :      *
     211             :      * If the controller is not disabled, issues a disable request and ignores the message.
     212             :      *
     213             :      * @param msg [in] Twist message interpreted as a 6DOF body force [Fx, Fy, Fz, Tx, Ty, Tz]
     214             :      */
     215           1 :     void sub_callback_teleop_force(const geometry_msgs::msg::Twist::ConstSharedPtr& msg);
     216             : 
     217             :     /**
     218             :      * @brief Service callback to enable (stationkeep) or disable the controller
     219             :      *
     220             :      * @param req [in]  Request with an enable flag
     221             :      * @param res [out] Response with a status flag (always true)
     222             :      */
     223           1 :     void srv_callback_controller(const std::shared_ptr<bb_controls_msgs::srv::Controller::Request> req,
     224             :                                  std::shared_ptr<bb_controls_msgs::srv::Controller::Response> res);
     225             : 
     226             :     /**
     227             :      * @brief Service callback to request a straight XYZ or multi-waypoint spline trajectory
     228             :      *
     229             :      * @param req [in]  SplineTraj request containing waypoints and relative-coordinate flags
     230             :      * @param res [out] Response populated with the assigned trajectory UUID, or empty on failure
     231             :      */
     232           1 :     void srv_callback_trajectory_xyz(const std::shared_ptr<bb_controls_msgs::srv::SplineTraj::Request> req,
     233             :                                      std::shared_ptr<bb_controls_msgs::srv::SplineTraj::Response> res);
     234             : 
     235             :     /**
     236             :      * @brief Service callback to request a circular arc (encircle) trajectory
     237             :      *
     238             :      * @param req [in]  EncircleTraj request containing radius, turn angle, and spiral parameters
     239             :      * @param res [out] Response populated with the assigned trajectory UUID, or empty on failure
     240             :      */
     241           1 :     void srv_callback_encircle_traj(const std::shared_ptr<bb_controls_msgs::srv::EncircleTraj::Request> req,
     242             :                                     std::shared_ptr<bb_controls_msgs::srv::EncircleTraj::Response> res);
     243             : 
     244             :     /**
     245             :      * @brief Action server goal handler — rejects goals when the controller is disabled
     246             :      *
     247             :      * @param uuid [in] Goal UUID assigned by the action client
     248             :      * @param goal [in] Locomotion goal
     249             :      *
     250             :      * @return ACCEPT_AND_EXECUTE if the controller is enabled; REJECT otherwise
     251             :      */
     252           1 :     rclcpp_action::GoalResponse poly_handle_goal(const rclcpp_action::GoalUUID& uuid,
     253             :                                                  std::shared_ptr<const Locomotion::Goal> goal);
     254             : 
     255             :     /**
     256             :      * @brief Action server cancel handler — always accepts cancel requests
     257             :      *
     258             :      * @param goal_handle [in] Handle to the goal being cancelled
     259             :      *
     260             :      * @return CancelResponse::ACCEPT
     261             :      */
     262           1 :     rclcpp_action::CancelResponse poly_handle_cancel(const std::shared_ptr<GoalHandleLocomotion> goal_handle);
     263             : 
     264             :     /**
     265             :      * @brief Action server accepted handler — preempts any active goal and starts a new execute thread
     266             :      *
     267             :      * @param goal_handle [in] Handle to the newly accepted goal
     268             :      */
     269           1 :     void poly_handle_accepted(const std::shared_ptr<GoalHandleLocomotion> goal_handle);
     270             : 
     271             :     /**
     272             :      * @brief Executes a Locomotion action: plans trajectory, waits for completion, checks tolerances
     273             :      *
     274             :      * Runs in action_execute_thread_.  Publishes periodic feedback and resolves the
     275             :      * action result to SUCCESS when pose errors fall within the goal tolerances,
     276             :      * or to ABORTED/FAILURE on preemption or planning errors.
     277             :      *
     278             :      * @param goal_handle [in] Handle used to publish feedback and set the final result
     279             :      */
     280           1 :     void poly_execute_action(const std::shared_ptr<GoalHandleLocomotion> goal_handle);
     281             : };
     282             : 
     283             : #endif  // CONTROLS_NODES_CONTROLSROS2WRAPPER_H

Generated by: LCOV version 1.14