mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 21:35:09 +00:00
opcua devices
opcua devices
This commit is contained in:
17
test/experiments/huairou.json
Normal file
17
test/experiments/huairou.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "huairou",
|
||||
"name": "huairou",
|
||||
"children": [
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "huairou",
|
||||
"config": {
|
||||
},
|
||||
"data": {
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
757
unilabos/devices/huairou/opcua_client.py
Normal file
757
unilabos/devices/huairou/opcua_client.py
Normal file
@@ -0,0 +1,757 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
OPC-UA客户端程序
|
||||
用于连接和控制制药/化工设备
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from asyncua import Client, ua
|
||||
from asyncua.common.node import Node
|
||||
import pandas as pd
|
||||
from typing import Dict, Any, Optional
|
||||
import json
|
||||
# 设置日志
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class OPCUADeviceClient:
|
||||
"""OPC-UA设备客户端类"""
|
||||
|
||||
def __init__(self, server_ip: str = "192.168.1.88", server_port: int = 4840):
|
||||
"""
|
||||
初始化客户端
|
||||
:param server_ip: 服务器IP地址
|
||||
:param server_port: 服务器端口号
|
||||
"""
|
||||
self.server_url = f"opc.tcp://{server_ip}:{server_port}"
|
||||
self.client = Client(self.server_url)
|
||||
self.connected = False
|
||||
|
||||
# 设备变量映射表
|
||||
# self.variables = {
|
||||
# "原料罐号码": {"type": "INT", "initial": 0, "node_id": None},
|
||||
# "反应罐号码": {"type": "INT", "initial": 0, "node_id": None},
|
||||
# "原料罐抓取触发": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
# "反应罐抓取触发": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
# "后处理动作触发": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
# "搅拌桨雾化快速": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
# "搅拌桨洗涤慢速": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
# "注射泵抽液速度": {"type": "STRING", "initial": "", "node_id": None},
|
||||
# "注射泵推液速度": {"type": "STRING", "initial": "", "node_id": None},
|
||||
# "抽原液次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
# "洗涤加水量": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
# "最开始加水量": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
# "雾化压力百分比": {"type": "INT", "initial": 0, "node_id": None},
|
||||
# "吸液针清洗触发": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
# "搅拌桨清洗触发": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
# "管路吹气触发": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
# "废液桶满报警": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
# "清水桶空报警": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
# "NMP桶空报警": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
# "丙酮桶空报警": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
# "门开报警": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
# }
|
||||
|
||||
self.variables = {
|
||||
"原料罐号码": {"type": "INT", "initial": 0,"node_id": None},
|
||||
"反应罐号码": {"type": "INT", "initial": 0,"node_id": None},
|
||||
"反应罐及原料罐抓取触发": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"后处理动作触发": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"搅拌浆雾化快速": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"搅拌浆洗涤慢速": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"注射泵抽液速度": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"注射泵推液速度": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"抽原液次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"第1次洗涤加水量": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"第2次洗涤加水量": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"第1次粉末搅拌时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"第2次粉末搅拌时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"第1次粉末洗涤次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"第2次粉末洗涤次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"最开始加水量": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"抽滤前搅拌时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"雾化压力Kpa": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"清洗及管路吹气触发": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"废液桶满报警": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"清水桶空报警": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"NMP桶空报警": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"丙酮桶空报警": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"门开报警": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"反应罐及原料罐抓取完成PLCtoPC": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"后处理动作完成PLCtoPC": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"清洗及管路吹气完成PLCtoPC": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"远程模式PLCtoPC": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"设备准备就绪PLCtoPC": {"type": "BOOL", "initial": False, "node_id": None},
|
||||
"NMP外壁清洗加注": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"NMP外壁清洗次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"NMP外壁清洗等待时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"NMP外壁清洗抽废时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"NMP内壁清洗加注": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"NMP内壁清洗次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"NMP泵清洗抽次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"NMP内壁清洗抽废时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"NMP搅拌桨清洗加注": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"NMP搅拌桨清洗次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"NMP搅拌桨清洗等待时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"NMP搅拌桨清洗抽废时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"清水外壁清洗加注": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"清水外壁清洗次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"清水外壁清洗等待时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"清水外壁清洗抽废时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"清水内壁清洗加注": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"清水内壁清洗次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"清水泵清洗抽次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"清水内壁清洗抽废时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"清水搅拌桨清洗加注": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"清水搅拌桨清洗次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"清水搅拌桨清洗等待时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"清水搅拌桨清洗抽废时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"丙酮外壁清洗加注": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"丙酮外壁清洗次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"丙酮外壁清洗等待时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"丙酮外壁清洗抽废时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"丙酮内壁清洗加注": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"丙酮内壁清洗次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"丙酮泵清洗抽次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"丙酮内壁清洗抽废时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"丙酮搅拌桨清洗加注": {"type": "REAL", "initial": 0.0, "node_id": None},
|
||||
"丙酮搅拌桨清洗次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"丙酮搅拌桨清洗等待时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"丙酮搅拌桨清洗抽废时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"管道吹气时间": {"type": "DINT", "initial": 0, "node_id": None},
|
||||
"注射泵正向空抽次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
"注射泵反向空抽次数": {"type": "INT", "initial": 0, "node_id": None},
|
||||
}
|
||||
|
||||
|
||||
@property
|
||||
def get_variables(self):
|
||||
return self.variables
|
||||
|
||||
async def connect(self):
|
||||
"""连接到OPC-UA服务器"""
|
||||
try:
|
||||
await self.client.connect()
|
||||
self.connected = True
|
||||
logger.info(f"已成功连接到OPC-UA服务器: {self.server_url}")
|
||||
|
||||
# 获取根节点
|
||||
root = self.client.get_root_node()
|
||||
objects = await root.get_child("0:Objects")
|
||||
|
||||
# 尝试查找设备节点 (通常在Objects下)
|
||||
await self._find_device_nodes(objects)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"连接失败: {e}")
|
||||
self.connected = False
|
||||
raise
|
||||
|
||||
async def _find_device_nodes(self, objects_node):
|
||||
"""查找设备节点"""
|
||||
try:
|
||||
# 获取Objects下的所有子节点
|
||||
children = await objects_node.get_children()
|
||||
|
||||
for child in children:
|
||||
browse_name = await child.read_browse_name()
|
||||
logger.info(f"发现节点: {browse_name}")
|
||||
|
||||
# 尝试在子节点中查找变量
|
||||
await self._search_variables_in_node(child)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"查找设备节点时出错: {e}")
|
||||
|
||||
async def _search_variables_in_node(self, node):
|
||||
"""在指定节点中搜索变量"""
|
||||
try:
|
||||
children = await node.get_children()
|
||||
for child in children:
|
||||
browse_name = await child.read_browse_name()
|
||||
variable_name = browse_name.Name
|
||||
|
||||
# 检查是否是我们需要的变量
|
||||
if variable_name in self.variables:
|
||||
self.variables[variable_name]["node_id"] = child
|
||||
logger.info(f"找到变量: {variable_name}")
|
||||
|
||||
# 递归搜索子节点
|
||||
try:
|
||||
await self._search_variables_in_node(child)
|
||||
except:
|
||||
pass # 如果节点没有子节点,忽略错误
|
||||
|
||||
except Exception as e:
|
||||
pass # 忽略搜索过程中的错误
|
||||
|
||||
async def disconnect(self):
|
||||
"""断开连接"""
|
||||
if self.connected:
|
||||
await self.client.disconnect()
|
||||
self.connected = False
|
||||
logger.info("已断开与OPC-UA服务器的连接")
|
||||
|
||||
async def read_variable(self, variable_name: str) -> Any:
|
||||
"""读取指定变量的值"""
|
||||
if not self.connected:
|
||||
raise ConnectionError("未连接到OPC-UA服务器")
|
||||
|
||||
if variable_name not in self.variables:
|
||||
raise ValueError(f"未知变量: {variable_name}")
|
||||
|
||||
node = self.variables[variable_name]["node_id"]
|
||||
if node is None:
|
||||
raise ValueError(f"未找到变量节点: {variable_name}")
|
||||
|
||||
try:
|
||||
value = await node.read_value()
|
||||
logger.info(f"读取变量 {variable_name}: {value}")
|
||||
return value
|
||||
except Exception as e:
|
||||
logger.error(f"读取变量 {variable_name} 失败: {e}")
|
||||
raise
|
||||
|
||||
async def write_variable(self, command: str):
|
||||
# """
|
||||
# 写入指定变量的值
|
||||
# Args:
|
||||
# command: 一个JSON格式的字符串,包含变量名和值
|
||||
# variable_name (str): 要写入的变量名
|
||||
# value (Any): 要写入的值
|
||||
# """
|
||||
# 将JSON字符串转换为字典
|
||||
cmd_str = command.replace("'", '"')
|
||||
cmd_dict = json.loads(cmd_str)
|
||||
|
||||
# 提取变量名和值
|
||||
variable_name = cmd_dict["variable_name"]
|
||||
value = cmd_dict["value"]
|
||||
|
||||
if not self.connected:
|
||||
raise ConnectionError("未连接到OPC-UA服务器")
|
||||
|
||||
if variable_name not in self.variables:
|
||||
raise ValueError(f"未知变量: {variable_name}")
|
||||
|
||||
node = self.variables[variable_name]["node_id"]
|
||||
if node is None:
|
||||
raise ValueError(f"未找到变量节点: {variable_name}")
|
||||
|
||||
try:
|
||||
var_type = self.variables[variable_name]["type"]
|
||||
|
||||
# 对于INT类型,直接尝试INT16
|
||||
if var_type == "INT":
|
||||
int_value = int(value)
|
||||
if -32768 <= int_value <= 32767:
|
||||
variant = ua.Variant(int_value, ua.VariantType.Int16)
|
||||
await node.write_value(variant)
|
||||
logger.info(f"写入变量 {variable_name}: {int_value} (Int16)")
|
||||
return
|
||||
else:
|
||||
raise ValueError(f"值 {int_value} 超出 Int16 范围 (-32768 到 32767)")
|
||||
|
||||
# 对于BOOL类型,直接使用Boolean
|
||||
elif var_type == "BOOL":
|
||||
bool_value = bool(value)
|
||||
variant = ua.Variant(bool_value, ua.VariantType.Boolean)
|
||||
await node.write_value(variant)
|
||||
logger.info(f"写入变量 {variable_name}: {bool_value} (Boolean)")
|
||||
return
|
||||
|
||||
# 对于其他类型,使用原有逻辑
|
||||
else:
|
||||
# 首先读取节点的当前值来确定正确的数据类型
|
||||
current_value = await node.read_value()
|
||||
current_variant = await node.read_data_value()
|
||||
|
||||
# 根据服务器的实际数据类型转换值
|
||||
converted_value = self._convert_value_with_variant_type(value, var_type, current_variant.Value.VariantType)
|
||||
|
||||
# 创建具有正确VariantType的DataValue
|
||||
data_value = ua.DataValue(ua.Variant(converted_value, current_variant.Value.VariantType))
|
||||
await node.write_value(data_value.Value)
|
||||
|
||||
logger.info(f"写入变量 {variable_name}: {converted_value} (类型: {current_variant.Value.VariantType})")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"写入变量 {variable_name} 失败: {e}")
|
||||
raise
|
||||
|
||||
def _convert_value_with_variant_type(self, value: Any, var_type: str, variant_type) -> Any:
|
||||
"""根据变量类型和OPC-UA VariantType转换值"""
|
||||
try:
|
||||
if var_type == "INT":
|
||||
# 确保值在INT16范围内 (-32768 到 32767)
|
||||
int_value = int(value)
|
||||
if variant_type == ua.VariantType.Int16:
|
||||
if -32768 <= int_value <= 32767:
|
||||
return int_value
|
||||
else:
|
||||
raise ValueError(f"值 {int_value} 超出 Int16 范围 (-32768 到 32767)")
|
||||
elif variant_type == ua.VariantType.UInt16:
|
||||
if 0 <= int_value <= 65535:
|
||||
return int_value
|
||||
else:
|
||||
raise ValueError(f"值 {int_value} 超出 UInt16 范围 (0 到 65535)")
|
||||
elif variant_type in [ua.VariantType.Int32, ua.VariantType.UInt32]:
|
||||
return int_value
|
||||
elif variant_type in [ua.VariantType.Int64, ua.VariantType.UInt64]:
|
||||
return int_value
|
||||
else:
|
||||
return int_value
|
||||
elif var_type == "BOOL":
|
||||
return bool(value)
|
||||
elif var_type == "REAL":
|
||||
# 根据实际的VariantType选择浮点类型
|
||||
if variant_type == ua.VariantType.Float:
|
||||
return float(value)
|
||||
elif variant_type == ua.VariantType.Double:
|
||||
return float(value)
|
||||
else:
|
||||
return float(value)
|
||||
elif var_type == "STRING":
|
||||
return str(value)
|
||||
else:
|
||||
return value
|
||||
except Exception as e:
|
||||
logger.warning(f"类型转换失败,使用原始值: {e}")
|
||||
return value
|
||||
|
||||
def _convert_value(self, value: Any, var_type: str) -> Any:
|
||||
"""根据变量类型转换值(简化版本)"""
|
||||
if var_type == "INT":
|
||||
return int(value)
|
||||
elif var_type == "BOOL":
|
||||
return bool(value)
|
||||
elif var_type == "REAL":
|
||||
return float(value)
|
||||
elif var_type == "STRING":
|
||||
return str(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
async def read_all_variables(self) -> Dict[str, Any]:
|
||||
"""读取所有变量的值"""
|
||||
results = {}
|
||||
for variable_name in self.variables.keys():
|
||||
try:
|
||||
value = await self.read_variable(variable_name)
|
||||
results[variable_name] = value
|
||||
except Exception as e:
|
||||
logger.warning(f"读取变量 {variable_name} 失败: {e}")
|
||||
results[variable_name] = None
|
||||
results = str(results)
|
||||
return results
|
||||
|
||||
async def write_multiple_variables(self, command: str):
|
||||
"""批量写入多个变量
|
||||
Args:
|
||||
command: 一个JSON格式的字符串,包含变量名和值的字典
|
||||
variables (dict): 变量名和值的字典,格式为 {"变量名1": 值1, "变量名2": 值2, ...}
|
||||
"""
|
||||
# 将JSON字符串转换为字典
|
||||
cmd_str = command.replace("'", '"')
|
||||
cmd_dict = json.loads(cmd_str)
|
||||
|
||||
# 提取变量字典
|
||||
variables_dict = cmd_dict["variables"]
|
||||
|
||||
for variable_name, value in variables_dict.items():
|
||||
try:
|
||||
# 构建单个变量的写入命令
|
||||
single_cmd = json.dumps({"variable_name": variable_name, "value": value})
|
||||
await self.write_variable(single_cmd)
|
||||
except Exception as e:
|
||||
logger.error(f"写入变量 {variable_name} 失败: {e}")
|
||||
|
||||
# 设备控制的便捷方法
|
||||
async def set_tank_numbers(self, command: str):
|
||||
"""设置原料罐和反应罐号码
|
||||
Args:
|
||||
command: 一个JSON格式的字符串,包含罐号码配置
|
||||
raw_material_tank (int): 原料罐号码
|
||||
reaction_tank (int): 反应罐号码
|
||||
safe_mode (bool): 是否使用安全模式写入(默认为false)
|
||||
"""
|
||||
# 将JSON字符串转换为字典
|
||||
cmd_str = command.replace("'", '"')
|
||||
cmd_dict = json.loads(cmd_str)
|
||||
|
||||
# 提取参数
|
||||
raw_material_tank = cmd_dict["raw_material_tank"]
|
||||
reaction_tank = cmd_dict["reaction_tank"]
|
||||
safe_mode = cmd_dict.get("safe_mode", False)
|
||||
|
||||
if safe_mode:
|
||||
# 构建安全写入变量的命令
|
||||
raw_cmd = json.dumps({"variable_name": "原料罐号码", "value": raw_material_tank})
|
||||
reaction_cmd = json.dumps({"variable_name": "反应罐号码", "value": reaction_tank})
|
||||
|
||||
await self.write_variable_safe(raw_cmd)
|
||||
await self.write_variable_safe(reaction_cmd)
|
||||
else:
|
||||
# 构建批量写入的命令
|
||||
variables = {
|
||||
"原料罐号码": raw_material_tank,
|
||||
"反应罐号码": reaction_tank
|
||||
}
|
||||
variables_cmd = json.dumps({"variables": variables})
|
||||
|
||||
await self.write_multiple_variables(variables_cmd)
|
||||
|
||||
async def trigger_grab_actions(self, command: str):
|
||||
"""触发抓取动作
|
||||
Args:
|
||||
command: 一个JSON格式的字符串,包含抓取动作的配置
|
||||
raw_material (bool): 是否触发原料罐抓取动作
|
||||
reaction (bool): 是否触发反应罐抓取动作
|
||||
"""
|
||||
# 将JSON字符串转换为字典
|
||||
cmd_str = command.replace("'", '"')
|
||||
cmd_dict = json.loads(cmd_str)
|
||||
|
||||
# 提取参数
|
||||
raw_material = cmd_dict.get("raw_material", False)
|
||||
reaction = cmd_dict.get("reaction", False)
|
||||
|
||||
variables = {}
|
||||
if raw_material:
|
||||
variables["原料罐抓取触发"] = True
|
||||
if reaction:
|
||||
variables["反应罐抓取触发"] = True
|
||||
|
||||
if variables:
|
||||
# 构建写入多个变量的命令
|
||||
variables_cmd = json.dumps({"variables": variables})
|
||||
await self.write_multiple_variables(variables_cmd)
|
||||
|
||||
async def set_stirring_speeds(self, command: str):
|
||||
"""设置搅拌桨速度
|
||||
Args:
|
||||
command: 一个JSON格式的字符串,包含搅拌桨速度配置
|
||||
atomization_speed (float): 雾化快速搅拌速度
|
||||
washing_speed (float): 洗涤慢速搅拌速度
|
||||
"""
|
||||
# 将JSON字符串转换为字典
|
||||
cmd_str = command.replace("'", '"')
|
||||
cmd_dict = json.loads(cmd_str)
|
||||
|
||||
# 提取参数
|
||||
atomization_speed = cmd_dict.get("atomization_speed", None)
|
||||
washing_speed = cmd_dict.get("washing_speed", None)
|
||||
|
||||
variables = {}
|
||||
if atomization_speed is not None:
|
||||
variables["搅拌桨雾化快速"] = atomization_speed
|
||||
if washing_speed is not None:
|
||||
variables["搅拌桨洗涤慢速"] = washing_speed
|
||||
|
||||
if variables:
|
||||
# 构建写入多个变量的命令
|
||||
variables_cmd = json.dumps({"variables": variables})
|
||||
await self.write_multiple_variables(variables_cmd)
|
||||
|
||||
async def set_pump_speeds(self, command: str):
|
||||
"""设置注射泵速度
|
||||
Args:
|
||||
command: 一个JSON格式的字符串,包含注射泵速度配置
|
||||
suction_speed (str): 注射泵抽液速度
|
||||
push_speed (str): 注射泵推液速度
|
||||
"""
|
||||
# 将JSON字符串转换为字典
|
||||
cmd_str = command.replace("'", '"')
|
||||
cmd_dict = json.loads(cmd_str)
|
||||
|
||||
# 提取参数
|
||||
suction_speed = cmd_dict.get("suction_speed", None)
|
||||
push_speed = cmd_dict.get("push_speed", None)
|
||||
|
||||
variables = {}
|
||||
if suction_speed is not None:
|
||||
variables["注射泵抽液速度"] = suction_speed
|
||||
if push_speed is not None:
|
||||
variables["注射泵推液速度"] = push_speed
|
||||
|
||||
if variables:
|
||||
# 构建写入多个变量的命令
|
||||
variables_cmd = json.dumps({"variables": variables})
|
||||
await self.write_multiple_variables(variables_cmd)
|
||||
|
||||
async def set_liquid_parameters(self, command: str):
|
||||
"""设置液体相关参数
|
||||
Args:
|
||||
command: 一个JSON格式的字符串,包含液体参数配置
|
||||
suction_times (int): 抽原液次数
|
||||
washing_water (float): 洗涤加水量
|
||||
initial_water (float): 最开始加水量
|
||||
atomization_pressure (int): 雾化压力百分比
|
||||
"""
|
||||
# 将JSON字符串转换为字典
|
||||
cmd_str = command.replace("'", '"')
|
||||
cmd_dict = json.loads(cmd_str)
|
||||
|
||||
# 提取参数
|
||||
suction_times = cmd_dict.get("suction_times", None)
|
||||
washing_water = cmd_dict.get("washing_water", None)
|
||||
initial_water = cmd_dict.get("initial_water", None)
|
||||
atomization_pressure = cmd_dict.get("atomization_pressure", None)
|
||||
|
||||
variables = {}
|
||||
if suction_times is not None:
|
||||
variables["抽原液次数"] = suction_times
|
||||
if washing_water is not None:
|
||||
variables["洗涤加水量"] = washing_water
|
||||
if initial_water is not None:
|
||||
variables["最开始加水量"] = initial_water
|
||||
if atomization_pressure is not None:
|
||||
variables["雾化压力百分比"] = atomization_pressure
|
||||
|
||||
if variables:
|
||||
# 构建写入多个变量的命令
|
||||
variables_cmd = json.dumps({"variables": variables})
|
||||
await self.write_multiple_variables(variables_cmd)
|
||||
|
||||
async def trigger_cleaning_actions(self, command: str):
|
||||
"""触发清洗动作
|
||||
Args:
|
||||
command: 一个JSON格式的字符串,包含清洗动作配置
|
||||
needle (bool): 是否触发吸液针清洗
|
||||
stirrer (bool): 是否触发搅拌桨清洗
|
||||
pipeline (bool): 是否触发管路吹气
|
||||
"""
|
||||
# 将JSON字符串转换为字典
|
||||
cmd_str = command.replace("'", '"')
|
||||
cmd_dict = json.loads(cmd_str)
|
||||
|
||||
# 提取参数
|
||||
needle = cmd_dict.get("needle", False)
|
||||
stirrer = cmd_dict.get("stirrer", False)
|
||||
pipeline = cmd_dict.get("pipeline", False)
|
||||
|
||||
variables = {}
|
||||
if needle:
|
||||
variables["吸液针清洗触发"] = True
|
||||
if stirrer:
|
||||
variables["搅拌桨清洗触发"] = True
|
||||
if pipeline:
|
||||
variables["管路吹气触发"] = True
|
||||
|
||||
if variables:
|
||||
# 构建写入多个变量的命令
|
||||
variables_cmd = json.dumps({"variables": variables})
|
||||
await self.write_multiple_variables(variables_cmd)
|
||||
|
||||
async def check_alarms(self) -> Dict[str, bool]:
|
||||
"""检查所有报警状态"""
|
||||
alarm_variables = [
|
||||
"废液桶满报警", "清水桶空报警", "NMP桶空报警", "丙酮桶空报警", "门开报警"
|
||||
]
|
||||
|
||||
alarms = {}
|
||||
for alarm in alarm_variables:
|
||||
try:
|
||||
status = await self.read_variable(alarm)
|
||||
alarms[alarm] = bool(status)
|
||||
except Exception as e:
|
||||
logger.warning(f"读取报警状态 {alarm} 失败: {e}")
|
||||
alarms[alarm] = None
|
||||
|
||||
return str(alarms)
|
||||
|
||||
async def emergency_stop(self):
|
||||
"""紧急停止 - 关闭所有触发器"""
|
||||
stop_variables = {
|
||||
"原料罐抓取触发": False,
|
||||
"反应罐抓取触发": False,
|
||||
"后处理动作触发": False,
|
||||
"吸液针清洗触发": False,
|
||||
"搅拌桨清洗触发": False,
|
||||
"管路吹气触发": False,
|
||||
}
|
||||
|
||||
await self.write_multiple_variables(stop_variables)
|
||||
logger.info("已执行紧急停止操作")
|
||||
|
||||
async def diagnose_variable_types(self):
|
||||
"""诊断所有变量的实际数据类型"""
|
||||
print("\n=== 变量类型诊断 ===")
|
||||
for variable_name, variable_info in self.variables.items():
|
||||
node = variable_info["node_id"]
|
||||
if node is not None:
|
||||
try:
|
||||
# 读取数据类型信息
|
||||
data_value = await node.read_data_value()
|
||||
variant_type = data_value.Value.VariantType
|
||||
current_value = data_value.Value.Value
|
||||
|
||||
# 读取节点的数据类型属性
|
||||
try:
|
||||
data_type = await node.read_data_type()
|
||||
print(f"{variable_name}:")
|
||||
print(f" 当前值: {current_value}")
|
||||
print(f" VariantType: {variant_type}")
|
||||
print(f" DataType: {data_type}")
|
||||
print(f" 我们的类型: {variable_info['type']}")
|
||||
print()
|
||||
except:
|
||||
print(f"{variable_name}:")
|
||||
print(f" 当前值: {current_value}")
|
||||
print(f" VariantType: {variant_type}")
|
||||
print(f" 我们的类型: {variable_info['type']}")
|
||||
print()
|
||||
|
||||
except Exception as e:
|
||||
print(f"{variable_name}: 读取失败 - {e}")
|
||||
else:
|
||||
print(f"{variable_name}: 节点未找到")
|
||||
|
||||
async def write_variable_safe(self, command: str):
|
||||
"""安全写入变量 - 包含详细的错误信息
|
||||
Args:
|
||||
command: 一个JSON格式的字符串,包含变量名和值
|
||||
variable_name (str): 要写入的变量名
|
||||
value (Any): 要写入的值
|
||||
"""
|
||||
# 将JSON字符串转换为字典
|
||||
cmd_str = command.replace("'", '"')
|
||||
cmd_dict = json.loads(cmd_str)
|
||||
|
||||
# 提取变量名和值
|
||||
variable_name = cmd_dict["variable_name"]
|
||||
value = cmd_dict["value"]
|
||||
|
||||
if not self.connected:
|
||||
raise ConnectionError("未连接到OPC-UA服务器")
|
||||
|
||||
if variable_name not in self.variables:
|
||||
raise ValueError(f"未知变量: {variable_name}")
|
||||
|
||||
node = self.variables[variable_name]["node_id"]
|
||||
if node is None:
|
||||
raise ValueError(f"未找到变量节点: {variable_name}")
|
||||
|
||||
try:
|
||||
# 读取当前值和类型信息
|
||||
current_data_value = await node.read_data_value()
|
||||
current_variant_type = current_data_value.Value.VariantType
|
||||
current_value = current_data_value.Value.Value
|
||||
|
||||
print(f"尝试写入变量: {variable_name}")
|
||||
print(f" 当前值: {current_value} (类型: {current_variant_type})")
|
||||
print(f" 目标值: {value}")
|
||||
|
||||
# 尝试多种类型转换方式
|
||||
success = False
|
||||
|
||||
# 方法1: 直接使用当前的VariantType
|
||||
try:
|
||||
var_type = self.variables[variable_name]["type"]
|
||||
converted_value = self._convert_value_with_variant_type(value, var_type, current_variant_type)
|
||||
variant = ua.Variant(converted_value, current_variant_type)
|
||||
await node.write_value(variant)
|
||||
print(f" ✅ 成功写入: {converted_value}")
|
||||
success = True
|
||||
except Exception as e1:
|
||||
print(f" ❌ 方法1失败: {e1}")
|
||||
|
||||
# 方法2: 让OPC-UA自动推断类型
|
||||
try:
|
||||
converted_value = self._convert_value(value, var_type)
|
||||
await node.write_value(converted_value)
|
||||
print(f" ✅ 成功写入 (自动类型): {converted_value}")
|
||||
success = True
|
||||
except Exception as e2:
|
||||
print(f" ❌ 方法2失败: {e2}")
|
||||
|
||||
# 方法3: 尝试不同的数值类型
|
||||
if var_type == "INT":
|
||||
for vt in [ua.VariantType.Int16, ua.VariantType.Int32, ua.VariantType.UInt16, ua.VariantType.UInt32]:
|
||||
try:
|
||||
variant = ua.Variant(int(value), vt)
|
||||
await node.write_value(variant)
|
||||
print(f" ✅ 成功写入 (类型 {vt}): {int(value)}")
|
||||
success = True
|
||||
break
|
||||
except:
|
||||
continue
|
||||
elif var_type == "REAL":
|
||||
for vt in [ua.VariantType.Float, ua.VariantType.Double]:
|
||||
try:
|
||||
variant = ua.Variant(float(value), vt)
|
||||
await node.write_value(variant)
|
||||
print(f" ✅ 成功写入 (类型 {vt}): {float(value)}")
|
||||
success = True
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
if not success:
|
||||
raise Exception("所有写入方法都失败了")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"写入变量 {variable_name} 失败: {e}")
|
||||
raise
|
||||
|
||||
def is_connected(self):
|
||||
return self.connected
|
||||
|
||||
|
||||
async def main():
|
||||
"""主函数 - 演示客户端使用"""
|
||||
client = OPCUADeviceClient()
|
||||
|
||||
try:
|
||||
# 连接到设备
|
||||
await client.connect()
|
||||
|
||||
# 读取所有变量的当前状态
|
||||
print("\n=== 读取所有变量状态 ===")
|
||||
all_values = await client.read_all_variables()
|
||||
for name, value in all_values.items():
|
||||
print(f"{name}: {value}")
|
||||
|
||||
# # 设置罐号码
|
||||
# print("\n=== 设置罐号码 ===")
|
||||
# await client.set_tank_numbers('{"raw_material_tank": 1, "reaction_tank": 2}')
|
||||
|
||||
# # 设置搅拌速度
|
||||
# print("\n=== 设置搅拌速度 ===")
|
||||
# await client.set_stirring_speeds('{"atomization_speed": 1000.0, "washing_speed": 500.0}')
|
||||
|
||||
# # 设置液体参数
|
||||
# print("\n=== 设置液体参数 ===")
|
||||
# await client.set_liquid_parameters('{"suction_times": 3, "washing_water": 100.0, "initial_water": 200.0, "atomization_pressure": 80}')
|
||||
|
||||
# # 检查报警状态
|
||||
# print("\n=== 检查报警状态 ===")
|
||||
# alarms = await client.check_alarms()
|
||||
# for alarm_name, status in alarms.items():
|
||||
# if status:
|
||||
# print(f"⚠️ {alarm_name}: 报警中")
|
||||
# else:
|
||||
# print(f"✅ {alarm_name}: 正常")
|
||||
|
||||
# # 等待一段时间
|
||||
# await asyncio.sleep(2)
|
||||
|
||||
# # 读取更新后的状态
|
||||
# print("\n=== 读取更新后的状态 ===")
|
||||
# updated_values = await client.read_all_variables()
|
||||
# for name, value in updated_values.items():
|
||||
# print(f"{name}: {value}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"操作失败: {e}")
|
||||
|
||||
finally:
|
||||
# 断开连接
|
||||
await client.disconnect()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 运行主程序
|
||||
asyncio.run(main())
|
||||
118
unilabos/registry/devices/huairou.yaml
Normal file
118
unilabos/registry/devices/huairou.yaml
Normal file
@@ -0,0 +1,118 @@
|
||||
huairou:
|
||||
description: huairou Device
|
||||
class:
|
||||
module: unilabos.devices.huairou.opcua_client:OPCUADeviceClient
|
||||
type: python
|
||||
# status_types:
|
||||
# variables:dict
|
||||
action_value_mappings:
|
||||
connect:
|
||||
type: SendCmd
|
||||
goal:
|
||||
command: {}
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
disconnect:
|
||||
type: SendCmd
|
||||
goal:
|
||||
command: {}
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
read_variable:
|
||||
type: SendCmd
|
||||
goal:
|
||||
command: variable_name
|
||||
feedback: value
|
||||
result:
|
||||
success: success
|
||||
write_variable:
|
||||
type: SendCmd
|
||||
goal: command
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
read_all_variables:
|
||||
type: SendCmd
|
||||
goal: {}
|
||||
feedback:
|
||||
results: results
|
||||
result:
|
||||
success: success
|
||||
write_multiple_variables:
|
||||
type: SendCmd
|
||||
goal: command
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
set_tank_numbers:
|
||||
type: SendCmd
|
||||
goal:
|
||||
command: command
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
trigger_grab_actions:
|
||||
type: SendCmd
|
||||
goal: command
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
set_stirring_speeds:
|
||||
type: SendCmd
|
||||
goal: command
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
set_pump_speeds:
|
||||
type: SendCmd
|
||||
goal: command
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
set_liquid_parameters:
|
||||
type: SendCmd
|
||||
goal: command
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
trigger_cleaning_actions:
|
||||
type: SendCmd
|
||||
goal: command
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
check_alarms:
|
||||
type: SendCmd
|
||||
goal: {}
|
||||
feedback: alarms
|
||||
result:
|
||||
success: success
|
||||
emergency_stop:
|
||||
type: SendCmd
|
||||
goal: {}
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
diagnose_variable_types:
|
||||
type: SendCmd
|
||||
goal: {}
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
write_variable_with_type:
|
||||
type: SendCmd
|
||||
goal: command
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
is_connected:
|
||||
type: bool
|
||||
description: Current status of the pump
|
||||
required:
|
||||
- is_connected
|
||||
additionalProperties: false
|
||||
Reference in New Issue
Block a user