# 智慧停车系统 · 设备端对接文档 > 版本:v1.0 > 适用对象:硬件端(车牌识别相机 / LPR 相机、UHF 读写器、道闸控制器)工程师 > 配套文档:`doc/设备接入层设计.md`、`doc/多岗亭改造方案.md` --- ## 1. 通信架构概述 系统与硬件设备之间采用 **MQTT 消息总线 + HTTP 图片传输** 两种通道: ``` ┌─────────────┐ MQTT 发布(识别事件/状态/回执) ┌──────────────────┐ │ 硬件设备 │ ─────────────────────────────▶ │ 智慧停车系统 │ │ (相机/道闸) │ │ (内嵌 MQTT Broker)│ │ │ ◀───────────────────────────── │ │ └─────────────┘ MQTT 订阅(道闸指令) └──────────────────┘ │ │ HTTP(抓拍图片,图片不进 MQTT) ▼ 图片存储/URL 引用 ``` 核心原则: - **MQTT 只传「元数据」**(车牌、RFID、状态、指令、回执),数据量小、实时性高。 - **图片走 HTTP**,绝不塞进 MQTT,避免撑爆 broker。 - 系统内嵌了一个 MQTT Broker(`mochi-mqtt`),设备**主动连接**它,而不是系统连接设备。 --- ## 2. MQTT 连接规范 | 参数 | 值 | 说明 | |------|-----|------| | 协议 | MQTT 3.1.1 | 标准 MQTT | | Broker 地址 | `tcp://<系统主机IP>:1883` | 系统内嵌 broker,默认端口 1883 | | ClientID | **设备编码(device_code)** | 必须全局唯一,建议用系统里配置的设备编码 | | 用户名 / 密码 | 留空 | 当前版本匿名接入(`allow-all-auth`) | | QoS | 1(至少一次) | 所有消息统一 QoS 1 | | Keep Alive | 30 秒 | 建议 30s,超时 broker 会判定离线 | | 遗嘱(LWT) | 必须配置 | 见 §2.1,用于系统感知设备离线 | ### 2.1 遗嘱(Last Will)配置(必配) 设备连接时必须设置 LWT 遗嘱消息,否则设备断电/断网后系统无法感知离线。 | 遗嘱参数 | 值 | |----------|-----| | Topic | `parking/lot/{lot}/booth/{booth}/channel/{ch}/gate/lwt` | | Payload | `{"schema":"gate.lwt.v1","device_code":"<设备编码>"}` | | QoS | 1 | | Retained | true | ### 2.2 心跳 / 在线状态(必发) 设备连接成功后,**立即发布一条 retained 的状态消息**,让系统知道设备上线: - Topic:`parking/lot/{lot}/booth/{booth}/channel/{ch}/gate/state` - Retained:`true` - Payload:见 §4.2 `gate.state.v1` --- ## 3. Topic 规范 ### 3.1 命名规则 统一前缀 `parking/`,中间为「停车场 → 岗亭 → 通道」三级坐标,末尾为消息类型: ``` parking/lot/{lot_id}/booth/{booth_id}/channel/{channel_id}/{类型} ``` > `{lot_id}`、`{booth_id}`、`{channel_id}` 是**系统分配的数字 ID**,不是编码(code)。设备端需从系统获取这三元组并配置到设备(见 §7)。 ### 3.2 Topic 总览 | 方向 | Topic | QoS | Retained | 说明 | |------|-------|-----|----------|------| | 设备 → 系统 | `.../channel/{ch}/camera/event` | 1 | 否 | 车牌/RFID 识别事件 | | 设备 → 系统 | `.../channel/{ch}/gate/state` | 1 | **是** | 道闸状态/在线心跳 | | 系统 → 设备 | `.../channel/{ch}/gate/cmd` | 1 | 否 | 开/关闸指令 | | 设备 → 系统 | `.../channel/{ch}/gate/cmd/ack` | 1 | 否 | 指令执行回执 | | 设备 → 系统 | `.../channel/{ch}/gate/lwt` | 1 | **是** | 遗嘱消息(离线通知) | ### 3.3 设备需要订阅的 Topic 设备只需订阅**自己坐标下的指令**: ``` parking/lot/{lot}/booth/{booth}/channel/{ch}/gate/cmd ``` --- ## 4. 消息格式定义(JSON) 所有消息为 JSON 文本,UTF-8 编码。每个消息都含 `schema` 字段用于区分类型。 ### 4.1 识别事件 `camera.event.v1`(设备 → 系统) 车牌识别(或 RFID 读取)成功后上报,触发系统执行进场/出场流程。 ```json { "schema": "camera.event.v1", "device_code": "CAM-A01", "plate_number": "粤A12345", "rfid_tag": "E28068940000500ABCDEF", "direction": "in", "ts": 1730000000, "image_path": "http://192.168.1.50/capture/20240814_001.jpg", "confidence": 0.98 } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | schema | string | 是 | 固定 `camera.event.v1` | | device_code | string | 是 | 设备编码,**必须与系统设备管理中配置的一致** | | plate_number | string | 否* | 车牌号(无牌车可空) | | rfid_tag | string | 否* | RFID 标签 EPC(无 RFID 可空) | | direction | string | 是 | 通行方向 `in`(进场)/ `out`(出场) | | ts | int64 | 是 | 事件时间戳,**Unix 秒** | | image_path | string | 否 | 抓拍图 URL 或路径(图片不走 MQTT) | | confidence | float | 否 | 识别置信度,0~1 | > `*` `plate_number` 与 `rfid_tag` 至少一个非空。 ### 4.2 道闸状态 `gate.state.v1`(设备 → 系统,retained) 道闸状态变化或上线时上报,系统据此维护设备在线状态。 ```json { "schema": "gate.state.v1", "device_code": "GATE-A01", "state": "closed", "ts": 1730000000 } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | schema | string | 是 | 固定 `gate.state.v1` | | device_code | string | 是 | 设备编码 | | state | string | 是 | `open` / `closed` / `moving` | | ts | int64 | 是 | Unix 秒时间戳 | ### 4.3 道闸指令 `gate.cmd.v1`(系统 → 设备) 系统在进出场放行时下发开/关闸指令,设备收到后执行并回执。 ```json { "schema": "gate.cmd.v1", "cmd_id": "3f2a1b9c-4d5e-4f6a-8b9c-0d1e2f3a4b5c", "device_code": "GATE-A01", "action": "open", "valid_time": 2 } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | schema | string | 是 | 固定 `gate.cmd.v1` | | cmd_id | string | 是 | 指令唯一编号(UUID),回执需原样带回 | | device_code | string | 是 | 目标设备编码 | | action | string | 是 | `open`(开闸)/ `close`(关闸) | | valid_time | int | 是 | 道闸动作保持时长(秒),默认 2,范围 1~255 | ### 4.4 指令回执 `gate.ack.v1`(设备 → 系统) 设备执行完指令后立即回执。**必须在 5 秒内回执**,否则系统判定超时。 ```json { "schema": "gate.ack.v1", "cmd_id": "3f2a1b9c-4d5e-4f6a-8b9c-0d1e2f3a4b5c", "device_code": "GATE-A01", "result": "success", "error": "" } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | schema | string | 是 | 固定 `gate.ack.v1` | | cmd_id | string | 是 | 对应指令的 `cmd_id`,**原样带回** | | device_code | string | 是 | 设备编码 | | result | string | 是 | `success`(成功)/ `failed`(失败) | | error | string | 否 | 失败原因(result=failed 时填写) | ### 4.5 遗嘱消息 `gate.lwt.v1`(设备 → 系统,retained) 设备异常断开时,由 broker 自动发布遗嘱消息。设备**无需主动发布**,只需在连接时配置好 LWT(见 §2.1)。 ```json { "schema": "gate.lwt.v1", "device_code": "GATE-A01" } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | schema | string | 是 | `gate.lwt.v1`(系统兼容 `gate.lwt`) | | device_code | string | 是 | 设备编码 | --- ## 5. 典型交互流程 ### 5.1 设备上线 ``` 设备 Broker(系统) │ 1. CONNECT(ClientID=device_code, LWT配置) │ │────────────────────────────────────────────▶│ │ 2. CONNACK │ │◀────────────────────────────────────────────│ │ 3. SUBSCRIBE .../gate/cmd │ │────────────────────────────────────────────▶│ │ 4. PUBLISH .../gate/state (retained, closed)│ │────────────────────────────────────────────▶│ ← 系统回写设备 online ``` ### 5.2 车牌识别 → 入场放行 ``` 相机 系统 道闸 │ PUBLISH camera/event(in) │ │ │────────────────────────────▶│ 黑名单/满位/临时车校验 │ │ │ PUBLISH gate/cmd(open) │ │ │────────────────────────────▶│ │ │ PUBLISH gate/cmd/ack │ │ │◀────────────────────────────│ │ │ (5秒内未回执则超时) │ ``` ### 5.3 车牌识别 → 出场放行 同 §5.2,区别是 `camera/event` 的 `direction` 为 `out`。系统会先做计费结算,再下发开闸指令。 ### 5.4 设备离线 ``` 道闸异常断开 │ ▼ Broker 自动发布 retained 的 gate/lwt 遗嘱 │ ▼ 系统收到 → 回写设备 offline + 记录「设备离线」异常 ``` --- ## 6. 字段枚举汇总 | 字段 | 取值 | 说明 | |------|------|------| | direction | `in` / `out` | 进场 / 出场 | | state | `open` / `closed` / `moving` | 道闸开启 / 关闭 / 动作中 | | action | `open` / `close` | 开闸 / 关闸 | | result | `success` / `failed` | 指令成功 / 失败 | | schema | 见 §4 | 消息类型标识 | --- ## 7. 设备部署配置(重要) ### 7.1 设备需要配置的 4 个参数 设备端部署时,需要从系统获取并配置以下参数: | 参数 | 来源 | 说明 | |------|------|------| | `device_code` | 系统「设备管理」 | 设备编码,全局唯一 | | `lot_id` | 系统「停车场管理」 | 所属停车场数字 ID | | `booth_id` | 系统「岗亭管理」 | 所属岗亭数字 ID | | `channel_id` | 系统「通道管理」 | 绑定通道数字 ID | ### 7.2 坐标层级关系 ``` 停车场 ParkingLot (lot_id) └── 岗亭 Booth (booth_id) └── 通道 Channel (channel_id) └── 设备 UHFReader (device_code) ``` 设备必须「绑定通道」后,坐标三元组 `(lot_id, booth_id, channel_id)` 才完整。MQTT 设备必须绑定通道(系统已强制校验)。 ### 7.3 获取方式 在系统界面「停车管理 → 停车场信息」中,依次查看停车场、岗亭、通道、设备的 ID;或通过后端接口查询(`/parking/lot/list`、`/parking/booth/list`、`/parking/channel/list`、`/parking/device/list`)。 > 注意:topic 中使用的是**数字 ID**(数据库主键),不是 lot_code/booth_code/channel_code 编码字符串。 --- ## 8. 图片关联(HTTP) ### 8.1 关联方式 抓拍图片**不通过 MQTT 传输**。识别事件中通过 `image_path` 字段引用图片,系统前端直接加载该 URL 显示。 ### 8.2 推荐方案(当前版本) 设备端在识别时抓拍图片,并**由设备自身提供 HTTP 访问**(或存到约定的图片服务器),然后在 `camera.event.v1` 的 `image_path` 里带上该图片的完整 URL: ```json "image_path": "http://192.168.1.50/capture/20240814_001.jpg" ``` 系统会把该 URL 存到车辆记录的入场/出场图片字段,工作台直接显示。 > 若后续需要「设备主动上传图片到系统」,可协商增加系统侧 HTTP 上传端点(POST multipart),当前版本未实现。 --- ## 9. 错误处理与重试约定 | 场景 | 约定 | |------|------| | 指令下发失败(Publish 失败) | 系统记录异常,本次放行标记失败 | | 指令超时(5 秒未收到 ack) | 系统判定「开闸失败」,记录道闸异常 | | ack result=failed | 系统记录「道闸执行失败」,保留 error 信息 | | 设备断连 | 靠 LWT 遗嘱触发,系统自动标记离线 | | 设备重连 | 重新 CONNECT 后,先向 `gate/lwt` 发布 retained 空载荷清除旧遗嘱,再立即重发 retained 的 `gate.state.v1` | | 识别事件重复 | 系统有 3 秒防抖,同一标识 3 秒内重复事件会被丢弃 | --- ## 10. 联调自检清单 - [ ] 设备能连上 `tcp://<系统IP>:1883`(匿名) - [ ] ClientID 设置为系统里的 `device_code` - [ ] 已配置 LWT 遗嘱(QoS 1 + retained) - [ ] 上线后先清除了 `gate/lwt` 的 retained 遗嘱,再发布 retained 的 `gate.state.v1` - [ ] 已订阅自己的 `gate/cmd` 指令 Topic - [ ] 识别到车牌后能发 `camera.event.v1`(direction 正确) - [ ] 收到 `gate.cmd.v1` 后 5 秒内能回 `gate.ack.v1` - [ ] 拔网线/断电后,系统设备列表状态变为「离线」 --- ## 附录 A:消息示例汇总 **A1 识别事件(进场)** ```json {"schema":"camera.event.v1","device_code":"CAM-A01","plate_number":"粤A12345","rfid_tag":"","direction":"in","ts":1730000000,"image_path":"http://192.168.1.50/cap/001.jpg","confidence":0.97} ``` **A2 识别事件(出场,仅 RFID)** ```json {"schema":"camera.event.v1","device_code":"CAM-B02","plate_number":"","rfid_tag":"E28068940000500ABCDEF","direction":"out","ts":1730000010,"image_path":"","confidence":0} ``` **A3 道闸状态(上线)** ```json {"schema":"gate.state.v1","device_code":"GATE-A01","state":"closed","ts":1730000000} ``` **A4 开闸指令(系统下发)** ```json {"schema":"gate.cmd.v1","cmd_id":"3f2a1b9c-4d5e-4f6a-8b9c-0d1e2f3a4b5c","device_code":"GATE-A01","action":"open","valid_time":2} ``` **A5 开闸回执(成功)** ```json {"schema":"gate.ack.v1","cmd_id":"3f2a1b9c-4d5e-4f6a-8b9c-0d1e2f3a4b5c","device_code":"GATE-A01","result":"success","error":""} ``` **A6 开闸回执(失败)** ```json {"schema":"gate.ack.v1","cmd_id":"3f2a1b9c-4d5e-4f6a-8b9c-0d1e2f3a4b5c","device_code":"GATE-A01","result":"failed","error":"motor timeout"} ``` **A7 遗嘱(离线)** ```json {"schema":"gate.lwt.v1","device_code":"GATE-A01"} ```