# PLC Control 接口说明 本文档基于当前服务端路由与处理器代码整理,覆盖 HTTP API 和 WebSocket 实时消息。 ## 基本信息 - 服务端默认提供静态 UI:`/ui` - HTTP API 前缀:`/api` - 公共实时 WebSocket:`/ws/public` - 客户端专属 WebSocket:`/ws/client/{client_id}` ## 通用错误响应 接口失败时通常返回: ```json { "err_code": 400, "err_msg": "Invalid JSON format", "err_detail": null } ``` 常见状态码: - `400 Bad Request`:参数错误 - `403 Forbidden`:写入权限不足或控制条件不满足 - `404 Not Found`:资源不存在 - `500 Internal Server Error`:服务端内部错误 --- ## Source ### GET `/api/source` 获取所有已启用数据源及其连接状态。 响应示例: ```json [ { "id": "uuid", "name": "PLC-1", "protocol": "opcua", "endpoint": "opc.tcp://127.0.0.1:4840", "security_policy": null, "security_mode": null, "enabled": true, "created_at": "2026-03-20 10:00:00.000", "updated_at": "2026-03-20 10:00:00.000", "is_connected": true, "last_error": null, "last_time": "2026-03-20 10:05:00.000" } ] ``` ### POST `/api/source` 创建数据源。 请求体: ```json { "name": "PLC-1", "endpoint": "opc.tcp://127.0.0.1:4840", "enabled": true } ``` 响应: ```json { "id": "uuid" } ``` ### PUT `/api/source/{source_id}` 更新数据源。请求体字段均可选: ```json { "name": "PLC-1", "endpoint": "opc.tcp://127.0.0.1:4840", "enabled": true, "security_policy": "None", "security_mode": "None", "username": "user", "password": "pass" } ``` ### DELETE `/api/source/{source_id}` 删除数据源。成功响应:`204 No Content` ### POST `/api/source/{source_id}/reconnect` 手动重连指定数据源。 ```json { "ok_msg": "Source reconnected successfully" } ``` ### POST `/api/source/{source_id}/browse` 从 OPC UA 源浏览节点并写入本地 `node` 表。 ```json { "ok_msg": "Browse completed", "total_nodes": 123 } ``` ### GET `/api/source/{source_id}/node-tree` 获取指定数据源的节点树(含 `children` 递归嵌套)。 --- ## Point ### GET `/api/point` 分页获取点位列表,同时返回实时监测值。 查询参数: - `source_id`:可选,按数据源过滤 - `equipment_id`:可选,按设备过滤 - `page`:页码 - `page_size`:每页条数(`-1` 表示全量) 响应示例: ```json { "data": [ { "point": { "id": "uuid", "node_id": "uuid", "name": "Temperature", "equipment_id": "uuid", "signal_role": "run", "created_at": "2026-03-20 10:00:00.000", "updated_at": "2026-03-20 10:00:00.000" }, "point_monitor": { "point_id": "uuid", "timestamp": "2026-03-20 10:05:00.000", "quality": "good", "value": 12.3, "value_type": "float", "value_text": "12.3" } } ], "total": 1, "page": 1, "page_size": 100 } ``` ### GET `/api/point/{point_id}` 获取单个点位。 ### GET `/api/point/{point_id}/history` 获取点位最近历史样本(进程内存环形缓冲,重启后清空)。 查询参数:`limit`(可选,默认 `120`,最大 `1000`) ```json [ { "timestamp": "2026-03-20 10:05:00.000", "quality": "good", "value": 12.3, "value_text": "12.3", "value_number": 12.3 } ] ``` ### PUT `/api/point/{point_id}` 更新点位元数据,字段均可选: ```json { "name": "Temperature", "description": "Room temperature", "unit": "°C", "equipment_id": "uuid", "signal_role": "run" } ``` ### DELETE `/api/point/{point_id}` 删除单个点位。 ### POST `/api/point/batch` 根据节点批量创建点位。 ```json { "node_ids": ["uuid1", "uuid2"] } ``` 响应: ```json { "success_count": 2, "failed_count": 0, "failed_node_ids": [], "created_point_ids": ["uuid3", "uuid4"] } ``` ### DELETE `/api/point/batch` 批量删除点位。 ```json { "point_ids": ["uuid1", "uuid2"] } ``` ### PUT `/api/point/batch/set-equipment` 批量设置点位的设备绑定和信号角色。 ```json { "point_ids": ["uuid1", "uuid2"], "equipment_id": "uuid", "signal_role": "run" } ``` ### PUT `/api/point/batch/set-tags` 批量设置点位的标签(`tag_id`,传 `null` 可清除绑定)。 ```json { "point_ids": ["uuid1", "uuid2"], "tag_id": "uuid" } ``` 响应: ```json { "ok_msg": "Point tags updated successfully", "updated_count": 2 } ``` ### POST `/api/point/value/batch` 批量写点。 请求头:`X-Write-Key: ` ```json { "items": [ { "point_id": "uuid", "value": 12.3 } ] } ``` --- ## Equipment ### GET `/api/equipment` 分页获取设备列表,包含每台设备绑定的点位数量。 查询参数:`page`、`page_size`(`-1` 全量)、`keyword`(可选,按 code/name 模糊搜索) 响应示例: ```json { "data": [ { "id": "uuid", "unit_id": "uuid", "code": "E01", "name": "投煤器1", "kind": "coal_feeder", "description": null, "created_at": "2026-03-20 10:00:00.000", "updated_at": "2026-03-20 10:00:00.000", "point_count": 5 } ], "total": 1, "page": 1, "page_size": 20 } ``` ### POST `/api/equipment` 创建设备。 ```json { "unit_id": "uuid", "code": "E01", "name": "投煤器1", "kind": "coal_feeder", "description": null } ``` 响应:`201 Created` ```json { "id": "uuid", "ok_msg": "Equipment created successfully" } ``` ### PUT `/api/equipment/{equipment_id}` 更新设备,字段均可选: ```json { "unit_id": "uuid", "code": "E01", "name": "投煤器1", "kind": "coal_feeder", "description": null } ``` ### DELETE `/api/equipment/{equipment_id}` 删除设备。成功响应:`204 No Content` ### GET `/api/equipment/{equipment_id}/points` 获取指定设备下所有绑定点位。 ### PUT `/api/equipment/batch/set-unit` 批量将设备绑定到控制单元。 ```json { "equipment_ids": ["uuid1", "uuid2"], "unit_id": "uuid" } ``` --- ## Unit(控制单元) ### GET `/api/unit` 分页获取控制单元列表。 查询参数:`page`、`page_size`、`keyword`(可选) 响应字段包含:`id`、`code`、`name`、`enabled`、`run_time_sec`、`stop_time_sec`、`acc_time_sec`、`bl_time_sec`、`require_manual_ack_after_fault` ### POST `/api/unit` 创建控制单元。 ```json { "code": "U01", "name": "1号机组", "description": null, "enabled": true, "run_time_sec": 60, "stop_time_sec": 30, "acc_time_sec": 3600, "bl_time_sec": 10, "require_manual_ack_after_fault": true } ``` 响应:`201 Created` ```json { "id": "uuid", "ok_msg": "Unit created successfully" } ``` ### GET `/api/unit/{unit_id}` 获取单个控制单元。 ### PUT `/api/unit/{unit_id}` 更新控制单元,字段均可选。 ### DELETE `/api/unit/{unit_id}` 删除控制单元。成功响应:`204 No Content` ### GET `/api/unit/{unit_id}/runtime` 获取控制单元的当前运行时状态(内存中,不持久化)。 响应示例: ```json { "unit_id": "uuid", "state": "running", "auto_enabled": true, "accumulated_run_sec": 3600000, "current_run_elapsed_sec": 60000, "current_stop_elapsed_sec": 0, "distributor_run_elapsed_sec": 0, "fault_locked": false, "flt_active": false, "comm_locked": false, "manual_ack_required": false, "last_tick_at": "2026-03-25 10:00:00.000" } ``` `state` 枚举值:`stopped` / `running` / `distributor_running` / `fault_locked` / `comm_locked` 注意:时间字段单位为毫秒(ms)。 ### GET `/api/unit/{unit_id}/detail` 获取控制单元及其下所有设备和点位的完整嵌套结构。 响应示例: ```json { "id": "uuid", "code": "U01", "name": "1号机组", "enabled": true, "equipments": [ { "id": "uuid", "code": "E01", "name": "投煤器1", "kind": "coal_feeder", "points": [ { "id": "uuid", "name": "启动命令", "signal_role": "start_cmd", "equipment_id": "uuid" } ] } ] } ``` --- ## Event(系统事件) ### GET `/api/event` 分页获取系统控制事件记录。 查询参数: - `unit_id`:可选,按控制单元过滤 - `event_type`:可选,按事件类型过滤 - `page`、`page_size` 响应示例: ```json { "data": [ { "id": "uuid", "event_type": "equipment.start_command_sent", "level": "info", "unit_id": "uuid", "equipment_id": "uuid", "message": "Start command sent to equipment ...", "payload": {}, "created_at": "2026-03-25 10:00:00.000" } ], "total": 1, "page": 1, "page_size": 20 } ``` --- ## Control(控制命令) 所有控制命令在执行前会校验:信号质量、REM 状态、FLT 状态、单元通讯锁、单元故障锁。 ### POST `/api/control/equipment/{equipment_id}/start` 向设备发送启动脉冲命令(写 HIGH → 延迟 300ms → 写 LOW)。 响应示例: ```json { "ok_msg": "Equipment start command sent", "equipment_id": "uuid", "unit_id": "uuid", "command_role": "start_cmd", "command_point_id": "uuid", "pulse_ms": 300 } ``` 失败(设备未处于可启动状态)返回 `403 Forbidden`。 ### POST `/api/control/equipment/{equipment_id}/stop` 向设备发送停止脉冲命令。响应结构同上。 ### POST `/api/control/unit/{unit_id}/start-auto` 启动指定控制单元的自动控制循环。单元须已启用(`enabled = true`)。 ```json { "ok_msg": "Auto control started", "unit_id": "uuid" } ``` ### POST `/api/control/unit/{unit_id}/stop-auto` 停止自动控制循环。设备当前状态保持不变,不会自动停机。 ```json { "ok_msg": "Auto control stopped", "unit_id": "uuid" } ``` ### POST `/api/control/unit/batch-start-auto` 批量启动所有已启用(`enabled = true`)控制单元的自动控制。已在运行、故障锁或通讯锁的单元将跳过。 ```json { "started": ["uuid1", "uuid2"], "skipped": ["uuid3"] } ``` ### POST `/api/control/unit/batch-stop-auto` 批量停止所有自动控制中的控制单元。 ```json { "stopped": ["uuid1", "uuid2"] } ``` ### POST `/api/control/unit/{unit_id}/ack-fault` 人工确认故障,解除故障锁定。要求:`fault_locked = true` 且 `flt_active = false`(故障信号已消失)。 ```json { "ok_msg": "Fault acknowledged", "unit_id": "uuid" } ``` --- ## Tag(标签) ### GET `/api/tag` 分页获取标签列表。 查询参数:`page`、`page_size` 响应示例: ```json { "data": [ { "id": "uuid", "name": "主蒸汽", "description": null, "created_at": "2026-03-20 10:00:00.000", "updated_at": "2026-03-20 10:00:00.000" } ], "total": 1, "page": 1, "page_size": 20 } ``` ### POST `/api/tag` 创建标签,可同时绑定点位。 ```json { "name": "主蒸汽", "description": null, "point_ids": ["uuid1", "uuid2"] } ``` 响应:`201 Created` ```json { "id": "uuid", "ok_msg": "Tag created successfully" } ``` ### GET `/api/tag/{tag_id}` 获取标签下所有绑定点位。 响应:点位对象数组。 ### PUT `/api/tag/{tag_id}` 更新标签,字段均可选(`point_ids` 传入时全量替换绑定关系): ```json { "name": "主蒸汽", "description": "描述", "point_ids": ["uuid1"] } ``` ### DELETE `/api/tag/{tag_id}` 删除标签。成功响应:`204 No Content` --- ## Page(自定义页面) ### GET `/api/page` 获取所有页面,按 `created_at` 排序。 查询参数:`name`(可选,模糊搜索) 响应:Page 对象数组。 ```json [ { "id": "uuid", "name": "总览", "data": { "slot_key": "point-uuid" }, "created_at": "2026-03-20 10:00:00.000", "updated_at": "2026-03-20 10:00:00.000" } ] ``` `data` 为 `{ slot_key: point_id }` 映射,用于页面布局与点位绑定。 ### POST `/api/page` 创建页面。 ```json { "name": "总览", "data": { "slot_a": "uuid1", "slot_b": "uuid2" } } ``` 响应:`201 Created` ```json { "id": "uuid", "ok_msg": "Page created successfully" } ``` ### GET `/api/page/{page_id}` 获取单个页面。 ### PUT `/api/page/{page_id}` 更新页面,字段均可选: ```json { "name": "总览", "data": { "slot_a": "uuid1" } } ``` ### DELETE `/api/page/{page_id}` 删除页面。成功响应:`204 No Content` --- ## Log(运行日志) ### GET `/api/logs` 读取服务端日志文件内容(默认取最新 `app.log*` 文件尾部 200 行)。 查询参数: - `file`:可选,指定文件名(仅允许 `app.log` 前缀) - `cursor`:可选,上次返回的字节偏移量;传入时增量读取 cursor 之后的内容 - `tail_lines`:可选,不传 cursor 时返回的尾部行数(默认 200,最大 2000) - `max_bytes`:可选,单次最多返回字节数(默认 64 KB,最大 512 KB) 响应示例: ```json { "file": "app.log", "cursor": 204800, "lines": ["2026-03-25 10:00:00 INFO ..."], "truncated": false, "reset": false } ``` - `truncated`:`true` 表示本次未读完,可用新 cursor 继续请求 - `reset`:`true` 表示文件已轮转(cursor > 文件大小),已从头读取 ### GET `/api/logs/stream` 以 **SSE**(Server-Sent Events)流式推送日志增量,每 800 ms 检查一次文件变化。 查询参数:`file`、`cursor`(可选,默认从文件末尾开始)、`max_bytes`(默认 32 KB) 事件格式: ``` event: log data: { "file": "app.log", "cursor": 204800, "lines": [...], "truncated": false, "reset": false } event: error data: log stream read failed ``` --- ## WebSocket ### 连接地址 - 公共广播:`/ws/public` - 客户端专属:`/ws/client/{client_id}` ### 服务端主动推送消息 #### `PointNewValue` 点位实时值更新: ```json { "type": "PointNewValue", "data": { "point_id": "uuid", "timestamp": "2026-03-20 10:05:00.000", "quality": "good", "value": 12.3, "value_type": "float", "value_text": "12.3" } } ``` #### `EventCreated` 系统事件创建(控制操作、故障、状态变更等): ```json { "type": "EventCreated", "data": { "id": "uuid", "event_type": "equipment.start_command_sent", "level": "info", "unit_id": "uuid", "equipment_id": "uuid", "message": "...", "created_at": "2026-03-25 10:00:00.000" } } ``` #### `UnitRuntimeChanged` 控制单元运行时状态变更(每 500ms tick 后广播): ```json { "type": "UnitRuntimeChanged", "data": { "unit_id": "uuid", "state": "running", "auto_enabled": true, "fault_locked": false, "comm_locked": false, "manual_ack_required": false, "accumulated_run_sec": 3600000 } } ``` #### `PointSetValueBatchResult` 批量写点结果回调: ```json { "type": "PointSetValueBatchResult", "data": { "success": true, "err_msg": null, "success_count": 1, "failed_count": 0, "results": [] } } ``` ### 客户端发送消息 #### 写权限认证 ```json { "type": "auth_write", "data": { "key": "your-write-key" } } ``` #### 批量写点 ```json { "type": "point_set_value_batch", "data": { "items": [ { "point_id": "uuid", "value": 12.3 } ] } } ``` --- ## 备注 - 运行时状态(`/runtime`)存储在内存中,服务重启后重置。 - 历史曲线数据(`/history`)同样是内存环形缓冲,重启后清空。 - 控制单元时间配置字段(`run_time_sec` 等)单位为秒,运行时 elapsed 字段单位为毫秒。 - 自动控制启动后,状态机以 500ms 为周期运行,实时状态通过 WebSocket `UnitRuntimeChanged` 推送。