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.
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.
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.
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