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:
- Reading a device template file
- Reading and processing the PLC information for data nodes
- Generating alarm and messaging files
Preparing
The example uses the CyberTech Engineering Nuget Feed.
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 ControlTemplate.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.");
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.
- Import the generated file as device template
- Instantiate the device template and acquire a new license from the portal
- 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:
- Adding AlarmEventPool to device information schema
- 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": []
}
]
}