网络 RTK
概述
DJI 开放机载的网络 RTK 能力,结合无人机 4G 模块联网能力,可以使用网络 RTK 服务实现厘米级精度定位需求。开发者通过 PSDK 提供的网络 RTK 接口,可以配置 NTRIP 服务参数、启动和停止网络 RTK 服务,并通过回调函数实时监控 RTK 服务的运行状态。
本项目在 PSDK Network RTK 能力基础上增加 MAVLink 对接:地面端通过 MAVLink 下发 NTRIP 配置、启动/停止 Network RTK 服务,并通过 MAVLink TUNNEL 获取服务状态。
说明:
- 该功能适用于 Matrice 400 + 妙算 3 搭配使用。
- Network RTK 只负责建立 NTRIP/RTCM 数据链路,不直接输出飞行定位坐标。
- RTK 收敛后的高精度坐标由 RTK 精准定位功能输出到标准 MAVLink 定位消息。
接口总览
开发者需要先注册状态回调,再启动网络 RTK 服务。下表按功能分类列出网络 RTK 的主要接口和回调:
| 功能分类 | 接口 / 回调函数 | 功能说明 |
|---|---|---|
| 状态回调注册 | DjiNetworkRtk_RegReceiveNetworkRtkStateCallback() |
注册网络 RTK 服务状态接收回调,建议在启动服务之前注册 |
| 启动服务 | DjiNetworkRtk_StartService() |
使用指定的 NTRIP 配置启动网络 RTK 服务 |
| 停止服务 | DjiNetworkRtk_StopService() |
停止网络 RTK 服务,断开高精度定位数据接收 |
本项目 MAVLink 封装接口如下:
| 功能分类 | MAVLink 接口 | 功能说明 |
|---|---|---|
| 配置下发 | TUNNEL payload_type=32791 |
分片下发 T_DjiNetworkRtkServiceConfig 配置 |
| 状态回传 | TUNNEL payload_type=32792 |
回传 Network RTK 状态、配置接收进度和最近一次 PSDK 返回码 |
| 启停控制 | COMMAND_LONG / MAV_CMD_USER_2 |
启动、停止、查询 Network RTK 服务 |
相关文件
| 文件 | 说明 |
|---|---|
src/dji_mavlink/dji_network_rtk_mavlink.hpp |
Network RTK MAVLink 接口和 payload type 定义 |
src/dji_mavlink/dji_network_rtk_mavlink.cpp |
配置分片重组、参数校验、Network RTK 启停、状态回传 |
src/dji_mavlink/mavlink_connector.cpp |
MAV_CMD_USER_2 和 TUNNEL 入口 |
psdk_lib/include/dji_network_rtk.h |
DJI PSDK Network RTK 官方接口 |
基础概念
网络 RTK 服务状态
网络 RTK 服务运行过程中会通过状态回调通知当前的服务状态,各状态含义如下:
| 状态枚举 | 值 | 说明 |
|---|---|---|
DJI_LOG_IN |
0 |
正在登录 NTRIP 服务 |
DJI_CONNECTING |
1 |
正在连接远端服务器 |
DJI_TRANSFER |
2 |
数据传输中,服务已成功启动。注意:此状态不代表 RTK 解算已收敛,需另行订阅 RTK 定位数据验证精度 |
DJI_RECONNECT |
3 |
正在尝试重新连接 |
DJI_BROKEN |
4 |
连接断开 |
DJI_STOPPING |
5 |
服务正在停止 |
DJI_REBOOTING |
6 |
需要重启飞机以切换 RTK 源。飞机不会自动重启,需手动重启后再次调用启动接口 |
NTRIP 服务配置参数
启动网络 RTK 服务时需要配置以下 NTRIP 参数:
| 参数 | 最大长度 | 结构体长度 | 说明 |
|---|---|---|---|
account |
15 字符 | 16 bytes | NTRIP 用户名 |
password |
63 字符 | 64 bytes | NTRIP 密码 |
host |
63 字符 | 64 bytes | NTRIP Caster 地址,仅支持 IP 地址,不支持域名 |
port |
5 字符 | 6 bytes | NTRIP 端口号,范围 1 到 65535 |
mountPoint |
31 字符 | 32 bytes | NTRIP 挂载点,仅允许字母、数字、-、_、. |
完整 T_DjiNetworkRtkServiceConfig 长度为 182 bytes,超过 MAVLink TUNNEL 单包 128 bytes,因此本项目使用分片协议下发配置。
功能介绍
注意:
- 以下代码均为参考示例,旨在帮助开发者理解各功能的调用流程和实现思路。开发者可根据自身负载设备的实际硬件特性和应用场景进行适配和调整。
- 以下流程中,主动调用的接口由 MAVLink 命令或 TUNNEL 消息触发;状态回调由 SDK 运行时自动触发。
网络 RTK 推荐启动流程:
- 注册 Network RTK 状态回调。
- 接收地面端通过 MAVLink TUNNEL 下发的 NTRIP 配置。
- 地面端发送
MAV_CMD_USER_2 param1=1。 - 机载端调用
DjiNetworkRtk_StartService()。 - 通过回调和
TUNNEL payload_type=32792回传状态。 - 若状态为
DJI_REBOOTING,手动重启飞机,重启后再次启动服务。 - 若状态为
DJI_TRANSFER,继续通过 RTK 定位数据确认是否收敛到 Float/Fixed。
状态回调注册
建议在启动 RTK 服务之前先注册状态回调函数,以便及时获取服务状态变化。各状态的含义参见“基础概念 - 网络 RTK 服务状态”。
static void ReceiveNetworkRtkStateCallback(E_DjiNetworkRtkOnboardState state)
{
switch (state) {
case DJI_LOG_IN:
USER_LOG_INFO("Network RTK state: Logging in");
break;
case DJI_CONNECTING:
USER_LOG_INFO("Network RTK state: Connecting");
break;
case DJI_TRANSFER:
USER_LOG_INFO("Network RTK state: Data transfer in progress, service started successfully");
break;
case DJI_RECONNECT:
USER_LOG_WARN("Network RTK state: Reconnecting");
break;
case DJI_BROKEN:
USER_LOG_ERROR("Network RTK state: Connection broken");
break;
case DJI_STOPPING:
USER_LOG_INFO("Network RTK state: Stopping");
break;
case DJI_REBOOTING:
USER_LOG_WARN("Network RTK state: Rebooting required, please manually reboot the aircraft");
break;
default:
USER_LOG_WARN("Network RTK state: Unknown state %d", state);
break;
}
}
returnCode = DjiNetworkRtk_RegReceiveNetworkRtkStateCallback(ReceiveNetworkRtkStateCallback);
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {
USER_LOG_ERROR("Register network RTK state callback failed, error code: 0x%08X", returnCode);
}本项目中,回调注册由 DjiNetworkRtkMavlink_Start() 在首次启动服务前自动完成。状态变化会同时通过 STATUSTEXT 和 TUNNEL payload_type=32792 回传到 MAVLink 端。
启动服务
通过 DjiNetworkRtk_StartService() 接口配置 NTRIP 参数并启动 RTK 服务。启动后需监控状态回调:
- 如果状态为
DJI_REBOOTING:表示当前 RTK 源不是自定义网络源,需要手动重启飞机完成源切换,重启后需再次调用此接口启动服务。 - 如果状态为
DJI_TRANSFER:表示网络 RTK 服务已连接成功。
T_DjiNetworkRtkServiceConfig config = {0};
// 配置 NTRIP 服务参数
strncpy((char *)config.account, "your_account", sizeof(config.account) - 1);
strncpy((char *)config.password, "your_password", sizeof(config.password) - 1);
strncpy((char *)config.host, "192.168.1.100", sizeof(config.host) - 1); // 仅支持 IP 地址,不支持域名
strncpy((char *)config.port, "8001", sizeof(config.port) - 1);
strncpy((char *)config.mountPoint, "RTCM32_GGB", sizeof(config.mountPoint) - 1);
returnCode = DjiNetworkRtk_StartService(&config);
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {
USER_LOG_ERROR("Start network RTK service failed, error code: 0x%08X", returnCode);
}推荐的调用流程:
- 调用
DjiNetworkRtk_StartService()。 - 如果状态回调报告
DJI_REBOOTING,手动重启飞机。 - 飞机重启后,再次调用
DjiNetworkRtk_StartService()。 - 监控状态回调等待
DJI_TRANSFER状态,并订阅 RTK 定位数据验证收敛。
本项目中,启动服务由 MAVLink 触发:
COMMAND_LONG.command = MAV_CMD_USER_2
COMMAND_LONG.param1 = 1前提条件:地面端必须先通过 TUNNEL payload_type=32791 下发有效配置。未配置时启动会失败,并通过 COMMAND_ACK=MAV_RESULT_FAILED 和状态 payload 回传错误码。
停止服务
当不再需要使用网络 RTK 服务时,调用以下接口停止服务。停止后可通过回调收到 DJI_STOPPING 状态。
returnCode = DjiNetworkRtk_StopService();
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {
USER_LOG_ERROR("Stop network RTK service failed, error code: 0x%08X", returnCode);
}本项目中,停止服务由 MAVLink 触发:
COMMAND_LONG.command = MAV_CMD_USER_2
COMMAND_LONG.param1 = 0查询当前状态:
COMMAND_LONG.command = MAV_CMD_USER_2
COMMAND_LONG.param1 = 2MAVLink 配置下发
Network RTK 配置通过 MAVLink TUNNEL 分片下发。
| 字段 | 值 |
|---|---|
payload_type |
32791 |
payload |
NetworkRtkConfigChunkHeader + data |
分片 Header
| 字段 | 类型 | 说明 |
|---|---|---|
version |
uint8_t |
当前为 1 |
config_id |
uint8_t |
配置批次 ID;新配置使用新的 ID |
total_len |
uint16_t |
固定为 sizeof(T_DjiNetworkRtkServiceConfig),当前为 182 |
offset |
uint16_t |
当前 data 在完整配置结构体中的偏移 |
每个 TUNNEL payload 最大 128 bytes。Header 为 6 bytes,因此单包最多携带 122 bytes 配置数据。
配置数据体必须按 DJI PSDK T_DjiNetworkRtkServiceConfig 的内存布局发送:
account[16] + password[64] + host[64] + port[6] + mountPoint[32]MAVLink 状态回传
Network RTK 状态通过 MAVLink TUNNEL 回传。
| 字段 | 值 |
|---|---|
payload_type |
32792 |
payload |
NetworkRtkStatePayload |
状态 Payload
| 字段 | 类型 | 说明 |
|---|---|---|
version |
uint8_t |
当前为 1 |
network_state |
uint8_t |
DJI Network RTK 状态 |
configured |
uint8_t |
1 表示已有有效配置 |
running |
uint8_t |
1 表示服务处于登录、连接、传输或重连状态;DJI_REBOOTING、DJI_BROKEN、DJI_STOPPING 时为 0 |
config_bytes_received |
uint16_t |
当前配置已接收字节数 |
config_total_len |
uint16_t |
配置总长度 |
last_return_code |
uint64_t |
最近一次 DJI PSDK 返回码 |
MAVLink 使用流程
- 地面端构造
T_DjiNetworkRtkServiceConfig二进制结构体。 - 使用
MAVLink TUNNEL payload_type=32791分片发送完整配置。 - 等待
payload_type=32792状态回传,确认configured=1。 - 发送
MAV_CMD_USER_2,param1=1启动 Network RTK。 - 监听
payload_type=32792状态。若收到DJI_REBOOTING,需要手动重启飞机,重启后再次发送启动命令。 - RTK 服务进入
DJI_TRANSFER后,继续通过标准 MAVLink 定位消息确认 RTK 是否 Float/Fixed。 - 停止服务时发送
MAV_CMD_USER_2,param1=0。
Python 例程
以下例程使用 pymavlink 构造 T_DjiNetworkRtkServiceConfig,通过 TUNNEL payload_type=32791 分片下发配置,然后使用 MAV_CMD_USER_2 启动 Network RTK,并监听 payload_type=32792 状态回传。
import struct
import time
from pymavlink import mavutil
NETWORK_RTK_CONFIG_PAYLOAD_TYPE = 32791
NETWORK_RTK_STATE_PAYLOAD_TYPE = 32792
MAV_CMD_USER_2 = mavutil.mavlink.MAV_CMD_USER_2
CONFIG_TOTAL_LEN = 16 + 64 + 64 + 6 + 32
CHUNK_HEADER_FMT = "<BBHH" # version, config_id, total_len, offset
STATE_PAYLOAD_FMT = "<BBBBHHQ" # version, state, configured, running, rx_bytes, total_len, last_return_code
CHUNK_HEADER_LEN = struct.calcsize(CHUNK_HEADER_FMT)
MAX_TUNNEL_PAYLOAD_LEN = 128
MAX_CHUNK_DATA_LEN = MAX_TUNNEL_PAYLOAD_LEN - CHUNK_HEADER_LEN
NETWORK_RTK_STATES = {
0: "DJI_LOG_IN",
1: "DJI_CONNECTING",
2: "DJI_TRANSFER",
3: "DJI_RECONNECT",
4: "DJI_BROKEN",
5: "DJI_STOPPING",
6: "DJI_REBOOTING",
255: "UNKNOWN",
}
def get_current_millis():
return int(time.time() * 1000)
def fixed_c_string(text, size):
raw = text.encode("ascii")
if len(raw) > size - 1:
raise ValueError(f"字段过长: {text}, 最大长度 {size - 1}")
return raw + b"\x00" * (size - len(raw))
def build_network_rtk_config(account, password, host, port, mount_point):
# 必须与 T_DjiNetworkRtkServiceConfig 内存布局一致:
# account[16] + password[64] + host[64] + port[6] + mountPoint[32]
return b"".join([
fixed_c_string(account, 16),
fixed_c_string(password, 64),
fixed_c_string(host, 64),
fixed_c_string(str(port), 6),
fixed_c_string(mount_point, 32),
])
def send_tunnel(master, payload_type, payload):
tunnel_payload = list(payload) + [0] * (128 - len(payload))
master.mav.tunnel_send(
master.target_system,
master.target_component,
payload_type,
len(payload),
tunnel_payload,
)
def send_network_rtk_config(master, config_bytes, config_id=1):
if len(config_bytes) != CONFIG_TOTAL_LEN:
raise ValueError(f"配置长度错误: {len(config_bytes)} != {CONFIG_TOTAL_LEN}")
print(f"开始下发 Network RTK 配置,总长度 {len(config_bytes)} bytes")
for offset in range(0, len(config_bytes), MAX_CHUNK_DATA_LEN):
chunk = config_bytes[offset:offset + MAX_CHUNK_DATA_LEN]
header = struct.pack(CHUNK_HEADER_FMT, 1, config_id, CONFIG_TOTAL_LEN, offset)
payload = header + chunk
send_time = get_current_millis()
send_tunnel(master, NETWORK_RTK_CONFIG_PAYLOAD_TYPE, payload)
print(f"[{send_time}] 发送配置分片: config_id={config_id}, offset={offset}, size={len(chunk)}")
# 等待一次状态回传,确认机载端接收进度
deadline = time.time() + 2.0
while time.time() < deadline:
msg = master.recv_match(type="TUNNEL", blocking=True, timeout=0.5)
if msg and msg.payload_type == NETWORK_RTK_STATE_PAYLOAD_TYPE:
state = parse_network_rtk_state(msg)
if state:
print_state(state)
break
time.sleep(0.05)
def parse_network_rtk_state(msg):
payload = bytes(msg.payload[:msg.payload_length])
if len(payload) < struct.calcsize(STATE_PAYLOAD_FMT):
return None
version, state, configured, running, rx_bytes, total_len, last_return_code = struct.unpack_from(
STATE_PAYLOAD_FMT, payload, 0
)
return {
"version": version,
"state": state,
"configured": configured,
"running": running,
"rx_bytes": rx_bytes,
"total_len": total_len,
"last_return_code": last_return_code,
}
def print_state(state):
now_ms = get_current_millis()
state_name = NETWORK_RTK_STATES.get(state["state"], f"UNKNOWN({state['state']})")
print(
f"[{now_ms}] Network RTK 状态: {state_name}, "
f"configured={state['configured']}, running={state['running']}, "
f"rx={state['rx_bytes']}/{state['total_len']}, "
f"last_return_code=0x{state['last_return_code']:016X}"
)
if state["state"] == 6:
print(" 提示:收到 DJI_REBOOTING,需要手动重启飞机,重启后再次发送启动命令")
elif state["state"] == 2:
print(" 提示:收到 DJI_TRANSFER,网络 RTK 数据链路已建立,但仍需查看 GPS_RAW_INT.fix_type 确认是否收敛")
def send_network_rtk_command(master, action):
# action: 1=start, 0=stop, 2=status
send_time = get_current_millis()
print(f"[{send_time}] 发送 Network RTK 命令: action={action}")
master.mav.command_long_send(
master.target_system,
master.target_component,
MAV_CMD_USER_2,
0,
action,
0, 0, 0, 0, 0, 0
)
ack = master.recv_match(type="COMMAND_ACK", blocking=True, timeout=5.0)
if ack:
ack_time = get_current_millis()
print(
f"[{ack_time}] 收到 COMMAND_ACK: "
f"command={ack.command}, result={ack.result}, delay={ack_time - send_time}ms"
)
return ack.result == mavutil.mavlink.MAV_RESULT_ACCEPTED
print(f"[{get_current_millis()}] 超时:未收到 Network RTK 命令应答")
return False
# 连接无人机(UDP 端口 14550)
master = mavutil.mavlink_connection("udpin:0.0.0.0:14550")
print("等待无人机心跳...")
master.wait_heartbeat()
print(f"无人机已连接: system={master.target_system}, component={master.target_component}")
# 按实际 NTRIP 服务修改以下参数。host 必须是 IPv4/IPv6 地址,不支持域名。
config = build_network_rtk_config(
account="your_account",
password="your_password",
host="192.168.1.100",
port="8001",
mount_point="RTCM32_GGB",
)
send_network_rtk_config(master, config, config_id=1)
if not send_network_rtk_command(master, action=1):
raise SystemExit("Network RTK 启动命令失败")
print("开始监听 Network RTK 状态,按 Ctrl+C 停止服务")
try:
while True:
msg = master.recv_match(type="TUNNEL", blocking=True, timeout=5.0)
if msg is None:
print(f"[{get_current_millis()}] 超时:未收到 Network RTK 状态")
continue
if msg.payload_type != NETWORK_RTK_STATE_PAYLOAD_TYPE:
continue
state = parse_network_rtk_state(msg)
if state:
print_state(state)
except KeyboardInterrupt:
print("准备停止 Network RTK")
finally:
send_network_rtk_command(master, action=0)
print("Network RTK 例程结束")注意事项
- Network RTK 配置中不要使用域名,DJI PSDK 接口要求
host为 IPv4 或 IPv6 地址。 DJI_TRANSFER只表示网络 RTK 数据链路建立,不表示 RTK 已经固定解。定位质量必须查看GPS_RAW_INT.fix_type或 RTK 订阅输出。- 配置分片为二进制协议,不是 JSON;地面端需要按小端格式写入 header。
- 密码不会通过日志和状态文本输出,地面端也应避免在 UI 或日志中明文保存。
- 如果未先发送有效配置就启动,机载端会返回失败,并通过状态 payload 回传错误码。
最后编辑:bai 更新时间:2026-07-08 09:44