使用python-opcua读写西门子plc纪要

在与plc连接中,一般常用的协议为snap7或者opcua。但两者在网络上的资源太少了,而且大部分是纯英文,在使用过程中不可避免的踩到了许多坑,长久下去可不是办法,snap7由于公司项目使用且大部分坑由前辈们填上,所以作者在此不便交流分享,此文章仅对opcua协议中遇到的问题作出分享。

1、连接限制

一个plc服务端一般支持最大连接数为5,同时应该尽量考虑创建不同的实例以及读写锁来实现读写分离

2、连接状态

        1) send_hello

        就离谱,特么这个函数竟然会报错

        2)服务端断开连接时,客户端毫无反应

        这一点在使用订阅模式时尤其注意,服务端断开后,你的订阅不会退出,但不会再有任何的监听信息。

        基于此,需要自己实现保活机制,并如果有订阅的话需在断开重连后重新订阅。

暂时就这些吧,奉上参考demo

"""
    opc-ua类
    实现工业机器交互
    opc-ua安装使用方式:
        pip3 install PyQt5
        pip3 install PyQt5-tools
        pip install opcua
        pip3 install opcua-client
        pip3 install opcua-modeler
    opc-ua 服务端调试工具:
        python环境下 cmd 运行 opcua-modeler
    opc-ua 客户端调试工具:
        python环境下 cmd 运行 opcua-client
"""
import logging
import threading
import time
from functools import wraps

from config.config import config
from opcua import Client, ua, Node

logger = logging.getLogger('default')


def read_lock_func(func):
    @wraps(func)
    def run(*args, **kwargs):
        with threading.Lock():
            res = func(*args, **kwargs)
        return res

    return run


def write_lock_func(func):
    @wraps(func)
    def run(*args, **kwargs):
        with threading.Lock():
            res = func(*args, **kwargs)
        return res

    return run


# 自定义回调函数
class SubHandler(object):
    def datachange_notification(self, node, val, attr):
        logger.info(f"data change event: node:{node} value:{val} ")


class EnumType:
    uaBool = ua.VariantType.Boolean
    uaString = ua.VariantType.String
    uaInt16 = ua.VariantType.Int16
    uaInt32 = ua.VariantType.Int32
    uaUInt32 = ua.VariantType.UInt32
    uaFloat = ua.VariantType.Float
    uaByte = ua.VariantType.Byte


class OpcClient:
    """
    OPCUA Client class
    """

    def __init__(self, ip, port, sub_id=None):
        """
        :param ip:  opc server ip
        :param port: opc server port
        :param sub_id: 需要监听的nodeId列表[{node_id:id,handle:handle}]
        """
        self._is_connect = False
        self.ip = ip
        self.port = port
        self.sub_id = sub_id
        self.client = Client("opc.tcp://{}:{}".format(ip, port))
        # self.client.secure_channel_timeout = 10000
        # self.client.session_timeout = 10000
        self.first_connect()
        t = threading.Thread(target=self.check_alive_and_connect_daemon, args=(10,))
        t.setDaemon(True)
        t.start()

    def disconnect(self):
        self.client.disconnect()
        logger.info("client disconnect")

    def first_connect(self):
        tmp = self.__connect__()
        if tmp is False:
            time.sleep(1)
            self.first_connect()
        else:
            self.client.load_type_definitions()
            return True

    def __connect__(self):
        try:
            logger.info("开始创建连接")
            self.client.connect()
        except Exception as e:
            self._is_connect = False
            logger.info("连接失败")
            return False
        self._is_connect = True
        logger.info("连接成功")
        if self.sub_id and len(self.sub_id) > 0:
            for item in self.sub_id:
                logger.info(f"订阅:{item['node_id']}")
                self.create_subscription(item["node_id"], item["handle"])
        return True

    def __check_alive__(self):
        try:
            self.client.send_hello()
        except Exception as e:
            try:
                self.client.disconnect()
            except Exception as c:
                pass
            self._is_connect = False
            return False
        self._is_connect = True
        return True

    def __check_alive_bak__(self):
        res = self.write_by_node_id(config['alive_signal'], 1, EnumType.uaByte)
        if res:
            self._is_connect = True
            #res = self.write_by_node_id(config["make_code_encode"], int(84205), EnumType.uaInt32)
            #print(f"res:{res}")
            return True
        else:
            self._is_connect = False
            return False

    def __check_alive_and_connect__(self):
        with threading.Lock():
            result = self.__check_alive_bak__()
            if result:
                return True
            else:
                logger.info("recv false")
                return self.__connect__()

    def check_alive_and_connect_daemon(self, timedelta):
        """
        保活机制
        :param timedelta: 状态查询时间
        :return:
        """
        while 1:
            self.__check_alive_and_connect__()
            time.sleep(timedelta)

    def create_subscription(self, node_id, node_handler, time_out=300):
        """
        注册监听事件
        :param node_id: node_id,not Node!!!
        :param node_handler:
        :param time_out:
        :return:
        """
        try:
            node = self.get_node(node_id)
        except Exception as e:
            raise Exception("not this node ,please check id!")
        sub = self.client.create_subscription(time_out, node_handler)
        sub.subscribe_data_change(node)

    def get_node(self, node_id):
        """
        获取节点对象
        :param node_id:
        :return:
        """
        try:
            node = self.client.get_node(node_id)
            return node
        except Exception as e:
            raise Exception(f"no this node {node_id}")

    @read_lock_func
    def read_by_node(self, node):
        """
        获取节点对象的值
        """
        if not isinstance(node, Node):
            raise Exception("please pass in the correct parameters")
        return node.get_value()

    @read_lock_func
    def read_by_node_id(self, node_id):
        """
        获取节点对象的值
        """
        node = self.client.get_node(node_id)
        if not isinstance(node, Node):
            raise Exception("please pass in the correct parameters")
        return node.get_value()

    @write_lock_func
    def write_by_node_id(self, node_id, value, ua_type):
        """
        在节点写入值
        :param node_id: 节点对象
        :param value: 写入的值
        :param ua_type: 写入值的ua类型,参照EnumType
        :return:
        """
        try:
            node = self.client.get_node(node_id)
        except Exception as e:
            logger.error(f"写入 获取node:{node_id}失败:{e}")
            return False
        if not isinstance(node, Node):
            raise Exception("please pass in the correct parameters")
        try:
            node.set_attribute(ua.AttributeIds.Value,
                               ua.DataValue(variant=ua.Variant(value=value,
                                                               varianttype=ua_type)))
            return True
        except Exception as e:
            logger.info(f"write error: {e}")
            return False

你可能感兴趣的:(python,开发语言,后端)