跳转至

03. 设备与配置

1. 设备动态码绑定(公开 + JWT)

POST /api/device/pre-check(公开)

请求:

{
  "mac": "AA:BB:CC:DD:EE:FF",
  "username": "alice",
  "device_password": "Ab12Cd34"
}

返回(已认证):

{ "code": 200, "data": { "status": "authenticated", "call_sign": "BG7XXX" } }

返回(需绑定):

{ "code": 200, "data": { "status": "need_bind", "message": "请使用动态码绑定设备" } }

POST /api/device/request-code(公开)

请求:

{ "mac": "AA:BB:CC:DD:EE:FF" }

返回:

{ "code": 200, "data": { "dynamic_code": "735921", "expires_in": 300 } }

POST /api/device/confirm-bind(公开)

请求:

{ "mac": "AA:BB:CC:DD:EE:FF" }

返回(等待中):

{ "code": 200, "data": { "status": "waiting", "message": "等待用户完成绑定" } }

返回(已就绪):

{
  "code": 200,
  "data": {
    "status": "ready",
    "username": "alice",
    "device_password": "Ab12Cd34",
    "ssid": 11,
    "dmr_id": 4601234
  }
}

POST /api/device/bind(JWT)

请求:

{ "dynamic_code": "735921" }

返回:

{
  "code": 200,
  "data": {
    "device_mac": "AA:BB:CC:DD:EE:FF",
    "call_sign": "BG7XXX",
    "message": "绑定成功,请配置设备参数",
    "available_ssids": [1, 2, 3, 4, 5, 106],
    "recommended_ssid": 1
  }
}

POST /api/device/submit-config(JWT)

请求:

{ "device_mac": "AA:BB:CC:DD:EE:FF", "ssid": 11 }

返回:

{
  "code": 200,
  "data": {
    "message": "配置已保存",
    "udp_auth_info": {
      "username": "alice",
      "device_password": "Ab12Cd34"
    },
    "dmr_id": 4601234
  }
}

说明:

  • ssid 仅允许普通设备范围:1-99、106-254。
  • 100-105 为幽灵设备保留段,255 为系统历史特殊保留值,均不可用于动态绑定。
  • available_ssids / recommended_ssid 已自动排除当前用户已有设备与待绑定配置,并按从小到大推荐。
  • 普通设备运行时唯一键为 owner_id + ssid;同一用户同一 SSID 同时只允许一台在线设备。
  • 设备端动态绑定接口里的 mac 仅用于“设备绑定 / 设备确认配置”流程;普通 UDP 设备上线后的快速重连判据由心跳包 DATA 扩展区中的可选 MAC 提供。

1.1 普通 UDP 设备快速重连补充说明

  • 心跳包固定 header 不变;MAC 不会写入 header,只会可选追加在心跳 DATA 的 GPS(24B) 之后。
  • 服务端在普通 UDP 设备在线时,会记录 owner_id + ssid -> mac 的运行时映射。
  • 如果设备短暂断线后重新建立了新的 UDP 源端口,但心跳包携带的 MAC 与当前在线实例一致,服务端会视为同一物理设备重连,允许直接接管新地址。
  • 如果 MAC 不同,则仍按“同一用户同一 SSID 已有在线设备”拒绝新实例。
  • 当设备被彻底判定离线后,这条运行时 MAC 映射会被删除。

1.2 设备接入点发现

POST /api/device/access-points/token(设备密码)

设备无法使用 Web 登录态时,可用用户名和设备密码换取最长 5 分钟的发现凭证:

{
  "username": "alice",
  "device_password": "Ab12Cd34"
}
{
  "code": 200,
  "data": {
    "access_token": "eyJ...",
    "token_type": "Bearer",
    "expires_at": "2026-07-23T15:00:00+08:00",
    "expires_in": 299
  }
}

响应禁止缓存。认证失败统一返回 401,不会指出用户名、账号状态或密码中的哪一项 不正确;接口同时受来源 IP 和“来源 IP + 用户名”限速。

GET /api/access-points(JWT / 发现凭证)

Authorization 可使用普通 Web access token,或上一个接口签发的短期发现凭证。

{
  "code": 200,
  "data": {
    "items": [
      {
        "id": "ap-fuzhou",
        "display_name": "福州电信入口",
        "udp_host": "radio.example.com",
        "udp_port": 60050,
        "region": "福建省 福州市",
        "priority": 100,
        "healthy_sample_at": "2026-07-23T14:59:58+08:00"
      }
    ],
    "server_time": "2026-07-23T15:00:00+08:00",
    "cache_max_age": 5
  }
}

客户端根据列表自行执行 ICMP 测速并选择入口。当前 dev 分支只返回管理员配置的 中心入口,不返回内部 NodeID、控制地址、节点凭据和运维指标。多收发器及其他节点入口 留待后续扩展;中心入口由管理员在“站点配置 → 接入点”中设置,未启用时不会使用 APRS 地址自动生成入口。

2. 用户设备密码(JWT)

GET /api/user/device-password

PUT /api/user/device-password

请求:

{ "new_password": "Ab12Cd34" }

POST /api/user/device-password/regenerate

返回示例:

{ "code": 200, "message": "设备密码已重新生成", "data": { "device_password": "Xy98Lm12" } }

GET /api/user/device-default-group

读取“后续首次登记的普通设备”默认群组。未设置时返回:

{ "code": 200, "data": { "group_id": null } }

PUT /api/user/device-default-group

设置默认群组:

{ "group_id": 1001 }

清空默认群组可提交 null 或 0:

{ "group_id": null }

说明:

  • 默认值仅在普通设备第一次登记入库时继承一次;已有设备重连不会被覆盖。
  • 未设置默认群组时,新设备以 group_id=0 登记,保持在线和可管理,但不参与语音、文本或群组互联转发。
  • 设备管理中的显式切组只覆盖该设备,不修改用户默认群组。
  • 只能选择启用的公开/私有实体群组;普通用户选择私有群组时必须是群主或已验证成员。

3. 设备管理(JWT + Approved)

说明:设备收发控制仅设备级生效。设备所有者或管理员通过 PUT /api/devices/:id 更新;当前群主或管理员也可以通过群组设备收发控制接口调整当前组内设备。 状态在设备切换群组后继续生效。

GET /api/devices

参数:page、limit、keyword、callsign、group_id、isonline

其中 isonline=true 只返回当前在线设备,isonline=false 只返回当前离线设备; 省略该参数返回全部设备,其他值会被拒绝并返回 400。

  • keyword:按设备名称或设备所有者呼号模糊搜索,后台设备页使用该参数。
  • callsign:按设备所有者呼号精确搜索,保留用于兼容现有客户端。

返回项补充:

  • last_online_ip:设备最近一次上线客户端 IP
  • last_online_ip_location:根据 IP 计算的归属地(实时计算)

GET /api/devices/list(兼容旧路径)

GET /api/device/get?id=123

GET /api/device/qth?id=123(兼容)

GET /api/device/qths

返回:设备位置列表。

PUT /api/devices/:id

请求示例:

{
  "name": "My ESP32",
  "status": 1,
  "priority": 100,
  "note": "备用机",
  "disable_send": false,
  "disable_recv": false
}

说明:

  • dev_model 由设备客户端通过 UDP 协议上报并由服务端校验后写入。
  • 该接口不支持设置/修改 dev_model(传入也会被忽略)。
  • 群组切换必须使用下方专用接口,不能通过通用更新接口绕过群组权限校验。

DELETE /api/devices/:id

POST /api/device/changegroup

PUT /api/devices/:id/group

兼容接口在 body 传 device_id;REST 路径会校验路径 ID 与 body ID 一致:

{
  "device_id": 123,
  "group_id": 1002,
  "password": "private_group_pwd"
}

说明:

  • 私有群组且用户未验证时,需要 password。
  • group_id=0 表示将设备设为未分组;设备仍在线可管理,但停止参与实际转发。

4. 设备配置同步(UDP 普通设备)

GET /api/devices/:id/config

PUT /api/devices/:id/config

请求示例:

{
  "rx_freq": "439500000",
  "tx_freq": "431500000",
  "rx_ctcss": "88.5",
  "tx_ctcss": "88.5",
  "rx_tone_mode": "ctcss",
  "rx_tone_value": "88.5",
  "tx_tone_mode": "ctcss",
  "tx_tone_value": "88.5",
  "sql_level": "4",
  "power_level": "3",
  "tx_bandwidth": "1",
  "rf_guard_enabled": "1",
  "rf_guard_single_tx_limit_s": "30",
  "rf_guard_window_s": "300",
  "rf_guard_max_tx_in_window_s": "60"
}

说明:

  • power_level 当前按设备能力归一化为 1(低)或 3(高),历史值 2 会兼容映射为 3。
  • rx_tone_mode / tx_tone_mode 支持 off、ctcss、cdcss_n、cdcss_i;对应的 *_tone_value 为 CTCSS 频率或 3 位 DCS 码值。
  • rf_guard_enabled 支持 0/1;为空时按默认开启处理。
  • rf_guard_single_tx_limit_s 范围 1-1800,默认 30。
  • rf_guard_window_s 范围 5-3600,默认 300。
  • rf_guard_max_tx_in_window_s 范围 1-rf_guard_window_s,默认 60,超过窗口值时会自动收敛到窗口上限。

POST /api/devices/:id/config/sync

返回示例:

{ "code": 200, "message": "配置已发送到设备" }

离线时:

{ "code": 200, "message": "设备离线,配置将在设备上线时自动同步" }

5. 管理员设备配置(Admin)

  • GET /api/admin/devices/:id/config
  • PUT /api/admin/devices/:id/config
  • POST /api/admin/devices/:id/config/sync

与前台配置接口字段一致,但无设备归属限制。

6. 固件管理

POST /api/firmware(Admin)

multipart/form-data 字段:

  • file:固件二进制文件(最大 16MB)
  • dev_model:设备型号(白名单:1=ESP32 1W射频版, 2=ESP32 无射频版)
  • version:版本号,semver 格式如 1.0.0 或 1.0.0-beta.1
  • changelog:更新日志(可选)

返回示例:

{
  "code": 200,
  "message": "固件上传成功",
  "data": {
    "id": 1,
    "dev_model": 1,
    "version": "1.2.0",
    "changelog": "修复WiFi重连问题",
    "file_name": "firmware.bin",
    "minio_path": "firmware/1/1.2.0/<sha256>/firmware.bin",
    "file_size": 1048576,
    "file_hash": "sha256hex...",
    "is_latest": true,
    "created_by": 1,
    "create_time": "2026-04-20T10:00:00Z"
  }
}

说明:

  • 同一 dev_model + version 组合不可重复,否则返回 409。
  • 上传新版本后,该型号旧版本的 is_latest 自动置为 false。

GET /api/firmware(Admin)

参数:dev_model(可选,0=全部)、page、page_size

每条记录额外返回一小时有效的预签名 download_url;bucket 和对象不需要公共读。

DELETE /api/firmware/:id(Admin)

删除固件记录及 MinIO 文件。若删除的是最新版本,自动将次新版本提升为 is_latest。

GET /api/public/firmware/latest(Public)

参数:dev_model(必填)

返回示例:

{
  "code": 200,
  "message": "成功",
  "data": {
    "id": 1,
    "dev_model": 1,
    "version": "1.2.0",
    "changelog": "修复WiFi重连问题",
    "file_name": "firmware.bin",
    "file_size": 1048576,
    "file_hash": "sha256hex...",
    "download_url": "https://...presigned...",
    "create_time": "2026-04-20T10:00:00Z"
  }
}

说明:

  • download_url 是短期 S3 预签名 GET URL;配置 DownloadURLPrefix 时只替换访问前缀,签名查询参数仍会保留。
  • file_hash 为 SHA-256,设备端可用于校验下载完整性。
  • 该型号无固件时返回 404。