Automation Interface und das Microsoft .NET Framework

Die in diesem Kapitel vorgestellte Funktionalität unterliegt nicht dem allgemeinen Support durch Beckhoff. Die Benutzung erfolgt auf eigene Gefahr. Für die Fehlerfreiheit der vorgestellten Funktionen wird nicht garantiert. Rückmeldungen auf eventuelle Fehler werden gerne entgegengenommen.

Für die Nutzung des Automation Interface aus dem Microsoft .NET Framework steht die Bibliothek BACnetExtensionCtrl.dll zur Verfügung, die mit TwinCAT ausgeliefert wird. In der Bibliothek sind Wrapper-Funktionen implementiert, die das programmatische Erstellen einer TwinCAT BACnet/IP Konfiguration u.a. mit C# erleichtern. In diesem Kapitel soll anhand ausgewählter Beispiele, die Verwendung der Bibliothek erläutert werden. Dabei werden nicht alle Varianten der implementierten Funktionen eingegangen. Ein vollständige Übersicht aller Funktionen erschließt sich durch die Verwendung von IntelliSense des Visual Studio. Alle BACnet-spezifischen Funktionen sind im Namensraum TwinCAT.BACnet angesiedelt.

Um die Bibliothek verwenden zu können, müssen einem .NET-Projekt Referenzen auf folgende Dateien hinzugefügt werden:

Für die folgenden Beispiele wird von dieser Namespace-Deklaration ausgegangen:

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;

Zunächst muss eine Referenz auf den TwinCAT System Manager akquiriert werden.

ITcSysManager ipSysMan = (ITcSysManager)Activator.CreateInstance(Type.GetTypeFromProgID("TCatSysManager.TcSysManager"));

BACnet-Device Konfiguration

Im Namensraum TwinCAT.BACnet.Extension kann mit Hilfe der Klasse BACnetExtension ein BACnetDevice erstellt werden (CreateBACnetDevice). Die Konfiguration von Elementen erfolgt im Automation Interface durch die Funktionen ProduceXml und ConsumeXml. Mit Hilfe von ProduceXml können Parameter gelesen und mit Hilfe von ConsumtXml geschrieben werden. Um Default-Parameter zu erhalten, sollte generell eine Konfiguration ausgelesen werden bevor Parameter verändert werden. Dies kann mit der Wrapper-Funktion BACnetExtension.GetXml erfolgen. Nachfolgend können mit Hilfe weiterer Wrapper-Funktionen Daten im XML-Dokument manipuliert werden. Abschließend können veränderte Parameter mit der Funktion XmlHelper.ConsumeAll gespeichert werden. Die Funktion XmlHelper.ConsumeParameters kann verwendet werden, wenn nur Parameter und keine Prozessdaten verändert wurden. Die Verarbeitung von ConsumeParameters ist dabei effizienter. Generell ist zu empfehlen die ConsumeXml-Funktionen der BACnet-Bibliothek zu verwenden, da hier Optimierungen implementiert sind, die bei großen Projekten eine schnellere Generierung ermöglichen.

Die meisten Funktionen von der BACnetExtensionCtrl.dll unterstützen 2 Varianten. Eine Variante verwendet ein ITcSmTreeItem als Parameter. Hier wird intern ProduceXml und ConsumeXml aufgerufen. In der zweiten Variante wird ein XmlDocument übergeben und erwartet, das Lesen und Schreiben der Daten extern realisiert werden. Bei komplexen Operationen auf einem TreeItem sollten die Funktionen mit XmlDocument verwendet werden, da ConsumeXml eine potenziell rechenaufwendige Operation ist und nicht mehrfach aufgerufen werden sollte.

Im folgenden Beispiel wird ein BACnet-Device erstellt. Der Modus für relative AmsNetIds wird aktiviert, der bei automatisch generierten Projekten empfohlen wird, da so automatisch eine gültige AmsNetId auf dem Zielsystem berechnet wird. Dabei werden die ersten vier Stellen der System-Net-Id verwendet. Mit Hilfe der Funktion SetNetworkAdapterVirtualName kann die Netzwerk-Adapter-Konfiguration über einen virtuellen Gerätenamen erstellt werden. Dies ist auf Windows CE-basierten Systemen sinnvoll, da so die generierte Konfiguration auf beliebigen CXxxx-Geräten eingesetzt werden kann, ohne manuell einen Netzwerkadapter bei der Inbetriebnahme auswählen zu müssen. Die IP-Adresse wird in der Standardeinstellung automatisch vom Betriebssystem bezogen: SetUseOsIpSettingsMode. Zusätzlich wird die automatische Sommer-/Winterzeitanpassung durch das Betriebssystem aktiviert (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 Konfiguration

Mit Hilfe der Funktion BACnetDeviceConfiguration.CreateBACnetServer kann ein BACnet Server erstellt werden. Dabei muss ein ITcSmTreeItem auf ein BACnet Device sowie eine BACnet-ID als Parameter übergeben werden. Im Beispiel ist erläutert, wie ein Passwort konfiguriert, sowie persistente Daten aktiviert werden können.

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-Objekt Konfiguration

Objekterzeugung und Property-Zugriff

Mit Hilfe der Funktion CreateObject können neue BACnet-Objekte erstellt werden. Es wird ein Modus mit automatischer ID-Generierung unterstützt sowie die explizite Konfiguration mit einer Objekt-ID. Mit Hilfe der Funktion WriteProperty können Property-Werte manipuliert werden. Dabei wird der Typ der Daten in <> hinter der Funktionen angeben. Die Ableitung der Property-Datentypen ist im allgemeinen Automation Interface Kapitel beschrieben. Um die Datentypen von Properties zu ermitteln kann auch die Funktion ReadProperty ohne <> verwendet werden. Parameter können so ausgelesen und der Typ des zurückgegebenen .NET Objekts ausgeben werden.

Im Beispiel werden einige Properties konfiguriert. Bei Properties vom Datentyp string wird automatisch eine entsprechende UTF8-Kodierung erstellt. Das heißt die in .NET verwendeten UCS2-Zeichenketten werden in das BACnet-spezifische UTF8-Format umgewandelt.

// automatic mode -> obj-id and objname are assigned automaticallyITcSmTreeItem 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);

Im folgenden Beispiel werden 10 MultistateValue-Objekte erzeugt und jeweils mit einer wachsenden Anzahl von Zuständen (NumberOfStates) konfiguriert.

// 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);
}

Auch komplexe BACnet-Properties können mit Hilfe der Bibliothek konfiguriert werden. Im folgenden Beispiel wird die Datelist-Property eines Calendar-Objekts mit jeweils einem DateRange-, Date- und WeekNDay-Eintrag konfiguriert.Die Bibliotheksfunktion WriteProperty wandelt dabei die .NET Klassen bzw. Objekten in serielle Datenströme (Byte-Array) um und speichert sie im HexBin-Format im XmlDocument.

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);

Im folgenden Beispiel wird ein Schedule-Objekt mit einer Ausnahmeliste konfiguriert, die aktiv wird, wenn das zuvor erstellte Calendar-Objekt aktiv ist. In der Ausnahmeliste wird zur Demonstration ein Blinken mit 10 Hz eines BinaryOutput-Objekt mit der Objekt-ID 10 konfiguriert.

// 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);

Prozessdatenkonfiguration

Auch Prozessdaten von BACnet-Objekten können mit Hilfe der Bibliothek aktiviert und mit der Automation Interface Funktionen LinkVariables verknüpft werden. Im Beispiel wird jeweils ein BinaryInput- und BinaryOutput-Objekt erstellt und die PresentValue-Properties über eine Device2Device-Mapping verknüpft. Mit Hilfe der Funktion XmlHelper.GetBaseTypeBitSize kann dabei die Bitlänge der Property-Prozessdaten ermittelt werden, die als Parameter der Funktion LinkVariables benötigt wird.

// 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);

Optionale Properties

Auch optionale Properties können mit Hilfe der Bibliotheksfunktionen EnableProperty und DisableProperty aktiviert und deaktiviert werden. Zusätzlich demonstriert das Beispiel wie über alle Properties eines Objekttyps iteriert werden kann. DisableProperty unterstützt dabei das Deaktivieren zusammenhängender Properties. Wird eine Property deaktiviert, welche die Existenz einer zweiten Property voraussetzt, wird auch diese automatisch deaktiviert. Sollen z.B. alle Properties des Instrinsic Reporting deaktiviert werden, reicht das Deaktivieren der Property NotifyType aus.

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);

Schreibschutzkonfiguration

Mit Hilfe der Funktion EnablePropertyWriteProtection kann der Schreibschutz einer Property aktiviert werden.

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);

Mit Hilfe der Funktion ObjectDescriptions.SupportedOfflineProps kann auch über alle Properties iteriert werden, die im Settings-Dialog der Objekte konfiguriert werden können. Im Gegensatz iteriert GetSupportedActiveOnlineProps über alle Properties eines Objekts die zur Laufzeit existieren.

BACnet-Client Konfiguration

Auch für die Konfiguration von entfernten BACnet-Geräten stehen Funktionen zur Verfügung. CreateBACnetClient erstellt einen BACnet-Client mit angegebener BACnet-ID. Mit Hilfe der Funktion CreateRemoteObject können BACnet-Objekte erstellt werden. Auch Prozessdaten von Client-Objekten können aktiviert und mit Hilfe der Automation Interface Funktion LinkVariables verknüpft werden. Dabei muss den Funktionen AddInputSymbol und AddOutputSymbol eine RemotePdCfg übergeben werden, welche den Modus der Prozessdatenbehandlung konfiguriert. RemotePdCfg fasst Parameter für lesende und schreibende Property-Zugriffe zusammen, das heißt die Remote-Konfiguration kann einmal erstellt und dann für das Anlegen von Input- und Output-Symbolen verwendet werden. Die Modi entsprechen dabei den im System Manager einstellbaren Optionen ( Polling, COV, WriteOnChange usw. ). Durch die Parameter RemotePdReadTickModulo und RemotePdWriteTickModulo kann eingestellt werden in welchen Zyklus Client-Interaktionen stattfinden sollen. Es empfiehlt sich bei vielen Client-Objekten die Interaktionen über mehrere Zyklen zu verteilen.

Funktionen mit dem Suffix Checked überprüfen vor dem Aktivieren einer Prozessdatenvariable, ob diese schon aktiviert wurde. Ggf. wird sie deaktiviert bevor sie neu aktiviert wird.

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 Konfiguration

Auch eine Notification Sink kann mit Hilfe der BACnetExtensionCtrl-Bibliothek erstellt werden. Im folgenden Beispiel wird eine Notification Sink mit der Prozess-ID 100 erstellt. Anschließend wird ein NotificationClass-Objekt erstellt und zum Senden lokaler EventNotifications an die erstellte Notification Sink konfiguriert.

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);

Abschließend kann die erstellte Konfiguration abgespeichert werden. Das Abspeichern erfolgt mit Hilfe der Automation Interface Funktionen SaveConfiguration.

ipSysMan.SaveConfiguration("C:\\BACnetExample.tsm");

Alle in diesem Kapitel vorgestellten Quellcodeausschnitte finden sich als ausführbares Visual Studio Projekt in folgender .zip-Datei. Das Projekt kann dabei auch als Startpunkt für ein eigenes Projekt verwendet werden.

Automation Interface und das Microsoft .NET Framework 1: BACnetLibraryExample.zip