Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
38f1fdb
waypoint mode handling
jorgenfj Nov 24, 2025
f3fc24b
update tests with new message
jorgenfj Nov 24, 2025
d5bbaca
initial package setup
jorgenfj Nov 25, 2025
4ff98d5
ros interface function declaration
jorgenfj Nov 25, 2025
4c30faa
node setup
jorgenfj Nov 26, 2025
dae4fa3
working prototype
jorgenfj Nov 27, 2025
2b39764
reentrant cb, multithreaded
jorgenfj Nov 27, 2025
676c54f
single threaded impl
jorgenfj Nov 27, 2025
ebb220c
conv threshold action goal
jorgenfj Nov 29, 2025
490e307
default thresholdref conv value
jorgenfj Nov 29, 2025
900f2af
removed switching logic
jorgenfj Nov 29, 2025
7065c6a
removed timer execution
jorgenfj Nov 29, 2025
7209ce3
sim test utils
jorgenfj Nov 29, 2025
87ed751
waypoint_manager_test setup
jorgenfj Nov 29, 2025
1818953
no rendering test arg
jorgenfj Nov 29, 2025
8248353
waypoint tests, timeout error
jorgenfj Nov 29, 2025
3aaf6f9
test refactor
jorgenfj Nov 30, 2025
8784b2e
format
jorgenfj Dec 6, 2025
4d73ecb
rename utils package
jorgenfj Dec 6, 2025
76183bc
test suite and description
jorgenfj Dec 6, 2025
b82db78
first waypoint test
jorgenfj Dec 6, 2025
ba2fdff
removed unused function
jorgenfj Dec 6, 2025
b1089b8
renamed service field to priority. Added simple tests
jorgenfj Dec 7, 2025
692e5ec
waypoint manager readme
jorgenfj Dec 7, 2025
21c1d19
uniform attitude naming convention
jorgenfj Dec 9, 2025
56ce067
fix pr requests
jorgenfj Dec 9, 2025
8527941
update tests with new service fields
jorgenfj Dec 9, 2025
cdc97f7
four corner test
jorgenfj Dec 9, 2025
d1557c8
update util func name
jorgenfj Dec 10, 2025
99def81
update with new action def
jorgenfj Dec 10, 2025
a2f413a
removed failing build type
jorgenfj Dec 10, 2025
5826046
test dependencies
jorgenfj Dec 10, 2025
4169c98
ignore failing yasmin package
jorgenfj Dec 11, 2025
3afe906
remove __init__ files
jorgenfj Dec 11, 2025
63f8854
quat_to_euler in make_pose helper
jorgenfj Dec 15, 2025
80e8ce5
added __init__ file
jorgenfj Dec 17, 2025
c65d013
removed sim deps for test packages
jorgenfj Dec 17, 2025
76f49ee
added action shutdown handling
jorgenfj Dec 18, 2025
2d27647
removed waypoint manager set setup
jorgenfj Dec 18, 2025
c0e1185
added waypoint manager node tests
jorgenfj Dec 18, 2025
3a69dbb
waypoint manager 4 corner sim test
jorgenfj Dec 18, 2025
546f836
added missing launch testing test dependency
jorgenfj Dec 18, 2025
1887321
add sleep for topic discovery
jorgenfj Dec 18, 2025
b26adc7
fix action member field name
jorgenfj Dec 18, 2025
f1f181a
removed unnecessary if here
jorgenfj Dec 19, 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
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ class ReferenceFilterNode : public rclcpp::Node {

Eigen::Vector18d fill_reference_state();

Eigen::Vector6d fill_reference_goal(
const geometry_msgs::msg::PoseStamped& goal);
Eigen::Vector6d fill_reference_goal(const geometry_msgs::msg::Pose& goal);

Comment on lines +77 to +78
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is stamped removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The function did not use the header, so no need to pass more than what is required

Copy link
Contributor

Choose a reason for hiding this comment

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

vortex utils ros conversion here instead?

Eigen::Vector6d apply_mode_logic(const Eigen::Vector6d& r_in, uint8_t mode);

vortex_msgs::msg::ReferenceFilter fill_reference_msg();

Expand Down
67 changes: 58 additions & 9 deletions guidance/reference_filter_dp/src/reference_filter_ros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,12 @@ Eigen::Vector18d ReferenceFilterNode::fill_reference_state() {
}

Eigen::Vector6d ReferenceFilterNode::fill_reference_goal(
const geometry_msgs::msg::PoseStamped& goal) {
double x{goal.pose.position.x};
double y{goal.pose.position.y};
double z{goal.pose.position.z};
const geometry_msgs::msg::Pose& goal) {
double x{goal.position.x};
double y{goal.position.y};
double z{goal.position.z};

const auto& o = goal.pose.orientation;
const auto& o = goal.orientation;
Eigen::Quaterniond q(o.w, o.x, o.y, o.z);
Eigen::Vector3d euler_angles = vortex::utils::math::quat_to_euler(q);
double roll{euler_angles(0)};
Expand Down Expand Up @@ -231,6 +231,43 @@ vortex_msgs::msg::ReferenceFilter ReferenceFilterNode::fill_reference_msg() {
return feedback_msg;
}

Eigen::Vector6d ReferenceFilterNode::apply_mode_logic(
const Eigen::Vector6d& r_in,
uint8_t mode) {
Eigen::Vector6d r_out = r_in;

switch (mode) {
case vortex_msgs::msg::Waypoint::FULL_POSE:
break;

case vortex_msgs::msg::Waypoint::ONLY_POSITION:
r_out(3) = x_(3);
r_out(4) = x_(4);
r_out(5) = x_(5);
break;
Comment on lines +243 to +247
Copy link
Contributor

Choose a reason for hiding this comment

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

Why isn't logic like ONLY_POSITION and ONLY_ORIENTATION handled in the motion controller(s) instead? Also with this you risk locking the desired orientation in awkward ways, instead of just not controlling said DOFs. If the locking behavior (fixing the desired value of one or more DOF) is something that is desired, that could easily be handled in the controller(s) in the same logic segment

Copy link
Contributor

Choose a reason for hiding this comment

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

I do think its fine that the wp manager/guidance controls which mode and so on, but the controller(s) should probably also know what mode it is performing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The locking is desired for this use case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree that the mode logic could be handled in the controller, but this setup currently works as a MVP, and fits nicely within the scope of the action.
I feel the guidance and controller system could use a refactor with a "main orchestrator" or something, and this mode logic can be revamped then. The waypoint manager could potentially be incorporated into this "main orchestrator" and be extended to support LOS/velocity controller as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

locking the drone to a desired orientation could be something that the state machine requests and guidance system does i don't see the point of the (DP) controller differenciating between locking orientation and locking pose it is the same thing but simpler, albeit could be better tuned to some extent if we separate them.

The use case in this situation is more that we do not really want to change the position which would require the state machine to remember its last waypoint, recover the (x,y,z) coordinates and then send the same waypoint but with changed orientation which seemes tedious. Same goes for position only vice versa.

The thing that could be something to discuss is if we want the state machine to only communicate its desires with the orchistrator/manager node, otherwise we could make the state machine directly send requests to DP guidance about locking orientation.

Copy link
Member

@chrstrom chrstrom Dec 18, 2025

Choose a reason for hiding this comment

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

Possible to just let each dof be addressable (i.e. Eigen::Index, strongly typed) and let whatever thing thats managing task execution lock whatever axes as required? (could also alias "common" things like position only, orientation only, z only, etc, etc). That way you keep the controller "dumb" in the sense that it has no knowledge of "mode", just "which axes are currently constrained". (I don't know anything about the current dp code so correct me if this is already the case 🔥)


case vortex_msgs::msg::Waypoint::FORWARD_HEADING: {
double dx = r_in(0) - x_(0);
double dy = r_in(1) - x_(1);

double forward_yaw = std::atan2(dy, dx);
Copy link
Contributor

Choose a reason for hiding this comment

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

2 things:

  • No reason to use both heading and yaw in this block, choose one.
  • r_ and x_ are really poor names. Would be better off using something like reference_pose and current_drone_state. Would not be bad to integrate some of the new structs in vortex utils either, but that is probably out of scope for this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

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

'out of scope for this PR' my favorite words

Copy link
Contributor

Choose a reason for hiding this comment

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

Issue material 👨‍🦯

Copy link
Contributor Author

Choose a reason for hiding this comment

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


r_out(3) = 0.0;
r_out(4) = 0.0;
r_out(5) = vortex::utils::math::ssa(forward_yaw);
break;
}

case vortex_msgs::msg::Waypoint::ONLY_ORIENTATION:
r_out(0) = x_(0);
r_out(1) = x_(1);
r_out(2) = x_(2);
break;
}

return r_out;
}

void ReferenceFilterNode::execute(
const std::shared_ptr<rclcpp_action::ServerGoalHandle<
vortex_msgs::action::ReferenceFilterWaypoint>> goal_handle) {
Expand All @@ -243,9 +280,21 @@ void ReferenceFilterNode::execute(

x_ = fill_reference_state();

const geometry_msgs::msg::PoseStamped goal = goal_handle->get_goal()->goal;
const geometry_msgs::msg::Pose goal =
goal_handle->get_goal()->waypoint.pose;
uint8_t mode = goal_handle->get_goal()->waypoint.mode;
double convergence_threshold =
goal_handle->get_goal()->convergence_threshold;
Comment on lines +283 to +287
Copy link
Contributor

Choose a reason for hiding this comment

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

I would try to use auto here, and elsewhere where it makes sense. It avoids visual clutter and potential accidental implicit conversions. However, do not use auto with Eigen types (eigen common pitfalls)


if (convergence_threshold <= 0.0) {
convergence_threshold = 0.1;
spdlog::warn(
"ReferenceFilter: Invalid convergence_threshold received (<= 0). "
"Using default 0.1");
}

r_ = fill_reference_goal(goal);
Eigen::Vector6d r_temp = fill_reference_goal(goal);
r_ = apply_mode_logic(r_temp, mode);

auto feedback = std::make_shared<
vortex_msgs::action::ReferenceFilterWaypoint::Feedback>();
Expand Down Expand Up @@ -274,12 +323,12 @@ void ReferenceFilterNode::execute(

vortex_msgs::msg::ReferenceFilter feedback_msg = fill_reference_msg();

feedback->feedback = feedback_msg;
feedback->reference = feedback_msg;

goal_handle->publish_feedback(feedback);
reference_pub_->publish(feedback_msg);

if ((x_.head(6) - r_.head(6)).norm() < 0.1) {
if ((x_.head(6) - r_.head(6)).norm() < convergence_threshold) {
result->success = true;
goal_handle->succeed(result);
x_.head(6) = r_.head(6);
Expand Down
63 changes: 63 additions & 0 deletions mission/waypoint_manager/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.8)
project(waypoint_manager)

if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 20)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(rclcpp_components REQUIRED)
find_package(vortex_msgs REQUIRED)
find_package(tf2_geometry_msgs REQUIRED)
find_package(spdlog REQUIRED)
find_package(fmt REQUIRED)

include_directories(include)

set(LIB_NAME "${PROJECT_NAME}_component")

add_library(${LIB_NAME} SHARED
src/waypoint_manager_ros.cpp)

ament_target_dependencies(${LIB_NAME} PUBLIC
rclcpp
rclcpp_components
rclcpp_action
vortex_msgs
tf2_geometry_msgs
spdlog
fmt
)

rclcpp_components_register_node(
${LIB_NAME}
PLUGIN "WaypointManagerNode"
EXECUTABLE ${PROJECT_NAME}_node
)

ament_export_targets(export_${LIB_NAME})

install(TARGETS ${LIB_NAME}
EXPORT export_${LIB_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)

install(
DIRECTORY include/
DESTINATION include
)

install(DIRECTORY
launch
DESTINATION share/${PROJECT_NAME}/
)

ament_package()
84 changes: 84 additions & 0 deletions mission/waypoint_manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@


# Waypoint Manager — ROS 2 Node


The **Waypoint Manager** node coordinates mission-level navigation by managing waypoint queues, forwarding them to a **Reference Filter** for trajectory generation, and exposing both an **action interface** (for mission planners) and a **service interface** (for perception-driven waypoint updates).



# Interfaces

* **[WaypointManager](https://github.com/vortexntnu/vortex-msgs/blob/main/action/WaypointManager.action) (action server)**
* **[WaypointAddition](https://github.com/vortexntnu/vortex-msgs/blob/main/srv/WaypointAddition.srv) (service server)**
* **[ReferenceFilterWaypoint](https://github.com/vortexntnu/vortex-msgs/blob/main/action/ReferenceFilterWaypoint.action) (action client)**


# Behavior Summary

### **Mission Start**

* On receiving a new action goal, any existing mission is aborted.
* Waypoints are stored, state is reset, and the first waypoint is sent to the reference filter.

### **During Mission**

* Each waypoint is executed sequentially.
* Pose feedback from the reference filter is forwarded to the mission planner.
* `convergence_threshold` determines when a waypoint is reached.
* If `persistent = true`, the action does not end even when waypoints run out.

### **Mission End**

* If `persistent = false`, the action completes when the last waypoint is reached.
* If cancelled externally, all state is cleared and reference filter goals are cancelled.

### **Dynamic Waypoint Addition**

Allowed only during **persistent** missions:

* `overwrite = true` → clears old queue and restarts from new waypoints
* `overwrite = false` → appends waypoints to queue
* `priority = true` → turn on priority mode. In this mode additional service requests with `priority = false` are ignored. Priority mode is set to false again when there are no more waypoints in queue.

---

### Check available interfaces:

```bash
ros2 action list
ros2 service list
```
beware of namespace!


## Example Action Goal (CLI)

```bash
ros2 action send_goal /orca/waypoint_manager vortex_msgs/action/WaypointManager \
'{
waypoints:[
{
pose:{position:{x:5.0,y:0.0,z:0.0},
orientation:{x:0,y:0,z:0,w:1}},
mode:1
}
],
convergence_threshold:0.1,
persistent:false
}'
```


## Example Waypoint Addition Service Call

```bash
ros2 service call /orca/waypoint_addition vortex_msgs/srv/WaypointAddition \
'{
waypoints:[
{pose:{position:{x:2,y:3,z:0},orientation:{x:0,y:0,z:0,w:1}},mode:1}
],
overwrite:false,
priority:true
}'
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#ifndef WAYPOINT_MANAGER__WAYPOINT_MANAGER_ROS_HPP_
#define WAYPOINT_MANAGER__WAYPOINT_MANAGER_ROS_HPP_

#include <memory>
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>

#include <vector>
#include <vortex_msgs/action/reference_filter_waypoint.hpp>
#include <vortex_msgs/action/waypoint_manager.hpp>
#include <vortex_msgs/msg/waypoint.hpp>
#include <vortex_msgs/srv/waypoint_addition.hpp>

namespace vortex::mission {

using WaypointManager = vortex_msgs::action::WaypointManager;
using WaypointManagerGoalHandle =
rclcpp_action::ServerGoalHandle<WaypointManager>;

using ReferenceFilterAction = vortex_msgs::action::ReferenceFilterWaypoint;
using ReferenceFilterGoalHandle =
rclcpp_action::ClientGoalHandle<ReferenceFilterAction>;

class WaypointManagerNode : public rclcpp::Node {
public:
explicit WaypointManagerNode(
const rclcpp::NodeOptions& options = rclcpp::NodeOptions());

private:
// @brief Create the action server for WaypointManager.
void set_waypoint_action_server();

// @brief Create the action client for ReferenceFilterWaypoint.
void set_reference_action_client();

// @brief Create the service servers for WaypointAddition.
void set_waypoint_service_server();

// @brief Convert ReferenceFilter feedback to a Pose message
// @param fb The ReferenceFilter feedback message
// @return The corresponding Pose message
geometry_msgs::msg::Pose reference_to_pose(
const ReferenceFilterAction::Feedback& fb) const;

// @brief Construct the result message for the WaypointManager action
// @param success Whether the action was successful
// @return The constructed result message
std::shared_ptr<vortex_msgs::action::WaypointManager_Result>
construct_result(bool success) const;

// @brief Clean up the mission state after completion or cancellation of a
// waypoint action. Cancel active goals and reset internal variables. Make
// system ready for next action.
void cleanup_mission_state();

// @brief Send the next goal to the ReferenceFilter action server based on
// the current waypoint index or finish the waypoint action if all waypoints
// have been processed.
void send_next_reference_filter_goal();

// @brief Handle incoming action goal requests
// @param uuid The goal UUID
// @param goal The goal message
// @return The goal response
rclcpp_action::GoalResponse handle_waypoint_goal(
const rclcpp_action::GoalUUID& uuid,
std::shared_ptr<const vortex_msgs::action::WaypointManager::Goal>
goal_msg);

// @brief Handle requests to cancel the waypoint action
// @param goal_handle The goal handle
// @return The cancel response
rclcpp_action::CancelResponse handle_waypoint_cancel(
const std::shared_ptr<rclcpp_action::ServerGoalHandle<
vortex_msgs::action::WaypointManager>> goal_handle);

// @brief Handle the accepted goal request
// @param goal_handle The goal handle
void handle_waypoint_accepted(
const std::shared_ptr<rclcpp_action::ServerGoalHandle<
vortex_msgs::action::WaypointManager>> goal_handle);

// @brief Handle incoming waypoint addition service requests
// Only accepted if waypoint action is running.
// @param request Incoming service request containing waypoint information.
// @param response Service response that should be populated and sent back
// to the caller.
void handle_waypoint_addition_service_request(
const std::shared_ptr<vortex_msgs::srv::WaypointAddition::Request>
request,
std::shared_ptr<vortex_msgs::srv::WaypointAddition::Response> response);

// @brief Send a goal to the reference filter
// @param goal_msg The action goal
void send_reference_filter_goal(
const vortex_msgs::action::ReferenceFilterWaypoint::Goal& goal_msg);

rclcpp_action::Client<vortex_msgs::action::ReferenceFilterWaypoint>::
SharedPtr reference_filter_client_;
rclcpp_action::Server<vortex_msgs::action::WaypointManager>::SharedPtr
waypoint_action_server_;
rclcpp::Service<vortex_msgs::srv::WaypointAddition>::SharedPtr
waypoint_service_server_;

std::vector<vortex_msgs::msg::Waypoint> waypoints_{};
std::size_t current_index_{0};
double convergence_threshold_{0.1};

bool persistent_action_mode_{false};
bool priority_mode_{false};

ReferenceFilterAction::Feedback latest_ref_feedback_;
bool have_reference_pose_{false};
bool cancel_in_progress_{false};
Copy link
Contributor

Choose a reason for hiding this comment

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

persistent_action_mode_active_
priority_mode_active_
has_reference_pose_
is_cancel_in_progress_
perhaps?


std::uint64_t mission_id_ = 0;
Comment on lines +111 to +112
Copy link
Contributor

Choose a reason for hiding this comment

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

Cant wait to see mission number 18,446,744,073,709,551,615 😂


std::shared_ptr<ReferenceFilterGoalHandle> active_reference_filter_goal_;
std::shared_ptr<WaypointManagerGoalHandle> active_action_goal_;
};

} // namespace vortex::mission

#endif // WAYPOINT_MANAGER__WAYPOINT_MANAGER_ROS_HPP_
Loading
Loading