/* ======================================================================== * Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; using Opc.Ua; using Opc.Ua.Client; namespace Opc.Ua.Client.Controls { /// /// A control which displays a list of events. /// public partial class EventListView : UserControl { /// /// Initializes the object. /// public EventListView() { InitializeComponent(); } #region Private Methods private Session m_session; private Subscription m_subscription; private MonitoredItem m_monitoredItem; private FilterDeclaration m_filter; private NodeId m_areaId; private bool m_isSubscribed; private bool m_displayConditions; #endregion #region Public Members /// /// Whether the control subscribes for new events. /// public bool IsSubscribed { get { return m_isSubscribed; } set { if (m_isSubscribed != value) { m_isSubscribed = value; if (m_session != null) { if (m_isSubscribed) { CreateSubscription(); } else { DeleteSubscription(); } } } } } /// /// Whether to display the events as conditions. /// public bool DisplayConditions { get { return m_displayConditions; } set { m_displayConditions = value; } } /// /// The context menu to use. /// public override ContextMenuStrip ContextMenuStrip { get { return this.EventsLV.ContextMenuStrip; } set { this.EventsLV.ContextMenuStrip = value; } } /// /// The event area displayed in the control. /// public NodeId AreaId { get { return m_areaId; } } /// /// The event filter applied to the control. /// public FilterDeclaration Filter { get { return m_filter; } } /// /// Changes the session. /// public void ChangeSession(Session session, bool fetchRecent) { if (Object.ReferenceEquals(session, m_session)) { return; } if (m_session != null) { DeleteSubscription(); m_session = null; } m_session = session; EventsLV.Items.Clear(); if (m_session != null && m_isSubscribed) { CreateSubscription(); if (fetchRecent) { ReadRecentHistory(); } } } /// /// Updates the control after the session has reconnected. /// public void SessionReconnected(Session session) { m_session = session; if (m_isSubscribed) { foreach (Subscription subscription in m_session.Subscriptions) { if (Object.ReferenceEquals(subscription.Handle, this)) { m_subscription = subscription; foreach (MonitoredItem monitoredItem in subscription.MonitoredItems) { m_monitoredItem = monitoredItem; break; } break; } } } } /// /// Changes the area monitored by the control. /// public void ChangeArea(NodeId areaId, bool fetchRecent) { m_areaId = areaId; EventsLV.Items.Clear(); if (fetchRecent) { ReadRecentHistory(); } if (m_subscription != null) { MonitoredItem monitoredItem = new MonitoredItem(m_monitoredItem); monitoredItem.StartNodeId = areaId; m_subscription.AddItem(monitoredItem); m_subscription.RemoveItem(m_monitoredItem); m_monitoredItem = monitoredItem; monitoredItem.Notification += new MonitoredItemNotificationEventHandler(MonitoredItem_Notification); m_subscription.ApplyChanges(); } } /// /// Changes the filter used to select the events. /// public void ChangeFilter(FilterDeclaration filter, bool fetchRecent) { m_filter = filter; EventsLV.Items.Clear(); int index = 0; if (m_filter != null) { // add or update existing columns. for (int ii = 0; ii < m_filter.Fields.Count; ii++) { if (m_filter.Fields[ii].DisplayInList) { if (index >= EventsLV.Columns.Count) { EventsLV.Columns.Add(new ColumnHeader()); } EventsLV.Columns[index].Text = m_filter.Fields[ii].InstanceDeclaration.DisplayName; EventsLV.Columns[index].TextAlign = HorizontalAlignment.Left; index++; } } } // remove extra columns. while (index < EventsLV.Columns.Count) { EventsLV.Columns.RemoveAt(EventsLV.Columns.Count - 1); } // adjust the width of the columns. for (int ii = 0; ii < EventsLV.Columns.Count; ii++) { EventsLV.Columns[ii].Width = -2; } // fetch recent history. if (fetchRecent) { ReadRecentHistory(); } // update subscription. if (m_subscription != null && m_filter != null) { m_monitoredItem.Filter = m_filter.GetFilter(); m_subscription.ApplyChanges(); } } /// /// Clears the event history in the control. /// public void ClearEventHistory() { EventsLV.Items.Clear(); // adjust the width of the columns. for (int ii = 0; ii < EventsLV.Columns.Count; ii++) { EventsLV.Columns[ii].Width = -2; } } /// /// Adds the event history to the control. /// public void AddEventHistory(HistoryEvent events) { for (int ii = 0; ii < events.Events.Count; ii++) { ListViewItem item = CreateListItem(m_filter, events.Events[ii].EventFields); EventsLV.Items.Add(item); } // adjust the width of the columns. for (int ii = 0; ii < EventsLV.Columns.Count; ii++) { EventsLV.Columns[ii].Width = -2; } } /// /// Refreshes the conditions displayed. /// public void ConditionRefresh() { if (m_subscription != null) { m_subscription.ConditionRefresh(); } } /// /// Returns the currently selected event at the specified index (null index is not valid). /// public VariantCollection GetSelectedEvent(int index) { if (EventsLV.SelectedItems.Count > index) { return EventsLV.SelectedItems[index].Tag as VariantCollection; } return null; } #endregion #region Private Methods /// /// Creates the subscription. /// private void CreateSubscription() { m_subscription = new Subscription(); m_subscription.Handle = this; m_subscription.DisplayName = null; m_subscription.PublishingInterval = 1000; m_subscription.KeepAliveCount = 10; m_subscription.LifetimeCount = 100; m_subscription.MaxNotificationsPerPublish = 1000; m_subscription.PublishingEnabled = true; m_subscription.TimestampsToReturn = TimestampsToReturn.Both; m_session.AddSubscription(m_subscription); m_subscription.Create(); m_monitoredItem = new MonitoredItem(); m_monitoredItem.StartNodeId = m_areaId; m_monitoredItem.AttributeId = Attributes.EventNotifier; m_monitoredItem.SamplingInterval = 0; m_monitoredItem.QueueSize = 1000; m_monitoredItem.DiscardOldest = true; ChangeFilter(m_filter, false); m_monitoredItem.Notification += new MonitoredItemNotificationEventHandler(MonitoredItem_Notification); m_subscription.AddItem(m_monitoredItem); m_subscription.ApplyChanges(); } /// /// Deletes the subscription. /// private void DeleteSubscription() { if (m_subscription != null) { m_subscription.Delete(true); m_session.RemoveSubscription(m_subscription); m_subscription = null; m_monitoredItem = null; } } /// /// Creates list item for an event. /// private ListViewItem CreateListItem(FilterDeclaration filter, VariantCollection fieldValues) { ListViewItem item = null; if (m_displayConditions) { NodeId conditionId = fieldValues[0].Value as NodeId; if (conditionId != null) { for (int ii = 0; ii < EventsLV.Items.Count; ii++) { VariantCollection fields = EventsLV.Items[ii].Tag as VariantCollection; if (fields != null && Utils.IsEqual(conditionId, fields[0].Value)) { item = EventsLV.Items[ii]; break; } } } } if (item == null) { item = new ListViewItem(); } item.Tag = fieldValues; int position = -1; for (int ii = 1; ii < filter.Fields.Count; ii++) { if (!filter.Fields[ii].DisplayInList) { continue; } position++; string text = null; Variant value = fieldValues[ii + 1]; // check for missing fields. if (value.Value == null) { text = String.Empty; } // display the name of a node instead of the node id. else if (value.TypeInfo.BuiltInType == BuiltInType.NodeId) { INode node = m_session.NodeCache.Find((NodeId)value.Value); if (node != null) { text = node.ToString(); } } // display local time for any time fields. else if (value.TypeInfo.BuiltInType == BuiltInType.DateTime) { DateTime datetime = (DateTime)value.Value; if (m_filter.Fields[ii].InstanceDeclaration.DisplayName.Contains("Time")) { text = datetime.ToLocalTime().ToString("HH:mm:ss.fff"); } else { text = datetime.ToLocalTime().ToString("yyyy-MM-dd"); } } // use default string format. else { text = value.ToString(); } // update subitem text. if (item.Text == String.Empty) { item.Text = text; item.SubItems[0].Text = text; } else { if (item.SubItems.Count <= position) { item.SubItems.Add(text); } else { item.SubItems[position].Text = text; } } } return item; } /// /// Updates the display with a new value for a monitored variable. /// private void MonitoredItem_Notification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e) { if (this.InvokeRequired) { this.BeginInvoke(new MonitoredItemNotificationEventHandler(MonitoredItem_Notification), monitoredItem, e); return; } try { // check for valid notification. EventFieldList notification = e.NotificationValue as EventFieldList; if (notification == null) { return; } // check if monitored item has changed. if (!Object.ReferenceEquals(m_monitoredItem, monitoredItem)) { return; } // check if the filter has changed. if (notification.EventFields.Count != m_filter.Fields.Count+1) { return; } if (m_displayConditions) { NodeId eventTypeId = m_filter.GetValue(Opc.Ua.BrowseNames.EventType, notification.EventFields, null); if (eventTypeId == Opc.Ua.ObjectTypeIds.RefreshStartEventType) { EventsLV.Items.Clear(); } if (eventTypeId == Opc.Ua.ObjectTypeIds.RefreshEndEventType) { return; } } // create an item and add to top of list. ListViewItem item = CreateListItem(m_filter, notification.EventFields); if (item.ListView == null) { EventsLV.Items.Insert(0, item); } // adjust the width of the columns. for (int ii = 0; ii < EventsLV.Columns.Count; ii++) { EventsLV.Columns[ii].Width = -2; } } catch (Exception exception) { ClientUtils.HandleException(this.Text, exception); } } /// /// Fetches the recent history. /// private void ReadRecentHistory() { // check if session is active. if (m_session != null) { // check if area supports history. IObject area = m_session.NodeCache.Find(m_areaId) as IObject; if (area != null && ((area.EventNotifier & EventNotifiers.HistoryRead) != 0)) { // get the last hour or 10 events. ReadEventDetails details = new ReadEventDetails(); details.StartTime = DateTime.UtcNow.AddSeconds(30); details.EndTime = details.StartTime.AddHours(-1); details.NumValuesPerNode = 10; details.Filter = m_filter.GetFilter(); // read the history. ReadHistory(details, m_areaId); } } } /// /// Fetches the recent history. /// private void ReadHistory(ReadEventDetails details, NodeId areaId) { HistoryReadValueIdCollection nodesToRead = new HistoryReadValueIdCollection(); HistoryReadValueId nodeToRead = new HistoryReadValueId(); nodeToRead.NodeId = areaId; nodesToRead.Add(nodeToRead); HistoryReadResultCollection results = null; DiagnosticInfoCollection diagnosticInfos = null; m_session.HistoryRead( null, new ExtensionObject(details), TimestampsToReturn.Neither, false, nodesToRead, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, nodesToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); if (StatusCode.IsBad(results[0].StatusCode)) { throw new ServiceResultException(results[0].StatusCode); } HistoryEvent events = ExtensionObject.ToEncodeable(results[0].HistoryData) as HistoryEvent; AddEventHistory(events); // release continuation points. if (results[0].ContinuationPoint != null && results[0].ContinuationPoint.Length > 0) { nodeToRead.ContinuationPoint = results[0].ContinuationPoint; m_session.HistoryRead( null, new ExtensionObject(details), TimestampsToReturn.Neither, true, nodesToRead, out results, out diagnosticInfos); } } /// /// Deletes the recent history. /// private void DeleteHistory(NodeId areaId, List events, FilterDeclaration filter) { // find the event id. int index = 0; foreach (FilterDeclarationField field in filter.Fields) { if (field.InstanceDeclaration.BrowseName == Opc.Ua.BrowseNames.EventId) { break; } index++; } // can't delete events if no event id. if (index >= filter.Fields.Count) { throw ServiceResultException.Create(StatusCodes.BadEventIdUnknown, "Cannot delete events if EventId was not selected."); } // build list of nodes to delete. DeleteEventDetails details = new DeleteEventDetails(); details.NodeId = areaId; foreach (VariantCollection e in events) { byte[] eventId = null; if (e.Count > index) { eventId = e[index].Value as byte[]; } details.EventIds.Add(eventId); } // delete the events. ExtensionObjectCollection nodesToUpdate = new ExtensionObjectCollection(); nodesToUpdate.Add(new ExtensionObject(details)); HistoryUpdateResultCollection results = null; DiagnosticInfoCollection diagnosticInfos = null; m_session.HistoryUpdate( null, nodesToUpdate, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, nodesToUpdate); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToUpdate); if (StatusCode.IsBad(results[0].StatusCode)) { throw new ServiceResultException(results[0].StatusCode); } // check for item level errors. if (results[0].OperationResults.Count > 0) { int count = 0; for (int ii = 0; ii < results[0].OperationResults.Count; ii++) { if (StatusCode.IsBad(results[0].OperationResults[ii])) { count++; } } // raise an error. if (count > 0) { throw ServiceResultException.Create( StatusCodes.BadEventIdUnknown, "Error deleting events. Only {0} of {1} deletes succeeded.", events.Count - count, events.Count); } } } private void ViewDetailsMI_Click(object sender, EventArgs e) { try { if (EventsLV.SelectedItems.Count == 0) { return; } VariantCollection fields = EventsLV.SelectedItems[0].Tag as VariantCollection; if (fields != null) { // new ViewEventDetailsDlg().ShowDialog(m_filter, fields); } } catch (Exception exception) { ClientUtils.HandleException(this.Text, exception); } } private void DeleteHistoryMI_Click(object sender, EventArgs e) { try { if (EventsLV.SelectedItems.Count == 0) { return; } List events = new List(); foreach (ListViewItem item in EventsLV.SelectedItems) { VariantCollection fields = item.Tag as VariantCollection; if (fields != null) { events.Add(fields); } } if (events.Count > 0) { DeleteHistory(m_areaId, events, m_filter); foreach (ListViewItem item in EventsLV.SelectedItems) { VariantCollection fields = item.Tag as VariantCollection; if (fields != null) { item.Font = new Font(item.Font, FontStyle.Strikeout); } } } } catch (Exception exception) { ClientUtils.HandleException(this.Text, exception); } } #endregion } }