今回やったのはS7-1500のPLCとPythonでSocket通信のプログラムを作ろうと思います。
https://qiita.com/soup01/items/6259e9963fff856fc075
Siemens側
Siemens側使ってるのは以下の4つのFBでうございます。
- FB65 “TCON”
- 接続を確立させるの関数です。(Socket.connect()のような)
- FB66 “TDISCON” for ending the connection
- 接続を切るの関数です。(Socket.close()のような)
- FB63 “TSEND” for sending data
- データを送信する関数です。(Socket.send()のような)
- FB64 “TRECV” for receiving data
- データを受信する関数です。(Socket.recv()のような)
関数の名前がわかった時点、Socket通信するためにはIP、PORTなど設定する必要がありますが、Siemnes側にはTCON_Paramという構造体が用意しています。もちろんDefault値のままのところもあり、ちゃんと設定する必要場合もあります。ここで簡単に説明します。
関数名 | データタイプ | Byteアドレス | Default | 役目 |
---|---|---|---|---|
Block_LENGTH | UInt | 0-1 | 64 | この構造体のメモリ長さ、64Byteがあるから64のままでOKです。 |
ID | CONN_OUC | 2-3 | 0 | 接続の番号、Siemensは各接続はこの番号に管理されています。 |
CONNECTION_TYPE | USINT | 4 | 17 | 17=TCP/IP,18:ISO ON TCP/IP,19=UDP |
ACTIVE_EST | BOOL | 5 | False | True=Active Connection、False=Passive Conncetion: |
LOCAL_DEVICE_ID | USINT | 6 | 1 | PLCのPN Interface ID |
LOCAL_TSAP_ID_LEN | USINT | 7 | 0 | LOCAL_TSAP_IDの長さ、0=TCP、2-16=ISO-TCP、2=UDP |
REM_SUBNET_ID_LEN | USINT | 8 | 0 | 使わない |
REM_STADDR_LEN | USINT | 9 | 4 | 相手のアドレス長さ、0=未定義、4=REM_STADDRが有効IP(TCP-ISO) |
REM_TSAP_ID_LEN | USINT | 10 | 2 | REM_TSAP_IDの長さ、0、2=TCP、2-16=TCP-ISO、0(UDP) |
NEXT_STADDR_LEN | BYTE | 11 | 0 | 使わない |
LOCAL_TSAP_ID | ARRAY[1..16]OF BYTE | 12-27 | 0 | PLCのPort番号 |
REM_SUBNET_ID | ARRAY[1..6]OF USINT | 28-33 | 0 | 使わない |
REM_STADDR | ARRAY[1..6]OF USINT | 34-39 | 0 | 相手のIP |
REM_TSAP_ID | ARRAY[1..16]OF BYTE | 40-55 | 0 | 相手のPort番号 |
NEXT_STADDR | ARRAY[1..6]OF BYTE | 56-61 | 0 | 使わない |
SPARE | WORD | 62-63 | 16#0 | 使わない |
プログラム
関数の説明はそこまでで、これから実際実装しましょう。
こちらは今回のTIAバージョンです。
TCON_Param
まずDB作り、TypeをTCON_Paramにします。
もちろん普通のGlobal DB作って、TCON_Paramを宣言してもよいです。これは好みです。
dbRecvData
dbSendData
OB1
次はOB1にこのようなプログラムを作ります。
TCON
こちらは相手と接続する関数を呼び出します。
M1.1がONすると、接続し、COMMIDがそのConnection IDを設定します。(ここは仮に1にする)
もしエラーが出た場合はそのStatusを保存します。
そして接続したらDONEがONになりますので、そのときStepを1に入れます。
こちらはTCONの設定です。CONNECTのところは先作ったTCON_ParamのDBに入れています。つまりここでなにか変更したら直接そのDBに反映することになります。
自分のIPが192.168.0.1、Connection IDは1、自分のPortが2000、相手の接続待ち。
相手のIPが192.168.0.229、Portは2001です。
TRCV
こちらはデータ受信の関数です。M1.1が立ち上げするとDATAの引数が相手からもらったデータをここに格納します。
もし受信OKならその受信したデータの長さを保存します。
もしエラーならStatusを保存します。
そしてデータ受信問題なければStepを10にします。
TSEND
こちらはデータ送信する関数です。M1.0が立ち上げるとDATAの引数を相手に送ります。
もしエラーならStatusを保存します。
最後、送信OKならStepを1に戻ります。また受信を待ちます。
TDISCON
こちらはConnectionを切断する関数です。まぁ、Connectionを切断です。
Python側
こっちはなにも特別なライブラリー使ってません。使用するのはSocketです。
import socket
DESTINATION_ADDR = '192.168.0.1'
SOURCE_PORT, DESTINATION_PORT = 2001, 2000
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('192.168.0.229', SOURCE_PORT))
sock.connect((DESTINATION_ADDR, DESTINATION_PORT))
sock.send(b'\x11\x00\x19\x29\x30\x30\x30\x30\x21\x28')
data = sock.recv(1024)
print(repr(data))
sock.close()
おまけ
先のOB1のBlockは全部グロープアドレス使っていますが、もしSocket通信の間が何個もあれば少し不便ですね。
ここでFucntion Block化します。
Function_Block "fbSocketComm_Passive"
Var_Input
ID : CONN_OUC;
Icon : Bool;
iRetry : Int:=3;
iReset : Bool;
iDisConnect : Bool;
End_Var
Var_Output
tConERRORSts : Word;
tRecvDNR : UDInt;
tRecvERRORSts : Word;
tSendERRORSts : Word;
tConError : Bool;
tSendError : Bool;
tRecvError : Bool;
tDisconError : Bool;
End_Var
Var_In_out
CONNECT : TCON_Param;
Send : Variant;
Recv : Variant;
End_Var
Var
tcon {S7_SetPoint := 'True'} : TCON;
trcv {S7_SetPoint := 'True'} : TRCV;
tsend {S7_SetPoint := 'True'} : TSEND;
tdiscon {S7_SetPoint := 'True'} : TDISCON;
R_TRIG1 {S7_SetPoint := 'True'} : R_TRIG;
Step : Int;
cmd : Array[0..999] Of Bool;
ton : Array[0..5] Of TON_TIME;
Retry: Array[0..3] Of Int;
con : Bool;
i : Int;
End_Var
Var_Temp
End_Var
Var Constant
End_Var
//Init
#trcv.ID := #tsend.ID := #tdiscon.ID := #tcon.ID := #ID;
//Start
#R_TRIG1(
CLK:=#Icon,
Q=>#con);
//Value init
If #con And #Step = 0 Then
#Step := 10;
For #i := 0 To 3 Do
#Retry[#i] := 0;
End_For;
End_If;
//Reset
If #iReset Then
#tConError := #tRecvError := #tSendError := False;
#tConERRORSts := #tRecvERRORSts := #tSendERRORSts := 0;
End_If;
Case #Step Of
//Tcon
10:
If #tcon.DONE Then
#Step := 100;
End_If;
If #tcon.ERROR Then
#Retry[0] += 1;
If #Retry[0] > #iRetry Then
#Step := 990;
Else
#Step := 900;
End_If;
End_If;
//TRecv
100:
If #trcv.NDR Then
#tRecvDNR := #trcv.RCVD_LEN;
#Step := 200;
End_If;
If #trcv.ERROR Then
#Step := 991;
#tRecvERRORSts := #trcv.STATUS;
End_If;
//TSend
200:
If #tsend.DONE Then
#Step := 100;
End_If;
If #tsend.ERROR Then
#tSendERRORSts := #tsend.STATUS;
#Step := 100;
End_If;
//Error
900:
#Step := 10;
990:
#tConError := True;
991:
#tRecvError := True;
992:
#tSendError := True;
End_Case;
For #i := 0 To 999 Do
#cmd[#i] := False;
End_For;
#cmd[#Step] := True;
//TCON
#tcon.REQ := #cmd[10];
#tcon(CONNECT := #CONNECT);
//TRCV
#trcv.EN_R := #cmd[100];
#trcv(DATA := #Recv);
//TSEND
#tsend.REQ := #cmd[200];
#tsend(DATA := #Send);
//TDISCON
#tdiscon.REQ := #iDisConnect;
#tdiscon();
最後はOB1でCallすればOKです。
お疲れ様です。