# Datentypen zur Laufzeit

> Anleitung: Strukturen und Enumerationen erst zur Laufzeit definieren, im Adressraum veröffentlichen und ihre Werte ohne eigene .NET-Typen bereitstellen.

Beteiligte Typen: `OpcServer`, `OpcStruct`, `OpcStructField`, `OpcEnum`, `OpcEnumField`, `OpcDataTypeNode`, `OpcDataObject`

Benutzerdefinierte Datentypen legen Sie üblicherweise über .NET-Typen und Attribute fest. Das beschreibt der Abschnitt [Datentypen bereitstellen](https://www.traeger.de/docs/development/opcua/server/node-types#datentypen-bereitstellen). Wenn die Datentypen erst zur Laufzeit feststehen, zum Beispiel weil sie aus einer Konfiguration oder einer angebundenen Steuerung stammen, definieren Sie sie stattdessen über die Klassen `OpcStruct` (Strukturen) und `OpcEnum` (Enumerationen). Die Werte solcher Datentypen stellt der Server als Instanzen von `OpcDataObject` bereit, ganz ohne eigene .NET-Typen.

## Dynamische Datentypen aktivieren

Damit der Server Werte dynamisch definierter Datentypen kodieren kann, aktivieren Sie die Eigenschaft `UseDynamic` des Servers. Standardmäßig bestimmt sie `OpcAutomatism.UseDynamic`, das deaktiviert ist:

```csharp
server.UseDynamic = true;
```

```vbnet
server.UseDynamic = True
```

## Strukturen definieren

Eine Struktur definieren Sie über die Methode `DefineType` der Klasse `OpcStruct`. Sie erhält die `OpcNodeId` und den Namen des Datentyps sowie seine Felder als Instanzen von `OpcStructField`. Jedes Feld besteht aus seinem Namen, der `OpcNodeId` seines Datentyps (zum Beispiel aus der Klasse `OpcDataTypes`) und optional einer Beschreibung. Die Methode `AsArray` macht ein Feld zum Array, die Methode `AsOptional` zu einem optionalen Feld:

```csharp
var machineDataTypeId = new OpcNodeId("MachineData", namespaceIndex: 2);
 
var machineDataType = OpcStruct.DefineType(
        machineDataTypeId,
        new OpcName("MachineData", namespaceIndex: 2),
        new OpcStructField("Speed", OpcDataTypes.Int32),
        new OpcStructField("JobName", OpcDataTypes.String),
        new OpcStructField("Temperatures", OpcDataTypes.Double).AsArray(),
        new OpcStructField("Comment", OpcDataTypes.String, description: "Optional comment.")
                .AsOptional());
```

```vbnet
Dim machineDataTypeId = New OpcNodeId("MachineData", namespaceIndex:=2)

Dim machineDataType = OpcStruct.DefineType( _
        machineDataTypeId, _
        New OpcName("MachineData", namespaceIndex:=2), _
        New OpcStructField("Speed", OpcDataTypes.Int32), _
        New OpcStructField("JobName", OpcDataTypes.String), _
        New OpcStructField("Temperatures", OpcDataTypes.Double).AsArray(), _
        New OpcStructField("Comment", OpcDataTypes.String, description:="Optional comment.") _
                .AsOptional())
```

Wenn eine Struktur eine andere erweitern soll, definieren Sie sie über die Methode `DefineSubType` und geben den Basistyp an:

```csharp
var extendedDataType = OpcStruct.DefineSubType(
        machineDataTypeId,
        new OpcNodeId("ExtendedMachineData", namespaceIndex: 2),
        new OpcName("ExtendedMachineData", namespaceIndex: 2),
        new OpcStructField("Operator", OpcDataTypes.String));
```

```vbnet
Dim extendedDataType = OpcStruct.DefineSubType( _
        machineDataTypeId, _
        New OpcNodeId("ExtendedMachineData", namespaceIndex:=2), _
        New OpcName("ExtendedMachineData", namespaceIndex:=2), _
        New OpcStructField("Operator", OpcDataTypes.String))
```

## Enumerationen definieren

Eine Enumeration definieren Sie über die Klasse `OpcEnum`, und zwar auf eine von zwei Arten:

- über die Methode `DefineStrings` als Liste von Namen (EnumStrings). Der Wert eines Eintrags ist sein Index.
- über die Methode `DefineValues` als Liste von Name-Wert-Paaren (EnumValues). Die Werte müssen nicht fortlaufend sein, und jeder Eintrag kann eine Beschreibung erhalten.

```csharp
var jobKindId = new OpcNodeId("JobKind", namespaceIndex: 2);
 
var jobKind = OpcEnum.DefineStrings(
        jobKindId,
        new OpcName("JobKind", namespaceIndex: 2),
        new OpcEnumField("Production"),
        new OpcEnumField("Maintenance"));
 
var errorCode = OpcEnum.DefineValues(
        new OpcNodeId("ErrorCode", namespaceIndex: 2),
        new OpcName("ErrorCode", namespaceIndex: 2),
        new OpcEnumField("None", value: 0),
        new OpcEnumField("Overheated", value: 10, description: "The temperature exceeded the limit."),
        new OpcEnumField("OutOfMaterial", value: 20, description: "The material supply is empty."));
```

```vbnet
Dim jobKindId = New OpcNodeId("JobKind", namespaceIndex:=2)

Dim jobKind = OpcEnum.DefineStrings( _
        jobKindId, _
        New OpcName("JobKind", namespaceIndex:=2), _
        New OpcEnumField("Production"), _
        New OpcEnumField("Maintenance"))

Dim errorCode = OpcEnum.DefineValues( _
        New OpcNodeId("ErrorCode", namespaceIndex:=2), _
        New OpcName("ErrorCode", namespaceIndex:=2), _
        New OpcEnumField("None", value:=0), _
        New OpcEnumField("Overheated", value:=10, description:="The temperature exceeded the limit."), _
        New OpcEnumField("OutOfMaterial", value:=20, description:="The material supply is empty."))
```

## Variablen mit dynamischen Datentypen anlegen

Variablen, die einen dynamisch definierten Datentyp verwenden, legen Sie wie gewohnt im Node-Manager an. Ihren Datentyp legt die Eigenschaft `DataTypeId` fest:

```csharp
// MyNodeManager.CreateNodes(...)
var machineDataNode = new OpcDataVariableNode(machineNode, "Data");
machineDataNode.DataTypeId = machineDataTypeId;
 
var jobKindNode = new OpcDataVariableNode(machineNode, "JobKind");
jobKindNode.DataTypeId = jobKindId;
```

```vbnet
'MyNodeManager.CreateNodes(...)
Dim machineDataNode = New OpcDataVariableNode(machineNode, "Data")
machineDataNode.DataTypeId = machineDataTypeId

Dim jobKindNode = New OpcDataVariableNode(machineNode, "JobKind")
jobKindNode.DataTypeId = jobKindId
```

## Datentypen veröffentlichen

Der Server veröffentlicht die Datentypen über Datentyp-Nodes. Diese Nodes erzeugt die Methode `Create` der Klasse `OpcDataTypeNode` aus der jeweiligen Definition. Weil der Server zum Erzeugen dieser Nodes die Datentypen der Felder auflöst, fügen Sie sie erst nach dem Start des Servers über die Methode `AddNode` des Node-Managers hinzu:

```csharp
server.Start();
 
// The data type nodes are added after the server has been started.
nodeManager.AddNode(OpcDataTypeNode.Create(machineDataType));
nodeManager.AddNode(OpcDataTypeNode.Create(jobKind));
nodeManager.AddNode(OpcDataTypeNode.Create(errorCode));
```

```vbnet
server.Start()

'The data type nodes are added after the server has been started.
nodeManager.AddNode(OpcDataTypeNode.Create(machineDataType))
nodeManager.AddNode(OpcDataTypeNode.Create(jobKind))
nodeManager.AddNode(OpcDataTypeNode.Create(errorCode))
```

## Werte bereitstellen

Einen Wert einer Struktur erzeugen Sie als `OpcDataObject`. Den dazu benötigten Datentyp liefert die Methode `GetDataType` des Servers, sobald der Datentyp-Node veröffentlicht ist. Die Felder sprechen Sie über ihren Namen an. Optionale Felder, denen Sie keinen Wert zuweisen, bleiben leer. Der Wert einer Enumeration ist eine Zahl: bei EnumStrings der Index, bei EnumValues der Wert des Eintrags:

```csharp
var dataType = server.GetDataType(machineDataTypeId);
var data = new OpcDataObject(dataType);
 
data["Speed"].Value = 1200;
data["JobName"].Value = "JOB0815";
data["Temperatures"].Value = new double[] { 20.5, 21.0, 21.3 };
 
machineDataNode.Value = data;
machineDataNode.ApplyChanges(server.SystemContext);
 
// Enumeration values are numbers (index or value of the field), here: Maintenance.
jobKindNode.Value = 1;
jobKindNode.ApplyChanges(server.SystemContext);
```

```vbnet
Dim dataType = server.GetDataType(machineDataTypeId)
Dim data = New OpcDataObject(dataType)

data("Speed").Value = 1200
data("JobName").Value = "JOB0815"
data("Temperatures").Value = New Double() {20.5, 21.0, 21.3}

machineDataNode.Value = data
machineDataNode.ApplyChanges(server.SystemContext)

'Enumeration values are numbers (index or value of the field), here: Maintenance.
jobKindNode.Value = 1
jobKindNode.ApplyChanges(server.SystemContext)
```

Ein Client, der die Datentypen des Servers zur Laufzeit auflöst (zum Beispiel über `OpcClient.UseDynamic`), erhält die Werte der Struktur ebenfalls als `OpcDataObject`. Das beschreibt die Seite [Strukturierte Daten](https://www.traeger.de/docs/development/opcua/client/structured-data).
