Python通过OPC UA协议和WinCC通讯

1. 安装KEPServerEX6,使用KEPServerEX6WinCC通讯来实时查看变量

为了便于查看WINCC上数据结构,可以通过KEPServerEx来读取WinCC OPC服务器上变量列表,查看某个变量的数据类型和地址

添加通道向导,OPC服务器:opc.tcp://192.168.0.252:4862/

 

2. WINCC OPC服务器端设置

 1)系统启动OpcUaServerWinCC服务,查看端口4862是否开放

netstat -an

上述命令会显示当前开放的端口号以及应用程序占用的端口号,其中"-a"表示列出所有连线中的Socket,而"-n"表示不把界面和端口名转换成数字和名称,这样能大大提高查询效率

2)修改WinCC文件设置,并重启WinCC

安全策略Basic256,消息模式:SignAndEncrypt

3) OPENSLL生成Python连接证书

set OPENSSL_CONF=D:\works\Python_Qt\wincc\ssl.conf

openssl genrsa -out private_key.pem 2048

openssl req -x509 -days 3650 -new -out certification.pem -key private_key.pem -config ssl.conf

PythonUA客户端)与WinCCUA服务器)通讯流程如下:

1.    UA客户端首次连接UA服务器时,客户端将获取服务器数字证书,验证是否可信任;

2.    UA客户端接受UA服务器证书,并将其存放于Client Certificate Store

3.    UA客户端发起创建环境请求,并传递Client.derUA服务器;

4.    UA服务器自动将Client.der存放于Rejected目录下,用户需要手动剪切至certs目录下。

在完成证书校验后,OPC UA服务器与客户端可以进行数据的交换。

ssl.conf文件内容如下:

[ req ]
default_bits = 2048
default_md = sha256
distinguished_name = subject
req_extensions = req_ext
x509_extensions = req_ext
string_mask = utf8only
prompt = no

[ req_ext ]
basicConstraints = CA:FALSE
nsCertType = client, server
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyCertSign
extendedKeyUsage= serverAuth, clientAuth
nsComment = "OpenSSL Generated Certificat"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
subjectAltName = URI:urn:opcua:python:server,IP: 127.0.0.1

[ subject ]
countryName = CN
stateOrProvinceName = HE
localityName = HE
organizationName = Administrator
commonName = PythonOpcUaServer

4) 信任证书

python程序连接OPC UA服务器后会在WinCC OPC UA服务器的目录C:\Program Files (x86)\Siemens \WinCC\opc\UAServer\PKI\CA\rejected\certs产生一个被拒绝的证书,将该证书移动到C:\Program Files (x86)\Siemens\WinCC\opc\UAServer\PKI\CA \certs下,然后安装该证书,将证书存储于受信任的根证书颁发机构。

3. python程序示例

# -*_ coding: utf-8 -*-

from opcua import Client

def browse(root,level=0):
     if root.get_node_class().name == 'Object':
         print('--'*level+root.get_browse_name().Name)
         level += 1
         for child in list(set(root.get_children())):
             if child.get_browse_name().Name != '所有结构实例的列表' and child.get_browse_name().Name != '所有变量的列表':
                browse(child,level)
     else:
         print('--'*level+root.get_browse_name().Name+'==>>'+'['+str(root.nodeid)+']')


def get_node_value(client, nodeid):
    try:
        node = client.get_node(nodeid)
        var = node.get_value()
    except:
        var = None
    return var


#创建OPCUA客户端
wincc_client = Client('opc.tcp://192.168.0.252:4862/', timeout = 10)
wincc_client.set_security_string(r"Basic256,SignAndEncrypt,D:\works\Python_Qt\wincc\certification.pem,D:\works\Python_Qt\wincc\private_key.pem")

r"""
​ 设置SecureConnection模式。
​ 字符串格式:Policy,Mode,certificate,private_key[,server_private_key]
​ 其中Policy为Basic128Rsa15 or Basic256,
​ Mode为Sign or SignAndEncrypt
​ certificate, private_key and server_private_key为 .pem 或 .der 文件的路径
 client.connect后会在WinCC OPCUA服务器C:\Program Files (x86)\Siemens\WinCC\opc\UAServer\PKI\CA\rejected\certs下
 产生一个被拒绝的证书,将该证书移动到C:\Program Files (x86)\Siemens\WinCC\opc\UAServer\PKI\CA\certs下,
 然后安装该证书,将证书存储于-受信任的根证书颁发机构。
"""

wincc_client.set_user('Administrator')
wincc_client.set_password('123456')
#与证书内uri保持一致
wincc_client.application_uri = "urn:opcua:python:server"

try:
    wincc_client.connect()
    wincc_client.load_type_definitions()

    """
    使用 get_children()和get_child(path) 两个函数获取获取节点、事件
    """
    
    root = wincc_client.get_root_node()
    print("Objects node is: ", root)
    rootwincc = root.get_child(['0:Objects','1:WinCC'])
    print("WinCC node is: ", rootwincc)    
    #browse(rootwincc)    
    root = wincc_client.get_root_node()
    print("WinCC node is: ", root.get_children()[1].get_children()[1])
    
    objects = wincc_client.get_objects_node()
    print("MyObject is: ", objects)
    
   
    #读取电机参数,数据类型和地址可以通过KEPServerEx读取
    print("水泵XS-M300A运行参数如下:")
    #mtest_name = wincc_client.get_node("ns=1;s=t|YL-MTEST.Name")
    #print("名称:", mtest_name.get_value())
    """
    mtest_state = wincc_client.get_node("ns=1;s=t|YL-MTEST.MotorState")
    mtest_current = wincc_client.get_node("ns=1;s=t|YL-MTEST.Current")
    mtest_speed = wincc_client.get_node("ns=1;s=t|YL-MTEST.Speed")
    mtest_torque = wincc_client.get_node("ns=1;s=t|YL-MTEST.Torque")
    """

    print("名称:", get_node_value(wincc_client, "ns=1;s=t|XS-M301A.Name"))
    print("电机状态:", get_node_value(wincc_client, "ns=1;s=t|XS-M301A.MotorState"))
    print("选择序号:", get_node_value(wincc_client, "ns=1;s=t|XS-M301A.SelectionIndex"))
    print("实际频率:", round(get_node_value(wincc_client, "ns=1;s=t|XS-M301A.addInfo_rData[0]"), 2))    
    print("设定频率:", round(get_node_value(wincc_client, "ns=1;s=t|XS-M301A.addInfo_rData[1]"), 2))    
    print("实际电流:", round(get_node_value(wincc_client, "ns=1;s=t|XS-M301A.addInfo_rData[2]"), 2))    
    print("最大允许电流:", round(get_node_value(wincc_client, "ns=1;s=t|XS-M301A.addInfo_rData[3]"), 2)) 
    print("---------------------------------------------------")
    print("循环水泵对应的流量及前后压力分别为:")
    print("循环泵泵总流量:", round(get_node_value(wincc_client, "ns=1;s=t|NJ-LQS5-MNL_056.rValue"), 2), 'm3/h')
    print("泵流量量程:", round(get_node_value(wincc_client, "ns=1;s=t|NJ-LQS5-MNL_056.rOsh"), 2), 'm3/h')
    print("泵流量低限:", round(get_node_value(wincc_client, "ns=1;s=t|NJ-LQS5-MNL_056.MM_LL_Threshold"), 2), 'm3/h')
     
    print("循环泵泵后压力:", round(get_node_value(wincc_client, "ns=1;s=t|NJ-LQS5-MNL_046.rValue"), 2), 'kPa')
    print("泵后压力量程:", round(get_node_value(wincc_client, "ns=1;s=t|NJ-LQS5-MNL_046.rOsh"), 2), 'kPa')
    print("泵后压力高限:", round(get_node_value(wincc_client, "ns=1;s=t|NJ-LQS5-MNL_046.MM_HH_Threshold"), 2), 'kPa')
    print("泵后压力低限:", round(get_node_value(wincc_client, "ns=1;s=t|NJ-LQS5-MNL_046.MM_LL_Threshold"), 2), 'kPa')

    print("循环泵泵前压力:", round(get_node_value(wincc_client, "ns=1;s=t|NJ-LQS5-MNL_045.rValue"), 2), 'kPa')
    print("泵前压力量程:", round(get_node_value(wincc_client, "ns=1;s=t|NJ-LQS5-MNL_045.rOsh"), 2), 'kPa')
    print("泵前压力高限:", round(get_node_value(wincc_client, "ns=1;s=t|NJ-LQS5-MNL_045.MM_HH_Threshold"), 2), 'kPa')
    print("泵前压力低限:", round(get_node_value(wincc_client, "ns=1;s=t|NJ-LQS5-MNL_045.MM_LL_Threshold"), 2), 'kPa')
    
finally:
    wincc_client.disconnect()

作者:YiSLWLL

物联沃分享整理
物联沃-IOTWORD物联网 » Python通过OPC UA协议和WinCC通讯

发表回复