Project#TwinCAT3 x Cisco Meraki API

いまの工場ではIOT40の時代に既に様々なデータを可視化するのが課題です。しかも可視化だけではなく安全に繋がる、拡張性ある、簡単に使える、リモート操作などの要求も出てきてます。

今回はMeraki D-1 グランプという活動がたまたまネットで見つかり、参加してみようと思ってCiscoさんからMerakiのスイッチを借りてきました。なので今回の記事ではそのCisco Meraki MS120-8FPスイッチを例にしてみます。Cisco Merakiのデバイスでは設定はCloud上で管理し、APIが提供して3rd Third Partyからデバイスの状態を取得や変更することもできます。さらにWebhook機能がついておりイベント発生するときリアルタイムでお知らせすることができます。

https://www.cisco.com/c/m/ja_jp/meraki/d1grandprix.html?mkt_tok=MDEwLUtOWi01MDEAAAGDxfVEJiGnLkeiu0cJ7hQVgNQS9kegg8T6MQ2TnwmSpo6fryX03Xo0V0CcA8o_x-ya9AAjV07PXu4ShKhDYX_nU9tsAUz1HU4SBtp5IKeUJXBjB5R0k88#~detail


Configuration

こちらが今回の構成になります。Cisco Meraki MS120-8FPスイッチは自宅のルータとつながりMerakiのCloudと接続させます。

Beckhoff社のTwinCAT3(SoftwarePLC)は別PCであり、無線で別Networkからインタネット接続し、TF6760使用しMeraki APIをアクセスしてCisco Meraki MS120-8FPの状態を取得しTF1800表示します。

さらにRaspberry Piがあり、Python製のLocal Serverを立ち上げ、Ngrokサービス経由でLocal ServerをPublishします。Cisco Meraki のWebhookサービスとそのNrgok サービスを接続しEvent発生するときのお知らせをRaspberry Piにも受け取ることができます。

Raspberry PiではMQTT Brokerを構築しておりCisco Meraki のWebhookから受け取った情報をMQTT TopicにMessage をPublishします。そTopicはTwinCATがSubscribeしており、最終的にTwinCAT3が間接的Webhookの情報を受け取ることになる。MessageをLog貯めてTF1800に表示する。

Meraki Side

まず以下の記事から基本のセットアップを完了してください。

Meraki#001_Basic Setup
Meraki#002_Add Network
Meraki#003_Add your License

次は以下の記事でAPIを有効にする方法を書いています。

Meraki#004_Play with API

あなたのCisco Meraki Deviceは基本Setup完了し、API Keyも持ってるはずです。


Raspberry PI Side

MQTT Broker セットアップはこちらの記事を参考にしてください。

PythonからSubscribe/Publishする

MQTT#using Python Library as a Subscriber and Publisher

Local Server をNgrokにPublush

ngrok#Test your server in Public area

Cisco Meraki WebhookとRaspberryの連携:

Meraki#005_Webhook x Raspberrypi

Webhook をMQTTに変換する

データをWehbookから受け取ったあとにMQTT Message でTopicをPublishします。

以下はそのコードです。

import http.server
import socketserver
import json
import paho.mqtt.client as mqtt
import time

def on_connect(client,userdata,flag,rc):
        print(‘connected with result code’+str(rc))

def on_disconnect(client,userdata,rc):
        if rc!=0:
                print(‘something wrong..’)

def on_publish(client,userdata,mid):
        print(‘publish:{0}’.format(mid))

URL=’127.0.0.1′
PORT=1883
BASE=’/Switch1′
FIX_TOPIC_WEBHOOK=’Wehook’
FIX_TOPIC_ALERT=’Alert’
FIX_TOPIC_DATE=’Date’

class MyHandler(http.server.BaseHTTPRequestHandler):
        def do_POST(self):
                self.send_response(200)
                self.end_headers()
                print(self.path)
                content_length = int(self.headers[‘Content-Length’])
                #post_data = self.rfile.read(content_length)
                #post_data_string=post_data.decode()
                #post_data_json=json.loads(post_data_string)
                data_in_json=json.loads(self.rfile.read(content_length).decode())

                #d[‘sentAt’]
            #d[‘deviceName’]
            #d[‘deviceSerial’]
            #d[‘alertType’]
            #d[‘alertTypeId’]
            #d[‘alertLevel’]
            #d[‘occurredAt’]

                client=mqtt.Client()
                client.on_connect=on_connect
                client.on_disconnect=on_disconnect
                client.on_publish=on_publish
                client.connect(URL,PORT,60)
                #client.publish(‘/Switch1/hello’,’hallo’)
                NetworkName=data_in_json[‘networkName’]
                DeviceName=data_in_json[‘deviceName’]

                client.publish(‘/’+NetworkName+’/’+DeviceName+’/’+FIX_TOPIC_WEBHOOK+’/’+FIX_TOPIC_ALERT+’/ID/’,data_in_json[‘alertTypeId’])
                client.publish(‘/’+NetworkName+’/’+DeviceName+’/’+FIX_TOPIC_WEBHOOK+’/’+FIX_TOPIC_ALERT+’/Type/’,data_in_json[‘alertType’])
                client.publish(‘/’+NetworkName+’/’+DeviceName+’/’+FIX_TOPIC_WEBHOOK+’/’+FIX_TOPIC_ALERT+’/Level/’,data_in_json[‘alertLevel’])
                client.publish(‘/’+NetworkName+’/’+DeviceName+’/’+FIX_TOPIC_WEBHOOK+’/’+FIX_TOPIC_ALERT+’/Occurred/’,data_in_json[‘occurredAt’])
                client.disconnect()



        def do_GET(self):
                self.send_response(200,”GET is received.”)
                print(“GET is received”)
                self.end_headers()

with socketserver.TCPServer((“”, 8001), MyHandler) as httpd:
        httpd.serve_forever()

Raspberry PIはいまMQTT Broker立ち上げてる状態でCisco Meraki Webhook サービスからのお知らせを受け取ることができ、MQTT MessageをPublishします。

TwinCAT Side

Raspberry PiとCisco Meraki側も準備が終わったところで次はBeckhoff社のIPCです。少し難しそうにみえますが、使用するLibraryはTF6701とTF6760、そして表示用のTF1800だけなので複雑ではありません。

Library

TF6701

TF6701を使えばMQTTを使ってBrokerと接続できます。BrokerはLocalなものだけではなくCloud ServicesのAWS・Google・MS Azureなども可能です。

Beckhoff#TwinCAT TF6701 TopicをSubscribeする
Beckhoff#TwinCAT TF6701 AWS IOT CoreにMessage Publishする

TF6760

TF6760を使用することによってServerにHTTPSリクエストを送信することや、JSON ObjectとPLC変数間の変換などができます。

Beckhoff#TwinCAT3 TF6760 HTTPS/REST GETリクエスト送信する

TF1800

表示用のLibraryです。


Points when using Meraki API

FIXHeader

MerakiのAPI使用するにはすべてのRequestもX-Cisco-Meraki-API-KeyのHeaderをつける必要があります。TwinCATでHeaderを追加するにFB_IotHttpHeaderFieldMapというFunction Blockを使用しています。

{
    “X-Cisco-Meraki-API-Key”: <Meraki_API_Key>
}

もしそのHeaderなしのリクエストをAPIに送信したら401  UnauthorizedがReturnします。

{ “errors”: [ “Invalid API key” ] }

https://developer.cisco.com/meraki/api-v1/#!authorization

Response 302

APIのドキュメントによりますと、そのAPI Base URLはこちらです。

https://api.meraki.com/api/v1

でもTwinCAT3のTF6760を使用するとリクエストは確かに正常に届きますが、Response 302が戻ります。Beckhoff TF6760のManualによりますと、TF6760のIOT DriverはRedirect Supportしません、なのでRedirect後のメッセージ、つまり100%正しいURLを引数として渡す必要があります。例えばHTTP Status Code 301ではServerがClientにリソースを別の場所に移動し、LocationのHeader FieldはそのHTTP Responsに付けてReturnしますね。

なので、最初にStatus 301,302がReturnされたらAPI URLをHeader Fieldの値に置き換えましょう。

ちなみに、Merakiの場合は以下のURLになります。

n424.meraki.com


Array of Json Object

Meraki APIの返答はたまにJsonの配列がくるので、GetArrayValueByIdxやGetArraySizeのMethodでJSONを引き出す必要があります。


Function Block

次は今回使用するFunction Blockを紹介します。一部は前回のTutorialで説明したのでよかったら参考にしてください。

Beckhoff#TwinCAT3 TF6760 HTTPS/REST GETリクエスト送信する

FB_IotHttpHeaderFieldMap

このFunction BlockをHTTPリクエストにHeaderを追加できます。そしてAddField () Methodを呼び出してHeaderを追加します。最後はそのFunction BlockをHttp Commandの引数として渡します。

VAR_OUTPUT

VariablesData TypeDescription
bError BOOL True=エラー発生中
hrErrorCode HRESULT ADS Return Code

Method – AddField

呼び出すとHeaderを追加します。

VAR_INPUT
VariablesData TypeDescription
sField STRING HTTP Requestに追加するHeader
sValue STRING そのHeader値
bAppendValue BOOL False=現在のHeader付加するTrue=現在のHeaderをクリアして追加
Return Value
VariablesData TypeDescription
AddField BOOL True=Method 実行成功

Implementation – Meraki API Side

GET Organizations

現在のorganizationsをリストアップして戻す。

https://developer.cisco.com/meraki/api-v1/#!get-organizations

Request

GET /organizations

curl https://api.meraki.com/api/v1/organizations \
  -L -H ‘X-Cisco-Meraki-API-Key: {MERAKI-API-KEY}’

Response

JSON Arrayが回答します。


Flow

Json Obejctが有効かをCheck>そしてJSON処理Reference が有効かCheck>Json Array からJsonObjectを取り出し、該当するMemeberがあるとさらにそのJson変数をTwinCAT変数に変換します。

DUT_Meraki_API_organizations

APIから返答されるObjectにそってDUTを作成します。

TYPE DUT_Meraki_API_organizations :
STRUCT
Id :STRING;
Url :STRING;
Name :STRING;

Licensing :STRING;
API :Bool;
Cloud :STRING;
END_STRUCT
END_TYPE


FC_Meraki_Organizations_JsonData_Encoder

その関数はAPIから返答されたJSON Objectを引数として渡し、内部で文字列などに変換します。

VAR
FUNCTION FC_Meraki_Organizations_JsonData_Encoder : DUT_Meraki_API_organizations
VAR_INPUT
jsonDoc    :SJsonValue;
FBJson     :REFERENCE TO FB_JsonDomParser;

END_VAR
VAR
udi32JsonObjectLength: UDINT;
jsonDocBase:SJsonValue;
jsonval: SJsonValue;
bValid:BOOL;
END_VAR


PROGRAM
// Reset all the Return to Zero
FC_Meraki_Organizations_JsonData_Encoder.Id:=”;
FC_Meraki_Organizations_JsonData_Encoder.Cloud:=”;
FC_Meraki_Organizations_JsonData_Encoder.API:=FALSE;
FC_Meraki_Organizations_JsonData_Encoder.Licensing:=”;
FC_Meraki_Organizations_JsonData_Encoder.Name:=”;
FC_Meraki_Organizations_JsonData_Encoder.Url:=”;

//Check the Reference is Valid or not
bValid:=__ISVALIDREF(FBJson);

IF jsonDoc <> 0 and bValid THEN

//Get the Basic Object, Json arary is returned from Meraki Api, and we only use index0 in here
udi32JsonObjectLength:=fbJson.GetArraySize(v:=jsonDoc);
jsonDocBase:=fbJson.GetArrayValueByIdx(v:=jsonDoc,idx:=udi32JsonObjectLength-1);

//Get id
IF fbJson.HasMember(v:=jsonDocBase,member:=’id’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’id’);
FC_Meraki_Organizations_JsonData_Encoder.Id:=fbJson.GetString(v:=jsonval);
END_IF

//Get Url
IF fbJson.HasMember(v:=jsonDocBase,member:=’url’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’url’);
FC_Meraki_Organizations_JsonData_Encoder.Url:=fbJson.GetString(v:=jsonval);
END_IF

//Get Name
IF fbJson.HasMember(v:=jsonDocBase,member:=’name’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’name’);
FC_Meraki_Organizations_JsonData_Encoder.name:=fbJson.GetString(v:=jsonval);
END_IF

//Get Licensing
IF fbJson.HasMember(v:=jsonDocBase,member:=’licensing’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’licensing’);
jsonval:=fbJson.FindMember(v:=jsonval,member:=’model’);
FC_Meraki_Organizations_JsonData_Encoder.Licensing:=fbJson.GetString(v:=jsonval);
END_IF

//Get Api
IF fbJson.HasMember(v:=jsonDocBase,member:=’api’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’api’);
jsonval:=fbJson.FindMember(v:=jsonval,member:=’enabled’);
FC_Meraki_Organizations_JsonData_Encoder.API:=fbJson.GetBool(v:=jsonval);
END_IF

//Get Cloud
IF fbJson.HasMember(v:=jsonDocBase,member:=’cloud’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’cloud’);
jsonval:=fbJson.FindMember(v:=jsonval,member:=’region’);
jsonval:=fbJson.FindMember(v:=jsonval,member:=’name’);
FC_Meraki_Organizations_JsonData_Encoder.Cloud:=fbJson.GetString(v:=jsonval);
END_IF
END_IF

GET Networks

現在Network情報が返答されます。

https://developer.cisco.com/meraki/api-v1/#!get-network

Request

/networks/{networkId}

curl -L –request GET \
–url https://api.meraki.com/api/v1/networks/{networkId} \
–header ‘Content-Type: application/json’ \
–header ‘Accept: application/json’ \
–header ‘X-Cisco-Meraki-API-Key: 6bec40cf957de430a6f1f2baa056b99a4fac9ea0’

Response

Network ObjectがReturnされます。

Flow

Json Obejctが有効かをCheck>そしてJSON処理Reference が有効かCheck>Json Array からJsonObjectを取り出し、該当するMemeberがあるとさらにそのJson変数をTwinCAT変数に変換します。

DUT_Meraki_API_Networks

APIから返答されるObjectにそってDUTを作成します。

TYPE DUT_Meraki_API_Networks :
STRUCT

Id :STRING;
OrganizationId :STRING;
NetworkType :STRING;
Name :STRING;
TimeZone :STRING;


END_STRUCT
END_TYPE

FC_Meraki_Networks_JsonData_Encoder 

その関数はAPIから返答されたJSON Objectを引数として渡し、内部で文字列などに変換します。

VAR
FUNCTION FC_Meraki_Networks_JsonData_Encoder : DUT_Meraki_API_Networks
VAR_INPUT
jsonDoc :SJsonValue;
FBJson :REFERENCE TO FB_JsonDomParser;
END_VAR
VAR
udi32JsonObjectLength: UDINT;
jsonDocBase:SJsonValue;
jsonval: SJsonValue;
bValid:BOOL;
END_VAR
PROGRAM
// Reset all the Return to Zero
FC_Meraki_Networks_JsonData_Encoder.Id:=”;
FC_Meraki_Networks_JsonData_Encoder.Name:=”;
FC_Meraki_Networks_JsonData_Encoder.NetworkType:=”;
FC_Meraki_Networks_JsonData_Encoder.OrganizationId:=”;
FC_Meraki_Networks_JsonData_Encoder.TimeZone:=”;

//Check the Reference is Valid or not
bValid:=__ISVALIDREF(FBJson);


IF jsonDoc <> 0 AND bValid THEN

//Get the Basic Object, Json arary is returned from Meraki Api, and we only use index0 in here
udi32JsonObjectLength:=fbJson.GetArraySize(v:=jsonDoc);
jsonDocBase:=fbJson.GetArrayValueByIdx(v:=jsonDoc,idx:=udi32JsonObjectLength-1);

//Get id
IF fbJson.HasMember(v:=jsonDocBase,member:=’id’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’id’);
FC_Meraki_Networks_JsonData_Encoder.Id:=fbJson.GetString(v:=jsonval);
END_IF

IF fbJson.HasMember(v:=jsonDocBase,member:=’organizationId’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’organizationId’);
FC_Meraki_Networks_JsonData_Encoder.OrganizationId:=fbJson.GetString(v:=jsonval);
END_IF

IF fbJson.HasMember(v:=jsonDocBase,member:=’name’) THEN
FC_Meraki_Networks_JsonData_Encoder.Name:=fbJson.GetString(v:=jsonval);
END_IF

IF fbJson.HasMember(v:=jsonDocBase,member:=’timeZone’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’timeZone’);
FC_Meraki_Networks_JsonData_Encoder.TimeZone:=fbJson.GetString(v:=jsonval);
END_IF
END_IF

Get Device

現在のDevice情報が返答されます。

https://developer.cisco.com/meraki/api-v1/#!get-device

Request

GET /devices/{serial}

curl -L –request GET \
–url https://api.meraki.com/api/v1/devices/{serial} \
–header ‘Content-Type: application/json’ \
–header ‘Accept: application/json’ \
–header ‘X-Cisco-Meraki-API-Key: MERAKI-API-KEY-MERAKI-API-KEYMERAKI-API-KEY’

Response

要求したSerial番号のデバイス情報がReturnされます。

Flow

Json Obejctが有効かをCheck>そしてJSON処理Reference が有効かCheck>Json Array からJsonObjectを取り出し、該当するMemeberがあるとさらにそのJson変数をTwinCAT変数に変換します。

DUT_Meraki_API_Devices

APIから返答されるObjectにそってDUTを作成します。

TYPE DUT_Meraki_API_Devices :
STRUCT
Name :STRING;
Lat :LREAL;
Lng :LREAL;
Serial :STRING;
Mac :STRING;
Address :STRING;
Notes :STRING;
Lanip :STRING;
Firmware :STRING;
END_STRUCT
END_TYPE

FC_Meraki_Devices_SetRequestURL

リクエストのURLが複雑になり、別の関数を作成しURLを構築します。

VAR
FUNCTION FC_Meraki_Devices_SetRequestURL : String
VAR_INPUT
id:STRING;
END_VAR
VAR
sId:STRING;
END_VAR
PROGRAM
sId:=”;
sId:=CONCAT(‘/api/v1/networks/’,id);
sId:=CONCAT(sId,’/devices’);

FC_Meraki_Devices_SetRequestURL:=sId;

FC_Meraki_Networks_JsonData_Encoder

その関数はAPIから返答されたJSON Objectを引数として渡し、内部で文字列などに変換します。

VAR
FUNCTION FC_Meraki_Networks_JsonData_Encoder : DUT_Meraki_API_Networks
VAR_INPUT
jsonDoc :SJsonValue;
FBJson :REFERENCE TO FB_JsonDomParser;
END_VAR
VAR
udi32JsonObjectLength: UDINT;
jsonDocBase:SJsonValue;
jsonval: SJsonValue;
bValid:BOOL;
END_VAR
PROGRAM
// Reset all the Return to Zero
FC_Meraki_Networks_JsonData_Encoder.Id:=”;
FC_Meraki_Networks_JsonData_Encoder.Name:=”;
FC_Meraki_Networks_JsonData_Encoder.NetworkType:=”;
FC_Meraki_Networks_JsonData_Encoder.OrganizationId:=”;
FC_Meraki_Networks_JsonData_Encoder.TimeZone:=”;

//Check the Reference is Valid or not
bValid:=__ISVALIDREF(FBJson);


IF jsonDoc <> 0 AND bValid THEN

//Get the Basic Object, Json arary is returned from Meraki Api, and we only use index0 in here
udi32JsonObjectLength:=fbJson.GetArraySize(v:=jsonDoc);
jsonDocBase:=fbJson.GetArrayValueByIdx(v:=jsonDoc,idx:=udi32JsonObjectLength-1);

//Get id
IF fbJson.HasMember(v:=jsonDocBase,member:=’id’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’id’);
FC_Meraki_Networks_JsonData_Encoder.Id:=fbJson.GetString(v:=jsonval);
END_IF

IF fbJson.HasMember(v:=jsonDocBase,member:=’organizationId’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’organizationId’);
FC_Meraki_Networks_JsonData_Encoder.OrganizationId:=fbJson.GetString(v:=jsonval);
END_IF

IF fbJson.HasMember(v:=jsonDocBase,member:=’name’) THEN
FC_Meraki_Networks_JsonData_Encoder.Name:=fbJson.GetString(v:=jsonval);
END_IF

IF fbJson.HasMember(v:=jsonDocBase,member:=’timeZone’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’timeZone’);
FC_Meraki_Networks_JsonData_Encoder.TimeZone:=fbJson.GetString(v:=jsonval);
END_IF
END_IF


Get Device Switch Ports

デバイスのPorts情報全部取得します。

Request

GET /devices/{serial}/switch/ports/statuses

curl -L –request GET \
–url https://api.meraki.com/api/v1/devices/{serial}/switch/ports/statuses \
–header ‘Content-Type: application/json’ \
–header ‘Accept: application/json’ \
–header ‘X-Cisco-Meraki-API-Key: ‘MERAKI-API-KEY-MERAKI-API-KEYMERAKI-API-KEY’

Response

す. すべてのPorts情報が含まれてるJSON ArrayがReturnされます。

Flow

Json Obejctが有効かをCheck>そしてJSON処理Reference が有効かCheck>Json Array からJsonObjectを取り出し、該当するMemeberがあるとさらにそのJson変数をTwinCAT変数に変換します。


DUT_Meraki_API_Ports

APIから返答されるObjectにそってDUTを作成します。

TYPE DUT_Meraki_API_Ports :
STRUCT
PortID :STRING;
Name :STRING;
Enabled :BOOL;
PoeEnabled :BOOL;
PortType :STRING;
Vlan :LINT;
VoiceVlan :LINT;
IsolationEnabled     :BOOL;
LinkNegotiation :STRING;
Udid :STRING;
END_STRUCT
END_TYPE

FC_Meraki_Ports_SetRequestUrl 

リクエストのURLが複雑になり、別の関数を作成しURLを構築します。

VAR
FUNCTION FC_Meraki_Ports_SetRequestUrl : String
VAR_INPUT
id:STRING;
END_VAR
VAR
sId:STRING;
END_VAR
PROGRAM
sId:=”;
sId:=CONCAT(‘/api/v1/devices/’,id);
sId:=CONCAT(sId,’/switch/ports’);

FC_Meraki_Ports_SetRequestUrl:=sId;

FC_Meraki_Ports_JsonData_Enconder 

その関数はAPIから返答されたJSON Objectを引数として渡し、内部で文字列などに変換します。

VAR
FUNCTION FC_Meraki_Ports_JsonData_Enconder : ARRAY[1..10] OF DUT_Meraki_API_Ports
VAR_INPUT
jsonDoc:SJsonValue;
FBJson :REFERENCE TO FB_JsonDomParser;
END_VAR
VAR
udi32JsonObjectLength: UDINT;
jsonDocBase:SJsonValue;
jsonval: SJsonValue;
bValid:BOOL;
i:INT;
END_VAR
PROGRAM
// Reset all the Return to Zero

FOR i := 1 TO 10 DO
FC_Meraki_Ports_JsonData_Enconder[i].Enabled:=FALSE;
FC_Meraki_Ports_JsonData_Enconder[i].IsolationEnabled:=FALSE;
FC_Meraki_Ports_JsonData_Enconder[i].LinkNegotiation:=”;
FC_Meraki_Ports_JsonData_Enconder[i].Name:=”;
FC_Meraki_Ports_JsonData_Enconder[i].PoeEnabled:=FALSE;
FC_Meraki_Ports_JsonData_Enconder[i].PortID:=”;
FC_Meraki_Ports_JsonData_Enconder[i].PortType:=”;
FC_Meraki_Ports_JsonData_Enconder[i].Udid:=”;
FC_Meraki_Ports_JsonData_Enconder[i].Vlan:=0;
FC_Meraki_Ports_JsonData_Enconder[i].VoiceVlan:=0;
END_FOR

//Check the Reference is Valid or not
bValid:=__ISVALIDREF(FBJson);


IF jsonDoc <> 0 AND bValid THEN
//Get the Basic Object, Json arary is returned from Meraki Api
udi32JsonObjectLength:=fbJson.GetArraySize(v:=jsonDoc);

FOR i:=1 TO UDINT_TO_INT(udi32JsonObjectLength) DO
jsonDocBase:=fbJson.GetArrayValueByIdx(v:=jsonDoc,idx:=i-1);
//Get id
IF fbJson.HasMember(v:=jsonDocBase,member:=’name’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’name’);
FC_Meraki_Ports_JsonData_Enconder[i].Name:=fbJson.GetString(v:=jsonval);
END_IF
IF fbJson.HasMember(v:=jsonDocBase,member:=’portId’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’portId’);
FC_Meraki_Ports_JsonData_Enconder[i].PortID:=fbJson.GetString(v:=jsonval);
END_IF
IF fbJson.HasMember(v:=jsonDocBase,member:=’enabled’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’enabled’);
FC_Meraki_Ports_JsonData_Enconder[i].Enabled:=fbJson.GetBool(v:=jsonval);
END_IF
IF fbJson.HasMember(v:=jsonDocBase,member:=’poeEnabled’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’poeEnabled’);
FC_Meraki_Ports_JsonData_Enconder[i].PoeEnabled:=fbJson.GetBool(v:=jsonval);
END_IF
IF fbJson.HasMember(v:=jsonDocBase,member:=’type’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’type’);
FC_Meraki_Ports_JsonData_Enconder[i].PortType:=fbJson.GetString(v:=jsonval);
END_IF
IF fbJson.HasMember(v:=jsonDocBase,member:=’vlan’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’vlan’);
FC_Meraki_Ports_JsonData_Enconder[i].Vlan:=fbJson.GetInt64(v:=jsonval);
END_IF
IF fbJson.HasMember(v:=jsonDocBase,member:=’voiceVlan’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’voiceVlan’);
FC_Meraki_Ports_JsonData_Enconder[i].VoiceVlan:=fbJson.GetInt64(v:=jsonval);
END_IF
IF fbJson.HasMember(v:=jsonDocBase,member:=’isolationEnabled’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’isolationEnabled’);
FC_Meraki_Ports_JsonData_Enconder[i].IsolationEnabled:=fbJson.GetBool(v:=jsonval);
END_IF
IF fbJson.HasMember(v:=jsonDocBase,member:=’linkNegotiationCapabilities’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’linkNegotiationCapabilities’);
FC_Meraki_Ports_JsonData_Enconder[i].LinkNegotiation:=fbJson.GetString(v:=jsonval);
END_IF

IF fbJson.HasMember(v:=jsonDocBase,member:=’udld’) THEN
jsonval:=fbJson.FindMember(v:=jsonDocBase,’udld’);
FC_Meraki_Ports_JsonData_Enconder[i].Udid:=fbJson.GetString(v:=jsonval);
END_IF
END_FOR



END_IF

Get Device Switch Ports Statuses

該当するデバイスのすべて PortsのMonitor Statusを取得します。

https://developer.cisco.com/meraki/api-v1/#!get-device-switch-ports-statuses

Request

GET /devices/{serial}/switch/ports/statuses

curl -L –request GET \
–url https://api.meraki.com/api/v1/devices/{serial}/switch/ports/statuses \
–header ‘Content-Type: application/json’ \
–header ‘Accept: application/json’ \
–header ‘X-Cisco-Meraki-API-Key: MERAKI-API-KEY-MERAKI-API-KEYMERAKI-API-KEY’

Response

Json ArrayがReturnします。

Flow

DUT_Meraki_API_MonitorPortStatus_configOverrides

APIから返答されるObjectにそってDUTを作成します。

TYPE DUT_Meraki_API_MonitorPortStatus_configOverrides :
STRUCT
configType :STRING;
allowedVlans :STRING;
vlan :LINT;

END_STRUCT
END_TYPE

DUT_Meraki_API_MonitorPortStatus_lldp

APIから返答されるObjectにそってDUTを作成します。

TYPE DUT_Meraki_API_MonitorPortStatus_lldp :
STRUCT
systemName :STRING;
systemDescription :STRING;
portId :STRING;
portDescription :STRING;
chassisId :STRING;
managementVlan :LINT;
portVlan :LINT;
managementAddress :LINT;
systemCapabilities :STRING;
END_STRUCT
END_TYPE

DUT_Meraki_API_MonitorPortStatus_secureConnect 

APIから返答されるObjectにそってDUTを作成します。

TYPE DUT_Meraki_API_MonitorPortStatus_secureConnect :
STRUCT
enabled :BOOL;
active :BOOL;
authenticationStatus :STRING;
configOverrides :DUT_Meraki_API_MonitorPortStatus_configOverrides;
END_STRUCT
END_TYPE

DUT_Meraki_API_MonitorPortStatus_usageInKb

APIから返答されるObjectにそってDUTを作成します。

TYPE DUT_Meraki_API_MonitorPortStatus_usageInKb :
STRUCT
Total :LINT;
Sent :LINT;
Recv :LINT;
END_STRUCT
END_TYPE

DUT_Meraki_API_MonitorPortStatus_usageInKbs 

APIから返答されるObjectにそってDUTを作成します。

TYPE DUT_Meraki_API_MonitorPortStatus_usageInKbs :
STRUCT
Total :LREAL;
Sent :LREAL;
Recv :LREAL;
END_STRUCT
END_TYPE

DUT_Meraki_API_MonitorPortStatus 

TYPE DUT_Meraki_API_MonitorPortStatus :
STRUCT
portId :STRING;
enabled :BOOL;
status :STRING;
isUplink :BOOL;
errors :STRING;
warnings :STRING;
Speed :STRING;
duplex :STRING;
usageInKb :DUT_Meraki_API_MonitorPortStatus_usageInKb;
cdp :DUT_Meraki_API_MonitorPortStatus_cdp;
lldp :DUT_Meraki_API_MonitorPortStatus_lldp;
clientCount :LINT;
powerUsageInWh :LREAL;
trafficInKbps :DUT_Meraki_API_MonitorPortStatus_usageInKbs;
secureConnect :DUT_Meraki_API_MonitorPortStatus_secureConnect;


END_STRUCT
END_TYPE

FC_Meraki_MonitorStatus_SetRequestURL

リクエストのURLが複雑になり、別の関数を作成しURLを構築します。

VAR
FUNCTION FC_Meraki_MonitorStatus_SetRequestURL : String
VAR_INPUT
id:STRING;
END_VAR
VAR
sId:STRING;
END_VAR
PROGRAM
// https://developer.cisco.com/meraki/api-v1/#!get-device-switch-ports-statuses
// /api/v1/devices/{serial}/switch/ports/statuses
sId:=”;
sId:=CONCAT(‘/api/v1/devices/’,id);
sId:=CONCAT(sId,’/switch/ports/statuses’);

FC_Meraki_MonitorStatus_SetRequestURL:=sId;

FC_Meraki_MonitorStatus_JsonData_Enconder 

その関数はAPIから返答されたJSON Objectを引数として渡し、内部で文字列などに変換します。

VAR
FUNCTION FC_Meraki_MonitorStatus_JsonData_Enconder : DUT_Meraki_API_MonitorPortStatus
VAR_INPUT
jsonDoc :SJsonValue;
FBJson :REFERENCE TO FB_JsonDomParser;
END_VAR
VAR
udi32JsonObjectLength: UDINT;

jsonval: SJsonValue;
jsonvalBuffer:SJsonValue;
bValid:BOOL;
END_VAR
PROGRAM
// Reset all the Return to Zero
FC_Meraki_MonitorStatus_JsonData_Enconder.cdp.address:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.clientCount:=0;
FC_Meraki_MonitorStatus_JsonData_Enconder.duplex:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.enabled:=FALSE;
FC_Meraki_MonitorStatus_JsonData_Enconder.errors:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.isUplink:=FALSE;
FC_Meraki_MonitorStatus_JsonData_Enconder.lldp.chassisId:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.lldp.managementAddress:=0;
FC_Meraki_MonitorStatus_JsonData_Enconder.lldp.managementVlan:=0;
FC_Meraki_MonitorStatus_JsonData_Enconder.lldp.portId:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.lldp.portVlan:=0;
FC_Meraki_MonitorStatus_JsonData_Enconder.lldp.systemCapabilities:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.lldp.systemDescription:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.lldp.systemName:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.portId:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.powerUsageInWh:=0;
FC_Meraki_MonitorStatus_JsonData_Enconder.secureConnect.active:=FALSE;
FC_Meraki_MonitorStatus_JsonData_Enconder.secureConnect.authenticationStatus:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.secureConnect.configOverrides.allowedVlans:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.secureConnect.configOverrides.configType:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.secureConnect.configOverrides.vlan:=0;
FC_Meraki_MonitorStatus_JsonData_Enconder.Speed:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.status:=”;
FC_Meraki_MonitorStatus_JsonData_Enconder.trafficInKbps.Recv:=0;
FC_Meraki_MonitorStatus_JsonData_Enconder.trafficInKbps.Sent:=0;
FC_Meraki_MonitorStatus_JsonData_Enconder.trafficInKbps.Total:=0;
FC_Meraki_MonitorStatus_JsonData_Enconder.usageInKb.Recv:=0;
FC_Meraki_MonitorStatus_JsonData_Enconder.usageInKb.Sent:=0;
FC_Meraki_MonitorStatus_JsonData_Enconder.usageInKb.Total:=0;
FC_Meraki_MonitorStatus_JsonData_Enconder.warnings:=”;

//Check the Reference is Valid or not
bValid:=__ISVALIDREF(FBJson);

IF jsonDoc <> 0 AND bValid THEN
//usageInKb
IF fbJson.HasMember(v:=jsonDoc,member:=’usageInKb’) THEN
jsonval:=fbJson.FindMember(v:=jsonDoc,’usageInKb’);
//total
IF fbJson.HasMember(v:=jsonval,member:=’total’) THEN
jsonvalBuffer:=fbJson.FindMember(v:=jsonval,’total’);
FC_Meraki_MonitorStatus_JsonData_Enconder.usageInKb.Total:=fbJson.GetInt64(v:=jsonvalBuffer);
END_IF
//sent
IF fbJson.HasMember(v:=jsonval,member:=’sent’) THEN
jsonvalBuffer:=fbJson.FindMember(v:=jsonval,’sent’);
FC_Meraki_MonitorStatus_JsonData_Enconder.usageInKb.Sent:=fbJson.GetInt64(v:=jsonvalBuffer);
END_IF
//recv
IF fbJson.HasMember(v:=jsonval,member:=’recv’) THEN
jsonvalBuffer:=fbJson.FindMember(v:=jsonval,’recv’);
FC_Meraki_MonitorStatus_JsonData_Enconder.usageInKb.recv:=fbJson.GetInt64(v:=jsonvalBuffer);
END_IF
END_IF
//trafficInKbps
IF fbJson.HasMember(v:=jsonDoc,member:=’trafficInKbps’) THEN
jsonval:=fbJson.FindMember(v:=jsonDoc,’trafficInKbps’);
//total
IF fbJson.HasMember(v:=jsonval,member:=’total’) THEN
jsonvalBuffer:=fbJson.FindMember(v:=jsonval,’total’);
FC_Meraki_MonitorStatus_JsonData_Enconder.trafficInKbps.Total:=fbJson.GetDouble(v:=jsonvalBuffer);
END_IF
//sent
IF fbJson.HasMember(v:=jsonval,member:=’sent’) THEN
jsonvalBuffer:=fbJson.FindMember(v:=jsonval,’sent’);
FC_Meraki_MonitorStatus_JsonData_Enconder.trafficInKbps.Sent:=fbJson.GetDouble(v:=jsonvalBuffer);
END_IF
//recv
IF fbJson.HasMember(v:=jsonval,member:=’recv’) THEN
jsonvalBuffer:=fbJson.FindMember(v:=jsonval,’recv’);
FC_Meraki_MonitorStatus_JsonData_Enconder.trafficInKbps.recv:=fbJson.GetDouble(v:=jsonvalBuffer);
END_IF
END_IF

//portId
IF fbJson.HasMember(v:=jsonDoc,member:=’portId’) THEN
jsonval:=fbJson.FindMember(v:=jsonDoc,’portId’);
FC_Meraki_MonitorStatus_JsonData_Enconder.portId:=fbJson.GetString(v:=jsonval);
END_IF
//enabled
IF fbJson.HasMember(v:=jsonDoc,member:=’enabled’) THEN
jsonval:=fbJson.FindMember(v:=jsonDoc,’enabled’);
FC_Meraki_MonitorStatus_JsonData_Enconder.enabled:=fbJson.GetBool(v:=jsonval);
END_IF
//status
IF fbJson.HasMember(v:=jsonDoc,member:=’status’) THEN
jsonval:=fbJson.FindMember(v:=jsonDoc,’status’);
FC_Meraki_MonitorStatus_JsonData_Enconder.status:=fbJson.GetString(v:=jsonval);
END_IF
//isUplink
IF fbJson.HasMember(v:=jsonDoc,member:=’isUplink’) THEN
jsonval:=fbJson.FindMember(v:=jsonDoc,’isUplink’);
FC_Meraki_MonitorStatus_JsonData_Enconder.isUplink:=fbJson.GetBool(v:=jsonval);
END_IF
//errors
IF fbJson.HasMember(v:=jsonDoc,member:=’errors’) THEN
jsonval:=fbJson.FindMember(v:=jsonDoc,’errors’);
FC_Meraki_MonitorStatus_JsonData_Enconder.errors:=fbJson.GetString(v:=jsonval);
END_IF
//warnings
IF fbJson.HasMember(v:=jsonDoc,member:=’warnings’) THEN
jsonval:=fbJson.FindMember(v:=jsonDoc,’warnings’);
FC_Meraki_MonitorStatus_JsonData_Enconder.warnings:=fbJson.GetString(v:=jsonval);
END_IF
END_IF

pMerakiAPI

Flow

VAR

PROGRAM pMerakiAPI
VAR

fbClient        :FB_IotHttpClient;
fbRequest       :FB_IotHttpRequest;
fbheader :FB_IotHttpHeaderFieldMap;

//
bSend:BOOL;
Step:INT;
sConect:STRING(9999);

StringBuffer:STRING(512);

//json
jsonDoc:SJsonValue;

fbJson              : FB_JsonDomParser;
fbJson2             : FB_JsonDomParser;
bgetfromJson:BOOL;
sId:STRING;


//Meraki
Meraki :DUT_Meraki;


si2: UDINT;
udi32JsonObjectLength: UDINT;
jsonDocBase:SJsonValue;
i:INT;
udi32JsonSubObjectLength: UDINT;
jsonvalBuffer:SJsonValue;
END_VAR
VAR CONSTANT
cStep_Init :INT:=0;
cStep_Meraki_Get_Organizations :INT:=10;
cStep_Meraki_Process_Oranizations_Data :INT:=15;
cStep_Meraki_GetNetworks :INT:=20;
cStep_Meraki_Process_Networks_Data :INT:=21;
cStep_Meraki_GetNetworkDevices :INT:=30;
cStep_Meraki_Process_NetworkDevices_Data     :INT:=31;

cStep_Meraki_Get_Ports :INT:=40;
cStep_Meraki_Process_Ports_Data :INT:=41;
cStep_Meraki_Get_MonitorStatus :INT:=50;
cStep_Meraki_Process_MonitorStatus_Data :INT:=51;
END_VAR

PROGRAM

CASE Step OF
cStep_Init:
(*
Configurate the Fix Header of Cisco API
The Meraki Dashboard API requires a header parameter of X-Cisco-Meraki-API-Key to provide authorization for each request.
Basic Foramt
curl https://api.meraki.com/api/v1/organizations \
  -H ‘X-Cisco-Meraki-API-Key: {MERAKI-API-KEY}’
*)
fbClient.sHostName:=’n424.meraki.com’;
fbClient.nHostPort:=443;
fbClient.stTLS.bNoServerCertCheck:=TRUE;
fbheader.AddField(
sField:=’X-Cisco-Meraki-API-Key’
,sValue:=’YourAPIKEY’
,bAppendValue:=FALSE
);
bSend:=TRUE;
Step:=cStep_Meraki_Get_Organizations;

cStep_Meraki_Get_Organizations:
(*
List the organizations that the user has privileges on
/organizations
https://developer.cisco.com/meraki/api-v1/#!get-organizations
*)
IF bSend THEN
fbRequest.sContentType:=’application/json’;
IF fbRequest.SendRequest(
sUri:=’/api/v1/organizations’
,fbClient:=fbClient
,eRequestType:=ETcIotHttpRequestType.HTTP_GET
,pContent:=0
,nContentSize:=0
,fbHeader:=fbheader
) THEN
Step:=cStep_Meraki_Process_Oranizations_Data;
END_IF;
bSend:=FALSE;
END_IF
cStep_Meraki_Process_Oranizations_Data:
IF NOT fbRequest.bBusy AND NOT fbRequest.bError THEN
fbRequest.GetContent(
pContent:=ADR(sConect)
,nContentSize:=SIZEOF(sConect)
,TRUE
);
Meraki.Organizations:=
FC_Meraki_Organizations_JsonData_Encoder(
jsonDoc:=fbRequest.GetJsonDomContent(fbJson)
,FBJson:=fbJson2
);
Step:=cStep_Meraki_GetNetworks;
END_IF
cStep_Meraki_GetNetworks:
sId:=”;
sId:=CONCAT(‘/api/v1/organizations/’,Meraki.Organizations.Id);
sId:=CONCAT(sId,’/networks’);
IF fbRequest.SendRequest(
sUri:=sId
,fbClient:=fbClient
,eRequestType:=ETcIotHttpRequestType.HTTP_GET
,pContent:=0
,nContentSize:=0
,fbHeader:=fbheader
) THEN
Step:=cStep_Meraki_Process_Networks_Data;
END_IF;
cStep_Meraki_Process_Networks_Data:
IF NOT fbRequest.bBusy AND NOT fbRequest.bError THEN
fbRequest.GetContent(
pContent:=ADR(sConect)
,nContentSize:=SIZEOF(sConect)
,TRUE
);
Meraki.Networks:=
FC_Meraki_Networks_JsonData_Encoder(jsonDoc:=fbRequest.GetJsonDomContent(fbJson),FBJson:=fbJson2);
Step:=cStep_Meraki_GetNetworkDevices;
END_IF
cStep_Meraki_GetNetworkDevices:
sId:=”;
sId:=CONCAT(‘/api/v1/networks/’,Meraki.Networks.Id);
sId:=CONCAT(sId,’/devices’);
sId:=FC_Meraki_Devices_SetRequestURL(id:=Meraki.Networks.Id);
IF fbRequest.SendRequest(
sUri:=sId
,fbClient:=fbClient
,eRequestType:=ETcIotHttpRequestType.HTTP_GET
,pContent:=0
,nContentSize:=0
,fbHeader:=fbheader
) THEN
Step:=cStep_Meraki_Process_NetworkDevices_Data;
END_IF;
cStep_Meraki_Process_NetworkDevices_Data:
IF NOT fbRequest.bBusy AND NOT fbRequest.bError THEN
fbRequest.GetContent(
pContent:=ADR(sConect)
,nContentSize:=SIZEOF(sConect)
,TRUE
);
Meraki.Devices:=FC_Meraki_Devices_JsonData_Enconder(
jsonDoc:=fbRequest.GetJsonDomContent(fbJson)
,FBJson:=fbJson2
);
Step:=cStep_Meraki_Get_Ports;
END_IF
cStep_Meraki_Get_Ports:
// /api/v1/devices/{serial}/switch/ports

sId:=FC_Meraki_Ports_SetRequestUrl(id:=Meraki.Devices.Serial);
IF fbRequest.SendRequest(
sUri:=sId
,fbClient:=fbClient
,eRequestType:=ETcIotHttpRequestType.HTTP_GET
,pContent:=0
,nContentSize:=0
,fbHeader:=fbheader
) THEN
Step:=cStep_Meraki_Process_Ports_Data;
END_IF;
cStep_Meraki_Process_Ports_Data:
IF NOT fbRequest.bBusy AND NOT fbRequest.bError THEN
fbRequest.GetContent(
pContent:=ADR(sConect)
,nContentSize:=SIZEOF(sConect)
,TRUE
);
Meraki.Ports:=FC_Meraki_Ports_JsonData_Enconder(
jsonDoc:=fbRequest.GetJsonDomContent(fbJson)
,FBJson:=fbJson2
);
Step:=cStep_Meraki_Get_MonitorStatus;
END_IF
cStep_Meraki_Get_MonitorStatus:
// https://developer.cisco.com/meraki/api-v1/#!get-device-switch-ports-statuses
// /api/v1/devices/{serial}/switch/ports/statuses

sId:=FC_Meraki_MonitorStatus_SetRequestURL(id:=Meraki.Devices.Serial);
IF fbRequest.SendRequest(
sUri:=sId
,fbClient:=fbClient
,eRequestType:=ETcIotHttpRequestType.HTTP_GET
,pContent:=0
,nContentSize:=0
,fbHeader:=fbheader
) THEN
Step:=cStep_Meraki_Process_MonitorStatus_Data;
END_IF;
cStep_Meraki_Process_MonitorStatus_Data:
IF NOT fbRequest.bBusy AND NOT fbRequest.bError THEN
fbRequest.GetContent(
pContent:=ADR(sConect)
,nContentSize:=SIZEOF(sConect)
,TRUE
);
jsonDoc:=fbRequest.GetJsonDomContent(fbJson);
//
IF jsonDoc <>0 THEN
udi32JsonObjectLength:=fbJson.GetArraySize(v:=jsonDoc);
jsonDocBase:=fbJson.GetArrayValueByIdx(v:=jsonDoc,idx:=udi32JsonObjectLength-1);

//Get the Basic Object, Json arary is returned from Meraki Api, and we only use index0 in here
udi32JsonObjectLength:=fbJson.GetArraySize(v:=jsonDoc);

FOR i:=1 TO UDINT_TO_INT(udi32JsonObjectLength) DO
jsonDocBase:=fbJson.GetArrayValueByIdx(v:=jsonDoc,idx:=i-1);
Meraki.MonitorStatus[i]:=FC_Meraki_MonitorStatus_JsonData_Enconder(jsonDocBase,fbJson2);

END_FOR
Step:=cStep_Meraki_Get_Ports;
END_IF
END_IF
END_CASE




fbClient.Execute();

Implementation – Meraki Webhook

こちらはRaspberryにあるMQTT Brokerと接続するパラメータを初期化し>そのBrokerと接続します。接続に成功したらSubscribeしたTopicが新規メッセージが送信できたかをCheckします。新規メッセージがきたらそのTopicと取得したTopicが一致してるかをCheckし、TwinCAT内のBufferに格納します。すべてのTopicが来たらLogging用の配列に転送しBufferをクリアします。

Flow


pGetWebhockFromPi

VAR

PROGRAM pGetWebhockFromPi
VAR
MQTTClient:FB_IotMqttClient;
payload:STRING(255);

fbMessageQueue : FB_IotMqttMessageQueue;
fbMessage: FB_IotMqttMessage;
sTopic :STRING;

bScbscribed:BOOL;
sTopicReceived:STRING(255);

WebhookBuffer :DUT_Meraki_Webhook;
Record :ARRAY[0..99]OF DUT_Meraki_Webhook;
bShift :BOOL;
i :INT;
currentIndex :INT;
END_VAR

PROGRAM

//MQTT Client Setup
//Parameters
MQTTClient.sHostName:=’192.168.3.10′;
MQTTClient.nKeepAlive:=60;
MQTTClient.ipMessageQueue:=fbMessageQueue;
//Execute() must always Run
MQTTClient.Execute(bConnect:=TRUE);
//Subscribe Topic
sTopic:=’/MyPlant1/#’;
IF MQTTClient.bConnected THEN
IF NOT bScbscribed THEN
MQTTClient.Subscribe(
sTopic:=sTopic
,eQoS:=0
);
END_IF
END_IF

//Check Message From Broker
IF fbMessageQueue.nQueuedMessages > 0 THEN
IF fbMessageQueue.Dequeue(fbMessage:=fbMessage) THEN
//Get the Topic
fbMessage.GetTopic(pTopic:=ADR(sTopicReceived), nTopicSize:=SIZEOF(sTopicReceived));
//Compare
IF fbMessage.CompareTopic(sTopic:=’/MyPlant1/MYSWTICH1/Wehook/Alert/ID/’) THEN
fbMessage.GetPayload(pPayload:=ADR(WebhookBuffer.ID), nPayloadSize:=SIZEOF(WebhookBuffer.ID), bSetNullTermination:=FALSE);
ELSIF
fbMessage.CompareTopic(sTopic:=’/MyPlant1/MYSWTICH1/Wehook/Alert/Type/’) THEN
fbMessage.GetPayload(pPayload:=ADR(WebhookBuffer.MessageType), nPayloadSize:=SIZEOF(WebhookBuffer.MessageType), bSetNullTermination:=FALSE);
ELSIF
fbMessage.CompareTopic(sTopic:=’/MyPlant1/MYSWTICH1/Wehook/Alert/Level/’) THEN
fbMessage.GetPayload(pPayload:=ADR(WebhookBuffer.Level), nPayloadSize:=SIZEOF(WebhookBuffer.Level), bSetNullTermination:=FALSE);
ELSIF
fbMessage.CompareTopic(sTopic:=’/MyPlant1/MYSWTICH1/Wehook/Alert/Occurred/’) THEN
fbMessage.GetPayload(pPayload:=ADR(WebhookBuffer.Occurred), nPayloadSize:=SIZEOF(WebhookBuffer.Occurred), bSetNullTermination:=FALSE);
END_IF
END_IF
END_IF

IF WebhookBuffer.ID <> ” 
AND WebhookBuffer.Level <> ”
AND WebhookBuffer.MessageType <>”
AND WebhookBuffer.Occurred <> ”
THEN

Record[0].ID:=WebhookBuffer.ID;
Record[0].Level:=WebhookBuffer.Level;
Record[0].MessageType:=WebhookBuffer.MessageType;
Record[0].Occurred:=WebhookBuffer.Occurred;

MEMCPY(
destAddr:=ADR(Record[1])
,srcAddr:=ADR(Record[0])
,n:=SIZEOF(Record[0])*99
);
WebhookBuffer.ID:=”;
WebhookBuffer.Level:=”;
WebhookBuffer.MessageType:=”;
WebhookBuffer.Occurred:=”;

Record[0].ID:=”;
Record[0].Level:=”;
Record[0].MessageType:=”;
Record[0].Occurred:=”;
END_IF

DUT_Meraki 

TYPE DUT_Meraki :
STRUCT
Organizations     :DUT_Meraki_API_organizations;
Networks     :DUT_Meraki_API_Networks;
Devices     :DUT_Meraki_API_Devices;
Ports   :ARRAY[1..10]OF DUT_Meraki_API_Ports;
MonitorStatus     :ARRAY[1..10]OF DUT_Meraki_API_MonitorPortStatus;
END_STRUCT
END_TYPE


Implement – Main

PROGRAM

pMerakiAPI();
pGetWebhockFromPi();

Visitilization

Result

Sample Code

https://github.com/soup01Threes/TwinCAT3/blob/main/TwinCAT%20HTTPReqWithCiscoMerakiAPI.zip

Summary

最初に感じたのはこれから様々な機器でもHTTP/REST APIをSupportすることになると思いますので、NetworkやWEBの知識がますます重要になります。今回使用したCisco Meraki MS120-8FPは想像以上に設定が簡単で、APIのドキュメントのかなり詳しいExampleも用意されています。Ciscoのデバイスはずっと黒いTerminalで操作すると思い込んでたので結構面白かったです。

次はBeckhoff社のTwinCAT3 TF6760もかなりできて少しびっくりしました。そのTF6760はCisco Meraki のAPIでJSONの配列 Payloadでも完璧にEncodeでき、しかも難しくありません。多分PLCじゃなくてもよいのではありませんか?と思う人が多いかもしれませんが、確かにその通りでRaspberry PI x Pythonで実装するのはもっと簡単でやすいです。

でもFA現場では、丈夫・Firmware変更なし・保証ある・止まらない・誰でもメンテナンスできるのは一番必須で、そう考えるとWindowsベースのBeckhoff TwinCAT3 Runtimeを実行したほうがよいと判断しました。TwinCAT3は実機なくてもOK、機能は7日無料試せる、ライブラリが多いという利点でTwinCAT3にしました。

そして従来の工業用ネットワークスイッチでは、

  • 専用Software・Switch のLocal Web Serverから構築
  • 現場まで足運ぶのは必要
  • 取得できる情報が限定的
  • その情報を取得できる機器も限定的
  • Cloud管理は難しい

このような悩みを一部解決できるのではないか?とわたしは思っています。

最後に、みなさんもこの絵をみたことがあると思います。最初に本当にこのCisco MerakiのスイッチとTwinCATうまく繋がれるの?APIも難しそうだし、TwinCATのTF6760も初期的なことしかやっていませんし…などなどの心配がいっぱいです。

でもやってるとどんどん不安から楽しいに変わり、調べれば調べるほど面白いことやみたことないことが増えてきます。Meraki D-1グランプリの作品として他の参加者より立派ではないが、すごく勉強になりました。また使ってみたいです。

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

シェアする

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

フォローする