news 2026/10/20 8:21:18

无人机RID数据结构解析与ASTM/ASD-STAN标准对比实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
无人机RID数据结构解析与ASTM/ASD-STAN标准对比实战

简介:这份资源面向无人机软件开发与低空监管技术学习者,聚焦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数据结构解析与标准对比:从一段十六进制报文说起

第一次拿到无人机RID(Remote 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 Type(ID 类型)、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', errors='ignore') 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 Structure,Wi-Fi Beacon 有 Vendor Specific IE,Wi-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 Data,RID 常用此类型 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 Type,RID 的 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')再解码,并指定errors='ignore',同时检查长度是否符合预期。

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, rel=1e-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 解析,最怕的不是协议复杂,而是拿到报文就急着写代码,不先确认字节边界。我自己的习惯是,每接一套新设备,先花十分钟把原始字节按字段表手工拆一遍,确认无误再写解析器。这个笨办法帮我省下了大量返工时间。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 8:21:00

空地协同路径规划实践:从任务分配到时间同步

1. 项目整体拆解&#xff1a;空地协同不是“会飞”加“会跑”拿到“空地多无人平台协同路径规划”这个课题&#xff0c;很多人的第一反应是&#xff1a;无人机管天上、无人车管地上&#xff0c;各跑各的&#xff0c;最后把路径拼在一起不就行了&#xff1f;真这么想就错了。我当…

作者头像 李华
网站建设 2026/10/20 8:20:42

Zabbix监控Oracle实战:基于orabbix实现JDBC直连与自动发现

开头做了部分省略&#xff08;后补充&#xff09;&#xff0c;以下是完整文章&#xff1a;在监控体系搭建这件事上&#xff0c;Oracle数据库的接入一直是让我比较头疼的一环。Zabbix本身很强&#xff0c;但面对Oracle这种商业数据库&#xff0c;它默认提供的监控手段并不算友好…

作者头像 李华
网站建设 2026/10/20 8:20:36

AI短剧剪辑工具选型与实战工作流:从素材管理到成片交付

关注短剧剪辑的朋友&#xff0c;应该能明显感觉到最近两年最大的变化&#xff1a;剪辑不再是一个纯手工活&#xff0c;而是一套“先由AI做粗活、再由人来定调”的协作流程。AI短剧剪辑软件的选型和工作流&#xff0c;也因此成了很多编导、剪辑师和自媒体人反复讨论的话题。写这…

作者头像 李华
网站建设 2026/10/20 8:20:15

计算机组成原理组队学习指南:从补码到迷你CPU仿真

最近带完一期计算机组成原理的组队学习&#xff0c;从任务卡设计、每日打卡、小组互讲再到最后的迷你CPU仿真实验&#xff0c;整套流程走下来&#xff0c;我最大的感受是&#xff1a;这门课真的不适合一个人闷头硬啃&#xff0c;但组队学习的价值也常常被高估。光靠“互相监督打…

作者头像 李华
网站建设 2026/10/20 8:19:14

AI心理服务落地:OpenClaw多模态交互与任务执行框架解析

把大模型接进心理服务&#xff0c;聊起来容易&#xff0c;用起来难。做了大半年的多模态交互与任务执行&#xff0c;我最大的体会是&#xff1a;情绪理解固然重要&#xff0c;但真正让服务落地的&#xff0c;是系统能不能在对话之外完成动作。OpenClaw是我这边用于多模态交互与…

作者头像 李华
网站建设 2026/10/20 8:19:06

LLM自学中删除过时内容的技巧:提升输出质量的关键

1. 为什么“删除”是LLM自学中最被低估的技能1.1 一个反直觉的真相&#xff1a;学得越多&#xff0c;效果可能越差大多数人接触大语言模型自学时&#xff0c;第一反应是“我要多喂数据、多存知识、多积累上下文”。这个思路在传统机器学习里没毛病&#xff0c;数据量越大&#…

作者头像 李华