/// <summary>
 /// Subscription data changed
 /// </summary>
 /// <param name="subscription"></param>
 /// <param name="notification"></param>
 /// <param name="stringTable"></param>
 private void OnSubscriptionDataChanged(Subscription subscription,
                                        DataChangeNotification notification, IList <string> stringTable)
 {
     try {
         if (OnSubscriptionChange == null)
         {
             return;
         }
         var message = new SubscriptionNotificationModel {
             ServiceMessageContext = subscription.Session.MessageContext,
             ApplicationUri        = subscription.Session.Endpoint.Server.ApplicationUri,
             EndpointUrl           = subscription.Session.Endpoint.EndpointUrl,
             SubscriptionId        = Id,
             Notifications         = notification
                                     .ToMonitoredItemNotifications(subscription.MonitoredItems)
                                     .ToList()
         };
         OnSubscriptionChange?.Invoke(this, message);
     }
     catch (Exception ex) {
         _logger.Debug(ex, "Exception processing subscription notification");
     }
 }
            /// <summary>
            /// Subscription data changed
            /// </summary>
            /// <param name="subscription"></param>
            /// <param name="notification"></param>
            /// <param name="stringTable"></param>
            private void OnSubscriptionDataChanged(Subscription subscription,
                                                   DataChangeNotification notification, IList <string> stringTable)
            {
                try {
                    if (OnSubscriptionChange == null)
                    {
                        return;
                    }
                    if (notification == null)
                    {
                        _logger.Warning("DataChange for subscription: {Subscription} having empty notification",
                                        subscription.DisplayName);
                        return;
                    }

                    if (_currentlyMonitored == null)
                    {
                        _logger.Information("DataChange for subscription: {Subscription} having no monitored items yet",
                                            subscription.DisplayName);
                        return;
                    }

                    // check if notification is a keep alive
                    var isKeepAlive = notification?.MonitoredItems?.Count == 1 &&
                                      notification?.MonitoredItems?.First().ClientHandle == 0 &&
                                      notification?.MonitoredItems?.First().Message?.NotificationData?.Count == 0;
                    var sequenceNumber = notification?.MonitoredItems?.First().Message?.SequenceNumber;
                    var publishTime    = (notification?.MonitoredItems?.First().Message?.PublishTime).
                                         GetValueOrDefault(DateTime.MinValue);

                    _logger.Debug("DataChange for subscription: {Subscription}, sequence#: " +
                                  "{Sequence} isKeepAlive{KeepAlive}, publishTime: {PublishTime}",
                                  subscription.DisplayName, sequenceNumber, isKeepAlive, publishTime);

                    var message = new SubscriptionNotificationModel {
                        ServiceMessageContext = subscription.Session.MessageContext,
                        ApplicationUri        = subscription.Session.Endpoint.Server.ApplicationUri,
                        EndpointUrl           = subscription.Session.Endpoint.EndpointUrl,
                        SubscriptionId        = Id,
                        Notifications         = (!isKeepAlive)
                            ? notification.ToMonitoredItemNotifications(
                            subscription.MonitoredItems).ToList()
                            : new List <MonitoredItemNotificationModel>()
                    };
                    message.IsKeyMessage = true;

                    // add the heartbeat for monitored items that did not receive a a datachange notification
                    // Try access lock if we cannot continue...
                    List <MonitoredItemWrapper> currentlyMonitored = null;
                    if (_lock.Wait(0))
                    {
                        try {
                            currentlyMonitored = _currentlyMonitored;
                        }
                        finally {
                            _lock.Release();
                        }
                    }
                    if (currentlyMonitored != null)
                    {
                        // add the heartbeat for monitored items that did not receive a
                        // a datachange notification
                        foreach (var item in currentlyMonitored)
                        {
                            if (isKeepAlive ||
                                !notification.MonitoredItems.Exists(m => m.ClientHandle == item.Item.ClientHandle))
                            {
                                if (item.TriggerHeartbeat(publishTime))
                                {
                                    var heartbeatValue = item.Item.LastValue.ToMonitoredItemNotification(item.Item);
                                    if (heartbeatValue != null)
                                    {
                                        heartbeatValue.SequenceNumber = sequenceNumber;
                                        heartbeatValue.IsHeartbeat    = true;
                                        heartbeatValue.PublishTime    = publishTime;
                                        message.Notifications.Add(heartbeatValue);
                                    }
                                }
                                else
                                {
                                    // just reset the heartbeat for the items already processed
                                    item.TriggerHeartbeat(publishTime);
                                }
                            }
                        }
                    }
                    OnSubscriptionChange?.Invoke(this, message);
                }
                catch (Exception ex) {
                    _logger.Debug(ex, "Exception processing subscription notification");
                }
            }