From c3e54ca9967b52314cb64a9878ed28c49e775b73 Mon Sep 17 00:00:00 2001 From: Cyprian Osinski Date: Sat, 20 Sep 2025 18:20:19 +0200 Subject: [PATCH 01/13] feat: converted velocity controller lqr to lifecycle node --- control/velocity_controller_lqr/README.md | 45 +++++ control/velocity_controller_lqr/README.txt | 1 - .../config/param_velocity_controller_lqr.yaml | 14 +- .../figures/ros2_transition_diagram.png | Bin 0 -> 107366 bytes .../launch/velocity_controller_lqr.launch.py | 15 +- .../scripts/velocity_controller_lqr_node.py | 187 ++++++++++++------ .../velocity_controller_lqr_lib.py | 2 + 7 files changed, 186 insertions(+), 78 deletions(-) create mode 100644 control/velocity_controller_lqr/README.md delete mode 100644 control/velocity_controller_lqr/README.txt create mode 100644 control/velocity_controller_lqr/figures/ros2_transition_diagram.png mode change 100755 => 100644 control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py diff --git a/control/velocity_controller_lqr/README.md b/control/velocity_controller_lqr/README.md new file mode 100644 index 000000000..27365f5f7 --- /dev/null +++ b/control/velocity_controller_lqr/README.md @@ -0,0 +1,45 @@ +## Overview: +--- +Contains the LQR controller package for the AUV Orca. The controller utilizes an LQR optimal controller (imported from the python control library), and controls pitch, yaw and surge. The controller is meant to traverse larger distances. + +#### Tuning of Parameters: + To tune parameters look at the config/param_velocity_controller_lqr.yaml file: + + +#### Launching the package: +```bash +1. inside ros2ws/colcon build --packages-select velocity_controller_lqr +``` + +```bash +2. Inisde ros2ws/: source install/setup.bash +``` + +```bash +3. ros2 launch velocity_controller_lqr velocity_controller_lqr.launch.py +``` + +#### Transitioning between states manually: +The ROS2 node is implemented using lifecycle nodes, which are managed externally by a lifecycle manager i.e a finite state machine. If you want to manually test the node do the following: + +**From Unconfigured ---> Inactive** +```bash +ros2 lifecycle set /velocity_controller_lqr_node configure +``` + +**From Configured ---> Active** +```bash +ros2 lifecycle set /velocity_controller_lqr_node activate +``` + +**From Active ---> Inactive** +```bash +ros2 lifecycle set /velocity_controller_lqr_node deactivate +``` + +For the full state diagram you can refer to the figure below, sourced from the official ROS2 Documentation: +![image info](./figures/ros2_transition_diagram.png) + + +### Theory +--- diff --git a/control/velocity_controller_lqr/README.txt b/control/velocity_controller_lqr/README.txt deleted file mode 100644 index c49e405ea..000000000 --- a/control/velocity_controller_lqr/README.txt +++ /dev/null @@ -1 +0,0 @@ -This package contains the velocity controller for the AUV Orca. The controller utilizes an LQR optimal controller (imported from the python control library), and controls pitch, yaw and surge. The controller is meant to traverse larger distances. diff --git a/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml b/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml index 727c1c71e..42f26f53d 100644 --- a/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml +++ b/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml @@ -1,6 +1,14 @@ -/**: +velocity_controller_lqr_node: ros__parameters: - dt: 0.1 + + topics: + odom_topic: /orca/odom + twist_topic: /dvl/twist + pose_topic: /dvl/pose + guidance_topic: /guidance/los + thrust_topic: /thrust/wrench_input + softwareoperation_topic: /softwareOperationMode + killswitch_topic: /softwareKillSwitch LQR_params: q_surge: 75 @@ -17,6 +25,8 @@ i_weight: 0.5 + dt: 0.1 + inertia_matrix: [30.0, 0.6, 0.0, 0.6, 1.629, 0.0, 0.0, 0.0, 1.729] #Clamp parameter diff --git a/control/velocity_controller_lqr/figures/ros2_transition_diagram.png b/control/velocity_controller_lqr/figures/ros2_transition_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..bfd9ee9c2242e95619844cf0453186e4f223391f GIT binary patch literal 107366 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

$bK->#q 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 literal 0 HcmV?d00001 diff --git a/control/velocity_controller_lqr/launch/velocity_controller_lqr.launch.py b/control/velocity_controller_lqr/launch/velocity_controller_lqr.launch.py index 6669744d5..5f4e9e38c 100644 --- a/control/velocity_controller_lqr/launch/velocity_controller_lqr.launch.py +++ b/control/velocity_controller_lqr/launch/velocity_controller_lqr.launch.py @@ -2,7 +2,7 @@ from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch_ros.actions import Node +from launch_ros.actions import LifecycleNode def generate_launch_description() -> LaunchDescription: @@ -22,20 +22,13 @@ def generate_launch_description() -> LaunchDescription: "param_velocity_controller_lqr.yaml", ) - topic_file = os.path.join( - get_package_share_directory("auv_setup"), - "config", - "robots", - "orca.yaml", - ) - - velocity_controller_node = Node( + velocity_controller_node = LifecycleNode( package="velocity_controller_lqr", executable="velocity_controller_lqr_node.py", name="velocity_controller_lqr_node", - namespace="orca", + namespace="", output="screen", - parameters=[parameter_file, topic_file], + parameters=[parameter_file], ) return LaunchDescription([velocity_controller_node]) diff --git a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py old mode 100755 new mode 100644 index aae5aeb4a..ca6597a0a --- a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py +++ b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 - import numpy as np import rclpy from geometry_msgs.msg import ( PoseWithCovarianceStamped, TwistWithCovarianceStamped, - WrenchStamped, + Wrench, ) from rclpy.executors import MultiThreadedExecutor -from rclpy.node import Node +from rclpy.lifecycle import LifecycleNode +from rclpy.lifecycle.node import LifecycleState, TransitionCallbackReturn from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy from std_msgs.msg import Bool, String from velocity_controller_lqr.velocity_controller_lqr_lib import ( @@ -20,98 +20,153 @@ from vortex_msgs.msg import LOSGuidance -class LinearQuadraticRegulator(Node): +class LinearQuadraticRegulator(LifecycleNode): def __init__(self): + # ----------------------- DEFINE RELIABILITY ------------------------ super().__init__("velocity_controller_lqr_node") - - self.get_topics() - - best_effort_qos = QoSProfile( + self.best_effort_qos = QoSProfile( reliability=ReliabilityPolicy.BEST_EFFORT, history=HistoryPolicy.KEEP_LAST, depth=1, ) - # ---------------------------- SUBSCRIBERS --------------------------- + self.reliable_qos = QoSProfile( + reliability=ReliabilityPolicy.RELIABLE, + history=HistoryPolicy.KEEP_LAST, + depth=10, + ) + + # ---------------- CALLBACK VARIABLES INITIALIZATION ---------------- + self.coriolis_matrix = np.zeros((3, 3)) + self.states = State() + self.guidance_values = GuidanceValues() + self.lqr_params = LQRParameters() + # ------------------------ INITIALIZE TO NONE ----------------------- + self.pose_subscriber = None + self.twist_subscriber = None + self.operationmode_subscriber = None + self.killswitch_subscriber = None + self.guidance_subscriber = None + self.publisherLQR = None + self.control_timer = None + + # ------------------ ROS2 PARAMETERS AND CONTROLLER ------------------ + inertia_matrix = self.get_parameters() + self.controller = LQRController(self.lqr_params, inertia_matrix) + + def on_configure(self, previous_state: LifecycleState) -> TransitionCallbackReturn: + # -------------------------- GET ALL TOPICS ------------------------- + ( + pose_topic, + twist_topic, + guidance_topic, + thrust_topic, + softwareoperation_topic, + killswitch_topic, + ) = self.get_topics() self.pose_subscriber = self.create_subscription( PoseWithCovarianceStamped, - self.pose_topic, + pose_topic, self.pose_callback, - qos_profile=best_effort_qos, + qos_profile=self.best_effort_qos, ) self.twist_subscriber = self.create_subscription( TwistWithCovarianceStamped, - self.twist_topic, + twist_topic, self.twist_callback, - qos_profile=best_effort_qos, + qos_profile=self.best_effort_qos, ) self.operationmode_subscriber = self.create_subscription( String, - self.operation_mode_topic, + softwareoperation_topic, self.operation_callback, - qos_profile=2, + qos_profile=self.best_effort_qos, ) self.killswitch_subscriber = self.create_subscription( Bool, - self.killswitch_topic, + killswitch_topic, self.killswitch_callback, - qos_profile=2, + qos_profile=self.best_effort_qos, ) self.guidance_subscriber = self.create_subscription( LOSGuidance, - self.los_topic, + guidance_topic, self.guidance_callback, - qos_profile=best_effort_qos, + qos_profile=self.best_effort_qos, ) # ---------------------------- PUBLISHERS ---------------------------- - self.publisherLQR = self.create_publisher( - WrenchStamped, self.wrench_input_topic, best_effort_qos + self.publisherLQR = self.create_lifecycle_publisher( + Wrench, thrust_topic, self.reliable_qos ) # ------------------------------ TIMERS ------------------------------ - dt = self.declare_parameter("dt", 0.1).get_parameter_value().double_value + dt = self.lqr_params.dt self.control_timer = self.create_timer(dt, self.control_loop) + self.control_timer.cancel() - # ------------------ ROS2 PARAMETERS AND CONTROLLER ------------------ - self.lqr_params = LQRParameters() - inertia_matrix = self.get_parameters() - self.controller = LQRController(self.lqr_params, inertia_matrix) + return TransitionCallbackReturn.SUCCESS - # ---------------- CALLBACK VARIABLES INITIALIZATION ----------------- - self.coriolis_matrix = np.zeros((3, 3)) - self.states = State() - self.guidance_values = GuidanceValues() + def on_activate(self, previous_state: LifecycleState) -> TransitionCallbackReturn: + self.control_timer.reset() + self.controller.reset_controller() + return super().on_activate(previous_state) - def get_topics(self): - """Get the topics from the parameter file.""" - topics = [ - "pose", - "twist", - "los", - "wrench_input", - "operation_mode", - "killswitch", - ] - for topic in topics: - if topic == "los": - self.declare_parameter("topics.guidance." + topic, "_") - setattr( - self, - topic + "_topic", - self.get_parameter("topics.guidance." + topic).value, - ) - continue - self.declare_parameter("topics." + topic, "_") - setattr( - self, - topic + "_topic", - self.get_parameter("topics." + topic).value, - ) + def on_deactivate(self, previous_state: LifecycleState) -> TransitionCallbackReturn: + self.control_timer.cancel() + self.controller.reset_controller() + return super().on_activate(previous_state) + + def on_cleanup(self, previous_state: LifecycleState) -> TransitionCallbackReturn: + self.destroy_publisher(self.publisherLQR) + self.destroy_timer(self.control_timer) + return TransitionCallbackReturn.SUCCESS + + def on_shutdown(self, previous_state: LifecycleState) -> TransitionCallbackReturn: + self.destroy_publisher(self.publisherLQR) + self.destroy_timer(self.control_timer) + return TransitionCallbackReturn.SUCCESS + + def get_topics(self) -> None: + """Get the topics from the parameter server. + + Returns: + odom_topic: str: The topic for accessing the odometry data from the parameter file + twist_topic: str: The topic for accessing the twist data from the parameter file + pose_topic: str: The topic for accessing the pose data from the parameter file + guidance_topic: str: The topic for accessing the guidance data the parameter file + thrust_topic: str: The topic for accessing the thrust data from the parameter file + """ + self.declare_parameter("topics.pose_topic", "/dvl/pose") + self.declare_parameter("topics.twist_topic", "/dvl/twist") + self.declare_parameter("topics.guidance_topic", "/guidance/los") + self.declare_parameter("topics.thrust_topic", "/thrust/wrench_input") + self.declare_parameter( + "topics.softwareoperation_topic", "/softwareOperationMode" + ) + self.declare_parameter("topics.killswitch_topic", "/softwareKillSwitch") + + pose_topic = self.get_parameter("topics.pose_topic").value + twist_topic = self.get_parameter("topics.twist_topic").value + guidance_topic = self.get_parameter("topics.guidance_topic").value + thrust_topic = self.get_parameter("topics.thrust_topic").value + softwareoperation_topic = self.get_parameter( + "topics.softwareoperation_topic" + ).value + killswitch_topic = self.get_parameter("topics.killswitch_topic").value + + return ( + pose_topic, + twist_topic, + guidance_topic, + thrust_topic, + softwareoperation_topic, + killswitch_topic, + ) def get_parameters(self): """Updates the LQR_params in the LQR_parameters Dataclass, and gets the inertia matrix from config. @@ -133,6 +188,8 @@ def get_parameters(self): self.declare_parameter("LQR_params.i_weight", 0.5) + self.declare_parameter("LQR_params.dt", 0.1) + self.declare_parameter("max_force", 99.5) self.declare_parameter( "inertia_matrix", [30.0, 0.6, 0.0, 0.6, 1.629, 0.0, 0.0, 0.0, 1.729] @@ -153,12 +210,14 @@ def get_parameters(self): self.lqr_params.i_weight = self.get_parameter("LQR_params.i_weight").value self.lqr_params.max_force = self.get_parameter("max_force").value + self.lqr_params.dt = self.get_parameter("LQR_params.dt").value + inertia_matrix_flat = self.get_parameter("inertia_matrix").value inertia_matrix = np.array(inertia_matrix_flat).reshape((3, 3)) return inertia_matrix - # ---------------------------------------------------------------CALLBACK FUNCTIONS--------------------------------------------------------------- + # ------------------------- CALLBACK FUNCTIONS --------------------------- def pose_callback(self, msg: PoseWithCovarianceStamped): """Callback function for the pose data from DVL. @@ -212,24 +271,24 @@ def killswitch_callback(self, msg: Bool): Parameters: String: msg: The killswitch data from the AUV. """ - self.controller.killswitch = msg.data - if self.controller.killswitch: + if msg.data == True: self.controller.reset_controller() + self.controller.killswitch = True + else: + self.controller.killswitch = False # ---------------------------------------------------------------PUBLISHER FUNCTIONS------------------------------------------------------------- def control_loop(self): """The control loop that calculates the input for the LQR controller.""" - msg = WrenchStamped() + msg = Wrench() u = self.controller.calculate_lqr_u( self.coriolis_matrix, self.states, self.guidance_values ) - msg.header.stamp = self.get_clock().now().to_msg() - msg.header.frame_id = "base_link" - msg.wrench.force.x = float(u[0]) - msg.wrench.torque.y = float(u[1]) - msg.wrench.torque.z = float(u[2]) + msg.force.x = float(u[0]) + msg.torque.y = float(u[1]) + msg.torque.z = float(u[2]) if ( self.controller.killswitch == False diff --git a/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py b/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py index af0528ee0..bbd37a313 100644 --- a/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py +++ b/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py @@ -78,6 +78,7 @@ class LQRParameters: max_force: float = 0.0 operation_mode: str = "xbox mode" killswitch: bool = True + dt: float = 0.1 class LQRController: @@ -227,6 +228,7 @@ def set_params(self, parameters: LQRParameters) -> None: self.operation_mode = parameters.operation_mode self.killswitch = parameters.killswitch + self.dt = parameters.dt def set_matrices(self, inertia_matrix: np.array) -> None: """Adjusts the matrices for the LQR controller. From 0ea41ab3fc54d5a5f6cf9b5b192ac9f356356104 Mon Sep 17 00:00:00 2001 From: Cyprian Osinski Date: Sat, 20 Sep 2025 20:28:58 +0200 Subject: [PATCH 02/13] Changed QOS to match killswitch and refactored some functions to be more clear and concise --- .../config/param_velocity_controller_lqr.yaml | 2 +- .../scripts/velocity_controller_lqr_node.py | 75 ++++++++++--------- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml b/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml index 42f26f53d..611c285b8 100644 --- a/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml +++ b/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml @@ -1,4 +1,4 @@ -velocity_controller_lqr_node: +/**: ros__parameters: topics: diff --git a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py index ca6597a0a..40d70cea7 100644 --- a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py +++ b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py @@ -38,6 +38,7 @@ def __init__(self): # ---------------- CALLBACK VARIABLES INITIALIZATION ---------------- self.coriolis_matrix = np.zeros((3, 3)) + self.inertia_matrix = np.zeros((3,3)) self.states = State() self.guidance_values = GuidanceValues() self.lqr_params = LQRParameters() @@ -51,10 +52,12 @@ def __init__(self): self.control_timer = None # ------------------ ROS2 PARAMETERS AND CONTROLLER ------------------ - inertia_matrix = self.get_parameters() - self.controller = LQRController(self.lqr_params, inertia_matrix) + self.get_and_reshape_inertia_matrix() + self.controller = LQRController(self.lqr_params, self.inertia_matrix) def on_configure(self, previous_state: LifecycleState) -> TransitionCallbackReturn: + + self.get_parameters() # -------------------------- GET ALL TOPICS ------------------------- ( pose_topic, @@ -96,7 +99,7 @@ def on_configure(self, previous_state: LifecycleState) -> TransitionCallbackRetu LOSGuidance, guidance_topic, self.guidance_callback, - qos_profile=self.best_effort_qos, + qos_profile=self.reliable_qos, ) # ---------------------------- PUBLISHERS ---------------------------- @@ -169,31 +172,25 @@ def get_topics(self) -> None: ) def get_parameters(self): - """Updates the LQR_params in the LQR_parameters Dataclass, and gets the inertia matrix from config. - - Returns: - inertia_matrix: np.array: The inertia matrix of the AUV - """ - self.declare_parameter("LQR_params.q_surge", 75) - self.declare_parameter("LQR_params.q_pitch", 175) - self.declare_parameter("LQR_params.q_yaw", 175) + """Updates the LQR_params in the LQR_parameters Dataclass.""" + + self.declare_parameter("LQR_params.q_surge") + self.declare_parameter("LQR_params.q_pitch") + self.declare_parameter("LQR_params.q_yaw") - self.declare_parameter("LQR_params.r_surge", 0.3) - self.declare_parameter("LQR_params.r_pitch", 0.4) - self.declare_parameter("LQR_params.r_yaw", 0.4) + self.declare_parameter("LQR_params.r_surge") + self.declare_parameter("LQR_params.r_pitch") + self.declare_parameter("LQR_params.r_yaw") - self.declare_parameter("LQR_params.i_surge", 0.3) - self.declare_parameter("LQR_params.i_pitch", 0.4) - self.declare_parameter("LQR_params.i_yaw", 0.3) + self.declare_parameter("LQR_params.i_surge") + self.declare_parameter("LQR_params.i_pitch") + self.declare_parameter("LQR_params.i_yaw") - self.declare_parameter("LQR_params.i_weight", 0.5) + self.declare_parameter("LQR_params.i_weight") - self.declare_parameter("LQR_params.dt", 0.1) + self.declare_parameter("LQR_params.dt") - self.declare_parameter("max_force", 99.5) - self.declare_parameter( - "inertia_matrix", [30.0, 0.6, 0.0, 0.6, 1.629, 0.0, 0.0, 0.0, 1.729] - ) + self.declare_parameter("max_force") self.lqr_params.q_surge = self.get_parameter("LQR_params.q_surge").value self.lqr_params.q_pitch = self.get_parameter("LQR_params.q_pitch").value @@ -212,15 +209,20 @@ def get_parameters(self): self.lqr_params.dt = self.get_parameter("LQR_params.dt").value - inertia_matrix_flat = self.get_parameter("inertia_matrix").value - inertia_matrix = np.array(inertia_matrix_flat).reshape((3, 3)) - return inertia_matrix + def get_and_reshape_inertia_matrix(self): + """Gets the inertia matrix from config and reshapes it to proper np array""" + self.declare_parameter("inertia_matrix") + self.inertia_matrix = self.get_parameter("inertia_matrix").value + inertia_matrix_reshaped = np.array(self.inertia_matrix).reshape((3, 3)) + + self.inertia_matrix = inertia_matrix_reshaped + # ------------------------- CALLBACK FUNCTIONS --------------------------- def pose_callback(self, msg: PoseWithCovarianceStamped): - """Callback function for the pose data from DVL. + """Callback function for the pose data from sensors. Parameters: msg: PoseWithCovarianceStamped The pose data from the DVL. @@ -281,15 +283,6 @@ def killswitch_callback(self, msg: Bool): def control_loop(self): """The control loop that calculates the input for the LQR controller.""" - msg = Wrench() - - u = self.controller.calculate_lqr_u( - self.coriolis_matrix, self.states, self.guidance_values - ) - msg.force.x = float(u[0]) - msg.torque.y = float(u[1]) - msg.torque.z = float(u[2]) - if ( self.controller.killswitch == False and self.controller.operation_mode == "autonomous mode" @@ -298,8 +291,16 @@ def control_loop(self): else: self.controller.reset_controller() + + msg = Wrench() + - + u = self.controller.calculate_lqr_u( + self.coriolis_matrix, self.states, self.guidance_values + ) + msg.force.x = float(u[0]) + msg.torque.y = float(u[1]) + msg.torque.z = float(u[2]) # ----------------------------------------------------------------------MAIN FUNCTION---------------------------------------------------------------- From 32417415d078315925e87720ed96361d54e1eddf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 18:29:09 +0000 Subject: [PATCH 03/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../scripts/velocity_controller_lqr_node.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py index 40d70cea7..f728af61d 100644 --- a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py +++ b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py @@ -38,7 +38,7 @@ def __init__(self): # ---------------- CALLBACK VARIABLES INITIALIZATION ---------------- self.coriolis_matrix = np.zeros((3, 3)) - self.inertia_matrix = np.zeros((3,3)) + self.inertia_matrix = np.zeros((3, 3)) self.states = State() self.guidance_values = GuidanceValues() self.lqr_params = LQRParameters() @@ -56,7 +56,6 @@ def __init__(self): self.controller = LQRController(self.lqr_params, self.inertia_matrix) def on_configure(self, previous_state: LifecycleState) -> TransitionCallbackReturn: - self.get_parameters() # -------------------------- GET ALL TOPICS ------------------------- ( @@ -173,7 +172,6 @@ def get_topics(self) -> None: def get_parameters(self): """Updates the LQR_params in the LQR_parameters Dataclass.""" - self.declare_parameter("LQR_params.q_surge") self.declare_parameter("LQR_params.q_pitch") self.declare_parameter("LQR_params.q_yaw") @@ -209,15 +207,13 @@ def get_parameters(self): self.lqr_params.dt = self.get_parameter("LQR_params.dt").value - def get_and_reshape_inertia_matrix(self): """Gets the inertia matrix from config and reshapes it to proper np array""" self.declare_parameter("inertia_matrix") self.inertia_matrix = self.get_parameter("inertia_matrix").value inertia_matrix_reshaped = np.array(self.inertia_matrix).reshape((3, 3)) - - self.inertia_matrix = inertia_matrix_reshaped + self.inertia_matrix = inertia_matrix_reshaped # ------------------------- CALLBACK FUNCTIONS --------------------------- @@ -291,9 +287,8 @@ def control_loop(self): else: self.controller.reset_controller() - + msg = Wrench() - u = self.controller.calculate_lqr_u( self.coriolis_matrix, self.states, self.guidance_values @@ -301,6 +296,8 @@ def control_loop(self): msg.force.x = float(u[0]) msg.torque.y = float(u[1]) msg.torque.z = float(u[2]) + + # ----------------------------------------------------------------------MAIN FUNCTION---------------------------------------------------------------- From 8d168839311175bb95869a8f7e7c0340b4f41426 Mon Sep 17 00:00:00 2001 From: Cyprian Osinski Date: Sat, 20 Sep 2025 20:32:44 +0200 Subject: [PATCH 04/13] precommit hooks and explicit return values --- .../scripts/velocity_controller_lqr_node.py | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py index f728af61d..bb3e109e4 100644 --- a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py +++ b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py @@ -1,22 +1,15 @@ #!/usr/bin/env python3 import numpy as np import rclpy -from geometry_msgs.msg import ( - PoseWithCovarianceStamped, - TwistWithCovarianceStamped, - Wrench, -) +from geometry_msgs.msg import (PoseWithCovarianceStamped, + TwistWithCovarianceStamped, Wrench) from rclpy.executors import MultiThreadedExecutor from rclpy.lifecycle import LifecycleNode from rclpy.lifecycle.node import LifecycleState, TransitionCallbackReturn from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy from std_msgs.msg import Bool, String from velocity_controller_lqr.velocity_controller_lqr_lib import ( - GuidanceValues, - LQRController, - LQRParameters, - State, -) + GuidanceValues, LQRController, LQRParameters, State) from vortex_msgs.msg import LOSGuidance @@ -170,7 +163,7 @@ def get_topics(self) -> None: killswitch_topic, ) - def get_parameters(self): + def get_parameters(self) -> None: """Updates the LQR_params in the LQR_parameters Dataclass.""" self.declare_parameter("LQR_params.q_surge") self.declare_parameter("LQR_params.q_pitch") @@ -207,7 +200,7 @@ def get_parameters(self): self.lqr_params.dt = self.get_parameter("LQR_params.dt").value - def get_and_reshape_inertia_matrix(self): + def get_and_reshape_inertia_matrix(self) -> None: """Gets the inertia matrix from config and reshapes it to proper np array""" self.declare_parameter("inertia_matrix") self.inertia_matrix = self.get_parameter("inertia_matrix").value @@ -217,7 +210,7 @@ def get_and_reshape_inertia_matrix(self): # ------------------------- CALLBACK FUNCTIONS --------------------------- - def pose_callback(self, msg: PoseWithCovarianceStamped): + def pose_callback(self, msg: PoseWithCovarianceStamped) -> None: """Callback function for the pose data from sensors. Parameters: msg: PoseWithCovarianceStamped The pose data from the DVL. @@ -230,7 +223,7 @@ def pose_callback(self, msg: PoseWithCovarianceStamped): msg.pose.pose.orientation.z, ) - def operation_callback(self, msg: String): + def operation_callback(self, msg: String) -> None: """Callback function for the operation mode data. Parameters: String: msg: The operation mode data from the AUV. @@ -238,7 +231,7 @@ def operation_callback(self, msg: String): """ self.controller.operation_mode = msg.data - def twist_callback(self, msg: TwistWithCovarianceStamped): + def twist_callback(self, msg: TwistWithCovarianceStamped) -> None: """Callback function for the Twist data from DVL. Parameters: msg: TwistWithCovarianceStamped The twist data from the DVL. @@ -253,7 +246,7 @@ def twist_callback(self, msg: TwistWithCovarianceStamped): msg.twist.twist.linear.z, ) - def guidance_callback(self, msg: LOSGuidance): + def guidance_callback(self, msg: LOSGuidance) -> None: """Callback function for the guidance data. Parameters: LOSGuidance: msg: The guidance data from the LOS guidance system. @@ -263,7 +256,7 @@ def guidance_callback(self, msg: LOSGuidance): self.guidance_values.pitch = msg.pitch self.guidance_values.yaw = msg.yaw - def killswitch_callback(self, msg: Bool): + def killswitch_callback(self, msg: Bool) -> None: """Callback function for the killswitch data. Parameters: String: msg: The killswitch data from the AUV. @@ -277,7 +270,7 @@ def killswitch_callback(self, msg: Bool): # ---------------------------------------------------------------PUBLISHER FUNCTIONS------------------------------------------------------------- - def control_loop(self): + def control_loop(self) -> None: """The control loop that calculates the input for the LQR controller.""" if ( self.controller.killswitch == False From c7c72d5e22503626fbb623c27cf67871bd57b9ef Mon Sep 17 00:00:00 2001 From: Cyprian Osinski Date: Sat, 20 Sep 2025 20:40:00 +0200 Subject: [PATCH 05/13] refactored to if guard for easy exit and so precommit hook gets off my ass --- .../scripts/velocity_controller_lqr_node.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py index bb3e109e4..4ed045e76 100644 --- a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py +++ b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py @@ -272,14 +272,9 @@ def killswitch_callback(self, msg: Bool) -> None: def control_loop(self) -> None: """The control loop that calculates the input for the LQR controller.""" - if ( - self.controller.killswitch == False - and self.controller.operation_mode == "autonomous mode" - ): - self.publisherLQR.publish(msg) - - else: + if (self.controller.killswitch == True or self.controller.operation_mode != "autonomous mode"): self.controller.reset_controller() + return msg = Wrench() @@ -290,6 +285,7 @@ def control_loop(self) -> None: msg.torque.y = float(u[1]) msg.torque.z = float(u[2]) + self.publisherLQR.publish(msg) # ----------------------------------------------------------------------MAIN FUNCTION---------------------------------------------------------------- From 90d74880a6bd27168f500bb2ada4d389534e9e2d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 18:41:15 +0000 Subject: [PATCH 06/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../scripts/velocity_controller_lqr_node.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py index 4ed045e76..2ff358e00 100644 --- a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py +++ b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py @@ -1,15 +1,22 @@ #!/usr/bin/env python3 import numpy as np import rclpy -from geometry_msgs.msg import (PoseWithCovarianceStamped, - TwistWithCovarianceStamped, Wrench) +from geometry_msgs.msg import ( + PoseWithCovarianceStamped, + TwistWithCovarianceStamped, + Wrench, +) from rclpy.executors import MultiThreadedExecutor from rclpy.lifecycle import LifecycleNode from rclpy.lifecycle.node import LifecycleState, TransitionCallbackReturn from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy from std_msgs.msg import Bool, String from velocity_controller_lqr.velocity_controller_lqr_lib import ( - GuidanceValues, LQRController, LQRParameters, State) + GuidanceValues, + LQRController, + LQRParameters, + State, +) from vortex_msgs.msg import LOSGuidance @@ -272,7 +279,10 @@ def killswitch_callback(self, msg: Bool) -> None: def control_loop(self) -> None: """The control loop that calculates the input for the LQR controller.""" - if (self.controller.killswitch == True or self.controller.operation_mode != "autonomous mode"): + if ( + self.controller.killswitch == True + or self.controller.operation_mode != "autonomous mode" + ): self.controller.reset_controller() return @@ -287,6 +297,7 @@ def control_loop(self) -> None: self.publisherLQR.publish(msg) + # ----------------------------------------------------------------------MAIN FUNCTION---------------------------------------------------------------- From 0fc0b832650c16ccf2336f9d811c2c87669588a9 Mon Sep 17 00:00:00 2001 From: Cyprian Osinski Date: Sat, 20 Sep 2025 20:43:04 +0200 Subject: [PATCH 07/13] added closing punctuation so pre commit hooks doesn't complain --- .../scripts/velocity_controller_lqr_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py index 2ff358e00..b73567f2f 100644 --- a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py +++ b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py @@ -208,7 +208,7 @@ def get_parameters(self) -> None: self.lqr_params.dt = self.get_parameter("LQR_params.dt").value def get_and_reshape_inertia_matrix(self) -> None: - """Gets the inertia matrix from config and reshapes it to proper np array""" + """Gets the inertia matrix from config and reshapes it to proper np array.""" self.declare_parameter("inertia_matrix") self.inertia_matrix = self.get_parameter("inertia_matrix").value inertia_matrix_reshaped = np.array(self.inertia_matrix).reshape((3, 3)) From 3f544906cfd1426ca693676f701bc7de88ac3c01 Mon Sep 17 00:00:00 2001 From: Cyprian Osinski Date: Sun, 21 Sep 2025 12:25:45 +0200 Subject: [PATCH 08/13] : changed some ownership stuff in the controller, and actually fixed reliable/best effort QOS --- .../scripts/velocity_controller_lqr_node.py | 29 +++++++++++-------- .../velocity_controller_lqr_lib.py | 5 +--- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py index b73567f2f..62d881bd8 100644 --- a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py +++ b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py @@ -42,13 +42,19 @@ def __init__(self): self.states = State() self.guidance_values = GuidanceValues() self.lqr_params = LQRParameters() - # ------------------------ INITIALIZE TO NONE ----------------------- + self.dt = None + # --------------------------- SUBSCRIBERS -------------------------- self.pose_subscriber = None self.twist_subscriber = None self.operationmode_subscriber = None self.killswitch_subscriber = None self.guidance_subscriber = None + # ------------------------ CONTROLLER MODES ------------------------ + self.killswitch = None + self.operation_mode = None + # --------------------------- PUBLISHERS --------------------------- self.publisherLQR = None + # ----------------------------- TIMERS ----------------------------- self.control_timer = None # ------------------ ROS2 PARAMETERS AND CONTROLLER ------------------ @@ -85,20 +91,20 @@ def on_configure(self, previous_state: LifecycleState) -> TransitionCallbackRetu String, softwareoperation_topic, self.operation_callback, - qos_profile=self.best_effort_qos, + qos_profile=self.reliable_qos, ) self.killswitch_subscriber = self.create_subscription( Bool, killswitch_topic, self.killswitch_callback, - qos_profile=self.best_effort_qos, + qos_profile=self.reliable_qos, ) self.guidance_subscriber = self.create_subscription( LOSGuidance, guidance_topic, self.guidance_callback, - qos_profile=self.reliable_qos, + qos_profile=self.best_effort_qos, ) # ---------------------------- PUBLISHERS ---------------------------- @@ -107,7 +113,7 @@ def on_configure(self, previous_state: LifecycleState) -> TransitionCallbackRetu ) # ------------------------------ TIMERS ------------------------------ - dt = self.lqr_params.dt + dt = self.dt self.control_timer = self.create_timer(dt, self.control_loop) self.control_timer.cancel() @@ -187,7 +193,6 @@ def get_parameters(self) -> None: self.declare_parameter("LQR_params.i_weight") self.declare_parameter("LQR_params.dt") - self.declare_parameter("max_force") self.lqr_params.q_surge = self.get_parameter("LQR_params.q_surge").value @@ -205,7 +210,7 @@ def get_parameters(self) -> None: self.lqr_params.i_weight = self.get_parameter("LQR_params.i_weight").value self.lqr_params.max_force = self.get_parameter("max_force").value - self.lqr_params.dt = self.get_parameter("LQR_params.dt").value + self.dt = self.get_parameter("LQR_params.dt").value def get_and_reshape_inertia_matrix(self) -> None: """Gets the inertia matrix from config and reshapes it to proper np array.""" @@ -236,7 +241,7 @@ def operation_callback(self, msg: String) -> None: Parameters: String: msg: The operation mode data from the AUV. """ - self.controller.operation_mode = msg.data + self.operation_mode = msg.data def twist_callback(self, msg: TwistWithCovarianceStamped) -> None: """Callback function for the Twist data from DVL. @@ -271,17 +276,17 @@ def killswitch_callback(self, msg: Bool) -> None: """ if msg.data == True: self.controller.reset_controller() - self.controller.killswitch = True + self.killswitch = True else: - self.controller.killswitch = False + self.killswitch = False # ---------------------------------------------------------------PUBLISHER FUNCTIONS------------------------------------------------------------- def control_loop(self) -> None: """The control loop that calculates the input for the LQR controller.""" if ( - self.controller.killswitch == True - or self.controller.operation_mode != "autonomous mode" + self.killswitch == True + or self.operation_mode != "autonomous mode" ): self.controller.reset_controller() return diff --git a/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py b/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py index bbd37a313..97b1299dd 100644 --- a/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py +++ b/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py @@ -76,9 +76,6 @@ class LQRParameters: i_yaw: float = 0.0 i_weight: float = 0.0 max_force: float = 0.0 - operation_mode: str = "xbox mode" - killswitch: bool = True - dt: float = 0.1 class LQRController: @@ -224,8 +221,8 @@ def set_params(self, parameters: LQRParameters) -> None: self.i_pitch = parameters.i_pitch self.i_yaw = parameters.i_yaw self.i_weight = parameters.i_weight + self.max_force = parameters.max_force - self.operation_mode = parameters.operation_mode self.killswitch = parameters.killswitch self.dt = parameters.dt From d44726539f7289dba58580f5ade3db5f0b357f13 Mon Sep 17 00:00:00 2001 From: Cyprian Osinski Date: Sun, 21 Sep 2025 12:44:27 +0200 Subject: [PATCH 09/13] split declare and get functions, removed failing lqr params stuff --- .../scripts/velocity_controller_lqr_node.py | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py index 62d881bd8..cbdb475cb 100644 --- a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py +++ b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py @@ -1,22 +1,15 @@ #!/usr/bin/env python3 import numpy as np import rclpy -from geometry_msgs.msg import ( - PoseWithCovarianceStamped, - TwistWithCovarianceStamped, - Wrench, -) +from geometry_msgs.msg import (PoseWithCovarianceStamped, + TwistWithCovarianceStamped, Wrench) from rclpy.executors import MultiThreadedExecutor from rclpy.lifecycle import LifecycleNode from rclpy.lifecycle.node import LifecycleState, TransitionCallbackReturn from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy from std_msgs.msg import Bool, String from velocity_controller_lqr.velocity_controller_lqr_lib import ( - GuidanceValues, - LQRController, - LQRParameters, - State, -) + GuidanceValues, LQRController, LQRParameters, State) from vortex_msgs.msg import LOSGuidance @@ -33,7 +26,7 @@ def __init__(self): self.reliable_qos = QoSProfile( reliability=ReliabilityPolicy.RELIABLE, history=HistoryPolicy.KEEP_LAST, - depth=10, + depth=2, ) # ---------------- CALLBACK VARIABLES INITIALIZATION ---------------- @@ -62,6 +55,7 @@ def __init__(self): self.controller = LQRController(self.lqr_params, self.inertia_matrix) def on_configure(self, previous_state: LifecycleState) -> TransitionCallbackReturn: + self.declare_parameters() self.get_parameters() # -------------------------- GET ALL TOPICS ------------------------- ( @@ -176,8 +170,8 @@ def get_topics(self) -> None: killswitch_topic, ) - def get_parameters(self) -> None: - """Updates the LQR_params in the LQR_parameters Dataclass.""" + def declare_parameters(self) -> None: + """Declares parameters that are to be used from the configuration file.""" self.declare_parameter("LQR_params.q_surge") self.declare_parameter("LQR_params.q_pitch") self.declare_parameter("LQR_params.q_yaw") @@ -194,6 +188,9 @@ def get_parameters(self) -> None: self.declare_parameter("LQR_params.dt") self.declare_parameter("max_force") + + def get_parameters(self) -> None: + """Gets the declared parameters from the configuration file.""" self.lqr_params.q_surge = self.get_parameter("LQR_params.q_surge").value self.lqr_params.q_pitch = self.get_parameter("LQR_params.q_pitch").value From f468c421c6abf8e732716443986fd826b57f2d21 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 21 Sep 2025 10:44:39 +0000 Subject: [PATCH 10/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../scripts/velocity_controller_lqr_node.py | 21 +++++++++++-------- .../velocity_controller_lqr_lib.py | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py index cbdb475cb..de085ac5f 100644 --- a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py +++ b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py @@ -1,15 +1,22 @@ #!/usr/bin/env python3 import numpy as np import rclpy -from geometry_msgs.msg import (PoseWithCovarianceStamped, - TwistWithCovarianceStamped, Wrench) +from geometry_msgs.msg import ( + PoseWithCovarianceStamped, + TwistWithCovarianceStamped, + Wrench, +) from rclpy.executors import MultiThreadedExecutor from rclpy.lifecycle import LifecycleNode from rclpy.lifecycle.node import LifecycleState, TransitionCallbackReturn from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy from std_msgs.msg import Bool, String from velocity_controller_lqr.velocity_controller_lqr_lib import ( - GuidanceValues, LQRController, LQRParameters, State) + GuidanceValues, + LQRController, + LQRParameters, + State, +) from vortex_msgs.msg import LOSGuidance @@ -188,10 +195,9 @@ def declare_parameters(self) -> None: self.declare_parameter("LQR_params.dt") self.declare_parameter("max_force") - + def get_parameters(self) -> None: """Gets the declared parameters from the configuration file.""" - self.lqr_params.q_surge = self.get_parameter("LQR_params.q_surge").value self.lqr_params.q_pitch = self.get_parameter("LQR_params.q_pitch").value self.lqr_params.q_yaw = self.get_parameter("LQR_params.q_yaw").value @@ -281,10 +287,7 @@ def killswitch_callback(self, msg: Bool) -> None: def control_loop(self) -> None: """The control loop that calculates the input for the LQR controller.""" - if ( - self.killswitch == True - or self.operation_mode != "autonomous mode" - ): + if self.killswitch == True or self.operation_mode != "autonomous mode": self.controller.reset_controller() return diff --git a/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py b/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py index 97b1299dd..09f008e51 100644 --- a/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py +++ b/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py @@ -221,7 +221,7 @@ def set_params(self, parameters: LQRParameters) -> None: self.i_pitch = parameters.i_pitch self.i_yaw = parameters.i_yaw self.i_weight = parameters.i_weight - + self.max_force = parameters.max_force self.operation_mode = parameters.operation_mode self.killswitch = parameters.killswitch From bd324988266f0eb9b2152a44378338130556880b Mon Sep 17 00:00:00 2001 From: Cyprian Osinski Date: Sun, 21 Sep 2025 12:58:19 +0200 Subject: [PATCH 11/13] what did it cost? everything. --- .../velocity_controller_lqr/velocity_controller_lqr_lib.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py b/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py index 09f008e51..2f845bc50 100644 --- a/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py +++ b/control/velocity_controller_lqr/velocity_controller_lqr/velocity_controller_lqr_lib.py @@ -223,9 +223,6 @@ def set_params(self, parameters: LQRParameters) -> None: self.i_weight = parameters.i_weight self.max_force = parameters.max_force - self.operation_mode = parameters.operation_mode - self.killswitch = parameters.killswitch - self.dt = parameters.dt def set_matrices(self, inertia_matrix: np.array) -> None: """Adjusts the matrices for the LQR controller. From efce34f2bfedcb0a4a63b17a81e50e0895c87c26 Mon Sep 17 00:00:00 2001 From: Cyprian Osinski Date: Wed, 1 Oct 2025 22:36:47 +0200 Subject: [PATCH 12/13] made node pull topics from centralized auv_setup param file, velocity controller builds but does not run. Need to investigate --- control/velocity_controller_lqr/README.md | 14 +- .../config/param_velocity_controller_lqr.yaml | 21 ++- .../launch/velocity_controller_lqr.launch.py | 11 +- control/velocity_controller_lqr/package.xml | 2 +- .../velocity_controller_lqr/requirements.txt | 2 +- .../scripts/velocity_controller_lqr_node.py | 131 +++++++++--------- 6 files changed, 94 insertions(+), 87 deletions(-) diff --git a/control/velocity_controller_lqr/README.md b/control/velocity_controller_lqr/README.md index 27365f5f7..8e8788928 100644 --- a/control/velocity_controller_lqr/README.md +++ b/control/velocity_controller_lqr/README.md @@ -3,21 +3,23 @@ Contains the LQR controller package for the AUV Orca. The controller utilizes an LQR optimal controller (imported from the python control library), and controls pitch, yaw and surge. The controller is meant to traverse larger distances. #### Tuning of Parameters: - To tune parameters look at the config/param_velocity_controller_lqr.yaml file: + To tune parameters look at the [config file](https://github.com/vortexntnu/vortex-auv/blob/velocity-controller-to-lifecycle/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml) file: #### Launching the package: +1. Navigate to your ros2ws directory then (change your own path after cd or skip first line entirely), ```bash -1. inside ros2ws/colcon build --packages-select velocity_controller_lqr +cd ~/ros2ws/ && +colcon build --packages-select velocity_controller_lqr && +source install/setup.bash ``` +2. Then simply run the node via the launch file ```bash -2. Inisde ros2ws/: source install/setup.bash + ros2 launch velocity_controller_lqr velocity_controller_lqr.launch.py ``` -```bash -3. ros2 launch velocity_controller_lqr velocity_controller_lqr.launch.py -``` +3. You will notice that not much is currently happening, however that is because this is a lifecycle node and you can now transition between the node states through the terminal. #### Transitioning between states manually: The ROS2 node is implemented using lifecycle nodes, which are managed externally by a lifecycle manager i.e a finite state machine. If you want to manually test the node do the following: diff --git a/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml b/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml index 611c285b8..6b1f6503d 100644 --- a/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml +++ b/control/velocity_controller_lqr/config/param_velocity_controller_lqr.yaml @@ -1,14 +1,14 @@ /**: ros__parameters: - topics: - odom_topic: /orca/odom - twist_topic: /dvl/twist - pose_topic: /dvl/pose - guidance_topic: /guidance/los - thrust_topic: /thrust/wrench_input - softwareoperation_topic: /softwareOperationMode - killswitch_topic: /softwareKillSwitch + # topics: + # odom_topic: /orca/odom + # twist_topic: /dvl/twist + # pose_topic: /dvl/pose + # guidance_topic: /guidance/los + # thrust_topic: /thrust/wrench_input + # softwareoperation_topic: /softwareOperationMode + # killswitch_topic: /softwareKillSwitch LQR_params: q_surge: 75 @@ -25,9 +25,6 @@ i_weight: 0.5 - dt: 0.1 + dt: 0.1 inertia_matrix: [30.0, 0.6, 0.0, 0.6, 1.629, 0.0, 0.0, 0.0, 1.729] - - #Clamp parameter - max_force: 99.5 diff --git a/control/velocity_controller_lqr/launch/velocity_controller_lqr.launch.py b/control/velocity_controller_lqr/launch/velocity_controller_lqr.launch.py index 5f4e9e38c..4826c240b 100644 --- a/control/velocity_controller_lqr/launch/velocity_controller_lqr.launch.py +++ b/control/velocity_controller_lqr/launch/velocity_controller_lqr.launch.py @@ -21,14 +21,21 @@ def generate_launch_description() -> LaunchDescription: "config", "param_velocity_controller_lqr.yaml", ) + + topic_file = os.path.join( + get_package_share_directory("auv_setup"), + "config", + "robots", + "orca.yaml", + ) velocity_controller_node = LifecycleNode( package="velocity_controller_lqr", executable="velocity_controller_lqr_node.py", name="velocity_controller_lqr_node", - namespace="", + namespace="orca", output="screen", - parameters=[parameter_file], + parameters=[parameter_file, topic_file], ) return LaunchDescription([velocity_controller_node]) diff --git a/control/velocity_controller_lqr/package.xml b/control/velocity_controller_lqr/package.xml index a27ed4771..b60804d44 100644 --- a/control/velocity_controller_lqr/package.xml +++ b/control/velocity_controller_lqr/package.xml @@ -2,7 +2,7 @@ velocity_controller_lqr - 1.0.0 + 0.6.9 Velocity controller package for the AUV Orca cyprian MIT diff --git a/control/velocity_controller_lqr/requirements.txt b/control/velocity_controller_lqr/requirements.txt index 2c2b91d3e..904eea556 100644 --- a/control/velocity_controller_lqr/requirements.txt +++ b/control/velocity_controller_lqr/requirements.txt @@ -1 +1 @@ -numpy<1.25.0 +numpy<1.25.0 \ No newline at end of file diff --git a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py index de085ac5f..ecd5a22cf 100644 --- a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py +++ b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py @@ -1,22 +1,15 @@ #!/usr/bin/env python3 import numpy as np import rclpy -from geometry_msgs.msg import ( - PoseWithCovarianceStamped, - TwistWithCovarianceStamped, - Wrench, -) +from geometry_msgs.msg import (PoseWithCovarianceStamped, + TwistWithCovarianceStamped, WrenchStamped) from rclpy.executors import MultiThreadedExecutor from rclpy.lifecycle import LifecycleNode from rclpy.lifecycle.node import LifecycleState, TransitionCallbackReturn from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy from std_msgs.msg import Bool, String from velocity_controller_lqr.velocity_controller_lqr_lib import ( - GuidanceValues, - LQRController, - LQRParameters, - State, -) + GuidanceValues, LQRController, LQRParameters, State) from vortex_msgs.msg import LOSGuidance @@ -46,7 +39,7 @@ def __init__(self): # --------------------------- SUBSCRIBERS -------------------------- self.pose_subscriber = None self.twist_subscriber = None - self.operationmode_subscriber = None + self.operation_mode_subscriber = None self.killswitch_subscriber = None self.guidance_subscriber = None # ------------------------ CONTROLLER MODES ------------------------ @@ -56,21 +49,22 @@ def __init__(self): self.publisherLQR = None # ----------------------------- TIMERS ----------------------------- self.control_timer = None - # ------------------ ROS2 PARAMETERS AND CONTROLLER ------------------ self.get_and_reshape_inertia_matrix() self.controller = LQRController(self.lqr_params, self.inertia_matrix) def on_configure(self, previous_state: LifecycleState) -> TransitionCallbackReturn: - self.declare_parameters() - self.get_parameters() + self.declare_params() + self.get_logger().info("1") + self.get_params() + self.get_logger().info("2") # -------------------------- GET ALL TOPICS ------------------------- ( pose_topic, twist_topic, guidance_topic, thrust_topic, - softwareoperation_topic, + software_operation_topic, killswitch_topic, ) = self.get_topics() @@ -88,9 +82,9 @@ def on_configure(self, previous_state: LifecycleState) -> TransitionCallbackRetu qos_profile=self.best_effort_qos, ) - self.operationmode_subscriber = self.create_subscription( + self.operation_mode_subscriber = self.create_subscription( String, - softwareoperation_topic, + software_operation_topic, self.operation_callback, qos_profile=self.reliable_qos, ) @@ -110,7 +104,7 @@ def on_configure(self, previous_state: LifecycleState) -> TransitionCallbackRetu # ---------------------------- PUBLISHERS ---------------------------- self.publisherLQR = self.create_lifecycle_publisher( - Wrench, thrust_topic, self.reliable_qos + WrenchStamped, thrust_topic, self.reliable_qos ) # ------------------------------ TIMERS ------------------------------ @@ -128,7 +122,7 @@ def on_activate(self, previous_state: LifecycleState) -> TransitionCallbackRetur def on_deactivate(self, previous_state: LifecycleState) -> TransitionCallbackReturn: self.control_timer.cancel() self.controller.reset_controller() - return super().on_activate(previous_state) + return super().on_deactivate(previous_state) def on_cleanup(self, previous_state: LifecycleState) -> TransitionCallbackReturn: self.destroy_publisher(self.publisherLQR) @@ -144,59 +138,65 @@ def get_topics(self) -> None: """Get the topics from the parameter server. Returns: - odom_topic: str: The topic for accessing the odometry data from the parameter file - twist_topic: str: The topic for accessing the twist data from the parameter file pose_topic: str: The topic for accessing the pose data from the parameter file + twist_topic: str: The topic for accessing the twist data from the parameter file guidance_topic: str: The topic for accessing the guidance data the parameter file thrust_topic: str: The topic for accessing the thrust data from the parameter file + software_operation_mode_topic: str: The topic for accessing the operation mode from the parameter file + killswitch_topic: str: The topic for accessing the killswitch bool from the parameter file + """ - self.declare_parameter("topics.pose_topic", "/dvl/pose") - self.declare_parameter("topics.twist_topic", "/dvl/twist") - self.declare_parameter("topics.guidance_topic", "/guidance/los") - self.declare_parameter("topics.thrust_topic", "/thrust/wrench_input") - self.declare_parameter( - "topics.softwareoperation_topic", "/softwareOperationMode" - ) - self.declare_parameter("topics.killswitch_topic", "/softwareKillSwitch") - - pose_topic = self.get_parameter("topics.pose_topic").value - twist_topic = self.get_parameter("topics.twist_topic").value - guidance_topic = self.get_parameter("topics.guidance_topic").value - thrust_topic = self.get_parameter("topics.thrust_topic").value - softwareoperation_topic = self.get_parameter( - "topics.softwareoperation_topic" - ).value - killswitch_topic = self.get_parameter("topics.killswitch_topic").value + self.declare_parameter("topics.pose", "_") + self.declare_parameter("topics.twist", "_") + self.declare_parameter("topics.guidance.los", "_") + self.declare_parameter("topics.wrench_input", "_") + self.declare_parameter("topics.operation_mode", "_") + self.declare_parameter("topics.killswitch", "_") + + pose_topic = self.get_parameter("topics.pose").value + twist_topic = self.get_parameter("topics.twist").value + guidance_topic = self.get_parameter("topics.guidance.los").value + thrust_topic = self.get_parameter("topics.wrench_input").value + operation_topic = self.get_parameter("topics.operation_mode").value + killswitch_topic = self.get_parameter("topics.killswitch").value return ( pose_topic, twist_topic, guidance_topic, thrust_topic, - softwareoperation_topic, + operation_topic, killswitch_topic, ) - def declare_parameters(self) -> None: + def declare_params(self) -> None: """Declares parameters that are to be used from the configuration file.""" - self.declare_parameter("LQR_params.q_surge") - self.declare_parameter("LQR_params.q_pitch") - self.declare_parameter("LQR_params.q_yaw") - - self.declare_parameter("LQR_params.r_surge") - self.declare_parameter("LQR_params.r_pitch") - self.declare_parameter("LQR_params.r_yaw") - - self.declare_parameter("LQR_params.i_surge") - self.declare_parameter("LQR_params.i_pitch") - self.declare_parameter("LQR_params.i_yaw") - - self.declare_parameter("LQR_params.i_weight") - - self.declare_parameter("LQR_params.dt") - self.declare_parameter("max_force") - - def get_parameters(self) -> None: + self.get_logger().info("cool") + + self.declare_parameter("LQR_params.q_surge", 0.0) + self.declare_parameter("LQR_params.q_pitch", 0.0) + self.declare_parameter("LQR_params.q_yaw", 0.0) + + self.get_logger().info("yaw") + + self.declare_parameter("LQR_params.r_surge", 0.0) + self.declare_parameter("LQR_params.r_pitch", 0.0) + self.declare_parameter("LQR_params.r_yaw", 0.0) + + self.get_logger().info("shaw") + + self.declare_parameter("LQR_params.i_surge", 0.0) + self.declare_parameter("LQR_params.i_pitch", 0.0) + self.declare_parameter("LQR_params.i_yaw", 0.0) + + self.get_logger().info("garhamat") + + self.declare_parameter("LQR_params.i_weight", 0.0) + + self.declare_parameter("dt", 0.0) + self.declare_parameter("propulsion.thrusters.max", 0.0) + + def get_params(self) -> None: """Gets the declared parameters from the configuration file.""" self.lqr_params.q_surge = self.get_parameter("LQR_params.q_surge").value self.lqr_params.q_pitch = self.get_parameter("LQR_params.q_pitch").value @@ -211,9 +211,9 @@ def get_parameters(self) -> None: self.lqr_params.i_yaw = self.get_parameter("LQR_params.i_yaw").value self.lqr_params.i_weight = self.get_parameter("LQR_params.i_weight").value - self.lqr_params.max_force = self.get_parameter("max_force").value + self.lqr_params.max_force = self.get_parameter("propulsion.thrusters.max").value - self.dt = self.get_parameter("LQR_params.dt").value + self.dt = self.get_parameter("dt").value def get_and_reshape_inertia_matrix(self) -> None: """Gets the inertia matrix from config and reshapes it to proper np array.""" @@ -291,14 +291,15 @@ def control_loop(self) -> None: self.controller.reset_controller() return - msg = Wrench() + msg = WrenchStamped() u = self.controller.calculate_lqr_u( - self.coriolis_matrix, self.states, self.guidance_values + self.coriolis_matrix, self.states, self.guidance_values ) - msg.force.x = float(u[0]) - msg.torque.y = float(u[1]) - msg.torque.z = float(u[2]) + + msg.wrench.force.x = float(u[0]) + msg.wrench.torque.y = float(u[1]) + msg.wrench.torque.z = float(u[2]) self.publisherLQR.publish(msg) From 782f78e475f6943dace27017531f53939669356f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 20:40:10 +0000 Subject: [PATCH 13/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../launch/velocity_controller_lqr.launch.py | 2 +- .../velocity_controller_lqr/requirements.txt | 2 +- .../scripts/velocity_controller_lqr_node.py | 27 ++++++++++++------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/control/velocity_controller_lqr/launch/velocity_controller_lqr.launch.py b/control/velocity_controller_lqr/launch/velocity_controller_lqr.launch.py index 4826c240b..cda6e32b3 100644 --- a/control/velocity_controller_lqr/launch/velocity_controller_lqr.launch.py +++ b/control/velocity_controller_lqr/launch/velocity_controller_lqr.launch.py @@ -21,7 +21,7 @@ def generate_launch_description() -> LaunchDescription: "config", "param_velocity_controller_lqr.yaml", ) - + topic_file = os.path.join( get_package_share_directory("auv_setup"), "config", diff --git a/control/velocity_controller_lqr/requirements.txt b/control/velocity_controller_lqr/requirements.txt index 904eea556..2c2b91d3e 100644 --- a/control/velocity_controller_lqr/requirements.txt +++ b/control/velocity_controller_lqr/requirements.txt @@ -1 +1 @@ -numpy<1.25.0 \ No newline at end of file +numpy<1.25.0 diff --git a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py index ecd5a22cf..fdbeac82a 100644 --- a/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py +++ b/control/velocity_controller_lqr/scripts/velocity_controller_lqr_node.py @@ -1,15 +1,22 @@ #!/usr/bin/env python3 import numpy as np import rclpy -from geometry_msgs.msg import (PoseWithCovarianceStamped, - TwistWithCovarianceStamped, WrenchStamped) +from geometry_msgs.msg import ( + PoseWithCovarianceStamped, + TwistWithCovarianceStamped, + WrenchStamped, +) from rclpy.executors import MultiThreadedExecutor from rclpy.lifecycle import LifecycleNode from rclpy.lifecycle.node import LifecycleState, TransitionCallbackReturn from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy from std_msgs.msg import Bool, String from velocity_controller_lqr.velocity_controller_lqr_lib import ( - GuidanceValues, LQRController, LQRParameters, State) + GuidanceValues, + LQRController, + LQRParameters, + State, +) from vortex_msgs.msg import LOSGuidance @@ -144,7 +151,7 @@ def get_topics(self) -> None: thrust_topic: str: The topic for accessing the thrust data from the parameter file software_operation_mode_topic: str: The topic for accessing the operation mode from the parameter file killswitch_topic: str: The topic for accessing the killswitch bool from the parameter file - + """ self.declare_parameter("topics.pose", "_") self.declare_parameter("topics.twist", "_") @@ -172,23 +179,23 @@ def get_topics(self) -> None: def declare_params(self) -> None: """Declares parameters that are to be used from the configuration file.""" self.get_logger().info("cool") - + self.declare_parameter("LQR_params.q_surge", 0.0) self.declare_parameter("LQR_params.q_pitch", 0.0) self.declare_parameter("LQR_params.q_yaw", 0.0) - + self.get_logger().info("yaw") self.declare_parameter("LQR_params.r_surge", 0.0) self.declare_parameter("LQR_params.r_pitch", 0.0) self.declare_parameter("LQR_params.r_yaw", 0.0) - + self.get_logger().info("shaw") self.declare_parameter("LQR_params.i_surge", 0.0) self.declare_parameter("LQR_params.i_pitch", 0.0) self.declare_parameter("LQR_params.i_yaw", 0.0) - + self.get_logger().info("garhamat") self.declare_parameter("LQR_params.i_weight", 0.0) @@ -294,9 +301,9 @@ def control_loop(self) -> None: msg = WrenchStamped() u = self.controller.calculate_lqr_u( - self.coriolis_matrix, self.states, self.guidance_values + self.coriolis_matrix, self.states, self.guidance_values ) - + msg.wrench.force.x = float(u[0]) msg.wrench.torque.y = float(u[1]) msg.wrench.torque.z = float(u[2])