# PX4 ROS 2 Navigation Interface
:::warning
실험
At the time of writing, parts of the PX4 ROS 2 Interface Library are experimental, and hence subject to change.
:::
The [PX4 ROS 2 Interface Library](../ros2/px4_ros2_interface_lib.md) navigation interface enables developers to send their position measurements to PX4 directly from ROS 2 applications, such as a VIO system or a map matching system.
The interface provides a layer of abstraction from PX4 and the uORB messaging framework, and introduces a few sanity checks on the requested state estimation updates sent via the interface.
These measurements are then fused into the EKF just as though they were internal PX4 measurements.
The library provides two classes, [`LocalPositionMeasurementInterface`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1LocalPositionMeasurementInterface.html) and [`GlobalPositionMeasurementInterface`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1GlobalPositionMeasurementInterface.html), which both expose a similar `update` method to provide either a local position or global position update to PX4, respectively.
The `update` method expects a position measurement `struct` ([`LocalPositionMeasurement`](https://auterion.github.io/px4-ros2-interface-lib/structpx4__ros2_1_1LocalPositionMeasurement.html) or [`GlobalPositionMeasurement`](https://auterion.github.io/px4-ros2-interface-lib/structpx4__ros2_1_1GlobalPositionMeasurement.html)) which developers can populate with their own generated position measurements.
## Installation and First Test
The following steps are required to get started:
1. Make sure you have a working [ROS 2 setup](../ros2/user_guide.md), with [`px4_msgs`](https://github.com/PX4/px4_msgs) in the ROS 2 workspace.
2. Clone the repository into the workspace:
```sh
cd $ros_workspace/src
git clone --recursive https://github.com/Auterion/px4-ros2-interface-lib
```
::: info
To ensure compatibility, use the latest _main_ branches for PX4, _px4_msgs_ and the library.
See also [here](https://github.com/Auterion/px4-ros2-interface-lib#compatibility-with-px4).
:::
3. Build the workspace:
```sh
cd ..
colcon build
```
4. In a different shell, start PX4 SITL:
```sh
cd $px4-autopilot
make px4_sitl gazebo-classic
```
(here we use Gazebo-Classic, but you can use any model or simulator)
5. In yet a different shell, run the micro XRCE agent (you can keep it running afterward):
```sh
MicroXRCEAgent udp4 -p 8888
```
6. Back in the ROS 2 terminal, source the workspace you just built (in step 3) and run the [global_navigation](https://github.com/Auterion/px4-ros2-interface-lib/tree/main/examples/cpp/navigation/global_navigation) example, which periodically sends dummy global position updates:
```sh
source install/setup.bash
ros2 run example_global_navigation_cpp example_global_navigation
```
You should get an output like this showing that the global interface is successfully sending position updates:
```sh
[INFO] [1702030701.836897756] [example_global_navigation_node]: example_global_navigation_node running!
[DEBUG] [1702030702.837279784] [example_global_navigation_node]: Successfully sent position update to navigation interface.
[DEBUG] [1702030703.837223884] [example_global_navigation_node]: Successfully sent position update to navigation interface.
```
7. In the PX4 shell, you can check that PX4 receives global position updates:
```sh
listener aux_global_position
```
The output should look like:
```sh
TOPIC: aux_global_position
aux_global_position
timestamp: 46916000 (0.528000 seconds ago)
timestamp_sample: 46916000 (0 us before timestamp)
lat: 12.343210
lon: 23.454320
alt: 12.40000
alt_ellipsoid: 0.00000
delta_alt: 0.00000
eph: 0.31623
epv: 0.44721
terrain_alt: 0.00000
lat_lon_reset_counter: 0
alt_reset_counter: 0
terrain_alt_valid: False
dead_reckoning: False
```
8. Now you are ready to use the navigation interface to send your own position updates.
## How to use the Library
To send a position measurement, you populate the position struct with the values you have measured.
Then call the interface's update function with that struct as the argument.
For a basic example of how to use this interface, check out the [examples](https://github.com/Auterion/px4-ros2-interface-lib/tree/main/examples/cpp/navigation) in the `Auterion/px4-ros2-interface-lib` repository, such as [examples/cpp/navigation/local_navigation](https://github.com/Auterion/px4-ros2-interface-lib/blob/main/examples/cpp/navigation/local_navigation/include/local_navigation.hpp) or [examples/cpp/navigation/global_navigation](https://github.com/Auterion/px4-ros2-interface-lib/blob/main/examples/cpp/navigation/local_navigation/include/global_navigation.hpp).
### Local Position Updates
First ensure that the PX4 parameter [`EKF2_EV_CTRL`](../advanced_config/parameter_reference.md#EKF2_EV_CTRL) is properly configured to fuse external local measurements, by setting the appropriate bits to `true`:
- `0`: Horizontal position data
- `1`: Vertical position data
- `2`: Velocity data
- `3`: Yaw data
To send a local position measurement to PX4:
1. Create a [`LocalPositionMeasurementInterface`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1LocalPositionMeasurementInterface.html) instance by providing it with: a ROS node, and the pose and velocity reference frames of your measurements.
2. Populate a [`LocalPositionMeasurement`](https://auterion.github.io/px4-ros2-interface-lib/structpx4__ros2_1_1LocalPositionMeasurement.html) `struct` with your measurements.
3. Pass the `struct` to the `LocalPositionMeasurementInterface` [`update()`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1LocalPositionMeasurementInterface.html#a6fd180b944710716d418b2cfe1c0c8e3) method.
The available pose and velocity reference frames for your measurements are defined by the following `enum`:
```cpp
enum class PoseFrame
{
Unknown,
LocalNED,
LocalFRD
};
enum class VelocityFrame
{
Unknown,
LocalNED,
LocalFRD,
BodyFRD
};
```
The `LocalPositionMeasurement` struct is defined as follows:
```cpp
struct LocalPositionMeasurement
{
rclcpp::Time timestamp_sample {};
std::optional position_xy {std::nullopt};
std::optional position_xy_variance {std::nullopt};
std::optional position_z {std::nullopt};
std::optional position_z_variance {std::nullopt};
std::optional velocity_xy {std::nullopt};
std::optional velocity_xy_variance {std::nullopt};
std::optional velocity_z {std::nullopt};
std::optional velocity_z_variance {std::nullopt};
std::optional attitude_quaternion {std::nullopt};
std::optional attitude_variance {std::nullopt};
};
```
The `update()` method of the local interface expects the following conditions to hold for `LocalPositionMeasurement`:
- The sample timestamp is defined.
- Values do not have a \`NAN\`\`.
- If a measurement value is provided, its associated variance value is well defined (e.g. if `position_xy` is defined, then `position_xy_variance` must be defined).
- If a measurement value is provided, its associated reference frame is not unknown (e.g. if `position_xy` is defined, then the interface was initialised with a pose frame different from `PoseFrame::Unknown`).
The following code snippet is an example of a ROS 2 node which uses the local navigation interface to send 3D pose updates in the North-East-Down (NED) reference frame to PX4:
```cpp
class MyLocalMeasurementUpdateNode : public rclcpp::Node
{
public:
MyLocalMeasurementUpdateNode()
: Node("my_node_name")
{
// Set pose measurement reference frame to north-east-down
const px4_ros2::PoseFrame pose_frame = px4_ros2::PoseFrame::LocalNED;
// We will only send pose measurements in this example
// Set velocity measurement reference frame to unknown
const px4_ros2::VelocityFrame velocity_frame = px4_ros2::VelocityFrame::Unknown;
// Initialize local interface [1]
_local_position_measurement_interface =
std::make_shared(*this, pose_frame, velocity_frame);
}
void sendUpdate()
{
while (running) { // Potentially make method run as a callback or on a timer
// Generate local position measurement
rclcpp::Time timestamp_sample = ...
Eigen::Vector2f position_xy = ...
Eigen::Vector2f position_xy_variance = ...
float position_z = ...
float position_z_variance = ...
// Populate the local position measurement struct [2]
px4_ros2::LocalPositionMeasurement local_position_measurement{};
local_position_measurement.timestamp_sample = timestamp_sample;
local_position_measurement.position_xy = position_xy;
local_position_measurement.position_xy_variance = position_xy_variance;
local_position_measurement.position_z = position_z;
local_position_measurement.position_z_variance = position_z_variance;
// Send measurement to PX4 using the interface [3]
try {
_local_position_measurement_interface->update(local_position_measurement);
} catch (const px4_ros2::NavigationInterfaceInvalidArgument & e) {
// Handle exceptions caused by invalid local_position_measurement definition
RCLCPP_ERROR(get_logger(), "Exception caught: %s", e.what());
}
}
}
private:
std::shared_ptr _local_position_measurement_interface;
};
```
### Global Position Updates
First ensure that the PX4 parameter [`EKF2_AGP_CTRL`](../advanced_config/parameter_reference.md#EKF2_AGP_CTRL) is properly configured to fuse external global measurements, by setting the appropriate bits to `true`:
- `0`: Horizontal position data
- `1`: Vertical position data
To send a global position measurement to PX4:
1. Create a [`GlobalPositionMeasurementInterface`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1GlobalPositionMeasurementInterface.html) instance by providing it with a ROS node.
2. Populate a [`GlobalPositionMeasurement`](https://auterion.github.io/px4-ros2-interface-lib/structpx4__ros2_1_1GlobalPositionMeasurement.html) `struct` with your measurements.
3. Pass the struct to the `GlobalPositionMeasurementInterface` [update()](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1GlobalPositionMeasurementInterface.html#a1a183b595ef7f6a22f3a83ba543fe86d) method.
The `GlobalPositionMeasurement` struct is defined as follows:
```cpp
struct GlobalPositionMeasurement
{
rclcpp::Time timestamp_sample {};
std::optional lat_lon {std::nullopt};
std::optional horizontal_variance {std::nullopt};
std::optional altitude_msl {std::nullopt};
std::optional vertical_variance {std::nullopt};
};
```
The `update()` method of the global interface expects the following conditions to hold for `GlobalPositionMeasurement`:
- The sample `timestamp_sample` is defined.
- Values do not have a NAN.
- If a measurement value is provided, its associated variance value is well defined (e.g. if `lat_lon` is defined, then `horizontal_variance` must be defined).
The following code snippet is an example of a ROS 2 node which uses the global navigation interface to send a measurement with latitude, longitude and altitude to PX4:
```cpp
class MyGlobalMeasurementUpdateNode : public rclcpp::Node
{
public:
MyGlobalMeasurementUpdateNode()
: Node("my_node_name")
{
// Initialize global interface [1]
_global_position_measurement_interface =
std::make_shared(*this);
}
void sendUpdate()
{
while (running) { // Potentially make method run as a callback or on a timer
// Generate global position measurement
rclcpp::Time timestamp_sample = ...
Eigen::Vector2d lat_lon = ...
float horizontal_variance = ...
float altitude_msl = ...
float vertical_variance = ...
// Populate the global position measurement struct [2]
px4_ros2::GlobalPositionMeasurement global_position_measurement{};
global_position_measurement.timestamp_sample = timestamp_sample;
global_position_measurement.lat_lon = lat_lon;
global_position_measurement.horizontal_variance = horizontal_variance;
global_position_measurement.altitude_msl = altitude_msl;
global_position_measurement.vertical_variance = vertical_variance;
// Send measurement to PX4 using the interface [3]
try {
_global_position_measurement_interface->update(global_position_measurement);
} catch (const px4_ros2::NavigationInterfaceInvalidArgument & e) {
// Handle exceptions caused by invalid global_position_measurement definition
RCLCPP_ERROR(get_logger(), "Exception caught: %s", e.what());
}
}
}
private:
std::shared_ptr _global_position_measurement_interface;
};
```
## Multiple Instances of an Interface
Using multiple instances of the same interface (e.g. local and local) to send estimation updates will stream all update messages to the same topic and result in cross-talk.
This should not affect measurement fusion into the EKF, but different measurement sources will become indistinguishable.