Automation Interface and the Microsoft .NET Framework
The functionality presented in this chapter is not covered by the general Beckhoff support. Use at your own risk. We are unable to guarantee the correctness of the presented functions. Feedback on any errors will be appreciated.
The library BACnetExtensionCtrl.dll (supplied with TwinCAT) is available for using the Automation Interface from the Microsoft .NET Framework. The library includes wrapper functions, which facilitate programmatic creation of a TwinCAT BACnet/IP configuration with C#, for example. This chapter uses selected examples to explain how to use the library. Not all options of the implemented functions are discussed in detail. A full overview of all functions can be obtained by using Visual Studio IntelliSense. All BACnet-specific functions are located in the TwinCAT.BACnet namespace.
To use the library, references to the following files have to be added to a .NET project:
- C:\TwinCAT\Io\AddIns\BACnetExtensionCtrl.dll
- C:\TwinCAT\Io\AddIns\TCatSysManagerLib.dll
The examples are based on the following namespace declaration:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TCatSysManagerLib;
using TwinCAT.BACnet.Extension;
using TwinCAT.BACnet.ExtensionHelper;
using TwinCAT.BACnet.BACnetDefinitions;
using System.Xml;
First obtain a reference to the TwinCAT System Manager.
ITcSysManager ipSysMan = (ITcSysManager)Activator.CreateInstance(Type.GetTypeFromProgID("TCatSysManager.TcSysManager"));
BACnet device configuration
In the TwinCAT.BACnet.Extension namespace a BACnetDevice can be created with the aid of the BACnetExtension class (CreateBACnetDevice). Elements are configured in the Automation Interface with the functions ProduceXml and ConsumeXml. ProduceXml is used for read parameters, ConsumtXml for writing. To obtain default parameters, a configuration should normally be read before parameters are modified. This can be done with the wrapper function BACnetExtension.GetXml. Further wrapper functions can then be used to manipulate data in the XML document. Modified parameters can be saved with the function XmlHelper.ConsumeAll. The function XmlHelper.ConsumeParameters can be used in cases where only parameters were modified, but not process data. This makes processing of ConsumeParameters more efficient. In general, it is advisable to use the ConsumeXml functions of the BACnet library, because it includes optimisations that speed up project generation for larger projects.
Most functions of BACnetExtensionCtrl.dll support 2 options. One option uses an ITcSmTreeItem as parameter. In this case ProduceXml and ConsumeXml are called internally. In the second variant an XmlDocument is transferred, and the system expects reading and writing of the data to be realised externally. For complex operations on a TreeItem the functions with XmlDocument should be used, since ConsumeXml is a potentially calculation-intensive operation that should not be called repeatedly.
The following example describes the creation of a BACnet device. The mode for relative AmsNetIDs is activated. This is recommended for automatically generated projects, since in this case a valid AmsNetID is automatically calculated on the target system. The first four digits of the system Net ID are used for this purpose. The function SetNetworkAdapterVirtualName can be used to create the network adapter configuration via a virtual device name. This is sensible on Windows CE-based systems, since the generated configuration can be used on any CXxxx devices, without having to manually select a network adapter during commissioning. By default, the IP address is automatically obtained from the operating system: SetUseOsIpSettingsMode. In addition, automatic summertime/wintertime adjustment through the operating system is activated (ActivateOsTimeMode).
ITcSmTreeItemipDevice = BACnetExtension.CreateBACnetDevice(ipSysMan);
XmlDocument doc = BACnetExtension.GetXml(ipDevice);
BACnetDeviceConfiguration.EnableRelativeAmsNetIdMode(doc);
BACnetDeviceConfiguration.SetNetworkAdapterVirtualName(doc, "TCIXPNPE1");
// use ip given by operating system
BACnetDeviceConfiguration.SetUseOsIpSettingsMode(doc, true);
// set daylightsavings status to automatic ( is also default )
BACnetDeviceConfiguration.ActivateOsTimeMode(doc);
// Commit all changes made to BACnet-Device configuration
XmlHelper.ConsumeAll(ipDevice, doc);
BACnet server configuration
The function BACnetDeviceConfiguration.CreateBACnetServer can be used to create a BACnet server. To this end an ITcSmTreeItem must be transferred to a BACnet device, with a BACnet ID as parameter. The example explains how to configure a password and how to activate persistent data.
ITcSmTreeItem ipServer = BACnetDeviceConfiguration.CreateBACnetServer(ipDevice, 123);
doc = BACnetExtension.GetXml(ipServer);
BACnetServerConfiguration.SetPassword(doc, newBACnetCharacterString("secure"));
// enable persistent data
BACnetServerConfiguration.SetPersistentDataMode(doc, true);
BACnetServerConfiguration.SetPersistentDataUsvMode(doc, false);
BACnetServerConfiguration.SetPersistentDataInterval(doc, 30 * 60); // set persistent data interval to 30 min.
// Commit all changes made to BACnet-Server configuration
XmlHelper.ConsumeAll(ipServer, doc);
BACnet object configuration
Object creation and property access
The function CreateObject can be used to create new BACnet objects. The system supports a mode with automatic ID generation and explicit configuration with an object ID. The function WriteProperty can be used to manipulate property values. The data type is specified in <> after the function. The generally Automation Interface section describes how the property data types are derived. The function ReadProperty can be used without <> to determine the data types of properties. In this way parameters can be read, and the type of the returned .NET object generated as output.
In the example several properties are configured. For properties of data type String a suitable UTF8 coding is created automatically. In other words, the UCS2 strings used in .NET are converted to the BACnet-specific UTF8 format.
// automatic mode -> obj-id and objname are assigned automatically
ITcSmTreeItem biObj = BACnetServerConfiguration.CreateObject(ipServer, BACnetObjectType.BinaryInput);
// manual obj-id configuration
ITcSmTreeItem boObj = BACnetServerConfiguration.CreateObject(ipServer, BACnetObjectType.BinaryOutput, 17);
doc = BACnetExtension.GetXml(boObj);
BACnetObjectConfiguration.WriteProperty<string>(doc, BACnetPropertyIdentifier.Description, "компании Beckhoff"); // UTF-8 Support
BACnetObjectConfiguration.WriteProperty<string>(doc, BACnetPropertyIdentifier.ObjectName, "bo_12_x");
BACnetObjectConfiguration.WriteProperty<bool>(doc, BACnetPropertyIdentifier.OutOfService, true);
BACnetObjectConfiguration.WriteProperty<Byte[]>(doc, BACnetPropertyIdentifier.EventEnable, newByte[] { 0x05, 0xE0 });
XmlHelper.ConsumeAll(boObj, doc);
In the following example 10 MultistateValue objects are generated and configured with an increasing number of states (NumberOfStates).
// create 10 multistate value objects with increasing number of states
for (uint i = 0; i < 10; i++)
{
ITcSmTreeItem mvObj = BACnetServerConfiguration.CreateObject(ipServer, BACnetObjectType.MultiStateValue);
doc = BACnetExtension.GetXml(mvObj);
CharacterStringExtList stateText = newCharacterStringExtList();
for( int j=0; j<i+2; j++ )
stateText.AddCharacterString("MV_State_" + j.ToString());
BACnetObjectConfiguration.WriteProperty<CharacterStringExtList>(doc, BACnetPropertyIdentifier.StateText, stateText);
BACnetObjectConfiguration.WriteProperty<string>(doc, BACnetPropertyIdentifier.Description, "Object with " + i.ToString() +" states");
BACnetObjectConfiguration.WriteProperty<UInt32>(doc, BACnetPropertyIdentifier.NumberOfStates, i + 2);
XmlHelper.ConsumeAll(mvObj, doc);
}
The library can also be used to configure complex BACnet properties. In the following example the Datelist property of a Calendar object is configured with DateRange, Date and WeekNDay entries. The WriteProperty library function converts the .NET classes/objects to serial data streams (byte arrays) and stores them in the XmlDocument in HexBin format.
ITcSmTreeItem calObj = BACnetServerConfiguration.CreateObject(ipServer, BACnetObjectType.Calendar);
doc = BACnetExtension.GetXml(calObj);
BACnetCalendarEntry[] calEntries = newBACnetCalendarEntry[3];
calEntries[0] = newBACnetCalendarEntry(newBACnetDate(112, 1, 1, 255)); // Add date: 1.Jan.2012
calEntries[1] = newBACnetCalendarEntry(newBACnetDateRange(newBACnetDate(255, 1,1,255), newBACnetDate(255, 7,19,155))); // DateRange 1.7.-19.7
calEntries[2] = newBACnetCalendarEntry(newBACnetWeekNDay(255, 255, 7)); // every sunday
BACnetObjectConfiguration.WriteProperty<BACnetCalendarEntry[]>(doc, BACnetPropertyIdentifier.Datelist, calEntries);
XmlHelper.ConsumeAll(calObj, doc);
The following examples shows the configuration of a schedule object's exception schedule. The exception list will be active if the calendar object created before is active. The example demonstrates blinking with a frequency of 10 Hz of a BinaryOutput object having the object identifier instance 10.
// remember calendar object for schedule configuration
BACnetObjectIdentifier calObjId = BACnetObjectConfiguration.ReadProperty<BACnetObjectIdentifier>(doc, BACnetPropertyIdentifier.ObjectIdentifier);
ITcSmTreeItem schedObj = BACnetServerConfiguration.CreateObject(ipServer, BACnetObjectType.Schedule);
doc = BACnetExtension.GetXml(schedObj);
// blink with 10 Hz
BACnetSpecialEvent specialEvent = newBACnetSpecialEvent(calObjId, 1, newBACnetTimeValueList());
specialEvent.AddBACnetTimeValue(
newBACnetTimeValue(newBACnetTime(255, 255, 255, 0),
newBACnetValue(BACnetBinaryPV.active)));
specialEvent.AddBACnetTimeValue(
newBACnetTimeValue(newBACnetTime(255, 255, 255, 50),
newBACnetValue(BACnetBinaryPV.inactive)));
BACnetObjectConfiguration.WriteProperty<BACnetSpecialEventList>(
doc,
BACnetPropertyIdentifier.ExceptionSchedule,
newBACnetSpecialEventList(newBACnetSpecialEvent[] { specialEvent }));
// set object/property reference to BO:0
BACnetObjectConfiguration.WriteProperty<BACnetDeviceObjectPropertyReference[]>(
doc,
BACnetPropertyIdentifier.ListOfObjectPropertyRefs,
newBACnetDeviceObjectPropertyReference[]{newBACnetDeviceObjectPropertyReference(
newBACnetObjectIdentifier(BACnetObjectType.BinaryOutput, 17), BACnetPropertyIdentifier.PresentValue)});
XmlHelper.ConsumeAll(schedObj, doc);
Process data configuration
The library can also be used to activate process data of BACnet objects and link them via the LinkVariables functions of the Automation Interface. In the example a BinaryInput object and a BinaryOutput object is created, and the PresentValue properties are linked via Device2Device mapping. The function XmlHelper.GetBaseTypeBitSize can be used to determine the bit length of the property process data, which is required as parameter for the function LinkVariables.
// create to objects to be linked together ( BO is on whenever BI is on )
ITcSmTreeItem pdBiObj = BACnetServerConfiguration.CreateObject(ipServer, BACnetObjectType.BinaryInput);
ITcSmTreeItem pdBoObj = BACnetServerConfiguration.CreateObject(ipServer, BACnetObjectType.BinaryOutput);
// add property PresentValue as input process data
doc = BACnetExtension.GetXml(pdBiObj);
string biVarName = BACnetObjectConfiguration.AddInputSymbolChecked(doc, BACnetPropertyIdentifier.PresentValue, BACnetObjectType.BinaryInput, false, null);
XmlHelper.ConsumeAll(pdBiObj, doc);
// add property PresentValue as output process data with priority 10
doc = BACnetExtension.GetXml(pdBoObj);
string boVarName = BACnetObjectConfiguration.AddOutputSymbolChecked(doc, BACnetPropertyIdentifier.PresentValue_Priority10, BACnetObjectType.BinaryOutput, false, null);
XmlHelper.ConsumeAll(pdBoObj, doc);
// get Property description to get process data size of present value properties
PropEntryDesc eDesc = PropertyDescriptions.GetPropDesc(BACnetPropertyIdentifier.PresentValue);
uint bitLen = XmlHelper.GetBaseTypeBitSize(eDesc.GetProcessDataTmcType(BACnetObjectType.BinaryInput));
// link the objects via process data
ipSysMan.LinkVariables(pdBiObj.PathName + "^Inputs^" + biVarName, pdBoObj.PathName + "^Outputs^" + boVarName, 0, 0, (int)bitLen);
Optional properties
Optional properties can be activated and deactivated with the library functions EnableProperty and DisableProperty. The example also demonstrates the process of iteration over all properties of an object type. DisableProperty supports disabling of related properties. If a property is disabled that requires the existence of a second property, this is also automatically disabled. For example, to deactivate all properties of Instrinsic Reporting, it is sufficient to deactivate the property NotifyType.
ITcSmTreeItem bvObj = BACnetServerConfiguration.CreateObject(ipServer, BACnetObjectType.BinaryValue);
doc = BACnetExtension.GetXml(bvObj);
// disable all optional properties
foreach (BACnetPropertyIdentifier prop inPropertyDescriptions.GetSupportedActiveOnlineProps(doc, BACnetObjectType.BinaryValue, true))
{
eDesc = PropertyDescriptions.GetPropDesc(prop);
if (PropertyDescriptions.IsOptProp(BACnetObjectType.BinaryValue, prop))
PropertyDescriptions.DisableProperty(doc, BACnetObjectType.BinaryValue, prop);
}
XmlHelper.ConsumeAll(bvObj, doc);
Write protection configuration
The function EnablePropertyWriteProtection can be used to activate write protection for a property.
ITcSmTreeItem bv2Obj = BACnetServerConfiguration.CreateObject(ipServer, BACnetObjectType.BinaryValue);
doc = BACnetExtension.GetXml(bv2Obj);
// enable write protection for all writable properties
foreach (BACnetPropertyIdentifier prop inPropertyDescriptions.GetSupportedActiveOnlineProps(doc, BACnetObjectType.BinaryValue, true))
{
eDesc = PropertyDescriptions.GetPropDesc(prop);
if (PropertyDescriptions.IsWritable(BACnetObjectType.BinaryValue, prop))
PropertyDescriptions.EnablePropertyWriteProtection(doc, BACnetObjectType.BinaryValue, prop);
}
XmlHelper.ConsumeAll(bv2Obj, doc);
The function ObjectDescriptions.SupportedOfflineProps can be used to iterate over all properties that can be configured in the Settings dialog for the objects. In contrast, GetSupportedActiveOnlineProps iterates over all object properties that exist at runtime.
BACnet client configuration
Functions are also available for configuring remote BACnet devices. CreateBACnetClient creates a BACnet client with specified BACnet ID. The function CreateRemoteObject can be used to create BACnet objects. Process data of client objects can be activated and linked via the LinkVariables function of the Automation Interface. A RemotePdCfg has to be transferred to the functions AddInputSymbol and AddOutputSymbol, which configures the process data processing mode. RemotePdCfg consolidates parameters for read and write property access. This means that the remote configuration can initially be created and then be used for creating input and output symbols. The modes correspond to the options that can be configured in the System Manager ( Polling, COV, WriteOnChange etc.). The parameters RemotePdReadTickModulo and RemotePdWriteTickModulo can be used to specify in which cycle client interactions are to take place. In systems with many client objects, it is advisable to distribute the interactions over several cycles.
Functions with the suffix Checked verify whether a process data variable has already been activated before attempting to activate it. If necessary, the variable is deactivated and then reactivated.
ITcSmTreeItem ipClient = BACnetDeviceConfiguration.CreateBACnetClient(ipDevice, 42);
// create BACnet remote object
ITcSmTreeItem remAV = BACnetClientConfiguration.CreateRemoteObject(ipClient, BACnetObjectType.AnalogValue, 17);
doc = BACnetExtension.GetXml(remAV);
// add input symbol - read data from remote device
BACnetObjectConfiguration.AddInputSymbolChecked(
doc,
BACnetPropertyIdentifier.PresentValue,
BACnetObjectType.AnalogValue,
true,
newBACnetObjectConfiguration.RemotePdCfg(BACnetObjectConfiguration.RemotePdCfg.RemotePdReadMode.COV_POLL, 1000, 1.0f, 240));
// add output symbol - with explicit remote configuration
// write property present value on change with priority 10
BACnetObjectConfiguration.RemotePdCfg remoteCfg = newBACnetObjectConfiguration.RemotePdCfg();
remoteCfg.WriteMode = BACnetObjectConfiguration.RemotePdCfg.RemotePdWriteMode.ONCHANGE;
remoteCfg.UsePriority = true;
remoteCfg.priority = 10;
remoteCfg.RemotePdWriteTickModulo = 2;
remoteCfg.resubsInterval = 240;
remoteCfg.cycleTimeWrite = 2000;
BACnetObjectConfiguration.AddOutputSymbolChecked(
doc,
BACnetPropertyIdentifier.PresentValue,
BACnetObjectType.AnalogValue, true,
remoteCfg);
XmlHelper.ConsumeAll(remAV, doc);
Notification sink configuration
The BACnetExtensionCtrl library can also be used to create a notification sink. In the following example a notification sink with the process ID 100 is created. Subsequently a NotificationClass object is created and configured for sending local EventNotifications to the notification sink.
ITcSmTreeItem ipSink = BACnetDeviceConfiguration.CreateNotificationSink(ipDevice);
doc = BACnetExtension.GetXml(ipSink);
BACnetNotificationSinkConfiguration.SetProcessId(doc, 100);
XmlHelper.ConsumeAll(ipSink, doc);
ITcSmTreeItem ipNotifyObj = BACnetServerConfiguration.CreateObject(ipServer, BACnetObjectType.NotificationClass);
doc = BACnetExtension.GetXml(ipNotifyObj);
BACnetDestination[] destArr = newBACnetDestination[1];
destArr[0] = newBACnetDestination();
destArr[0].processIdentifier = 100;
destArr[0].recipient = newBACnetRecipient(newBACnetObjectIdentifier(BACnetObjectType.Device, 123));
BACnetObjectConfiguration.WriteProperty<BACnetDestination[]>(doc, BACnetPropertyIdentifier.RecipientList, destArr);
XmlHelper.ConsumeAll(ipNotifyObj, doc);
The configuration can then be saved. The SaveConfiguration function of the Automation Interface is used to save the configuration.
ipSysMan.SaveConfiguration("C:\\BACnetExample.tsm");
All source code extracts presented in this section are available in the form of an executable Visual Studio project in the following .zip file. The project can also be used as a starting point for a separate project.