用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/MIUAV/vibe-coding-ros2 --skill ros2-bridge命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
基于 SOC 职业分类
| name | ros2-bridge |
| description | PX4 AirSim 与 ROS2 桥接技能 - SITL/HITL 集成、无人机控制、传感器数据同步、多机协同 |
| user-invocable | true |
| argument-hint | PX4 AirSim桥接 OR PX4 ros2桥接 OR 无人机仿真 OR airsim多机 OR SITL HITL |
PX4 Autopilot + AirSim 仿真器与 ROS2 之间的通讯桥接完整指南
当需要以下帮助时使用此技能:
┌─────────────────────────────────────────────────────────────┐
│ PX4 Autopilot │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Commander │───▶│ Navigator │───▶│ Actuator │ │
│ └─────────────┘ └──────────────┘ └───────────────┘ │
└───────────┬─────────────────┬─────────────────────────────┘
│ │
▼ ▼
┌───────────────┐ ┌──────────────┐
│ AirSim API │◄─│ uORB Topics │
│ │ │ (mavlink) │
└───────┬───────┘ └──────────────┘
│ │
▼ ▼
┌───────────────┐ ┌──────────────┐
│ AirSim Sim │ │ MAVLink │
│ │ │ (ROS2) │
└───────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌───────────────┐ ┌──────────────┐
│ Sensors │ │ ROS2 │
│ (Cam/Lidar) │ │ Bridge │
└───────────────┘ └──────────────┘
# 1. 安装 AirSim
git clone https://github.com/microsoft/AirSim.git
cd AirSim
./setup.sh
./build.sh
# 2. 安装 PX4 SITL
git clone --recursive https://github.com/PX4/PX4-Autopilot.git
cd PX4-Autopilot
make px4_sitl_default
# 3. 配置 AirSim 作为 PX4 后端
export PX4_SIMULATOR=AirSim
export PX4_GAZEBO_HOSTNAME=127.0.0.1
# 安装 mavros 和 mavlink
sudo apt install -y ros-humble-mavros ros-humble-mavlink
sudo apt install -y geographiclib-tools
# 初始化 mavros 地理数据库
sudo /opt/ros/humble/lib/mavros/install_geographiclib_datasets.sh
# 源码安装(最新版本)
cd ~/ws/src
git clone -b humble https://github.com/mavlink/mavros.git
git clone -b humble https://github.com/mavlink/mavlink-ros2.git
cd ~/ws && colcon build --packages-select mavros mavros_extras
# px4_mavros.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
# mavros 节点
Node(
package='mavros',
executable='mavros_node',
name='mavros',
parameters=[{
# 链接配置
'pluginlibs': ['mavros'],
'plugin_lock_key': '',
# 系统配置
'system_id': 1,
'component_id': 1,
'mavlink_system': 1,
# 话题命名空间
'fcu_url': 'udp://:14540@127.0.0.1:14557', # PX4 SITL
# 或串口: 'serial:///dev/ttyACM0:921600'
# gz_bridge 需要这个
'fcu_protocol': 'v2.0',
# 传感器/位置源
'sensor_bitrate': 0,
'conn_timeout': 5.0,
'timeout': 5.0,
# 目标系统
'target_system_id': 1,
'target_component_id': 1,
: ,
}],
remappings=[
(, ),
(, ),
(, ),
(, ),
(, ),
(, ),
(, ),
(, ),
(, ),
(, ),
(, ),
(, ),
(, ),
(, ),
],
output=,
emulate_tty=
),
Node(
package=,
executable=,
parameters=[{
:
}]
)
])
# airsim_camera_bridge.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
# 前视相机
Node(
package='airsim_ros_pkgs',
executable='img_pub_node',
name='front_camera',
parameters=[{
'camera_name': 'front_center',
'publish_rate': 30,
'publish_via_ros2': True,
'ros2_namespace': '/drone0',
'topic_id': 'front_camera/image_raw'
}]
),
# 深度相机
Node(
package='airsim_ros_pkgs',
executable='img_pub_node',
name='depth_camera',
parameters=[{
'camera_name': 'depth_center',
'publish_rate': 15,
'publish_via_ros2': True,
'ros2_namespace': '/drone0',
'topic_id': 'depth_camera/image_raw'
}]
),
# 红外相机
Node(
package='airsim_ros_pkgs',
executable='img_pub_node',
name='seg_camera',
parameters=[{
'camera_name': ,
: ,
: ,
: ,
:
}]
),
])
# airsim_lidar_bridge.launch.py
def generate_launch_description():
return LaunchDescription([
Node(
package='airsim_ros_pkgs',
executable='lidar_pub_node',
name='lidar',
parameters=[{
'lidar_name': 'Lidar1',
'publish_rate': 10,
'publish_via_ros2': True,
'ros2_namespace': '/drone0',
'topic_id': 'lidar/scan',
'frame_id': 'lidar_link',
# 激光雷达参数
'points_per_second': 100000,
'angle_min': -3.14159,
'angle_max': 3.14159,
'range_min': 0.5,
'range_max': 100.0,
}]
)
])
# airsim_gps_imu_bridge.launch.py
def generate_launch_description():
return LaunchDescription([
# GPS
Node(
package='airsim_ros_pkgs',
executable='gps_pub_node',
name='gps',
parameters=[{
'gps_name': 'Gps1',
'publish_rate': 10,
'publish_via_ros2': True,
'ros2_namespace': '/drone0',
'topic_id': 'gps/fix'
}]
),
# IMU
Node(
package='airsim_ros_pkgs',
executable='imu_pub_node',
name='imu',
parameters=[{
'imu_name': 'Imu1',
'publish_rate': 100,
'publish_via_ros2': True,
'ros2_namespace': '/drone0',
'topic_id': 'imu/data'
}]
),
])
# drone_control.launch.py
def generate_launch_description():
return LaunchDescription([
# 起飞服务
Node(
package='mavros',
executable='mavros_node',
name='mavros_takeoff',
parameters=[{
'fcu_url': 'udp://:14540@127.0.0.1:14557',
}],
remappings=[
('/mavros/cmd/arming', '/drone0/mavros/cmd/arming'),
('/mavros/cmd/takeoff', '/drone0/mavros/cmd/takeoff'),
('/mavros/cmd/land', '/drone0/mavros/cmd/land'),
]
),
])
# drone_control.py
import rclpy
from rclpy.node import Node
from mavros_msgs.srv import CommandBool, CommandTOL, SetMode
from mavros_msgs.msg import State, GlobalPosition, LocalPosition
from geometry_msgs.msg import PoseStamped, Twist
from geographic_msgs.msg import GeoPoseStamped
class DroneController(Node):
def __init__(self, drone_name='drone0'):
super().__init__(f'{drone_name}_controller')
self.drone_name = drone_name
# 服务客户端
self.arming_client = self.create_client(CommandBool, f'/{drone_name}/mavros/cmd/arming')
self.takeoff_client = self.create_client(CommandTOL, f'/{drone_name}/mavros/cmd/takeoff')
self.land_client = self.create_client(CommandTOL, f'/{drone_name}/mavros/cmd/land')
self.set_mode_client = self.create_client(SetMode, f'/{drone_name}/mavros/set_mode')
# 订阅状态
self.state_sub = self.create_subscription(
State, f'//mavros/state', .state_callback, )
.local_pos_sub = .create_subscription(
PoseStamped, , .pos_callback, )
.cmd_vel_pub = .create_publisher(
Twist, , )
.local_pos_pub = .create_publisher(
PoseStamped, , )
.current_state =
.current_pos =
():
.current_state = msg
():
.current_pos = msg
():
client.wait_for_service(timeout_sec=):
.get_logger().info()
():
.wait_for_service(.arming_client)
req = CommandBool.Request()
req.value =
future = .arming_client.call_async(req)
rclpy.spin_until_future_complete(, future)
future.result().success
():
.wait_for_service(.takeoff_client)
req = CommandTOL.Request()
req.altitude = altitude
req.latitude =
req.longitude =
req.min_pitch =
req.yaw =
future = .takeoff_client.call_async(req)
rclpy.spin_until_future_complete(, future)
future.result().success
():
.wait_for_service(.land_client)
req = CommandTOL.Request()
future = .land_client.call_async(req)
rclpy.spin_until_future_complete(, future)
future.result().success
():
.wait_for_service(.set_mode_client)
req = SetMode.Request()
req.custom_mode = mode
future = .set_mode_client.call_async(req)
rclpy.spin_until_future_complete(, future)
future.result().mode_sent
():
cmd = Twist()
cmd.linear.x = linear[]
cmd.linear.y = linear[]
cmd.linear.z = linear[]
cmd.angular.x = angular[]
cmd.angular.y = angular[]
cmd.angular.z = angular[]
.cmd_vel_pub.publish(cmd)
():
pos = PoseStamped()
pos.header.stamp = .get_clock().now().to_msg()
pos.header.frame_id =
pos.pose.position.x = x
pos.pose.position.y = y
pos.pose.position.z = z
.local_pos_pub.publish(pos)
():
rclpy.init()
controller = DroneController()
controller.get_logger().info()
controller.arm():
controller.get_logger().info()
:
controller.get_logger().error()
controller.get_logger().info()
controller.takeoff(altitude=):
controller.get_logger().info()
:
controller.get_logger().error()
rate = controller.create_rate()
_ ():
controller.publish_cmd_vel(linear=(, , ), angular=(, , ))
rclpy.spin_once(controller)
rate.sleep()
controller.get_logger().info()
controller.land()
rclpy.shutdown()
# settings.json 配置
{
"SeeDocsAt": "https://github.com/Microsoft/AirSim/blob/main/docs/settings.md",
"SettingsVersion": 1.2,
"SimMode": "Multirotor",
"Vehicles": {
"Drone0": {
"VehicleType": "SimpleFlight",
"X": 0, "Y": 0, "Z": 0,
"Yaw": 0,
"Cameras": {
"front_center": {
"CaptureSettings": [
{
"ImageType": 0,
"Width": 640,
"Height": 480
}
]
}
}
},
"Drone1": {
"VehicleType": "SimpleFlight",
"X": 10, "Y": 0, "Z": 0,
"Yaw": 0,
"Cameras": {...}
}
}
}
# multi_drone_bridge.launch.py
def generate_launch_description():
nodes = []
# 为每架无人机启动 mavros
for i, port in enumerate([14540, 14541, 14542]):
drone_ns = f'drone{i}'
nodes.append(
Node(
package='mavros',
executable='mavros_node',
name='mavros',
namespace=drone_ns,
parameters=[{
'system_id': i + 1,
'component_id': 1,
'fcu_url': f'udp://:14540@{127.0.0.1}:{14557 + i}',
'target_system_id': i + 1,
}],
remappings=[
('/mavros/state', f'/{drone_ns}/mavros/state'),
('/mavros/local_position/pose', f'/{drone_ns}/mavros/local_position/pose'),
('/mavros/setpoint_velocity/cmd_vel_unstamped', f'/{drone_ns}/cmd_vel'),
]
)
)
return LaunchDescription(nodes)
# depth_perception.py
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image, CameraInfo
from geometry_msgs.msg import TransformStamped
from cv_bridge import CvBridge
import cv2
import numpy as np
class DepthPerception(Node):
def __init__(self):
super().__init__('depth_perception')
self.bridge = CvBridge()
# 订阅深度图像
self.depth_sub = self.create_subscription(
Image, '/drone0/depth_camera/image_raw', self.depth_callback, 10)
# 订阅 RGB 图像
self.rgb_sub = self.create_subscription(
Image, '/drone0/front_camera/image_raw', self.rgb_callback, 10)
# 发布检测结果
self.detection_pub = self.create_publisher(
Image, '/drone0/detections', 10)
self.latest_depth = None
self.latest_rgb = None
def depth_callback():
.latest_depth = .bridge.imgmsg_to_cv2(msg)
():
.latest_rgb = .bridge.imgmsg_to_cv2(msg)
.latest_depth :
depth_m = np.array(.latest_depth, dtype=np.float32)
valid_depth = depth_m[depth_m > ]
(valid_depth) > :
min_dist = valid_depth.()
max_dist = valid_depth.()
avg_dist = valid_depth.mean()
.get_logger().info(
)
depth_colored = cv2.applyColorMap(
cv2.convertScaleAbs(depth_m, alpha=), cv2.COLORMAP_JET)
out_msg = .bridge.cv2_to_imgmsg(depth_colored, )
.detection_pub.publish(out_msg)
# 检查 mavros 连接状态
ros2 service call /drone0/mavros/get_log_info mavros_msgs/srv/FileClose
# 查看 PX4 状态
ros2 topic echo /drone0/mavros/state
# 检查飞行模式
ros2 topic echo /drone0/mavros/extended_state
# airsim_api_test.py
import airsim
# 连接
client = airsim.MultirotorClient()
client.confirmConnection()
# 获取状态
state = client.getMultirotorState()
print(f"State: {state}")
# 解锁
client.armDisarm(True)
# 起飞
client.takeoff()
# 飞到目标位置
client.moveToPositionAsync(0, 0, -10, 5).join()
# 降落
client.land()
端口配置:确保 PX4 SITL 和 mavros 使用正确的 UDP 端口
时钟同步:PX4 使用仿真时钟,确保 use_sim_time:=true
多机ID:每架无人机使用不同的 system_id
安全检查:飞行前确认无人机状态
GPS原点:AirSim 中设置与 ROS2 地图一致的 GPS 原点
| 问题 | 原因 | 解决方案 |
|---|---|---|
| mavros 连接失败 | 端口被占用 | 检查 PX4 是否启动 |
| 无人机不响应 | 未解锁 | 调用 arming 服务 |
| 位置漂移 | GPS 未同步 | 设置一致的 GPS 原点 |
| 相机无数据 | AirSim 插件未加载 | 检查 settings.json |