# 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: ` 请求体: ```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}` 当前返回的是标签下点位,而不是标签自身详情。