Skip to main content
Version: 2.7

Example Generating DeviceInfo

The example shows how a device information model can be generated by some external data sources, like a PLC description.

The Example is written in C# and .net6.

The main program flow is:

  1. Reading a device template file
  2. Reading and processing the PLC information for data nodes
  3. Generating alarm and messaging files

Preparing

The example uses the CyberTech Engineering Nuget Feed.

NOTE

Only customers with a valid HumanOS SDK Subscription are allowed to access the Nuget Feed.

Reading the Device Info Template

First the data files are read, like:

  • PLC.json containing the PLC structure of a Siemens S7 Control
  • Template.json containing the basic structure of the HumanOS Device
//1. Read the PLC memory layout
Console.Write("Reading the PLC memory layout ...");
JObject jPlcMemory = JObject.Parse(File.ReadAllText("./InputFiles/PLC.json"));
Console.WriteLine("ok.");

//2. Reading the device template
Console.Write("Reading the device template ...");
TDeviceSchemaInfo DeviceSchema = TDeviceInfoBuilder.createFromJson(File.ReadAllText("./InputFiles/Template.json"), true);
Console.WriteLine("ok.");
NOTE

The Id of the device must correlate with a valid device license from HumanOS License Portal.

Best practice is to use this example to build a device template for HumanOS IoT Designer.

  1. Import the generated file as device template
  2. Instantiate the device template and acquire a new license from the portal
  3. Deploy the gateway afterwards.

Generating DataNodes

Data nodes are defined by the TUHALDataNodeInfo class.

The example extracts data type and address from the PLC.json and converts them to an adequate format for HumanOS.

TUHALDataNodeInfo DataNode = new TUHALDataNodeInfo(Guid.NewGuid(),
jData["Name"].ToString(),
convertToCSharpDataType(jData["DataType"].ToString()),
"",
convertToAddress("Plc1.DataBlock", jData["DataType"].ToString(), jData["Address"].ToString()),
HumanOS.Kernel.DataModel.EDataClass.Event,
true, false, true, 0, 0);

DeviceSchema.DataNodes.Add(DataNode);

Converting Siemens DataTypes

Siemens data types are mapped to data types for DataNode object:

private static string convertToCSharpDataType(string strPLCDataType)
{
Type tType = typeof(string);
switch (strPLCDataType.ToUpper())
{
case "BIT":
tType = typeof(bool);
break;

case "BYTE":
tType = typeof(byte);
break;

case "WORD":
tType = typeof(UInt16);
break;

case "DWORD":
tType = typeof(UInt32);
break;

case "INT":
tType = typeof(short);
break;

case "DINT":
tType = typeof(int);
break;

case "REAL":
tType = typeof(double);
break;

default:
throw new NotSupportedException($"PLC data type '{strPLCDataType}' is not supported.");
}
return tType.FullName;
}

Generating DataType for Siemens S7 Addresses

The HumanOS.UHAL.SiemensS7Control plugin uses a dedicated address format to access the data on a Siemens S7 control.

Following code converts the data from PLC.json to this address format:

convertToAddress("Plc1.DataBlock", jData["DataType"].ToString(), jData["Address"].ToString())

Method convertToAddress():

private static string convertToAddress(string strBase, string strPLCDataType, string strAddress)
{
StringBuilder Retval = new StringBuilder();
Retval.Append(strBase);
Retval.Append(".");

switch (strPLCDataType.ToUpper())
{
case "BIT":
Retval.Append("Bool");
break;

case "BYTE":
Retval.Append("Uint8");
break;

case "WORD":
Retval.Append("Uint16");
break;

case "DWORD":
Retval.Append("Uint32");
break;

case "INT":
Retval.Append("Int16");
break;

case "DINT":
Retval.Append("Int32");
break;

case "REAL":
Retval.Append("Float32");
break;

default:
throw new NotSupportedException($"PLC data type '{strPLCDataType}' is not supported.");
}
Retval.Append(":");
Retval.Append(strAddress);
return Retval.ToString();

Generating Alarm Event Pool

Generating alarm event pools requires two steps:

  1. Adding AlarmEventPool to device information schema
  2. Generating OEM message file for Siemens S7 including alarm text.

AlarmEventPool

The alarm event pool contains a task to update the pool based on a bitmap (array of bits).

TUHALAlarmEventPoolNodeInfo Retval = new TUHALAlarmEventPoolNodeInfo
{
Id = Guid.NewGuid(),
Name = "Alarming",
};

TAlarmEventTaskInfo Task = new TAlarmEventTaskInfo
{
Id = Guid.NewGuid(),
Name = "Alarm Messages",
Address = "Plc1.OEMAlarmEvent:0"
};

int iBitCount = (int)jAlarms["BitCount"];
int iByteCount = iBitCount % 8 == 0 ? iBitCount / 8 : iBitCount / 8 + 1;
int iAddress = jAlarms["StartAddress"].ToString().getSubString(2).toInt();

Task.Properties.Add(new TPropertyInfo("MessageMappingFile", "Messages.json")); //MAKE SURE THE FILE EXISTS BEFORE STARTING HUMANOS
Task.Properties.Add(new TPropertyInfo("MessageFormat", "BitMessage"));
Task.Properties.Add(new TPropertyInfo("StartAddress", $"{strAddressBase}.ByteArray:{iAddress}[{iByteCount}]"));
Task.Properties.Add(new TPropertyInfo("MessageCount", "System.Int32", iBitCount));

Retval.Tasks.Add(Task);

OEM Message File

The OEM message file contains all alarm messages in a HumanOS readable format.

//Generate message file
TOemMessageFile MessageFile = new TOemMessageFile();

foreach (JToken jMessage in jAlarms.SelectTokens("Messages[*]"))
{
TOemMessage Message = new TOemMessage()
{
Id = (int)jMessage["Bit"],
AlarmType = convertSiemensMessageType((string)jMessage["Type"]),
OemId = (string)jMessage["Number"],
Text = (string)jMessage["Text"]
};
MessageFile.Messages.Add(Message);
}
ostrMessageFile = TJsonSerializer.serialize(MessageFile);

Results

Device Information Model

{
"Id": "a4738fe8-28aa-4be5-b758-3b0899ac10b8",
"Name": "SiemensS7DeviceTemplate",
"DriverId": "b657332c-a383-47ee-bedd-6156af9c9083",
"Address": "Cpu=S7300;Address=192.168.44.62;Rack=0;Slot=2",
"Properties": [],
"DataNodes": [
{
"Id": "5b8d81bd-cdde-4814-ba27-22cf4fe40e47",
"Name": "SpindleSpeed",
"Address": "Plc1.DataBlock.Float32:100.14",
"DataType": "System.Double",
"Unit": "",
"HistoryMode": {
"HistoryTableName": "",
"IdField": "",
"TaggedFields": [],
"TrackedFields": []
},
"Access": {
"Read": true,
"Receive": true
},
"Properties": [
{
"Name": "Description",
"DataType": "System.String",
"Value": "Spindle Speed im m/s"
}
]
},
{
"Id": "0ba1dea6-e859-4bb2-8818-61ac7b4aaeff",
"Name": "Override",
"Address": "Plc1.DataBlock.Float32:100.15",
"DataType": "System.Double",
"Unit": "",
"HistoryMode": {
"HistoryTableName": "",
"IdField": "",
"TaggedFields": [],
"TrackedFields": []
},
"Access": {
"Read": true,
"Receive": true
},
"Properties": [
{
"Name": "Description",
"DataType": "System.String",
"Value": "Spindle Override Switch in %"
}
]
},
{
"Id": "5fb8d348-f63d-41d2-9005-997ca70ffcda",
"Name": "PartCounter",
"Address": "Plc1.Counter.Uint32:1",
"DataType": "System.UInt32",
"Unit": "",
"HistoryMode": {
"HistoryTableName": "",
"IdField": "",
"TaggedFields": [],
"TrackedFields": []
},
"Access": {
"Read": true,
"Receive": true
},
"Properties": [
{
"Name": "Description",
"DataType": "System.String",
"Value": ""
}
]
}
],
"ConstantNodes": [],
"Commands": [],
"Groups": [],
"AlarmEventPool": {
"Id": "c1cd8bf2-a0dc-48bb-922f-0840a7bbbb84",
"Name": "Alarming",
"Tasks": [
{
"Id": "b89e2295-c6fb-4a69-8784-9dcdf0043c73",
"Name": "Alarm Messages",
"Address": "Plc1.OEMAlarmEvent:0",
"Properties": [
{
"Name": "MessageMappingFile",
"DataType": "System.String",
"Value": "Messages.json"
},
{
"Name": "MessageFormat",
"DataType": "System.String",
"Value": "BitMessage"
},
{
"Name": "StartAddress",
"DataType": "System.String",
"Value": "Plc1.DataBlock.ByteArray:900[3]"
},
{
"Name": "MessageCount",
"DataType": "System.Int32",
"Value": 20
}
]
}
],
"Properties": []
},
"Skills": [],
"TaskProcessors": [],
"ProcessingNetworks": [],
"Rules": []
}

OEM Messages

{
"Messages": [
{
"AlarmType": "Alarm",
"OemId": "A112",
"Text": "Alarm 1",
"Properties": []
},
{
"Id": 1,
"AlarmType": "Warning",
"OemId": "W05",
"Text": "Warning 1",
"Properties": []
}
]
}