
简介面向无人机远程识别RID开发者的源码解析包基于ArduPilot的ArduRemoteID库逐层拆解Wi-Fi Beacon内Vendor Specific IE覆盖OUI、OUI Type、Message Counter到ODID_MessagePack_encoded的完整链路并对OpenDroneID的Basic ID、Location、Self ID、System、Operator ID及MessagePack容器等六类消息字段与解码逐条说明。资源共3个文件以HTML导读页和inscode工程文件为主配合gitignore规则压缩包仅7KB轻量易读便于快速查阅。已有95人学习适合刚接触RID协议或关注国标合规的软硬件开发者。内容不仅对比了GB42590-2023与GB46750-2025在数据结构要求上的差异也点明雷迅C-RID模块目前仅按旧标准广播、需在36个月宽限期内调整以及PC端扫描工具需完善对新标准支持等关键结论可帮助读者掌握RID广播报文的核心解析方法并明确后续合规改造的方向。 前阵子调试一架无人机时我站在起降点边上另一架没做任何标识的飞行器直接从头顶掠过。那一刻我意识到你没法知道它从哪来、谁在操控、有没有备案。无人机RIDRemote ID远程识别就是为了解决这个问题而生的。简单说就是给每一架飞起来的无人机发一张“空中身份证”周期性广播自己的ID、位置、速度和操作员信息。最近我把这个方向的开发资料整理成了一个解析库顺手把ASTM F3411、prEN 4709-002、ICAO DRIP以及部分厂商私有协议的报文结构做了横向对比。这篇文章会直接讲RID的数据结构长什么样、不同标准之间差在哪、解析逻辑怎么落地成代码以及我实际调试中踩过的坑。适合正在做无人机地面端、监管平台、反制设备或者想了解RID的开发者。先说清楚一个容易混淆的点这里的“数据结构”不是严蔚敏那本《数据结构》教材里的链表和二叉树而是RID报文里字节级别的字段组织方式。搞懂它你才能从一段蓝牙广播里解出真实可用的飞行器信息。1. RID解决什么问题看懂结构之前先弄懂场景1.1 从“看到无人机”到“认出无人机”在RID出现之前地面观测方拿一台手机只能通过无线电侦察发现附近有2.4GHz信号在活动但完全不知道这架飞机是谁的、离自己多远、会不会飞进禁飞区。雷达对小型无人机也基本无效——它们的雷达截面积太小低速、低空飞行时几乎就是隐形目标。RID的思路很直接飞行器主动向周围广播自己的身份信息地面接收端只要用手机、平板或专用接收器就能解析出来。这相当于民航客机的二次雷达应答机只不过换成了适用于轻型无人机的广播协议。从应用场景看它既能帮助普通公众识别“头顶这架飞机是否可疑”也能让监管机构采集数据、执法取证还能让景区、机场周边的管理系统自动做预警。1.2 RID链路里有三个角色整个RID系统可以拆成三部分广播端无人机、接收端手机/专用设备、管理平台地面系统。广播端把经过编码的RID消息通过蓝牙或WiFi Beacon周期性发出接收端在附近收集这些广播包解码成结构化数据管理平台再汇聚多台接收端上报的数据做态势显示和风险判断。需要注意的是RID广播只负责“对方主动公开自己”它不保证一定被接收。信号可能被遮挡广播频率也可能因为天线设计受限。所以我们在做数据结构解析时要习惯“尽力而为”的无线链路假设——一段广播包可能被截断、丢字节、甚至被篡改但协议设计本身要保证一点只要有完整消息到达接收端就能还原出可靠信息。理解这一点后再去看字段设计很多设计意图就变得合理了。2. 四种主流RID标准体系盘点报文源头不一样解析入口就不同2.1 ASTM F3411事实上的基准格式ASTM F3411是目前最常被引用的RID标准全称是《Standard Specification for Remote ID and Tracking》。它定义了直接远程识别Direct RID和网络远程识别Network RID两条路径。我们做本地接收端主要关注Direct RID。F3411规定了蓝牙4.0及以上广播和WiFi Beacon两种承载方式。报文采用一种紧凑的位字段结构一个消息包含消息类型、协议版本号和若干业务字段多条消息可以打包在同一个广播帧里组成Message Pack。比如Basic ID类型承载飞行器ID和机型Location/Vector类型承载经纬度、高度、速度、航向Authentication类型承载数字签名信息和认证方式System类型承载操作员ID、遥控站位置和紧急状态。F3411经历了19a到22a的版本演变。早期版本大量使用0x0F开头的Message Pack后期版本则逐步过渡到更规整的Canonical格式。做解析库时这两个版本最好都兼容后续我会细说区别在哪。2.2 prEN 4709-002欧洲落地版的取舍欧洲方面ASD-STAN发布了prEN 4709-002《Direct Remote Identification》。它在技术结构上大量参考了F3411但不是简单照搬。欧盟法规体系强调“数据最小化”所以prEN 4709-002对消息字段做了删减比如不会强制广播过于精细的地理围栏信息而是把重点放在飞行器ID、操作员注册号、位置和速度这些可直接执法的要素上。在载荷格式上prEN 4709-002的广播消息一般也以BLE或WiFi承载字段布局与F3411高度相似但在操作员标识、签名认证等字段上有自己的编码约定。这意味着如果你只是按F3411写死了解析逻辑遇到欧洲标准的设备就会莫名丢字段或解析错位。这也是我做标准对比的最初动机——一套代码通吃就必须在消息解码层做适配。2.3 私有方案与ICAO DRIP兼容没统一时怎么办除了公开标准还存在厂商私有协议。比较典型的是大疆的AeroScope它主要用于机场、重点区域安防等场景通过自定义协议接收附近无人机的信息。这类私有协议不会公开完整的报文结构通常以授权SDK的形式提供给集成商。我们在开源解析库里不会深入做它但会设计一个可插拔的适配器接口方便接入私有数据源。ICAO DRIP是国际民航组织推动的全球远程识别框架核心目标是让各国标准和设备最终能互操作。目前它在数据链路层仍依赖F3411等基础标准但增加了一层更统一的身份目录和互操作机制。对开发者来说DRIP目前更像是“目标方向”实际落地解析时还是得看具体国家采用哪套格式。3. 数据结构对比字段、编码与认证机制差异3.1 四种标准的核心对比表我做了一个横向对比表方便开发时快速决定按哪套协议来写解码器比较维度ASTM F3411-22aprEN 4709-002ICAO DRIP厂商私有协议以AeroScope为例发布组织ASTM国际 美国监管框架ASD-STAN欧洲ICAO厂商自行定义承载方式BLE 4.0, WiFi BeaconBLE, WiFi Beacon未定死以F3411/区域标准为参考通常为定制无线帧消息头Canonical首字节 类型兼容旧版Message Pack与F3411风格接近但字段裁剪尚未标准化到字节级私有魔数ID组成飞行器ID ID类型 操作员ID类似但操作员字段更强调注册号期望统一身份体系厂商账号体系位置信息经纬度、压力高度、椭球高度、速度、航向经纬度、高度、速度沿用基础标准通常包含高精度位置认证方式支持数字签名ECDSA P-256/Ed25519、分页签名支持签名但对部分数据字段做最小化推进网络化认证多为私有加密/鉴权强制程度部分区域强制欧盟区域强制建议性框架仅特定领域生效这个表只是结构级对比字节级差异更多体现在字段顺序和位宽上。比如同样一个“速度”字段F3411旧版本里可能是4位枚举加扩展新版本里则可能是16位整数解析时不能靠猜必须按版本分支处理。3.2 关键字段差异为什么会影响你的解析器我实际开发中最显著的挫败感来自“版本字节”的处理。F3411-19a的Message Pack使用0x0F作为起始字节后面跟着单条消息长度和消息体而F3411-22a的Canonical帧直接用首字节的低4位表示消息类型、高4位表示协议版本。如果你用老版本的逻辑去解析新版本的帧会被0x10开头的字节误判成消息类型为0导致后续字段直接错位。另一个典型差异是经纬度编码。RID里的纬度不是常见的float或double而是用24位有符号整数表示1e-7度。没见过这个格式的开发者往往会把这个数直接当int丢给界面结果地图上出现一个出现在海里的坐标。标准对比时如果不把这些编码细节列出来看再多的协议文档你也记不住哪里容易踩坑。3.3 Message Pack和Canonical的底层设计思路为什么要有两套编码方式老版的Message Pack把多条消息塞进一个广播帧节约了BLE广播包的长度限制但缺点是要多一层“解包”逻辑且遇到缺帧时很难定位到具体哪条消息损坏。新版的Canonical方案把每条消息独立成帧结构更直观消息边界清晰但一个广播周期需要更多的包。从我实际抓包情况来看市面上的设备差异很大。旧的航模飞控改装模块多走Message Pack新出的量产无人机大多已经是Canonical。解析库要想稳定运行最好的做法是在入口处统一做一次“格式归一化”先判断首字节再转成内部通用的消息对象后续就不要再碰原始字节。这样即使标准再更新你只需要增加一个新格式解析器业务逻辑不用改。4. 项目代码实战从原始广播字节到明文数据4.1 工程需求与模块划分这个解析库是面向Android和嵌入式Linux场景设计的。工程上拆成三层接收层负责扫描BLE广播、解析WiFi Beacon输出原始载荷字节。解码层识别消息格式解出结构化的飞行器信息。业务层把解码结果对接给地面站显示、告警或数据库存档。解码层是最核心的部分。下面我用Python写了一个简化版原型方便讲清思路C/C实现时结构是一样的。4.2 报文解包与字段解析先看一下如何从一段BLE广播数据中提取RID消息RID_MSG_PACK_HEADER 0x0F RID_MSG_TYPE_MASK 0x0F # 分别负责不同消息类型的解析 def parse_basic_id(payload): # 第一个字节高4位是ID类型低4位是航空器类型 id_type payload[0] 4 ua_type payload[0] 0x0F uas_id payload[1:20].decode(utf-8, errorsreplace).rstrip(\x00) return { id_type: id_type, ua_type: ua_type, uas_id: uas_id, } def parse_location(payload): # 位置消息不是简单的int而是按位拆字段 # 这里简化部分位域实际实现要注意24位经纬度符号扩展 lat_raw int.from_bytes(payload[8:11], byteorderbig, signedTrue) lon_raw int.from_bytes(payload[11:14], byteorderbig, signedTrue) return { latitude_raw: lat_raw, longitude_raw: lon_raw, # 1e-7单位为度 latitude: lat_raw * 1e-7, longitude: lon_raw * 1e-7, altitude: int.from_bytes(payload[14:16], byteorderbig, signedTrue), } def decode_rid_packet(data: bytes): first data[0] if first RID_MSG_PACK_HEADER: msg_count data[1] offset 2 results [] for _ in range(msg_count): msg_len data[offset] msg_type data[offset 1] RID_MSG_TYPE_MASK msg_payload data[offset 2 : offset 2 msg_len - 2] if msg_type 0x00: # Basic ID results.append(parse_basic_id(msg_payload)) elif msg_type 0x01: # Location/Vector results.append(parse_location(msg_payload)) offset msg_len 1 return results else: # Canonical格式首字节低4位是消息类型 msg_type first RID_MSG_TYPE_MASK msg_payload data[1:] if msg_type 0x00: return parse_basic_id(msg_payload) elif msg_type 0x01: return parse_location(msg_payload) return None这段代码里有几个位置需要仔细处理。第一id_type和ua_type的位宽在不同版本里有变化不能想当然第二经纬度是24位有符号数int.from_bytes必须带上signedTrue否则负纬度会变成一个很大的正数第三字符串ID的编码通常是UTF-8空字符填充rstrip(\x00)不能省。4.3 签名验证链路的取舍RID的认证消息Authentication并不是必须的但在监管场景下只有验签通过的数据才可信。完整实现涉及ECDSA P-256或Ed25519验签、分页消息收集、时间戳窗口校验等步骤。简化版的验签流程可以这样理解import hashlib from cryptography.hazmat.primitives.asymmetric import ed25519 def verify_rid_signature(public_key_bytes, signed_data, signature_bytes): try: public_key ed25519.Ed25519PublicKey.from_public_bytes(public_key_bytes) # 签名内容不是原始广播而是按协议规定排序后的关键字段哈希 digest hashlib.sha256(signed_data).digest() public_key.verify(signature_bytes, digest) return True except Exception: return False这里我特意强调“按协议规定排序后的关键字段哈希”因为RID认证消息里的签名内容并不等于你收到的原始报文。你需要把Basic ID中的飞行器ID、Location消息中的位置、System消息中的操作员信息各自提取出来按标准要求的顺序拼接、哈希再验签。顺序错了、字段多了或少了验签都过不了。这也是很多团队说“签名算法没问题但就是验不过”的根源。在实际项目中我建议分两步做认证先验签再判断时间戳新鲜度。验签通过只说明数据确实来自持有私钥的设备时间戳新鲜度才说明它现在还活着。两步都通过才可以把数据标记为“可信”否则只标记为“未知”。5. 解析RID数据时我踩过的坑5.1 版本升级导致的首字节语义变化我第一次调试时用旧版Message Pack逻辑解析新设备广播结果Basic ID里的ID类型和机型全反了。排查了很久最后打印出首字节才发现是0x10而不是0x0F。这个坑的隐蔽性在于0x0F和0x10在二进制上差一位解析器不会崩溃只会静默错误。解决方法是做一个版本探测如果你看到首字节等于0x0F走Message Pack如果首字节高位是0x1X走Canonical其他值则尝试按WiFi Beacon厂商字段处理。在项目里把这个判断放在最前面并输出一条debug日志能帮你节省大量抓包时间。5.2 位字段和符号数最容易算错的半小时RID协议大量使用不满一个字节的位字段。比如Location消息里状态、高度类型、速度字段可能是按2位、3位、4位划分的。刚开始我直接用(payload[0] 4) 0x0F这种办法取值后来发现不同设备对保留位的填充方式不一致导致读出来的速度偶尔异常。更经典的是经纬度24位有符号数的符号扩展问题。如果只按普通24位无符号整数读南纬度或西经度会变成巨大正数。后来我在解析函数里加了一行if lat_raw 0x800000: lat_raw - 0x1000000这一行就解决了所有负坐标问题。遇到这类位操作建议先写单元测试用已知经纬度的抓包样本去验证而不是靠肉眼看结果。5.3 验签不通过问题往往不在算法本身签名验不过时优先怀疑三点签名内容构造顺序、公钥长度与算法不匹配、时间戳窗口漂移。我的一个调试案例是Ed25519公钥被误用成64字节而标准规定的是压缩公钥32字节。代码没报错但每次验签都返回False。把公钥格式修正后验签立即通过。另外认证消息可能分多页广播接收端要先把同一认证事件的所有页缓存下来再开始验签。如果只收到部分页就去验签肯定失败。缓存超时可以设成5秒超过就丢弃避免内存被无效页耗尽。5.4 广播收不到先查信道再骂协议RID的BLE广播通常在BLE Advertising信道的37/38/39上发送WiFi Beacon则有自己的发送间隔。我用笔记本的普通蓝牙适配器去扫经常扫不到完整的广播包原因是PC蓝牙驱动对广播包的过滤策略很激进。后来我换成了手机上的BLE扫描工具或者在嵌入式平台上用hcitool lescan --duplicates开启重复包上报才稳定抓到数据。如果你也遇到“明明飞机在头顶软件却什么都没有”先别怀疑RID协议检查一下BLE扫描过滤是否放行非连接广播以及是否开启了厂商数据上报。6. 解析结果怎么用一个自然的扩展方向当你能稳定解出RID结构化数据后最直接的用法是接入地面站实时显示或者对接数据库做历史轨迹回放。我在这套解析库的基础上又加了一个很小的数据集生成工具它可以持续接收广播按飞行器ID聚合轨迹并把经纬度、高度、速度写入CSV或SQLite。这样用来做合规自检、测试报告或者给上层算法提供真实数据都很方便。如果后续要做更复杂的异常识别也可以把RID数据与雷达、光电设备的数据做融合。RID提供身份和意图传感器提供物理轨迹两者合在一起整个低空态势感知才完整。做这个项目的过程中我最深的体会是RID的难点其实不在加密算法也不在协议复杂度而在于“版本多、细节碎、设备差异大”。只要你一开始就把消息格式归一化、把位字段读取封装成带版本的函数后面加支持就会越做越快。希望这份梳理能帮你少走几个月弯路。本文还有配套的精品资源点击获取