Let’s get started by sending an OPC UA TCP Hello message to localhost.
The hello message is used to negotiate buffer size, message size, and the maximum number of chunks per message to use.
It contains the following fields:
Parameter | Datatype | Value | Comments |
---|---|---|---|
MessageType | 3 ACII bytes | HEL | from hello |
IsFinal | 1 ASCII byte | F | from final |
MessageSize | ULInt32 | 32+len(EndpointUrl) | |
ProtocolVersion | ULInt32 | 0 | uncertified stack |
ReceiverBufferSize | ULInt32 | 65536 | |
SendBufferSize | ULInt32 | 65536 | |
MaxMessageSize | ULInt32 | 0 | unlimited |
MaxChunkCount | ULInt32 | 0 | unlimited |
EndpointUrl | PascalString | opc.tcp://opycua:4840 |
Using Construct we can create a struct, containing these fields.
from construct import *
c = Struct('OPC UA TCP Hello Message',
String('MessageType', 3),
String('Reserved', 1),
ULInt32('MessageSize'),
ULInt32('ProtocolVersion'),
ULInt32('ReceiveBufferSize'),
ULInt32('SendBufferSize'),
ULInt32('MaxMessageSize'),
ULInt32('MaxChunkCount'),
PascalString('EndpointUrl',
length_field=ULInt32('length'),
encoding='utf8'),
)
We ‘ll be using 2 snippets.
- The first one will act as the OPC ua server. It simply waits and listens on a port. This is necessary because otherwise we cannot send a packet to this port.
- The second one will act as the OPC ua client. It will encode the field of the OPC UA hello message as binary data and send it to the listening port.
Wireshark will also be running, so we can capture the message.
import socket
import sys
from construct import *
c = Struct('OPC UA TCP Hello Message',
String('MessageType', 3),
String('Reserved', 1),
ULInt32('MessageSize'),
ULInt32('ProtocolVersion'),
ULInt32('ReceiveBufferSize'),
ULInt32('SendBufferSize'),
ULInt32('MaxMessageSize'),
ULInt32('MaxChunkCount'),
PascalString('EndpointUrl', length_field=ULInt32('length'), encoding='utf8'),
)
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('localhost', 4840)
sock.bind(server_address)
sock.listen(1)
while True:
print >>sys.stderr, '\nwaiting for a connection'
connection, client_address = sock.accept()
try:
data = connection.recv(53)
print >>sys.stderr, 'received "%s"' % data
unpacked_data = c.parse(data)
print >>sys.stderr, 'unpacked:', unpacked_data
finally:
connection.close()
Socket code based on an article from Doug Hellmann [pymotw_socket]
Start Wireshark and listen for OPC ua traffic [uadissector] on the loopback device (lo).
import socket
import sys
from construct import *
c = Struct('OPC UA TCP Hello Message',
String('MessageType', 3),
String('Reserved', 1),
ULInt32('MessageSize'),
ULInt32('ProtocolVersion'),
ULInt32('ReceiveBufferSize'),
ULInt32('SendBufferSize'),
ULInt32('MaxMessageSize'),
ULInt32('MaxChunkCount'),
PascalString('EndpointUrl', length_field=ULInt32('length'), encoding='utf8'),
)
x = Container(
MessageType = 'HEL' ,
Reserved = 'F' ,
MessageSize = 53 ,
ProtocolVersion = 0 ,
ReceiveBufferSize = 9000 ,
SendBufferSize = 9000 ,
MaxMessageSize = 0 ,
MaxChunkCount = 0 ,
EndpointUrl = 'opc.tcp://opycua:4841',
)
packed_data = c.build(x)
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('localhost', 4840)
sock.connect(server_address)
try:
# Send data
print >>sys.stderr, 'sending "%s"' % packed_data
sock.sendall(packed_data)
finally:
print >>sys.stderr, 'closing socket'
sock.close()
We hope this little example shows that it is a great idea to implement an OPC ua stack in python.
Anyway, here is a screenshot from Wireshark:
[pymotw_socket] | The socket code in this snippet is based on the PyMOTW article on the socket module by Doug Hellmann. |
[uadissector] | The OPC ua dissector is created by Gerhard Gappmeier from ascolab GmbH |