Skip to content

Commit 296e8d6

Browse files
authored
Tmp/reference filter quat (#688)
* update waypoint utils to quat * refernce filter quat implementation * RPY reference testing publisher * sim test for quat ref filter * update pkg maintainer :) * add reference quat sim test to workflow * helpers for step function * fix readme * ref_quat remove waypoint_utils and ref feedback * remove RPY debug publisher * pre-commit
1 parent 2d347c2 commit 296e8d6

19 files changed

Lines changed: 627 additions & 458 deletions

.github/workflows/simulator-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ jobs:
1919
"tests/simulator_tests/waypoint_navigation/simulator_test.sh",
2020
"tests/simulator_tests/los_test/simulator_test.sh",
2121
"tests/simulator_tests/waypoint_manager_test/simulator_test.sh",
22+
"tests/simulator_tests/waypoint_navigation_quat/simulator_test.sh",
2223
]'

guidance/reference_filter_dp_quat/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ set(CORE_LIB_NAME "${PROJECT_NAME}")
2828

2929
add_library(${CORE_LIB_NAME} SHARED
3030
src/lib/reference_filter.cpp
31-
src/lib/waypoint_utils.cpp
3231
src/lib/waypoint_follower.cpp
3332
)
3433

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
## Reference filter (quaternion)
2+
3+
### Third-order reference filter
4+
5+
The underlying filter is the third-order model (Fossen, 2021):
6+
```math
7+
\dot{x}_d = A_d x_d + B_d r
8+
```
9+
where
10+
```math
11+
A_d = \begin{bmatrix}
12+
0_{n\times n} & I_n & 0_{n\times n} \\
13+
0_{n\times n} & 0_{n \times n} & I_n \\
14+
-\Omega^3 & -(2 \Delta + I_n) \Omega^2 & -(2 \Delta + I_n) \Omega
15+
\end{bmatrix}
16+
```
17+
and
18+
```math
19+
B_d = \begin{bmatrix}
20+
0_{n \times n} \\
21+
0_{n \times n} \\
22+
\Omega^3
23+
\end{bmatrix}.
24+
```
25+
26+
The state is integrated using forward Euler: $x_{i+1} = x_i + \dot{x}_i \cdot dt$.
27+
28+
### Error-state formulation
29+
30+
This package avoids the singularity issues of Euler angles by using a quaternion-based error state.
31+
32+
**Nominal pose** — a `Pose` (position + quaternion) representing the current best estimate of the reference trajectory. This is the actual output.
33+
34+
**Error state** — the 18D filter state, where the first 6 elements are small errors relative to the nominal:
35+
```math
36+
x = \begin{bmatrix} \delta p \\ \delta \phi \\ \dot{\eta} \\ \ddot{\eta} \end{bmatrix} \in \mathbb{R}^{18}
37+
```
38+
39+
| Indices | Meaning |
40+
|---|---|
41+
| `x[0:3]` | Position error $\delta p$ from nominal (meters) |
42+
| `x[3:6]` | Orientation error $\delta \phi$ from nominal (rotation vector, radians) |
43+
| `x[6:9]` | World-frame linear velocity (m/s) |
44+
| `x[9:12]` | World-frame angular velocity (rad/s) |
45+
| `x[12:18]` | Accelerations (linear + angular) |
46+
47+
### Step cycle
48+
49+
Each time step performs three operations:
50+
51+
1. **Compute reference error** — The 6D reference $r$ is the error between the waypoint goal and the nominal pose:
52+
- $r_{0:3} = p_{goal} - p_{nominal}$
53+
- $r_{3:6} = q_{\mathrm{err}}(q_{nominal}, q_{goal})$
54+
55+
The `quaternion_error` function returns $2 \cdot \text{vec}(q_{nominal}^{-1} \otimes q_{goal})$, which for small angles approximates the rotation vector from nominal to goal.
56+
57+
2. **Integrate** — The standard filter step:
58+
```math
59+
\dot{x} = A_d x + B_d r, \quad x \leftarrow x + \dot{x} \cdot dt
60+
```
61+
62+
3. **Reset** — Absorb position and orientation errors into the nominal pose, then zero them:
63+
- $p_{nominal} \leftarrow p_{nominal} + \delta p, \quad \delta p \leftarrow 0$
64+
- $q_{nominal} \leftarrow q_{nominal} \otimes \exp(\delta \phi), \quad \delta \phi \leftarrow 0$
65+
66+
where $\exp(\delta \phi)$ converts the rotation vector to a quaternion via `AngleAxis`.
67+
68+
The reset step keeps $\delta p$ and $\delta \phi$ near zero at all times, ensuring the linearized quaternion error remains accurate. The velocity and acceleration states ($x_{6:18}$) are **not** reset — they carry over to provide smooth, continuous motion.
69+
70+
### Output message (`ReferenceFilterQuat`)
71+
72+
| Field | Source | Meaning |
73+
|---|---|---|
74+
| `x`, `y`, `z` | Nominal pose position | Desired position in world frame (m) |
75+
| `qw`, `qx`, `qy`, `qz` | Nominal pose quaternion | Desired orientation (unit quaternion) |
76+
| `x_dot`, `y_dot`, `z_dot` | `x[6:9]` | World-frame linear velocity (m/s) |
77+
| `roll_dot`, `pitch_dot`, `yaw_dot` | `x[9:12]` | World-frame angular velocity (rad/s) |
78+
79+
80+
81+
82+
### Waypoint modes
83+
84+
Each waypoint has a mode that determines which degrees of freedom are controlled by the reference filter. The mode also determines how convergence is measured.
85+
86+
| Mode | Controlled DOFs | Convergence metric |
87+
|---|---|---|
88+
| `FULL_POSE` | All 6 DOF (position + orientation) | Position error norm + quaternion error norm |
89+
| `ONLY_POSITION` | x, y, z (orientation holds current value) | Position error norm |
90+
| `FORWARD_HEADING` | x, y, z + yaw toward target | Position error norm + yaw component of quaternion error |
91+
| `ONLY_ORIENTATION` | Orientation only (position holds current value) | Quaternion error norm |
92+
93+
For all modes, convergence is reached when the error metric drops below the `convergence_threshold` specified in the action goal.
94+
95+
### Action Server
96+
97+
The action server handles goal requests and publishes guidance commands. The server always prioritizes new goal requests, aborting ongoing requests when a new one arrives.
98+
99+
- Action name: `/reference_filter`
100+
- Goal type: Waypoint (pose + mode + convergence threshold)
101+
- Result type: bool
102+
- Guidance topic: `/dp/reference`
103+
104+
### Overwriting the reference during an action
105+
106+
While an action is executing, the reference goal can be updated by publishing a `geometry_msgs/msg/PoseStamped` to the reference pose topic. The convergence check will use the updated reference.

guidance/reference_filter_dp_quat/include/reference_filter_dp_quat/lib/waypoint_follower.hpp

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,25 @@
33

44
#include <mutex>
55
#include <vortex/utils/types.hpp>
6+
#include <vortex/utils/waypoint_utils.hpp>
67
#include "reference_filter_dp_quat/lib/eigen_typedefs.hpp"
78
#include "reference_filter_dp_quat/lib/reference_filter.hpp"
8-
#include "reference_filter_dp_quat/lib/waypoint_types.hpp"
99

1010
namespace vortex::guidance {
1111

12-
using vortex::utils::types::PoseEuler;
12+
using vortex::utils::types::Pose;
1313
using vortex::utils::types::Twist;
14+
using vortex::utils::types::Waypoint;
15+
using vortex::utils::types::WaypointMode;
1416

1517
/**
1618
* @brief Manages reference filter state and waypoint following logic.
1719
*
20+
* Uses an error-state formulation: the 18D state vector holds
21+
* [δp(3), δφ(3), velocity(6), acceleration(6)], while a separate
22+
* nominal Pose tracks the full quaternion orientation. Each step,
23+
* position/orientation errors are absorbed into the nominal and reset.
24+
*
1825
* Thread-safe: all public methods acquire a mutex.
1926
*/
2027
class WaypointFollower {
@@ -28,52 +35,74 @@ class WaypointFollower {
2835
* @param waypoint Target waypoint with mode.
2936
* @param convergence_threshold Max error norm to consider target reached.
3037
*/
31-
void start(const PoseEuler& pose,
38+
void start(const Pose& pose,
3239
const Twist& twist,
3340
const Waypoint& waypoint,
3441
double convergence_threshold);
3542

3643
/**
3744
* @brief Advance the filter by one time step.
38-
* @return The updated filter state.
45+
*
46+
* Computes the error-state reference, integrates, then absorbs
47+
* position/orientation errors into the nominal pose.
3948
*/
40-
Eigen::Vector18d step();
49+
void step();
4150

4251
/**
43-
* @brief Check if the measured pose has converged to the reference goal.
52+
* @brief Check if the measured pose has converged to the waypoint goal.
4453
* @param measured_pose Current measured pose.
4554
* @return True if the error norm is within the convergence threshold.
4655
*/
47-
bool within_convergance(const Eigen::Vector6d& measured_pose) const;
56+
bool within_convergance(const Pose& measured_pose) const;
4857

4958
/**
5059
* @brief Update the reference goal pose mid-sequence.
5160
* @param reference_goal_pose The new reference pose.
5261
*/
53-
void set_reference(const PoseEuler& reference_goal_pose);
62+
void set_reference(const Pose& reference_goal_pose);
5463

5564
/**
56-
* @brief Snap the position component of the filter state to the reference.
65+
* @brief Snap the nominal pose to the waypoint goal and zero
66+
* errors/velocity.
5767
*
58-
* Useful after convergence to eliminate any remaining steady-state offset
68+
* Useful after convergence to eliminate any remaining steady-state offset.
5969
*/
6070
void snap_state_to_reference();
6171

62-
/// @brief Get the current 18D filter state.
63-
Eigen::Vector18d state() const;
72+
/**
73+
* @brief Get the current nominal pose (position + quaternion).
74+
*/
75+
Pose pose() const;
76+
77+
/**
78+
* @brief Get the current world-frame velocity (linear + angular).
79+
*/
80+
Eigen::Vector6d velocity() const;
6481

65-
/// @brief Get the current 6D reference goal pose.
66-
Eigen::Vector6d reference() const;
82+
/**
83+
* @brief Get the current waypoint goal.
84+
*/
85+
Pose waypoint_goal() const;
6786

6887
private:
69-
Eigen::Vector18d compute_initial_state(const PoseEuler& pose,
70-
const Twist& twist);
88+
/**
89+
* @brief Compute error-state reference based on the current nominal pose
90+
* and waypoint goal.
91+
*/
92+
Eigen::Vector6d update_reference() const;
93+
94+
/**
95+
* @brief Absorb position/orientation errors into the nominal pose and
96+
* reset the error states.
97+
*/
98+
void inject_and_reset();
7199

72100
mutable std::mutex mutex_;
73101
ReferenceFilter filter_;
74102
double dt_seconds_{0.01};
103+
Pose nominal_pose_;
75104
Eigen::Vector18d state_ = Eigen::Vector18d::Zero();
76-
Eigen::Vector6d reference_goal_ = Eigen::Vector6d::Zero();
105+
Pose waypoint_goal_;
77106
WaypointMode waypoint_mode_{WaypointMode::FULL_POSE};
78107
double convergence_threshold_{0.1};
79108
};

guidance/reference_filter_dp_quat/include/reference_filter_dp_quat/lib/waypoint_types.hpp

Lines changed: 0 additions & 34 deletions
This file was deleted.

guidance/reference_filter_dp_quat/include/reference_filter_dp_quat/lib/waypoint_utils.hpp

Lines changed: 0 additions & 42 deletions
This file was deleted.

guidance/reference_filter_dp_quat/include/reference_filter_dp_quat/ros/reference_filter_ros.hpp

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
#include <rclcpp/rclcpp.hpp>
1010
#include <rclcpp_action/rclcpp_action.hpp>
1111
#include <vortex/utils/types.hpp>
12-
#include <vortex_msgs/action/reference_filter_waypoint.hpp>
13-
#include <vortex_msgs/msg/reference_filter.hpp>
12+
#include <vortex_msgs/action/reference_filter_quat_waypoint.hpp>
13+
#include <vortex_msgs/msg/reference_filter_quat.hpp>
1414
#include <vortex_msgs/msg/waypoint.hpp>
1515
#include "reference_filter_dp_quat/lib/waypoint_follower.hpp"
1616

@@ -37,17 +37,17 @@ class ReferenceFilterNode : public rclcpp::Node {
3737
rclcpp_action::GoalResponse handle_goal(
3838
const rclcpp_action::GoalUUID& uuid,
3939
std::shared_ptr<
40-
const vortex_msgs::action::ReferenceFilterWaypoint::Goal> goal);
40+
const vortex_msgs::action::ReferenceFilterQuatWaypoint::Goal> goal);
4141

4242
/// @brief Accept all cancel requests.
4343
rclcpp_action::CancelResponse handle_cancel(
4444
const std::shared_ptr<rclcpp_action::ServerGoalHandle<
45-
vortex_msgs::action::ReferenceFilterWaypoint>> goal_handle);
45+
vortex_msgs::action::ReferenceFilterQuatWaypoint>> goal_handle);
4646

4747
/// @brief Join the old execution thread and spawn a new one for the goal.
4848
void handle_accepted(
4949
const std::shared_ptr<rclcpp_action::ServerGoalHandle<
50-
vortex_msgs::action::ReferenceFilterWaypoint>> goal_handle);
50+
vortex_msgs::action::ReferenceFilterQuatWaypoint>> goal_handle);
5151

5252
/**
5353
* @brief Execute the action goal in a loop until convergence or
@@ -56,16 +56,16 @@ class ReferenceFilterNode : public rclcpp::Node {
5656
*/
5757
void execute(
5858
const std::shared_ptr<rclcpp_action::ServerGoalHandle<
59-
vortex_msgs::action::ReferenceFilterWaypoint>> goal_handle);
59+
vortex_msgs::action::ReferenceFilterQuatWaypoint>> goal_handle);
6060

61-
rclcpp_action::Server<
62-
vortex_msgs::action::ReferenceFilterWaypoint>::SharedPtr action_server_;
61+
rclcpp_action::Server<vortex_msgs::action::ReferenceFilterQuatWaypoint>::
62+
SharedPtr action_server_;
6363

6464
ReferenceFilterParams filter_params_;
6565

6666
std::unique_ptr<WaypointFollower> follower_;
6767

68-
rclcpp::Publisher<vortex_msgs::msg::ReferenceFilter>::SharedPtr
68+
rclcpp::Publisher<vortex_msgs::msg::ReferenceFilterQuat>::SharedPtr
6969
reference_pub_;
7070

7171
rclcpp::Subscription<geometry_msgs::msg::PoseStamped>::SharedPtr
@@ -77,11 +77,9 @@ class ReferenceFilterNode : public rclcpp::Node {
7777
rclcpp::Subscription<
7878
geometry_msgs::msg::TwistWithCovarianceStamped>::SharedPtr twist_sub_;
7979

80-
rclcpp::TimerBase::SharedPtr reference_pub_timer_;
81-
8280
std::chrono::milliseconds time_step_{};
8381

84-
vortex::utils::types::PoseEuler current_pose_;
82+
vortex::utils::types::Pose current_pose_;
8583

8684
vortex::utils::types::Twist current_twist_;
8785

0 commit comments

Comments
 (0)