diff --git a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj index c074651a..900bfc23 100644 --- a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj +++ b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs index ee885abf..ae85b1de 100644 --- a/src/PepperDash.Essentials.Core/Routing/Extensions.cs +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -1,4 +1,6 @@ -using Serilog.Events; +using PepperDash.Essentials.Core.Queues; +using PepperDash.Essentials.Core.Routing; +using Serilog.Events; using System; using System.Collections.Generic; using System.Diagnostics; @@ -17,6 +19,8 @@ namespace PepperDash.Essentials.Core { private static readonly Dictionary RouteRequests = new Dictionary(); + private static readonly GenericQueue routeRequestQueue = new GenericQueue("routingQueue"); + /// /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute /// and then attempts a new Route and if sucessful, stores that RouteDescriptor @@ -33,6 +37,15 @@ namespace PepperDash.Essentials.Core ReleaseAndMakeRoute(destination, source, signalType, inputPort, outputPort); } + public static void ReleaseRoute(this IRoutingInputs destination) + { + routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty)); + } + + public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey) + { + routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey)); + } public static void RemoveRouteRequestForDestination(string destinationKey) { @@ -45,133 +58,6 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(LogEventLevel.Information, messageTemplate, null, destinationKey); } - private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null) - { - if (destination == null) throw new ArgumentNullException(nameof(destination)); - if (source == null) throw new ArgumentNullException(nameof(source)); - if (destinationPort == null) Debug.LogMessage(LogEventLevel.Information, "Destination port is null"); - if (sourcePort == null) Debug.LogMessage(LogEventLevel.Information, "Source port is null"); - - var routeRequest = new RouteRequest - { - Destination = destination, - DestinationPort = destinationPort, - Source = source, - SourcePort = sourcePort, - SignalType = signalType - }; - - - - var coolingDevice = destination as IWarmingCooling; - - //We already have a route request for this device, and it's a cooling device and is cooling - if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) - { - coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown; - - coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; - - RouteRequests[destination.Key] = routeRequest; - - Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down and already has a routing request stored. Storing new route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); - - return; - } - - //New Request - if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) - { - coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; - - RouteRequests.Add(destination.Key, routeRequest); - - Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down. Storing route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); - return; - } - - if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false) - { - var handledRequest = RouteRequests[destination.Key]; - - coolingDevice.IsCoolingDownFeedback.OutputChange -= handledRequest.HandleCooldown; - - RouteRequests.Remove(destination.Key); - - Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); - } - - destination.ReleaseRoute(destinationPort?.Key ?? string.Empty); - - RunRouteRequest(routeRequest); - } - - private static void RunRouteRequest(RouteRequest request) - { - try - { - if (request.Source == null) - return; - - var (audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort); - - if (audioOrSingleRoute == null && videoRoute == null) - return; - - RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(audioOrSingleRoute); - - if (videoRoute != null) - { - RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(videoRoute); - } - - Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination); - - audioOrSingleRoute.ExecuteRoutes(); - videoRoute?.ExecuteRoutes(); - } catch(Exception ex) - { - Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request); - } - } - - public static void ReleaseRoute(this IRoutingInputs destination) - { - ReleaseRoute(destination, string.Empty); - } - - /// - /// Will release the existing route on the destination, if it is found in - /// RouteDescriptorCollection.DefaultCollection - /// - /// - public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey) - { - try - { - Debug.LogMessage(LogEventLevel.Information, "Release route for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); - - if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling) - { - var coolingDevice = destination as IWarmingCooling; - - coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown; - } - - RouteRequests.Remove(destination.Key); - - var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPortKey); - if (current != null) - { - Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key); - current.ReleaseRoutes(); - } - } catch (Exception ex) - { - Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'",null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); - } - } - /// /// Builds a RouteDescriptor that contains the steps necessary to make a route between devices. /// Routes of type AudioVideo will be built as two separate routes, audio and video. If @@ -234,6 +120,126 @@ namespace PepperDash.Essentials.Core return (audioRouteDescriptor, videoRouteDescriptor); } + private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (source == null) throw new ArgumentNullException(nameof(source)); + if (destinationPort == null) Debug.LogMessage(LogEventLevel.Information, "Destination port is null"); + if (sourcePort == null) Debug.LogMessage(LogEventLevel.Information, "Source port is null"); + + var routeRequest = new RouteRequest + { + Destination = destination, + DestinationPort = destinationPort, + Source = source, + SourcePort = sourcePort, + SignalType = signalType + }; + + var coolingDevice = destination as IWarmingCooling; + + //We already have a route request for this device, and it's a cooling device and is cooling + if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) + { + coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown; + + coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; + + RouteRequests[destination.Key] = routeRequest; + + Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down and already has a routing request stored. Storing new route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); + + return; + } + + //New Request + if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) + { + coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; + + RouteRequests.Add(destination.Key, routeRequest); + + Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down. Storing route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); + return; + } + + if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false) + { + var handledRequest = RouteRequests[destination.Key]; + + coolingDevice.IsCoolingDownFeedback.OutputChange -= handledRequest.HandleCooldown; + + RouteRequests.Remove(destination.Key); + + Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); + } + + routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty)); + + routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest)); + } + + private static void RunRouteRequest(RouteRequest request) + { + try + { + if (request.Source == null) + return; + + var (audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort); + + if (audioOrSingleRoute == null && videoRoute == null) + return; + + RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(audioOrSingleRoute); + + if (videoRoute != null) + { + RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(videoRoute); + } + + Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination); + + audioOrSingleRoute.ExecuteRoutes(); + videoRoute?.ExecuteRoutes(); + } catch(Exception ex) + { + Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request); + } + } + + /// + /// Will release the existing route on the destination, if it is found in + /// RouteDescriptorCollection.DefaultCollection + /// + /// + private static void ReleaseRouteInternal(IRoutingInputs destination, string inputPortKey) + { + try + { + Debug.LogMessage(LogEventLevel.Information, "Release route for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); + + if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling) + { + var coolingDevice = destination as IWarmingCooling; + + coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown; + } + + RouteRequests.Remove(destination.Key); + + var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPortKey); + if (current != null) + { + Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key); + current.ReleaseRoutes(); + } + } catch (Exception ex) + { + Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'",null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); + } + } + /// /// The recursive part of this. Will stop on each device, search its inputs for the /// desired source and if not found, invoke this function for the each input port diff --git a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs index bd8a42d2..19d8655b 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs @@ -38,5 +38,10 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(ex, "Exception handling cooldown", Destination); } } + + public override string ToString() + { + return $"Route {Source?.Key ?? "No Source Device"}:{SourcePort?.Key ?? "auto"} to {Destination?.Key ?? "No Destination Device"}:{DestinationPort?.Key ?? "auto"}"; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RouteRequestQueueItem.cs b/src/PepperDash.Essentials.Core/Routing/RouteRequestQueueItem.cs new file mode 100644 index 00000000..05681991 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RouteRequestQueueItem.cs @@ -0,0 +1,45 @@ +using PepperDash.Core; +using PepperDash.Essentials.Core.Queues; +using System; +using Serilog.Events; + +namespace PepperDash.Essentials.Core.Routing +{ + public class RouteRequestQueueItem : IQueueMessage + { + private readonly Action action; + private readonly RouteRequest routeRequest; + + public RouteRequestQueueItem(Action routeAction, RouteRequest request) + { + action = routeAction; + routeRequest = request; + } + + public void Dispatch() + { + Debug.LogMessage(LogEventLevel.Information, "Dispatching route request {routeRequest}", null, routeRequest); + action(routeRequest); + } + } + + public class ReleaseRouteQueueItem : IQueueMessage + { + private readonly Action action; + private readonly IRoutingInputs destination; + private readonly string inputPortKey; + + public ReleaseRouteQueueItem(Action action, IRoutingInputs destination, string inputPortKey) + { + this.action = action; + this.destination = destination; + this.inputPortKey = inputPortKey; + } + + public void Dispatch() + { + Debug.LogMessage(LogEventLevel.Information, "Dispatching release route request for {destination}:{inputPortKey}", null, destination?.Key ?? "no destination", string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); + action(destination, inputPortKey); + } + } +} diff --git a/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj b/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj index 69f6d24e..d87662fb 100644 --- a/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj +++ b/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj @@ -30,6 +30,6 @@ - + \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/SoftCodec/GenericSoftCodec.cs b/src/PepperDash.Essentials.Devices.Common/SoftCodec/GenericSoftCodec.cs index f623c539..7fdad9b4 100644 --- a/src/PepperDash.Essentials.Devices.Common/SoftCodec/GenericSoftCodec.cs +++ b/src/PepperDash.Essentials.Devices.Common/SoftCodec/GenericSoftCodec.cs @@ -28,14 +28,14 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec for(var i = 1; i <= props.OutputCount; i++) { - var outputPort = new RoutingOutputPort($"{Key}-output{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this); + var outputPort = new RoutingOutputPort($"output{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this); OutputPorts.Add(outputPort); } for(var i = 1; i<= props.ContentInputCount; i++) { - var inputPort = new RoutingInputPort($"{Key}-contentInput{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, $"contentInput{i}", this); + var inputPort = new RoutingInputPort($"contentInput{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, $"contentInput{i}", this); InputPorts.Add(inputPort); } @@ -47,7 +47,7 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec for(var i = 1; i <=props.CameraInputCount; i++) { - var cameraPort = new RoutingInputPort($"{Key}-cameraInput{i}", eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, $"cameraInput{i}", this); + var cameraPort = new RoutingInputPort($"cameraInput{i}", eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, $"cameraInput{i}", this); InputPorts.Add(cameraPort); } diff --git a/src/PepperDash.Essentials/PepperDash.Essentials.csproj b/src/PepperDash.Essentials/PepperDash.Essentials.csproj index d64846e5..68cdf106 100644 --- a/src/PepperDash.Essentials/PepperDash.Essentials.csproj +++ b/src/PepperDash.Essentials/PepperDash.Essentials.csproj @@ -49,7 +49,7 @@ - +