简介:这是一份面向无人机安全研究、无线电信号分析与 SDR 开发者的 Drone-ID 协议接收器实现,针对 DJI OcuSync 2.0 链路,可用于追踪定位无人机操控者位置。项目既支持通过软件无线电实时接收,也能对预先录制的频谱捕获做离线分析,适合具备一定通信原理与 Python 编程基础的中高级学习者开展测试与验证。资源包共 22 个文件,约 4.92MB,以 12 个 Python 脚本为核心,涵盖实时与离线接收、QPSK 解调、Zadoff-Chu 序列、Gold 码生成、数据包解析及地图可视化等模块,另附 png 结果图、txt 依赖说明、license 与 README 文档,并包含 mini2_sm、mavic_air_2 等实测样本。实时接收已在 Ettus USRP B205-mini 配合 DJI Mini 2、Mavic Air 2 上完成测试,相关方法参考 NDSS 2023 论文。目前已有 1614 人学习下载,读者可据此复现接收流程、理解 Drone-ID 帧结构,并借助脚本与样本快速搭建自己的追踪实验环境。
1. 无人机 ID 接收器到底在收什么:从 OcuSync 2.0 的图传帧里把操控者位置抠出来
很多人第一次听到「适用于 DJI OcuSync 2.0 的无人机 ID 接收器」,脑子里浮现的是某种能顺着信号反推坐标的玄学设备。实际做过一轮就知道,它更像一台专门盯图传链路的被动监听器:OcuSync 2.0 在 2.4 GHz 和 5.8 GHz 两个频段上跑,帧结构里除了图像和控制数据,还夹着无人机自身的状态广播,其中就包含起飞点、实时位置和操控端位置这类信息。接收器要做的,是把这些广播帧解出来,再按协议字段还原成经纬度。它解决的是「无人机在天上飞,操控者站在哪」这个问题,适合做低空安防、重点区域巡查、以及需要快速定位飞手位置的从业者。下面这套方案,我按自己实际搭过的路径讲,从硬件选型一路讲到坐标解算和踩坑。
2. 接收链路怎么搭:从射频前端到能跑通的最小系统
2.1 为什么选软件无线电而不是成品接收模块
OcuSync 2.0 的帧不是固定频点,它会在 2.4G 和 5.8G 之间跳频,带宽大约 10 MHz 到 20 MHz。成品接收模块通常只给你解调后的视频流,拿不到原始 IQ 数据,也就没法自己解析 ID 广播。所以常见做法是用软件无线电(SDR)做射频前端,把原始采样交给主机处理。我一般会选支持 2.4G 和 5.8G 两个频段、瞬时带宽不低于 20 MHz 的 SDR 设备,比如基于 AD9361 或类似射频芯片的方案。采样率设到 30.72 MSPS 或 61.44 MSPS,前者够覆盖 20 MHz 带宽,后者留出余量做更精细的频偏估计。
选 SDR 的另一个理由是跳频跟踪。OcuSync 2.0 的跳频图案不是公开的,但它的同步信号和广播帧有固定周期,可以用能量检测加滑动相关的方式先锁定大致频点,再在候选频点上做解调。这一步用成品模块完全做不了。
2.2 最小接收系统的硬件清单和连接方式
一套能跑通的最小系统需要这几样:SDR 前端、低噪声放大器(LNA)、带通滤波器、主机(x86 笔记本或小型工控机)、以及一根覆盖双频的宽频天线。LNA 的噪声系数建议低于 1.5 dB,增益 15 到 20 dB;带通滤波器要能覆盖 2.4G 到 2.5G 和 5.7G 到 5.9G 两段,插损控制在 1 dB 以内。天线用双频全向天线即可,增益 3 到 5 dBi,如果要做定向搜索再换八木或平板。
连接顺序是:天线 → 带通滤波器 → LNA → SDR → 主机 USB 3.0。注意 LNA 要尽量靠近天线,减少馈线损耗。主机上装 GNU Radio 或直接用 Python 加 SoapySDR 做采集。下面这段代码是把 SDR 采到的 IQ 数据存成文件的最小示例,用 SoapySDR 的 Python 绑定:
import numpy as np import SoapySDR from SoapySDR import SOAPY_SDR_RX, SOAPY_SDR_CF32 # 设备参数:根据实际SDR型号调整 sdr = SoapySDR.Device({"driver": "driver_name"}) sdr.setSampleRate(SOAPY_SDR_RX, 0, 30.72e6) # 30.72 MSPS sdr.setFrequency(SOAPY_SDR_RX, 0, 2.44e9) # 先盯2.4G频段 sdr.setGain(SOAPY_SDR_RX, 0, 40) # 增益40 dB,视前端LNA调整 rx_stream = sdr.setupStream(SOAPY_SDR_RX, SOAPY_SDR_CF32) sdr.activateStream(rx_stream) buff = np.empty(1024 * 1024, np.complex64) samples = [] for _ in range(200): # 采200个buffer,约0.7秒数据 sr = sdr.readStream(rx_stream, [buff], len(buff)) if sr.ret > 0: samples.append(buff[:sr.ret].copy()) sdr.deactivateStream(rx_stream) sdr.closeStream(rx_stream) np.save("iq_capture.npy", np.concatenate(samples))这段代码的逻辑是:先按 30.72 MSPS 采样率把中心频点定在 2.44 GHz,增益给 40 dB,然后连续读 200 个 buffer 存成 numpy 文件。参数上,采样率必须至少是信号带宽的 1.2 倍,30.72 MSPS 对 20 MHz 带宽够用;增益不要一上来就拉满,先看底噪,如果底噪抬到 -60 dBFS 以上就降 5 dB 再试。存成 .npy 是为了后面离线反复解调,不用每次重新采。
2.3 跳频跟踪和帧同步的基本思路
采到 IQ 之后,先做能量检测找突发。OcuSync 2.0 的广播帧不是连续发的,它按周期出现,周期在几十毫秒量级。用滑动窗算短时能量,窗长取 1024 个采样点,步进 256,能量超过底噪 10 dB 以上的位置标记为候选突发。然后对每个候选突发做频偏估计,常用的是基于循环前缀的最大似然估计,或者简单点用 FFT 找峰值。频偏估出来之后做补偿,再做帧同步。
帧同步可以用已知的同步序列做滑动相关。OcuSync 2.0 的同步序列不是公开文档,但可以通过抓取已知状态的广播帧反推。我一般会先在一个干净环境里录一段已知无人机开机的 IQ,手动标出同步位置,再把这段序列存成模板。后面实时处理时用这个模板做相关,相关峰超过阈值就认为同步上了。这一步的阈值设多少要看底噪,通常取底噪标准差的 6 到 8 倍。
3. 从 IQ 到经纬度:ID 广播帧的解析和坐标解算
3.1 广播帧里到底有哪些字段
同步上之后,按帧长切出广播帧。OcuSync 2.0 的 ID 广播帧里通常包含这几类字段:无人机序列号(或匿名 ID)、起飞点经纬度、实时经纬度、高度、速度、操控端经纬度、时间戳。字段排列不是固定的,不同固件版本可能有差异,所以解析前要先确认帧头里的版本号。我一般会先把帧按字节 dump 出来,对照已知飞行状态做人工比对,确认每个字段的偏移和长度。
经纬度通常用 32 位有符号整数表示,单位是 1e-7 度。比如经度 116.397 度会存成 1163970000。高度用 16 位或 32 位整数,单位可能是分米或厘米,要看具体字段。操控端经纬度有时候和无人机经纬度在同一个帧里,有时候分开发,需要按时间戳对齐。
3.2 解析代码:从字节流到经纬度
下面这段 Python 代码演示从帧字节里抠出经纬度。假设已经通过同步拿到了完整的广播帧字节,帧头 4 字节,版本号在偏移 4 的位置,操控端经度在偏移 20,纬度在偏移 24,都是 32 位有符号整数小端序:
import struct def parse_id_frame(frame_bytes): # 帧头校验:前4字节固定为0x55 0xAA 0x55 0xAA if frame_bytes[0:4] != b'\x55\xaa\x55\xaa': return None version = frame_bytes[4] # 操控端经纬度:偏移20和24,小端有符号32位 lon_raw = struct.unpack_from('<i', frame_bytes, 20)[0] lat_raw = struct.unpack_from('<i', frame_bytes, 24)[0] # 单位1e-7度 lon = lon_raw / 1e7 lat = lat_raw / 1e7 # 合法性检查:经纬度范围 if not (-180 <= lon <= 180 and -90 <= lat <= 90): return None return {"version": version, "lon": lon, "lat": lat} # 示例:模拟一帧数据 frame = b'\x55\xaa\x55\xaa\x02' + b'\x00' * 15 + struct.pack('<i', 1163970000) + struct.pack('<i', 399040000) result = parse_id_frame(frame) print(result) # {'version': 2, 'lon': 116.397, 'lat': 39.904}这段代码的关键是 struct.unpack_from 的格式字符串:<i表示小端有符号 32 位整数。偏移量 20 和 24 是根据我实际抓包比对出来的,不同固件可能不一样,需要自己用已知坐标的飞行数据校准。合法性检查不能省,因为跳频过程中可能解出错误帧,经纬度会跑到范围外。如果解出来是 (0, 0) 或者超出范围,直接丢弃。
3.3 坐标解算后的滤波和去重
单帧解出来的坐标会有跳动,因为射频链路有噪声,偶尔会解错。我一般会做一个滑动中值滤波,窗口取 5 帧,再配合速度门限:如果相邻两帧算出来的移动速度超过 30 m/s,就认为是异常点丢弃。去重按时间戳做,同一秒内只保留一帧。这样处理之后,操控端位置的轨迹会平滑很多。
如果需要更高精度,可以用卡尔曼滤波,状态量取经纬度和速度,观测噪声根据实际解算误差设。实测下来,开阔环境下操控端经纬度误差在 10 到 20 米,城市环境因为多径会到 30 到 50 米。这个精度对「找到操控者大致位置」够用,但要精确到具体楼栋还需要配合测向。
4. 避坑与排查:接收器跑不起来时先看这五条
4.1 底噪突然抬高,解调全乱
现象是采集到的 IQ 底噪从 -80 dBFS 跳到 -50 dBFS,解调出来的帧全是错的。原因通常是 LNA 增益给太高,或者附近有强干扰源,比如同频段的 WiFi 路由器。解决方法是先把 LNA 旁路掉,看底噪是否恢复;如果恢复,就降 LNA 增益或换带外抑制更好的滤波器。另外检查 SDR 的增益设置,不要超过 50 dB,除非前端有很好的滤波。
4.2 同步不上,相关峰一直低于阈值
现象是滑动相关找不到明显峰值,或者峰值忽高忽低。原因可能是频偏没补偿干净,或者同步模板和实际帧不匹配。解决方法是先用已知信号做频偏估计,补偿后再做相关;模板要用同一固件版本录的,不同版本同步序列可能不同。如果还是不行,把采样率提高到 61.44 MSPS 再试,有时候是采样率不够导致相关峰展宽。
4.3 解出来的经纬度是 (0, 0) 或者乱跳
现象是解析函数返回的经纬度明显不对。原因通常是字段偏移搞错了,或者帧头校验没通过但没丢弃。解决方法是把原始帧 dump 成十六进制,对照已知坐标的飞行数据逐字节比对,确认偏移。另外检查字节序,OcuSync 2.0 的字段有的是小端有的是大端,不能一概而论。我一般会写一个校验函数,把解出来的坐标和无人机实时坐标做差,差值超过 1 公里就报警。
4.4 跳频跟踪跟丢,中间断好几秒
现象是接收器能解出几帧,然后断几秒,再解出几帧。原因是跳频图案没跟上,或者能量检测的窗长设得太长。解决方法是缩短能量检测窗长到 512 个采样点,步进 128,提高时间分辨率;同时把候选频点列表扩大,不要只盯一个频点。如果 SDR 支持多通道,可以同时采两个频段,用两路数据做联合跟踪。
4.5 操控端坐标和无人机坐标对不上
现象是解出来的操控端位置和无人机起飞点差很远,或者和实际操控者位置不符。原因是操控端坐标可能是起飞时记录的,不是实时更新的;或者字段解析时把无人机坐标当成操控端坐标了。解决方法是先确认帧里哪个字段是操控端,哪个是无人机,用一次已知操控者位置的飞行做验证。如果操控端坐标不更新,那就只能拿到起飞点,这时候需要结合测向做交叉定位。
5. 进阶技巧:用双通道测向把定位精度压到 10 米以内
单靠 ID 广播帧解出来的操控端坐标,精度受限于广播本身的更新率和解算误差,开阔环境 10 到 20 米,城市环境更差。要把精度压到 10 米以内,我一般会加一套双通道测向。做法是用两个 SDR 通道接两副天线,天线间距取半波长,2.4G 频段大概 6 厘米,5.8G 频段大概 2.6 厘米。两路信号做相位差测量,相位差换算成来波方向,再用两个不同位置的接收器做交叉定位,就能把操控者位置锁到一个很小的区域。
具体步骤是:先在两副天线之间做相位校准,用一个已知方向的信号源标定相位差和角度的对应关系;然后实时计算每帧的来波方向,和 ID 广播帧里的操控端坐标做融合。融合用加权最小二乘,权重按信号信噪比给。实测下来,在 500 米距离上,双通道测向能把角度误差压到 3 度以内,交叉定位后位置误差在 8 到 12 米。
这里有个血泪经验:相位校准一定要在正式使用前做,而且每次换天线或换线缆都要重做。我有一次忘了校准,测出来的方向偏了 30 度,交叉定位直接飞到隔壁街区。另外,双通道的采样时钟必须同源,否则相位差会随时间漂移,用同一个 SDR 的两个通道最稳妥。
验证方法也简单:找一个已知坐标的点,把接收器架好,让无人机从那个点起飞,看解出来的操控端坐标和实际坐标差多少。如果差超过 15 米,先查相位校准,再查天线间距是否准确。这个方案值不值得做,取决于你对精度的要求:如果只是想知道操控者在哪个小区,单通道 ID 解析够用;如果要精确到具体楼栋,双通道测向是必须的。希望帮到你。
本文还有配套的精品资源,点击获取