Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c3e54ca
feat: converted velocity controller lqr to lifecycle node
Q3rkses Sep 20, 2025
0ea41ab
Changed QOS to match killswitch and refactored some functions to be m…
Q3rkses Sep 20, 2025
3241741
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 20, 2025
8d16883
precommit hooks and explicit return values
Q3rkses Sep 20, 2025
c7c72d5
refactored to if guard for easy exit and so precommit hook gets off m…
Q3rkses Sep 20, 2025
90d7488
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 20, 2025
0fc0b83
added closing punctuation so pre commit hooks doesn't complain
Q3rkses Sep 20, 2025
3f54490
<feat>: changed some ownership stuff in the controller, and actually …
Q3rkses Sep 21, 2025
d447265
split declare and get functions, removed failing lqr params stuff
Q3rkses Sep 21, 2025
f468c42
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 21, 2025
bd32498
what did it cost? everything.
Q3rkses Sep 21, 2025
5cedb95
Merge branch 'main' into velocity-controller-to-lifecycle
Andeshog Sep 23, 2025
0859536
Merge branch 'main' into velocity-controller-to-lifecycle
Andeshog Sep 23, 2025
a5b2185
Merge branch 'main' into velocity-controller-to-lifecycle
Q3rkses Oct 1, 2025
efce34f
made node pull topics from centralized auv_setup param file, velocity…
Q3rkses Oct 1, 2025
7e47a5b
Merge branch 'velocity-controller-to-lifecycle' of github.com:vortexn…
Q3rkses Oct 1, 2025
782f78e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 1, 2025
d93fe58
Merge branch 'main' into velocity-controller-to-lifecycle
Andeshog Oct 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions control/velocity_controller_lqr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,20 @@ 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 = 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])
2 changes: 1 addition & 1 deletion control/velocity_controller_lqr/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>velocity_controller_lqr</name>
<version>1.0.0</version>
<version>0.6.9</version>
<description>Velocity controller package for the AUV Orca</description>
<maintainer email="cyprian.github@gmail.com">cyprian</maintainer>
<license>MIT</license>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from geometry_msgs.msg import (
PoseWithCovarianceStamped,
TwistWithCovarianceStamped,
Wrench,
WrenchStamped,
)
from rclpy.executors import MultiThreadedExecutor
from rclpy.lifecycle import LifecycleNode
Expand Down Expand Up @@ -46,7 +46,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 ------------------------
Expand All @@ -56,21 +56,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")
Comment on lines +64 to +67
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make more sense to declare the parameters in the constructor (since it only has to happen once) and use the configure callback to get them?

# -------------------------- GET ALL TOPICS -------------------------
(
pose_topic,
twist_topic,
guidance_topic,
thrust_topic,
softwareoperation_topic,
software_operation_topic,
killswitch_topic,
) = self.get_topics()

Expand All @@ -88,9 +89,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,
)
Expand All @@ -110,7 +111,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 ------------------------------
Expand All @@ -128,7 +129,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)
Expand All @@ -144,59 +145,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
"""
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")
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

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.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)
Comment on lines +183 to +185
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need to be the same type as what they are in the config file. Currently they are Int. Suggest you stick to float, as all the others are also float.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same goes for propulsion.thrusters.max


self.get_logger().info("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.r_surge", 0.0)
self.declare_parameter("LQR_params.r_pitch", 0.0)
self.declare_parameter("LQR_params.r_yaw", 0.0)

self.declare_parameter("LQR_params.i_surge")
self.declare_parameter("LQR_params.i_pitch")
self.declare_parameter("LQR_params.i_yaw")
self.get_logger().info("shaw")

self.declare_parameter("LQR_params.i_weight")
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.declare_parameter("LQR_params.dt")
self.declare_parameter("max_force")
self.get_logger().info("garhamat")

def get_parameters(self) -> None:
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
Expand All @@ -211,9 +218,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."""
Expand Down Expand Up @@ -291,14 +298,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
)
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)

Expand Down
Loading