0313界面对接
This commit is contained in:
@ -3,53 +3,86 @@ from opcua import Client, ua
|
||||
import time
|
||||
from datetime import datetime
|
||||
import configparser
|
||||
from threading import Thread
|
||||
|
||||
# class OpcuaUiSignal:
|
||||
# value_changed = Signal(str, str, object)
|
||||
# opc_disconnected = Signal(str) # OPC服务断开信号,参数:断开原因
|
||||
# opc_reconnected = Signal() # OPC重连成功信号
|
||||
# opc_log = Signal(str) # OPC运行日志信号,参数:日志信息
|
||||
|
||||
from threading import Thread,RLock
|
||||
import logging
|
||||
import queue
|
||||
|
||||
logging.getLogger("opcua").setLevel(logging.WARNING)
|
||||
logging.getLogger("opcua.client.ua_client").setLevel(logging.WARNING)
|
||||
logging.getLogger("opcua.uaprotocol").setLevel(logging.WARNING)
|
||||
#控制程序opcua 需要需要在opc_config中配置_f结尾的key才会收到通知
|
||||
# Opcua回调处理器
|
||||
class SubscriptionHandler:
|
||||
def __init__(self):
|
||||
def __init__(self,parent):
|
||||
self.node_id_to_name = {}
|
||||
# self.opc_signal = opc_signal
|
||||
#设置派单模式(1自动派单 2手动派单0未知)
|
||||
# self.pd_set_mode=0
|
||||
# #设置方量{ID:派单表ID,Volumn:修改后方量}
|
||||
# self.pd_set_volumn=''
|
||||
# #派单通知数据{ErpID:ID:派单表ID,Flag:状态,ErrorMsg:失败异常信息 }
|
||||
# self.pd_notify=''
|
||||
# 添加队列和锁
|
||||
self.notification_queue = queue.Queue()
|
||||
self.processing_thread = Thread(target=self._process_notifications, daemon=True,name="opcua_recv_thread")
|
||||
self.lock = RLock() # 可重入锁
|
||||
self.parent=parent
|
||||
self.notify_callback=self.parent.notify_callback
|
||||
|
||||
def start_accept(self):
|
||||
self.processing_thread.start()
|
||||
|
||||
def datachange_notification(self, node, val, data):
|
||||
try:
|
||||
node_id = node.nodeid.to_string()
|
||||
var_name = self.node_id_to_name.get(node_id)
|
||||
# self.opc_signal.value_changed.emit(node_id, var_name, val)
|
||||
print(f"node_id: {node_id}, var_name: {var_name}, val: {val}")
|
||||
self.notification_queue.put_nowait((var_name, val))
|
||||
# setattr(self, var_name, val)
|
||||
# if self.notify_callback:
|
||||
# self.notify_callback(var_name,val)
|
||||
except Exception as e:
|
||||
err_msg = f"opcua解析值变化事件失败: {e}"
|
||||
# self.opc_signal.opc_log.emit(err_msg)
|
||||
print(err_msg)
|
||||
|
||||
def _process_notifications(self):
|
||||
"""后台线程处理通知(串行执行)"""
|
||||
while self.parent.is_running:
|
||||
try:
|
||||
var_name, val = self.notification_queue.get(timeout=1)
|
||||
# 使用锁保护共享资源
|
||||
with self.lock:
|
||||
self.notify_callback(var_name,val)
|
||||
self.notification_queue.task_done()
|
||||
except queue.Empty:
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"处理通知失败: {e}")
|
||||
|
||||
|
||||
class OpcuaClientFeed(Thread):
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self,notify_callback=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.server_url = ""
|
||||
self.client = None
|
||||
|
||||
self.name="opcua_feed_client"
|
||||
self.target_var_paths = []
|
||||
#订阅配置文件中的参数
|
||||
self.subscription_name=["pd_notify","pd_set_mode","pd_set_volume"]
|
||||
# self.server_url = ""
|
||||
self.read_opc_config() # 读取配置文件
|
||||
|
||||
self.client = Client(self.server_url) # 初始化opc客户端
|
||||
self.notify_callback=notify_callback
|
||||
|
||||
self.connected = False
|
||||
self.subscription = None
|
||||
self.monitored_items = []
|
||||
self.is_running = True # 线程运行标志位
|
||||
self.node_id_mapping = {} # node_id 和 可读变量名的映射表
|
||||
self.is_reconnect_tip_sent = False # 重连失败提示是否已发送
|
||||
self.handler = SubscriptionHandler(self)
|
||||
|
||||
# self.opc_signal = OpcuaUiSignal()
|
||||
# self.handler = SubscriptionHandler(self.opc_signal)
|
||||
self.handler = SubscriptionHandler()
|
||||
|
||||
self.target_var_paths = []
|
||||
|
||||
# 参数
|
||||
self.heartbeat_interval = None # 心跳检测间隔
|
||||
self.reconnect_interval = None # 首次/掉线重连间隔
|
||||
self.sub_interval = None # 订阅间隔 (单位:ms)
|
||||
|
||||
def start_accept(self):
|
||||
self.handler.start_accept()
|
||||
|
||||
def stop_run(self):
|
||||
"""停止线程+断开连接"""
|
||||
@ -65,7 +98,6 @@ class OpcuaClientFeed(Thread):
|
||||
self.connected = True
|
||||
msg = f"成功连接到OPCUA服务器: {self.server_url}"
|
||||
print(msg)
|
||||
# self.opc_signal.opc_log.emit(msg)
|
||||
self.is_reconnect_tip_sent = False
|
||||
return True
|
||||
except Exception as e:
|
||||
@ -73,7 +105,7 @@ class OpcuaClientFeed(Thread):
|
||||
err_msg = f"连接OPCUA服务器失败: {e}"
|
||||
print(err_msg)
|
||||
if not self.is_reconnect_tip_sent:
|
||||
# self.opc_signal.opc_log.emit(err_msg)
|
||||
print(err_msg)
|
||||
# 标记为已发送,后续不重复在UI上显示
|
||||
self.is_reconnect_tip_sent = True
|
||||
return False
|
||||
@ -111,41 +143,37 @@ class OpcuaClientFeed(Thread):
|
||||
if not self.connected:
|
||||
return False
|
||||
try:
|
||||
# self.opc_signal.opc_log.emit("开始构建nodeid映射表...")
|
||||
objects_node = self.client.get_objects_node()
|
||||
self.handler.node_id_to_name = self.node_id_mapping
|
||||
for var_name, path_list in self.target_var_paths:
|
||||
target_node = objects_node.get_child(path_list)
|
||||
node_id = target_node.nodeid.to_string()
|
||||
self.node_id_mapping[node_id] = var_name
|
||||
# self.opc_signal.opc_log.emit("nodeid映射表构建成功")
|
||||
print("nodeid映射表构建成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
err_msg = f"构建{var_name}映射表失败: {e}"
|
||||
print(err_msg)
|
||||
# self.opc_signal.opc_log.emit(err_msg)
|
||||
return False
|
||||
|
||||
def create_multi_subscription(self, interval=None):
|
||||
"""订阅多个变量(基于映射表的nodeid)"""
|
||||
if not self.connected:
|
||||
return
|
||||
if not self.node_id_mapping and not self.build_node_id_mapping():
|
||||
if not self.node_id_mapping:
|
||||
return
|
||||
try:
|
||||
interval = int(interval) if interval else self.sub_interval
|
||||
self.subscription = self.client.create_subscription(interval, self.handler)
|
||||
# self.opc_signal.opc_log.emit(f"opcua订阅创建成功(间隔:{interval}ms)")
|
||||
for node_id, var_name in self.node_id_mapping.items():
|
||||
var_node = self.client.get_node(node_id)
|
||||
monitored_item = self.subscription.subscribe_data_change(var_node)
|
||||
self.monitored_items.append(monitored_item)
|
||||
# self.opc_signal.opc_log.emit(f"已订阅变量: {var_name} (nodeid: {node_id})")
|
||||
print(f"已订阅变量: {var_name} (nodeid: {node_id})")
|
||||
for node_id, var_name in self.node_id_mapping.items():
|
||||
if var_name in self.subscription_name:
|
||||
var_node = self.client.get_node(node_id)
|
||||
monitored_item = self.subscription.subscribe_data_change(var_node)
|
||||
self.monitored_items.append(monitored_item)
|
||||
print(f"已订阅变量: {var_name} (nodeid: {node_id})")
|
||||
except Exception as e:
|
||||
err_msg = f"创建批量订阅失败: {e}"
|
||||
print(err_msg)
|
||||
# self.opc_signal.opc_log.emit(err_msg)
|
||||
|
||||
def read_opc_config(self, cfg_path = "config/opc_config.ini"):
|
||||
"""读取OPC配置文件, 初始化所有参数和节点列表"""
|
||||
@ -157,23 +185,19 @@ class OpcuaClientFeed(Thread):
|
||||
self.heartbeat_interval = cfg.getint("OPC_SERVER_CONFIG", "heartbeat_interval")
|
||||
self.reconnect_interval = cfg.getint("OPC_SERVER_CONFIG", "reconnect_interval")
|
||||
self.sub_interval = cfg.getint("OPC_SERVER_CONFIG", "sub_interval")
|
||||
|
||||
|
||||
# 2. 读取OPC节点配置
|
||||
node_section = cfg["OPC_NODE_LIST"]
|
||||
for readable_name, node_path_str in node_section.items():
|
||||
node_path_list = node_path_str.split(",")
|
||||
self.target_var_paths.append( (readable_name, node_path_list) )
|
||||
# print("target_var_paths", self.target_var_paths)
|
||||
print("target_var_paths", self.target_var_paths)
|
||||
except Exception as e:
|
||||
print(f"读取配置文件失败: {e},使用默认配置启动!")
|
||||
self.server_url = "opc.tcp://localhost:4840/zjsh_feed/server/"
|
||||
self.heartbeat_interval = 4
|
||||
self.reconnect_interval = 2
|
||||
self.sub_interval = 500
|
||||
self.target_var_paths = [
|
||||
("upper_weight", ["2:upper", "2:upper_weight"]),
|
||||
("lower_weight", ["2:lower", "2:lower_weight"])
|
||||
]
|
||||
# 参数合法性检验
|
||||
self.heartbeat_interval = self.heartbeat_interval if isinstance(self.heartbeat_interval, int) and self.heartbeat_interval >=1 else 4
|
||||
self.reconnect_interval = self.reconnect_interval if isinstance(self.reconnect_interval, int) and self.reconnect_interval >=1 else 2
|
||||
@ -185,26 +209,31 @@ class OpcuaClientFeed(Thread):
|
||||
:param var_readable_name: 变量可读名称(如"upper_weight")
|
||||
:param value: 要写入的值
|
||||
"""
|
||||
max_wait = 30 # 最大等待30秒
|
||||
wait_count = 0
|
||||
while (not self.node_id_mapping and wait_count < max_wait):
|
||||
time.sleep(0.5)
|
||||
wait_count += 1
|
||||
if wait_count % 2 == 0: # 每2秒打印一次
|
||||
print(f"等待OPC连接中...({wait_count/2}秒)")
|
||||
|
||||
if not self.connected:
|
||||
# self.opc_signal.opc_log.emit(f"{var_readable_name}写入失败: OPC服务未连接")
|
||||
print(f"{var_readable_name}写入失败: OPC服务未连接")
|
||||
return
|
||||
|
||||
target_node_id = None
|
||||
for node_id, name in self.node_id_mapping.items():
|
||||
if name == var_readable_name:
|
||||
target_node_id = node_id
|
||||
break
|
||||
if not target_node_id:
|
||||
# self.opc_signal.opc_log.emit(f"写入失败:未找到变量名 {var_readable_name} 对应的nodeid")
|
||||
return
|
||||
try:
|
||||
target_node = self.client.get_node(target_node_id)
|
||||
# variant = ua.Variant(float(value), ua.VariantType.Double)
|
||||
target_node.set_value(value)
|
||||
# self.opc_signal.opc_log.emit(f"写入成功:{var_readable_name} = {value}")
|
||||
except Exception as e:
|
||||
err_msg = f"opcua写入值失败: {e}"
|
||||
print(err_msg)
|
||||
# self.opc_signal.opc_log.emit(err_msg)
|
||||
|
||||
# ===== 心跳检测函数 =====
|
||||
def _heartbeat_check(self):
|
||||
@ -215,45 +244,38 @@ class OpcuaClientFeed(Thread):
|
||||
except Exception as e:
|
||||
err_msg = f"心跳检测失败, OPCUA服务已断开 {e}"
|
||||
print(err_msg)
|
||||
# self.opc_signal.opc_log.emit(err_msg)
|
||||
return False
|
||||
|
||||
# ===== 掉线重连函数 =====
|
||||
def _auto_reconnect(self):
|
||||
"""掉线后自动重连+重建映射+恢复订阅"""
|
||||
# self.opc_signal.opc_disconnected.emit("OPC服务掉线, 开始自动重连...")
|
||||
print("OPC服务掉线, 开始自动重连...")
|
||||
try:
|
||||
self.disconnect()
|
||||
except Exception as e:
|
||||
print(f"_auto_reconnect: 断开旧连接时出现异常: {e}")
|
||||
while self.is_running:
|
||||
# self.opc_signal.opc_log.emit(f"重试连接OPC服务器: {self.server_url}")
|
||||
print(f"重试连接OPC服务器: {self.server_url}")
|
||||
if self.connect():
|
||||
self.build_node_id_mapping()
|
||||
self.create_multi_subscription()
|
||||
# self.opc_signal.opc_reconnected.emit()
|
||||
# self.opc_signal.opc_log.emit("OPCUA服务器重连成功, 所有订阅已恢复正常")
|
||||
print("OPCUA服务器重连成功, 所有订阅已恢复正常")
|
||||
break
|
||||
time.sleep(self.reconnect_interval)
|
||||
|
||||
def _init_connect_with_retry(self):
|
||||
"""连接opc服务器"""
|
||||
# self.opc_signal.opc_log.emit("OPC客户端初始化, 开始连接服务器...")
|
||||
print("OPC客户端初始化, 开始连接服务器...")
|
||||
while self.is_running:
|
||||
if self.connect():
|
||||
self.build_node_id_mapping()
|
||||
self.create_multi_subscription()
|
||||
break
|
||||
# self.opc_signal.opc_log.emit(f"连接OPCUA服务器失败, {self.reconnect_interval}秒后重试...")
|
||||
print(f"连接OPCUA服务器失败, {self.reconnect_interval}秒后重试...")
|
||||
time.sleep(self.reconnect_interval)
|
||||
|
||||
def run(self) -> None:
|
||||
"""opcua客户端线程主函数"""
|
||||
self.read_opc_config() # 读取配置文件
|
||||
self.client = Client(self.server_url) # 初始化opc客户端
|
||||
|
||||
# 连接opc服务器
|
||||
self._init_connect_with_retry()
|
||||
|
||||
@ -269,6 +291,9 @@ class OpcuaClientFeed(Thread):
|
||||
|
||||
if __name__ == "__main__":
|
||||
opcua_client = OpcuaClientFeed()
|
||||
opcua_client.run()
|
||||
opcua_client.write_value_by_name("upper_weight", 100.0)
|
||||
opcua_client.start()
|
||||
opcua_client.write_value_by_name("pd_set_mode", 1)
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
@ -61,24 +61,36 @@ class SimpleOPCUAServer:
|
||||
"""创建OPC UA变量"""
|
||||
# 创建变量时显式指定数据类型和初始值
|
||||
#上料斗
|
||||
# 上料斗
|
||||
self.upper_weight = self.upper.add_variable(self.namespace, "upper_weight", ua.Variant(0.0, ua.VariantType.Float))
|
||||
self.upper_is_arch = self.upper.add_variable(self.namespace, "upper_is_arch", ua.Variant(False, ua.VariantType.Boolean))
|
||||
self.upper_door_closed = self.upper.add_variable(self.namespace, "upper_door_closed", ua.Variant(False, ua.VariantType.Boolean))
|
||||
self.upper_door_closed = self.upper.add_variable(self.namespace, "upper_door_closed", ua.Variant(False, ua.VariantType.Int16))
|
||||
self.upper_volume = self.upper.add_variable(self.namespace, "upper_volume", ua.Variant(0.0, ua.VariantType.Float))
|
||||
self.upper_door_position = self.upper.add_variable(self.namespace, "upper_door_position", ua.Variant(0, ua.VariantType.Int16))
|
||||
|
||||
#下料斗
|
||||
# 下料斗
|
||||
self.lower_weight = self.lower.add_variable(self.namespace, "lower_weight", ua.Variant(0.0, ua.VariantType.Float))
|
||||
self.lower_is_arch = self.lower.add_variable(self.namespace, "lower_is_arch", ua.Variant(False, ua.VariantType.Boolean))
|
||||
self.lower_angle = self.lower.add_variable(self.namespace, "lower_angle", ua.Variant(0.0, ua.VariantType.Float))
|
||||
|
||||
#模具车
|
||||
# 模具车
|
||||
self.mould_finish_weight = self.mould.add_variable(self.namespace, "mould_finish_weight", ua.Variant(0.0, ua.VariantType.Float))
|
||||
self.mould_need_weight = self.mould.add_variable(self.namespace, "mould_need_weight", ua.Variant(0.0, ua.VariantType.Float))
|
||||
self.mould_finish_ratio = self.mould.add_variable(self.namespace, "mould_finish_ratio", ua.Variant(0.0, ua.VariantType.Float))
|
||||
|
||||
self.mould_frequency = self.mould.add_variable(self.namespace, "mould_frequency", ua.Variant(230, ua.VariantType.Int32))
|
||||
self.mould_vibrate_status = self.mould.add_variable(self.namespace, "mould_vibrate_status", ua.Variant(False, ua.VariantType.Boolean))
|
||||
self.feed_status = self.mould.add_variable(self.namespace, "feed_status", ua.Variant(0, ua.VariantType.Int16))
|
||||
self.pd_data=self.pd.add_variable(self.namespace, "pd_data", ua.Variant("", ua.VariantType.String))
|
||||
self.pd_notify=self.pd.add_variable(self.namespace, "pd_notify", ua.Variant("", ua.VariantType.String))
|
||||
self.pd_set_mode=self.pd.add_variable(self.namespace, "set_mode", ua.Variant(0, ua.VariantType.Int16))
|
||||
self.pd_set_volume=self.pd.add_variable(self.namespace, "set_volume", ua.Variant("", ua.VariantType.String))
|
||||
|
||||
# 系统对象
|
||||
self.sys_set_mode=self.sys.add_variable(self.namespace, "set_mode", ua.Variant(0, ua.VariantType.Int16))
|
||||
self.sys_segment_refresh=self.sys.add_variable(self.namespace, "segment_refresh", ua.Variant(0, ua.VariantType.Int16))
|
||||
self.sys_pd_refresh=self.sys.add_variable(self.namespace, "pd_refresh", ua.Variant(0, ua.VariantType.Int16))
|
||||
|
||||
# 在创建变量后立即设置可写权限(不需要等待服务器启动)
|
||||
self.upper_weight.set_writable(True)
|
||||
self.lower_weight.set_writable(True)
|
||||
@ -93,64 +105,19 @@ class SimpleOPCUAServer:
|
||||
self.mould_vibrate_status.set_writable(True)
|
||||
self.feed_status.set_writable(True)
|
||||
self.pd_data.set_writable(True)
|
||||
self.pd_notify.set_writable(True)
|
||||
self.pd_set_mode.set_writable(True)
|
||||
self.pd_set_volume.set_writable(True)
|
||||
self.sys_set_mode.set_writable(True)
|
||||
self.mould_finish_ratio.set_writable(True)
|
||||
self.sys_segment_refresh.set_writable(True)
|
||||
self.sys_pd_refresh.set_writable(True)
|
||||
self.lower_angle.set_writable(True)
|
||||
|
||||
print("[变量创建] 变量创建完成,AccessLevel权限已设置")
|
||||
|
||||
# 验证并打印当前的AccessLevel属性
|
||||
# try:
|
||||
# al = self.upper_weight.get_attribute(ua.AttributeIds.AccessLevel)
|
||||
# ual = self.upper_weight.get_attribute(ua.AttributeIds.UserAccessLevel)
|
||||
# print(f"[变量创建] upper_weight AccessLevel: {al.Value.Value}, UserAccessLevel: {ual.Value.Value}")
|
||||
|
||||
# al2 = self.lower_weight.get_attribute(ua.AttributeIds.AccessLevel)
|
||||
# ual2 = self.lower_weight.get_attribute(ua.AttributeIds.UserAccessLevel)
|
||||
# print(f"[变量创建] lower_weight AccessLevel: {al2.Value.Value}, UserAccessLevel: {ual2.Value.Value}")
|
||||
|
||||
# except Exception as e:
|
||||
# print(f"[变量创建] 获取权限属性失败: {e}")
|
||||
|
||||
def setup_variable_permissions(self):
|
||||
"""设置变量权限 - 在服务器启动后调用"""
|
||||
try:
|
||||
# 重新设置变量为可写,确保权限生效
|
||||
self.upper_weight.set_writable(True)
|
||||
self.lower_weight.set_writable(True)
|
||||
print("[权限设置] 变量权限已重新设置")
|
||||
|
||||
# 验证权限
|
||||
try:
|
||||
al = self.upper_weight.get_attribute(ua.AttributeIds.AccessLevel)
|
||||
ual = self.upper_weight.get_attribute(ua.AttributeIds.UserAccessLevel)
|
||||
print(f"[权限设置] upper_weight AccessLevel: {al.Value.Value}, UserAccessLevel: {ual.Value.Value}")
|
||||
except Exception as e:
|
||||
print(f"[权限设置] 验证失败: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[权限设置] 设置权限失败: {e}")
|
||||
print("[权限设置] 尝试强制设置...")
|
||||
|
||||
|
||||
# def setup_state_listeners(self):
|
||||
# """设置状态监听器 - 事件驱动更新"""
|
||||
# if hasattr(self.state, 'state_updated'):
|
||||
# self.state.state_updated.connect(self.on_state_changed)
|
||||
# print("状态监听器已设置 - 事件驱动模式")
|
||||
|
||||
# def on_state_changed(self, property_name, value):
|
||||
# """状态变化时的回调函数"""
|
||||
# try:
|
||||
# # 根据属性名更新对应的OPC UA变量
|
||||
# if property_name == "upper_weight":
|
||||
# self.upper_weight.set_value(value)
|
||||
# elif property_name == "lower_weight":
|
||||
# self.lower_weight.set_value(value)
|
||||
|
||||
# # 可以在这里添加更多状态映射
|
||||
# print(f"状态更新: {property_name} = {value}")
|
||||
|
||||
# except Exception as e:
|
||||
# print(f"状态更新错误: {e}")
|
||||
print("[变量创建] 变量创建完成")
|
||||
|
||||
|
||||
def start(self):
|
||||
"""启动服务器"""
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user