05. 无线电与实时通信¶
1. 会话与路由模型¶
在线收发区分三层身份:账号、稳定的客户端安装实例 client_instance_id,以及服务端签发的临时 session_id。幽灵客户端必须携带有效 UUID 实例 ID,并同时声明现代多收与来源频道能力;旧 raw-JWT 或无实例 ID 客户端不再支持。
- 标准 UDP 实体设备仍按
owner_id + ssid保持单端在线,冲突规则不变。 - 同一账号可以有多个幽灵 Session,Web、Android、iOS、Windows 和 macOS 之间互不覆盖。
- 每个 Session 只有一个发送频道
tx_group_id。 - 每个 Session 可以有多个接收频道
rx_group_ids,服务端会自动保证其中包含tx_group_id。 - 接收订阅和发送频道按
user_id + dev_model + client_instance_id持久化。 multi_receive_v1和source_group_v1是必需能力;缺少任一项时认证失败。
默认上限为同一账号 8 个幽灵 Session、每个 Session 16 个接收频道。群组必须存在、启用、非虚拟,并通过当前用户的群组查看权限校验。
2. 在线收发 API(JWT + Approved)¶
GET /api/radio/sessions¶
返回当前账号的所有幽灵 Session,不包含其他账号或标准实体设备。
{
"code": 200,
"data": [
{
"session_id": "7fbbbd37-9207-4df2-b79d-e400e0a09fd0",
"client_instance_id": "41a065d7-f9e1-4785-bbcb-22c3ca8784ad",
"dev_model": 105,
"ssid": 105,
"transport": "websocket",
"protocol_version": 1,
"capabilities": ["multi_receive_v1", "source_group_v1"],
"online_since": "2026-08-05T12:00:00Z",
"last_activity": "2026-08-05T12:01:00Z",
"tx_group_id": 1001,
"rx_group_ids": [1001, 1002],
"disable_send": false,
"disable_recv": false
}
]
}
PUT /api/radio/sessions/:session_id/routing¶
原子更新指定 Session 的唯一发送频道和接收频道集合。
{
"tx_group_id": 1002,
"rx_group_ids": [1001, 1002, 1003]
}
服务端先规范化和校验全部群组,再持久化实例偏好,最后投影到 WS、UDP 和节点互联运行时。任一环节失败时不会留下部分路由;运行时投影失败时会回滚已持久化偏好。PTT 期间如果 tx_group_id 发生变化,服务端返回 409 ptt_active;最后语音帧后约 900ms 自动过期。成功响应的 data 是规范化后的完整 Session。
DELETE /api/radio/sessions/:session_id¶
只断开当前账号拥有的指定 Session,不影响同账号其他手机、浏览器标签页或平台。不存在或不属于当前账号时返回 404 session_not_found。
管理员使用 GET /api/admin/radio/sessions 查看全局脱敏摘要,使用
DELETE /api/admin/radio/sessions/:session_id 精确断开 Session。后台页面位于
/admin/radio-sessions;完整实例 ID、UDP tag 和网络端点不会进入列表响应。
状态接口¶
GET /api/radio/config:返回 Web 默认配置。GET /api/radio/status:保留旧单 Session 结构;新客户端应使用/api/radio/sessions。
旧 PUT /api/radio/ssid、PUT /api/radio/group 与 GET /api/radio/conflict 均已移除。Web SSID 固定为 105,切换发送频道或收听集合必须使用 Session routing API。
群组运行状态¶
GET /api/radio/groups/stats:返回当前用户可访问群组的实时统计。GET /api/radio/groups/:id/devices:返回群组中的 UDP、WS 和边缘设备,包含is_ghost、disable_send、disable_recv和last_activity。
3. WebSocket /ws¶
浏览器先通过 POST /api/auth/ws-token/sync 写入 HttpOnly ws_token,再建立连接。浏览器在 URL query 中提交:
/ws?client_instance_id=<uuid>&protocol_version=1&capabilities=multi_receive_v1,source_group_v1
浏览器 WebSocket API 不能设置自定义握手 Header,因此 Web 实现使用 query;非浏览器客户端也可以使用 X-DraARL-Client-Instance-ID、X-DraARL-Protocol-Version 和 X-DraARL-Capabilities。
升级成功后,客户端首先收到文本控制事件:
{
"type": "auth_success",
"data": {
"session_id": "7fbbbd37-9207-4df2-b79d-e400e0a09fd0",
"client_instance_id": "41a065d7-f9e1-4785-bbcb-22c3ca8784ad",
"protocol_version": 1,
"tx_group_id": 1001,
"rx_group_ids": [1001, 1002]
}
}
路由由 API 或权限变更更新后会收到 routing_updated。媒体和心跳继续使用 90 字节 DraARLv1 二进制帧;下行 Type 4/5 的 Reserved 为大端 source_group_id。Web 按 source_group_id + sender 隔离 Opus 解码流,并通过多频道混音器同时播放;每个频道可以单独调节音量。
完整协议见 WebSocket 协议详解。
4. UDP 幽灵设备¶
UDP 幽灵设备使用版本化 Type 1 JSON 认证,成功后得到 session_id 和 32 位 session_tag。后续上行心跳、文本和语音必须在 Reserved 中携带 session_tag,同时保持 Username、SSID、DevModel 和已认证 UDP 端点一致。服务端始终使用 Session 中的 tx_group_id 路由,不信任媒体包自行声明的目标频道。
下行 Type 4/5 的 Reserved 表示真实 source_group_id。UDP 地址改变时必须重新认证,不能只凭 tag 接管 Session。
标准 UDP 实体设备不解析以上 Session 语义,仍使用设备密码、单端身份和原有快速重连规则。详细字节格式见 Protocol.md。
5. 路由错误¶
| HTTP | error |
说明 |
|---|---|---|
| 400 | invalid_routing |
发送频道或接收集合无效 |
| 403 | group_forbidden |
至少一个群组无访问权限 |
| 404 | session_not_found |
Session 不存在或不属于当前账号 |
| 404 | group_not_found |
至少一个群组不存在 |
| 409 | ptt_active |
当前 Session 的半双工租约尚未过期,暂不能切换发送频道 |
| 422 | subscription_limit |
接收频道超过上限 |