
做无人机吊舱/负载开发的工程师应该都有类似的经历飞控已经算好了GPS位置PSDK也把这份数据送到了机载电脑上但下游算法团队只认ROS话题张口就要/gps/navsatfix。前阵子我在妙算3上做负载开发最开始以为这个需求很简单——不就是一个订阅接口加一个ros::Publisher吗结果真正动手才发现把PSDK的数据变成ROS话题不难难的是让CMake把两套构建体系揉在一起还能顺利编译通过。这篇文章从数据链路、PSDK订阅、ROS节点设计、CMake配置、实测验证几个方面完整复盘这条开发链路希望能帮正卡在编译或数据不通上的兄弟一把。适合三类人正在做PSDK负载开发、想把无人机数据接进ROS、以及被CMake折磨到头秃的开发者。1. 先理清链路PSDK在这个需求里到底扮演什么角色1.1 什么项目会产生PSDK把GPS数据交给ROS的需求我这两年接触过的场景大致分三类。第一类是巡检/测绘挂载无人机挂一个多光谱相机或者激光雷达需要在机载电脑上实时记录飞行轨迹把每个采样点对应到经纬度第二类是机载协同无人机自动巡航到一个目标区域后要把自身位置发给地面站或协作编队第三类是数据回传负载采集的数据和GPS信息一起打包推流。这些场景的共同点是定位数据已经在飞控里算好了只是要从机载电脑这边取出来再以ROS话题的形式交出去。这里的机载电脑大部分情况下就是妙算2/3这类嵌入式平台。妙算本身不参与GPS解算它只是跑PSDK的宿主机。PSDK这一层做的事情相当于从飞控的遥测总线上把GPS信息订阅过来然后交给你自己的业务代码。业务代码可以是无人机的控制逻辑也可以是ROS节点。1.2 数据从飞控到ROS话题到底经历了哪几步很多人一开始会把这条链路想得太玄。其实它本质上就是一次跨进程/跨模块的数据搬运完整路径如下飞控内GPS模块 → 飞控内部融合导航算法 → 无人机对外PSDK接口串口/USB/网络 → 机载电脑上的PSDK库 → 订阅回调函数 → 你的应用层代码 → ROS消息发布 → 下游节点订阅GPS模块在飞控内部飞控通过串口或者网络把遥测数据推给PSDK。PSDK初始化之后你注册一个回调飞控上送来一帧遥测回调就被触发一次。回调里拿到的是一个C结构体字段里有经纬度、高度、速度、卫星数。你的任务就是把这个结构体里的数据转换成一个sensor_msgs/NavSatFix消息用ROS发布出去。需要说清楚的是从飞控到机载电脑这一段的传输格式和物理链路PSDK已经替你封装好了你不需要关心串口协议怎么解析、数据帧校验怎么做。你真正要处理的是拿到结构体之后怎么处理、怎么发布、怎么让CMake把PSDK编译进来这三件事。1.3 独立进程桥接与单进程融合的取舍方案无非两种。第一种是独立进程PSDK程序跑在一个独立进程里订阅到GPS之后通过共享内存、socket或者serial把数据转发给一个ROS节点。第二种是单进程融合直接写一个ROS节点在节点内部完成PSDK初始化和数据发布。我在第一个项目里用的是方案A原因是被PSDK本身的构建体系劝退了想着各跑各的互不干扰。但实际用下来方案A多了一跳转发要么引入共享内存的同步问题要么引入socket的延迟和断连问题最终还是要写一个桥接层工作量并没有省。第二次做类似项目直接改成方案B一个进程里先ros::init再DjiCore_Init订阅回调和ROS发布循环都在同一个可执行文件里反而清爽很多。下表是两种方案的关键对比新人建议直接选方案B。对比项方案A独立进程桥接方案B单进程融合调试复杂度需要同时看两个进程日志还要考虑进程异常退出后的重启一个gdb就能跟踪全部链路日志统一数据延迟多一跳IPC一般多1~3ms几乎零额外延迟耦合度松耦合PSDK崩溃不影响ROS节点紧耦合PSDK初始化失败则整个节点退出构建难度各自构建互相独立需要同时处理CMake和catkin两套体系后续扩展桥接层要给每个新数据都加转发协议扩展话题直接publish不用改协议我在后面章节里所有示例代码都基于方案B展开。2. PSDK侧拿GPS订阅机制、回调处理和底层数据长什么样2.1 初始化PSDK时最容易忽略的两个配置PSDK初始化这一步大部分教程都会给一个最短流程配置硬件接口然后DjiCore_Init。但有两个配置项目里第一回跑不通基本都是因为它们。第一个是硬件连接方式要和物理链路匹配。妙算通过type-C或者网口连接飞机那么你在初始化里得告诉PSDK我用的是哪个串口/哪个网卡设备。串口方式要指定设备名和波特率网络方式要指定网卡名。如果你用的是USB转网络或者RNDIS虚拟网卡设备名可能是eth0也可能是usb0别拿ifconfig看一眼就想当然要以板子上实际枚举出的设备为准。第二个是SDK在飞机端要处于使能状态。这一步不在机载电脑上而在飞手的遥控器/Pilot App里。PSDK负载插上飞机之后需要在App的负载管理页面里打开负载开发模式或者类似开关确认设备ID与开发者信息已经激活。很多兄弟在室内调试时发现GPS数据一直是0第一反应是代码问题排查到最后才发现App里压根没打开SDK使能。这个坑我踩过不止一次。初始化代码框架大致如下不同PSDK版本的API名字可能有差异但流程一致DjiCore_InitUserConfig userConfig; memset(userConfig, 0, sizeof(userConfig)); // 配置硬件平台串口方式示例 userConfig.hardwareConfig.uartConfig.deviceName /dev/ttyTHS1; userConfig.hardwareConfig.uartConfig.baudRate 921600; // 初始化 DjiReturnCode ret DjiCore_Init(userConfig); if (ret ! DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) { ROS_ERROR(DjiCore_Init failed, code: 0x%08x, ret); return -1; }2.2 订阅GPS_FUSED topic的正确姿势PSDK的订阅接口用的是话题频率回调的模式。GPS相关的topic有好几个比如DJI_FC_SUBSCRIPTION_TOPIC_GPS_RAW和DJI_FC_SUBSCRIPTION_TOPIC_GPS_FUSED。我建议直接订阅GPS_FUSED这是飞控融合后的定位结果比原始GPS解算更平滑在城市峡谷、树荫等场景下抗丢星能力也更好。订阅时指定频率GPS数据一般用10Hz就够没必要用50Hz占带宽。以下是简化示例DjiFcSubscription_SubscribeTopic( DJI_FC_SUBSCRIPTION_TOPIC_GPS_FUSED, DJI_FC_SUBSCRIPTION_TOPIC_FREQ_10_HZ, GpsFusedInfoCallback, this);订阅成功之后回调就开始被触发。回调函数长这样static void GpsFusedInfoCallback(DjiFcSubscriptionGPSFused gpsFusedInfo, void *userData) { GpsDataMgr *mgr static_castGpsDataMgr *(userData); mgr-UpdateGpsData(gpsFusedInfo); }userData参数可以传一个对象指针我就是在这个地方把PSDK回调数据接进自己的管理类里的。2.3 回调里不能直接ros::publish的三个原因刚上手时我很自然地在回调函数里直接构造NavSatFix消息然后publish()跑起来偶尔会出现卡顿甚至死锁。后来仔细想明白了有三个原因导致这不是个好做法回调线程是PSDK内部协议栈线程如果在这个线程里做阻塞操作包括可能被订阅者拖慢的ROS发布有可能干扰PSDK的实时收发严重时影响飞控通信稳定性。发布频率和回调频率强耦合。一旦ROS订阅链路处理变慢倒逼PSDK回调堆积底层缓冲区可能出现问题。不方便做数据预处理。你可能需要在发布前做坐标转换、异常值剔除、状态判断而这些逻辑放在一个失控回调里很难维护。正确做法是回调里只做拷贝数据到互斥锁保护的共享变量数据发布交给独立的ROS主循环。我封装了一个GpsDataMgrclass GpsDataMgr { public: void UpdateGpsData(const DjiFcSubscriptionGPSFused gps) { std::lock_guardstd::mutex lock(mutex_); latestGps_ gps; hasNewData_ true; } DjiFcSubscriptionGPSFused GetGpsData() { std::lock_guardstd::mutex lock(mutex_); return latestGps_; } private: std::mutex mutex_; DjiFcSubscriptionGPSFused latestGps_ {0}; bool hasNewData_ false; };2.4 把PSDK的GPS结构体翻译成ROS的世界坐标PSDK返回的结构体常见字段有latitude、longitude、height、heightSea、velocityNorth、velocityEast、velocityDown、satelliteNumber。这里有几个极易踩的坑。经纬度单位PSDK里latitude、longitude的单位是度是浮点数不是弧度也不是度分秒。拿到之后直接赋给NavSatFix的latitude和longitude即可。如果你在代码里看到的是double放心用。高度字段height和heightSea是不一样的概念前者可能是相对某个参考面的高度后者是海拔高度。做测绘场景时建议优先用海拔高度也就是heightSea并且要了解你用的PSDK版本里height到底是指相对起飞点还是相对椭球面。不同固件版本行为不一定一致最稳妥的办法是室外试飞一次对比手机GPS和App姿态界面的高度。速度字段velocityNorth等三个分量是NED坐标系下的地速分量北向为正东向为正地向为负直接拿来做速度融合时要先搞清飞控给出的正方向定义。这里各家定义可能不一致不要想当然。这段代码做的事情是把结构体翻译成sensor_msgs::NavSatFix并附带卫星数做状态判断。sensor_msgs::NavSatFix ConvertToNavSatFix(const DjiFcSubscriptionGPSFused gps) { sensor_msgs::NavSatFix msg; msg.header.stamp ros::Time::now(); // 一般场景先用本地时间后面细说 msg.header.frame_id gps_link; msg.latitude gps.latitude; msg.longitude gps.longitude; msg.altitude gps.heightSea; if (gps.satelliteNumber 4) { msg.status.status sensor_msgs::NavSatStatus::STATUS_FIX; } else { msg.status.status sensor_msgs::NavSatStatus::STATUS_NO_FIX; } return msg; }3. ROS节点设计消息类型、线程模型和发布时间戳3.1 为什么我推荐sensor_msgs/NavSatFix而不是自定义消息先上结论GPS主数据用sensor_msgs/NavSatFix额外信息用自定义消息或者geometry_msgs扩展。用标准消息的好处是下游的同仁拿到话题就能直接跑通rviz里的NavSat显示、robot_localization等定位融合包不需要额外装你的消息定义。自定义消息虽然可以把速度、卫星数、PDOP都塞进去但对方每次编译都要先catkin_make你的包跨团队协作很麻烦。消息类型适用场景缺点sensor_msgs/NavSatFix通用GPS发布下游接rviz、定位融合没有速度/卫星数等字段geographic_msgs/GeoPoint只要一个地理位置点传输极简字段太少不适合做数据融合自定义msg需要速度、卫星数、PDOP、RTK状态等信息一起打包下游要先编译你的msg依赖我实际项目和算法组对接时主话题用NavSatFix速度、航向信息单独用geometry_msgs/TwistStamped发布卫星数和定位状态用std_msgs/UInt8发布三线并行互不阻塞。3.2 节点内部的PSDK回调线程 ROS主循环双线程协作整个节点的线程模型如下主线程ros::init→DjiCore_Init→ 启动PSDK订阅 → 进入ros::Rate(10)发布循环。PSDK内部线程在DjiCore_Init之后由PSDK自动创建一有遥测数据就调用你注册的回调。回调线程和ROS主循环之间就是GpsDataMgr这个带锁的共享缓存。发布循环示例ros::Rate rate(10); while (ros::ok()) { DjiFcSubscriptionGPSFused gps gpsMgr.GetGpsData(); auto navFixMsg ConvertToNavSatFix(gps); gpsPublisher_.publish(navFixMsg); geometry_msgs::TwistStamped speedMsg; speedMsg.header.stamp ros::Time::now(); speedMsg.header.frame_id gps_link; speedMsg.twist.linear.x gps.velocityNorth; speedMsg.twist.linear.y gps.velocityEast; speedMsg.twist.linear.z gps.velocityDown; speedPublisher_.publish(speedMsg); rate.sleep(); }注意GetGpsData()里如果还没有任何新数据返回的全零结构体是无效的。建议在结构体里加一个bool hasNewData_标记发布前判断或者用卫星数是否为0做初判。3.3 时间戳与frame_id别糊弄后续做定位融合会感谢自己很多人的第一个版本都是msg.header.stamp ros::Time::now()这没什么大问题但要想做严肃的传感器融合时间戳必须是采集时刻而不是发布时刻。PSDK回调把数据从飞控拿过来的那一刻和你在while循环里publish的时刻中间经过了锁竞争、线程调度延迟可达几ms到几十ms。对于GPS这种低频传感器几十ms的延迟对定位影响不大但如果你后续把IMU、气压计、视觉里程计融合在一起时间戳乱套是致命的。更严谨的做法是使用飞控同步出来的UTC时间。PSDK在部分机型上提供UTC时间和PPS秒脉冲接口可以取出GPS授时把header.stamp设成GPS时间折算后的ros::Time。frame_id的统一也很重要。GPS数据对应的坐标系是地球固联坐标系WGS84你在RViz里要显示位置需要在TF树里声明gps_link和机身坐标系之间的变换。建议把frame_id固定成一个有意义的名字比如gps_link、base_link保持全项目一致。3.4 顺手把速度、卫星数、RTK数据一起发出来做负载项目GPS往往不是唯一要传的数据。我在一次测绘项目里下游算法除了要经纬度还要速度矢量和卫星数量来判断当前定位质量。如果是RTK模式还要RTK的FIX状态。于是我把发布设计成三路话题/dji_psdk/gps/navsatfixsensor_msgs/NavSatFix主定位数据/dji_psdk/gps/velocitygeometry_msgs/TwistStamped地速NED分量/dji_psdk/gps/satellitesstd_msgs/UInt8可见卫星数这样下游可以按需订阅不会因为一个话题塞太满而增加解析成本。类似思路也可以扩展到IMU、电池等其它PSDK数据。4. CMake配置实战PSDK源码与catkin项目如何和平共处4.1 PSDK官方构建方式和catkin构建方式的冲突点PSDK官方工程本身是一个完整的CMake项目有独立的CMakeLists.txt还带了第三方依赖比如libusb、jsoncpp、部分通信协议栈。你要做的是把这个独立CMake项目嵌套进一个ROS1的catkin包本质也是CMake里。冲突点主要有三个target命名空间PSDK子目录里的目标名和ROS包目标名可能重名比如两边都有sample这种默认名需要手动改。编译选项冲突PSDK可能设置了自己的C标准、警告级别、宏定义。add_subdirectory之后这些选项会影响当前目录及其后的所有目标。依赖库链接顺序静态库链接顺序在GCC里非常敏感被依赖的库必须放在后面。PSDK的静态库如果没有通过CMake target导出而是直接用target_link_libraries加一个.a文件顺序错了就是一堆undefined reference。4.2 方案Aadd_subdirectory直接集成PSDK源码如果你的PSDK源码放在ROS包目录下的3rd/psdk可以直接在CMakeLists.txt里cmake_minimum_required(VERSION 3.0.2) project(psdk_ros_gps) set(CMAKE_BUILD_TYPE Release) add_compile_options(-stdc11) find_package(catkin REQUIRED COMPONENTS roscpp sensor_msgs std_msgs geometry_msgs ) # 把PSDK源码作为子目录加入 set(PSDK_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/psdk) add_subdirectory(${PSDK_SRC_DIR} psdk_build) include_directories( ${catkin_INCLUDE_DIRS} ${PSDK_SRC_DIR}/include ${PSDK_SRC_DIR}/platform ${CMAKE_CURRENT_SOURCE_DIR}/include ) catkin_package( INCLUDE_DIRS include CATKIN_DEPENDS roscpp sensor_msgs std_msgs geometry_msgs ) add_executable(dji_psdk_gps_node src/gps_data_mgr.cpp src/main.cpp ) target_link_libraries(dji_psdk_gps_node ${catkin_LIBRARIES} payload-sdk pthread rt )关键点在第16行的psdk_build参数这是给PSDK子目录单独指定的构建输出目录避免它把中间文件和你的catkin构建产物混在一起。第26行的payload-sdk是PSDK CMake工程生成的库目标名不同版本可能叫payload_sdk或libpayload-sdk.a以实机编译输出为准。4.3 方案B独立编译PSDK库再链接如果PSDK源码不方便放进ROS包里也可以先单独编译PSDK生成静态库产物然后在ROS包CMakeLists里引用。cd psdk_src mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease make -j4 # 或者加入 -DCMAKE_INSTALL_PREFIX 后 make installROS包的CMakeLists里改成set(PSDK_LIB_DIR /path/to/psdk_src/build) set(PSDK_INC_DIR /path/to/psdk_src/include) link_directories(${PSDK_LIB_DIR}) include_directories(${PSDK_INC_DIR})这两种方案各有取舍。方案A的优点是版本跟随清晰、跨机器编译不会忘带库缺点是构建依赖复杂PSDK子目录一旦有编译问题会阻塞整个catkin构建。方案B灵活但你必须保证独立编译时的交叉编译器、系统库和你ROS包的编译环境完全一致否则链接阶段会爆出各式各样的ABI错误。我给在妙算板子上直接编译的兄弟一个建议能不交叉编译就不交叉编译直接在板子上跑catkin_make让PSDK库和ROS包使用同一个编译器、同一份系统头文件。方案优点缺点add_subdirectory一次构建、版本锁定、不易漏依赖PSDK子目录编译问题会阻塞整个包独立编译库构建解耦、PSDK版本可单独维护库/编译器/系统版本不一致会踩ABI坑4.4 编译链接最常见的五个报错与排查思路报错一undefined reference to DjiCore_Init。这个最直接就是PSDK库没链接进去。先确认target_link_libraries里真的加了PSDK的库再确认库的路径对不对。如果用的是独立编译的.a文件检查文件名和路径。还有一种可能是C语言头文件没有用extern C包含导致C link阶段找不到符号。正确做法是在节点源码里这样包头文件extern C { #include psdk_typedef.h #include dji_core.h }报错二头文件找不到。fatal error: dji_core.h: No such file or directory。说明PSDK头文件目录没有添加进include_directories。顺便检查一下PSDK的头文件是不是分散多个目录有些版本在include/dji下有额外拆分都要加进来。报错三重定义/符号冲突。排查PSDK子目录里是否有和目标名重名的库或变量。我之前遇到过PSDK的sample可执行文件目标名与自己写的sample重名CMake在生成阶段直接报错。改名即可。报错四链接顺序导致undefined reference。如果你用.a文件而不是CMake target顺序必须遵守被依赖者放后面的规则。例如target_link_libraries(dji_psdk_gps_node ${catkin_LIBRARIES} payload-sdk pthread rt)。有些经验不足的兄弟把payload-sdk放在${catkin_LIBRARIES}前面如果PSDK里面用了ROS的第三方库就会报一堆解析不了的符号。报错五编译通过运行一跑就段错误/崩溃。这类问题多半不是CMake本身而是PSDK初始化和ROS初始化顺序、回调线程访问未初始化对象。我的习惯是先ros::init再创建ros::NodeHandle最后才DjiCore_Init。回调里引用的所有对象都要保证在DjiCore_Init之前已经构造完成。另外一个常见崩溃来源是PSDK初始化失败后继续跑数据线程访问了无效的句柄或空指针。初始化返回后务必做错误判断失败就直接退出节点不要硬往下走。5. 从编译到验证在妙算3上完整跑通GPS发布链路5.1 开发环境准备ROS、PSDK依赖和板端编译要点妙算3常见的系统是Ubuntu 18.04或20.04。ROS1分别对应Melodic和Noetic。如果你用的是妙算3我建议直接安装与系统版本匹配的ROS版本不要强行在18.04上装Noetic依赖冲突会让你怀疑人生。新手想快速搭环境可以用社区里流传的鱼香ROS一键安装脚本比自己倒腾源和密钥省不少时间。PSDK编译依赖主要是libusb、libncurses等Ubuntu上一条命令就能装齐sudo apt install -y build-essential cmake libusb-1.0-0-dev libncurses5-dev在板子上编译时还有一个细节不要加-j8以上并行度。妙算3这类板载平台内存有限并行编译过猛容易直接OOM。我用catkin_make -j2稳如老狗慢一点但不会中途挂掉。5.2 编译、打包与launch启动假设你的ROS包名字叫psdk_ros_gps放在catkin_ws/src下整个源码结构大致如下catkin_ws/src/psdk_ros_gps/ ├── CMakeLists.txt ├── package.xml ├── include/psdk_ros_gps/gps_data_mgr.h ├── src/ │ ├── gps_data_mgr.cpp │ └── main.cpp ├── launch/ │ └── psdk_gps.launch └── 3rd/psdk/编译cd ~/catkin_ws catkin_make -j2 source devel/setup.bash启动先roscore再单独跑节点或用launchroslaunch psdk_ros_gps psdk_gps.launchlaunch文件内容很简单launch node pkgpsdk_ros_gps typedji_psdk_gps_node namedji_psdk_gps_node outputscreen / /launch5.3 用rostopic和rviz确认GPS数据真的活了节点跑起来之后第一时间检查话题是否正常输出。rostopic list | grep dji_psdk rostopic echo /dji_psdk/gps/navsatfix如果能看到经纬度一行行刷出来并且数值和你当前测试地点基本一致说明链路已经通了。再跑一个rostopic hz确认频率rostopic hz /dji_psdk/gps/navsatfix正常应该在10Hz左右浮动。如果频率远低于10Hz可能是发布循环里某个处理太耗时或者PSDK订阅频率没配好。可视化方面rviz里添加一个NavSat显示工具把话题选到/dji_psdk/gps/navsatfixframe_id设为gps_link就能在三维视图中看到一个位置点。配合底图插件可以直接确认飞机当前位置对不对。5.4 GPS数据没更新/乱跳/一直0的排查顺序遇到数据异常别急着改代码按照链路从下往上排查我用一张表总结排查点现象优先检查项可能原因话题一直0飞机是否在室外取得GPS定位室内/窗边GPS收不到星话题一直0App里PSDK是否使能负载开发模式未开启话题一直0订阅返回值机型和固件不支持该topic话题乱跳是否在城市峡谷/桥下多路径效应严重话题乱跳卫星数是否太低低于4颗基本没法用频率不稳CPU是否被打满板端有其他高负载进程发布值恒定回调是否真的被调用回调里先打日志确认我在实际项目中遇到过一次GPS数据上电后一直是0的情况代码逻辑完全没问题最后发现是Pilot App的设备管理中没把该负载设备标记为开发模式。这个开关的位置每代App都变找不到就翻一下开发者文档里的截图千万别在代码里死磕。6. 跑通后还能再想一层扩展用法与版本适配经验6.1 从GPS扩展到IMU、电池、避障数据GPS链路跑通意味着你已经掌握了PSDK遥测数据进入ROS的通用套路。同样的步骤可以迁移到其他数据IMU订阅DJI_FC_SUBSCRIPTION_TOPIC_IMU_ACC_RAW等发布成sensor_msgs/Imu电池信息订阅电池相关topic发布成sensor_msgs/BatteryState避障信息发布成自定义消息或sensor_msgs/PointCloud核心逻辑还是那个模式回调写入共享缓存ROS主循环读取发布。所以我建议你把GpsDataMgr改成一个通用的SubscriptionBridge把订阅PSDK topic → 锁保护 → ROS发布这个模式模板化后面加数据源就是加配置文件的事。6.2 高精度场景RTK订阅与时间同步如果你的项目是测绘、电力巡检这种对精度要求高的场景GPS_FUSED的平面精度米级是不够的。M300/M350 RTK机型可以通过PSDK订阅RTK_POSITION相关topic拿到厘米级定位数据。RTK数据的使用有以下几点要注意必须等RTK达到FIX状态别只看有没有数据就发布。浮点解的数据精度仍然不稳定。RTK数据发布到NavSatFix时status.service设为SERVICE_GLONASS或SERVICE_GPS等实际使用的星座status.status设为STATUS_GBAS_FIX一类表示RTK固定解。RTK和GPS_FUSED同时发布会让下游迷茫建议用一个参数控制二选一或者明确命名话题后缀/gps_rtk。时间同步方面如果要做高精度融合建议不要用ROS本地时钟做时间戳而是把GPS授时时间转成UTC再换算成ROS时间戳。这一步能提升后续robot_localization融合定位的精度和稳定性。6.3 版本适配妙算3、ROS1/ROS2、PSDK版本怎么组合最后聊一下版本适配这部分是我踩坑最多的地方。平台组合体验评价说明妙算3 Ubuntu 18.04 ROS Melodic PSDK 3.x最稳官方示例基本能一把过妙算3 Ubuntu 20.04 ROS Noetic PSDK 3.x可用需要处理一些依赖兼容妙算3 ROS2 Foxy/Humble PSDK 3.x折腾CMake要从catkin换成ament工作量大电脑Docker Ubuntu ROS PSDK模拟适合预研数据流验证方便不代表实机行为如果你看到这里正准备从ROS1切到ROS2提前打个预防针ROS2的构建不是catkin_package()而是ament_target_dependencies()add_subdirectory(PSDK)的方法倒是还能用但CMAKE的变量作用域和链接库写法都需要重写。我的建议是如果机型支持且没有强需求必须用ROS2先用ROS1 Melodic把业务跑通别在构建系统上消耗太多时间。还有一个关于PSDK版本的小提醒在developer.dji.com下载PSDK源码时不同的无人机机型、不同的固件版本对PSDK版本有明确要求。M30系列、M350 RTK、Mavic 3行业版支持的协议不完全一样。用错版本轻则订阅不到数据重则飞机端通信异常。准备移植到新机型时第一件事就是查官方兼容性表格别拿旧工程直接试飞。最后再分享一个我个人的调试习惯在GPS节点启动后先在终端里把PSDK的日志等级调低有些版本通过环境变量配置一旦数据异常能第一时间看到底层报错调试期尽量在室外空旷地方测室内测试得到的GPS一直0会让排查方向跑遍全宇宙。这个项目做完之后我觉得最有价值的收获反而是CMake那套两套构建体系共存的经验后面做其他传感器接入基本都能套用同一个模板。