# PX4 ROS2 导航接口 :::warning Experimental 在撰写本文时,PX4 ROS 2 接口库的部分内容仍处于试验阶段,因此可能会发生变动。 ::: [PX4 ROS 2 Interface Library](../ros2/px4_ros2_interface_lib.md) 中的导航接口,支持开发者直接从 ROS 2 应用(如视觉惯性里程计系统或地图匹配系统)向 PX4 发送位置测量数据。 该接口提供了对 PX4 和 uORB 消息框架的抽象层,并对通过该接口发送的请求状态估计更新引入了一些合理性检查。 这些测量数据随后会被融合到扩展EKF中,其处理方式与 PX4 内部生成的测量数据完全一致。 库提供两个类,[`LocalPositionMeasurementInterface`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1LocalPositionMeasurementInterface.html) 和 [`GlobalPositionMeasureInterface`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1GlobalPositionMeasurementInterface.html) 它都会暴露出一个类似的 "update" 方法来提供一个本地位置或全球位置更新到 PX4。 `update`方法需要一个位置测量`struct`(`LocalPositionMeasure`](https://auterion.github.io/px4-ros2-interface-lib/structpx4__ros2_1_1LocalPositionMeasurement.html)或[\`GlobalPositionMeasure\`](https://auterion.github.io/px4-ros2-interface-lib/structpx4__ros2_1_1GlobalPositionMeasurement.html)],开发者可以在其中填入自己生成的位置测量数据。 ## 安装与首次测试 开始使用前,需完成以下步骤: 1. 请确保您在 ROS 2 工作区中有 [ROS 2 设置](../ros2/user_guide.md) 与 [`px4_msgs`](https://github.com/PX4/px4_msgs]。 2. 将代码仓库克隆到工作空间中 ```sh cd $ros_workspace/src git clone --recursive https://github.com/Auterion/px4-ros2-interface-lib ``` 提示信息 为确保兼容性,请使用 PX4、px4_msgs(PX4 消息包)及该库的最新 main 分支。 另请参阅 [here](https://github.com/Auterion/px4-ros2-interface-lib#compatibility-with-px4) ::: 3. 构建工作空间: ```sh cd .. colcon版本 ``` 4. 在另一个外壳中,启动 PX4 SITL: ```sh cd $px4-autopilot make px4_sitl gazebo-classic ``` (这里我们使用Gazebo-Classic,但你可以使用任何模型或模拟器) 5. 在另一个独立的终端中,运行 micro XRCE 代理(运行后可保持后台持续运行): ```sh MicroXRCEAgent udp4 -p 8888 ``` 6. 返回ROS2终端,为您刚刚构建的工作空间(步骤3),运行 [global_navigation](https://github.com/Auterion/px4-ros2-interface-lib/tree/main/examples/cpp/navigation/global_navigation) 示例。 它会周期性地发送虚拟的全球位置更新(数据): ```sh source install/setup.bash ros2 run example_global_navigation_cpp example_global_navigation ``` 你应会看到如下所示的输出,该输出表明全球位置接口正成功发送位置更新: ```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. 在 PX4 终端(PX4 shell)中,你可以通过以下操作检查 PX4 是否接收到全球位置更新(数据) ```sh listener aux_global_position ``` 输出内容应如下所示: ```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. 现在你已准备好使用该导航接口发送自己的位置更新数据了。 ## 如何使用代码库 要发送位置测量数据,你需要用所测量的值填充位置结构体。 然后以此结构调用接口的更新功能作为参数。 关于如何使用此接口的基本示例,请在“Auterion/px4-ros2-interface-lib”仓库中查看 [examples](https://github.com/Auterion/px4-ros2-interface-lib/tree/main/examples/cpp/navigation) 例如[示例/cpp/navigation/local_navigation](https://github.com/Auterion/px4-ros2-interface-lib/blob/main/examples/cpp/navigation/local_navigation/include/local_navigation.hpp)或[示例/cpp/navigation/global_navigation](https://github.com/Auterion/px4-ros2-interface-lib/blob/main/examples/cpp/navigation/local_navigation/include/global_navigation.hpp)。 ### 局部位置更新 首先确保正确配置 PX4 参数[`EKF2_EV_CTRL`](../advanced_config/parameter_reference.md#EKF2_EV_CTRL)通过将相应的位设置为true,可以正确配置(系统)以融合外部局部测量数据: - `0`: 水平位置数据 - `1`:垂直位置数据 - `2`:速度数据 - `3`:偏航角数据 向PX4发送局部位置测量: 1. 通过提供一个 ROS节点,创建一个 [`localPositionMeasureInterface` ](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1LocalPositionMeasurementInterface.html) 实例:您测量的位置和速度参考框架。 2. 包含一个[`本地定位测量`](https://auterion.github.io/px4-ros2-interface-lib/structpx4__ros2_1_1LocalPositionMeasurement.html) `structt` ,包含你测量的数据。 3. 将 `struct` 传入 `LocalPositionMeasurementInterface` [`update()`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1LocalPositionMeasurementInterface.html#a6fd180b944710716d418b2cfe1c0c8e3) 方法中。 你的测量数据可用的位置和速度参考坐标系由以下枚举(enum)定义: ```cpp enum class PoseFrame { Unknown, LocalNED, LocalFRD }; enum class VelocityFrame { Unknown, LocalNED, LocalFRD, BodyFRD }; ``` `LocalPositionMeasurement`结构定义如下: ```cpp struct LocalPositionMeasurement { rclcpp::Time timestamp_sample {}; std::optional<0> position_xy {std::nullopt}; std::optional<0> position_xy_variance {std::nullopt}; std::optional<1> position_z {std::nullopt}; std::optional<1> position_z_variance {std::nullopt}; std::optional<0> velocity_xy {std::nullopt}; std::optional<0> velocity_xy_variance {std::nullopt}; std::optional<1> velocity_z {std::nullopt}; std::optional<1> velocity_z_variance {std::nullopt}; std::optional<2> attitude_quaternion {std::nullopt}; std::optional<3> attitude_variance {std::nullopt}; }; ``` 局部接口的update()方法要求LocalPositionMeasurement(局部位置测量结构体)满足以下条件: - 示例时间戳已定义。 - 数值不得包含 `NAN` 。 - 如果提供了某个测量值,则其关联的方差值必须明确定义(例如,若position_xy已定义,则position_xy_variance也必须定义)。 - 如果提供了某个测量值,那么其关联的参考坐标系不得为 “未知”(例如,若position_xy已定义,则接口必须以不同于PoseFrame::Unknown的位置坐标系进行初始化)。 以下是一个 ROS 2 节点的示例代码片段,该节点使用局部导航接口向 PX4 发送东北天(NED)参考坐标系下的 3D 位姿更新: ```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; }; ``` ### 全局位置更新 首先确保正确配置 PX4 参数[`EKF2_EV_CTRL`](../advanced_config/parameter_reference.md#EKF2_AGP_CTRL)通过将相应的位设置为true,可以正确配置(系统)以融合外部全部测量数据: - `0`: 水平位置数据 - `1`:垂直位置数据 向PX4发送全局位置测量: 1. 创建一个 [`GlobalPositionMeasureInterface`](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1GlobalPositionMeasurementInterface.html) 实例,并提供一个 ROS节点。 2. 包含一个[`GlobalPositionMeasurement`](https://auterion.github.io/px4-ros2-interface-lib/structpx4__ros2_1_1GlobalPositionMeasurement.html) `struct` ,包含你测量的数据。 3. 将结构移至`GlobalPositionMeasurementInterface` [update()](https://auterion.github.io/px4-ros2-interface-lib/classpx4__ros2_1_1GlobalPositionMeasurementInterface.html#a1a183b595ef7f6a22f3a83ba543fe86d) 方法中。 `GlobalPositionMeasurement`结构定义如下: ```cpp struct GlobalPositionMeasurement { rclcpp::Time timestamp_sample {}; std::optional lat_lon {std::nullopt}; std::optional<1> horizontal_variance {std::nullopt}; std::optional<1> altitude_msl {std::nullopt}; std::optional<1> vertical_variance {std::nullopt}; ``` 全局接口的 `update()` 方法预计在 `GlobalPositionMeasurement` 中保留以下条件: - 样本`timestamp_sample`已定义。 - 数据不得包含NAN。 - 如果提供了某个测量值,那么其关联的方差值必须明确定义(例如,若lat_lon(经纬度)已定义,则horizontal_variance(水平方差)也必须定义)。 下面的代码片段是一个 ROS 2 节点的示例,该节点使用全局导航接口来发送一个测量纬度。 经度和高度到 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<0> _global_position_measurement_interface; }; ``` ## 接口的多个实例 使用同一接口的多个实例(例如,多个局部接口实例)发送估计更新时,会将所有更新消息发送到同一个主题,从而导致串扰。 这不应影响计量并入EKF,但不同的计量来源将无法区分。