LoginSignup
1
1

More than 3 years have passed since last update.

PythonとSiemensPLCでSocket通信しよう

Posted at

今回やったのはS7-1500のPLCとPythonでSocket通信のプログラムを作ろうと思います。

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値のままのところもあり、ちゃんと設定する必要場合もあります。ここで簡単に説明します。

image.png

関数名 データタイプ 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バージョンです。
image.png

TCON_Param

まずDB作り、TypeをTCON_Paramにします。
もちろん普通のGlobal DB作って、TCON_Paramを宣言してもよいです。これは好みです。
image.png

dbRecvData

image.png

dbSendData

image.png

OB1

次はOB1にこのようなプログラムを作ります。

TCON

こちらは相手と接続する関数を呼び出します。
M1.1がONすると、接続し、COMMIDがそのConnection IDを設定します。(ここは仮に1にする)
もしエラーが出た場合はそのStatusを保存します。
そして接続したらDONEがONになりますので、そのときStepを1に入れます。
image.png

こちらはTCONの設定です。CONNECTのところは先作ったTCON_ParamのDBに入れています。つまりここでなにか変更したら直接そのDBに反映することになります。
自分のIPが192.168.0.1、Connection IDは1、自分のPortが2000、相手の接続待ち。
相手のIPが192.168.0.229、Portは2001です。
image.png

TRCV

こちらはデータ受信の関数です。M1.1が立ち上げするとDATAの引数が相手からもらったデータをここに格納します。
もし受信OKならその受信したデータの長さを保存します。
もしエラーならStatusを保存します。
そしてデータ受信問題なければStepを10にします。
image.png

TSEND

こちらはデータ送信する関数です。M1.0が立ち上げるとDATAの引数を相手に送ります。
もしエラーならStatusを保存します。
最後、送信OKならStepを1に戻ります。また受信を待ちます。
image.png

TDISCON

こちらはConnectionを切断する関数です。まぁ、Connectionを切断です。
image.png

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化します。

fbSocketComm_Passive

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です。
image.png

お疲れ様です。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1