591 lines
8.2 KiB
Markdown
591 lines
8.2 KiB
Markdown
# PLC Control 接口说明
|
||
|
||
本文档基于当前服务端路由与处理器代码整理,覆盖 HTTP API、SSE 日志流和 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"
|
||
}
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"ok_msg": "Source updated successfully"
|
||
}
|
||
```
|
||
|
||
### 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`
|
||
|
||
获取指定数据源的节点树。
|
||
|
||
响应字段:
|
||
|
||
- `id`
|
||
- `source_id`
|
||
- `external_id`
|
||
- `namespace_uri`
|
||
- `namespace_index`
|
||
- `identifier_type`
|
||
- `identifier`
|
||
- `browse_name`
|
||
- `display_name`
|
||
- `node_class`
|
||
- `parent_id`
|
||
- `children`
|
||
|
||
---
|
||
|
||
## Point
|
||
|
||
### GET `/api/point`
|
||
|
||
分页获取点位列表。
|
||
|
||
查询参数:
|
||
|
||
- `source_id`:可选,按数据源过滤
|
||
- `page`:页码
|
||
- `page_size`:每页条数
|
||
|
||
响应示例:
|
||
|
||
```json
|
||
{
|
||
"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_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
|
||
}
|
||
}
|
||
],
|
||
"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
|
||
}
|
||
]
|
||
```
|
||
|
||
说明:
|
||
|
||
- `value_number` 便于前端直接绘图
|
||
- 非数值型点位时,`value_number` 可能为 `null`
|
||
|
||
### PUT `/api/point/{point_id}`
|
||
|
||
更新点位元数据。
|
||
|
||
请求体:
|
||
|
||
```json
|
||
{
|
||
"name": "Temperature",
|
||
"description": "Room temperature",
|
||
"unit": "°C",
|
||
"tag_id": "uuid"
|
||
}
|
||
```
|
||
|
||
### DELETE `/api/point/{point_id}`
|
||
|
||
删除单个点位。
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"ok_msg": "Point deleted successfully"
|
||
}
|
||
```
|
||
|
||
### 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"]
|
||
}
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"deleted_count": 2
|
||
}
|
||
```
|
||
|
||
### PUT `/api/point/batch/set-tags`
|
||
|
||
批量设置点位标签。
|
||
|
||
请求体:
|
||
|
||
```json
|
||
{
|
||
"point_ids": ["uuid1", "uuid2"],
|
||
"tag_id": "uuid"
|
||
}
|
||
```
|
||
|
||
### POST `/api/point/value/batch`
|
||
|
||
批量写点。
|
||
|
||
请求头:
|
||
|
||
- `X-Write-Key: <key>`
|
||
|
||
请求体:
|
||
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"point_id": "uuid",
|
||
"value": 12.3
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"err_msg": null,
|
||
"success_count": 1,
|
||
"failed_count": 0,
|
||
"results": [
|
||
{
|
||
"point_id": "uuid",
|
||
"success": true,
|
||
"err_msg": null
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Tag
|
||
|
||
### GET `/api/tag`
|
||
|
||
分页获取标签列表。
|
||
|
||
查询参数:
|
||
|
||
- `page`
|
||
- `page_size`
|
||
|
||
### GET `/api/tag/{tag_id}`
|
||
|
||
当前实现返回该标签下的点位列表。
|
||
|
||
### POST `/api/tag`
|
||
|
||
创建标签。
|
||
|
||
请求体:
|
||
|
||
```json
|
||
{
|
||
"name": "Area-A",
|
||
"description": "Area A points",
|
||
"point_ids": ["uuid1", "uuid2"]
|
||
}
|
||
```
|
||
|
||
### PUT `/api/tag/{tag_id}`
|
||
|
||
更新标签。
|
||
|
||
请求体:
|
||
|
||
```json
|
||
{
|
||
"name": "Area-A",
|
||
"description": "Updated",
|
||
"point_ids": ["uuid1", "uuid2"]
|
||
}
|
||
```
|
||
|
||
### DELETE `/api/tag/{tag_id}`
|
||
|
||
删除标签。
|
||
|
||
成功响应:`204 No Content`
|
||
|
||
---
|
||
|
||
## Page
|
||
|
||
`page` 用于保存页面布局或组件映射数据。
|
||
|
||
### GET `/api/page`
|
||
|
||
查询页面列表。
|
||
|
||
查询参数:
|
||
|
||
- `name`:可选,按名称模糊搜索
|
||
|
||
### GET `/api/page/{page_id}`
|
||
|
||
获取单个页面。
|
||
|
||
### POST `/api/page`
|
||
|
||
创建页面。
|
||
|
||
请求体:
|
||
|
||
```json
|
||
{
|
||
"name": "Dashboard",
|
||
"data": {
|
||
"widgetA": "uuid1",
|
||
"widgetB": "uuid2"
|
||
}
|
||
}
|
||
```
|
||
|
||
### PUT `/api/page/{page_id}`
|
||
|
||
更新页面。
|
||
|
||
请求体字段均可选:
|
||
|
||
```json
|
||
{
|
||
"name": "Dashboard",
|
||
"data": {
|
||
"widgetA": "uuid1"
|
||
}
|
||
}
|
||
```
|
||
|
||
### DELETE `/api/page/{page_id}`
|
||
|
||
删除页面。
|
||
|
||
成功响应:`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
|
||
}
|
||
```
|
||
|
||
### GET `/api/logs/stream`
|
||
|
||
SSE 实时日志流。
|
||
|
||
事件类型:
|
||
|
||
- `log`
|
||
- `error`
|
||
|
||
客户端可使用 `EventSource` 订阅。
|
||
|
||
---
|
||
|
||
## WebSocket
|
||
|
||
## 连接地址
|
||
|
||
- 公共广播:`/ws/public`
|
||
- 客户端专属:`/ws/client/{client_id}`
|
||
|
||
## 服务端主动消息
|
||
|
||
### `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
|
||
}
|
||
}
|
||
```
|
||
|
||
### `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
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 备注
|
||
|
||
- 历史曲线接口当前使用内存缓存,服务重启后历史会清空。
|
||
- 实时遥测与 WebSocket 推送是“最新值优先”的设计,在高压场景下允许丢弃部分中间消息。
|
||
- `/api/tag/{tag_id}` 当前返回的是标签下点位,而不是标签自身详情。
|