/// <summary> /// Sends the Observations to the MQTT broker /// </summary> /// <param name="sendAdditionalProperties">If true, the 'Writeable' and 'Forceable' properties will be sent in the Observation</param> /// <returns></returns> private async Task <bool> UpdateValues(SubscriptionReader si, ReadResult <SubscriptionResultItem> results, bool sendAdditionalProperties = false) { if (!results.Success) { Prompts.AddRange(results.Prompts); return(false); } var devices = results.DataRead.GroupBy(a => a.ValueItemChangeEvent.Id.Remove(a.ValueItemChangeEvent.Id.LastIndexOf('/')).Remove(0, 2)); foreach (var device in devices) { var observations = new List <Observation>(); var deviceMessage = new IotEdgeMessage { Format = "rec2.3", Observations = observations, DeviceId = device.Key }; AddUpdatedValuesToMessage(observations, device.Key, device.ToList(), si.CachedSubscribedItems, sendAdditionalProperties); var messageBuilder = new MqttApplicationMessageBuilder(); var message = messageBuilder.WithRetainFlag().WithAtLeastOnceQoS().WithTopic(ValuePushTopic).WithPayload(deviceMessage.ToJson()).Build(); Logger.LogTrace(LogCategory.Processor, this.Name, $"Sending Message to MQTT Broker: {deviceMessage.ToJson()}"); await ManagedMqttClient.PublishAsync(message); } return(true); }
/// <summary> /// It is possible that if there is an internal timeout on the Subscription in EBO, that subscription read requests will end up returned with errors and /// an empty value. This attempts to resolve this, by manually getting the value of those points. /// </summary> /// <param name="si"></param> /// <param name="results"></param> private void CheckAndRetryValuesWithError(SubscriptionReader si, ReadResult <SubscriptionResultItem> results) { try { var erroredValuesToRetry = results.DataRead.Where(a => a.ValueItemChangeEvent.State == EwsValueStateEnum.Error); Logger.LogInfo(LogCategory.Processor, this.Name, $"Manually fetching values for {erroredValuesToRetry.Count()} objects."); var getValuesResponse = si.EwsConnection.GetValues(si, erroredValuesToRetry.Select(a => a.ValueItemChangeEvent.Id).ToArray()); foreach (var value in getValuesResponse.GetValuesItems) { var resultToEdit = results.DataRead.FirstOrDefault(a => a.ValueItemChangeEvent.Id == value.Id); if (resultToEdit == null) { continue; } var stateCorrect = Enum.TryParse(value.State, out EwsValueStateEnum ewsValueStateEnum); resultToEdit.ValueItemChangeEvent.State = stateCorrect ? ewsValueStateEnum : EwsValueStateEnum.Error; resultToEdit.ValueItemChangeEvent.Value = value.Value; } } catch (Exception ex) { Logger.LogError(LogCategory.Processor, this.Name, ex.ToJSON()); // Intentionally doing nothing here, we don't wanna stop the processor, let's just continue. } }
public INamedChannelReader <T> Subscribe(string name) { lock (_channel) { if (_closed) { throw new ChannelClosedException("Write end closed. Impossible to subscribe."); } INamedChannelReader <T> ch = new SubscriptionReader <T>(Name, name); return(ch); } }
/// <summary> /// Sends the Observations to the MQTT broker /// </summary> /// <param name="sendAdditionalProperties">If true, the 'Writeable' and 'Forceable' properties will be sent in the Observation</param> /// <returns></returns> private async Task <bool> UpdateValues(SubscriptionReader si, ReadResult <SubscriptionResultItem> results, bool sendAdditionalProperties = false) { if (!results.Success) { Prompts.AddRange(results.Prompts); return(false); } if (results.DataRead.Any(a => a.ValueItemChangeEvent.State == EwsValueStateEnum.Error)) { CheckAndRetryValuesWithError(si, results); } var signalChanges = results.DataRead.GroupBy(a => a.ValueItemChangeEvent.Id.Remove(a.ValueItemChangeEvent.Id.LastIndexOf('/')).Remove(0, 2)).ToList(); var devices = _tempSignals.GroupBy(a => a.DatabasePath.Remove(a.DatabasePath.LastIndexOf('/'))); foreach (var device in devices) { var observations = new List <Observation>(); var deviceMessage = new IotEdgeMessage { Format = "rec2.3", Observations = observations, DeviceId = device.Key }; var signalChangesForDevice = signalChanges.FirstOrDefault(a => a.Key == device.Key); AddUpdatedValuesToMessage(observations, device.Key, signalChangesForDevice == null || !signalChangesForDevice.ToList().Any() ? new List <SubscriptionResultItem>() : signalChangesForDevice.ToList(), si.CachedSubscribedItems, sendAdditionalProperties); if (deviceMessage.Observations != null && deviceMessage.Observations.Count > 0) { var messageBuilder = new MqttApplicationMessageBuilder(); var managedMessageBuilder = new ManagedMqttApplicationMessageBuilder(); var message = messageBuilder.WithRetainFlag().WithAtLeastOnceQoS().WithTopic(ValuePushTopic).WithPayload(deviceMessage.ToJson()).Build(); Logger.LogTrace(LogCategory.Processor, this.Name, $"Sending Message to MQTT Broker: {deviceMessage.ToJson()}"); await ManagedMqttClient.PublishAsync(managedMessageBuilder.WithApplicationMessage(message).Build()); } } return(true); }
private async Task <bool> ReadExistingSubscriptions(List <Signal> signals) { Logger.LogTrace(LogCategory.Processor, this.Name, $"Reading existing subscriptions.."); var activeSubscriptions = Cache.RetrieveItem($"ActiveSubscriptions", () => new List <string>(), CacheTenantId, 0) as List <string>; var activeSubscriptionsToIterate = activeSubscriptions.ToList(); foreach (var sub in activeSubscriptionsToIterate) { if (IsCancellationRequested) { return(false); } var subscription = Cache.RetrieveItem($"ActiveSubscriptions#{sub}", CacheTenantId); Logger.LogDebug(LogCategory.Processor, $"Reading existing subscription: {sub}"); try { CheckCancellationToken(); var si = new SubscriptionReader { Address = EboEwsSettings.Address, UserName = EboEwsSettings.UserName, Password = EboEwsSettings.Password, SubscriptionEventType = EwsSubscriptionEventTypeEnum.ValueItemChanged, SubscriptionId = sub }; var results = si.ReadData(); // Attempt to update the values by reading the subscription, if this fails return all Prompts if (!await UpdateValues(si, results)) { if (!si.IsResubscribeRequired) { return(false); } activeSubscriptions.Remove(sub); Cache.DeleteItem($"ActiveSubscriptions#{sub}", CacheTenantId); } // It's possible that the subscription id has changed if it failed to be renewed/ updated... reset it here if (si.SubsciptionChanged) { Logger.LogDebug(LogCategory.Processor, $"Subscription Id {sub} has changed to {si.SubscriptionId}, updating cache values to represent this"); activeSubscriptions.Remove(sub); activeSubscriptions.Add(si.SubscriptionId); Cache.DeleteItem($"ActiveSubscriptions#{sub}", CacheTenantId); Cache.AddOrUpdateItem(subscription, $"ActiveSubscriptions#{si.SubscriptionId}", CacheTenantId, 0); } } catch (Exception) { activeSubscriptions.Remove(sub); Cache.DeleteItem($"ActiveSubscriptions#{sub}", CacheTenantId); } } // Save any changes to cache Cache.AddOrUpdateItem(activeSubscriptions, $"ActiveSubscriptions", CacheTenantId, 0); return(true); }
private async Task <bool> SubscribeAndReadNew(List <Signal> signals) { Logger.LogTrace(LogCategory.Processor, this.Name, $"Creating and reading new subscriptions.."); var activeSubscriptions = Cache.RetrieveItem($"ActiveSubscriptions", () => new List <string>(), CacheTenantId, 0) as List <string>; var subscribedIds = new List <string>(); foreach (var subscription in activeSubscriptions) { var itemsToAdd = Cache.RetrieveItem <List <string> >($"ActiveSubscriptions#{subscription}", null, CacheTenantId); if (itemsToAdd != null) { subscribedIds.AddRange(itemsToAdd); } } var unsubscribedIds = signals.Select(a => a.EwsId).Where(a => !subscribedIds.Contains(a)).ToList(); Logger.LogDebug(LogCategory.Processor, this.Name, $"Found {unsubscribedIds.Count} points that are not currently subscribed to."); Logger.LogTrace(LogCategory.Processor, this.Name, $"Unsubscribed Point Ids: {unsubscribedIds.ToJSON()}"); while (unsubscribedIds.Any()) { if (IsCancellationRequested) { return(false); } try { var idsToSubscribeTo = unsubscribedIds.Take(MaxItemsPerSubscription).ToList(); CheckCancellationToken(); var si = new SubscriptionReader { Address = EboEwsSettings.Address, UserName = EboEwsSettings.UserName, Password = EboEwsSettings.Password, SubscriptionEventType = EwsSubscriptionEventTypeEnum.ValueItemChanged, Ids = idsToSubscribeTo }; // Attempt to update the values by reading the subscription, if this fails return all false as this could go on forever. var results = si.ReadData(); // If all the ids we subscribed to failed, just continue on.. nothing to see here.. if (si.FailedSubscribedItems.Count == idsToSubscribeTo.Count) { return(true); } if (!await UpdateValues(si, results, true)) { return(false); } Cache.AddOrUpdateItem(si.SubscribedItems, $"ActiveSubscriptions#{si.SubscriptionId}", CacheTenantId, 0); unsubscribedIds = unsubscribedIds.Skip(MaxItemsPerSubscription).ToList(); activeSubscriptions.Add(si.SubscriptionId); Cache.AddOrUpdateItem(activeSubscriptions, $"ActiveSubscriptions", CacheTenantId, 0); // Add any prompts generated from reader to the list of prompts Prompts.AddRange(si.ReadData().Prompts); if (si.FailedSubscribedItems.Any()) { Logger.LogInfo(LogCategory.Processor, this.Name, $"Some items failed to be subscribed to: {si.FailedSubscribedItems.ToJSON()}"); } } catch (Exception ex) { Prompts.Add(ex.ToPrompt()); break; } } return(true); // TODO: How to handle subscriptions to value items that keep failing? }