シーメンス#S7-1500でHTTPリクエスト(GET)を送る

今回はシーメンスのPLCを使ってHTTPのGETリクエストをサーバーに送り、そして返事をもらうのプログラムを作ります。

HTTPってなに?

The Hypertext Transfer Protocol (HTTP)簡単にいうと、データ転送のプロトコルでWWW(World Wide Web)からWeb ページ。データをやり取りするためにのものです。

いまIOTはますますすすめるのでHTTP・HTTPSにもこれからFAの世界に広がると思いあmす。

以下はHTTPのGETリクエストの中身の例です。ざっくりいうと、PLCにもSocket通信で同じなデータ構造さえ送れば行けるはずです。

こちらはもう少し詳しいの中身説明です。

もし興味ある方なら、こちらのLINK更に色々な情報書いています。

https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages

https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET

https://en.wikipedia.org/wiki/HTTP_message_body

テストサーバー

ここでDocker用のテストサーバーダウンロードできます。

https://httpbin.org/#/

もちろん、自作でもOKです。

ちなみに、今回のサーバーは自作になります。

シーメンス公式ライブラリ

一応あるみたいですが、なんだかいまいちうまく動かない場合もありますので、今回はこのライブラリから一部のCodeを使って、自分のCode更に足すのようなイメージになります。

https://cache.industry.siemens.com/dl/files/879/109763879/att_987991/v2/109763879_LHTTP_DOC_V10_en.pdf

https://support.industry.siemens.com/cs/document/109763879/library-for-http-communication-(lhttp)?dti=0&lc=en-WW

構成

実機がありませんので、PLCSIM Advanced 3.0を使ってS7-1500をシミュレーション行います。そしてRaspberry PI3があり、中にPythonのFlask Package使ってテストサーバーを立ち上げ、GET通信テストをやります。

TIA

まず、プログラム始まるまえに、実際やらないと行けないことを書き出します。

  1. GETリクエストを書きます。
  2. リクエストを送ります。
  3. サーバーからの返事をEcodeします。

の、3つですね。

プログラム

最初はSiemensからダウンロードしたライブラリをそのまま流用したいですが、どうかちょっと問題ありまして、いつもエラーが出てきますので、ライブラリの一部を切り出し、自分なりにプログラムを改造しました。

FC_CheckCharIsNumbers

このFunctionは文字が数値がどうかをチェックします。
文字をCHAR_TO_BYTEのFunctionでByte変換し、48-57の間であれば(0-9)Trueを返し、それ以外はFalseになります。

Program

FUNCTION “FC_CheckCharIsNumbers” : Bool
{ S7_Optimized_Access := ‘TRUE’ }
VERSION : 0.1
  VAR_INPUT
      “char” : Char;
  END_VAR

  VAR_TEMP
      w : Word;
      ret : Word;
  END_VAR


BEGIN

CASE CHAR_TO_BYTE(IN := #char) OF
    48..57:
        #FC_CheckCharIsNumbers := True;
       
    ELSE
        #FC_CheckCharIsNumbers := False;
       
END_CASE;

END_FUNCTION

FC_CheckRespCode

このFunctionはGetリクエストからきたのResponse Codeが正常かどうかを確認するFunctionです。もしResponse Codeが>=200 AND <=399ならTrueになります。

それ以外はFalseです。

Program

FUNCTION “FC_CheckRespCode” : Bool
{ S7_Optimized_Access := ‘TRUE’ }
VERSION : 0.1
  VAR_IN_OUT
      RcvBuffer : Array[*] of Char;
  END_VAR

  VAR_TEMP
      tempString : String;
      tempIsNumber : Bool;
      tempRespCode : UInt;
  END_VAR

  VAR CONSTANT
      cPosition : Int := 9;
      cCntString : UInt := 3;
      cNormal : UInt := 200;
      cError : UInt := 400;
  END_VAR


BEGIN

#FC_CheckRespCode := False;

IF “FC_CheckCharIsNumbers”(“char” := #RcvBuffer[#cPosition])
    AND “FC_CheckCharIsNumbers”(“char” := #RcvBuffer[#cPosition + 1])
    AND “FC_CheckCharIsNumbers”(“char” := #RcvBuffer[#cPosition + 2])
THEN
    #tempIsNumber := True;
END_IF;

   
IF #tempIsNumber THEN
   
    Chars_TO_Strg(Chars := #RcvBuffer
                  ,pChars := #cPosition
                  ,Cnt := #cCntString
                  ,Strg => #tempString
    );
    #tempRespCode := STRING_TO_UINT(#tempString);
   
    IF #tempRespCode >=#cNormal AND #tempRespCode <#cError THEN
        #FC_CheckRespCode := True;
    ELSE
        #FC_CheckRespCode := False;
    END_IF;
ELSE
    #FC_CheckRespCode := False;
END_IF;


END_FUNCTION

FC_CheckTCON_IP_v4

このFunctionではTCPU通信用のパラメータ設定が正しいかどうかをCheckするFunctionです。もし設定がALL正しいならTrueが返しており、ほかはFalseになります。

Program

FUNCTION “FC_CheckTCON_IP_v4” : Word
{ S7_Optimized_Access := ‘TRUE’ }
VERSION : 0.1
  VAR_IN_OUT
      TCON_IP_v4 {InstructionName := ‘TCON_IP_v4’; LibVersion := ‘1.0’} : TCON_IP_v4;
  END_VAR

  VAR CONSTANT
      ERROR_IP_ADDRESS_SETTING : Word := 16#8010;
      ERROR_CONECTIONTYPE : Word := 16#8011;
      ERROR_INTERFACE_ID : Word := 16#8012;
      ERROR_ID : Word := 16#8013;
      ERROR_LOCALPORT_IS_ZERO : Word := 16#8014;
      ERROR_REMOTEPORT_IS_ZERO : Word := 16#8015;
      CONFIGOK : Word := 16#7000;
  END_VAR


BEGIN

#FC_CheckTCON_IP_v4 := 0;

IF #TCON_IP_v4.RemoteAddress.ADDR[1] = 0 THEN
    #FC_CheckTCON_IP_v4 := #ERROR_IP_ADDRESS_SETTING;
    RETURN;
END_IF;

IF #TCON_IP_v4.ConnectionType <> 11
    AND #TCON_IP_v4.ConnectionType <> 17
    AND #TCON_IP_v4.ConnectionType <> 19 THEN
    #FC_CheckTCON_IP_v4 := #ERROR_CONECTIONTYPE;
    RETURN;
END_IF;

IF #TCON_IP_v4.InterfaceId = 0 THEN
    #FC_CheckTCON_IP_v4 := #ERROR_INTERFACE_ID;
    RETURN;
END_IF;

IF #TCON_IP_v4.ID = 0 THEN
    #FC_CheckTCON_IP_v4 := #ERROR_ID;
    RETURN;
END_IF;

IF #TCON_IP_v4.LocalPort = 0 THEN
    #FC_CheckTCON_IP_v4 := #ERROR_LOCALPORT_IS_ZERO;
    RETURN;
END_IF;

IF #TCON_IP_v4.RemotePort = 0 THEN
    #FC_CheckTCON_IP_v4 := #ERROR_REMOTEPORT_IS_ZERO;
    RETURN;
END_IF;

#FC_CheckTCON_IP_v4 := #CONFIGOK;



END_FUNCTION

FC_ClearBuffer

このFunctionは受信BufferのByte配列をすべて空文字にクリアします。

Program

FUNCTION “FC_ClearBuffer” : Void
{ S7_Optimized_Access := ‘TRUE’ }
VERSION : 0.1
  VAR_IN_OUT
      in : Array[*] of Char;
  END_VAR

  VAR_TEMP
      i : Int;
  END_VAR


BEGIN
FOR #i:=LOWER_BOUND(ARR:=#in, DIM:=1)
TO UPPER_BOUND(ARR:=#in, DIM:=1)
DO
   
    #in[#i]:=’ ‘;
END_FOR;


   
END_FUNCTION

FC_GetRequest_Createor

このFunctionはGetリクエストを作成します。

Program

FUNCTION “FC_GetRequest_Createor” : Void
{ S7_Optimized_Access := ‘TRUE’ }
VERSION : 0.1
  VAR_INPUT
      url : String;   // URL, i.e. “http://www.siemens.com/simatic”
      data : String;   // Optional query parameters that are appended to URL, i.e. “lang=en&q=simatic”
  END_VAR

  VAR_IN_OUT
      chars : Array[0..601] of Char;   // GET request (max. length: url (254) + data (254) + header (94)
  END_VAR

  VAR_TEMP
      tempUrl : String;   // Copy of URL
      tempHost : String;   // Host part of URL
      tempPath : String;   // Path part of URL
      tempQuery : String;   // Query part of URL
      tempPos : Int;   // General tag for positions
      tempLen : UInt;   // General tag for lengths
      tempRet : Word;   // General tag for return values
      tempRequest : Struct   // GET request
        chars : Array[0..601] of Char;   // GET request (max. length: url (254) + data (254) + header (94)
        length : UInt;   // Length of GET request
      END_STRUCT;
  END_VAR


BEGIN
#tempPath := ‘/’;

IF #data <> ” THEN // Parameter data is in use
    IF #data[1] = ‘?’ OR #data[1] = ‘&’ THEN
        #tempQuery := DELETE(IN := #data, P := 1, L := 1);
    ELSE
        #tempQuery := #data;
    END_IF;
    #tempPos := FIND(IN1 := #tempPath, IN2 := ‘?’); // Check if URL already contains query parameters
    IF #tempPos <> 0 THEN // URL already contains query parameters
        #tempQuery := CONCAT(IN1 := ‘&’, IN2 := #tempQuery); // Append query parameters with &
    ELSE // URL does not contain query parameters
        #tempQuery := CONCAT(IN1 := ”, IN2 := #tempQuery); // Append query parameters with ?
    END_IF;
END_IF;

Strg_TO_Chars(Strg := CONCAT(IN1 := ‘GET ‘, IN2 := #tempPath), // Path cannot be longer than 249 characters –> save to use CONCAT
              pChars := 0,
              Cnt => #tempLen,
              Chars := #tempRequest.chars);
#tempPos := UINT_TO_INT(#tempLen);

Strg_TO_Chars(Strg := #tempQuery,
              pChars := #tempPos,
              Cnt => #tempLen,
              Chars := #tempRequest.chars);
#tempPos += UINT_TO_INT(#tempLen);

Strg_TO_Chars(Strg := CONCAT(IN1 := ‘ HTTP/1.1$R$LHost: ‘, IN2 := #tempHost), // Host will not use all 254 characters –> save to use CONCAT
              pChars := #tempPos,
              Cnt => #tempLen,
              Chars := #tempRequest.chars);
#tempPos += UINT_TO_INT(#tempLen);

Strg_TO_Chars(Strg := ‘$R$LUser-Agent: SIMATIC S7-1500 (LHTTP)$R$LAccept: text/html$R$LAccept-Encoding: identity$R$LAccept-Charset: utf-8$R$L$R$L’,
              pChars := #tempPos,
              Cnt => #tempLen,
              Chars := #tempRequest.chars);
#tempRequest.length := INT_TO_UINT(#tempPos) + #tempLen;

#chars := #tempRequest.chars;

END_FUNCTION

FB_TCPIPGetRequestCreator

このFunction Blockでは、Getリクエストを作成し、TCPIPを通信しサーバーへリクエスト送ることができます。もちろん、エラー出る場合はエラーBitが返します。

urlはServerのアドレス。

dataはデータの中身です。

ResetはTCPIP通信のBlockとStepをリセットします。

Startは通信始まるの信号です。

注意するのは、TCPIPの通信設定はInstance直接に渡しますので事前に設定する必要がありますね。

Program

FUNCTION_BLOCK “FB_TCPIPGetRequestCreator”
{ S7_Optimized_Access := ‘TRUE’ }
VERSION : 0.1
  VAR_INPUT
      url { ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’} : String;   // URL, i.e. “http://www.siemens.com/simatic”
      data { ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’} : String;   // Optional query parameters that are appended to URL, i.e. “lang=en&q=simatic”
      Reset { ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’} : Bool;
      Start { ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’} : Bool;
  END_VAR

  VAR_OUTPUT
      Error { ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’} : Bool;
      Status { ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’} : UInt;
  END_VAR

  VAR
      Step { ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’} : UInt;
      chars { ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’} : Array[0..601] of Char;
      insTSEND_C {InstructionName := ‘TSEND_C’; LibVersion := ‘3.2’; ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’; S7_SetPoint := ‘False’} : TSEND_C;
      insTRCV {InstructionName := ‘TRCV’; LibVersion := ‘4.0’; ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’; S7_SetPoint := ‘False’} : TRCV;
      statRcvBuffer { ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’} : Array[0..1459] of Char;
      insTON {InstructionName := ‘TON_TIME’; LibVersion := ‘1.0’; ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’; S7_SetPoint := ‘False’} : Array[0..2] of TON_TIME;
      TCON_IP_V4_SEC {InstructionName := ‘TCON_IP_V4_SEC’; LibVersion := ‘1.0’; ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’; S7_SetPoint := ‘False’} : TCON_IP_V4_SEC;
      statError { ExternalAccessible := ‘False’; ExternalVisible := ‘False’; ExternalWritable := ‘False’} : Bool;
  END_VAR

  VAR CONSTANT
      cStepInit : UInt := 1;
      cStepCONT : UInt := 5;
      cStepSend : UInt := 10;
      cStepClearBuffer : UInt := 15;
      cStepRCV : UInt := 20;
      cStepRecpCodeCheck : UInt := 25;
      cStepDataProcess : UInt := 30;
      cNormal : UInt := 16#7000;
      cStepErrorTCONIPV4 : UInt := 8000;
      cStepErrorSend : UInt := 8010;
      cStepErrorRCV : UInt := 8020;
      cStepErrorRespCode : UInt := 8030;
  END_VAR


BEGIN

//Create Request
“FC_GetRequest_Createor”(url := #url
                        ,data := #data
                        ,chars := #chars
);

//Reset
IF #Reset THEN
    IF #Step >= #cStepErrorTCONIPV4 THEN
        #Step:=#cStepInit;
    END_IF;
END_IF;

//Start
IF #Start AND NOT #Reset AND #Step=0 THEN
    #Step:=#cStepInit;
END_IF;

#insTRCV(
        DATA := #statRcvBuffer
);

#insTSEND_C(
            CONNECT := #TCON_IP_V4_SEC.ConnPara
            ,LEN := 0
            ,DATA := #chars
);
//Step for Send and Receive Process
CASE #Step OF
    //
    #cStepInit:
        “FC_ClearBuffer”(in := #statRcvBuffer);
        #insTRCV.ADHOC := False;
        #insTSEND_C.CONT:=False;
        #insTSEND_C.REQ:=False;
        #insTRCV.EN_R:=False;
        #insTSEND_C.COM_RST := True;
        #statError:=False;
       
        IF “FC_CheckTCON_IP_v4″(#TCON_IP_V4_SEC.ConnPara) > #cNormal THEN
            #Step:=#cStepErrorTCONIPV4;
        END_IF;
   
        #Step:=#cStepCONT;
       
    #cStepCONT:
        #insTSEND_C.COM_RST:=False;
        #insTSEND_C.CONT:=True;
        IF  #insTSEND_C.STATUS=16#7004 THEN
            #Step:=#cStepSend;
        END_IF;
       
    #cStepSend:
        #insTSEND_C.REQ:=True;
        IF #insTSEND_C.DONE THEN
                #Step := #cStepClearBuffer;
             
        END_IF;
        IF #insTSEND_C.ERROR THEN
            #Step:=#cStepErrorSend;
        END_IF;
       
    #cStepClearBuffer:
     
      #insTSEND_C.REQ:=FALSE;
        #Step:=#cStepRCV;
        #insTRCV.EN_R := True;
       
    #cStepRCV:
       
        #insTRCV.ID := #TCON_IP_V4_SEC.ConnPara.ID;
        IF #insTRCV.NDR  OR #statRcvBuffer[0]<>’ ‘ THEN
            #Step:=#cStepRecpCodeCheck;
            #insTON[1].IN := False;
        END_IF;
       
        #insTON[1](
                  IN := TRUE
                  ,PT := T#3s
        );
        IF #insTON[1].Q THEN
            #Step:=#cStepErrorRCV;
            #insTON[1].IN := False;
        END_IF;
       
    #cStepRecpCodeCheck:
        IF “FC_CheckRespCode”(RcvBuffer:=#statRcvBuffer) THEN
            #Step:=#cStepDataProcess;
        ELSE
            #Step:=#cStepErrorRespCode;
        END_IF;
       
    #cStepDataProcess:
        #insTON[0](
                  IN := True
                  , PT := T#500ms
        );
        IF #insTON[0].Q THEN
            #insTON[0].IN := False;
            #Step := #cStepSend;
        END_IF;
       
    #cStepErrorTCONIPV4,#cStepErrorSend,#cStepErrorRCV,#cStepErrorRespCode:
        #statError:=True;
       
       
END_CASE;


#Error:=#statError;
#Status:=#Step;


END_FUNCTION_BLOCK

Flask

テストサーバーはFlaskで構築します。

https://flask.palletsprojects.com/en/2.0.x/installation/

Install

$ pip install Flask

Program

from flask import Flask

app=Flask(__name__)

@app.route(‘/’)
def hello_world():
    return “hello”

@app.route(‘/mydata’)
def get_data():
    return “data-1-2-3-4”,200


if __name__==’__main__’:
    app.run(host=’192.168.0.50′)

テスト

TIAからOB1を通信設定を入れ、FB_TCPIPGえtRequestCreatorを呼び出します。

Serverからみたら、Getリクエストがちゃんと届いていますね。

Bufferからみても、Getリクエストの返しもきちんときていますね。

以下のLinkでサンプルCODEをダウンロードしてください。

https://github.com/soup01Threes/Prototyping/blob/main/Project_GETReq.zip

Footer_Basic

Find ME

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

シェアする

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

フォローする