Anleitung
Historische Daten
Historische Werte von Nodes speichern und für Clients bereitstellen, mit eigenem History Provider oder dem OpcNodeHistorian.
Anleitung
Historische Daten
Beteiligte TypenOpcServerOpcNodeManagerIOpcNodeOpcHistoryValueOpcHistoryModificationInfoOpcValueCollectionOpcStatusCollectionOpcDeleteHistoryOptionsOpcReadHistoryOptionsIOpcNodeHistoryProviderOpcNodeHistorianOpcNodeHistory<T>OpcModifiedNodeHistoryIOpcEventHistoryProvider
Laut OPC-UA-Spezifikation kann jeder Node der Kategorie Variable die Werte seines Value-Attributs im zeitlichen Verlauf aufzeichnen. Bei jeder Änderung des Value-Attributs speichert der Server den neuen Wert zusammen mit dem Zeitstempel (engl. Timestamp) des Value-Attributs. Diese Paare aus Wert und Zeitstempel heißen historische Daten. Wo der Server die Daten speichert, entscheidet der Server selbst. Der Client stellt über das Attribut IsHistorizing des Nodes fest, ob der Server für einen Node historische Daten bereitstellt beziehungsweise Wertänderungen historisch speichert. Ein Client kann historische Daten lesen (engl. read), ändern (engl. update), ersetzen (engl. replace), löschen (engl. delete) oder erzeugen (engl. create). Am häufigsten liest der Client die historischen Daten.
Der Server verwaltet die historischen Daten wahlweise direkt im Node-Manager des jeweiligen Nodes, über den Node-Historian im Arbeitsspeicher (in-memory) oder über einen benutzerdefinierten Historian. Laut OPC UA verwenden historische Werte immer ihren Zeitstempel als Schlüssel. Unter allen historischen Werten eines Nodes ist ein Zeitstempel deshalb immer eindeutig. Er identifiziert genau einen Wert und dessen Qualitätsinformationen, also Statusinformationen. Die so gespeicherten historischen Daten teilen sich außerdem in reine und modifizierte (engl. modified) historische Daten. Die modifizierten Daten sind aus Sicht einer Datenbank eine Art Änderungsprotokoll (engl. Changelog). Mit diesem Änderungsprotokoll lassen sich historische Daten verarbeiten, die vor einer Manipulation der ursprünglichen historischen Daten gültig waren. Zugleich macht es jede Änderung der historischen Daten nachvollziehbar. Wenn zum Beispiel ein historischer Wert ersetzt wird, speichert der Server den vorherigen Wert in der modifizierten Historie. Auch einen historischen Wert, der aus der Historie entfernt wird, speichert er in der modifizierten Historie. Zusätzlich speichert die modifizierte Historie die Art der Änderung, den Zeitstempel der Änderung und den Namen des Benutzers, der die Änderung veranlasst hat.
Aufzeichnung aktivieren
Wenn ein Client die (modifizierten) historischen Daten eines Nodes lesen möchte, muss der Node ein Variablen-Node sein. Außerdem müssen die Aufzeichnung historischer Daten aktiviert und der Zugriff darauf freigegeben sein:
Mit OpcNodeHistorian: Der Historian aktiviert die Aufzeichnung historischer Daten und gibt sie frei:
// "this" points to the Node-Manager of the node.
var machineIsRunningHistorian = new OpcNodeHistorian(this, machineIsRunningNode);'"Me" points to the Node-Manager of the node.
Dim machineIsRunningHistorian = New OpcNodeHistorian(Me, machineIsRunningNode)Manuell: Aktivieren Sie die Aufzeichnung historischer Daten und geben Sie sie frei:
machineIsRunningNode.AccessLevel |= OpcAccessLevel.HistoryReadOrWrite;
machineIsRunningNode.UserAccessLevel |= OpcAccessLevel.HistoryReadOrWrite;
machineIsRunningNode.IsHistorizing = true;machineIsRunningNode.AccessLevel = machineIsRunningNode.AccessLevel Or OpcAccessLevel.HistoryReadOrWrite
machineIsRunningNode.UserAccessLevel = machineIsRunningNode.UserAccessLevel Or OpcAccessLevel.HistoryReadOrWrite
machineIsRunningNode.IsHistorizing = TrueWertänderungen automatisch aufzeichnen
So überwacht der Server Änderungen des Value-Attributs eines Variablen-Nodes und überführt sie in einen Speicher für die historischen Werte:
Mit OpcNodeHistorian: Stellen Sie den Historian so ein, dass er die Historie automatisch aktualisiert:
machineIsRunningHistorian.AutoUpdateHistory = true;machineIsRunningHistorian.AutoUpdateHistory = TrueManuell: Abonnieren Sie das Ereignis BeforeApplyChanges des Variablen-Nodes, um die Änderungen des Value-Attributs zu überwachen:
machineIsRunningNode.BeforeApplyChanges += HandleBeforeApplyChanges;
...
private void HandleBeforeApplyChanges(object sender, EventArgs e)
{
// Update (modified) Node History here.
}machineIsRunningNode.BeforeApplyChanges = AddressOf HandleBeforeApplyChanges;
...
Private Sub HandleBeforeApplyChanges(ByVal sender As Object, ByVal e As EventArgs)
'Update (modified) Node History here.
End SubLeseanfragen beantworten
So stellt der Server dem Client die historischen Daten zur Verfügung:
History Provider verwenden: Wenn Sie einen IOpcNodeHistoryProvider wie den OpcNodeHistorian verwenden, teilen Sie ihn dem Server über den Node-Manager mit:
protected override IOpcNodeHistoryProvider RetrieveNodeHistoryProvider(IOpcNode node)
{
if (node == machineIsRunningNode)
return machineIsRunningHistorian;
return base.RetrieveNodeHistoryProvider(node);
}Protected Overrides Function RetrieveNodeHistoryProvider( _
ByVal node As IOpcNode) As IOpcNodeHistoryProvider
If node Is machineIsRunningNode Then
Return machineIsRunningHistorian
End If
Return MyBase.RetrieveNodeHistoryProvider(node)
End FunctionEigenen History Provider verwenden: Ein benutzerdefinierter IOpcNodeHistoryProvider beantwortet die Anfrage mit seiner Methode ReadHistory. Wenn der Parameter timeFlowsBackward gesetzt ist, erwartet der Client die Werte in umgekehrter zeitlicher Reihenfolge. Der Parameter maxCount gibt an, wie viele Werte der Client höchstens anfordert (null, wenn er keine Begrenzung vorgibt). Der Server liefert die Werte seitenweise an den Client. Die Methode kann sie deshalb auch nach und nach bereitstellen (zum Beispiel über yield return):
public IEnumerable<OpcHistoryValue> ReadHistory(
OpcContext context,
DateTime? startTime,
DateTime? endTime,
bool timeFlowsBackward,
uint? maxCount,
OpcReadHistoryOptions options)
{
// Read (modified) Node History here.
}Public Function ReadHistory( _
ByVal context As OpcContext, _
ByVal startTime As DateTime?, _
ByVal endTime As DateTime?, _
ByVal timeFlowsBackward As Boolean, _
ByVal maxCount As UInteger?, _
ByVal options As OpcReadHistoryOptions) As IEnumerable(Of OpcHistoryValue) _
Implements IOpcNodeHistoryProvider.ReadHistory
'Read (modified) Node History here.
End FunctionAb Version 2.59 berechnet der Server Werte zu Zeitpunkten und aggregierte Werte aus den Grenzwerten des angefragten Zeitraums. Rohwert-Anfragen mit ReturnBounds liefern diese Grenzwerte mit. Liefern Sie deshalb zusätzlich den Wert direkt vor und den Wert direkt nach dem angefragten Zeitraum, sonst erhält der Client BadNoData.
Historie im Node-Manager verwalten: Wenn sich der Node-Manager selbst um die Historie seiner Nodes kümmern soll, implementieren Sie die Methode ReadHistory:
protected override IEnumerable<OpcHistoryValue> ReadHistory(
OpcContext context,
IOpcNode node,
DateTime? startTime,
DateTime? endTime,
bool timeFlowsBackward,
uint? maxCount,
OpcReadHistoryOptions options)
{
// Read (modified) Node History here.
}Protected Overrides Function ReadHistory( _
ByVal context As OpcContext, _
ByVal node As IOpcNode, _
ByVal startTime As DateTime?, _
ByVal endTime As DateTime?, _
ByVal timeFlowsBackward As Boolean, _
ByVal maxCount As UInteger?, _
ByVal options As OpcReadHistoryOptions) As IEnumerable(Of OpcHistoryValue)
'Read (modified) Node History here.
End FunctionAggregierte Werte und Werte zu Zeitpunkten
Beteiligte TypenOpcNodeManagerIOpcNodeHistoryProviderOpcNodeHistorianOpcAggregateTypeOpcHistoryValue
Ein Server, der die Rohwerte eines Nodes bereitstellt, beantwortet ohne weiteren Code auch Anfragen nach aggregierten Werten und nach Werten zu bestimmten Zeitpunkten. Das gilt für einen IOpcNodeHistoryProvider, den OpcNodeHistorian und die Methode ReadHistory des Node-Managers. Das OPC UA .NET SDK liest dazu die Rohwerte des angefragten Zeitraums über dieselbe Methode ReadHistory und berechnet das Ergebnis selbst:
- Aggregierte Werte (Processed): Für jedes Intervall des Zeitraums berechnet der Server das angefragte Aggregat, zum Beispiel Mittelwert, Minimum, Maximum oder Anzahl (siehe
OpcAggregateType). Wenn ein Client ein Aggregat anfordert, das der Server nicht unterstützt, erhält er für diesen Node den StatusBadAggregateNotSupported. - Werte zu Zeitpunkten (
AtTime): Wenn zum angefragten Zeitpunkt ein Rohwert existiert, liefert der Server diesen. Wenn der Zeitpunkt zwischen zwei Rohwerten liegt, interpoliert der Server linear zwischen ihnen. Nach dem letzten Rohwert liefert er den letzten Wert. Wenn es vor dem Zeitpunkt keinen Rohwert gibt, lautet der StatusBadNoData. Wenn der Server bei der Berechnung Rohwerte mit schlechtem Status übersprungen hat, kennzeichnet er das Ergebnis als unsicher (UncertainDataSubNormal).
Sie implementieren also nur das Lesen der Rohwerte, wie es der Abschnitt Leseanfragen beantworten zeigt. Ein Client fordert die berechneten Werte dann zum Beispiel so an (siehe auch Historische Daten im Client-Kapitel):
DateTime endTime = DateTime.UtcNow;
DateTime startTime = endTime.AddHours(-8);
var averagePerHour = client.ReadNodeHistoryProcessed(
startTime,
endTime,
OpcAggregateType.Average,
aggregateInterval: 3600000,
nodeId: "ns=2;s=Machine/Temperature");
OpcHistoryValue atNoon = client.ReadNodeHistoryAtTime(
time: DateTime.UtcNow.Date.AddHours(12),
nodeId: "ns=2;s=Machine/Temperature");Dim endTime As DateTime = DateTime.UtcNow
Dim startTime As DateTime = endTime.AddHours(-8)
Dim averagePerHour = client.ReadNodeHistoryProcessed( _
startTime, _
endTime, _
OpcAggregateType.Average, _
aggregateInterval:=3600000, _
nodeId:="ns=2;s=Machine/Temperature")
Dim atNoon As OpcHistoryValue = client.ReadNodeHistoryAtTime( _
time:=DateTime.UtcNow.Date.AddHours(12), _
nodeId:="ns=2;s=Machine/Temperature")Werte einfügen (Insert)
Wenn ein Client historische Daten eines Nodes erzeugen möchte, muss der Server die neuen Werte sowohl in der Historie als auch in der modifizierten Historie ablegen:
History Provider verwenden: Wenn Sie einen IOpcNodeHistoryProvider wie den OpcNodeHistorian verwenden, teilen Sie ihn dem Server über den Node-Manager mit:
protected override IOpcNodeHistoryProvider RetrieveNodeHistoryProvider(IOpcNode node)
{
if (node == machineIsRunningNode)
return machineIsRunningHistorian;
return base.RetrieveNodeHistoryProvider(node);
}Protected Overrides Function RetrieveNodeHistoryProvider( _
ByVal node As IOpcNode) As IOpcNodeHistoryProvider
If node Is machineIsRunningNode Then
Return machineIsRunningHistorian
End If
Return MyBase.RetrieveNodeHistoryProvider(node)
End FunctionEigenen History Provider verwenden: Ein benutzerdefinierter IOpcNodeHistoryProvider verwendet dafür seine Methode CreateHistory:
public OpcStatusCollection CreateHistory(
OpcContext context,
OpcHistoryModificationInfo modificationInfo,
OpcValueCollection values)
{
// Create (modified) Node History here.
}Public Function CreateHistory( _
ByVal context As OpcContext, _
ByVal modificationInfo As OpcHistoryModificationInfo, _
ByVal values As OpcValueCollection) As OpcStatusCollection
'Create (modified) Node History here.
End FunctionHistorie im Node-Manager verwalten: Wenn sich der Node-Manager selbst um die Historie seiner Nodes kümmern soll, implementieren Sie die Methode CreateHistory:
protected override OpcStatusCollection CreateHistory(
IOpcNode node,
OpcHistoryModificationInfo modificationInfo,
OpcValueCollection values)
{
// Create (modified) Node History here.
}Protected Overrides Function CreateHistory( _
ByVal node As IOpcNode, _
ByVal modificationInfo As OpcHistoryModificationInfo, _
ByVal values As OpcValueCollection) As OpcStatusCollection
'Create (modified) Node History here.
End FunctionWerte löschen (Delete)
Wenn ein Client historische Daten eines Nodes löschen möchte, muss der Server die zu löschenden Werte in die modifizierte Historie übertragen und aus der eigentlichen Historie löschen. Modifizierte Historienwerte, die gelöscht werden sollen, kann er direkt aus der modifizierten Historie entfernen:
History Provider verwenden: Wenn Sie einen IOpcNodeHistoryProvider wie den OpcNodeHistorian verwenden, teilen Sie ihn dem Server über den Node-Manager mit:
protected override IOpcNodeHistoryProvider RetrieveNodeHistoryProvider(IOpcNode node)
{
if (node == machineIsRunningNode)
return machineIsRunningHistorian;
return base.RetrieveNodeHistoryProvider(node);
}Protected Overrides Function RetrieveNodeHistoryProvider( _
ByVal node As IOpcNode) As IOpcNodeHistoryProvider
If node Is machineIsRunningNode Then
Return machineIsRunningHistorian
End If
Return MyBase.RetrieveNodeHistoryProvider(node)
End FunctionEigenen History Provider verwenden: Ein benutzerdefinierter IOpcNodeHistoryProvider verwendet dafür eine seiner Methoden DeleteHistory:
public OpcStatusCollection DeleteHistory(
OpcContext context,
OpcHistoryModificationInfo modificationInfo,
IEnumerable<DateTime> times)
{
// Delete Node History entries and add them to the modified Node History here.
}
public OpcStatusCollection DeleteHistory(
OpcContext context,
OpcHistoryModificationInfo modificationInfo,
OpcValueCollection values)
{
// Delete Node History entries and add them to the modified Node History here.
}
public OpcStatusCollection DeleteHistory(
OpcContext context,
OpcHistoryModificationInfo modificationInfo,
DateTime? startTime,
DateTime? endTime,
OpcDeleteHistoryOptions options)
{
// Delete Node History entries and add them to the modified Node History here.
}Public Function DeleteHistory( _
ByVal context As OpcContext, _
ByVal modificationInfo As OpcHistoryModificationInfo, _
ByVal times As IEnumerable(Of DateTime)) As OpcStatusCollection
'Delete Node History entries and add them to the modified Node History here.
End Function
Public Function DeleteHistory( _
ByVal context As OpcContext, _
ByVal modificationInfo As OpcHistoryModificationInfo, _
ByVal values As OpcValueCollection) As OpcStatusCollection
'Delete Node History entries and add them to the modified Node History here.
End Function
Public Function DeleteHistory( _
ByVal context As OpcContext, _
ByVal modificationInfo As OpcHistoryModificationInfo, _
ByVal startTime As DateTime?, _
ByVal endTime As DateTime?, _
ByVal options As OpcDeleteHistoryOptions) As OpcStatusCollection
'Delete Node History entries and add them to the modified Node History here.
End FunctionHistorie im Node-Manager verwalten: Wenn sich der Node-Manager selbst um die Historie seiner Nodes kümmern soll, implementieren Sie die Methoden DeleteHistory:
protected override OpcStatusCollection DeleteHistory(
IOpcNode node,
OpcHistoryModificationInfo modificationInfo,
IEnumerable<DateTime> times)
{
// Delete Node History entries and add them to the modified Node History here.
}
protected override OpcStatusCollection DeleteHistory(
IOpcNode node,
OpcHistoryModificationInfo modificationInfo,
OpcValueCollection values)
{
// Delete Node History entries and add them to the modified Node History here.
}
protected override OpcStatusCollection DeleteHistory(
IOpcNode node,
OpcHistoryModificationInfo modificationInfo,
DateTime? startTime,
DateTime? endTime,
OpcDeleteHistoryOptions options)
{
// Delete Node History entries and add them to the modified Node History here.
}Protected Overrides Function DeleteHistory( _
ByVal node As IOpcNode, _
ByVal modificationInfo As OpcHistoryModificationInfo, _
ByVal times As IEnumerable(Of DateTime)) As OpcStatusCollection
'Delete Node History entries and add them to the modified Node History here.
End Function
Protected Overrides Function DeleteHistory( _
ByVal node As IOpcNode, _
ByVal modificationInfo As OpcHistoryModificationInfo, _
ByVal values As OpcValueCollection) As OpcStatusCollection
'Delete Node History entries and add them to the modified Node History here.
End Function
Protected Overrides Function DeleteHistory( _
ByVal node As IOpcNode, _
ByVal modificationInfo As OpcHistoryModificationInfo, _
ByVal startTime As DateTime?, _
ByVal endTime As DateTime?, _
ByVal options As OpcDeleteHistoryOptions) As OpcStatusCollection
'Delete Node History entries and add them to the modified Node History here.
End FunctionWerte ersetzen (Replace)
Wenn ein Client historische Daten eines Nodes ersetzen möchte, muss der Server die zu ersetzenden Werte in die modifizierte Historie übertragen und in der eigentlichen Historie ersetzen:
History Provider verwenden: Wenn Sie einen IOpcNodeHistoryProvider wie den OpcNodeHistorian verwenden, teilen Sie ihn dem Server über den Node-Manager mit:
protected override IOpcNodeHistoryProvider RetrieveNodeHistoryProvider(IOpcNode node)
{
if (node == machineIsRunningNode)
return machineIsRunningHistorian;
return base.RetrieveNodeHistoryProvider(node);
}Protected Overrides Function RetrieveNodeHistoryProvider( _
ByVal node As IOpcNode) As IOpcNodeHistoryProvider
If node Is machineIsRunningNode Then
Return machineIsRunningHistorian
End If
Return MyBase.RetrieveNodeHistoryProvider(node)
End FunctionEigenen History Provider verwenden: Ein benutzerdefinierter IOpcNodeHistoryProvider verwendet dafür seine Methode ReplaceHistory:
public OpcStatusCollection ReplaceHistory(
OpcContext context,
OpcHistoryModificationInfo modificationInfo,
OpcValueCollection values)
{
// Replace Node History entries and add them to the modified Node History here.
}Public Function ReplaceHistory( _
ByVal context As OpcContext, _
ByVal modificationInfo As OpcHistoryModificationInfo, _
ByVal values As OpcValueCollection) As OpcStatusCollection
'Replace Node History entries and add them to the modified Node History here.
End FunctionHistorie im Node-Manager verwalten: Wenn sich der Node-Manager selbst um die Historie seiner Nodes kümmern soll, implementieren Sie die Methode ReplaceHistory:
protected override OpcStatusCollection ReplaceHistory(
IOpcNode node,
OpcHistoryModificationInfo modificationInfo,
OpcValueCollection values)
{
// Replace Node History entries and add them to the modified Node History here.
}Protected Overrides Function ReplaceHistory( _
ByVal node As IOpcNode, _
ByVal modificationInfo As OpcHistoryModificationInfo, _
ByVal values As OpcValueCollection) As OpcStatusCollection
'Replace Node History entries and add them to the modified Node History here.
End FunctionEinfügen oder ersetzen (Update)
Ein Client kann historische Daten eines Nodes erzeugen, wenn sie noch nicht existieren, oder ersetzen, wenn sie bereits existieren. Im Sinne von OPC UA heißt das Aktualisieren (engl. Update). Nicht existierende Einträge muss der Server in die Historie und in die modifizierte Historie eintragen. Existierende Einträge muss er in der Historie ersetzen und in der modifizierten Historie eintragen:
History Provider verwenden: Wenn Sie einen IOpcNodeHistoryProvider wie den OpcNodeHistorian verwenden, teilen Sie ihn dem Server über den Node-Manager mit:
protected override IOpcNodeHistoryProvider RetrieveNodeHistoryProvider(IOpcNode node)
{
if (node == machineIsRunningNode)
return machineIsRunningHistorian;
return base.RetrieveNodeHistoryProvider(node);
}Protected Overrides Function RetrieveNodeHistoryProvider( _
ByVal node As IOpcNode) As IOpcNodeHistoryProvider
If node Is machineIsRunningNode Then
Return machineIsRunningHistorian
End If
Return MyBase.RetrieveNodeHistoryProvider(node)
End FunctionEigenen History Provider verwenden: Ein benutzerdefinierter IOpcNodeHistoryProvider verwendet dafür seine Methode UpdateHistory:
public OpcStatusCollection UpdateHistory(
OpcContext context,
OpcHistoryModificationInfo modificationInfo,
OpcValueCollection values)
{
// Update (modified) Node History entries here.
}Public Function UpdateHistory( _
ByVal context As OpcContext, _
ByVal modificationInfo As OpcHistoryModificationInfo, _
ByVal values As OpcValueCollection) As OpcStatusCollection
'Update (modified) Node History entries here.
End FunctionHistorie im Node-Manager verwalten: Wenn sich der Node-Manager selbst um die Historie seiner Nodes kümmern soll, implementieren Sie die Methode UpdateHistory:
protected override OpcStatusCollection UpdateHistory(
IOpcNode node,
OpcHistoryModificationInfo modificationInfo,
OpcValueCollection values)
{
// Update (modified) Node History entries here.
}Protected Overrides Function UpdateHistory( _
ByVal node As IOpcNode, _
ByVal modificationInfo As OpcHistoryModificationInfo, _
ByVal values As OpcValueCollection) As OpcStatusCollection
'Update (modified) Node History entries here.
End FunctionHistorie im Speicher verwalten
Mit der Klasse OpcNodeHistory<T> lassen sich die Daten der Historie im Speicher verwalten, bis Version 2.58 auch die der modifizierten Historie. Ab Version 2.59 verwaltet die Klasse OpcModifiedNodeHistory die modifizierte Historie. Auch der OpcNodeHistorian verwendet diese Klassen. Die Klasse OpcNodeHistory<T> bietet mehrere Methoden für die üblichen Zugriffsszenarien auf historische Daten. Über ihre Konstruktoren legen Sie die Größe der Historie fest, also ihre Kapazität; das gilt auch für den OpcNodeHistorian. Ohne Angabe ist die Kapazität -1, die Historie ist also unbegrenzt. Ab Version 2.59 löst eine Kapazität von 0 oder kleiner als -1 eine ArgumentOutOfRangeException aus. Das gilt auch für den Parameter modifiedCapacity der Konstruktoren von OpcNodeHistorian. Außerdem können Sie die Historie vorab laden und über mehrere Ereignisse überwachen.
Eine Historie abhängig von der Art der historischen Daten definieren:
Einfache Historie: Verwenden Sie als Typparameter die Klasse OpcHistoryValue:
var history = new OpcNodeHistory<OpcHistoryValue>();Dim history = New OpcNodeHistory(Of OpcHistoryValue)()Modifizierte Historie: Ab Version 2.59 verwenden Sie die Klasse OpcModifiedNodeHistory. Sie enthält Einträge vom Typ OpcModifiedHistoryValue und nimmt auch mehrere Änderungen eines Werts mit demselben Zeitstempel auf. Die Einträge sind nach Zeitstempel sortiert, bei gleichem Zeitstempel steht die neueste Änderung vorn. Wenn die Kapazität erreicht ist, entfernt die Klasse die älteste Änderung. Die Eigenschaft OpcNodeHistorian.ModifiedHistory hat diesen Typ. Bis Version 2.58 verwenden Sie OpcNodeHistory<T> mit dem Typparameter OpcModifiedHistoryValue:
var modifiedHistory = new OpcNodeHistory<OpcModifiedHistoryValue>();Dim modifiedHistory = New OpcNodeHistory(Of OpcModifiedHistoryValue)()Szenarien mit der Node-Historie im Speicher
Mit der Klasse OpcNodeHistory<T> implementieren Sie die üblichen History-Szenarien Read, Create, Delete, Replace und Update so:
Szenario: Create History:
var results = OpcStatusCollection.Create(OpcStatusCode.Good, values.Count);
for (int index = 0; index < values.Count; index++) {
var result = results[index];
var value = OpcHistoryValue.Create(values[index]);
if (MatchesValueType(value)) {
if (history.Contains(value.Timestamp)) {
result.Update(OpcStatusCode.BadEntryExists);
}
else {
history.Add(value);
var modifiedValue = value.CreateModified(modificationInfo);
modifiedHistory.Add(modifiedValue);
result.Update(OpcStatusCode.GoodEntryInserted);
}
}
else {
result.Update(OpcStatusCode.BadTypeMismatch);
}
}
return results;Dim results = OpcStatusCollection.Create(OpcStatusCode.Good, values.Count)
For index As Integer = 0 To values.Count - 1
Dim result = results(index)
Dim value = OpcHistoryValue.Create(values(index))
If MatchesValueType(value) Then
If history.Contains(value.Timestamp) Then
result.Update(OpcStatusCode.BadEntryExists)
Else
history.Add(value)
Dim modifiedValue = value.CreateModified(modificationInfo)
modifiedHistory.Add(modifiedValue)
result.Update(OpcStatusCode.GoodEntryInserted)
End If
Else
result.Update(OpcStatusCode.BadTypeMismatch)
End If
Next
Return resultsSzenario: Delete History:
Nach Zeitstempel:
var results = OpcStatusCollection.Create(OpcStatusCode.Good, times.Count());
int index = 0;
foreach (var time in times) {
var result = results[index++];
if (this.history.Contains(time)) {
var value = this.history[time];
this.history.RemoveAt(time);
var modifiedValue = value.CreateModified(modificationInfo);
this.modifiedHistory.Add(modifiedValue);
}
else {
result.Update(OpcStatusCode.BadNoEntryExists);
}
}
return results;Dim results = OpcStatusCollection.Create(OpcStatusCode.Good, times.Count())
Dim index As Integer = 0
For Each time In times
Dim result = results(Math.Min(System.Threading.Interlocked.Increment(index), index - 1))
If Me.history.Contains(time) Then
Dim value = Me.history(time)
Me.history.RemoveAt(time)
Dim modifiedValue = value.CreateModified(modificationInfo)
Me.modifiedHistory.Add(modifiedValue)
Else
result.Update(OpcStatusCode.BadNoEntryExists)
End If
Next
Return resultsNach Werten:
var results = OpcStatusCollection.Create(OpcStatusCode.Good, values.Count);
for (int index = 0; index < values.Count; index++) {
var timestamp = OpcHistoryValue.Create(values[index]).Timestamp;
var result = results[index];
if (history.Contains(timestamp)) {
var value = history[timestamp];
history.RemoveAt(timestamp);
var modifiedValue = value.CreateModified(modificationInfo);
modifiedHistory.Add(modifiedValue);
}
else {
result.Update(OpcStatusCode.BadNoEntryExists);
}
}
return results;Dim results = OpcStatusCollection.Create(OpcStatusCode.Good, values.Count)
For index As Integer = 0 To values.Count - 1
Dim timestamp = OpcHistoryValue.Create(values(index)).Timestamp
Dim result = results(index)
If history.Contains(timestamp) Then
Dim value = history(timestamp)
history.RemoveAt(timestamp)
Dim modifiedValue = value.CreateModified(modificationInfo)
modifiedHistory.Add(modifiedValue)
Else
result.Update(OpcStatusCode.BadNoEntryExists)
End If
Next
Return resultsNach Zeitspanne:
var results = new OpcStatusCollection();
bool isModified = (options & OpcDeleteHistoryOptions.Modified)
== OpcDeleteHistoryOptions.Modified;
if (isModified) {
modifiedHistory.RemoveRange(startTime, endTime);
}
else {
var values = history.Enumerate(startTime, endTime).ToArray();
history.RemoveRange(startTime, endTime);
for (int index = 0; index < values.Length; index++) {
var value = values[index];
modifiedHistory.Add(value.CreateModified(modificationInfo));
results.Add(OpcStatusCode.Good);
}
}
return results;Dim results = New OpcStatusCollection()
Dim isModified As Boolean = (options And OpcDeleteHistoryOptions.Modified) _
= OpcDeleteHistoryOptions.Modified
If isModified Then
modifiedHistory.RemoveRange(startTime, endTime)
Else
Dim values = history.Enumerate(startTime, endTime).ToArray()
history.RemoveRange(startTime, endTime)
For index As Integer = 0 To values.Length - 1
Dim value = values(index)
modifiedHistory.Add(value.CreateModified(modificationInfo))
results.Add(OpcStatusCode.Good)
Next
End If
Return resultsSzenario: Replace History:
var results = OpcStatusCollection.Create(OpcStatusCode.Good, values.Count);
for (int index = 0; index < values.Count; index++) {
var result = results[index];
var value = OpcHistoryValue.Create(values[index]);
if (this.MatchesNodeValueType(value)) {
if (this.history.Contains(value.Timestamp)) {
var oldValue = this.history[value.Timestamp];
history.Replace(value);
var modifiedValue = oldValue.CreateModified(modificationInfo);
modifiedHistory.Add(modifiedValue);
result.Update(OpcStatusCode.GoodEntryReplaced);
}
else {
result.Update(OpcStatusCode.BadNoEntryExists);
}
}
else {
result.Update(OpcStatusCode.BadTypeMismatch);
}
}
return results;Dim results = OpcStatusCollection.Create(OpcStatusCode.Good, values.Count)
For index As Integer = 0 To values.Count - 1
Dim result = results(index)
Dim value = OpcHistoryValue.Create(values(index))
If Me.MatchesNodeValueType(value) Then
If Me.history.Contains(value.Timestamp) Then
Dim oldValue = Me.history(value.Timestamp)
history.Replace(value)
Dim modifiedValue = oldValue.CreateModified(modificationInfo)
modifiedHistory.Add(modifiedValue)
result.Update(OpcStatusCode.GoodEntryReplaced)
Else
result.Update(OpcStatusCode.BadNoEntryExists)
End If
Else
result.Update(OpcStatusCode.BadTypeMismatch)
End If
Next
Return resultsSzenario: Update History:
var results = OpcStatusCollection.Create(OpcStatusCode.Good, values.Count);
for (int index = 0; index < values.Count; index++) {
var result = results[index];
var value = OpcHistoryValue.Create(values[index]);
if (MatchesValueType(value)) {
if (history.Contains(value.Timestamp)) {
var oldValue = this.history[value.Timestamp];
history.Replace(value);
var modifiedValue = oldValue.CreateModified(modificationInfo);
modifiedHistory.Add(modifiedValue);
result.Update(OpcStatusCode.GoodEntryReplaced);
}
else {
history.Add(value);
var modifiedValue = value.CreateModified(modificationInfo);
modifiedHistory.Add(modifiedValue);
result.Update(OpcStatusCode.GoodEntryInserted);
}
}
else {
result.Update(OpcStatusCode.BadTypeMismatch);
}
}
return results;Dim results = OpcStatusCollection.Create(OpcStatusCode.Good, values.Count)
For index As Integer = 0 To values.Count - 1
Dim result = results(index)
Dim value = OpcHistoryValue.Create(values(index))
If MatchesValueType(value) Then
If history.Contains(value.Timestamp) Then
Dim oldValue = Me.history(value.Timestamp)
history.Replace(value)
Dim modifiedValue = oldValue.CreateModified(modificationInfo)
modifiedHistory.Add(modifiedValue)
result.Update(OpcStatusCode.GoodEntryReplaced)
Else
history.Add(value)
Dim modifiedValue = value.CreateModified(modificationInfo)
modifiedHistory.Add(modifiedValue)
result.Update(OpcStatusCode.GoodEntryInserted)
End If
Else
result.Update(OpcStatusCode.BadTypeMismatch)
End If
Next
Return resultsSzenario: Read History:
bool isModified = (options & OpcReadHistoryOptions.Modified)
== OpcReadHistoryOptions.Modified;
OpcHistoryValue[] values;
if (isModified) {
values = modifiedHistory
.Enumerate(startTime, endTime)
.Cast<OpcHistoryValue>()
.ToArray();
}
else {
values = history
.Enumerate(startTime, endTime)
.ToArray();
}
// Return the values in reverse order in case the time flows backward.
if (timeFlowsBackward)
Array.Reverse(values);
return values;Dim isModified As Boolean = (options And OpcReadHistoryOptions.Modified) _
= OpcReadHistoryOptions.Modified
Dim values As OpcHistoryValue()
If isModified Then
values = modifiedHistory _
.Enumerate(startTime, endTime) _
.Cast(Of OpcHistoryValue)() _
.ToArray()
Else
values = history _
.Enumerate(startTime, endTime) _
.ToArray()
End If
'Return the values in reverse order in case the time flows backward.
If timeFlowsBackward Then
Array.Reverse(values)
End If
Return valuesEreignis-Historie bereitstellen
Beteiligte TypenOpcNodeManagerOpcObjectNodeIOpcEventHistoryProviderOpcEventOpcEventFilter
Neben den Werten einer Variable kann ein Server auch die Ereignisse eines Nodes im zeitlichen Verlauf bereitstellen. Clients fragen sie über einen HistoryRead für Ereignisse ab. Setzen Sie dazu für den Node, über den die Ereignisse gemeldet werden, die Eigenschaft IsHistorizingEvents. In der Regel ist das ein OpcObjectNode (siehe Ereignisse an einem Node bereitstellen). Stellen Sie außerdem eine Implementierung der Schnittstelle IOpcEventHistoryProvider bereit. Deren Methode ReadEventHistory liefert die Ereignisse eines Zeitraums. Die Parameter timeFlowsBackward und maxCount haben dieselbe Bedeutung wie beim Lesen der Werte. Wenn eine Grenze des Zeitraums nicht angegeben ist, enthält der Parameter null oder DateTime.MinValue. Aus den gelieferten Ereignissen übernimmt das SDK nur die vom Client angeforderten Felder (SelectClause des Filters). Eine Bedingung des Filters (WhereClause) wertet es nicht aus. Eine einfache Ereignis-Historie im Speicher kann so aussehen:
public class MachineEventHistory : IOpcEventHistoryProvider
{
private readonly List<OpcEvent> events = new List<OpcEvent>();
public MachineEventHistory(OpcObjectNode node)
{
this.Node = node;
this.Node.IsHistorizingEvents = true;
}
public OpcObjectNode Node { get; }
public void Add(OpcEvent historyEvent)
{
lock (this.events)
this.events.Add(historyEvent);
}
public IEnumerable<OpcEvent> ReadEventHistory(
OpcContext context,
OpcEventFilter filter,
DateTime? startTime,
DateTime? endTime,
bool timeFlowsBackward,
uint? maxCount)
{
// In case the time flows backward the start time is the later one.
DateTime? from = timeFlowsBackward ? endTime : startTime;
DateTime? to = timeFlowsBackward ? startTime : endTime;
var result = new List<OpcEvent>();
lock (this.events) {
foreach (var item in this.events) {
if (IsSet(from) && item.Time < from)
continue;
if (IsSet(to) && item.Time > to)
continue;
result.Add(item);
}
}
if (timeFlowsBackward)
result.Reverse();
if (maxCount != null && result.Count > maxCount.Value)
result.RemoveRange((int)maxCount.Value, result.Count - (int)maxCount.Value);
return result;
}
private static bool IsSet(DateTime? time)
{
return time != null && time != DateTime.MinValue;
}
}Public Class MachineEventHistory
Implements IOpcEventHistoryProvider
Private ReadOnly events As New List(Of OpcEvent)()
Public Sub New(ByVal node As OpcObjectNode)
Me.Node = node
Me.Node.IsHistorizingEvents = True
End Sub
Public ReadOnly Property Node As OpcObjectNode Implements IOpcEventHistoryProvider.Node
Public Sub Add(ByVal historyEvent As OpcEvent)
SyncLock Me.events
Me.events.Add(historyEvent)
End SyncLock
End Sub
Public Function ReadEventHistory( _
ByVal context As OpcContext, _
ByVal filter As OpcEventFilter, _
ByVal startTime As DateTime?, _
ByVal endTime As DateTime?, _
ByVal timeFlowsBackward As Boolean, _
ByVal maxCount As UInteger?) As IEnumerable(Of OpcEvent) _
Implements IOpcEventHistoryProvider.ReadEventHistory
'In case the time flows backward the start time is the later one.
Dim from As DateTime? = If(timeFlowsBackward, endTime, startTime)
Dim [to] As DateTime? = If(timeFlowsBackward, startTime, endTime)
Dim result = New List(Of OpcEvent)()
SyncLock Me.events
For Each item In Me.events
If IsSet(from) AndAlso item.Time < from.Value Then Continue For
If IsSet([to]) AndAlso item.Time > [to].Value Then Continue For
result.Add(item)
Next
End SyncLock
If timeFlowsBackward Then
result.Reverse()
End If
If maxCount.HasValue AndAlso result.Count > maxCount.Value Then
result.RemoveRange(CInt(maxCount.Value), result.Count - CInt(maxCount.Value))
End If
Return result
End Function
Private Shared Function IsSet(ByVal time As DateTime?) As Boolean
Return time.HasValue AndAlso time.Value <> DateTime.MinValue
End Function
End ClassDie Ereignis-Historie teilen Sie dem Server über den Node-Manager mit. Wenn der Node selbst die Schnittstelle IOpcEventHistoryProvider implementiert, verwendet der Server ihn auch ohne diese Methode:
protected override IOpcEventHistoryProvider RetrieveEventHistoryProvider(IOpcNode node)
{
if (node == this.machineEventHistory.Node)
return this.machineEventHistory;
return base.RetrieveEventHistoryProvider(node);
}Protected Overrides Function RetrieveEventHistoryProvider( _
ByVal node As IOpcNode) As IOpcEventHistoryProvider
If node Is Me.machineEventHistory.Node Then
Return Me.machineEventHistory
End If
Return MyBase.RetrieveEventHistoryProvider(node)
End FunctionUm ein Ereignis aufzuzeichnen, erzeugen Sie beim Melden mit der Methode CreateEvent des Ereignis-Nodes eine Momentaufnahme und legen sie in der Historie ab:
activatedEvent.Message = "Machine started";
activatedEvent.ReportEvent(this.SystemContext);
// Stores a snapshot of the event in the event history.
this.machineEventHistory.Add(activatedEvent.CreateEvent(this.SystemContext));activatedEvent.Message = "Machine started"
activatedEvent.ReportEvent(Me.SystemContext)
'Stores a snapshot of the event in the event history.
Me.machineEventHistory.Add(activatedEvent.CreateEvent(Me.SystemContext))Wie ein Client die Ereignis-Historie liest, beschreibt der Abschnitt Ereignis-Historie lesen im Client-Kapitel.

