# 智慧停车边缘端自动与手工接入对接指南 > 版本:v1.0 > > 适用对象:车牌识别相机、道闸控制器、边缘网关固件和设备代理开发人员 > > 配套文档:[设备端对接文档.md](./设备端对接文档.md)、[边缘设备自动接入实施方案.md](./边缘设备自动接入实施方案.md) ## 1. 对接目标 边缘端在首次安装、恢复出厂或更换管理系统地址后,需要支持以下两种接入方式: 1. **UDP 自动发现**:设备不知道管理系统地址时,在局域网监听发现请求,返回自己的身份和 HTTPS 配置地址。管理系统随后通过 HTTPS 完成身份确认和配置下发。 2. **手工 IP 接入**:UDP 广播不可用或设备不支持发现协议时,管理员在管理系统页面输入设备 IP 和 HTTPS 端口。设备仍通过相同的 HTTPS 身份和配置接口完成接入,不得因为地址是手工输入而跳过身份确认。 两种方式完成配置后,边缘端都必须主动连接 MQTT Broker,并发布 retained 的 `gate.state.v1`。管理系统只有收到该状态消息后,才把设备标记为在线。 发现和配置通道只用于接入,不承载车牌、图片、开闸指令等正常业务数据。设备上线、状态、识别事件、开闸指令和回执使用 MQTT;抓拍图片由边缘端通过 HTTPS 主动上传到管理系统,MQTT 事件只引用上传成功返回的 `image_id`。 ## 2. 边缘端必须实现的能力 ### 2.1 固定身份 设备出厂时必须生成并永久保存以下信息: | 字段 | 要求 | | --- | --- | | `device_id` | 永久硬件 ID,不能由管理员输入、修改或因 IP 变化而改变;建议使用安全芯片序列号或出厂序列号 | | `device_code` | 初始可以为空或使用出厂临时编码;管理员绑定业务通道后会下发正式编码 | | `device_type` | 例如 `lpr-gate`、`gate-controller`、`camera` | | `device_model` | 固件型号 | | `firmware_version` | 当前固件版本 | | 设备签名密钥 | 每台设备独立的 Ed25519 私钥,私钥不得通过 UDP、日志或 MQTT 暴露 | `device_id` 是设备身份主键。设备发现响应里的 `device_id`、HTTPS 身份接口返回的 `device_id` 和 MQTT 状态对应的设备编码必须能够关联;发现阶段和 HTTPS 阶段出现不同永久 ID 时必须拒绝继续配置。 ### 2.2 HTTPS 配置服务 设备必须在配置端口监听 HTTPS,默认端口为 `8443`,实际端口可由固件配置。证书可以是出厂自签名证书,但证书必须稳定保存,不能每次启动随机生成。 必须提供以下接口: | 方法 | 路径 | 用途 | | --- | --- | --- | | `GET` | `/api/v1/identity` | 返回设备公开身份、证书/公钥指纹和是否需要配对码 | | `POST` | `/api/v1/pair` | 校验一次性配对码,建立本次管理系统信任关系 | | `POST` | `/api/v1/provision` | 原子保存 MQTT 和停车场通道配置 | 当前管理系统会先读取 `/api/v1/identity`,再由管理员核对证书指纹并调用 `/api/v1/pair`,最后调用 `/api/v1/provision`。接口必须只返回必要信息,不返回私钥、MQTT 长期密码或其他设备密钥。 ### 2.3 MQTT 客户端 设备必须支持 MQTT 3.1.1、QoS 1、Keep Alive 30 秒、LWT 和 retained 消息。设备是 MQTT 客户端,管理系统是 Broker;设备不得等待管理系统反向连接设备。 ## 3. UDP 自动发现对接 ### 3.1 监听规则 - 传输协议:IPv4 UDP。 - 监听地址:`0.0.0.0:31001`。 - 单个 UDP 包最大处理长度:4KB;超过长度直接丢弃。 - 设备只响应合法、未过期的 `discovery.request.v1`。 - 响应必须单播回请求报文的源 IP 和源端口,不要再次广播。 - 不要在 UDP 请求或响应中携带 MQTT 密码、私钥、配对码或停车场业务数据。 - UDP 监听应在设备尚未配置 MQTT 时工作;设备已经在线时是否响应由设备策略决定,但不能自动覆盖已生效配置。 管理系统会在本机 IPv4 网卡的定向广播地址上发送 3 次请求,间隔 500ms,收集窗口约 3 秒。设备不能假设管理系统使用固定源端口。 ### 3.2 请求格式 `discovery.request.v1` ```json { "schema": "discovery.request.v1", "request_id": "7ba3a5ea-3ca4-45fe-b59d-1dc47b6ab5a1", "issued_at": 1760000000, "expires_at": 1760000030, "system_id": "smart-parking-HOST01", "nonce": "base64-32-random-bytes" } ``` 设备处理要求: 1. `schema` 必须等于 `discovery.request.v1`。 2. `request_id` 必须作为本次请求关联 ID 原样带回。 3. `expires_at` 必须大于当前时间;建议允许不超过 30 秒的时钟误差。 4. `nonce` 必须原样带回,不能生成新的 nonce 替换它。 5. 同一 `request_id + nonce` 的重复请求可以重复响应,但不能触发配置或重启 MQTT。 6. 非法 JSON、过期请求和包长度超过 4KB 直接丢弃,不返回错误广播。 ### 3.3 响应格式 `discovery.response.v1` ```json { "schema": "discovery.response.v1", "request_id": "7ba5a3ea-3ca4-45fe-b59d-1dc47b6ab5a1", "device_id": "SN-20260819-0001", "device_code": "EDGE-A01", "device_type": "lpr-gate", "device_model": "LPR-GW-200", "firmware_version": "2.1.0", "ip": "192.168.10.26", "config_url": "https://192.168.10.26:8443/api/v1/provision", "public_key_fingerprint": "SHA256:4A8F...", "signing_public_key": "base64-ed25519-public-key", "nonce": "base64-32-random-bytes", "signature": "base64-ed25519-signature" } ``` 字段要求: | 字段 | 要求 | | --- | --- | | `ip` | 必须是设备当前 IPv4 地址,并且等于设备发送 UDP 响应时的本地地址;不要填管理系统地址或旧地址 | | `config_url` | 必须使用 `https`,主机必须等于 `ip`;端口必须是设备实际 HTTPS 配置端口 | | `public_key_fingerprint` | `signing_public_key` 原始 32 字节 Ed25519 公钥的 SHA-256;支持 `SHA256:` 前缀,推荐十六进制大写或小写均可 | | `signing_public_key` | Base64 编码的 Ed25519 公钥;每台设备固定,不因每次响应变化 | | `signature` | Base64 编码的 Ed25519 签名 | 当前管理系统验证的签名原文是以下 6 个字段按顺序以单个换行符连接,不能添加空格、JSON 格式或末尾换行: ```text schema request_id device_id ip config_url nonce ``` 例如: ```text discovery.response.v1 7ba5a3ea-3ca4-45fe-b59d-1dc47b6ab5a1 SN-20260819-0001 192.168.10.26 https://192.168.10.26:8443/api/v1/provision base64-32-random-bytes ``` 管理系统会校验 schema、request_id、nonce、UDP 源 IP、`config_url` 主机、签名和公钥指纹。设备不得把发现响应当作已信任或已配置;自动发现后仍必须通过 HTTPS 身份读取和管理员确认。 ## 4. HTTPS 身份和配对对接 ### 4.1 身份接口 管理系统请求: ```http GET /api/v1/identity HTTP/1.1 Host: : Accept: application/json ``` 成功响应 HTTP 200: ```json { "device_id": "SN-20260819-0001", "device_code": "EDGE-A01", "device_type": "lpr-gate", "device_model": "LPR-GW-200", "firmware_version": "2.1.0", "certificate_fingerprint": "SHA256:ABCDEF...", "public_key_fingerprint": "SHA256:4A8F...", "pairing_required": true } ``` 字段处理: - `device_id` 必填且永久不变;为空时管理系统会拒绝设备。 - `certificate_fingerprint` 应为当前 HTTPS 服务器证书 DER 原文的 SHA-256。设备返回值必须与实际 TLS 证书一致。 - `public_key_fingerprint` 是设备签名公钥指纹,可与 UDP 响应中的值一致。 - `pairing_required=true` 时,管理系统会要求管理员输入一次性配对码。 - 身份接口不得因为未配置 MQTT 而失败;身份接口是首次接入的前置接口。 ### 4.2 证书和 TLS 要求 设备必须: 1. 只接受 HTTPS,不提供把配置数据明文放在 HTTP 或 UDP 中的兼容路径。 2. 使用 TLS 1.2 或更高版本。 3. 在整个设备生命周期内保持证书稳定;证书更换必须有明确的重新配对流程。 4. 正确返回证书链和服务器名称/IP 对应信息。出厂自签名证书可以使用,但必须让管理员能够从设备标签、二维码或受控维护界面取得指纹。 5. 对配置接口进行请求大小限制、超时限制和并发限制,防止配置服务被当作通用代理。 管理系统首次读取身份时会获取 TLS 对端证书指纹;管理员必须把页面指纹与设备标签或二维码核对。管理系统不会因为 IP 可达就自动信任设备。 ### 4.3 配对接口 当身份响应 `pairing_required=true` 时,管理系统请求: ```http POST /api/v1/pair HTTP/1.1 Content-Type: application/json {"pairing_code":"ONE-TIME-CODE"} ``` 成功返回任意 2xx,建议返回: ```json { "result": "paired", "device_id": "SN-20260819-0001", "expires_at": 1760003600 } ``` 设备实现要求: - 配对码必须是一次性或短时有效凭据,比较时使用常量时间比较。 - 错误次数必须限流;连续失败不能擦除既有 MQTT 配置。 - 成功后将本次管理系统或证书指纹标记为已配对,配对码不能再次使用。 - 错误、过期、重复使用统一返回 4xx,并在设备安全日志中记录,不把配对码明文写入日志。 - `pairing_required=false` 时,设备仍必须完成 HTTPS 证书指纹核对;不能仅凭此字段跳过管理员确认。 ## 5. HTTPS 配置下发对接 ### 5.1 请求格式 管理系统请求: ```http POST /api/v1/provision HTTP/1.1 Content-Type: application/json ``` 当前管理系统实际发送的 JSON 字段如下: ```json { "schema": "provision.request.v1", "request_id": "f0f2f011-cc14-4965-8a61-9a65b3ec2f0b", "device_id": "SN-20260819-0001", "device_code": "GATE-A01", "mqtt": { "host": "192.168.10.10", "port": 1883, "tls": false, "client_id": "GATE-A01" }, "image_upload": { "url": "http://192.168.10.10:8888/device-images/upload", "max_bytes": 8388608 }, "route": { "parking_lot_id": 2, "booth_id": 5, "channel_id": 8, "direction": "in" } } ``` 字段要求: | 字段 | 设备端处理 | | --- | --- | | `schema` | 必须为 `provision.request.v1` | | `request_id` | 保存最近处理结果;相同请求重试应幂等返回,不重复生成凭据或反复重启 | | `device_id` | 必须与本机永久 ID 完全一致,不一致返回 409 或 403 | | `device_code` | 成功配置后作为 MQTT ClientID、消息 `device_code` 和业务设备编码;不能为空 | | `mqtt.host` | 管理系统下发给设备的可达地址;不能把它替换为设备自身 IP | | `mqtt.port` | 1-65535;按 `mqtt.tls` 选择 TLS 或普通 MQTT | | `mqtt.tls` | 为 true 时必须校验 Broker 证书;不能降级为明文连接 | | `mqtt.client_id` | 当前应与 `device_code` 相同,必须全局唯一 | | `image_upload.url` | 边缘端主动上传抓拍图片的管理系统内网地址 | | `image_upload.max_bytes` | 单张图片最大字节数;当前为 8MB | | `route.*` | 保存停车场、岗亭、通道数字 ID 和方向;Topic 使用这些数字 ID,不是编码字符串 | 当前版本管理系统没有在 JSON 中发送 MQTT 用户名、密码、`topic_prefix`、`config_version`、`nonce` 或请求签名字段。设备端必须忽略未知字段,但不能自行要求当前版本不存在的必填字段,否则会导致接入失败。生产环境启用设备级账号、双向 TLS 或配置签名时,应通过协议版本升级增加这些字段,并保持旧版本的明确兼容策略。 ### 5.2 原子保存和响应 设备收到合法配置后必须按以下顺序处理: 1. 校验 HTTPS 会话已通过证书指纹/配对信任,校验 `device_id`、`device_code`、端口、Topic 坐标和方向。 2. 校验 `mqtt.host` 是设备实际可达的单播地址;禁止 `127.0.0.1`、`localhost`、`0.0.0.0`、广播和组播地址。 3. 将新配置写入临时文件或事务存储,完成校验和后原子替换正式配置。 4. 配置写入失败时保留旧配置,并返回 4xx/5xx;不能只写入 Broker 地址而丢失通道坐标。 5. 成功保存后返回 HTTP 200(或 202): ```json { "result": "accepted", "request_id": "f0f2f011-cc14-4965-8a61-9a65b3ec2f0b", "message": "configuration saved; mqtt reconnecting" } ``` `accepted` 只表示设备已经保存配置,不表示已经在线。返回响应后设备应异步重启 MQTT 客户端,不要让 HTTPS 请求等待 Broker 连接完成。 建议错误响应: | HTTP | 场景 | 设备行为 | | --- | --- | --- | | 400 | JSON、字段、端口或 Topic 坐标无效 | 不改配置,返回字段错误 | | 401/403 | 未配对或权限不足 | 不改配置,记录安全事件 | | 409 | `device_id`、`request_id` 或配置版本冲突 | 返回现有结果或冲突原因,不覆盖配置 | | 429 | 配置/配对请求过于频繁 | 延迟重试,不修改配置 | | 500 | 本地存储失败 | 保留旧配置,返回失败 | ## 6. 两种接入方式的设备行为差异 ### 6.1 自动发现 设备需要额外实现 UDP 监听和签名响应。完整过程: ```text 启动设备 └─ 监听 UDP :31001、启动 HTTPS 身份服务 管理系统广播 discovery.request.v1 └─ 设备校验 request_id/nonce/过期时间 └─ 设备单播 discovery.response.v1 管理系统通过 HTTPS GET /identity └─ 管理员核对证书指纹和配对码 管理系统 HTTPS POST /provision └─ 设备原子保存 MQTT 配置并返回 accepted 设备连接 MQTT └─ 清除旧 LWT retained └─ 发布 retained gate.state.v1 ``` UDP 响应不等于配置授权。设备不能收到发现请求后自动修改 `device_code`、业务通道或 MQTT 配置,所有修改必须来自已配对的 HTTPS 配置请求。 ### 6.2 手工 IP 接入 设备不需要收到 UDP 请求,也不需要知道管理系统 IP;只要管理员能够从管理系统访问设备的 HTTPS IP 和端口即可: ```text 设备启动 HTTPS :8443 管理员在系统输入设备 IP/端口 └─ 管理系统 GET /api/v1/identity └─ 管理员核对证书指纹,输入配对码 └─ 管理系统 POST /api/v1/pair(需要时) └─ 管理系统 POST /api/v1/provision 设备连接 MQTT └─ 清除旧 LWT retained └─ 发布 retained gate.state.v1 ``` 手工 IP 常用于跨 VLAN、UDP 广播被禁、无线隔离、VPN 或设备不支持发现协议的网络。设备端无需为这些场景降低 TLS、永久 ID 或配对校验要求。 ## 7. MQTT 配置完成后的行为 ### 7.1 连接和 Topic 设备从 `provision` 中读取: ```text Broker: mqtt[ s ]://: ClientID: ,当前必须等于 device_code Keep Alive: 30 秒 QoS: 1 ``` Topic 模板: ```text parking/lot/{parking_lot_id}/booth/{booth_id}/channel/{channel_id}/gate/state parking/lot/{parking_lot_id}/booth/{booth_id}/channel/{channel_id}/gate/lwt parking/lot/{parking_lot_id}/booth/{booth_id}/channel/{channel_id}/gate/cmd parking/lot/{parking_lot_id}/booth/{booth_id}/channel/{channel_id}/gate/cmd/ack parking/lot/{parking_lot_id}/booth/{booth_id}/channel/{channel_id}/camera/event ``` 设备只订阅自己的 `gate/cmd`。不要订阅 `#` 或其他停车场的 Topic。 ### 7.2 上线顺序 连接 MQTT 后必须: 1. CONNECT 时设置 LWT Topic 为自己的 `gate/lwt`,Payload 为: ```json {"schema":"gate.lwt.v1","device_code":"GATE-A01"} ``` LWT 使用 QoS 1、retained=true。 2. CONNACK 成功后订阅自己的 `gate/cmd`。 3. 向自己的 `gate/lwt` 发布**空 retained payload**,清除旧的离线遗嘱。 4. 立即发布 retained 的 `gate.state.v1`: ```json { "schema":"gate.state.v1", "device_code":"GATE-A01", "state":"closed", "ts":1760000000 } ``` 5. 后续状态变化继续发布 `gate.state.v1`;断开连接时由 Broker 发布 LWT。 管理系统等待最多约 30 秒接收 `gate.state.v1`。只有收到状态消息,设备才会从“配置已保存”变成“在线”。 ### 7.3 道闸指令和回执 收到 `gate.cmd.v1` 后,设备必须校验 `device_code`、`action` 和 `valid_time`,执行动作后在 5 秒内向 `gate/cmd/ack` 发布: ```json { "schema":"gate.ack.v1", "cmd_id":"3f2a1b9c-4d5e-4f6a-8b9c-0d1e2f3a4b5c", "device_code":"GATE-A01", "result":"success", "error":"" } ``` 失败时 `result` 为 `failed`,`error` 使用可读但不包含密钥的诊断信息。相同 `cmd_id` 的重复命令应幂等处理,不能因为 MQTT QoS 1 重投而连续开闸。 ### 7.4 抓拍图片 HTTP 主动上传 每次识别后,边缘端必须先通过 `image_upload.url` 上传图片,成功获得 `image_id` 后再发布 MQTT 识别事件。不得在 MQTT 中发送 Base64 图片或图片二进制数据。 ```http POST /device-images/upload HTTP/1.1 Content-Type: multipart/form-data ``` 表单字段:`device_code`(当前 MQTT 设备编码)、`image_id`(每张图片全局唯一 UUID)、`event_id`(同一次识别 UUID)、`direction`(`in` / `out`)、`image`(JPEG/PNG/WebP 图片,最大 8MB)。成功响应的 `data.image_id` 与请求值相同。网络超时后可使用相同的 `device_code + image_id` 重试,管理系统会返回已有图片记录,不重复保存。 联调时管理端会记录以下结构化日志(不记录图片内容): - `边缘设备图片上传结果`:包含 `result`、`remote_addr`、`device_code`、`image_id`、`event_id`、`content_type`、`size`、`idempotent` 和 `duration`。 - `边缘设备图片上传失败`:包含失败阶段、来源地址、设备编码、图片 ID、耗时和错误原因。常见阶段包括设备未配置、缺少文件、大小超限、格式校验、对象存储和数据库保存。 - `收到车辆识别 MQTT 事件`、`车辆识别事件字段已解析`:确认 MQTT 事件已经到达并展示 `event_id`、`image_id`、车牌和方向。 - `车辆识别事件图片引用校验成功`:确认 MQTT 引用已经匹配到该设备上传的图片;若失败会记录 `车辆识别事件引用的图片不可用`。 - `处理车辆识别事件`:包含通行处理结果和总耗时。 上传成功后发布: ```json { "schema": "camera.event.v1", "event_id": "3e0d2bb5-74bd-4a2c-a5d8-6c3ac6472a1b", "image_id": "447a82ca-7e02-4dd4-9e78-d865f84e3c1d", "device_code": "GATE-A01", "plate_number": "粤A12345", "direction": "in", "ts": 1760000000 } ``` 管理系统只接受已由同一 `device_code` 上传的 `image_id`;图片尚未上传、上传失败或属于其他设备时,该识别事件不进入通行流程。为兼容存量固件,当前管理系统暂时仍接受旧 `image_path` 字段,但新固件必须迁移到 `image_id`。 ## 8. 设备端状态机和重试 建议实现以下状态: | 状态 | 进入条件 | 退出条件 | | --- | --- | --- | | `unprovisioned` | 出厂或恢复出厂 | 收到合法并已配对的配置 | | `identity_ready` | HTTPS 身份服务可用 | 配置成功或重新配对 | | `provisioning` | 正在校验/保存配置 | 原子保存成功或失败 | | `provisioned` | 已保存 MQTT 配置 | 发布在线状态或配置失败 | | `online` | 收到 MQTT CONNACK 并发布 `gate.state.v1` | MQTT 断开 | | `offline` | LWT、连接失败或心跳超时 | MQTT 重连并重新发布状态 | | `failed` | 配置或本地存储失败 | 管理员修正后重新配置 | 重试要求: - MQTT 连接失败使用指数退避,建议 1s、2s、4s、8s、15s,上限 60s。 - HTTPS 配置请求失败时保留旧的可运行 MQTT 配置;不要把设备置于无配置状态。 - `request_id` 重试必须返回同一处理结果,不能重复生成凭据或重复清理数据。 - 设备重启后从持久化配置恢复 MQTT、Topic 和业务坐标,并重新发布 retained state。 - 恢复出厂必须显式清除 MQTT 凭据、业务坐标、配对信任和旧的请求幂等记录,但不能清除制造商永久 `device_id` 和签名密钥。 ## 9. 安全要求 边缘端必须满足: 1. `device_id` 永久且不可由页面输入覆盖。 2. HTTPS 配置接口只能由已建立信任的管理系统调用;设备不能仅凭来源 IP 信任请求。 3. 配对码一次性、短时有效、限流,日志不记录明文。 4. 签名私钥存储在受保护区域;UDP 响应只暴露公钥和指纹。 5. MQTT 凭据不能写入 UDP 响应、普通日志或识别事件。 6. `mqtt.tls=true` 时必须验证 Broker 证书,禁止自动降级为明文。 7. 只处理自己的 MQTT Topic;拒绝 Topic 路由坐标与本地配置不一致的指令。 8. 配置更新必须原子化并可回滚;断电不能产生半份配置。 9. HTTPS、UDP 和 MQTT 输入都要限制包体、字段长度和请求频率。 10. 记录审计所需的 request ID、结果和错误码,但不得记录配对码、私钥或长期密码。 ## 10. 联调验收清单 ### 自动发现 - [ ] 设备在 `0.0.0.0:31001` 监听并能收到定向广播。 - [ ] 设备能校验 schema、过期时间、request ID 和 nonce。 - [ ] 响应单播回管理系统 UDP 源地址和源端口。 - [ ] `ip`、`config_url`、签名和公钥指纹满足格式要求。 - [ ] 错误签名、过期请求、伪造 request ID 和超过 4KB 的包被丢弃。 - [ ] 自动发现后仍必须通过 HTTPS 身份、证书指纹和配对确认。 ### 手工 IP - [ ] 不依赖 UDP 也能通过 HTTPS 读取身份。 - [ ] HTTPS 端口可配置,默认 `8443`,证书指纹稳定。 - [ ] 永久 ID 与设备标签/二维码一致。 - [ ] 正确配对码成功,错误/过期/重复配对码失败并限流。 - [ ] 未配对或 `device_id` 不一致的 `/provision` 不修改配置。 ### 配置和 MQTT - [ ] `/provision` 成功时配置原子保存并返回 `result=accepted`。 - [ ] 配置失败时旧配置仍可运行。 - [ ] 设备按下发坐标生成自己的 Topic 并只订阅自己的命令 Topic。 - [ ] MQTT 重连后先清除 retained LWT,再发布 retained `gate.state.v1`。 - [ ] 管理系统在 30 秒内收到状态并显示在线。 - [ ] `gate.cmd.v1` 在 5 秒内返回匹配 `cmd_id` 的 `gate.ack.v1`。 - [ ] 断电/断网可触发 LWT,恢复后可重新上线。 - [ ] 同一 `request_id` 和 `cmd_id` 重试不会造成重复配置或重复开闸。 ## 11. 版本兼容约定 当前管理系统实现以本文第 5.1 节的 `provision.request.v1` 字段为准,MQTT 默认可能使用匿名连接,具体地址由管理系统 `mqtt.advertised-host` 和 `mqtt.advertised-port` 下发。边缘端应忽略未知 JSON 字段并保留向后兼容能力。 后续启用设备级 MQTT 账号、双向 TLS、配置请求签名、`expires_at`、`nonce` 或 `config_version` 时,管理系统会增加协议版本或字段约束。设备端应将协议版本、已处理的 `request_id` 和配置版本持久化,以便平滑升级和幂等重试。