diff --git a/API.md b/API.md index 6dba9c8..5b9f7b0 100644 --- a/API.md +++ b/API.md @@ -1,6 +1,6 @@ # PLC Control 接口说明 -本文档基于当前服务端路由与处理器代码整理,覆盖 HTTP API、SSE 日志流和 WebSocket 实时消息。 +本文档基于当前服务端路由与处理器代码整理,覆盖 HTTP API 和 WebSocket 实时消息。 ## 基本信息 @@ -24,7 +24,7 @@ 常见状态码: - `400 Bad Request`:参数错误 -- `403 Forbidden`:写入权限不足 +- `403 Forbidden`:写入权限不足或控制条件不满足 - `404 Not Found`:资源不存在 - `500 Internal Server Error`:服务端内部错误 @@ -74,16 +74,12 @@ 响应: ```json -{ - "id": "uuid" -} +{ "id": "uuid" } ``` ### PUT `/api/source/{source_id}` -更新数据源。 - -请求体字段均可选: +更新数据源。请求体字段均可选: ```json { @@ -97,63 +93,29 @@ } ``` -响应: - -```json -{ - "ok_msg": "Source updated successfully" -} -``` - ### DELETE `/api/source/{source_id}` -删除数据源。 - -成功响应:`204 No Content` +删除数据源。成功响应:`204 No Content` ### POST `/api/source/{source_id}/reconnect` 手动重连指定数据源。 -响应: - ```json -{ - "ok_msg": "Source reconnected successfully" -} +{ "ok_msg": "Source reconnected successfully" } ``` ### POST `/api/source/{source_id}/browse` 从 OPC UA 源浏览节点并写入本地 `node` 表。 -响应: - ```json -{ - "ok_msg": "Browse completed", - "total_nodes": 123 -} +{ "ok_msg": "Browse completed", "total_nodes": 123 } ``` ### GET `/api/source/{source_id}/node-tree` -获取指定数据源的节点树。 - -响应字段: - -- `id` -- `source_id` -- `external_id` -- `namespace_uri` -- `namespace_index` -- `identifier_type` -- `identifier` -- `browse_name` -- `display_name` -- `node_class` -- `parent_id` -- `children` +获取指定数据源的节点树(含 `children` 递归嵌套)。 --- @@ -161,13 +123,14 @@ ### GET `/api/point` -分页获取点位列表。 +分页获取点位列表,同时返回实时监测值。 查询参数: - `source_id`:可选,按数据源过滤 +- `equipment_id`:可选,按设备过滤 - `page`:页码 -- `page_size`:每页条数 +- `page_size`:每页条数(`-1` 表示全量) 响应示例: @@ -175,28 +138,22 @@ { "data": [ { - "id": "uuid", - "node_id": "uuid", - "name": "Temperature", - "description": null, - "unit": null, - "tag_id": null, - "created_at": "2026-03-20 10:00:00.000", - "updated_at": "2026-03-20 10:00:00.000", + "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": { - "protocol": "opcua", - "source_id": "uuid", "point_id": "uuid", - "client_handle": 1001, - "scan_mode": "subscribe", "timestamp": "2026-03-20 10:05:00.000", "quality": "good", "value": 12.3, "value_type": "float", - "value_text": "12.3", - "old_value": 12.1, - "old_timestamp": "2026-03-20 10:04:59.000", - "value_changed": true + "value_text": "12.3" } } ], @@ -212,13 +169,9 @@ ### GET `/api/point/{point_id}/history` -获取点位最近历史样本。数据来自进程内存中的环形缓冲,不是持久化历史库。 +获取点位最近历史样本(进程内存环形缓冲,重启后清空)。 -查询参数: - -- `limit`:可选,默认 `120`,最大 `1000` - -响应示例: +查询参数:`limit`(可选,默认 `120`,最大 `1000`) ```json [ @@ -232,23 +185,17 @@ ] ``` -说明: - -- `value_number` 便于前端直接绘图 -- 非数值型点位时,`value_number` 可能为 `null` - ### PUT `/api/point/{point_id}` -更新点位元数据。 - -请求体: +更新点位元数据,字段均可选: ```json { "name": "Temperature", "description": "Room temperature", "unit": "°C", - "tag_id": "uuid" + "equipment_id": "uuid", + "signal_role": "run" } ``` @@ -256,24 +203,12 @@ 删除单个点位。 -响应: - -```json -{ - "ok_msg": "Point deleted successfully" -} -``` - ### POST `/api/point/batch` 根据节点批量创建点位。 -请求体: - ```json -{ - "node_ids": ["uuid1", "uuid2"] -} +{ "node_ids": ["uuid1", "uuid2"] } ``` 响应: @@ -291,32 +226,19 @@ 批量删除点位。 -请求体: - ```json -{ - "point_ids": ["uuid1", "uuid2"] -} +{ "point_ids": ["uuid1", "uuid2"] } ``` -响应: +### PUT `/api/point/batch/set-equipment` -```json -{ - "deleted_count": 2 -} -``` - -### PUT `/api/point/batch/set-tags` - -批量设置点位标签。 - -请求体: +批量设置点位的设备绑定和信号角色。 ```json { "point_ids": ["uuid1", "uuid2"], - "tag_id": "uuid" + "equipment_id": "uuid", + "signal_role": "run" } ``` @@ -324,36 +246,203 @@ 批量写点。 -请求头: - -- `X-Write-Key: ` - -请求体: +请求头:`X-Write-Key: ` ```json { "items": [ - { - "point_id": "uuid", - "value": 12.3 - } + { "point_id": "uuid", "value": 12.3 } ] } ``` -响应: +--- + +## Equipment + +### GET `/api/equipment` + +分页获取设备列表,包含每台设备绑定的点位数量。 + +查询参数:`page`、`page_size`(`-1` 全量)、`keyword`(可选,按 code/name 模糊搜索) + +响应示例: ```json { - "success": true, - "err_msg": null, - "success_count": 1, - "failed_count": 0, - "results": [ + "data": [ { - "point_id": "uuid", - "success": true, - "err_msg": null + "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" + } + ] } ] } @@ -361,183 +450,163 @@ --- -## Tag +## Event(系统事件) -### GET `/api/tag` +### GET `/api/event` -分页获取标签列表。 +分页获取系统控制事件记录。 查询参数: -- `page` -- `page_size` +- `unit_id`:可选,按控制单元过滤 +- `event_type`:可选,按事件类型过滤 +- `page`、`page_size` -### GET `/api/tag/{tag_id}` - -当前实现返回该标签下的点位列表。 - -### POST `/api/tag` - -创建标签。 - -请求体: +响应示例: ```json { - "name": "Area-A", - "description": "Area A points", - "point_ids": ["uuid1", "uuid2"] + "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 } ``` -### PUT `/api/tag/{tag_id}` - -更新标签。 - -请求体: - -```json -{ - "name": "Area-A", - "description": "Updated", - "point_ids": ["uuid1", "uuid2"] -} -``` - -### DELETE `/api/tag/{tag_id}` - -删除标签。 - -成功响应:`204 No Content` - --- -## Page +## Control(控制命令) -`page` 用于保存页面布局或组件映射数据。 +所有控制命令在执行前会校验:信号质量、REM 状态、FLT 状态、单元通讯锁、单元故障锁。 -### GET `/api/page` +### POST `/api/control/equipment/{equipment_id}/start` -查询页面列表。 +向设备发送启动脉冲命令(写 HIGH → 延迟 300ms → 写 LOW)。 -查询参数: - -- `name`:可选,按名称模糊搜索 - -### GET `/api/page/{page_id}` - -获取单个页面。 - -### POST `/api/page` - -创建页面。 - -请求体: +响应示例: ```json { - "name": "Dashboard", - "data": { - "widgetA": "uuid1", - "widgetB": "uuid2" - } + "ok_msg": "Equipment start command sent", + "equipment_id": "uuid", + "unit_id": "uuid", + "command_role": "start_cmd", + "command_point_id": "uuid", + "pulse_ms": 300 } ``` -### PUT `/api/page/{page_id}` +失败(设备未处于可启动状态)返回 `403 Forbidden`。 -更新页面。 +### POST `/api/control/equipment/{equipment_id}/stop` -请求体字段均可选: +向设备发送停止脉冲命令。响应结构同上。 + +### POST `/api/control/unit/{unit_id}/start-auto` + +启动指定控制单元的自动控制循环。单元须已启用(`enabled = true`)。 ```json -{ - "name": "Dashboard", - "data": { - "widgetA": "uuid1" - } -} +{ "ok_msg": "Auto control started", "unit_id": "uuid" } ``` -### DELETE `/api/page/{page_id}` +### POST `/api/control/unit/{unit_id}/stop-auto` -删除页面。 - -成功响应:`204 No Content` - ---- - -## Log - -### GET `/api/logs` - -读取日志文件内容。 - -查询参数: - -- `file`:可选,指定日志文件名,仅允许 `app.log*` -- `cursor`:可选,从指定游标后读取 -- `tail_lines`:可选,默认 `200` -- `max_bytes`:可选 - -响应: +停止自动控制循环。设备当前状态保持不变,不会自动停机。 ```json -{ - "file": "app.log", - "cursor": 1024, - "lines": ["..."], - "truncated": false, - "reset": false -} +{ "ok_msg": "Auto control stopped", "unit_id": "uuid" } ``` -### GET `/api/logs/stream` +### POST `/api/control/unit/{unit_id}/ack-fault` -SSE 实时日志流。 +人工确认故障,解除故障锁定。要求:`fault_locked = true` 且 `flt_active = false`(故障信号已消失)。 -事件类型: - -- `log` -- `error` - -客户端可使用 `EventSource` 订阅。 +```json +{ "ok_msg": "Fault acknowledged", "unit_id": "uuid" } +``` --- ## WebSocket -## 连接地址 +### 连接地址 - 公共广播:`/ws/public` - 客户端专属:`/ws/client/{client_id}` -## 服务端主动消息 +### 服务端主动推送消息 -### `PointNewValue` +#### `PointNewValue` + +点位实时值更新: ```json { "type": "PointNewValue", "data": { - "protocol": "opcua", - "source_id": "uuid", "point_id": "uuid", - "client_handle": 1001, - "scan_mode": "subscribe", "timestamp": "2026-03-20 10:05:00.000", "quality": "good", "value": 12.3, "value_type": "float", - "value_text": "12.3", - "old_value": 12.1, - "old_timestamp": "2026-03-20 10:04:59.000", - "value_changed": true + "value_text": "12.3" } } ``` -### `PointSetValueBatchResult` +#### `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 { @@ -552,30 +621,25 @@ SSE 实时日志流。 } ``` -## 客户端发送消息 +### 客户端发送消息 -### 写权限认证 +#### 写权限认证 ```json { "type": "auth_write", - "data": { - "key": "your-write-key" - } + "data": { "key": "your-write-key" } } ``` -### 批量写点 +#### 批量写点 ```json { "type": "point_set_value_batch", "data": { "items": [ - { - "point_id": "uuid", - "value": 12.3 - } + { "point_id": "uuid", "value": 12.3 } ] } } @@ -585,6 +649,7 @@ SSE 实时日志流。 ## 备注 -- 历史曲线接口当前使用内存缓存,服务重启后历史会清空。 -- 实时遥测与 WebSocket 推送是“最新值优先”的设计,在高压场景下允许丢弃部分中间消息。 -- `/api/tag/{tag_id}` 当前返回的是标签下点位,而不是标签自身详情。 +- 运行时状态(`/runtime`)存储在内存中,服务重启后重置。 +- 历史曲线数据(`/history`)同样是内存环形缓冲,重启后清空。 +- 控制单元时间配置字段(`run_time_sec` 等)单位为秒,运行时 elapsed 字段单位为毫秒。 +- 自动控制启动后,状态机以 500ms 为周期运行,实时状态通过 WebSocket `UnitRuntimeChanged` 推送。