网络 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_2TUNNEL 入口
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 端口号,范围 165535
mountPoint 31 字符 32 bytes NTRIP 挂载点,仅允许字母、数字、-_.

完整 T_DjiNetworkRtkServiceConfig 长度为 182 bytes,超过 MAVLink TUNNEL 单包 128 bytes,因此本项目使用分片协议下发配置。

功能介绍

注意:

  • 以下代码均为参考示例,旨在帮助开发者理解各功能的调用流程和实现思路。开发者可根据自身负载设备的实际硬件特性和应用场景进行适配和调整。
  • 以下流程中,主动调用的接口由 MAVLink 命令或 TUNNEL 消息触发;状态回调由 SDK 运行时自动触发。

网络 RTK 推荐启动流程:

  1. 注册 Network RTK 状态回调。
  2. 接收地面端通过 MAVLink TUNNEL 下发的 NTRIP 配置。
  3. 地面端发送 MAV_CMD_USER_2 param1=1
  4. 机载端调用 DjiNetworkRtk_StartService()
  5. 通过回调和 TUNNEL payload_type=32792 回传状态。
  6. 若状态为 DJI_REBOOTING,手动重启飞机,重启后再次启动服务。
  7. 若状态为 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() 在首次启动服务前自动完成。状态变化会同时通过 STATUSTEXTTUNNEL 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);
}

推荐的调用流程:

  1. 调用 DjiNetworkRtk_StartService()
  2. 如果状态回调报告 DJI_REBOOTING,手动重启飞机。
  3. 飞机重启后,再次调用 DjiNetworkRtk_StartService()
  4. 监控状态回调等待 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 = 2

MAVLink 配置下发

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_REBOOTINGDJI_BROKENDJI_STOPPING 时为 0
config_bytes_received uint16_t 当前配置已接收字节数
config_total_len uint16_t 配置总长度
last_return_code uint64_t 最近一次 DJI PSDK 返回码

MAVLink 使用流程

  1. 地面端构造 T_DjiNetworkRtkServiceConfig 二进制结构体。
  2. 使用 MAVLink TUNNEL payload_type=32791 分片发送完整配置。
  3. 等待 payload_type=32792 状态回传,确认 configured=1
  4. 发送 MAV_CMD_USER_2param1=1 启动 Network RTK。
  5. 监听 payload_type=32792 状态。若收到 DJI_REBOOTING,需要手动重启飞机,重启后再次发送启动命令。
  6. RTK 服务进入 DJI_TRANSFER 后,继续通过标准 MAVLink 定位消息确认 RTK 是否 Float/Fixed。
  7. 停止服务时发送 MAV_CMD_USER_2param1=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-06-23 14:15
最后编辑:bai  更新时间:2026-07-08 09:44