Siemens#Send a HTTP Get Request from S7-1500

This time, I will use a Siemens PLC to create a program that sends HTTP GET requests to a server and receives the replies.

What is HTTP?

The Hypertext Transfer Protocol (HTTP) is,a data transfer protocol for exchanging data from the World Wide Web (WWW) to Web pages. It is used to exchange data from WWW (World Wide Web) to Web pages.

As IOT is getting more and more popular, HTTP and HTTPS are expected to spread to the world of FA in the future.

The following is an example of the contents of an HTTP GET request. In a nutshell, it should be possible to send the same data structure to PLCs via Socket communication.

Here is a more detailed description of the contents.

If you are interested, here is a link to more information.

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

Test Server

You can download a test server for Docker here.

https://httpbin.org/#/

Of course, you can also make your own.

In this Tutorial I will create a python server with Flask.

Siemens Library

You can use the library that was downloaded from Siemens, but it may not work properly in some time, I will use some codes from this library and add them to my own codes.

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

Configuration

Since I do not have an actual device, I will use PLCSIM Advanced 3.0 to simulate the S7-1500. And we have a Raspberry PI3, inside of which we will set up a test server using Python’s Flask Package to perform GET communication tests.

TIA

First, before we start the program, let’s write out what we actually need to do.

  • Write the GET request.
  • Send the request.
  • Ecode the reply from the server.

Program

I will use the Siemens library and modify the code inside to match my application in this tutorial.

FC_CheckCharIsNumbers

This Function checks if a character is a number.

The character is converted to a Byte with the Function CHAR_TO_BYTE and returns True if it is between 48-57 (0-9) and False otherwise.

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

This function checks whether the Response Code from a Get request is valid or not. If the Response Code is >=200 AND <=399, it is True.

Otherwise, it is 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

This Function checks if the parameter settings for TCP communication are correct. If the settings are ALL correct, True is returned, otherwise 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

This Function clears all Byte arrays in the receive Buffer to empty characters.

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

This Function creates a Get request.

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

This Function Block creates a Get request and sends the request to the server via TCPIP. Of course, if an error occurs, an error Bit will be returned.

  • url is the address of the server.
  • data is the content of the data.
  • Reset resets the Block and Step of the TCP communication.
  • Start is the signal to start communication.

Note that the TCP communication settings are passed directly to the Instance, so it is necessary to set them in advance.

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

The test server will be built with 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′)

Test

Put in the communication settings for OB1 from TIA and call FB_TCPIPGe tRequestCreator.

From the Server’s point of view, the Get request has been received properly.

From Buffer’s point of view, the Get request returns are also coming in properly.

Please download the sample project from my github:

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

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

シェアする

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

フォローする