docs(api): rewrite API.md to cover all current endpoints

Add Equipment, Unit, Event, Control sections. Update Point (equipment_id
filter, signal_role in PUT). Add EventCreated and UnitRuntimeChanged to
WebSocket. Remove stale SSE log stream section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-03-25 08:50:47 +08:00
parent 2732238be7
commit c2ed1e70fb
1 changed files with 322 additions and 257 deletions

579
API.md
View File

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