最近は色々バタバタ始まりてBlogの更新は少々遅れました。今回はTF6760 Iot HTTPS/RESTについて話しします。このTF6760を使用することによってServerにHTTPSリクエストを送信することや、JSON ObjectとPLC変数間の変換などができます。
System Requirement
インストール
特になにかダウンロードする必要がありません。
Driver: TcIotDrivers.sys(TwinCAT3 XAE・XARに含まれています)
PLC library: Tc3_IotBase(TwinCAT3 XAEに含まれています)
TF6760 Iot HTTPS/REST
下図のようにTF6760を使用することによって、PLCライブラリ Tc3_IotBaseのライブラリからClientを作成し、 REST APIにあるServerにHTTPコマンド(GET/PUTなど)からデータを受送信することができます。
HTTP?
HTTPは標準なProtocolでServerとClient間でIP通信することを実現します。Client側がよくみるのはWeb Browserですね。例えばGoogle.jpを入力するとWeb Browser実はServerにWebsiteをリクエストしています。そして今回使うのはREST-WebServicesです。
注意するのはHTTP Protocolはコマンドことに独立しています。つまりServerがClientへ返信したあとにConnectionが切断します。もちろんClient側が切らないでくださいーのようなリクエストもできます。
Methods
注意するのはServerはすべてのコマンドをSupportするわけでもないです。各サーバーのAPドキュメントをしっかり読みましょう。
Status Code
HTTP返信はHeaderとBodyに分けられています。そのStatus CodeはServer側がどんな返事をClientに返すかをCheckするのは非常重要です。
(よくPage見つからないときに404やServerエラー501がみえませんか?)
ライブラリ追加
Tc3_IotBase
Tc3_JsonXml
Function Block
ちなみに今回Sample Codeで使ってないMethodやFBは説明しませんので、ご了承ください。Function Block使用するときに色々なパラメタや返信を調べる必要の場合がありますので、Chromeから右クリックし>Inspectします。
このような画面が出てきます。
下にあるNetwork Tabをクリックします。
そして先Serverに送ったリクエストをクリックすると返信の中身は確認できます。
FB_IotHttpClient
そのFBはTwinCAT SystemをHTTP Serverと通信することを確立し、特定の一つServerと接続できます。Execute()は毎周期を呼び出ししHTTP Clientとして働き、通信確率したらFB_IotHttpRequest を使用しServerにリクエスト送ります。
VAR_INPUT | ||
sHostName | STRING | Default=127.0.0.1、APIの接続先 |
nHostPort | UINT | Default=80、使用Port |
bKeepAlive | BOOL | Default=True、Serverと接続はServer側かClient側が切るまでConnection続ける。*注意するのはすべてのServerにもSupportするではない |
tConnectionTimeout | TIME | Timeout時間 |
stTLS | ST_IotSocketTls | Serer側がTLS接続必要なときで構成する必要があるパラメタ |
VAR_OUTPUT | ||
bError | STRING | True=エラーある |
hrErrorCode | HRESULT | エラーCode |
bConfigured | BOOL | True=Block設定完了 |
bConnected | BOOL | True=Hostと接続した |
Method
Disconnect | ServerのConnectionを切断する |
Execute | Background通信で使われてるメソッドです。そのメソッドは毎周期で呼び出す必要があります。 |
FB_IotHttpRequest
そのFBを使用するとTwinCAT SystemがServerにHTTP リクエスト送信することができます。Serverから来たHTTP返信はFB_IotHttpRequest.bBusyはTrue>False戻りのタイミングで処理し、最後はJSON Formatの文字列や特定のHeader Fieldから必要なデータを取り出します。
VAR_INPUT | ||
sContentType | STRING | RequestのContect Type設定 |
eCompressionMode | E_IotHttpCompressionMode | compression Mode |
VAR_OUTPUT | ||
bError | BOOL | True=エラーある |
bBusy | BOOL | True=FB実行中 |
eErrorId | ETcIotHttpRequestError | エラーID |
nStatusCode | UINT | HTTP Server返信のStatus Code |
nContentSize | UDINT | HTTP Server返信のSize |
sContentTypeの設定を確認する見えます。
eCompressionModeの設定を確認する場所:
Method
GetContent
そのメソッド使用するとHTTP返信を変数として格納できます。注意するのはFB_IotHttpRequestのBusy FlagがTrue>Falseに戻ってからそのメソッドを実行ください。
pContent | PVOID | ReadDataのメモリアドレス |
nContentSize | UDINT | そのReadDataのSize |
bSetNullTermination | BOOL | |
Return | ||
GetContent | BOOL | True=Methodが呼び出し成功 |
GetHeaderField
そのメソッド使用するとHTTP返信のHeaderを指定し読み出します。注意するのはFB_IotHttpRequestのBusy FlagがTrue>Falseに戻ってからそのメソッドを実行ください。
sField | STRING | 取りたいHeader Field |
pValue | PVOID | ReadDataのメモリアドレス |
nValueSize | UDINT | そのReadDataのSize |
Return | ||
GetHeaderField | BOOL | True=Methodが呼び出し成功 |
HeaderはInspectから確認できます。
GetJsonDomContent
そのメソッド使用するとHTTP 返信からJSON Objectに変換できます。注意するのはFB_IotHttpRequestのBusy FlagがTrue>Falseに戻ってからそのメソッドを実行ください。
fbJson | REFERENCE TO FB_JsonDomParser | FB_JsonDomParserのインスタでJSON Obejctを分解する |
Return | ||
GetJsonDomContent | SJsonValue | JSON Root Node |
SendRequest
そのメソッド使用するとHTTP Serverにリクエスト送ります。
sUri | STRING | HTTP リクエスト部分 |
fbClient | REFERENCE TO FB_IotHttpClient | FB_IotHttpClientをそのまま入れればよい |
eRequestType | ETcIotHttpRequestType | GET/POSTなどのコマンドタイプ |
pContent | PVOID | |
nContentSize | UDINT | |
fbHeader | REFERENCE TO FB_IotHttpHeaderFieldMap | |
Return | ||
SendRequest | BOOL | True=Methodが呼び出し成功 |
FB_JsonDomParser
そのFunction BlockはJson Objectを別Objectを変換することが可能です。
Method
GetJson
そのメソッドはJSON Objectから文字列に変換します。もし文字列は255より長いならNULLに戻しします。
v | SJsonValue | Json Obejct |
Return | ||
GetJson | STRING(255) | JSON Objectから変換されたString |
FindMember
そのメソッドはJSON Objectから指定のプロパティを検索し戻しします。もし該当するプロパティないなら0になります。
v | SJsonValue | Json Obejct |
member | STRING | 指定のプロパティ |
Return | ||
FindMember | SJsonValue | そのプロパティに含まれてるJson Object |
ParseDocument
文字列からJSON Object変換する関数です。
sJson | STRING | 変換したい文字列 |
Return | ||
ParseDocument | SJsonValue | 変換されたJson Object |
MemberBegin
JSON Object内に最初にあるプロパティを返しします。そのSJsonIteratorをMemberEnd() とNextMember()を一緒に使用しJSON内のプロパティと値をLoopingします。
v | SJsonValue | 取りたいの最初のプロパティ |
Return | ||
MemberBegin | SJsonIterator | JSON Object内最初のプロパティ |
MemberEnd
JSON Object内に最後にあるプロパティを返しします。そのSJsonIteratorはMemberBegin() とNextMember()を一緒に使用しJSON内のプロパティと値をLoopingします。
v | SJsonValue | 取りたいの最後のプロパティ |
Return | ||
MemberEnd | SJsonIterator | JSON Object内最後のプロパティ |
NextMember
JSON Object内にいまプロパティの次のものを返しします。そのSJsonIteratorはMemberBegin() MemberEnd()を一緒に使用しJSON内のプロパティと値をLoopingします。
v | SJsonValue | 取りたいの次のプロパティ |
Return | ||
NextMember | SJsonIterator | JSON Object内次のプロパティ |
GetMemberName
現在SJsonIteratorにあるJSONプロパティの名前を返しします。そ関数はMemberBegin() 、NextMember()とNextMember()を一緒に使用しJSON内のプロパティと値をLoopingします。
i | SJsonIterator | |
Return | ||
GetMemberName | STRING | いまプロパティの名前 |
GetMemberValue
現在SJsonIteratorにあるJSONプロパティの値を返しします。そ関数はMemberBegin() 、NextMember()とNextMember()を一緒に使用しJSON内のプロパティと値をLoopingします。
i | SJsonIterator | |
Return | ||
GetMemberValue | STRING | いまプロパティの値 |
TEST API
次はTest APIに話します。Google上でたくさんありますのでこれはその一つだけです。
今回はGET リクエストを使用し、赤枠のリクエストをServerに試します。
ChromeやFirefoxなどのBrowserで以下のLinkを入力します。
https://gorest.co.in/public/v1/users
Browserからこのような返信がきています。
PLCでは長い文字列を処理するのは少々面倒なので赤線の分だけの返信を取りたいと考えています。
リクエストを最後に/1666を入れるとid=1666のUserだけ返信が返してきます。
https://gorest.co.in/public/v1/users/1666
Example
これからはTESTAPIからHTTP RESTリクエスト送り、返事をJSONから文字列に変換したり、特定のHeaderやPropertyから値をとったりします。
Codeは少し長いですが、よろしくお願います。
_CallRestAPI
この_CallRestAPIではClientを作成・Serverと接続・リクエスト送りの仕事をやっています。次回はFunction Blockにする予定です。
VAR
VAR //Client IotHttpClient:FB_IotHttpClient; //Request IotHttpRequest:FB_IotHttpRequest; JsonDomParser:FB_JsonDomParser; R_TRIG :R_TRIG; bSendRequest:BOOL; bGetContentResult :BOOL; sContent:STRING(511); //Json jsonDoc:SJsonValue; jsonVal:SJsonValue; JsonSaxWriter:FB_JsonReadWriteDataType; jsonDocWithSpeicificMember:SJsonValue; jsonCurrentIterator:SJsonIterator; jsonLastIterator :SJsonIterator; sProperty:STRING; jsonLoopValue:SJsonValue; sHeaderValues:STRING; jsonDataWithSpecificMember:STRING(511); sJsonPropertyValue:STRING; //Unitiy nReqCount:UDINT; iState:INT; nVaildCount:INT:=0; bConfigurated:BOOL; iVaildHeaderCount:INT; bReset:BOOL; bError:BOOL; nError:INT; END_VAR |
VAR CONSTANT
VAR CONSTANT ciStepInit :INT:=0; ciStepSendRequest:INT:=10; ciStepGetContent:INT:=20; ciStepError :INT:=8000; ciHTTPSPort :UINT:=443; END_VAR |
Code
//Start to Send the Request R_TRIG( CLK:=bSendRequest ); CASE iState OF ciStepInit: IF NOT IotHttpClient.bConfigured THEN IotHttpClient.nHostPort:=ciHTTPSPort; IotHttpClient.sHostName:=’gorest.co.in’; IotHttpClient.bKeepAlive:=TRUE; IotHttpClient.tConnectionTimeout:=T#10S; IotHttpClient.stTLS.bNoServerCertCheck:=TRUE; nReqCount:=0; nVaildCount:=0; bError:=FALSE; bReset:=FALSE; nError:=DUTE_HTTPRequest_ERRORCode.E_ERR_Noraml; ELSE iState:=ciStepSendRequest; END_IF ciStepSendRequest: IF R_TRIG.Q THEN IF IotHttpRequest.SendRequest( sUri:=’/public/v1/users/62′ ,fbClient:=IotHttpClient ,eRequestType:=ETcIotHttpRequestType.HTTP_GET ,pContent:=0 ,nContentSize:=0 ,fbHeader:=0 ) THEN nReqCount:=nReqCount+1; bSendRequest:=FALSE; iState:=ciStepGetContent; END_IF; END_IF ciStepGetContent: bGetContentResult:=IotHttpRequest.GetContent( pContent:=ADR(sContent) ,nContentSize:=SIZEOF(sContent) ,bSetNullTermination:=TRUE ); IF IotHttpClient.bError THEN iState:=ciStepError; nError:=DUTE_HTTPRequest_ERRORCode.E_ERR_FB_TioHttpRequest; ELSE CASE IotHttpRequest.nStatusCode OF 200..299: //get the Json Dom from Response jsonDoc:=IotHttpRequest.GetJsonDomContent( fbJson:=JsonDomParser ); //get the Header IF IotHttpRequest.GetHeaderField( sField:=’Server’ ,pValue:=ADR(sHeaderValues) ,nValueSize:=SIZEOF(sHeaderValues) ) THEN iVaildHeaderCount:=iVaildHeaderCount+1; END_IF; //get ‘data’ property from JSON DOM jsonDataWithSpecificMember:=JsonDomParser.GetJson( v:=JsonDomParser.FindMember( jsonDoc ,’data’ ) ); //Convert it to Dom again jsonDocWithSpeicificMember:=JsonDomParser.ParseDocument( jsonDataWithSpecificMember ); //While loop + NextMember(),MemberBegin(),MemberEnd jsonCurrentIterator:=JsonDomParser.MemberBegin(jsonDoc); jsonLastIterator:=JsonDomParser.MemberEnd(jsonDoc); WHILE jsonCurrentIterator <> jsonLastIterator DO sProperty:=JsonDomParser.GetMemberName( jsonCurrentIterator ); jsonLoopValue:=JsonDomParser.GetMemberValue( jsonCurrentIterator ); IF sProperty =’email’ THEN JsonDomParser.CopyString( jsonLoopValue ,sJsonPropertyValue ,SIZEOF(sJsonPropertyValue) ); END_IF jsonCurrentIterator:=JsonDomParser.NextMember( jsonCurrentIterator ); END_WHILE iState:=ciStepSendRequest; //if json Dom from Response is 0, somethings error in your program IF jsonDoc <> 0 THEN nVaildCount:=nVaildCount+1; ELSE iState:=ciStepError; nError:=DUTE_HTTPRequest_ERRORCode.E_ERR_JsonDOM_IsZero; END_IF ciStepError: IF bReset THEN iState:=ciStepInit; END_IF END_CASE END_IF; END_CASE IotHttpClient.Execute(); |
MAIN
最後は先のPOUを呼び出しだけですね。
_CallRestAPI(); |
結果
こちらはServerから返ってきたの返事です。
特定したHeader値です。
While loop使用しmail Propertiesの値をとります。
最後に
SampleCodeどうぞ↓
シーメンスのS71500版は昔書いたことありますのでよかったらどうぞ↓
http://soup01.com/ja/2021/07/21/post-3045/
はーい、お疲れ様です。
もしなにか質問あれば、メール・コメント・Twitterなどでもどうぞ!
Twitterのご相談:@3threes2
メールのご相談:soup01threes*gmail.com (*を@に)