ダミーサーバの実装#
今回、TwinCATのUDP通信のクライアントソフトを評価するため、ダミーのUDPサーバを実装します。どの言語でも構いませんが、Python上でasyncioフレームワークを使った方法が最も簡単です。このスクリプトを動作させるWindowsコンピュータ上では、ポート9998のUDPの受信を許可するようにファイヤウォール設定を行う必要があります。
UDPサーバは、ポート9998番にメッセージを受信したら、表 6.4に従いそれぞれの値を変数にマップします。
受信後は、送り主のIPアドレスとポートに対して表 6.5のメッセージを応答します。
表 6.4 Pythonのダミーサーバへの送信データ# 先頭バイト数
Python側型指定
TwinCAT型指定
サイズ
送信データ
0
5Byteの文字列
STRING(4)
5Byte
‘SCOM’ に続き、NULL文字を1byte合計5Byteを送る。
4
unsigned short
UINT
2Byte
PLC側のカウンタ値。
6
long long int
ULINT
8Byte
16#FFFFFFFFFFFFFFFF 固定値
表 6.5 Pythonのダミーサーバからの応答データ# 先頭バイト数
Python側型指定
TwinCAT型指定
サイズ
送信データ
0
5Byteの文字列
STRING(4)
5Byte
‘RCOM’ に続き、NULL文字を1byte合計5Byteを送る。
4
unsigned short
UINT
2Byte
サーバ側のカウンタ値。
6
float
REAL
4Byte
PLC側のカウンタ値
1import asyncio
2from struct import pack, unpack
3import random
4
5class DummyMeasurementMachineProtocol:
6
7 def __init__(self):
8 self.counter = 0;
9
10 def connection_made(self, transport):
11 self.transport = transport
12
13 def datagram_received(self, data, addr):
14 """
15 受信イベントハンドラ
16 struct.pack/unpackは以下のドキュメント参照のこと。
17 https://docs.python.org/ja/3/library/struct.html
18 PLCからデータ受信したバイトデータ data を以下の変数へ展開。
19 '<' : リトルエンディアンのバイト配列として解釈
20 '5s' : 5Byte 文字列を、 commandへ展開し、decode()処理して文字列型へ
21 'H' : unsigned short型(PLCではUINT型)としてsender_sequenceに展開
22 'Q' : unsigned long long int 型 (PLCではULINT型)としてvalueに展開
23 """
24 command, sender_sequence, value = unpack('<5sHQ', data)
25 command = command.decode()
26 # 標準出力への表示
27 print(f"Received {command}, {sender_sequence}, {value} from {addr}")
28 print(f"Send sequence : {self.counter} to {addr}")
29 """
30 PLCに送信する電文データをバイトデータへパック
31 '<' : リトルエンディアンのバイト配列に組み立てる
32 '5s' : 5Byte 文字列として、'RCOM'という文字にNULLを付加したバイトデータをセット
33 'H' : unsigned short型(PLCではUINT型)として受信回数のカウンタself.counter値をセット
34 'f' : float 型 (PLCではREAL型)としてsender_sequenceを型変換してセット
35 """
36 send_data = pack('<5sHf', b'RCOM\x00', self.counter, sender_sequence)
37 # 組み立てた電文をPLCに送り返す。
38 self.transport.sendto(send_data, addr)
39 # シーケンス番号を繰り上げる。unsigned shortの最大値(65535)になったらリセット。
40 if self.counter < 65535:
41 self.counter += 1;
42 else:
43 self.counter = 0;
44
45
46loop = asyncio.get_event_loop()
47print("Starting UDP server")
48# ポート9998のUDPで全ホストからの接続を待つ。
49listen = loop.create_datagram_endpoint(
50 DummyMeasurementMachineProtocol,
51 local_addr=('0.0.0.0', 9998)
52 )
53transport, protocol = loop.run_until_complete(listen)
54
55try:
56 loop.run_forever()
57except KeyboardInterrupt:
58 pass
59
60transport.close()
61loop.close()