今回はシーメンスの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用のテストサーバーダウンロードできます。
もちろん、自作でもOKです。
ちなみに、今回のサーバーは自作になります。
シーメンス公式ライブラリ
一応あるみたいですが、なんだかいまいちうまく動かない場合もありますので、今回はこのライブラリから一部のCodeを使って、自分のCode更に足すのようなイメージになります。
構成
実機がありませんので、PLCSIM Advanced 3.0を使ってS7-1500をシミュレーション行います。そしてRaspberry PI3があり、中にPythonのFlask Package使ってテストサーバーを立ち上げ、GET通信テストをやります。
TIA
まず、プログラム始まるまえに、実際やらないと行けないことを書き出します。
- GETリクエストを書きます。
- リクエストを送ります。
- サーバーからの返事を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