mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-10-24 01:53:35 +08:00
Move PX4 Guide source into /docs (#24490)
* Add vitepress tree * Update existing workflows so they dont trigger on changes in the docs path * Add nojekyll, package.json, LICENCE etc * Add crowdin docs upload/download scripts * Add docs flaw checker workflows * Used docs prefix for docs workflows * Crowdin obvious fixes * ci: docs move to self hosted runner runs on a beefy server for faster builds Signed-off-by: Ramon Roche <mrpollo@gmail.com> * ci: don't run build action for docs or ci changes Signed-off-by: Ramon Roche <mrpollo@gmail.com> * ci: update runners Signed-off-by: Ramon Roche <mrpollo@gmail.com> * Add docs/en * Add docs assets and scripts * Fix up editlinks to point to PX4 sources * Download just the translations that are supported * Add translation sources for zh, uk, ko * Update latest tranlsation and uorb graphs * update vitepress to latest --------- Signed-off-by: Ramon Roche <mrpollo@gmail.com> Co-authored-by: Ramon Roche <mrpollo@gmail.com>
This commit is contained in:
co-authored by
Ramon Roche
parent
8e6d2ebe4a
commit
88d623bedb
@@ -0,0 +1,45 @@
|
||||
# ROS 2
|
||||
|
||||
[ROS 2](https://docs.ros.org/en/humble/#) is a powerful general purpose robotics library that can be used with the PX4 Autopilot to create powerful drone applications.
|
||||
|
||||
:::warning
|
||||
Tip
|
||||
The PX4 development team highly recommend that you use/migrate to this version of ROS!
|
||||
|
||||
This is the newest version of [ROS](http://www.ros.org/) (Robot Operating System).
|
||||
It significantly improves on ROS "1", and in particular allows a much deeper and lower-latency integration with PX4.
|
||||
:::
|
||||
|
||||
ROS는 일반적인 로봇 공학 문제를 해결하고, Linux용으로 작성된 소프트웨어 라이브러리에 대한 액세스를 해결하는 개발자의 활발한 생태계 시스템의 이점이 있습니다.
|
||||
It can be used, for example, for [computer vision](../computer_vision/index.md) solutions.
|
||||
|
||||
ROS 2 enables a very deep integration with PX4, to the extent that you can create flight modes in ROS 2 that are indistinguisable from internal PX4 modes, and directly read from and write to internal uORB topics at high rate.
|
||||
It is recommended (in particular) for control and communication from a companion computer where low latency is important, when leveraging existing libraries from Linux, or when writing new high level flight modes.
|
||||
|
||||
Communication between ROS 2 and PX4 uses middleware that implements the [XRCE-DDS protocol](../middleware/uxrce_dds.md).
|
||||
This middleware exposes PX4 [uORB messages](../msg_docs/index.md) as ROS 2 messages and types, effectively allowing direct access to PX4 from ROS 2 workflows and nodes.
|
||||
The middleware uses uORB message definitions to generate code to serialise and deserialise the messages heading in and out of PX4.
|
||||
These same message definitions are used in ROS 2 applications to allow the messages to be interpreted.
|
||||
|
||||
:::info
|
||||
ROS 2 can also connect with PX4 using [MAVROS](https://github.com/mavlink/mavros/tree/ros2/mavros) instead of XRCE-DDS.
|
||||
This option is supported by the MAVROS project (it is not documented here).
|
||||
:::
|
||||
|
||||
To use the [ROS 2](../ros2/user_guide.md) over XRCE-DDS effectively, you must (at time of writing) have a reasonable understanding of the PX4 internal architecture and conventions, which differ from those used by ROS.
|
||||
In the near term future we plan to provide ROS 2 APIs to abstract PX4 conventions, along with examples demonstrating their use.
|
||||
|
||||
## Topics
|
||||
|
||||
이 섹션의 주요 주제는 다음과 같습니다.
|
||||
|
||||
- [ROS 2 User Guide](../ros2/user_guide.md): A PX4-centric overview of ROS 2, covering installation, setup, and how to build ROS 2 applications that communicate with PX4.
|
||||
- [ROS 2 Offboard Control Example](../ros2/offboard_control.md): A C++ tutorial examples showing how to do position control in [offboard mode](../flight_modes/offboard.md) from a ROS 2 node.
|
||||
- [ROS 2 Multi Vehicle Simulation](../ros2/multi_vehicle.md): Instructions for connecting to multipole PX4 simulations via single ROS 2 agent.
|
||||
- [PX4 ROS 2 Interface Library](../ros2/px4_ros2_interface_lib.md): A C++ library that simplies interacting with PX4 from ROS 2.
|
||||
Can be used to create and register flight modes wrtten using ROS2 and send position estimates from ROS2 applications such as a VIO system.
|
||||
- [ROS 2 Message Translation Node](../ros2/px4_ros2_msg_translation_node.md): A ROS 2 message translation node that enables communcation between PX4 and ROS 2 applications that were compiled with different sets of messages versions.
|
||||
|
||||
## 추가 정보
|
||||
|
||||
- [XRCE-DDS (PX4-ROS 2/DDS Bridge)](../middleware/uxrce_dds.md): PX4 middleware for connecting to ROS 2.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Multi-Vehicle Simulation with ROS 2
|
||||
|
||||
[XRCE-DDS](../middleware/uxrce_dds.md) allows for multiple clients to be connected to the same agent over UDP.
|
||||
This is particular useful in simulation as only one agent needs to be started.
|
||||
|
||||
## Setup and Requirements
|
||||
|
||||
The only requirements are
|
||||
|
||||
- To be able to run [multi-vehicle simulation](../simulation/multi-vehicle-simulation.md) without ROS 2 with the desired simulator ([Gazebo](../sim_gazebo_gz/multi_vehicle_simulation.md), [Gazebo Classic](../sim_gazebo_classic/multi_vehicle_simulation.md#multiple-vehicle-with-gazebo-classic), [FlightGear](../sim_flightgear/multi_vehicle.md) and [JMAVSim](../sim_jmavsim/multi_vehicle.md)).
|
||||
- To be able to use [ROS 2](../ros2/user_guide.md) in a single vehicle simulation.
|
||||
|
||||
## Principle of Operation
|
||||
|
||||
In simulation each PX4 instance receives a unique `px4_instance` number starting from `0`.
|
||||
This value is used to assign a unique value to [UXRCE_DDS_KEY](../advanced_config/parameter_reference.md#UXRCE_DDS_KEY):
|
||||
|
||||
```sh
|
||||
param set UXRCE_DDS_KEY $((px4_instance+1))
|
||||
```
|
||||
|
||||
:::info
|
||||
By doing so, `UXRCE_DDS_KEY` will always coincide with [MAV_SYS_ID](../advanced_config/parameter_reference.md#MAV_SYS_ID).
|
||||
:::
|
||||
|
||||
Moreover, when `px4_instance` is greater than zero, a unique ROS 2 [namespace prefix](../middleware/uxrce_dds.md#customizing-the-namespace) in the form `px4_$px4_instance` is added:
|
||||
|
||||
```sh
|
||||
uxrce_dds_ns="-n px4_$px4_instance"
|
||||
```
|
||||
|
||||
:::info
|
||||
The environment variable `PX4_UXRCE_DDS_NS`, if set, will override the namespace behavior described above.
|
||||
:::
|
||||
|
||||
The first instance (`px4_instance=0`) does not have an additional namespace in order to be consistent with the default behavior of the `xrce-dds` client on a real vehicle.
|
||||
This mismatch can be fixed by manually using `PX4_UXRCE_DDS_NS` on the first instance or by starting adding vehicles from index `1` instead of `0` (this is the default behavior adopted by [sitl_multiple_run.sh](https://github.com/PX4/PX4-Autopilot/blob/main/Tools/simulation/gazebo-classic/sitl_multiple_run.sh) for Gazebo Classic).
|
||||
|
||||
The default client configuration in simulation is summarized as follows:
|
||||
|
||||
| `PX4_UXRCE_DDS_NS` | `px4_instance` | `UXRCE_DDS_KEY` | client namespace |
|
||||
| ------------------ | -------------- | ---------------- | --------------------- |
|
||||
| not provided | 0 | `px4_instance+1` | none |
|
||||
| provided | 0 | `px4_instance+1` | `PX4_UXRCE_DDS_NS` |
|
||||
| not provided | > 0 | `px4_instance+1` | `px4_${px4_instance}` |
|
||||
| provided | > 0 | `px4_instance+1` | `PX4_UXRCE_DDS_NS` |
|
||||
|
||||
## Adjusting the `target_system` value
|
||||
|
||||
PX4 accepts [VehicleCommand](../msg_docs/VehicleCommand.md) messages only if their `target_system` field is zero (broadcast communication) or coincides with `MAV_SYS_ID`.
|
||||
In all other situations, the messages are ignored.
|
||||
Therefore, when ROS 2 nodes want to send `VehicleCommand` to PX4, they must ensure that the messages are filled with the appropriate `target_system` value.
|
||||
|
||||
For example, if you want to send a command to your third vehicle, which has `px4_instance=2`, you need to set `target_system=3` in all your `VehicleCommand` messages.
|
||||
@@ -0,0 +1,222 @@
|
||||
# ROS 2 오프보드 제어 예
|
||||
|
||||
The following C++ example shows how to do position control in [offboard mode](../flight_modes/offboard.md) from a ROS 2 node.
|
||||
|
||||
The example starts sending setpoints, enters offboard mode, arms, ascends to 5 metres, and waits.
|
||||
While simple, it shows the main principles of how to use offboard control and how to send vehicle commands.
|
||||
|
||||
It has been tested on Ubuntu 20.04 with ROS 2 Foxy and PX4 v1.14.
|
||||
|
||||
:::warning
|
||||
_Offboard_ control is dangerous.
|
||||
If you are operating on a real vehicle be sure to have a way of gaining back manual control in case something goes wrong.
|
||||
:::
|
||||
|
||||
:::info
|
||||
ROS and PX4 make a number of different assumptions, in particular with respect to [frame conventions](../ros/external_position_estimation.md#reference-frames-and-ros).
|
||||
There is no implicit conversion between frame types when topics are published or subscribed!
|
||||
|
||||
This example publishes positions in the NED frame, as expected by PX4.
|
||||
To subscribe to data coming from nodes that publish in a different frame (for example the ENU, which is the standard frame of reference in ROS/ROS 2), use the helper functions in the [frame_transforms](https://github.com/PX4/px4_ros_com/blob/main/src/lib/frame_transforms.cpp) library.
|
||||
:::
|
||||
|
||||
## Trying it out
|
||||
|
||||
Follow the instructions in [ROS 2 User Guide](../ros2/user_guide.md) to install PX and run the simulator, install ROS 2, and start the XRCE-DDS Agent.
|
||||
|
||||
After that we can follow a similar set of steps to those in [ROS 2 User Guide > Build ROS 2 Workspace](../ros2/user_guide.md#build-ros-2-workspace) to run the example.
|
||||
|
||||
:::tip
|
||||
Make sure that QGC is connected to PX4 before running the ROS 2 node.
|
||||
This is needed because, by default, you cannot arm a vehicle without a connection to ground station (QGC) or an established RC connection (this ensures there is always the option to regain manual control).
|
||||
:::
|
||||
|
||||
To build and run the example:
|
||||
|
||||
1. Open a new terminal.
|
||||
|
||||
2. Create and navigate into a new colcon workspace directory using:
|
||||
|
||||
```sh
|
||||
mkdir -p ~/ws_offboard_control/src/
|
||||
cd ~/ws_offboard_control/src/
|
||||
```
|
||||
|
||||
3. Clone the [px4_msgs](https://github.com/PX4/px4_msgs) repo to the `/src` directory (this repo is needed in every ROS 2 PX4 workspace!):
|
||||
|
||||
```sh
|
||||
git clone https://github.com/PX4/px4_msgs.git
|
||||
# checkout the matching release branch if not using PX4 main.
|
||||
```
|
||||
|
||||
4. Clone the example repository [px4_ros_com](https://github.com/PX4/px4_ros_com) to the `/src` directory:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/PX4/px4_ros_com.git
|
||||
```
|
||||
|
||||
5. Source the ROS 2 development environment into the current terminal and compile the workspace using `colcon`:
|
||||
|
||||
:::: tabs
|
||||
|
||||
::: tab humble
|
||||
|
||||
```sh
|
||||
cd ..
|
||||
source /opt/ros/humble/setup.bash
|
||||
colcon build
|
||||
```
|
||||
|
||||
|
||||
:::
|
||||
|
||||
::: tab foxy
|
||||
|
||||
```sh
|
||||
cd ..
|
||||
source /opt/ros/foxy/setup.bash
|
||||
colcon build
|
||||
```
|
||||
|
||||
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
6. Source the `local_setup.bash`:
|
||||
|
||||
```sh
|
||||
source install/local_setup.bash
|
||||
```
|
||||
|
||||
7. Launch the example.
|
||||
|
||||
```
|
||||
ros2 run px4_ros_com offboard_control
|
||||
```
|
||||
|
||||
The vehicle should arm, ascend 5 metres, and then wait (perpetually).
|
||||
|
||||
## 구현
|
||||
|
||||
The source code of the offboard control example can be found in [PX4/px4_ros_com](https://github.com/PX4/px4_ros_com) in the directory [/src/examples/offboard/offboard_control.cpp](https://github.com/PX4/px4_ros_com/blob/main/src/examples/offboard/offboard_control.cpp).
|
||||
|
||||
:::info
|
||||
PX4 publishes all the messages used in this example as ROS topics by default (see [dds_topics.yaml](https://github.com/PX4/PX4-Autopilot/blob/main/src/modules/uxrce_dds_client/dds_topics.yaml)).
|
||||
:::
|
||||
|
||||
PX4 requires that the vehicle is already receiving `OffboardControlMode` messages before it will arm in offboard mode, or before it will switch to offboard mode when flying.
|
||||
In addition, PX4 will switch out of offboard mode if the stream rate of `OffboardControlMode` messages drops below approximately 2Hz.
|
||||
The required behaviour is implemented by the main loop spinning in the ROS 2 node, as shown below:
|
||||
|
||||
```cpp
|
||||
auto timer_callback = [this]() -> void {
|
||||
|
||||
if (offboard_setpoint_counter_ == 10) {
|
||||
// Change to Offboard mode after 10 setpoints
|
||||
this->publish_vehicle_command(VehicleCommand::VEHICLE_CMD_DO_SET_MODE, 1, 6);
|
||||
|
||||
// Arm the vehicle
|
||||
this->arm();
|
||||
}
|
||||
|
||||
// OffboardControlMode needs to be paired with TrajectorySetpoint
|
||||
publish_offboard_control_mode();
|
||||
publish_trajectory_setpoint();
|
||||
|
||||
// stop the counter after reaching 11
|
||||
if (offboard_setpoint_counter_ < 11) {
|
||||
offboard_setpoint_counter_++;
|
||||
}
|
||||
};
|
||||
timer_ = this->create_wall_timer(100ms, timer_callback);
|
||||
```
|
||||
|
||||
The loop runs on a 100ms timer.
|
||||
For the first 10 cycles it calls `publish_offboard_control_mode()` and `publish_trajectory_setpoint()` to send [OffboardControlMode](../msg_docs/OffboardControlMode.md) and [TrajectorySetpoint](../msg_docs/TrajectorySetpoint.md) messages to PX4.
|
||||
The `OffboardControlMode` messages are streamed so that PX4 will allow arming once it switches to offboard mode, while the `TrajectorySetpoint` messages are ignored (until the vehicle is in offboard mode).
|
||||
|
||||
After 10 cycles `publish_vehicle_command()` is called to change to offboard mode, and `arm()` is called to arm the vehicle.
|
||||
After the vehicle arms and changes mode it starts tracking the position setpoints.
|
||||
The setpoints are still sent in every cycle so that the vehicle does not fall out of offboard mode.
|
||||
|
||||
The implementations of the `publish_offboard_control_mode()` and `publish_trajectory_setpoint()` methods are shown below.
|
||||
These publish the [OffboardControlMode](../msg_docs/OffboardControlMode.md) and [TrajectorySetpoint](../msg_docs/TrajectorySetpoint.md) messages to PX4 (respectively).
|
||||
|
||||
The `OffboardControlMode` is required in order to inform PX4 of the _type_ of offboard control behing used.
|
||||
Here we're only using _position control_, so the `position` field is set to `true` and all the other fields are set to `false`.
|
||||
|
||||
```cpp
|
||||
/**
|
||||
* @brief Publish the offboard control mode.
|
||||
* For this example, only position and altitude controls are active.
|
||||
*/
|
||||
void OffboardControl::publish_offboard_control_mode()
|
||||
{
|
||||
OffboardControlMode msg{};
|
||||
msg.position = true;
|
||||
msg.velocity = false;
|
||||
msg.acceleration = false;
|
||||
msg.attitude = false;
|
||||
msg.body_rate = false;
|
||||
msg.thrust_and_torque = false;
|
||||
msg.direct_actuator = false;
|
||||
msg.timestamp = this->get_clock()->now().nanoseconds() / 1000;
|
||||
offboard_control_mode_publisher_->publish(msg);
|
||||
}
|
||||
```
|
||||
|
||||
`TrajectorySetpoint` provides the position setpoint.
|
||||
In this case, the `x`, `y`, `z` and `yaw` fields are hardcoded to certain values, but they can be updated dynamically according to an algorithm or even by a subscription callback for messages coming from another node.
|
||||
|
||||
```cpp
|
||||
/**
|
||||
* @brief Publish a trajectory setpoint
|
||||
* For this example, it sends a trajectory setpoint to make the
|
||||
* vehicle hover at 5 meters with a yaw angle of 180 degrees.
|
||||
*/
|
||||
void OffboardControl::publish_trajectory_setpoint()
|
||||
{
|
||||
TrajectorySetpoint msg{};
|
||||
msg.position = {0.0, 0.0, -5.0};
|
||||
msg.yaw = -3.14; // [-PI:PI]
|
||||
msg.timestamp = this->get_clock()->now().nanoseconds() / 1000;
|
||||
trajectory_setpoint_publisher_->publish(msg);
|
||||
}
|
||||
```
|
||||
|
||||
The `publish_vehicle_command()` sends [VehicleCommand](../msg_docs/VehicleCommand.md) messages with commands to the flight controller.
|
||||
We use it above to change the mode to offboard mode, and also in `arm()` to arm the vehicle.
|
||||
While we don't call `disarm()` in this example, it is also used in the implementation of that function.
|
||||
|
||||
```cpp
|
||||
/**
|
||||
* @brief Publish vehicle commands
|
||||
* @param command Command code (matches VehicleCommand and MAVLink MAV_CMD codes)
|
||||
* @param param1 Command parameter 1
|
||||
* @param param2 Command parameter 2
|
||||
*/
|
||||
void OffboardControl::publish_vehicle_command(uint16_t command, float param1, float param2)
|
||||
{
|
||||
VehicleCommand msg{};
|
||||
msg.param1 = param1;
|
||||
msg.param2 = param2;
|
||||
msg.command = command;
|
||||
msg.target_system = 1;
|
||||
msg.target_component = 1;
|
||||
msg.source_system = 1;
|
||||
msg.source_component = 1;
|
||||
msg.from_external = true;
|
||||
msg.timestamp = this->get_clock()->now().nanoseconds() / 1000;
|
||||
vehicle_command_publisher_->publish(msg);
|
||||
}
|
||||
```
|
||||
|
||||
:::info
|
||||
[VehicleCommand](../msg_docs/VehicleCommand.md) is one of the simplest and most powerful ways to command PX4, and by subscribing to [VehicleCommandAck](../msg_docs/VehicleCommandAck.md) you can also confirm that setting a particular command was successful.
|
||||
The param and command fields map to [MAVLink commands](https://mavlink.io/en/messages/common.html#mav_commands) and their parameter values.
|
||||
:::
|
||||
|
||||
## See Also
|
||||
|
||||
- [Python ROS2 offboard examples with PX4](https://github.com/Jaeyoung-Lim/px4-offboard) (Jaeyoung-Lim/px4-offboard).
|
||||
@@ -0,0 +1,524 @@
|
||||
# PX4 ROS 2 Control Interface
|
||||
|
||||
<Badge type="tip" text="PX4 v1.15" /> <Badge type="warning" text="Experimental" />
|
||||
|
||||
:::warning
|
||||
실험
|
||||
At the time of writing, parts of the PX4 ROS 2 Control Interface are experimental, and hence subject to change:
|
||||
|
||||
- The architecture and core interfaces for defining modes in ROS 2 modes are largely stable, and are tested in CI.
|
||||
The library offers significant benefits over using offboard mode in its current state.
|
||||
- Only a few setpoint types have settled (the others are still under development).
|
||||
You may need to use internal PX4 topics which may not remain backwards-compatible over time.
|
||||
- The API is not fully documented.
|
||||
|
||||
:::
|
||||
|
||||
The [PX4 ROS 2 Interface Library](../ros2/px4_ros2_interface_lib.md) is a C++ library that simplifies controlling PX4 from ROS 2.
|
||||
|
||||
Developers use the library to create and dynamically register modes written using ROS 2.
|
||||
These modes are dynamically registered with PX4, and appear to be part of PX4 to a ground station or other external system.
|
||||
They can even replace the default modes in PX4 with enhanced ROS 2 versions, falling back to the original version if the ROS2 mode fails.
|
||||
|
||||
The library also provides classes for sending different types of setpoints, ranging from high-level navigation tasks all the way down to direct actuator controls.
|
||||
These classes abstract the internal setpoints used by PX4, and that can therefore be used to provide a consistent ROS 2 interface for future PX4 and ROS releases.
|
||||
|
||||
PX4 ROS 2 modes are easier to implement and maintain than PX4 internal modes, and provide more resources for developers in terms of processing power and pre-existing libraries.
|
||||
Unless the mode is safety-critical, requires strict timing or very high update rates, or your vehicle doesn't have a companion computer, you should [consider using PX4 ROS 2 modes in preference to PX4 internal modes](../concept/flight_modes.md#internal-vs-external-modes).
|
||||
|
||||
## 개요
|
||||
|
||||
This diagram provides a conceptual overview of how the control interface modes and mode executors interact with PX4.
|
||||
|
||||

|
||||
|
||||
<!-- Source: https://docs.google.com/drawings/d/1WByCfgcytnaow7r41VhYJL8OGrw1RjFO51GoPMQBCNA/edit -->
|
||||
|
||||
The following sections define and explain the terms used in the diagram.
|
||||
|
||||
### Definitions
|
||||
|
||||
#### Mode
|
||||
|
||||
A mode defined using the interface library has the following properties:
|
||||
|
||||
- A mode is a component that can send setpoints to the vehicle in order to control its motion (such as velocity or direct actuator commands).
|
||||
- A mode selects a setpoint type and sends it while it is active.
|
||||
It can switch between multiple setpoint types.
|
||||
- A mode can't activate other modes, and must be activated by the user (through RC/GCS), the flight controller in a failsafe situation, a _mode executor_, or some other external system.
|
||||
- Has a name displayed by the GCS.
|
||||
- Can configure its mode requirements (for example that it requires a valid position estimate).
|
||||
- A mode can perform different tasks, such as flying to a target, lowering a winch, releasing a payload and then fly back.
|
||||
- A mode can replace a mode defined in PX4.
|
||||
|
||||
#### Mode Executor
|
||||
|
||||
A mode executor is an optional component for scheduling modes.
|
||||
For example, the mode executor for a custom payload delivery or survey mode might first trigger a take-off, then switch to the custom mode, and when that completes trigger an RTL.
|
||||
|
||||
Specifically, it has the following properties:
|
||||
|
||||
- A mode executor is an optional component one level higher than a mode.
|
||||
It is a state machine that can activate modes, and wait for their completion.
|
||||
- It can only do so while it is in charge.
|
||||
For that, an executor has exactly one _owned mode_ (and a mode can be owned by at most one executor).
|
||||
This mode serves as activation for the executor: when the user selects the mode, the owning executor gets activated and can select any mode.
|
||||
It stays in charge until the user switches modes (by RC or from a GCS), or a failsafe triggers a mode switch.
|
||||
Should the failsafe clear, the executor gets reactivated.
|
||||
- This allows multiple executors to coexist.
|
||||
- Executors cannot activate other executors.
|
||||
- Within the library, a mode executor is always implemented in combination with a custom mode.
|
||||
|
||||
::: info
|
||||
|
||||
- These definitions guarantee that a user can take away control from a custom mode or executor at any point in time by commanding a mode switch through RC or a GCS.
|
||||
- A mode executor is transparent to the user.
|
||||
It gets indirectly selected and activated through the owning mode, and thus the mode should be named accordingly.
|
||||
|
||||
:::
|
||||
|
||||
#### Configuration Overrides
|
||||
|
||||
Both modes and executors can define configuration overrides, allowing customisation of certain behaviors while the mode or executor is active.
|
||||
|
||||
These are currently implemented:
|
||||
|
||||
- _Disabling auto-disarm_.
|
||||
This permits landing and then taking off again (e.g. to release a payload).
|
||||
- _Ability to defer non-essential failsafes_.
|
||||
This allows an executor to run an action without being interrupted by non-critical failsafe.
|
||||
For example, ignoring a low-battery failsafe so that a winch operation can complete.
|
||||
|
||||
### Comparison to Offboard Control
|
||||
|
||||
The above concepts provide a number of advantages over traditional [offboard control](../ros/offboard_control.md):
|
||||
|
||||
- Multiple nodes or applications can coexist and even run at the same time.
|
||||
But only one node can _control the vehicle_ at a given time, and this node is well defined.
|
||||
- Modes have a distinct name and be displayed/selected in the GCS.
|
||||
- Modes are integrated with the failsafe state machine and arming checks.
|
||||
- The setpoint types that can be sent are well defined.
|
||||
- ROS 2 modes can replace flight controller internal modes (such as [Return mode](../flight_modes/return.md)).
|
||||
|
||||
## 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
|
||||
source install/setup.bash
|
||||
```
|
||||
|
||||
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. Run the micro XRCE agent in a new shell (you can keep it running afterward):
|
||||
|
||||
```sh
|
||||
MicroXRCEAgent udp4 -p 8888
|
||||
```
|
||||
|
||||
6. Start QGroundControl.
|
||||
|
||||
::: info
|
||||
Use QGroundControl Daily, which supports dynamically updating the list of modes.
|
||||
|
||||
:::
|
||||
|
||||
7. Back in the ROS 2 terminal, run one of the example modes:
|
||||
|
||||
```sh
|
||||
ros2 run example_mode_manual_cpp example_mode_manual
|
||||
```
|
||||
|
||||
You should get an output like this showing 'My Manual Mode' mode being registered:
|
||||
|
||||
```sh
|
||||
[DEBUG] [example_mode_manual]: Checking message compatibility...
|
||||
[DEBUG] [example_mode_manual]: Subscriber found, continuing
|
||||
[DEBUG] [example_mode_manual]: Publisher found, continuing
|
||||
[DEBUG] [example_mode_manual]: Registering 'My Manual Mode' (arming check: 1, mode: 1, mode executor: 0)
|
||||
[DEBUG] [example_mode_manual]: Subscriber found, continuing
|
||||
[DEBUG] [example_mode_manual]: Publisher found, continuing
|
||||
[DEBUG] [example_mode_manual]: Got RegisterExtComponentReply
|
||||
[DEBUG] [example_mode_manual]: Arming check request (id=1, only printed once)
|
||||
```
|
||||
|
||||
8. On the PX4 shell, you can check that PX4 registered the new mode:
|
||||
|
||||
```sh
|
||||
commander status
|
||||
```
|
||||
|
||||
The output should contain:
|
||||
|
||||
```plain
|
||||
INFO [commander] Disarmed
|
||||
INFO [commander] navigation mode: Position
|
||||
INFO [commander] user intended navigation mode: Position
|
||||
INFO [commander] in failsafe: no
|
||||
INFO [commander] External Mode 1: nav_state: 23, name: My Manual Mode
|
||||
```
|
||||
|
||||
9. At this point you should be able to see the mode in QGroundControl as well:
|
||||
|
||||

|
||||
|
||||
10. Select the mode, make sure you have a manual control source (physical or virtual joystick), and arm the vehicle.
|
||||
The mode will then activate, and it should print the following output:
|
||||
|
||||
```sh
|
||||
[DEBUG] [example_mode_manual]: Mode 'My Manual Mode' activated
|
||||
```
|
||||
|
||||
11. Now you are ready to create your own mode.
|
||||
|
||||
## How to use the Library
|
||||
|
||||
The following sections describe specific functionality provided by this library.
|
||||
In addition, any other PX4 topic can be subscribed or published.
|
||||
|
||||
### Mode Class Definition
|
||||
|
||||
This section steps through an example of how to create a class for a custom mode.
|
||||
|
||||
For a complete application, check out the [examples in the `Auterion/px4-ros2-interface-lib` repository](https://github.com/Auterion/px4-ros2-interface-lib/tree/main/examples/cpp), such as [examples/cpp/modes/manual](https://github.com/Auterion/px4-ros2-interface-lib/blob/main/examples/cpp/modes/manual/include/mode.hpp).
|
||||
|
||||
```cpp{1,5,7-9,24-31}
|
||||
class MyMode : public px4_ros2::ModeBase // [1]
|
||||
{
|
||||
public:
|
||||
explicit MyMode(rclcpp::Node & node)
|
||||
: ModeBase(node, Settings{"My Mode"}) // [2]
|
||||
{
|
||||
// [3]
|
||||
_manual_control_input = std::make_shared<px4_ros2::ManualControlInput>(*this);
|
||||
_rates_setpoint = std::make_shared<px4_ros2::RatesSetpointType>(*this);
|
||||
}
|
||||
|
||||
void onActivate() override
|
||||
{
|
||||
// Called whenever our mode gets selected
|
||||
}
|
||||
|
||||
void onDeactivate() override
|
||||
{
|
||||
// Called when our mode gets deactivated
|
||||
}
|
||||
|
||||
void updateSetpoint(const rclcpp::Duration & dt) override
|
||||
{
|
||||
// [4]
|
||||
const Eigen::Vector3f thrust_sp{0.F, 0.F, -_manual_control_input->throttle()};
|
||||
const Eigen::Vector3f rates_sp{
|
||||
_manual_control_input->roll() * 150.F * M_PI / 180.F,
|
||||
-_manual_control_input->pitch() * 150.F * M_PI / 180.F,
|
||||
_manual_control_input->yaw() * 100.F * M_PI / 180.F
|
||||
};
|
||||
_rates_setpoint->update(rates_sp, thrust_sp);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<px4_ros2::ManualControlInput> _manual_control_input;
|
||||
std::shared_ptr<px4_ros2::RatesSetpointType> _rates_setpoint;
|
||||
};
|
||||
```
|
||||
|
||||
- `[1]`: First we create a class that inherits from [`px4_ros2::ModeBase`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1ModeBase.html).
|
||||
- `[2]`: In the constructor, we pass the mode name. This also allows us to configure some other things, like replacing a flight controller internal mode.
|
||||
- `[3]`: This is where we create all objects that we want to use later on.
|
||||
This can be RC input, setpoint type(s), or telemetry. `*this` is passed as a `Context` to each object, which associates the object with the mode.
|
||||
- `[4]`: Whenever the mode is active, this method gets called regularly (the update rate depends on the setpoint type).
|
||||
Here is where we can do our work and generate a new setpoint.
|
||||
|
||||
After creating an instance of that mode, `mode->doRegister()` must be called which does the actual registration with the flight controller and returns `false` if it fails.
|
||||
In case a mode executor is used, `doRegister()` must be called on the mode executor, instead of for the mode.
|
||||
|
||||
### Mode Executor Class Definition
|
||||
|
||||
This section steps through an example of how to create a mode executor class.
|
||||
|
||||
```cpp{1,4-5,9-16,20,33-57}
|
||||
class MyModeExecutor : public px4_ros2::ModeExecutorBase // [1]
|
||||
{
|
||||
public:
|
||||
MyModeExecutor(rclcpp::Node & node, px4_ros2::ModeBase & owned_mode) // [2]
|
||||
: ModeExecutorBase(node, px4_ros2::ModeExecutorBase::Settings{}, owned_mode),
|
||||
_node(node)
|
||||
{ }
|
||||
|
||||
enum class State // [3]
|
||||
{
|
||||
Reset,
|
||||
TakingOff,
|
||||
MyMode,
|
||||
RTL,
|
||||
WaitUntilDisarmed,
|
||||
};
|
||||
|
||||
void onActivate() override
|
||||
{
|
||||
runState(State::TakingOff, px4_ros2::Result::Success); // [4]
|
||||
}
|
||||
|
||||
void onDeactivate(DeactivateReason reason) override { }
|
||||
|
||||
void runState(State state, px4_ros2::Result previous_result)
|
||||
{
|
||||
if (previous_result != px4_ros2::Result::Success) {
|
||||
RCLCPP_ERROR(_node.get_logger(), "State %i: previous state failed: %s", (int)state,
|
||||
resultToString(previous_result));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (state) { // [5]
|
||||
case State::Reset:
|
||||
break;
|
||||
|
||||
case State::TakingOff:
|
||||
takeoff([this](px4_ros2::Result result) {runState(State::MyMode, result);});
|
||||
break;
|
||||
|
||||
case State::MyMode: // [6]
|
||||
scheduleMode(
|
||||
ownedMode().id(), [this](px4_ros2::Result result) {
|
||||
runState(State::RTL, result);
|
||||
});
|
||||
break;
|
||||
|
||||
case State::RTL:
|
||||
rtl([this](px4_ros2::Result result) {runState(State::WaitUntilDisarmed, result);});
|
||||
break;
|
||||
|
||||
case State::WaitUntilDisarmed:
|
||||
waitUntilDisarmed([this](px4_ros2::Result result) {
|
||||
RCLCPP_INFO(_node.get_logger(), "All states complete (%s)", resultToString(result));
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
rclcpp::Node & _node;
|
||||
};
|
||||
```
|
||||
|
||||
- `[1]`: First we create a class that inherits from [`px4_ros2::ModeExecutorBase`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1ModeExecutorBase.html).
|
||||
- `[2]`: The constructor takes our custom mode that is associated with the executor and passes it to the constructor of `ModeExecutorBase`.
|
||||
- `[3]`: We define an enum for the states we want to run through.
|
||||
- `[4]`: `onActivate` gets called when the executor becomes active. At this point we can start to run through our states.
|
||||
How you do this is up to you, in this example a method `runState` is used to execute the next state.
|
||||
- `[5]`: On switching to a state we call an asynchronous method from `ModeExecutorBase` to start the desired mode: `run`, `takeoff`, `rtl`, and so on.
|
||||
These methods are passed a function that is called on completion; the callback provides a `Result` argument that tells you whether the operation succeeded or not.
|
||||
The callback runs the next state on success.
|
||||
- `[6]`: We use the `scheduleMode()` method to start the executor's "owned mode", following the same pattern as the other state handlers.
|
||||
|
||||
### Setpoint Types
|
||||
|
||||
A mode can choose its setpoint type(s) it wants to use to control the vehicle.
|
||||
The used types also define the compatibility with different vehicle types.
|
||||
|
||||
The following sections provide a list of supported setpoint types:
|
||||
|
||||
- [GotoSetpointType](#go-to-setpoint-gotosetpointtype): Smooth position and (optionally) heading control
|
||||
- [DirectActuatorsSetpointType](#direct-actuator-control-setpoint-directactuatorssetpointtype): Direct control of motors and flight surface servo setpoints
|
||||
|
||||
:::tip
|
||||
The other setpoint types are currently experimental, and can be found in: [px4_ros2/control/setpoint_types/experimental](https://github.com/Auterion/px4-ros2-interface-lib/tree/main/px4_ros2_cpp/include/px4_ros2/control/setpoint_types/experimental).
|
||||
|
||||
You can add your own setpoint types by adding a class that inherits from `px4_ros2::SetpointBase`, sets the configuration flags according to what the setpoint requires, and then publishes any topic containing a setpoint.
|
||||
:::
|
||||
|
||||
#### Go-to Setpoint (GotoSetpointType)
|
||||
|
||||
:::info
|
||||
This setpoint type is currently only supported for multicopters.
|
||||
:::
|
||||
|
||||
Smoothly control position and (optionally) heading setpoints with the [px4_ros2::GotoSetpointType](https://github.com/Auterion/px4-ros2-interface-lib/blob/main/px4_ros2_cpp/include/px4_ros2/control/setpoint_types/goto.hpp) setpoint type.
|
||||
The setpoint type is streamed to FMU based position and heading smoothers formulated with time-optimal, maximum-jerk trajectories, with velocity and acceleration constraints.
|
||||
|
||||
The most trivial use is simply inputting a 3D position into the update method:
|
||||
|
||||
```cpp
|
||||
const Eigen::Vector3f target_position_m{-10.F, 0.F, 3.F};
|
||||
_goto_setpoint->update(target_position_m);
|
||||
```
|
||||
|
||||
In this case, heading will remain _uncontrolled_.
|
||||
To additionally control heading, specify it as the second input argument:
|
||||
|
||||
```cpp
|
||||
const Eigen::Vector3f target_position_m{-10.F, 0.F, 3.F};
|
||||
const float heading_rad = 3.14F;
|
||||
_goto_setpoint->update(
|
||||
target_position_m,
|
||||
heading_rad);
|
||||
```
|
||||
|
||||
An additional feature of the go-to setpoint is dynamic control on the underlying smoothers' speed limits (i.e. maximum horizontal and vertical translational velocities as well as heading rate).
|
||||
If, as above, left unspecified, the smoothers will default to the vehicle's default maximums (typically set to the physical limitations).
|
||||
The smoothers will _only_ decrease speed limits, never increase.
|
||||
|
||||
```cpp
|
||||
_goto_setpoint->update(
|
||||
target_position_m,
|
||||
heading_rad,
|
||||
max_horizontal_velocity_m_s,
|
||||
max_vertical_velocity_m_s,
|
||||
max_heading_rate_rad_s);
|
||||
```
|
||||
|
||||
All arguments in the update method except the position are templated as `std::optional<float>`, meaning that if one desires constraining the heading rate, but not the translating velocities, this is possible using a `std::nullopt`:
|
||||
|
||||
```cpp
|
||||
_goto_setpoint->update(
|
||||
target_position_m,
|
||||
heading_rad,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
max_heading_rate_rad_s);
|
||||
```
|
||||
|
||||
#### Direct Actuator Control Setpoint (DirectActuatorsSetpointType)
|
||||
|
||||
Actuators can be directly controlled using the [px4_ros2::DirectActuatorsSetpointType](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1DirectActuatorsSetpointType.html) setpoint type.
|
||||
Motors and servos can be set independently.
|
||||
Be aware that the assignment is vehicle and setup-specific.
|
||||
For example to control a quadrotor, you need to set the first 4 motors according to its [output configuration](../concept/control_allocation.md).
|
||||
|
||||
:::info
|
||||
If you want to control an actuator that does not control the vehicle's motion, but for example a payload servo, see [below](#controlling-an-independent-actuator-servo).
|
||||
:::
|
||||
|
||||
### Controlling an Independent Actuator/Servo
|
||||
|
||||
If you want to control an independent actuator (a servo), follow these steps:
|
||||
|
||||
1. [Configure the output](../payloads/generic_actuator_control.md#generic-actuator-control-with-mavlink)
|
||||
2. Create an instance of [px4_ros2::PeripheralActuatorControls](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1PeripheralActuatorControls.html) in the constructor of your mode.
|
||||
3. Call the `set()` method to control the actuator(s).
|
||||
This can be done independently of any active setpoints.
|
||||
|
||||
### 텔레메트리
|
||||
|
||||
You can access PX4 telemetry topics directly via the following classes:
|
||||
|
||||
- [OdometryGlobalPosition](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1OdometryGlobalPosition.html): Global position
|
||||
- [OdometryLocalPosition](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1OdometryLocalPosition.html): Local position, velocity, acceleration, and heading
|
||||
- [OdometryAttitude](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1OdometryAttitude.html): Vehicle attitude
|
||||
|
||||
For example, you can query the vehicle's current position estimate as follows:
|
||||
|
||||
```cpp
|
||||
std::shared_ptr<px4_ros2::OdometryLocalPosition> _vehicle_local_position;
|
||||
...
|
||||
|
||||
// Get vehicle's last local position
|
||||
_vehicle_local_position->positionNed();
|
||||
|
||||
// Check last horizontal position is valid
|
||||
_vehicle_local_position->positionXYValid();
|
||||
```
|
||||
|
||||
:::info
|
||||
These topics provide a wrapper around the internal PX4 topics, allowing the library to maintain compatibility if the internal topics change.
|
||||
Check [px4_ros2/odometry](https://github.com/Auterion/px4-ros2-interface-lib/tree/main/px4_ros2_cpp/include/px4_ros2/odometry) for new topics, and of course you can use any ROS 2 topic published from PX4.
|
||||
:::
|
||||
|
||||
### Failsafes and Mode Requirements
|
||||
|
||||
Each mode has a set of requirement flags.
|
||||
These are generally automatically set, depending on which objects are used within the context of a mode.
|
||||
For example when adding manual control input with the code below the requirement flag for manual control gets set:
|
||||
|
||||
```cpp
|
||||
_manual_control_input = std::make_shared<px4_ros2::ManualControlInput>(*this);
|
||||
```
|
||||
|
||||
Specifically, setting a flag has the following consequences in PX4, if the condition is not met:
|
||||
|
||||
- arming is not allowed, while the mode is selected
|
||||
- when already armed, the mode cannot be selected
|
||||
- when armed and the mode is selected, the relevant failsafe is triggered (e.g. RC loss for the manual control requirement).
|
||||
Check the [safety page](../config/safety.md) for how to configure failsafe behavior.
|
||||
A failsafe is also triggered when the mode crashes or becomes unresponsive while it is selected.
|
||||
|
||||
This is the corresponding flow diagram for the manual control flag:
|
||||
|
||||

|
||||
|
||||
<!-- source: https://drive.google.com/file/d/1g_NlQlw7ROLP_mAi9YY2nDwP0zTNsFlB/view -->
|
||||
|
||||
It is possible to manually update any mode requirement after the mode is registered.
|
||||
For example to add home position as requirement:
|
||||
|
||||
```cpp
|
||||
modeRequirements().home_position = true;
|
||||
```
|
||||
|
||||
The full list of flags can be found in [requirement_flags.hpp](https://github.com/Auterion/px4-ros2-interface-lib/blob/main/px4_ros2_cpp/include/px4_ros2/common/requirement_flags.hpp).
|
||||
|
||||
#### Deferring Failsafes
|
||||
|
||||
A mode or mode executor can temporarily defer non-essential failsafes by calling the method [`deferFailsafesSync()`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1ModeExecutorBase.html#a16ec5be6ebe70e1d0625bf696c3e29ae).
|
||||
To get notified when a failsafe would be triggered, override the method [`void onFailsafeDeferred()`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1ModeExecutorBase.html#ad80a234c8cb2f4c186fa2b7bffd1a1dd).
|
||||
|
||||
Check the [integration test](https://github.com/Auterion/px4-ros2-interface-lib/blob/main/px4_ros2_cpp/test/integration/overrides.cpp) for an example.
|
||||
|
||||
### Assigning a Mode to an RC Switch or Joystick Action
|
||||
|
||||
External modes can be assigned to [RC switches](../config/flight_mode.md) or joystick actions.
|
||||
When assigning a mode to an RC switch, you need to know the index (because the parameter metadata does not contain the dynamic mode name).
|
||||
Use `commander status` while the mode is running to get that information.
|
||||
|
||||
예:
|
||||
|
||||
```plain
|
||||
INFO [commander] External Mode 1: nav_state: 23, name: My Manual Mode
|
||||
```
|
||||
|
||||
means you would select **External Mode 1** in QGC:
|
||||
|
||||

|
||||
|
||||
:::info
|
||||
PX4 ensures a given mode is always assigned to the same index by storing a hash of the mode name.
|
||||
This makes it independent of startup ordering in case of multiple external modes.
|
||||
:::
|
||||
|
||||
### Replacing an Internal Mode
|
||||
|
||||
An external mode can replace an existing internal mode, such as [Return](../flight_modes/return.md) mode (RTL).
|
||||
By doing so, whenever RTL gets selected (through the user or a failsafe situation), the external mode is used instead of the internal one.
|
||||
The internal one is only used as a fallback when the external one becomes unresponsive or crashes.
|
||||
|
||||
The replacement mode can be set in the settings of the `ModeBase` constructor:
|
||||
|
||||
```cpp
|
||||
Settings{kName, false, ModeBase::kModeIDRtl}
|
||||
```
|
||||
@@ -0,0 +1,74 @@
|
||||
# PX4 ROS 2 Interface Library
|
||||
|
||||
<Badge type="tip" text="PX4 v1.15" /> <Badge type="warning" text="Experimental" />
|
||||
|
||||
:::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](https://github.com/Auterion/px4-ros2-interface-lib) is a C++ library that simplifies controlling and interacting with PX4 from ROS 2.
|
||||
|
||||
The library provides two high-level interfaces for developers:
|
||||
|
||||
1. The [Control Interface](./px4_ros2_control_interface.md) allows developers to create and dynamically register modes written using ROS 2.
|
||||
It provides classes for sending different types of setpoints, ranging from high-level navigation tasks all the way down to direct actuator controls.
|
||||
2. The [Navigation Interface](./px4_ros2_navigation_interface.md) enables sending vehicle position estimates to PX4 from ROS 2 applications, such as a VIO system.
|
||||
|
||||
<!--
|
||||
## Overview
|
||||
-->
|
||||
|
||||
## Installation in a ROS 2 Workspace
|
||||
|
||||
To get started using the library within an existing ROS 2 workspace:
|
||||
|
||||
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
|
||||
source install/setup.bash
|
||||
```
|
||||
|
||||
<!--
|
||||
## How to Use the Library
|
||||
-->
|
||||
|
||||
## CI: Integration Tests
|
||||
|
||||
When opening a pull request to PX4, CI runs the library integration tests.
|
||||
These test that mode registration, failsafes, and mode replacement, work as expected.
|
||||
|
||||
The tests can also be run locally from PX4:
|
||||
|
||||
```sh
|
||||
./test/ros_test_runner.py
|
||||
```
|
||||
|
||||
And to run only a single case:
|
||||
|
||||
```sh
|
||||
./test/ros_test_runner.py --verbose --case <case>
|
||||
```
|
||||
|
||||
You can list the available test cases with:
|
||||
|
||||
```sh
|
||||
./test/ros_test_runner.py --list-cases
|
||||
```
|
||||
@@ -0,0 +1,452 @@
|
||||
# PX4 ROS 2 Message Translation Node
|
||||
|
||||
<Badge type="tip" text="main (planned for: PX4 v1.16+)" /> <Badge type="warning" text="Experimental" />
|
||||
|
||||
The message translation node allows ROS 2 applications that were compiled against different versions of the PX4 messages to interwork with newer versions of PX4, and vice versa, without having to change either the application or the PX4 side.
|
||||
|
||||
## 개요
|
||||
|
||||
The translation of messages from one definition version to another is possible thanks to the introduction of [message versioning](../middleware/uorb.md#message-versioning).
|
||||
|
||||
The translation node has access to all message versions previously defined by PX4.
|
||||
It dynamically observes the DDS data space, monitoring the publications, subscriptions and services originating from either PX4 via the [uXRCE-DDS Bridge](../middleware/uxrce_dds.md), or ROS 2 applications.
|
||||
When necessary, it converts messages to the current versions expected by both applications and PX4, ensuring compatibility.
|
||||
|
||||

|
||||
|
||||
<!-- doc source: ../../assets/middleware/ros2/px4_ros2_interface_lib/translation_node.drawio -->
|
||||
|
||||
To support the coexistence of different versions of the same messages within the ROS 2 domain, the ROS 2 topic-names for publications, subscriptions, and services include their respective message version as a suffix.
|
||||
This naming convention takes the form `<topic_name>_v<version>`, as shown in the diagram above.
|
||||
|
||||
## 사용법
|
||||
|
||||
### 설치
|
||||
|
||||
The following steps describe how to install and run the translation node on your machine.
|
||||
|
||||
1. (Optional) Create a new ROS 2 workspace in which to build the message translation node and its dependencies:
|
||||
|
||||
```sh
|
||||
mkdir -p /path/to/ros_ws/src
|
||||
```
|
||||
|
||||
2. Run the following helper script to copy the message definitions and translation node into your ROS workspace directory.
|
||||
|
||||
```sh
|
||||
cd /path/to/ros_ws
|
||||
/path/to/PX4-Autopilot/Tools/copy_to_ros_ws.sh .
|
||||
```
|
||||
|
||||
3. Build and source the workspace.
|
||||
|
||||
```sh
|
||||
colcon build
|
||||
source /path/to/ros_ws/install/setup.bash
|
||||
```
|
||||
|
||||
4. Finally, run the translation node.
|
||||
|
||||
```sh
|
||||
ros2 run translation_node translation_node_bin
|
||||
```
|
||||
|
||||
You should see an output similar to:
|
||||
|
||||
```sh
|
||||
[INFO] [1734525720.729530513] [translation_node]: Registered pub/sub topics and versions:
|
||||
[INFO] [1734525720.729594413] [translation_node]: Registered services and versions:
|
||||
```
|
||||
|
||||
With the translation node running, any simultaneously running ROS 2 application designed to communicate with PX4 can do so, as long as it uses message versions recognized by the node.
|
||||
The translation node will print a warning if it encounters an unknown topic version.
|
||||
|
||||
:::info
|
||||
After making a modification in PX4 to the message definitions and/or translation node code, you will need to rerun the steps above from point 2 to update your ROS workspace accordingly.
|
||||
:::
|
||||
|
||||
### In ROS Applications
|
||||
|
||||
While developing a ROS 2 application that communicates with PX4, it is not necessary to know the specific version of a message being used.
|
||||
The message version can be added generically to a topic name like this:
|
||||
|
||||
:::: tabs
|
||||
|
||||
:::tab C++
|
||||
|
||||
```c++
|
||||
topic_name + "_v" + std::to_string(T::MESSAGE_VERSION)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::tab Python
|
||||
|
||||
```python
|
||||
topic_name + "_v" + VehicleAttitude.MESSAGE_VERSION
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
where `T` is the message type, e.g. `px4_msgs::msg::VehicleAttitude`.
|
||||
|
||||
For example, the following implements a minimal subscriber and publisher node that uses two versioned PX4 messages and topics:
|
||||
|
||||
:::: tabs
|
||||
|
||||
:::tab C++
|
||||
|
||||
```c++
|
||||
#include <string>
|
||||
#include <rclcpp/rclcpp.hpp>
|
||||
#include <px4_msgs/msg/vehicle_command.hpp>
|
||||
#include <px4_msgs/msg/vehicle_attitude.hpp>
|
||||
|
||||
// Template function to get the message version suffix
|
||||
// The correct message version is directly inferred from the message defintion
|
||||
template <typename T>
|
||||
std::string getMessageNameVersion() {
|
||||
if (T::MESSAGE_VERSION == 0) return "";
|
||||
return "_v" + std::to_string(T::MESSAGE_VERSION);
|
||||
}
|
||||
|
||||
class MinimalPubSub : public rclcpp::Node {
|
||||
public:
|
||||
MinimalPubSub() : Node("minimal_pub_sub") {
|
||||
// Use template function to define the correct topics automatically
|
||||
const std::string sub_topic = "/fmu/out/vehicle_attitude" + getMessageNameVersion<px4_msgs::msg::VehicleAttitude>();
|
||||
const std::string pub_topic = "/fmu/in/vehicle_command" + getMessageNameVersion<px4_msgs::msg::VehicleCommand>();
|
||||
|
||||
_subscription = this->create_subscription<px4_msgs::msg::VehicleAttitude>(
|
||||
sub_topic, 10,
|
||||
std::bind(&MinimalPubSub::attitude_callback, this, std::placeholders::_1));
|
||||
_publisher = this->create_publisher<px4_msgs::msg::VehicleCommand>(pub_topic, 10);
|
||||
}
|
||||
|
||||
private:
|
||||
void attitude_callback(const px4_msgs::msg::VehicleAttitude::SharedPtr msg) {
|
||||
RCLCPP_INFO(this->get_logger(), "Received attitude message.");
|
||||
}
|
||||
|
||||
rclcpp::Publisher<px4_msgs::msg::VehicleCommand>::SharedPtr _publisher;
|
||||
rclcpp::Subscription<px4_msgs::msg::VehicleAttitude>::SharedPtr _subscription;
|
||||
};
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::tab Python
|
||||
|
||||
```python
|
||||
import rclpy
|
||||
from rclpy.node import Node
|
||||
from px4_msgs.msg import VehicleCommand, VehicleAttitude
|
||||
|
||||
# Helper function to get the message version suffix
|
||||
# The correct message version is directly inferred from the message definition
|
||||
def get_message_name_version(msg_class):
|
||||
if msg_class.MESSAGE_VERSION == 0:
|
||||
return ""
|
||||
return f"_v{msg_class.MESSAGE_VERSION}"
|
||||
|
||||
class MinimalPubSub(Node):
|
||||
def __init__(self):
|
||||
super().__init__('minimal_pub_sub')
|
||||
|
||||
# Use helper function to define the correct topics automatically
|
||||
sub_topic = f"/fmu/out/vehicle_attitude{get_message_name_version(VehicleAttitude)}"
|
||||
pub_topic = f"/fmu/in/vehicle_command{get_message_name_version(VehicleCommand)}"
|
||||
|
||||
self._subscription = self.create_subscription(
|
||||
VehicleAttitude,
|
||||
sub_topic,
|
||||
self.attitude_callback,
|
||||
10
|
||||
)
|
||||
|
||||
self._publisher = self.create_publisher(
|
||||
VehicleCommand,
|
||||
pub_topic,
|
||||
10
|
||||
)
|
||||
|
||||
def attitude_callback(self, msg):
|
||||
self.get_logger().info("Received attitude message.")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
On the PX4 side, the DDS client automatically adds the version suffix if a message definition contains the field `uint32 MESSAGE_VERSION = x`.
|
||||
|
||||
:::info
|
||||
Version 0 of a topic means that no `_v<version>` suffix should be added.
|
||||
:::
|
||||
|
||||
## Development
|
||||
|
||||
### Definitions
|
||||
|
||||
A **message** defines the data format used for communication, whether over a topic or a service.
|
||||
Therefore a message can be either a _topic_ message defined by a `.msg` file, or a _service_ message defined by a `.srv` file.
|
||||
|
||||
A **versioned message** is a message for which changes are tracked and each change results in a version bump, with the previous state of the definition being stored in history.
|
||||
The latest version of every message is stored in `msg/versioned/` for topics (or `srv/versioned` for services), and all older versions are stored in `msg/px4_msgs_old/msg/` (or `msg/px4_msgs_old/srv/`).
|
||||
|
||||
A **version translation** defines a bidirectional mapping of the contents of one or more message definition across different versions.
|
||||
Each translation is stored as a separate `.h` header file under `msg/translation_node/translations/`.
|
||||
Message translations can be either _direct_ or _generic_.
|
||||
|
||||
- A **direct translation** defines a bidirectional mapping of the contents of a _single_ message between two of its versions.
|
||||
This is the simpler case and should be preferred if possible.
|
||||
- A **generic translation** defines a bidirectional mapping of the contents of `n` input messages to `m` output messages across different versions.
|
||||
This can be used for merging or splitting a message, or when moving a field from one message to another.
|
||||
|
||||
### File Structure
|
||||
|
||||
Starting from PX4 v1.16 (main), the PX4-Autopilot `msg/` and `srv/` directories are structured as follows:
|
||||
|
||||
```
|
||||
PX4-Autopilot
|
||||
├── ...
|
||||
├── msg/
|
||||
├── *.msg # Non-versioned topic message files
|
||||
├── versioned/ # Latest versioned topic message files
|
||||
├── px4_msgs_old/ # History of versioned messages (.msg + .srv) [ROS 2 package]
|
||||
└── translation_node/ # Translation node and translation headers [ROS 2 package]
|
||||
└── srv/
|
||||
├── *.srv # Non-versioned service message files
|
||||
└── versioned/ # Latest versioned service message files
|
||||
```
|
||||
|
||||
This structure introduces new directories: `versioned/`, `px4_msgs_old/`, and `translation_node/`.
|
||||
|
||||
#### Directories `msg/versioned/` and `srv/versioned/`
|
||||
|
||||
- Contain the current latest version of each message.
|
||||
- Files in these directories must include a `MESSAGE_VERSION` field to indicate that they are versioned.
|
||||
- File names follow the conventional naming scheme (without a version suffix).
|
||||
|
||||
Example directory structure:
|
||||
|
||||
```
|
||||
PX4-Autopilot
|
||||
├── ...
|
||||
├── msg/
|
||||
└── versioned/
|
||||
├── VehicleAttitude.msg # e.g. MESSAGE_VERSION = 3
|
||||
└── VehicleGlobalPosition.msg # e.g. MESSAGE_VERSION = 2
|
||||
└── srv/
|
||||
└── versioned/
|
||||
└── VehicleCommand.srv # e.g. MESSAGE_VERSION = 2
|
||||
```
|
||||
|
||||
#### Directory `px4_msgs_old/`
|
||||
|
||||
- Archives the history of all versioned messages, including both topic and service messages (resp. under `msg/` and `srv/` subdirectories).
|
||||
- Each file includes a `MESSAGE_VERSION` field.
|
||||
- File names reflect the message's version with a suffix (e.g., `V1`, `V2`).
|
||||
|
||||
Example directory structure (matching the example above):
|
||||
|
||||
```
|
||||
...
|
||||
msg/
|
||||
└── px4_msgs_old/
|
||||
├── msg/
|
||||
├── VehicleAttitudeV1.msg
|
||||
├── VehicleAttitudeV2.msg
|
||||
└── VehicleGlobalPositionV1.msg
|
||||
└── srv/
|
||||
└── VehicleCommandV1.srv
|
||||
```
|
||||
|
||||
#### Directory `translation_node/`
|
||||
|
||||
- Contains headers for translating between all different versions of messages.
|
||||
- Each translation (direct or generic) is a single `.h` header file.
|
||||
- The header `all_translation.h` acts as the main header, and includes all subsequent translation headers.
|
||||
|
||||
Example directory structure (matching the example above):
|
||||
|
||||
```
|
||||
...
|
||||
msg/
|
||||
└── translation_node/
|
||||
└── translations/
|
||||
├── all_translations.h # Main header
|
||||
├── translation_vehicle_attitude_v1.h # Direct translation v0 <-> v1
|
||||
├── translation_vehicle_attitude_v2.h # Direct translation v1 <-> v2
|
||||
├── translation_vehicle_attitude_v3.h # Direct translation v2 <-> latest (v3)
|
||||
├── translation_vehicle_global_position_v1.h # Direct translation v0 <-> v1
|
||||
├── translation_vehicle_global_position_v2.h # Direct translation v1 <-> latest (v2)
|
||||
├── translation_vehicle_command_v1.h # Direct translation v0 <-> v1
|
||||
└── translation_vehicle_command_v2.h # Direct translation v1 <-> latest (v2)
|
||||
```
|
||||
|
||||
### Updating a Versioned Message
|
||||
|
||||
This section provides a step-by-step walkthrough and a basic working example of what the process of changing a versioned message looks like.
|
||||
|
||||
The example describes the process of updating the `VehicleAttitude` message definition to contain an additional `new_field` entry, incrementing the message version from `3` to `4`, and creating a new direct translation in the process.
|
||||
|
||||
1. **Create an Archived Definition of the Current Versioned Message**
|
||||
|
||||
Copy the versioned `.msg` topic message file (or `.srv` service message file) to `px4_msgs_old/msg/` (or `px4_msgs_old/srv/`), and append its message version to the file name.
|
||||
|
||||
For example:<br>
|
||||
Copy `msg/versioned/VehicleAttitude.msg` → `msg/versioned/px4_msgs_old/msg/VehicleAttitudeV3.msg`
|
||||
|
||||
2. **Update Translation References to the Archived Definition**
|
||||
|
||||
Update the existing translations header files `msg/translation_node/translations/*.h` to reference the newly archived message definition.
|
||||
|
||||
For example, update references in those files:<br>
|
||||
|
||||
- Replace `px4_msgs::msg::VehicleAttitude` → `px4_msgs_old::msg::VehicleAttitudeV3`
|
||||
- Replace `#include <px4_msgs/msg/vehicle_attitude.hpp>` → `#include <px4_msgs_old/msg/vehicle_attitude_v3.hpp>`
|
||||
|
||||
3. **Update the Versioned Definition**
|
||||
|
||||
Update the versioned `.msg` topic message file (or `.srv` service message file) with required changes.
|
||||
|
||||
First increment the `MESSAGE_VERSION` field.
|
||||
Then update the message fields that prompted the version change.
|
||||
|
||||
For example, update `msg/versioned/VehicleAttitude.msg` from:
|
||||
|
||||
```.msg
|
||||
uint32 MESSAGE_VERSION = 3
|
||||
uint64 timestamp
|
||||
...
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```.msg
|
||||
uint32 MESSAGE_VERSION = 4 # Increment
|
||||
uint64 timestamp
|
||||
float32 new_field # Make definition changes
|
||||
...
|
||||
```
|
||||
|
||||
4. **Add a New Translation Header**
|
||||
|
||||
Add a new version translation to bridge the archived version and the updated current version, by creating a new translation header.
|
||||
|
||||
For example, create a direct translation header `translation_node/translations/translation_vehicle_attitude_v4.h`:
|
||||
|
||||
```c++
|
||||
// Translate VehicleAttitude v3 <--> v4
|
||||
#include <px4_msgs_old/msg/vehicle_attitude_v3.hpp>
|
||||
#include <px4_msgs/msg/vehicle_attitude.hpp>
|
||||
|
||||
class VehicleAttitudeV4Translation {
|
||||
public:
|
||||
using MessageOlder = px4_msgs_old::msg::VehicleAttitudeV3;
|
||||
static_assert(MessageOlder::MESSAGE_VERSION == 3);
|
||||
|
||||
using MessageNewer = px4_msgs::msg::VehicleAttitude;
|
||||
static_assert(MessageNewer::MESSAGE_VERSION == 4);
|
||||
|
||||
static constexpr const char* kTopic = "fmu/out/vehicle_attitude";
|
||||
|
||||
static void fromOlder(const MessageOlder &msg_older, MessageNewer &msg_newer) {
|
||||
msg_newer.timestamp = msg_older.timestamp;
|
||||
msg_newer.timestamp_sample = msg_older.timestamp_sample;
|
||||
msg_newer.q[0] = msg_older.q[0];
|
||||
msg_newer.q[1] = msg_older.q[1];
|
||||
msg_newer.q[2] = msg_older.q[2];
|
||||
msg_newer.q[3] = msg_older.q[3];
|
||||
msg_newer.delta_q_reset = msg_older.delta_q_reset;
|
||||
msg_newer.quat_reset_counter = msg_older.quat_reset_counter;
|
||||
|
||||
// Populate `new_field` with some value
|
||||
msg_newer.new_field = -1;
|
||||
}
|
||||
|
||||
static void toOlder(const MessageNewer &msg_newer, MessageOlder &msg_older) {
|
||||
msg_older.timestamp = msg_newer.timestamp;
|
||||
msg_older.timestamp_sample = msg_newer.timestamp_sample;
|
||||
msg_older.q[0] = msg_newer.q[0];
|
||||
msg_older.q[1] = msg_newer.q[1];
|
||||
msg_older.q[2] = msg_newer.q[2];
|
||||
msg_older.q[3] = msg_newer.q[3];
|
||||
msg_older.delta_q_reset = msg_newer.delta_q_reset;
|
||||
msg_older.quat_reset_counter = msg_newer.quat_reset_counter;
|
||||
|
||||
// Discards `new_field` from MessageNewer
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_TOPIC_TRANSLATION_DIRECT(VehicleAttitudeV4Translation);
|
||||
```
|
||||
|
||||
Version translation templates are provided here:
|
||||
|
||||
- [Direct Topic Message Translation Template](https://github.com/PX4/PX4-Autopilot/blob/main/msg/translation_node/translations/example_translation_direct_v1.h)
|
||||
- [Generic Topic Message Translation Template](https://github.com/PX4/PX4-Autopilot/blob/main/msg/translation_node/translations/example_translation_multi_v2.h)
|
||||
- [Direct Service Message Translation Template](https://github.com/PX4/PX4-Autopilot/blob/main/msg/translation_node/translations/example_translation_service_v1.h)
|
||||
|
||||
5. **Include New Headers in `all_translations.h`**
|
||||
|
||||
Add all newly created headers to [`translations/all_translations.h`](https://github.com/PX4/PX4-Autopilot/blob/main/msg/translation_node/translations/all_translations.h) so that the translation node can find them.
|
||||
|
||||
For example, append the following line to `all_translation.h`:
|
||||
|
||||
```c++
|
||||
#include "translation_vehicle_attitude_v4.h"
|
||||
```
|
||||
|
||||
Note that in the example above and in most cases, step 4 only requires the developer to create a direct translation for the definition change.
|
||||
This is because the changes only involved a single message.
|
||||
In more complex cases of splitting, merging and/or moving definitions then a generic translation must be created.
|
||||
|
||||
For example when moving a field from one message to another, a single generic translation should be added with the two older message versions as input, and the two newer versions as output.
|
||||
This ensures there is no information lost when translating forward or backward.
|
||||
|
||||
This is exactly the approach shown by the [Generic Topic Message Translation Template](https://github.com/PX4/PX4-Autopilot/blob/main/msg/translation_node/translations/example_translation_multi_v2.h), omitting only the code for actually modifying fields in the `fromOlder()` and `toOlder()` methods.
|
||||
|
||||
:::warning
|
||||
If a nested message definition changes, all messages including that message also require a version update.
|
||||
For example this would be the case for message [PositionSetpointTriplet](../msg_docs/PositionSetpointTriplet.md) if it were versioned.
|
||||
This is primarily important for services which are more likely reference other message definitions.
|
||||
:::
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The translation node dynamically monitors the topics and services.
|
||||
It then instantiates the counterside of the publications and subscribers as required.
|
||||
For example if there is an external publisher for version 1 of a topic and subscriber for version 2.
|
||||
|
||||
Internally, it maintains a graph of all known topic and version tuples (which are the graph nodes).
|
||||
The graph is connected by the message translations.
|
||||
As arbitrary message translations can be registered, the graph can have cycles and multiple paths from one node to another.
|
||||
Therefore on a topic update, the graph is traversed using a shortest path algorithm.
|
||||
When moving from one node to the next, the message translation method is called with the current topic data.
|
||||
If a node contains an instantiated publisher (because it previously detected an external subscriber), the data is published.
|
||||
Thus, multiple subscribers of any version of the topic can be updated with the correct version of the data.
|
||||
|
||||
For translations with multiple input topics, the translation continues once all input messages are available.
|
||||
|
||||
## 제한 사항
|
||||
|
||||
- Translation of service messages does not work on ROS Humble, but does on ROS Jazzy.
|
||||
This is because the current implementation depends on a service API that is not yet available in ROS Humble.
|
||||
Translation of topic messages is fully supported.
|
||||
- Services messages only support a linear history, i.e. no message splitting or merging.
|
||||
- Having both publishers and subscribers for two different versions of the same topic is currently not handled by the translation node and would trigger infinite circular publications.
|
||||
This refers to the following problematic configuration:
|
||||
|
||||
```
|
||||
app 1: pub topic_v1, sub topic_v1
|
||||
app 2: pub topic_v2, sub topic_v2
|
||||
```
|
||||
|
||||
In practice this configuration is unlikely to occur because ROS topics shared with the FMU are intended to be directional (e.g. `/fmu/out/vehicle_status` or `/fmu/in/trajectory_setpoint`), therefore apps typically do not publish and subscribe simultaneously to the same topic.
|
||||
The translation node could be extended to handle this corner-case if required.
|
||||
|
||||
Original document with requirements: https://docs.google.com/document/d/18_RxV1eEjt4haaa5QkFZAlIAJNv9w5HED2aUEiG7PVQ/edit?usp=sharing
|
||||
@@ -0,0 +1,308 @@
|
||||
# PX4 ROS 2 Navigation Interface
|
||||
|
||||
<Badge type="tip" text="PX4 v1.15" /> <Badge type="warning" text="Experimental" />
|
||||
|
||||
:::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<Eigen::Vector2f> position_xy {std::nullopt};
|
||||
std::optional<Eigen::Vector2f> position_xy_variance {std::nullopt};
|
||||
std::optional<float> position_z {std::nullopt};
|
||||
std::optional<float> position_z_variance {std::nullopt};
|
||||
|
||||
std::optional<Eigen::Vector2f> velocity_xy {std::nullopt};
|
||||
std::optional<Eigen::Vector2f> velocity_xy_variance {std::nullopt};
|
||||
std::optional<float> velocity_z {std::nullopt};
|
||||
std::optional<float> velocity_z_variance {std::nullopt};
|
||||
|
||||
std::optional<Eigen::Quaternionf> attitude_quaternion {std::nullopt};
|
||||
std::optional<Eigen::Vector3f> 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<px4_ros2::LocalPositionMeasurementInterface>(*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<px4_ros2::LocalPositionMeasurementInterface> _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<Eigen::Vector2d> lat_lon {std::nullopt};
|
||||
std::optional<float> horizontal_variance {std::nullopt};
|
||||
|
||||
std::optional<float> altitude_msl {std::nullopt};
|
||||
std::optional<float> 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<px4_ros2::GlobalPositionMeasurementInterface>(*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<px4_ros2::GlobalPositionMeasurementInterface> _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.
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user