# PLC Control API 本文档基于当前后端代码整理,覆盖 HTTP API、SSE 日志流和 WebSocket 实时消息。 ## 基本信息 - UI: `/ui` - HTTP API 前缀: `/api` - 公共 WebSocket: `/ws/public` - 客户端专属 WebSocket: `/ws/client/{client_id}` - 日志 SSE: `/api/logs/stream` ## 通用错误响应 失败时通常返回: ```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` 获取数据源列表及连接状态。 ### POST `/api/source` 创建数据源。 请求示例: ```json { "name": "PLC-1", "protocol": "opcua", "endpoint": "opc.tcp://127.0.0.1:4840", "enabled": true } ``` ### PUT `/api/source/{source_id}` 更新数据源,字段均可选。 ### DELETE `/api/source/{source_id}` 删除数据源。成功返回 `204 No Content`。 ### POST `/api/source/{source_id}/reconnect` 手动重连数据源。 ### POST `/api/source/{source_id}/browse` 从 OPC UA 数据源浏览节点并写入本地 `node` 表。 ### GET `/api/source/{source_id}/node-tree` 获取数据源节点树。 ## Point ### GET `/api/point` 分页获取点位列表,同时返回实时监测值。 查询参数: - `source_id` - `equipment_id` - `page` - `page_size` ### GET `/api/point/{point_id}` 获取单个点位。 ### GET `/api/point/{point_id}/history` 获取最近历史样本。历史数据保存在进程内环形缓冲区,服务重启后清空。 查询参数: - `limit`: 默认 `120`,最大 `1000` ### PUT `/api/point/{point_id}` 更新点位元数据。 可更新字段: ```json { "name": "Temperature", "description": "Room temperature", "unit": "C", "tag_id": "uuid", "equipment_id": "uuid", "signal_role": "run" } ``` 说明: - 点位变更设备绑定或信号角色后,会唤醒相关控制单元任务,使控制引擎尽快使用最新映射。 ### DELETE `/api/point/{point_id}` 删除单个点位。 说明: - 删除后会同步通知相关控制单元刷新配置。 ### POST `/api/point/batch` 根据节点批量创建点位。 ### DELETE `/api/point/batch` 批量删除点位。 说明: - 删除后会同步通知相关控制单元刷新配置。 ### PUT `/api/point/batch/set-equipment` 批量设置点位设备绑定和信号角色。 ```json { "point_ids": ["uuid1", "uuid2"], "equipment_id": "uuid", "signal_role": "run" } ``` 说明: - 更新前后关联到的控制单元都会被唤醒,避免控制引擎继续使用旧映射。 ### PUT `/api/point/batch/set-tags` 批量设置点位标签。 ### POST `/api/point/value/batch` 批量写点。 请求头: - `X-Write-Key: ` 请求示例: ```json { "items": [ { "point_id": "uuid", "value": 12.3 } ] } ``` ## Equipment ### GET `/api/equipment` 分页获取设备列表,包含点位数量和已绑定信号角色点。 ### GET `/api/equipment/{equipment_id}` 获取单个设备。 ### GET `/api/equipment/{equipment_id}/points` 获取设备下所有点位。 ### POST `/api/equipment` 创建设备。 说明: - 如果设备绑定了控制单元,创建成功后会唤醒对应控制单元。 ### PUT `/api/equipment/{equipment_id}` 更新设备。 说明: - 如果设备更换了 `unit_id`、`kind` 或其他控制相关配置,旧单元和新单元都会被唤醒。 ### PUT `/api/equipment/batch/set-unit` 批量调整设备所属控制单元。 说明: - 批量更新前关联到的旧单元,以及更新后关联到的新单元,都会收到唤醒通知。 ### DELETE `/api/equipment/{equipment_id}` 删除设备。成功返回 `204 No Content`。 说明: - 删除后会唤醒原所属控制单元。 ## Unit ### GET `/api/unit` 分页获取控制单元列表,返回单元基础信息、运行时快照和设备摘要。 ### 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 } ``` 约束: - `run_time_sec > 0` - `stop_time_sec > 0` - `acc_time_sec > 0` - `bl_time_sec > 0` - `acc_time_sec > run_time_sec` ### PUT `/api/unit/{unit_id}` 更新控制单元,字段均可选。 约束: - 如果更新后涉及时长字段,仍然必须满足上面的全部约束。 ### DELETE `/api/unit/{unit_id}` 删除控制单元。成功返回 `204 No Content`。 ### GET `/api/unit/{unit_id}` 获取单个控制单元及其设备摘要。 ### GET `/api/unit/{unit_id}/detail` 获取控制单元完整详情,包括设备和点位列表。 ### GET `/api/unit/{unit_id}/runtime` 获取控制单元当前运行时状态。 响应字段: ```json { "unit_id": "uuid", "state": "running", "auto_enabled": true, "accumulated_run_sec": 3600000, "display_acc_sec": 3600000, "fault_locked": false, "flt_active": false, "comm_locked": false, "manual_ack_required": false, "rem_local": false } ``` `rem_local` 说明: - `true`: 单元下存在设备 REM 信号为本地模式(REM=0,信号质量正常) - `rem_local = true` 时,自动控制会被强制停止,且禁止重新启动,直到所有 REM 恢复远程 `state` 枚举: - `stopped` - `running` - `distributor_running` - `fault_locked` - `comm_locked` 说明: - `accumulated_run_sec` 和 `display_acc_sec` 单位都是毫秒。 - 运行时状态保存在内存中,服务重启后重置。 ## Event ### GET `/api/event` 分页获取系统事件。 查询参数: - `unit_id` - `event_type` - `page` - `page_size` 常见事件类型: - `unit.auto_control_started` - `unit.auto_control_stopped` - `unit.fault_locked` - `unit.fault_acked` - `unit.comm_locked` - `unit.comm_recovered` - `unit.rem_local` — 设备切换为本地模式,自动控制已停止(warn) - `unit.rem_recovered` — 设备切换回远程模式,自动控制需手动重新启动(warn) - `equipment.start_command_sent` - `equipment.stop_command_sent` ## Control 所有控制命令在执行前都会校验: - 信号质量 - REM 状态(REM=0 时拒绝手动指令) - FLT 状态 - 单元 `auto_enabled` - 单元 `comm_locked` - 单元 `fault_locked` - 单元 `manual_ack_required` - 单元 `rem_local`(自动控制启动时额外校验) ### POST `/api/control/equipment/{equipment_id}/start` 发送设备启动脉冲命令。 ### POST `/api/control/equipment/{equipment_id}/stop` 发送设备停止脉冲命令。 ### POST `/api/control/unit/{unit_id}/start-auto` 启动单元自动控制。 前置条件: - 单元已启用 - `fault_locked = false` - `comm_locked = false` - `manual_ack_required = false` - `rem_local = false`(有设备处于本地模式时不允许启动) 成功响应: ```json { "ok_msg": "Auto control started", "unit_id": "uuid" } ``` ### POST `/api/control/unit/{unit_id}/stop-auto` 停止单元自动控制。 ### POST `/api/control/unit/batch-start-auto` 批量启动所有已启用单元的自动控制。 会跳过以下单元: - 已经 `auto_enabled = true` - `fault_locked = true` - `comm_locked = true` - `manual_ack_required = true` - `rem_local = true` 说明: - 单个启动和批量启动现在使用相同的阻断规则。 ### POST `/api/control/unit/batch-stop-auto` 批量停止自动控制。 ### POST `/api/control/unit/{unit_id}/ack-fault` 人工确认故障。 前置条件: - `fault_locked = true` - `flt_active = false` ## Tag ### GET `/api/tag` 分页获取标签列表。 ### POST `/api/tag` 创建标签。 ### GET `/api/tag/{tag_id}` 获取标签下已绑定点位。 ### PUT `/api/tag/{tag_id}` 更新标签。 ### DELETE `/api/tag/{tag_id}` 删除标签。成功返回 `204 No Content`。 ## Page ### GET `/api/page` 获取自定义页面列表。 ### POST `/api/page` 创建页面。 ### GET `/api/page/{page_id}` 获取单个页面。 ### PUT `/api/page/{page_id}` 更新页面。 ### DELETE `/api/page/{page_id}` 删除页面。成功返回 `204 No Content`。 ## Log ### GET `/api/logs` 读取日志文件内容。默认读取最新的 `app.log*` 文件。 查询参数: - `file`: 指定文件名,仅允许 `app.log*` - `cursor`: 增量读取位置 - `tail_lines`: 默认 `200`,最大 `2000` - `max_bytes`: 默认 `64KB`,最大 `512KB` 响应示例: ```json { "file": "app.log", "cursor": 204800, "lines": ["2026-03-25 10:00:00 INFO ..."], "truncated": false, "reset": false } ``` 字段说明: - `truncated = true`: 当前还有未读完内容,可继续用新 `cursor` 拉取 - `reset = true`: 文件被截断或读取起点被重置 ### GET `/api/logs/stream` 通过 SSE 推送日志增量。 查询参数: - `file` - `cursor` - `max_bytes` 默认行为: - 如果没有传 `file`,服务端会始终跟随最新的 `app.log*` - 如果日志轮转到了新文件,流会自动切换到新文件,并推送一条 `reset = true` 的日志事件 事件示例: ```text event: log data: { "file": "app.log.1", "cursor": 1024, "lines": ["..."], "truncated": false, "reset": true } event: error data: log stream read failed ``` ## WebSocket ### 服务端推送消息 #### `PointNewValue` 点位实时值更新。 #### `EventCreated` 系统事件创建。 #### `UnitRuntimeChanged` 控制单元运行时状态更新。 ```json { "type": "UnitRuntimeChanged", "data": { "unit_id": "uuid", "state": "running", "auto_enabled": true, "fault_locked": false, "flt_active": false, "comm_locked": false, "manual_ack_required": false, "rem_local": false, "accumulated_run_sec": 3600000, "display_acc_sec": 3600000 } } ``` #### `PointSetValueBatchResult` 批量写点结果回调。 ### 客户端消息 #### `auth_write` ```json { "type": "auth_write", "data": { "key": "your-write-key" } } ``` #### `point_set_value_batch` ```json { "type": "point_set_value_batch", "data": { "items": [ { "point_id": "uuid", "value": 12.3 } ] } } ``` ## 备注 - 控制引擎现在会在每轮单元循环中重新加载设备和角色映射,而不是只在任务启动时加载一次。 - 设备和点位的控制相关配置变更后,会主动唤醒对应单元任务,使新配置尽快生效。 - 日志流默认跟随最新日志文件,适配日志轮转场景。 - REM 信号为本地模式(REM=0)时,手动控制指令(start/stop)和自动控制启动均被拒绝;若自动控制正在运行,引擎会立即停止并记录 `unit.rem_local` 事件。REM 恢复远程后记录 `unit.rem_recovered`(warn),自动控制需操作员手动重新启动。