mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-04-27 10:30:02 +00:00
feat: Add new developer guides for old devices and PLC framework integration
This commit is contained in:
@@ -18,13 +18,15 @@ Uni-Lab 开发团队在仓库中提供了 3 个样例:
|
||||
|
||||
- 单一机械设备**电夹爪**,通讯协议可见 [增广夹爪通讯协议](https://doc.rmaxis.com/docs/communication/fieldbus/),驱动代码位于 `unilabos/devices/gripper/rmaxis_v4.py`
|
||||
- 单一通信设备**IO 板卡**,驱动代码位于 `unilabos/device_comms/gripper/SRND_16_IO.py`
|
||||
- 执行多设备复杂任务逻辑的**PLC**,Uni-Lab 提供了基于地址表的接入方式和点动工作流编写,测试代码位于 `unilabos/device_comms/modbus_plc/test/test_workflow.py`
|
||||
- 执行多设备复杂任务逻辑的**PLC**,Uni-Lab 提供了基于地址表的接入方式和点动工作流编写,测试代码位于 `unilabos/device_comms/modbus_plc/test/test_workflow.py`。详细框架说明请参考 {doc}`plc_framework`
|
||||
|
||||
---
|
||||
|
||||
## 其他工业通信协议:CANopen, Ethernet, OPCUA...
|
||||
|
||||
【敬请期待】
|
||||
Uni-Lab 已实现基于 OPC UA 协议的 PLC 接管框架,用于后处理工站等项目。与 Modbus 框架相比,OPC UA 框架额外提供了自动节点发现、订阅推送、断线重连等特性。详细说明请参考 {doc}`plc_framework`。
|
||||
|
||||
其他协议(CANopen、EtherCAT 等)【敬请期待】
|
||||
|
||||
## 没有接口的老设备老软件:使用 PyWinAuto
|
||||
|
||||
|
||||
281
docs/developer_guide/plc_framework.md
Normal file
281
docs/developer_guide/plc_framework.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# PLC 设备接管框架
|
||||
|
||||
> 本文档面向初次接触 UniLab-OS 的开发者,介绍系统如何通过工业协议"接管"(连接并控制)PLC 设备。
|
||||
|
||||
## 什么是"PLC 接管"?
|
||||
|
||||
**PLC**(可编程逻辑控制器)是工厂设备的控制核心,驱动机械臂、泵、阀门等硬件。UniLab-OS 通过网络协议直接读写 PLC 内部变量,从而控制设备运行:
|
||||
|
||||
```
|
||||
UniLab-OS(Python) ←通信协议→ PLC ←电信号→ 电机/气缸/传感器
|
||||
```
|
||||
|
||||
UniLab-OS 提供两套接管框架,对应两种工业协议:
|
||||
|
||||
| 框架 | 协议 | 应用项目 | 核心文件 |
|
||||
| --------------- | ---------------- | ------------------ | ----------------------------------------------------------- |
|
||||
| **Modbus 框架** | Modbus TCP / RTU | 扣式电池装配工站 | `unilabos/device_comms/modbus_plc/client.py` |
|
||||
| **OPC UA 框架** | OPC UA | 后处理工站(怀柔) | `unilabos/devices/workstation/post_process/post_process.py` |
|
||||
|
||||
两套框架**设计思想完全一致**,底层通信协议不同。理解一个,另一个基本触类旁通。
|
||||
|
||||
---
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 节点(Node)
|
||||
|
||||
节点是 PLC 内部一个具体的变量地址,可以理解为 PLC 的一个输入/输出端口。
|
||||
|
||||
| 属性 | 说明 | 示例 |
|
||||
| ---- | -------------------------------------- | -------------------- |
|
||||
| 名称 | 人类可读标识 | `COIL_SYS_START_CMD` |
|
||||
| 地址 | PLC 内存地址 | `0x0064` |
|
||||
| 类型 | Coil / HoldRegister / InputRegister 等 | `coil` |
|
||||
|
||||
```
|
||||
PLC 内存空间
|
||||
├── Coil 区: True / False ← 控制开关量(启动/停止/复位)
|
||||
├── Hold Reg: 120, 35.5 … ← 存参数值(速度、位置)
|
||||
└── Input Reg: 99.8, 42 … ← 只读传感器数据
|
||||
```
|
||||
|
||||
### 动作生命周期(Action Lifecycle)
|
||||
|
||||
每个设备动作被拆分为 4 个阶段,用 `try/finally` 保证安全性:
|
||||
|
||||
```python
|
||||
try:
|
||||
init(...) # 写入参数(速度、位置等)— 可选
|
||||
start(...) # 发触发信号 + 轮询等待完成
|
||||
stop(...) # 复位触发信号(成功时执行)
|
||||
except:
|
||||
is_err = True
|
||||
finally:
|
||||
cleanup(...) # 无论成败都执行,作为安全兜底
|
||||
```
|
||||
|
||||
| 阶段 | 何时执行 | 典型内容 |
|
||||
| --------- | ----------------------- | ------------------------------------ |
|
||||
| `init` | 成功路径(可选) | 写运动速度 = 20.0 |
|
||||
| `start` | 成功路径 | 写启动位 = True,等待完成位 = True |
|
||||
| `stop` | 成功路径 | 写启动位 = False(正常复位) |
|
||||
| `cleanup` | **无论成败**(finally) | 安全兜底复位,防止异常时设备持续运动 |
|
||||
|
||||
> **为什么 `cleanup` 无论成败都执行?**
|
||||
> 若 `start` 阶段因传感器故障抛出异常,`stop` 会被跳过,PLC 触发位仍为 `True`——设备可能持续运动。`cleanup` 放在 `finally` 块中,作为最后的安全保障,确保 PLC 一定被复位到安全状态。实际上大多数动作将 `cleanup` 设为 `null`,由 `stop` 负责正常复位即可。
|
||||
|
||||
---
|
||||
|
||||
## Modbus 框架
|
||||
|
||||
**核心文件**:`unilabos/device_comms/modbus_plc/client.py`
|
||||
**参考实现**:`unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py`
|
||||
|
||||
### 连接与节点注册
|
||||
|
||||
```python
|
||||
from unilabos.device_comms.modbus_plc.client import TCPClient, BaseClient
|
||||
|
||||
# 1. 建立 TCP 连接
|
||||
client = TCPClient(addr="172.16.28.102", port=502)
|
||||
client.client.connect()
|
||||
|
||||
# 2. 从 CSV 加载节点定义
|
||||
nodes = BaseClient.load_csv("coin_cell_assembly_b.csv")
|
||||
|
||||
# 3. 注册节点,之后可按名称访问
|
||||
client.register_node_list(nodes)
|
||||
|
||||
# 访问节点
|
||||
client.use_node('COIL_SYS_START_CMD').write(True)
|
||||
value, err = client.use_node('COIL_SYS_START_STATUS').read(1)
|
||||
```
|
||||
|
||||
**CSV 格式**(`Name` / `DeviceType` / `Address` / `DataType`):
|
||||
|
||||
| Name | DeviceType | Address | DataType |
|
||||
| ------------------ | ------------- | ------- | -------- |
|
||||
| COIL_SYS_START_CMD | coil | 100 | INT16 |
|
||||
| REG_SPEED | hold_register | 200 | FLOAT32 |
|
||||
|
||||
### 三段式接管流程(扣式电池工站)
|
||||
|
||||
PLC 设备通常需要按固定顺序切换模式,以扣式电池工站为例:
|
||||
|
||||
```
|
||||
Python PLC
|
||||
│── 写 HAND_CMD = True ─────────>│ 切换到手动模式
|
||||
│<─ 读 HAND_STATUS = True ────────│ 确认进入手动
|
||||
│── 写 INIT_CMD = True ──────────>│ 执行初始化
|
||||
│<─ 读 INIT_STATUS = True ─────────│ 初始化完成
|
||||
│── 写 HAND_CMD = False ──────────>│ 复位(脉冲信号)
|
||||
│── 写 INIT_CMD = False ──────────>│ 复位
|
||||
│── 写 AUTO_CMD = True ───────────>│ 切换自动模式
|
||||
│<─ 读 AUTO_STATUS = True ─────────│ 自动模式就绪
|
||||
│── 写 AUTO_CMD = False ──────────>│ 复位
|
||||
│── 写 START_CMD = True ──────────>│ 开始运行
|
||||
│<─ 读 START_STATUS = True ────────│ 运行确认
|
||||
│── 写 START_CMD = False ──────────>│ 复位
|
||||
```
|
||||
|
||||
> **脉冲信号模式**:命令写 `True` → 等待 PLC 状态位确认 → 命令写回 `False`,这是大多数 PLC 的标准触发方式,而不是保持高电平。
|
||||
|
||||
### JSON 配置方式
|
||||
|
||||
Modbus 框架支持纯 JSON 配置,无需手写 Python 流程:
|
||||
|
||||
```json
|
||||
{
|
||||
"register_node_list_from_csv_path": {"path": "M01.csv"},
|
||||
"create_flow": [
|
||||
{
|
||||
"name": "归位",
|
||||
"action": [{
|
||||
"address_function_to_create": [
|
||||
{"func_name": "pos_tip", "node_name": "M01_idlepos_coil_w", "mode": "write", "value": true},
|
||||
{"func_name": "pos_tip_read", "node_name": "M01_idlepos_coil_r", "mode": "read", "value": 1},
|
||||
{"func_name": "manual_stop", "node_name": "M01_manual_stop_coil_r","mode": "read", "value": 1}
|
||||
],
|
||||
"create_init_function": {"func_name": "idel_init", "node_name": "M01_idlepos_velocity_rw", "mode": "write", "value": 20.0},
|
||||
"create_start_function": {
|
||||
"func_name": "idel_position",
|
||||
"write_functions": ["pos_tip"],
|
||||
"condition_functions": ["pos_tip_read", "manual_stop"],
|
||||
"stop_condition_expression": "pos_tip_read[0] and manual_stop[0]"
|
||||
},
|
||||
"create_stop_function": {"func_name": "idel_stop", "node_name": "M01_idlepos_coil_w", "mode": "write", "value": false},
|
||||
"create_cleanup_function": null
|
||||
}]
|
||||
}
|
||||
],
|
||||
"execute_flow": ["归位"]
|
||||
}
|
||||
```
|
||||
|
||||
执行:
|
||||
|
||||
```python
|
||||
client.execute_procedure_from_json(json_data)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## OPC UA 框架
|
||||
|
||||
**核心文件**:`unilabos/devices/workstation/post_process/post_process.py`
|
||||
**参考实现**:`unilabos/devices/workstation/post_process/opcua_huairou.json`
|
||||
|
||||
### 与 Modbus 的主要区别
|
||||
|
||||
| 特性 | Modbus | OPC UA |
|
||||
| ---------- | -------------------- | --------------------------------- |
|
||||
| 节点发现 | 手动填写 CSV 地址 | **自动遍历**服务器节点树 |
|
||||
| 数据获取 | 轮询(主动问) | **订阅推送**(有变化时通知) |
|
||||
| 节点标识 | 数字地址(如 `100`) | 字符串 NodeId(如 `ns=2;s=速度`) |
|
||||
| 断线处理 | 无 | **后台监控线程**自动重连 |
|
||||
| 认证安全 | 无 | 支持用户名/密码 |
|
||||
| 工作流调用 | 手动调用 | **自动注册为实例方法** |
|
||||
|
||||
### 连接与节点发现
|
||||
|
||||
```python
|
||||
from unilabos.devices.workstation.post_process.post_process import OpcUaClient
|
||||
|
||||
client = OpcUaClient(
|
||||
url="opc.tcp://192.168.1.100:4840",
|
||||
username="admin", # 可选
|
||||
password="123456", # 可选
|
||||
config_path="opcua_huairou.json", # 自动加载工作流配置
|
||||
cache_timeout=5.0, # 节点值缓存 5 秒
|
||||
subscription_interval=500, # 每 500ms 接收推送
|
||||
)
|
||||
|
||||
# 节点自动通过订阅保持最新值,读取直接查本地缓存
|
||||
value = client.get_node_value("grab_complete")
|
||||
```
|
||||
|
||||
### JSON 配置方式
|
||||
|
||||
```json
|
||||
{
|
||||
"register_node_list_from_csv_path": {"path": "opcua_nodes_huairou.csv"},
|
||||
"create_flow": [
|
||||
{
|
||||
"name": "trigger_grab_action",
|
||||
"description": "触发反应罐及原料罐抓取动作",
|
||||
"parameters": ["reaction_tank_number", "raw_tank_number"],
|
||||
"action": [{
|
||||
"init_function": {
|
||||
"func_name": "init_grab_params",
|
||||
"write_nodes": ["reaction_tank_number", "raw_tank_number"]
|
||||
},
|
||||
"start_function": {
|
||||
"func_name": "start_grab",
|
||||
"write_nodes": {"grab_trigger": true},
|
||||
"condition_nodes": ["grab_complete"],
|
||||
"stop_condition_expression": "grab_complete == True",
|
||||
"timeout_seconds": 999999.0
|
||||
},
|
||||
"stop_function": {
|
||||
"func_name": "stop_grab",
|
||||
"write_nodes": {"grab_trigger": false}
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
配置加载后,工作流自动注册为实例方法:
|
||||
|
||||
```python
|
||||
# 直接调用,传入参数,框架自动写入对应节点
|
||||
client.trigger_grab_action(reaction_tank_number=2, raw_tank_number=3)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 新增设备快速上手
|
||||
|
||||
### 使用 Modbus 框架
|
||||
|
||||
```
|
||||
1. 从 PLC 工程师处拿到地址表,按格式填写 CSV(Name/DeviceType/Address/DataType)
|
||||
2. 继承 BaseClient,在 __init__ 中连接并注册节点
|
||||
3. 参考 coin_cell_assembly.py 编写三段式接管函数(手动→初始化→自动→启动)
|
||||
4. 或直接编写 JSON 配置,调用 execute_procedure_from_json()
|
||||
```
|
||||
|
||||
### 使用 OPC UA 框架
|
||||
|
||||
```
|
||||
1. 确认设备支持 OPC UA,拿到服务器 URL(格式:opc.tcp://IP:PORT)
|
||||
2. 准备 CSV 节点定义文件(可选,也可让框架自动发现)
|
||||
3. 编写 JSON 配置:定义 parameters、init/start/stop 函数
|
||||
4. 实例化 OpcUaClient,传入 config_path,直接调用自动注册的工作流方法
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
**Q: `node {name} is not registered` 报错?**
|
||||
A: 节点名不在 CSV 或未调用 `register_node_list_from_csv_path()`。
|
||||
|
||||
**Q: 程序卡死在 `while not status(): sleep(1)`?**
|
||||
A: PLC 未返回预期完成信号。检查:PLC 是否在正确运行模式、状态位地址是否正确、PLC 有无报警。
|
||||
|
||||
**Q: OPC UA 连接成功但读不到节点?**
|
||||
A: 检查节点名称是否与服务器显示名一致(区分中英文)。可调用 `_find_nodes()` 打印服务器全部节点。
|
||||
|
||||
**Q: 应该选 Modbus 还是 OPC UA?**
|
||||
A: 取决于设备支持的协议,由 PLC 工程师决定。OPC UA 功能更完整,条件允许优先选择。
|
||||
|
||||
---
|
||||
|
||||
## 下一步
|
||||
|
||||
- {doc}`add_device` - 将驱动集成进 UniLab-OS 设备节点
|
||||
- {doc}`add_action` - 为设备添加可调度的动作指令
|
||||
- {doc}`add_yaml` - 编写设备注册表 YAML 文件
|
||||
@@ -17,6 +17,9 @@ developer_guide/http_api.md
|
||||
developer_guide/networking_overview.md
|
||||
developer_guide/add_device.md
|
||||
developer_guide/add_action.md
|
||||
developer_guide/add_old_device.md
|
||||
developer_guide/plc_framework.md
|
||||
developer_guide/add_protocol.md
|
||||
developer_guide/add_registry.md
|
||||
developer_guide/add_yaml.md
|
||||
developer_guide/action_includes.md
|
||||
|
||||
Reference in New Issue
Block a user