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

571
API.md
View File

@ -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": [
{
"point": {
"id": "uuid",
"node_id": "uuid",
"name": "Temperature",
"description": null,
"unit": null,
"tag_id": null,
"equipment_id": "uuid",
"signal_role": "run",
"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": {
"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: <key>`
请求体:
请求头:`X-Write-Key: <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` 推送。