This Getting Started Lesson shows how to implement HistoryRead for Events. It is based on Lesson 7.
The most relevant code can be found in the file Lesson8NodeManager.HistoryRead.cs. The following paragraphs give a brief overview of the implementation steps which are necessary to provide support for HistoryRead for events. Please refer to the source code for more details. The code contains comments starting with “HistoryReadEvent”, explaining the implementation details.
To test reading of Historical Events, connect to the server with UaExpert.
Choose Document → Add from the menu bar, select “Event View” from the drop down menu “Document Type” and confirm with “Add” to add the Event View tab to the main window.
Drag and drop the folder “Controllers” from the Address Space window to the Configuration window of the Event View tab (see Figure 8.1).
Expand the tree and select “ControllerEventType” beneath “SimpleEvents” to recieve the corresponding events (see Figure 8.2).
Open the “Event History” tab and click on the “Refresh” symbol (orange arrows). The historical event will now show up in the list (see Figure 8.4).
For convenience, the file Lesson8NodeManager.HistoryRead.cs, which contains most of the new code, is included here. The complete source code can be found in the Visual Studio Solution containing the Server Getting Started lessons.
using System.Collections.Generic;
namespace YourCompany.GettingStarted
{
internal partial class Lesson8aNodeManager
{
protected override HistoryReadResult HistoryReadEvent(
RequestContext context,
ReadEventDetails details,
HistoryEventHandle nodeHandle,
ref HistoryContinuationPoint continuationPoint)
{
HistoryReadResult result = new HistoryReadResult();
HistoryEventSource eventSource = m_historyEventManager.GetEventSource(nodeHandle.NodeId);
if (eventSource == null)
{
return new HistoryReadResult() {
StatusCode = StatusCodes.BadHistoryOperationUnsupported };
}
EventFilterResult filterResult = null;
StatusCode error =
Server.FilterManager.ValidateFilter(context, details.Filter, out filterResult);
if (error.IsBad())
{
return new HistoryReadResult() {
StatusCode = StatusCodes.BadEventFilterInvalid };
}
HistoricalEventFields eventFields = null;
if (continuationPoint != null)
{
eventFields = GetHistoricalEventFieldsFromContinationPoint(continuationPoint);
if (eventFields == null)
{
return new HistoryReadResult() {
StatusCode = StatusCodes.BadContinuationPointInvalid };
}
continuationPoint = null;
}
else
{
eventFields = GetHistoricalEventFieldsFromDataSource(eventSource, context, details, nodeHandle);
}
bool useContinuationPoint;
HistoryEvent data = eventFields.NextEventFields(details.NumValuesPerNode, out useContinuationPoint);
if (useContinuationPoint)
{
continuationPoint = CreateHistoryEventsContinuationPoint(eventFields);
}
return result;
}
private void SetupEventHistory()
{
QualifiedNameCollection eventFieldNames = new QualifiedNameCollection();
eventFieldNames.Add(BrowseNames.EventType);
eventFieldNames.Add(BrowseNames.Time);
eventFieldNames.Add(BrowseNames.ReceiveTime);
eventFieldNames.Add(BrowseNames.EventId);
eventFieldNames.Add(BrowseNames.SourceName);
eventFieldNames.Add(BrowseNames.SourceNode);
eventFieldNames.Add(BrowseNames.Severity);
eventFieldNames.Add(BrowseNames.Message);
eventFieldNames.Add(
new QualifiedName(yourorganisation.BA.BrowseNames.State, TypeNamespaceIndex));
eventFieldNames.Add(
new QualifiedName(yourorganisation.BA.BrowseNames.Temperature, TypeNamespaceIndex));
m_historyEventManager = new HistoryEventManager(Server, eventFieldNames);
NodeId controllersId =
new NodeId(
"Controllers", InstanceNamespaceIndex);
ObjectNode controllersFolder = FindInMemoryNode(controllersId)
as ObjectNode;
if (controllersFolder != null)
{
lock (InMemoryNodeLock)
{
controllersFolder.EventNotifier = EventNotifiers.HistoryRead | EventNotifiers.SubscribeToEvents;
}
}
HistoryEventSource eventSource = new HistoryEventSource();
m_historyEventManager.AddEventSource(controllersId, eventSource, true);
CreateVariableSettings HistoricalEventFilterSettings = new CreateVariableSettings()
{
RequestedNodeId =
new NodeId(BrowseNames.HistoricalEventFilter, InstanceNamespaceIndex),
TypeDefinitionId = VariableTypeIds.PropertyType,
ParentNodeId = controllersFolder.NodeId
};
m_historicalEventFilter = CreateVariable(
Server.DefaultRequestContext, HistoricalEventFilterSettings);
foreach (BlockConfiguration block in m_system.GetBlocks())
{
NodeId controllerId =
new NodeId(block.Name, InstanceNamespaceIndex);
ObjectNode controllerObject = FindInMemoryNode(controllerId)
as ObjectNode;
if (controllerObject != null)
{
lock (InMemoryNodeLock)
{
controllerObject.EventNotifier = (byte)(controllerObject.EventNotifier|EventNotifiers.HistoryRead);
}
AddReference(
Server.DefaultRequestContext, controllerObject.NodeId, ReferenceTypeIds.HasProperty,
false, m_historicalEventFilter.NodeId,
true);
m_historyEventManager.AddEventSource(controllerId, eventSource, false);
}
}
}
private HistoricalEventFields GetHistoricalEventFieldsFromDataSource(
HistoryEventSource eventSource,
RequestContext context,
ReadEventDetails details,
HistoryEventHandle nodeHandle)
{
bool timeFlowsBackward;
if (details.EndTime ==
DateTime.MinValue
|| (details.EndTime.CompareTo(details.StartTime) >= 0)
&& details.StartTime !=
DateTime.MinValue)
{
startTime = details.StartTime;
endTime = details.EndTime;
timeFlowsBackward = false;
}
else
{
startTime = details.EndTime;
endTime = details.StartTime;
timeFlowsBackward = true;
}
System.Collections.Generic.IEnumerable<
System.Collections.Generic.KeyValuePair<
System.DateTime, HistoryEventData>> result
= eventSource.Events.Where(
e => e.Key.CompareTo(startTime) >= 0
&& (endTime ==
DateTime.MinValue || e.Key.CompareTo(endTime) <= 0)
&& (e.Value.SourceNode == nodeHandle.NodeId || nodeHandle.NodeId ==
new NodeId(
"Controllers", InstanceNamespaceIndex)));
if (timeFlowsBackward)
{
result = result.Reverse();
}
HistoricalEventFields ret = new HistoricalEventFields();
try
{
Server.FilterManager.UpdateReferenceCount(details.Filter,
true);
foreach (KeyValuePair<DateTime, HistoryEventData> element in result)
{
GenericEvent genericEvent = element.Value.ToGenericEvent(
Server.FilterManager);
if (!FilterManager.Evaluate(context, details.Filter.WhereClause, genericEvent))
{
continue;
}
HistoryEventFieldList fields = new HistoryEventFieldList();
foreach (SimpleAttributeOperand clause in details.Filter.SelectClauses)
{
Variant value = genericEvent.Get(clause);
fields.EventFields.Add(value);
}
ret.Data.Add(fields);
}
}
finally
{
Server.FilterManager.UpdateReferenceCount(details.Filter,
false);
}
return ret;
}
private HistoricalEventFields GetHistoricalEventFieldsFromContinationPoint(HistoryContinuationPoint continuationPoint)
{
return continuationPoint as HistoricalEventFields;
}
private HistoryContinuationPoint CreateHistoryEventsContinuationPoint(HistoricalEventFields events)
{
return events;
}
#region Private fields
private VariableNode m_historicalEventFilter;
private HistoryEventManager m_historyEventManager;
#endregion
}
#region HistoryEventSource class
internal class HistoryEventSource
{
public HistoryEventSource()
{
m_events = new SortedList<DateTime, HistoryEventData>();
}
#region public properties
public SortedList<DateTime, HistoryEventData > Events
{
get
{
return m_events;
}
}
#endregion
#region public interface
public void AddEvent(NodeId nodeId, HistoryEventData e)
{
m_events.Add(e.Time, e);
}
#endregion
#region private fields
private SortedList<DateTime, HistoryEventData> m_events;
#endregion
}
#endregion
#region HistoryEventData
internal class HistoryEventData
{
public HistoryEventData()
{
Data =
new Dictionary<QualifiedName,Variant>();
}
#region Public Interface
public void AddEventField(QualifiedName browseName, Variant value)
{
if (browseName == "Time")
{
Time = value.ToDateTime();
}
else if (browseName == "SourceNode")
{
SourceNode = value.ToNodeId();
}
Data.Add(browseName, value);
}
public GenericEvent ToGenericEvent(FilterManager filterManager)
{
GenericEvent ret = new GenericEvent(filterManager)
{
Time = Time,
SourceNode = SourceNode
};
foreach (KeyValuePair<QualifiedName,Variant> pair in Data)
{
ret.Set(AbsoluteName.ToString(pair.Key), pair.Value);
}
return ret;
}
#endregion
#region Public Properties
public DateTime Time {
get;
internal set;}
public NodeId SourceNode {
get;
internal set;}
public Dictionary<QualifiedName, Variant> Data { get; internal set;}
#endregion
}
#endregion
#region HistoricalEventFields class
internal class HistoricalEventFields : HistoryContinuationPoint
{
public HistoricalEventFields()
{
Data =
new HistoryEventFieldListCollection();
}
#region Public Properties
public HistoryEventFieldListCollection
Data {
get; set; }
#endregion
public HistoryEvent NextEventFields(uint NumValuesPerNode, out bool EventsRemaining)
{
HistoryEvent ret = new HistoryEvent();
EventsRemaining = false;
int iIndex = 0;
for (; (iIndex < NumValuesPerNode || NumValuesPerNode == 0) &&
Data.Count > 0; iIndex++)
{
ret.Events.Add(
Data.First());
}
{
EventsRemaining = true;
}
return ret;
}
}
#endregion
#region HistoryEventManager
internal class HistoryEventManager
{
#region Constructor
public HistoryEventManager(ServerManager server, QualifiedNameCollection browseNames)
{
m_filterManager = server.FilterManager;
RegisterEventFields(browseNames);
m_client = server.InternalClient;
m_requestContext = server.DefaultRequestContext;
m_sources = new Dictionary<NodeId, HistoryEventSource>();
}
#endregion
#region Public Properties
public QualifiedNameCollection EventFieldBrowseNames
{
get
{
return m_eventFieldBrowseNames;
}
}
public EventFilter EventFilter
{
get
{
return m_eventFilter;
}
}
#endregion
#region Public Interface
public void AddEventSource(NodeId nodeId, HistoryEventSource eventSource, bool createSubsricption)
{
if (createSubsricption)
{
CreateEventSubscription(nodeId, eventSource, m_eventFilter);
}
m_sources.Add(nodeId, eventSource);
}
private void CreateEventSubscription(NodeId nodeId, HistoryEventSource eventSource, EventFilter eventFilter)
{
List<InternalClientFullEventMonitoredItem> montoredItmes
= new List<InternalClientFullEventMonitoredItem>();
InternalClientFullEventMonitoredItem eventMonitoredItem = new InternalClientFullEventMonitoredItem()
{
Filter = eventFilter
};
eventMonitoredItem.Callback = OnInternalClientEventEvent;
eventMonitoredItem.CallbackData = eventSource;
montoredItmes.Add(eventMonitoredItem);
m_client.CreateEventMonitoredItems(
m_requestContext,
montoredItmes);
}
public HistoryEventSource GetEventSource(NodeId nodeId)
{
HistoryEventSource ret;
if (m_sources.TryGetValue(nodeId, out ret))
{
return ret;
}
return null;
}
#endregion
#region Helpers
private void RegisterEventFields(QualifiedNameCollection browseNames)
{
m_eventFieldBrowseNames = browseNames;
m_eventFilter = new EventFilter();
if (m_eventFieldBrowseNames == null)
{
m_eventFieldBrowseNames = new QualifiedNameCollection();
}
foreach (QualifiedName browseName in m_eventFieldBrowseNames)
{
m_filterManager.CreateFieldHandle(AbsoluteName.ToString(browseName));
SelectEventField(m_eventFilter, browseName);
}
}
private void SelectEventField(
EventFilter filter,
QualifiedName browseName)
{
SimpleAttributeOperand eventField = new SimpleAttributeOperand()
{
TypeDefinitionId = ObjectTypeIds.BaseEventType,
AttributeId = Attributes.Value,
BrowsePath = new QualifiedNameCollection(new QualifiedName[] { browseName })
};
filter.SelectClauses.Add(eventField);
}
private void OnInternalClientEventEvent(
RequestContext context,
MonitoredItemHandle itemHandle,
EventFieldList eventFields,
object callbackData)
{
HistoryEventSource eventSource = callbackData as HistoryEventSource;
AddEventFieldsToEventSource(eventSource, itemHandle.NodeId, eventFields);
}
private void AddEventFieldsToEventSource(HistoryEventSource eventSource, NodeId sourceNodeId, EventFieldList eventFields)
{
HistoryEventData data = new HistoryEventData();
if (eventFields.EventFields.Count == m_eventFieldBrowseNames.Count)
{
for (int i = 0; i < m_eventFieldBrowseNames.Count; i++)
{
try
{
data.AddEventField(m_eventFieldBrowseNames[i], eventFields.EventFields[i]);
}
catch (Exception)
{
}
}
eventSource.AddEvent(sourceNodeId, data);
}
}
#endregion
#region private fields
Dictionary<NodeId, HistoryEventSource> m_sources;
QualifiedNameCollection m_eventFieldBrowseNames;
EventFilter m_eventFilter;
FilterManager m_filterManager;
ServerInternalClient m_client;
RequestContext m_requestContext;
#endregion
}
#endregion
}