plc_control/API.md

869 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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: <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` 推送。