跳转至

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 接收频道超过上限