At today’s factories, the challenge is to visualize various data because of IOT4.0. Moreover, there are demands for not only visualization but also safety, expandability, easy use, and remote operation.
This time, I applied the activity called Meraki D-1 Grump in Japan, and I borrowed a Meraki switch from Cisco to try . So, in this article, I’ll take the Cisco Meraki MS120-8FP switch as an example. On Cisco Meraki devices, the settings are managed on the Cloud, and APIs can also provide and change the device status from the 3rd Third Party. In addition, it has a webhook function that allows you to be notified in real time when an event occurs.
Configuration
Here is the configuration in this tutorial,the Cisco Meraki MS120-8FP switch is connected to my Home router for cloud connection.
Beckhoff TwinCAT3(SoftwarePLC) is connected to the internet with wireless – and using TF6760 to send the HTTP/REST request to Meraki API to get the information/status of this Cisco Meraki MS120-8FP switch.
A MQTT Broker is configured in the Raspberry Pi and receives the message from the Meraki Webhook system and changes to MQTT format.. TF6701 is used to subscribe to the following Topic and stored as a webhook info history.
Finally,TF1800 is used to display all this information.
Meraki Side
Please reference these tutorials to set up your Meraki device.
Please reference this tutorial to enable your API.
Now the basic setup of Cisco Meraki Devices is Finished and API Key is ready.
Raspberry PI Side
Please Reference this Tutorial to setup the MQTT Broker in your Raspberry pi.
Please reference this link to get more information about how to Subscribe/Public the Topic.
Please reference this link to set up a Ngrok server and Publish it to Ngrok services.
Please reference this link to get more information about how to integrate the Ngrok services to your local server and receive the webhook.
Change Webhook message to MQTT Message
Here is the Sample to start up a Http local server in Python, and receive the message from Cisco Meraki Webhook, then publish it to the MQTT Broker.
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() |
Now a MQTT broker is started up in your raspberry pi and available to receive any message from Cisco Meraki Webhook message>then use mqtt to pulich to message again.
TwinCAT Side
There are no Special things that need to be implemented in Beckhoff Side. TF6701,TF676- and TF1800 are used in this project.
Library
TF6701
TF6701 is used to Connect with a MQTT Broker.Of course this function can be connected to AWS/Google/MS Azure but not just Local Broker.
TF6760
TF6760 is used to configure and send the HTTP Request and take care of the JSON Object.
TF1800
TF1800 is used for display.
Points when using Meraki API
FIXHeader
A X-Cisco-Meraki-API-Key Header must be added into your Request while sending to the Meraki server.FB_IotHttpHeaderFieldMap is used to insert the Fix header in your http request.
{ “X-Cisco-Meraki-API-Key”: <Meraki_API_Key> } |
A 401 Unauthorized is returned while there is no A X-Cisco-Meraki-API-Key Header/Wrong Header in your Request.
{ “errors”: [ “Invalid API key” ] } |
https://developer.cisco.com/meraki/api-v1/#!authorization
Response 302
Reference to the API Documents, the base URL of the API is shown as below.
Although TF6760 of TwinCAT3 can send the request to the Meraki server without any problem, the 302 status code is returned.Reference to the TF6760 Manual, redirect is not supported – So we need to enter the 100% correct URL as the parameter.
Here is the API URL of the Cisco Meraki API.
n424.meraki.com |
Array of Json Object
Json Object is returned for the Meraki API – we would use GetArrayValueByIdx or GetArraySize method to get the element inside the json object.
Function Block
I will only explain the function block that is used in this tutorial.
Please reference this link to get more details about Beckhoff TwinCAT3 TF6760;
FB_IotHttpHeaderFieldMap
We can use this function block to add the fix header into your http request and add a header by calling the AddField() method.
Finally we only need to pass this function block directly to the HTTP Function Block.
VAR_OUTPUT
Variables | Data Type | Description |
bError | BOOL | True=Error |
hrErrorCode | HRESULT | ADS Return Code |
Method – AddField
Add a header.
VAR_INPUT
Variables | Data Type | Description |
sField | STRING | The header that you would like to add in the HTTP Request |
sValue | STRING | The header value |
bAppendValue | BOOL | False=appendTrue=clear the current header and add again |
Return Value
Variables | Data Type | Description |
AddField | BOOL | True=Method is executed |
Implementation – Meraki API Side
GET Organizations
Get the list of your 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 is returned.
Flow
Check if the Json Object is valid or not > Check if the JSON Data Process Function Block is valid or not> Get the Json Object from Json Array > Check if the Member exists in the object or not> convert it to TwinCAT variable.
DUT_Meraki_API_organizations
A DUT that is referenced by the Cisco Meraki API object.
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
A Function to convert the json Object to String and return as a Cisco meraki Structure variable.
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: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx’ |
Response
Network ObjectがReturnされます。
Flow
Check if the Json Object is valid or not > Check if the JSON Data Process Function Block is valid or not> Get the Json Object from Json Array > Check if the Member exists in the object or not> convert it to TwinCAT variable.
DUT_Meraki_API_Networks
A DUT that is referenced by the Cisco Meraki API object.
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
A Function to convert the json Object to String and return as a Cisco meraki Structure variable.
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
A API Request to get the information of the 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: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx’ |
Response
The device information is returned if the serial number is matched.
Flow
Check if the Json Object is valid or not > Check if the JSON Data Process Function Block is valid or not> Get the Json Object from Json Array > Check if the Member exists in the object or not> convert it to TwinCAT variable.
DUT_Meraki_API_Devices
A DUT that is referenced by the Cisco Meraki API object.
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
Because the URL is being complex, A separate function is used to generate the Request.
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
A Function to convert the json Object to String and return as a Cisco meraki Structure variable.
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
Get All the port information from the device.
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: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx’ |
Response
A JSON Array that includes all the port information is returned.
Flow
Check if the Json Object is valid or not > Check if the JSON Data Process Function Block is valid or not> Get the Json Object from Json Array > Check if the Member exists in the object or not> convert it to TwinCAT variable.
DUT_Meraki_API_Ports
A DUT that is referenced by the Cisco Meraki API object.
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
Because the URL is being complex, A separate function is used to generate the Request.
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
A Function to convert the json Object to String and return as a Cisco meraki Structure variable.
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
Get the Monitor status for All Ports in a device,
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: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx’ |
Response
Json ArrayがReturnします。
A JSON Array is returned.
Flow
DUT_Meraki_API_MonitorPortStatus_configOverrides
A DUT that is referenced by the Cisco Meraki API object.
TYPE DUT_Meraki_API_MonitorPortStatus_configOverrides : STRUCT configType :STRING; allowedVlans :STRING; vlan :LINT; END_STRUCT END_TYPE |
DUT_Meraki_API_MonitorPortStatus_lldp
A DUT that is referenced by the Cisco Meraki API object.
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
A DUT that is referenced by the Cisco Meraki API object.
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
A DUT that is referenced by the Cisco Meraki API object.
TYPE DUT_Meraki_API_MonitorPortStatus_usageInKb : STRUCT Total :LINT; Sent :LINT; Recv :LINT; END_STRUCT END_TYPE |
DUT_Meraki_API_MonitorPortStatus_usageInKbs
A DUT that is referenced by the Cisco Meraki API object.
TYPE DUT_Meraki_API_MonitorPortStatus_usageInKbs : STRUCT Total :LREAL; Sent :LREAL; Recv :LREAL; END_STRUCT END_TYPE |
DUT_Meraki_API_MonitorPortStatus
A DUT that is referenced by the Cisco Meraki API object.
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
Because the URL is being complex, A separate function is used to generate the Request.
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
A Function to convert the json Object to String and return as a Cisco meraki Structure variable.
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
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
I believe that more and more devices will be supported in the future because of this IOT4.0.
So the knowledge of network/Web would be very important.
Configuring a Cisco Meraki is very easy because of theirUser-friendly Dashboard, Because in my image, you can only set up the Cisco device by using Terminal and Commands…
Meraki is a really very Interesting Device.
The Next point is I am very surprised by the power of TwinCAT TF6760. This TwinCAT TF6760 can extract a standard Web API payload with a complex JSON array object(Cisco Meraki API in this case).
You may ask – Why can I replace the twincat with Raspberry and python only?
My answer is – Exactly you can, but in the Factory Automation Field, we need to confirm these devices that using in the production line are;
- Same
- maintenanable
- Easy to modify
- Non-Stop
- Easy to replace
So, the Window-base system TwinCAT3 Runtime is my choice.
And also, the classic network device in FA area,
- You may Need a specific software
- You can only access the device locally
- You can only access less information from the device
- Not all devices can access this device to get the information
- Cloud is not support
In the future, we may see this type of cloud-base network device in your next site.
Finally, You may have seen this picture before and this is my mindset before I start this project.
- Can I play with this Cisco Meraki Switch?
- Can I make the connection between TwinCAT and this switch?
- Can I understand the API document?
- Can TF6760 access decode the JSON Object?
But After I started the project, these types of anxiety are becoming too enjoyable!The more that I search, the more interesting things I have never seen.
Sure My demo was not as good as the other participants, but it was a great learning experience. I want to use it again.