DraARLv1 协议规范¶
协议概述¶
DraARLv1 (Digital Radio Advanced Application Protocol v1) 是 DraARL 平台的设备通信协议,用于设备与服务器之间的实时通信。
报文结构¶
整体布局¶
+--------+--------+--------+--------+--------+--------+--------+--------+
| 0-3 | 4-5 | 6-37 | 38-47 | 48 | 49 | 50 | 51-53 |
| Ver | Length |Username|DevPass | Type |DevModel| SSID | DMRID |
| 4B | 2B | 32B | 10B | 1B | 1B | 1B | 3B |
+--------+--------+--------+--------+--------+--------+--------+--------+
| 54-85 | 86-89 | 90+ |
|CallSign| Rsv | DATA |
| 32B | 4B | 变长 |
+--------+--------+--------+-------------------------------------------------+
固定头部:90 字节
最小报文:90 字节
最大报文:800 字节(UDP 服务端限制,超出将被静默丢弃)
字节序¶
所有多字节字段使用 大端序 (Big-Endian)
字段详解¶
Header 字段¶
| 偏移 | 长度 | 字段名 | 类型 | 说明 |
|---|---|---|---|---|
| 0 | 4B | Version | string | 协议版本标识,固定为 "DraA" (0x44726141) |
| 4 | 2B | Length | uint16 BE | 报文总长度(包含头部和数据) |
| 6 | 32B | Username | string | 用户名,UTF-8 编码,不足部分用 \0 填充 |
| 38 | 10B | DevicePassword | string | 设备准入密码,ASCII 字母数字,不足部分用 \0 填充 |
| 48 | 1B | Type | byte | 数据包类型,见 数据包类型 |
| 49 | 1B | DevModel | byte | 设备型号,见 设备型号 |
| 50 | 1B | SSID | byte | 设备子号 (0-255) |
| 51 | 3B | DMRID | uint24 BE | DMR ID |
| 54 | 32B | CallSign | string | 业余电台呼号,服务器填充,设备发送时留空 |
| 86 | 4B | Reserved | - | 保留字段,填 0 |
| 90 | 变长 | DATA | []byte | 负载数据 |
数据包类型¶
| 值 | 常量名 | 说明 | DATA 内容 |
|---|---|---|---|
| 0 | TypeControl | 控制指令 | 控制命令数据(见 控制指令格式) |
| 1 | TypeJWTAuth | JWT 认证 | JWT Token 字符串(见 JWT 认证包格式) |
| 2 | TypeHeartbeat | 心跳包 | 可选携带 GPS 位置信息,并可在 DATA 末尾追加 MAC |
| 3 | TypeConfig | 设备配置 | TLV 格式配置数据(见 Config 包协议) |
| 4 | TypeTextMessage | 文本消息 | UTF-8 编码文本(见 文本消息格式) |
| 5 | TypeOpus16K | Opus 16K 语音 | Opus 16kHz 编码语音帧 |
| 6 | TypeServerVoice | 服务器互联语音 | 见 服务器互联语音格式 |
| 7 | TypeATPassThrough | AT 透传 | AT 命令透传(见 AT 透传格式) |
控制指令格式 (TypeControl)¶
当 Type = 0 (TypeControl) 时,用于设备与服务器之间的控制命令交互。
控制命令类型¶
| DATA[0] | 命令名 | 方向 | 说明 |
|---|---|---|---|
| 0x01 | PTT Start | 设备→服务器 | PTT 按下,开始发射 |
| 0x02 | PTT Stop | 设备→服务器 | PTT 松开,停止发射 |
| 0x03 | Query Status | 双向 | 查询设备/服务器状态 |
| 0x04 | Status Response | 双向 | 状态响应 |
| 0x05 | Reboot | 服务器→设备 | 远程重启设备 |
| 0x06 | Factory Reset | 服务器→设备 | 恢复出厂设置 |
DATA 格式¶
+--------+--------+--------+
| 0 | 1 | 2+ |
| CmdID | Status | Payload|
| 1B | 1B | 变长 |
+--------+--------+--------+
| 偏移 | 长度 | 字段名 | 说明 |
|---|---|---|---|
| 0 | 1B | CommandID | 控制命令类型 |
| 1 | 1B | Status | 状态码(0=成功,非0=错误) |
| 2 | 变长 | Payload | 可选负载数据 |
PTT 控制流程¶
文本消息格式 (TypeTextMessage)¶
当 Type = 4 (TypeTextMessage) 时,DATA 区域携带 UTF-8 编码的文本消息。
DATA 格式¶
+--------+--------+
| 0 | 1+ |
| Flags | Text |
| 1B | 变长 |
+--------+--------+
| 偏移 | 长度 | 字段名 | 说明 |
|---|---|---|---|
| 0 | 1B | Flags | 标志位(见下表) |
| 1 | 变长 | Text | UTF-8 编码文本内容 |
Flags 标志位¶
| Bit | 名称 | 说明 |
|---|---|---|
| 0 | Urgent | 紧急消息(1=紧急) |
| 1 | System | 系统消息(1=系统) |
| 2-7 | Reserved | 保留,填0 |
消息路由规则¶
- 群组消息:发送到设备当前所属群组的所有在线设备
- 系统消息:由服务器生成,广播到所有在线设备
- 紧急消息:优先显示,可触发设备端告警
示例¶
普通文本消息:
DATA = [0x00, 0xE4, 0xBD, 0xA0, 0xE5, 0xA5, 0xBD]
│ └──────────────────────────────────────┘
│ "你好" (UTF-8)
└─ Flags = 0 (普通消息)
紧急系统消息:
DATA = [0x03, 系统消息UTF-8字节...]
│ └── 紧急系统消息内容
└─ Flags = 0x03 (Urgent + System)
AT 透传格式 (TypeATPassThrough)¶
当 Type = 7 (TypeATPassThrough) 时,用于设备与服务器之间的 AT 命令透传。主要用于远程设备管理和调试。
AT 命令类型¶
| 命令前缀 | 说明 | 示例 |
|---|---|---|
AT+ |
标准 AT 命令 | AT+VERSION? |
AT+CFG |
配置查询/设置 | AT+CFG=FREQ,439500000 |
AT+RST |
设备重启 | AT+RST |
AT+INFO |
设备信息查询 | AT+INFO |
DATA 格式¶
+--------+--------+
| 0 | 1+ |
| Result | Data |
| 1B | 变长 |
+--------+--------+
| 偏移 | 长度 | 字段名 | 说明 |
|---|---|---|---|
| 0 | 1B | Result | 结果码(0=成功,非0=错误) |
| 1 | 变长 | Data | AT 命令字符串或响应数据 |
AT 命令流程¶
支持的 AT 命令列表¶
| 命令 | 说明 | 响应示例 |
|---|---|---|
AT+VERSION? |
查询固件版本 | OK\nv1.2.3 |
AT+INFO |
查询设备信息 | OK\nModel:ESP32\nSN:xxx |
AT+CFG |
查询所有配置 | OK\nrx_freq:439500000\n... |
AT+CFG=KEY,VALUE |
设置配置项 | OK |
AT+RST |
重启设备 | OK |
AT+SCAN |
扫描可用频率 | OK\n439.500,439.750,... |
设备型号¶
| 值 | 常量名 | 说明 | 认证方式 |
|---|---|---|---|
| 0 | DevModelUnknown | 未知设备 | - |
| 1 | DevModelESP32Radio | ESP32 链路盒子(1W 射频版) | 设备密码 |
| 2 | DevModelESP32NoRadio | ESP32 链路盒子(无射频版) | 设备密码 |
| 100 | DevModelWeChatMini | 微信小程序 | - |
| 101 | DevModelAndroid | Android 客户端 | JWT Token |
| 102 | DevModelIOS | iOS 客户端 | JWT Token |
| 103 | DevModelWindows | Windows 客户端 | JWT Token |
| 104 | DevModelMacOS | macOS 客户端 (预留) | JWT Token |
| 105 | DevModelBrowser | 浏览器客户端 | JWT Token |
| 106 | DevModelLegacyBridge | 互联设备(历史型号,仅兼容显示) | 设备密码 |
| 107 | DevModelLegacyESP32 | ESP32 链路台/手咪(历史型号,仅兼容显示) | 设备密码 |
| 236 | DevModelNSBridge | 南山对讲软件桥接器 | 设备密码 |
| 237 | DevModelTTBridge | 涛涛对讲软件桥接器 | 设备密码 |
| 238 | DevModelHTBridge | 本视对讲(HT)软件桥接器 | 设备密码 |
| 239 | DevModelNRL2Bridge | NRL2 系统软件桥接器 | 设备密码 |
DevModel 分段规则¶
| 范围 | 含义 | 说明 |
|---|---|---|
| 0-99 | 互联产品段 | 本轮已明确启用:1=ESP32 链路盒子(1W 射频版),2=ESP32 链路盒子(无射频版) |
| 100-105 | 幽灵/保留段 | 与 SSID 保留段一致,供 JWT 客户端使用 |
| 106-107 | 历史兼容段 | 仅保留读取/显示,不再作为正式新型号 |
| 108-150 | 当前无效保留段 | 不再兼容 110-113 |
| 151-255 | 扩展/待定段 | 其中 236-239 为当前正式互联网桥软件型号 |
兼容策略:历史
106/107设备记录继续可读可显,但不再作为新建/编辑推荐型号。
SSID 分配规则¶
| SSID 范围 | 用途 | 认证方式 | 说明 |
|---|---|---|---|
| 1-99 | 普通设备 | 设备密码 | 用户自定义,适用于嵌入式设备 |
| 100 | 预留 | - | 原微信小程序 |
| 101-104 | UDP 幽灵设备 | JWT Token | App/PC 客户端,SSID = DevModel |
| 105 | Web 幽灵设备 | JWT Token | WebSocket 客户端 |
| 106-254 | 普通设备扩展 | 设备密码 | 用户自定义,适用于嵌入式设备 |
| 255 | 系统历史保留 | - | 系统保留,用户不可分配 |
普通设备可用范围: 1-99 和 106-254 (共 248 个),使用设备密码认证
幽灵/保留段范围: 100-105,其中 100 为预留;101-105 使用 JWT Token 认证,且 SSID 固定等于 DevModel
Config 包协议¶
TypeConfig (Type=3) 仅用于 UDP 普通设备(SSID 1-99, 106-254)的配置同步,不适用于幽灵设备(App/PC 客户端)。
包类型¶
| DATA[0] | 方向 | 说明 |
|---|---|---|
| 0x01 | 服务端 → 设备 | 查询配置请求 |
| 0x02 | 双向 | 配置下发/上报(支持动态下发部分配置项) |
| 0x03 | 服务端 → 设备 | 时间同步 |
TLV 数据格式¶
当 DATA[0] = 0x02 时,DATA 区域格式:
DATA[0] = 0x02 (配置标识)
DATA[1] = N (配置项数量)
DATA[2...] = TLV 列表
TLV 格式:
┌──────────┬──────────┬─────────────────┐
│ Type (1B)│ Len (1B) │ Value (N bytes) │
└──────────┴──────────┴─────────────────┘
当 DATA[0] = 0x03 时,DATA 区域格式:
DATA[0] = 0x03 (时间同步标识)
DATA[1] = 保留字节 (当前实现固定为 0x00)
DATA[2:10] = Unix 时间戳 (8字节,big-endian int64,毫秒)
配置项 Type 定义¶
| Type | 配置键 | 长度 | 格式 | 说明 |
|---|---|---|---|---|
| 0x01 | rx_freq | 8 | big-endian uint64 | 接收频率 (Hz) |
| 0x02 | tx_freq | 8 | big-endian uint64 | 发射频率 (Hz) |
| 0x03 | rx_ctcss | 4 | big-endian float32 | 接收亚音 (Hz, 0=关闭) |
| 0x04 | tx_ctcss | 4 | big-endian float32 | 发射亚音 (Hz, 0=关闭) |
| 0x05 | sql_level | 1 | uint8 | 静噪等级 (0-8) |
| 0x06 | power_level | 1 | uint8 | 功率等级 (1=低, 3=高;历史值 2 兼容映射为高) |
| 0x07 | tx_bandwidth | 1 | uint8 | 发射带宽 (1=窄带, 2=宽带) |
| 0x08 | rx_tone_mode | 1 | uint8 | 接收亚音类型 (0=OFF, 1=CTCSS, 2=CDCSS_N, 3=CDCSS_I) |
| 0x09 | rx_tone_value | 8 | ASCII | 接收亚音值(如 88.5、023) |
| 0x0A | tx_tone_mode | 1 | uint8 | 发射亚音类型 (0=OFF, 1=CTCSS, 2=CDCSS_N, 3=CDCSS_I) |
| 0x0B | tx_tone_value | 8 | ASCII | 发射亚音值(如 88.5、023) |
| 0x0C | rf_guard_enabled | 1 | uint8 | 射频保护开关 (0=关, 1=开) |
| 0x0D | rf_guard_single_tx_limit_s | 2 | big-endian uint16 | 单次发射上限 (秒, 1-1800) |
| 0x0E | rf_guard_window_s | 2 | big-endian uint16 | 统计窗口 (秒, 5-3600) |
| 0x0F | rf_guard_max_tx_in_window_s | 2 | big-endian uint16 | 窗口内累计发射上限 (秒, 1-window_s) |
| 0x10 | timestamp | 8 | big-endian int64 | Unix 时间戳 (毫秒) |
频率配置约定¶
tx_freq / rx_freq保持手动输入,单位为 Hz。- 当前频率设置卡片按
devmodel + cardid选择实现,本轮默认卡片为sa818-radio-v1。 - 亚音采用双层模型:
OFF:值固定为0CTCSS:标准 38 个模拟亚音值CDCSS_N / CDCSS_I:SA818 数字亚音码表,正/反向极性分开- 服务端保留
rx_ctcss / tx_ctcss作为镜像字段: - 模拟亚音时回填实际 Hz 值
- 数字亚音或解析失败时旧字段回退为
0(无亚音) - 数字亚音完整下发使用
rx_tone_* / tx_tone_*,设备侧可按 SA818 实际参数格式组合为023N / 023I等表达。
示例¶
下发单个配置项(接收频率 439.500MHz):
DATA = [0x02, 0x01, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x32, 0x3C, 0xE0]
│ │ │ │ └─────────────────────────────────────────────┘
│ │ │ │ 439500000 Hz (0x1A323CE0)
│ │ │ └─ 长度 8
│ │ └─ Type=rx_freq
│ └─ 配置项数量 1
└─ 配置下发标识
设备上报全部配置:
DATA = [0x02, 0x0F, TLV1, TLV2, TLV3, TLV4, TLV5, TLV6, TLV7, TLV8, TLV9, TLV10, TLV11, TLV12, TLV13, TLV14, TLV15]
└─ 当前典型全量上报为 15 个配置项
完整数字亚音下发(示例,按 Type 升序):
DATA = [
0x02, 0x0F,
0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x32, 0x3C, 0xE0, // rx_freq = 439500000
0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x19, 0xB8, 0x2A, 0xE0, // tx_freq = 431500000
0x03, 0x04, 0x00, 0x00, 0x00, 0x00, // rx_ctcss = 0
0x04, 0x04, 0x00, 0x00, 0x00, 0x00, // tx_ctcss = 0
0x05, 0x01, 0x05, // sql_level = 5
0x06, 0x01, 0x03, // power_level = 3 (高)
0x07, 0x01, 0x02, // tx_bandwidth = 2 (宽)
0x08, 0x01, 0x02, // rx_tone_mode = CDCSS_N
0x09, 0x08, 0x30, 0x32, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, // rx_tone_value = "023"
0x0A, 0x01, 0x03, // tx_tone_mode = CDCSS_I
0x0B, 0x08, 0x34, 0x33, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, // tx_tone_value = "431"
0x0C, 0x01, 0x01, // rf_guard_enabled = 1
0x0D, 0x02, 0x00, 0x1E, // rf_guard_single_tx_limit_s = 30
0x0E, 0x02, 0x01, 0x2C, // rf_guard_window_s = 300
0x0F, 0x02, 0x00, 0x3C // rf_guard_max_tx_in_window_s = 60
]
说明:服务端编码时遍历 map,TLV 实际顺序可变;设备应按 Type 解析,不应依赖顺序。
同步流程¶
设备上线同步:
设备上线 (认证成功)
│
├─ 数据库有配置记录
│ └─ 服务端发送 Config(0x02 + TLV) 下发配置
│
└─ 数据库无配置记录
└─ 服务端发送 Config(0x01) 查询请求
│
└─ 设备上报 Config(0x02 + TLV)
│
└─ 服务端解析存储到数据库
控制台修改同步:
用户通过控制台修改配置
│
├─ 保存变更到数据库
│
└─ 设备在线?
├─ 是 → 发送 Config(0x02 + 变更项TLV) 动态下发
└─ 否 → 等待设备上线时同步
时间同步:
服务端会在普通 UDP 设备上线完成配置同步后发送一次时间同步包;设备上报配置后,服务端也会再发送一次时间同步包作为 ACK。
DATA = [0x03, 0x00, ts(8 bytes)]
│ │ └───────────── Unix 时间戳(毫秒,大端序)
│ └─ 保留字节(当前实现固定为 0)
└─ 时间同步标识
特殊数据包格式¶
JWT 认证包格式 (TypeJWTAuth)¶
当 Type = 1 (TypeJWTAuth) 时,用于幽灵设备(App/PC 客户端)的 JWT Token 认证。
认证请求¶
客户端发送 JWT Token 进行身份认证:
┌──────────────────────────────────────────────────────────────┐
│ Header (90 字节) │
│ Version: "DraA" │
│ Length: 90 + Token长度 │
│ Username: 空 (可选,用于日志) │
│ DevicePassword: 空 │
│ Type: 1 (TypeJWTAuth) │
│ DevModel: 101/102/103/104 │
│ SSID: 0 (服务器会用 DevModel 作为 SSID) │
│ DMRID: 0 │
│ CallSign: 空 (服务器填充) │
│ Reserved: 4字节保留 │
├─────────────────────────────────────────────────────────—────┤
│ DATA 区域 │
│ JWT Token 字符串 (UTF-8 编码,直到包尾) │
└──────────────────────────────────────────────────────────────┘
认证响应¶
服务器返回认证结果:
┌──────────────────────────────────────────────────────────────┐
│ Header (90 字节) │
│ Version: "DraA" │
│ Length: 90 + DATA长度 │
│ Username: 回显用户名 │
│ DevicePassword: 空 │
│ Type: 1 │
│ DevModel: 回显 │
│ SSID: 服务器分配 (等于 DevModel) │
│ DMRID: 0 │
│ CallSign: 成功时填充用户呼号,失败时为空 │
│ Reserved: 4字节保留 │
├──────────────────────────────────────────────────────────────┤
│ DATA 区域 │
│ [0]: 状态码 │
│ 0 = 认证成功 │
│ 1 = Token 无效或过期 │
│ 2 = 用户不存在 │
│ 3 = 用户已禁用 │
│ 4 = 用户未审核 │
│ 5 = 无效的设备型号 (非 101-104) │
│ 6 = 同平台已有在线幽灵设备 │
│ [1:]: 成功时为空,失败时为错误消息文本 │
└──────────────────────────────────────────────────────────────┘
认证流程¶
注意:JWT 认证包仅用于幽灵设备(DevModel 101-104)。普通设备(如 ESP32、互联设备、各类对讲桥接器)仍使用设备密码认证,通过心跳包完成。
Opus 语音格式 (TypeOpus16K)¶
当 Type = 5 (TypeOpus16K) 时,DATA 区域携带 Opus 编码的语音数据。
编码参数¶
| 参数 | 值 | 说明 |
|---|---|---|
| 采样率 | 16000 Hz | 16kHz 宽带语音 |
| 声道数 | 1 | 单声道 |
| 帧时长 | 60 ms | 每个 Opus 帧 60ms |
| 每帧采样数 | 960 | 16000 × 0.06 |
| 比特率 | 16-32 kbps | VOIP 模式 |
合并帧格式(WebSocket 客户端)¶
为优化弱网性能,WebSocket 客户端采用 2 帧合并发送 策略:
- 每 120ms 发送一个数据包
- 每个数据包包含 2 个 60ms 的 Opus 帧
- 使用帧长度前缀标识每帧边界
DATA 区域格式:
+-------------+-------------+-------------+-------------+
| Frame1 Len | Frame1 Data | Frame2 Len | Frame2 Data |
| 2B | N bytes | 2B | M bytes |
+-------------+-------------+-------------+-------------+
| 字段 | 长度 | 说明 |
|---|---|---|
| Frame1 Len | 2B | 第 1 帧长度(大端序 uint16) |
| Frame1 Data | 变长 | 第 1 帧 Opus 数据(60ms) |
| Frame2 Len | 2B | 第 2 帧长度(大端序 uint16) |
| Frame2 Data | 变长 | 第 2 帧 Opus 数据(60ms) |
性能优化说明¶
| 项目 | 优化前 | 优化后 |
|---|---|---|
| 帧时长 | 20ms | 60ms |
| 发送间隔 | 20ms | 120ms |
| 每秒发包数 | 50 | ~8.3 |
| 单包大小 | 110-170B | 330-570B |
| 头部开销占比 | 50-80% | ~25-35% |
| 额外延迟 | - | +100ms |
注意:120ms 延迟对半双工语音通信完全可接受,且大幅降低弱网丢包影响。
解码兼容性¶
接收端应支持:
- 合并帧格式:按长度前缀解析多个 Opus 帧
- 单帧格式:兼容无长度前缀的单帧数据(用于 UDP 设备直发)
判断逻辑:
- 从 DATA 起始位置开始,按
2B 长度 + 帧数据循环尝试解析 - 若首个长度字段非法(
0、>1000、或越界),回退为单帧格式 - 若已成功解析出至少 1 帧,后续出现非法长度时停止继续解析并保留已解析帧
服务器互联语音格式 (TypeServerVoice)¶
当 Type = 6 (TypeServerVoice) 时,DATA 区域前 68 字节用于存储原始发送方信息:
+--------+--------+--------+--------+
| 0-31 | 32-63 | 64-67 | 68+ |
|OrigUser|OrigCall|OrigIP | Voice |
| 32B | 32B | 4B | 变长 |
+--------+--------+--------+--------+
| 偏移 | 长度 | 字段名 | 说明 |
|---|---|---|---|
| 0 | 32B | OriginalUsername | 原始发送方用户名 |
| 32 | 32B | OriginalCallSign | 原始发送方呼号 |
| 64 | 4B | OriginalIP | 原始服务器 IP |
| 68 | 变长 | VoiceData | 实际语音数据 |
心跳包扩展格式¶
心跳包的 DATA 区域可携带位置信息:
+--------+--------+--------+--------+
| 0-7 | 8-15 | 16-23 | 24+ |
| Lat | Lon | Alt | Extra |
| 8B | 8B | 8B | 变长 |
+--------+--------+--------+--------+
| 偏移 | 长度 | 字段名 | 说明 |
|---|---|---|---|
| 0 | 8B | Latitude | 纬度 (float64 BE) |
| 8 | 8B | Longitude | 经度 (float64 BE) |
| 16 | 8B | Altitude | 海拔高度 (float64 BE) |
扩展约定:
- 固定头部保持不变:MAC 仅允许追加在心跳包
DATA区域,90 字节 header 的字段、偏移、长度均不变。 DATA[24:]为可选扩展区;当前约定可放置设备 MAC 文本,格式为 ASCII 大写冒号分隔,例如AA:BB:CC:DD:EE:FF。- 旧设备如果不携带 MAC,服务端仍按旧规则兼容处理。
- 如果设备携带合法 MAC,服务端会将其作为“同一物理设备快速重连”的辅助判据。
示例:
DATA = [24字节GPS][ASCII "AA:BB:CC:DD:EE:FF"]
设备认证流程¶
认证方式(三轨制)¶
平台支持三种认证方式,根据设备类型和场景自动选择:
| 设备类型 | SSID 范围 | 认证方式 | 说明 |
|---|---|---|---|
| 普通设备 | 1-99, 106-254 | 设备密码 | ESP32、互联设备、对讲桥接器等嵌入式/桥接设备 |
| 幽灵/保留段 | 100-105 | 100 预留;101-105 为 JWT Token |
100 为预留位;101-104 为 App/PC 客户端;105 为 Web |
| 新设备绑定 | 1-99, 106-254 | 动态码绑定 | 新设备首次上线,通过动态码完成账号绑定 |
设计说明:
- 普通设备(如 ESP32 链路台/手咪、互联设备、对讲桥接器)性能有限,不适合进行 JWT 解析,继续使用传统的设备密码认证
- 幽灵设备(App/PC 客户端)性能充足,使用 JWT 认证可以复用现有登录接口,无需预共享密钥
- 动态码绑定:新设备首次上线时,通过 HTTP 接口完成与用户账号的绑定,绑定成功后获取认证信息
- 普通 UDP 在线冲突:同一用户同一 SSID 只允许一台在线,新地址冲突时新设备收到拒绝响应,旧设备保持在线
- 普通 UDP 同 MAC 快速重连:如果新心跳携带的 MAC 与当前在线实例记录的 MAC 一致,则视为同一物理设备短暂断线后的重连,允许直接接管新地址
- 普通 UDP MAC 映射生命周期:设备在线时记录
owner_id + ssid -> mac;设备被彻底判定离线后删除该映射 - 幽灵设备在线冲突:同一用户同一平台只允许一个在线实例,冲突时新设备认证失败,旧设备保持在线
1. 普通设备上线认证(设备密码)¶
2. 幽灵设备认证(JWT Token)¶
幽灵设备(App/PC 客户端)使用 JWT Token 进行认证,详见 JWT 认证包格式。
3. 新设备动态码绑定流程¶
新设备首次上线时,通过动态码绑定流程完成与用户账号的关联。此流程适用于没有密码输入功能的设备(如无键盘/触摸屏的嵌入式硬件),用户通过 Web 端输入设备显示的动态码完成绑定。
绑定流程时序图¶
接口说明¶
| 接口 | 方法 | 认证 | 说明 |
|---|---|---|---|
/api/device/pre-check |
POST | 无 | 设备上电后检查存储的账号密码是否有效 |
/api/device/request-code |
POST | 无 | 请求生成 6 位动态码 |
/api/device/bind |
POST | JWT | 用户通过动态码绑定设备 |
/api/device/submit-config |
POST | JWT | 用户提交设备配置(SSID) |
/api/device/confirm-bind |
POST | 无 | 设备轮询获取绑定状态和配置 |
详细步骤¶
步骤 1:预检查 (pre-check)
设备上电后,使用本地存储的账号密码(如果有)进行预检查:
- 验证成功:返回
status: "authenticated",设备可直接进入 UDP 认证流程 - 验证失败/无存储:返回
status: "need_bind",设备进入动态码绑定流程
步骤 2:请求动态码 (request-code)
设备请求生成动态码:
- 服务器生成 6 位数字动态码
- 动态码有效期 60 秒
- 设备在屏幕/LED 上显示动态码
步骤 3:用户绑定 (bind)
用户在 Web 端输入设备显示的动态码:
- 验证动态码有效性和设备状态
- 将设备 MAC 与用户账号关联
- 返回设备 MAC 和用户呼号
步骤 4:提交配置 (submit-config)
用户为设备配置 SSID:
- SSID 范围:1-99 或 106-254(普通设备可用范围)
- 服务器将用户名、设备密码、SSID、DMRID 写入设备配置
步骤 5:确认绑定 (confirm-bind)
设备轮询此接口获取配置:
- 状态
waiting:等待用户完成绑定 - 状态
ready:绑定完成,返回认证信息
安全机制¶
- 动态码单次使用:绑定成功后动态码立即失效
- 有效期限制:动态码 60 秒过期,绑定状态 10 分钟过期
- 限速保护:各接口均有独立的限速策略
- 账号状态校验:用户必须已审核通过才能绑定设备
4. 认证失败处理¶
- 当前实现中,连续认证失败第 4 次触发首轮封禁:10 秒
- 后续继续失败依次升级:30 秒、60 秒、300 秒(阶梯递增)
封禁 Key:IP + Username
语音转发流程¶
字符编码规则¶
| 字段 | 编码 | 字符集 | 说明 |
|---|---|---|---|
| Username | UTF-8 | 字母、数字、下划线 | 用户名 |
| DevicePassword | ASCII | 大小写字母、数字 | 设备准入密码,6-10 位 |
| CallSign | ASCII | 大写字母、数字 | 业余电台呼号 |
| TextMessage | UTF-8 | 任意 Unicode | 文本消息 |
安全限制¶
UDP 服务端实施以下安全策略,超出限制的数据包将被静默丢弃:
| 限制项 | 阈值 | 说明 |
|---|---|---|
| 最大包体 | 800 字节 | 含 90 字节头部,防止异常大包攻击 |
| 包速率 | 150 包/秒 | 按 IP+Port 维度限速,兼顾 FRP 隧道共享 IP 场景 |
注意:正常语音通信(合并帧 ~8.3 包/秒,单帧 ~16.7 包/秒)远低于限速阈值,不会受到影响。 150 包/秒的阈值设计考虑了:
- FRP/NAT 场景下多设备共享同一出口 IP
- 客户端网络抖动后的"追赶发送"突发场景
- 60-180ms 大包架构下丢包的严重影响
版本历史¶
| 版本 | 日期 | 说明 |
|---|---|---|
| v1.0 | 2026-03 | 初始版本,替代 NRL2 协议 |
| v1.1 | 2026-03 | 移除 G.711 编解码支持,统一使用 Opus 16K 格式 |
| v1.2 | 2026-03 | 简化协议头,移除 Status 和 SeqNum 字段,头部从 93 字节简化为 90 字节 |
| v1.3 | 2026-03 | 优化弱网性能:Opus 帧时长从 20ms 改为 60ms,WebSocket 客户端采用 2 帧合并发送(120ms 间隔) |
| v1.4 | 2026-03 | 新增 JWT 认证方式(Type=1),支持幽灵设备(App/PC 客户端)接入;新增设备型号 104 (macOS)、107 (ESP32);定义 SSID 分配规则 |
| v1.5 | 2026-03 | 性能优化:PTT 会话阈值从 200ms 提升到 600ms(适配 60-180ms 大包架构);限速器从 25 PPS 放宽到 150 PPS;放弃批量发送缓冲,改用直接发送;补全 WS→UDP Ghost 路由;修复时长统计 |
| v1.6 | 2026-03 | 新增 Config 包协议 (Type=3),支持 UDP 普通设备配置同步(TLV 格式),包含查询、下发、上报、时间同步四种操作 |
| v1.7 | 2026-03 | 新增动态码绑定流程,支持无输入能力的普通设备通过 Web 端完成账号绑定 |
| v1.8 | 2026-04 | 新增设备型号 236/237/238/239,分别对应南山、涛涛、本视对讲(HT)、NRL2 系统软件桥接器 |
| v1.9 | 2026-04 | 重构 DevModel 分段与动态绑定 SSID 规则;新增 rx_tone_* / tx_tone_* 数字亚音表达;SQL 收敛为 0-8;功率统一为高/低两档并兼容历史中档 |
| v1.10 | 2026-04 | Config 包新增 rf_guard_* 四项射频保护配置,支持单次发射上限、统计窗口与窗口内累计发射上限 |