Python通过OPC UA协议和WinCC通讯
1. 安装KEPServerEX6,使用KEPServerEX6与WinCC通讯来实时查看变量
为了便于查看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
Python(UA客户端)与WinCC(UA服务器)通讯流程如下:
1. UA客户端首次连接UA服务器时,客户端将获取服务器数字证书,验证是否可信任;
2. UA客户端接受UA服务器证书,并将其存放于Client Certificate Store;
3. UA客户端发起创建环境请求,并传递Client.der至UA服务器;
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