简介这份资源面向无人机软件开发与低空监管技术学习者聚焦Remote ID数据结构解析与国标差异对比基于ArduPilot的ArduRemoteID开源库逐层拆解Wi-Fi Beacon中Vendor Specific IE的OUI、OUI Type、Message Counter及ODID_MessagePack_encoded结构并逐条解析Basic ID、Location、Self ID、System、Operator ID等六类OpenDroneID编码消息的字段含义与解码结果。资源包共3个文件以html页面为主体呈现解析内容辅以inscode工程配置与gitignore版本控制文件压缩包约7KB轻量易读。目前已有147人学习。读者可借此掌握RID报文从物理层到应用层的完整解码链路理解GB42590-2023与GB46750-2025在数据格式、字段顺序、编码方式上的强制要求差异并了解雷迅C-RID模块的合规现状与36个月宽限期政策为PC端扫描工具适配新国标提供排错与重构思路。1. 无人机RID数据结构解析与标准对比从一段十六进制报文说起第一次拿到无人机RIDRemote ID远程识别的原始报文时我盯着串口助手里那串十六进制发了半天呆——02 19 41 42 43 ...看着像乱码其实每个字节都有严格定义。RID 的本质是无人机在飞行过程中对外广播的一套身份与位置信息监管方、接收端、第三方服务都靠它识别“谁在飞、飞在哪、什么时候飞的”。它解决的是低空飞行器“看不见、管不住”的问题适合做无人机监管终端、反制设备、飞控固件、空域管理平台的从业者。标题里的“数据结构解析”指的是把广播报文按协议字段拆开“标准对比”则是把主流的两套规范放在一起看差异。很多人卡在第一步报文抓到了却不知道从哪个字节开始读。2. RID 报文的数据结构从字节流到可读字段2.1 广播帧的整体布局与字段边界RID 广播报文最常见的载体是蓝牙低功耗BLE的扩展广播也有 Wi-Fi Beacon 和 Wi-Fi NAN 两种形式。不管哪种载体载荷部分遵循同一套消息结构一个字节的消息类型Message Type紧跟一个字节的协议版本然后是具体的消息体。消息类型决定了后面怎么解析——是基本身份、位置信息、还是系统信息。我一般先把抓到的原始字节按“消息类型 → 版本 → 消息体”三段切开再逐段对照字段表。以基本身份消息Basic ID为例消息体里包含 UA Type无人机类型、UAS ID TypeID 类型、UAS ID实际标识通常是序列号或会话 ID。位置消息Location/Vector则包含经纬度、高度、速度、航向、时间戳、精度等级等。字段之间没有分隔符全靠固定偏移量定位所以偏移量算错一个字节后面全乱。这里有个容易忽略的点经纬度不是直接存浮点数而是按value / 10^7的定点数编码纬度范围 -90 到 90经度 -180 到 180。高度用的是value / 2 - 1000米也就是分辨率 0.5 米偏移 -1000 米。这些缩放因子如果记混解析出来的坐标能偏出好几公里。2.2 用 Python 解析一段 Basic ID 报文下面这段代码是我常用的最小解析器输入是去掉载体头之后的 RID 载荷字节输出是结构化字典。它只处理 Basic ID 和 Location 两种消息够用来验证抓包是否正确。import struct # RID 消息类型枚举部分 MSG_BASIC_ID 0x0 MSG_LOCATION 0x1 MSG_AUTH 0x2 MSG_SELF_ID 0x3 MSG_SYSTEM 0x4 MSG_OPERATOR_ID 0x5 def parse_rid_payload(payload: bytes) - dict: 解析 RID 载荷返回结构化字段 if len(payload) 2: raise ValueError(payload too short) msg_type payload[0] version payload[1] body payload[2:] result {msg_type: msg_type, version: version} if msg_type MSG_BASIC_ID: # Basic ID: UA Type(1B) ID Type(1B) UAS ID(20B) if len(body) 22: raise ValueError(basic id body too short) ua_type body[0] id_type body[1] uas_id body[2:22].rstrip(b\x00).decode(ascii, errorsignore) result.update({ ua_type: ua_type, id_type: id_type, uas_id: uas_id, }) elif msg_type MSG_LOCATION: # Location: 按固定偏移解析 # 纬度(4B) 经度(4B) 高度(2B) 速度(2B) 航向(2B) 时间戳(2B) ... if len(body) 16: raise ValueError(location body too short) lat_raw struct.unpack(i, body[0:4])[0] lon_raw struct.unpack(i, body[4:8])[0] alt_raw struct.unpack(H, body[8:10])[0] speed_raw struct.unpack(H, body[10:12])[0] heading_raw struct.unpack(H, body[12:14])[0] ts_raw struct.unpack(H, body[14:16])[0] result.update({ latitude: lat_raw / 1e7, longitude: lon_raw / 1e7, altitude_m: alt_raw / 2.0 - 1000.0, speed_mps: speed_raw / 2.0, # 常见缩放具体看版本 heading_deg: heading_raw / 2.0, timestamp_s: ts_raw / 10.0, # 部分版本以 0.1s 为单位 }) return result # 示例一段 Basic ID 载荷 raw bytes.fromhex(001141424330303030303030303030303030303030303030) print(parse_rid_payload(raw))逻辑说明先取消息类型和版本再按类型分支。Basic ID 的 UAS ID 是 20 字节定长尾部补零用rstrip去掉再解码。Location 里所有多字节整数都是大端网络字节序用i和H解。参数说明lat_raw / 1e7是定点转浮点alt_raw / 2.0 - 1000.0是高度还原速度和航向的缩放因子在不同版本里可能不同实际对接时要以对应规范为准。这段代码跑通说明你的抓包和字段偏移是对的。2.3 消息类型与版本号的组合判断消息类型只有 0 到 5 几个值但版本号会影响消息体的长度和字段含义。我见过有人拿旧版解析器去读新版报文结果时间戳字段错位解析出来的时间差了十几秒。稳妥做法是先读版本号再查该版本对应的字段表不要假设所有版本结构一致。如果版本号不在已知范围内直接丢弃并记录不要硬解。3. 两套主流标准的结构差异ASTM 与 ASD-STAN 对比3.1 消息定义层面的异同目前业界主要参考两套规范一套是 ASTM 的 F3411 系列另一套是 ASD-STAN 的对应标准。两者在消息类型划分上大体一致都包含 Basic ID、Location、Auth、Self ID、System、Operator ID 这几类但字段的排列顺序、缩放因子、可选字段的有无存在差异。比如 Location 消息里ASTM 把速度放在高度之后而另一套标准在某些版本里把垂直速度单独拆出来。如果你同时对接两类设备解析器必须按标准分支不能一套代码通吃。我做对比时习惯列一张字段映射表把两套标准的同一语义字段放在同一行标注偏移量和缩放因子。这样在写解析器时只需要在字段读取处加一个标准判断不用重写整个解析逻辑。字段语义ASTM 偏移ASTM 缩放ASD-STAN 偏移ASD-STAN 缩放纬度0-3/1e70-3/1e7经度4-7/1e74-7/1e7高度8-9/2-10008-9/2-1000水平速度10-11/210-11/2垂直速度无独立字段—12-13/2航向12-13/214-15/2这张表不是官方文档的复制而是我在实际对接中整理出来的常用映射具体版本仍以对应规范为准。有了它解析器里只需要一个standard参数就能切换偏移量。3.2 载体差异对解析的影响BLE、Wi-Fi Beacon、Wi-Fi NAN 三种载体在载荷外面包的头不一样。BLE 扩展广播有自己的 AD StructureWi-Fi Beacon 有 Vendor Specific IEWi-Fi NAN 有服务发现帧。解析时第一步永远是剥掉载体头拿到 RID 载荷再走上面的解析逻辑。我一般把载体解析和载荷解析分成两个模块载体模块只负责提取载荷载荷模块只认字节这样换载体时不用动核心解析代码。def extract_ble_payload(ad_data: bytes) - bytes: 从 BLE AD Structure 中提取 RID 载荷 i 0 while i len(ad_data): length ad_data[i] if length 0: break ad_type ad_data[i 1] ad_value ad_data[i 2: i 1 length] # 0x16 是 Service DataRID 常用此类型 if ad_type 0x16: # 前两字节是 Service UUID后面才是 RID 载荷 return ad_value[2:] i 1 length raise ValueError(no RID payload found)逻辑说明BLE 的 AD Structure 是“长度 类型 值”的 TLV 序列循环遍历找到 Service Data 类型跳过两字节 UUID 后返回剩余部分。参数说明0x16是蓝牙规范里 Service Data 的 AD TypeRID 的 Service UUID 因标准而异这里只做示例实际要按你对接的标准替换判断条件。3.3 标准选择对落地的影响如果你做的是监管接收端通常要同时兼容两套标准因为市面上设备可能来自不同阵营。如果做的是飞控固件只需要按目标市场选择对应标准即可。我的建议是解析层做成可配置的标准差异集中在字段映射表里不要散落在代码各处。这样新增一套标准时只加一张表不改逻辑。4. 避坑与排查RID 解析中最容易翻车的五个点4.1 字节序搞反坐标飞到南极现象解析出来的纬度是负数且数值巨大或者经度完全不对。原因RID 多字节整数是大端序有人习惯小端直接struct.unpack(i, ...)。解决统一用前缀并在代码里加断言纬度绝对值不超过 90经度不超过 180超出就报警。4.2 版本号没判断时间戳错位现象时间戳解析出来是几十秒甚至几百秒和实际飞行时间对不上。原因不同版本的时间戳单位和偏移不同旧解析器读新报文。解决先读版本号按版本查字段表未知版本直接丢弃并记录原始字节方便事后分析。4.3 载体头没剥干净消息类型读错现象消息类型解析出来是 0x16 或 0xAA 这种不该出现的值。原因把 BLE 的 AD Type 或 Wi-Fi 的 Vendor IE 头当成了 RID 载荷。解决在载体解析模块里加校验确认 Service UUID 匹配后再返回载荷不匹配就继续遍历。4.4 UAS ID 解码出乱码现象Basic ID 里的 UAS ID 打印出来是一串问号或不可见字符。原因UAS ID 是 ASCII 定长字段尾部补零直接decode会把零字节也解出来。解决先rstrip(b\x00)再解码并指定errorsignore同时检查长度是否符合预期。4.5 缩放因子记混高度差出上千米现象高度解析出来是负几百米或者比实际高出一千多米。原因高度公式是raw / 2 - 1000有人记成raw / 2或raw - 1000。解决把缩放因子写成常量并加注释单元测试里用已知报文验证比如原始值 2000 对应高度 0 米。5. 进阶技巧用已知报文做回归验证与标准切换5.1 构造最小测试集我习惯在项目里维护一个小型测试集每条记录包含原始十六进制载荷、标准类型、期望解析结果。每次改解析器先跑这个测试集确保没有回归。测试集不需要多覆盖 Basic ID、Location、System 三种消息每套标准各两条即可。import pytest TEST_CASES [ { name: basic_id_astm, standard: astm, payload: 001141424330303030303030303030303030303030303030, expect: {msg_type: 0, version: 1, uas_id: ABC00000000000000000}, }, { name: location_astm, standard: astm, payload: 0101 0FFFFFFF 0FFFFFFF 0FA0 0064 00B4 0064, expect: {msg_type: 1, version: 1, latitude: pytest.approx(26.8435455, rel1e-3)}, }, ] pytest.mark.parametrize(case, TEST_CASES, ids[c[name] for c in TEST_CASES]) def test_parse(case): result parse_rid_payload(bytes.fromhex(case[payload])) for k, v in case[expect].items(): assert result[k] v逻辑说明用参数化测试把每条用例独立跑失败时能直接定位到哪条报文。参数说明pytest.approx用于浮点比较避免精度问题导致误报。测试集里的期望值是我根据字段定义手算的不是从真实设备抄的这样不涉及任何具体项目信息。5.2 标准切换的配置化写法把标准差异抽成配置字典解析函数根据配置读字段。这样新增标准时只加配置不改函数体。FIELD_MAP { astm: { location: { lat: (0, 4, i, 1e7), lon: (4, 8, i, 1e7), alt: (8, 10, H, 2.0, -1000.0), speed: (10, 12, H, 2.0), heading: (12, 14, H, 2.0), } }, asd: { location: { lat: (0, 4, i, 1e7), lon: (4, 8, i, 1e7), alt: (8, 10, H, 2.0, -1000.0), speed: (10, 12, H, 2.0), v_speed: (12, 14, H, 2.0), heading: (14, 16, H, 2.0), } }, }逻辑说明每个字段用元组描述偏移、字节序、缩放和偏移量解析时统一循环处理。参数说明元组最后两个值是可选的缩放和偏移没有就不传。这样写的好处是标准差异一目了然排查时直接看配置表就知道哪个字段对不上。5.3 抓包验证的实操习惯我一般先用手机或开发板抓一段真实广播存成十六进制文本再用解析器跑一遍把结果和肉眼观察到的飞行状态对比。如果坐标、高度、速度都对得上说明解析链路没问题。如果对不上先查载体头再查版本号最后查缩放因子。这个顺序能解决九成以上的解析异常。做 RID 解析最怕的不是协议复杂而是拿到报文就急着写代码不先确认字节边界。我自己的习惯是每接一套新设备先花十分钟把原始字节按字段表手工拆一遍确认无误再写解析器。这个笨办法帮我省下了大量返工时间。希望帮到你。本文还有配套的精品资源点击获取