PythonとシーメンスPLCでSocket通信しよう

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

image.png
関数名データタイプByteアドレスDefault役目
Block_LENGTHUInt0-164この構造体のメモリ長さ、64Byteがあるから64のままでOKです。
IDCONN_OUC2-30接続の番号、Siemensは各接続はこの番号に管理されています。
CONNECTION_TYPEUSINT41717=TCP/IP,18:ISO ON TCP/IP,19=UDP
ACTIVE_ESTBOOL5FalseTrue=Active Connection、False=Passive Conncetion:
LOCAL_DEVICE_IDUSINT61PLCのPN Interface ID
LOCAL_TSAP_ID_LENUSINT70LOCAL_TSAP_IDの長さ、0=TCP、2-16=ISO-TCP、2=UDP
REM_SUBNET_ID_LENUSINT80使わない
REM_STADDR_LENUSINT94相手のアドレス長さ、0=未定義、4=REM_STADDRが有効IP(TCP-ISO)
REM_TSAP_ID_LENUSINT102REM_TSAP_IDの長さ、0、2=TCP、2-16=TCP-ISO、0(UDP)
NEXT_STADDR_LENBYTE110使わない
LOCAL_TSAP_IDARRAY[1..16]OF BYTE12-270PLCのPort番号
REM_SUBNET_IDARRAY[1..6]OF USINT28-330使わない
REM_STADDRARRAY[1..6]OF USINT34-390相手のIP
REM_TSAP_IDARRAY[1..16]OF BYTE40-550相手のPort番号
NEXT_STADDRARRAY[1..6]OF BYTE56-610使わない
SPAREWORD62-6316#0使わない

プログラム

image.png

関数の説明はそこまでで、これから実際実装しましょう。
こちらは今回のTIAバージョンです。

TCON_Param

image.png

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

dbRecvData

image.png

dbSendData

image.png

OB1

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

TCON

image.png

こちらは相手と接続する関数を呼び出します。
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です。

TRCV

image.png

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

TSEND

image.png

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

TDISCON

image.png

こちらは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();
image.png

最後はOB1でCallすればOKです。

お疲れ様です。

Footer_Basic

Please Support some devices for my blog

Amazon Gift List

Find ME

Twitter:@3threes2
Email:soup01threes*gmail.com (* to @)
YoutubeChannel:https://www.youtube.com/channel/UCQ3CHGAIXZAbeOC_9mjQiWQ

シェアする

  • このエントリーをはてなブックマークに追加

フォローする