From 61c85c7ca9acc4e412be4702d581c081ff229651 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Wed, 8 May 2024 08:37:24 -0500 Subject: [PATCH] refactor: move routing interfaces into their own files --- .../PepperDash.Essentials.Core.csproj | 3 + .../Routing/Extensions.cs | 252 +++++++++++ .../Routing/GenericExtensions.cs | 261 +++++++++++ .../Routing/IHasCurrentSourceInfoChange.cs | 30 ++ .../Routing/IInputSync.cs | 2 +- .../Routing/IRmcRouting.cs | 10 + .../Routing/IRmcRoutingWithFeedback.cs | 11 + .../Routing/IRouting.cs | 21 + .../Routing/IRoutingFeedback.cs | 16 + .../IRoutingHasVideoInputSyncFeedbacks.cs | 19 + .../Routing/IRoutingInputs.cs | 18 + .../Routing/IRoutingInputsExtensions.cs | 426 ------------------ .../Routing/IRoutingInputsOutputs.cs | 16 + .../Routing/IRoutingNumeric.cs | 7 + .../Routing/IRoutingNumericWithFeedback.cs | 9 + .../Routing/IRoutingOutputs.cs | 19 + .../Routing/IRoutingSink.cs | 22 + .../Routing/IRoutingSinkWithSwitching.cs | 20 + .../Routing/IRoutingSource.cs | 9 + .../Routing/IRoutingWithClear.cs | 12 + .../Routing/ITxRouting.cs | 8 + .../Routing/ITxRoutingWithFeedback.cs | 9 + .../Routing/RouteDescriptor.cs | 152 +++++++ .../Routing/RouteDescriptorCollection.cs | 123 +++++ .../Routing/RouteRequest.cs | 44 ++ .../Routing/RouteSwitchDescriptor.cs | 60 +++ .../Routing/RoutingInputPort.cs | 77 ++++ .../RoutingInputPortWithVideoStatuses.cs | 30 ++ .../Routing/RoutingInterfaces.cs | 203 --------- .../Routing/RoutingNumericEventArgs.cs | 43 ++ .../Routing/RoutingOutputPort.cs | 75 +++ .../Routing/RoutingPort.cs | 201 +-------- .../Routing/RoutingPortCollection.cs | 17 + .../Routing/eRoutingPortConnectionType.cs | 8 + .../Routing/eRoutingSignalType.cs | 16 + 35 files changed, 1440 insertions(+), 809 deletions(-) create mode 100644 src/PepperDash.Essentials.Core/Routing/Extensions.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/GenericExtensions.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IHasCurrentSourceInfoChange.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRmcRouting.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRmcRoutingWithFeedback.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRouting.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRoutingFeedback.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRoutingHasVideoInputSyncFeedbacks.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs delete mode 100644 src/PepperDash.Essentials.Core/Routing/IRoutingInputsExtensions.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRoutingInputsOutputs.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRoutingNumeric.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRoutingNumericWithFeedback.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/IRoutingWithClear.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/ITxRouting.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/ITxRoutingWithFeedback.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/RouteRequest.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/RoutingInputPortWithVideoStatuses.cs delete mode 100644 src/PepperDash.Essentials.Core/Routing/RoutingInterfaces.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/RoutingNumericEventArgs.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/eRoutingPortConnectionType.cs create mode 100644 src/PepperDash.Essentials.Core/Routing/eRoutingSignalType.cs diff --git a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj index da717cb4..c38c0fbd 100644 --- a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj +++ b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj @@ -21,6 +21,9 @@ pdbonly + + + diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs new file mode 100644 index 00000000..4d33ed4a --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -0,0 +1,252 @@ +using PepperDash.Core; +using Serilog.Events; +using System.Collections.Generic; +using System.Linq; + + +namespace PepperDash.Essentials.Core +{ + + /// + /// Extensions added to any IRoutingInputs classes to provide discovery-based routing + /// on those destinations. + /// + public static class Extensions + { + private static readonly Dictionary RouteRequests = new Dictionary(); + /// + /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute + /// and then attempts a new Route and if sucessful, stores that RouteDescriptor + /// in RouteDescriptorCollection.DefaultCollection + /// + public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) + { + var routeRequest = new RouteRequest { + Destination = destination, + Source = source, + 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.Verbose, "Device: {0} is cooling down and already has a routing request stored. Storing new route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key); + + return; + } + + //New Request + if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) + { + coolingDevice.IsCoolingDownFeedback.OutputChange -= routeRequest.HandleCooldown; + + coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; + + RouteRequests.Add(destination.Key, routeRequest); + + Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down. Storing route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key); + return; + } + + if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false) + { + RouteRequests.Remove(destination.Key); + Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is NOT cooling down. Removing stored route request and routing to source key: {1}", null, destination.Key, routeRequest.Source.Key); + } + + destination.ReleaseRoute(); + + RunRouteRequest(routeRequest); + } + + private static void RunRouteRequest(RouteRequest request) + { + if (request.Source == null) + return; + + var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType); + + if (newRoute == null) + return; + + RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute); + + Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination); + + newRoute.ExecuteRoutes(); + } + + /// + /// Will release the existing route on the destination, if it is found in + /// RouteDescriptorCollection.DefaultCollection + /// + /// + public static void ReleaseRoute(this IRoutingSink destination) + { + + 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); + if (current != null) + { + Debug.LogMessage(LogEventLevel.Debug, "Releasing current route: {0}", destination, current.Source.Key); + current.ReleaseRoutes(); + } + } + + /// + /// 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 + /// a route is discovered, a new RouteDescriptor is returned. If one or both parts + /// of an audio/video route are discovered a route descriptor is returned. If no route is + /// discovered, then null is returned + /// + public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) + { + var routeDescriptor = new RouteDescriptor(source, destination, signalType); + + // if it's a single signal type, find the route + if (!signalType.HasFlag(eRoutingSignalType.AudioVideo)) + { + Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {0}", null, source.Key); + + if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescriptor)) + routeDescriptor = null; + + return routeDescriptor; + } + // otherwise, audioVideo needs to be handled as two steps. + + Debug.LogMessage(LogEventLevel.Debug, "Attempting to build audio and video routes from {0}", destination, source.Key); + + var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescriptor); + + if (!audioSuccess) + Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key); + + var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescriptor); + + if (!videoSuccess) + Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key); + + if (!audioSuccess && !videoSuccess) + routeDescriptor = null; + + + return routeDescriptor; + } + + /// + /// 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 + /// hoping to find the source. + /// + /// + /// + /// The RoutingOutputPort whose link is being checked for a route + /// Prevents Devices from being twice-checked + /// This recursive function should not be called with AudioVideo + /// Just an informational counter + /// The RouteDescriptor being populated as the route is discovered + /// true if source is hit + static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, + RoutingOutputPort outputPortToUse, List alreadyCheckedDevices, + eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable) + { + + cycle++; + + Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", null, cycle, source.Key, destination.Key); + + RoutingInputPort goodInputPort = null; + + var destDevInputTies = TieLineCollection.Default.Where(t => + t.DestinationPort.ParentDevice == destination && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo))); + + // find a direct tie + var directTie = destDevInputTies.FirstOrDefault( + t => t.DestinationPort.ParentDevice == destination + && t.SourcePort.ParentDevice == source); + if (directTie != null) // Found a tie directly to the source + { + goodInputPort = directTie.DestinationPort; + } + else // no direct-connect. Walk back devices. + { + Debug.LogMessage(LogEventLevel.Verbose, "is not directly connected to {0}. Walking down tie lines", destination, source.Key); + + // No direct tie? Run back out on the inputs' attached devices... + // Only the ones that are routing devices + var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); + + //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration + if (alreadyCheckedDevices == null) + alreadyCheckedDevices = new List(); + alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs); + + foreach (var inputTieToTry in attachedMidpoints) + { + var upstreamDeviceOutputPort = inputTieToTry.SourcePort; + var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs; + Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {0}", destination, upstreamRoutingDevice.Key); + + // Check if this previous device has already been walked + if (alreadyCheckedDevices.Contains(upstreamRoutingDevice)) + { + Debug.LogMessage(LogEventLevel.Verbose, "Skipping input {0} on {1}, this was already checked", destination, upstreamRoutingDevice.Key, destination.Key); + continue; + } + // haven't seen this device yet. Do it. Pass the output port to the next + // level to enable switching on success + var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort, + alreadyCheckedDevices, signalType, cycle, routeTable); + if (upstreamRoutingSuccess) + { + Debug.LogMessage(LogEventLevel.Verbose, "Upstream device route found", destination); + goodInputPort = inputTieToTry.DestinationPort; + break; // Stop looping the inputs in this cycle + } + } + } + + + if (goodInputPort == null) + { + Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key); + return false; + } + + // we have a route on corresponding inputPort. *** Do the route *** + + if (outputPortToUse == null) + { + // it's a sink device + routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort)); + } + else if (destination is IRouting) + { + routeTable.Routes.Add(new RouteSwitchDescriptor(outputPortToUse, goodInputPort)); + } + else // device is merely IRoutingInputOutputs + Debug.LogMessage(LogEventLevel.Verbose, "No routing. Passthrough device", destination); + + return true; + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/GenericExtensions.cs b/src/PepperDash.Essentials.Core/Routing/GenericExtensions.cs new file mode 100644 index 00000000..4406d14f --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/GenericExtensions.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DM; + +using PepperDash.Core; +using Serilog.Events; + + +namespace PepperDash.Essentials.Core +{ + + /// + /// Extensions added to any IRoutingInputs classes to provide discovery-based routing + /// on those destinations. + /// + public static class GenericExtensions + { + //private static readonly Dictionary> RouteRequests = new(); + + /// + /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute + /// and then attempts a new Route and if sucessful, stores that RouteDescriptor + /// in RouteDescriptorCollection.DefaultCollection + /// + public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) + { + var routeRequest = new RouteRequest { + Destination = destination, + Source = source, + SignalType = signalType + }; + + var coolingDevice = destination as IWarmingCooling; + var existingRouteRequest = destination.GetRouteRequest(); + + //We already have a route request for this device, and it's a cooling device and is cooling + if (existingRouteRequest != null && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) + { + coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown; + + coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; + + destination.UpdateRouteRequest(routeRequest); + + Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down and already has a routing request stored. Storing new route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key); + + return; + } + + //New Request + if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) + { + coolingDevice.IsCoolingDownFeedback.OutputChange -= routeRequest.HandleCooldown; + + coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; + + destination.UpdateRouteRequest(routeRequest); + + Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down. Storing route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key); + return; + } + + if (existingRouteRequest != null && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false) + { + destination.UpdateRouteRequest(null); + + Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is NOT cooling down. Removing stored route request and routing to source key: {1}", null, destination.Key, routeRequest.Source.Key); + } + + destination.ReleaseRoute(); + + RunRouteRequest(routeRequest); + } + + public static void RunRouteRequest(RouteRequest request) + { + if (request.Source == null) + return; + + var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType); + + if (newRoute == null) + return; + + RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute); + + Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination); + + newRoute.ExecuteRoutes(); + } + + /// + /// Will release the existing route on the destination, if it is found in + /// RouteDescriptorCollection.DefaultCollection + /// + /// + public static void ReleaseRoute(this IRoutingSink destination) + { + var existingRouteRequest = destination.GetRouteRequest(); + + if (existingRouteRequest != null && destination is IWarmingCooling) + { + var coolingDevice = destination as IWarmingCooling; + + coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown; + } + + destination.UpdateRouteRequest(null); + + var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination); + if (current != null) + { + Debug.LogMessage(LogEventLevel.Debug, "Releasing current route: {0}", destination, current.Source.Key); + current.ReleaseRoutes(); + } + } + + /// + /// 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 + /// a route is discovered, a new RouteDescriptor is returned. If one or both parts + /// of an audio/video route are discovered a route descriptor is returned. If no route is + /// discovered, then null is returned + /// + public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) + { + var routeDescriptor = new RouteDescriptor(source, destination, signalType); + + // if it's a single signal type, find the route + if (!signalType.HasFlag(eRoutingSignalType.AudioVideo)) + { + Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {0}", null, source.Key); + + if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescriptor)) + routeDescriptor = null; + + return routeDescriptor; + } + // otherwise, audioVideo needs to be handled as two steps. + + Debug.LogMessage(LogEventLevel.Debug, "Attempting to build audio and video routes from {0}", destination, source.Key); + + var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescriptor); + + if (!audioSuccess) + Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key); + + var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescriptor); + + if (!videoSuccess) + Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key); + + if (!audioSuccess && !videoSuccess) + routeDescriptor = null; + + + return routeDescriptor; + } + + /// + /// 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 + /// hoping to find the source. + /// + /// + /// + /// The RoutingOutputPort whose link is being checked for a route + /// Prevents Devices from being twice-checked + /// This recursive function should not be called with AudioVideo + /// Just an informational counter + /// The RouteDescriptor being populated as the route is discovered + /// true if source is hit + static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, + RoutingOutputPort outputPortToUse, List> alreadyCheckedDevices, + eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable) + { + + cycle++; + + Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", null, cycle, source.Key, destination.Key); + + RoutingInputPort goodInputPort = null; + + var destDevInputTies = TieLineCollection.Default.Where(t => + t.DestinationPort.ParentDevice == destination && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo))); + + // find a direct tie + var directTie = destDevInputTies.FirstOrDefault( + t => t.DestinationPort.ParentDevice == destination + && t.SourcePort.ParentDevice == source); + if (directTie != null) // Found a tie directly to the source + { + goodInputPort = directTie.DestinationPort; + } + else // no direct-connect. Walk back devices. + { + Debug.LogMessage(LogEventLevel.Verbose, "is not directly connected to {0}. Walking down tie lines", destination, source.Key); + + // No direct tie? Run back out on the inputs' attached devices... + // Only the ones that are routing devices + var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); + + //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration + if (alreadyCheckedDevices == null) + alreadyCheckedDevices = new List>(); + alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs); + + foreach (var inputTieToTry in attachedMidpoints) + { + var upstreamDeviceOutputPort = inputTieToTry.SourcePort; + var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs; + Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {0}", destination, upstreamRoutingDevice.Key); + + // Check if this previous device has already been walked + if (alreadyCheckedDevices.Contains(upstreamRoutingDevice)) + { + Debug.LogMessage(LogEventLevel.Verbose, "Skipping input {0} on {1}, this was already checked", destination, upstreamRoutingDevice.Key, destination.Key); + continue; + } + // haven't seen this device yet. Do it. Pass the output port to the next + // level to enable switching on success + var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort, + alreadyCheckedDevices, signalType, cycle, routeTable); + if (upstreamRoutingSuccess) + { + Debug.LogMessage(LogEventLevel.Verbose, "Upstream device route found", destination); + goodInputPort = inputTieToTry.DestinationPort; + break; // Stop looping the inputs in this cycle + } + } + } + + + if (goodInputPort == null) + { + Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key); + return false; + } + + // we have a route on corresponding inputPort. *** Do the route *** + + if (outputPortToUse == null) + { + // it's a sink device + routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort)); + } + else if (destination is IRouting) + { + routeTable.Routes.Add(new RouteSwitchDescriptor(outputPortToUse, goodInputPort)); + } + else // device is merely IRoutingInputOutputs + Debug.LogMessage(LogEventLevel.Verbose, "No routing. Passthrough device", destination); + + return true; + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IHasCurrentSourceInfoChange.cs b/src/PepperDash.Essentials.Core/Routing/IHasCurrentSourceInfoChange.cs new file mode 100644 index 00000000..20b46520 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IHasCurrentSourceInfoChange.cs @@ -0,0 +1,30 @@ +/* Unmerged change from project 'PepperDash.Essentials.Core (net6)' +Before: +namespace PepperDash.Essentials.Core.Routing.Interfaces +After: +using PepperDash; +using PepperDash.Essentials; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Routing; +using PepperDash.Essentials.Core.Routing; +using PepperDash.Essentials.Core.Routing.Interfaces +*/ +namespace PepperDash.Essentials.Core.Routing +{ + /// + /// The handler type for a Room's SourceInfoChange + /// + public delegate void SourceInfoChangeHandler(SourceListItem info, ChangeType type); + //******************************************************************************************* + // Interfaces + + /// + /// For rooms with a single presentation source, change event + /// + public interface IHasCurrentSourceInfoChange + { + string CurrentSourceInfoKey { get; set; } + SourceListItem CurrentSourceInfo { get; set; } + event SourceInfoChangeHandler CurrentSourceChange; + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IInputSync.cs b/src/PepperDash.Essentials.Core/Routing/IInputSync.cs index 465b48a2..accb70a7 100644 --- a/src/PepperDash.Essentials.Core/Routing/IInputSync.cs +++ b/src/PepperDash.Essentials.Core/Routing/IInputSync.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace PepperDash.Essentials.Core.Routing { - public interface IVideoSync: IKeyed + public interface IVideoSync : IKeyed { bool VideoSyncDetected { get; } diff --git a/src/PepperDash.Essentials.Core/Routing/IRmcRouting.cs b/src/PepperDash.Essentials.Core/Routing/IRmcRouting.cs new file mode 100644 index 00000000..bddcbcf7 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRmcRouting.cs @@ -0,0 +1,10 @@ +namespace PepperDash.Essentials.Core.Routing +{ + /// + /// Defines a receiver that has internal routing (DM-RMC-4K-Z-SCALER-C) + /// + public interface IRmcRouting : IRoutingNumeric + { + IntFeedback AudioVideoSourceNumericFeedback { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRmcRoutingWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRmcRoutingWithFeedback.cs new file mode 100644 index 00000000..3648204c --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRmcRoutingWithFeedback.cs @@ -0,0 +1,11 @@ +using PepperDash.Essentials.Core.Routing; + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines an IRmcRouting with a feedback event + /// + public interface IRmcRoutingWithFeedback : IRmcRouting + { + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRouting.cs b/src/PepperDash.Essentials.Core/Routing/IRouting.cs new file mode 100644 index 00000000..e3231858 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRouting.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines a midpoint device as have internal routing. Any devices in the middle of the + /// signal chain, that do switching, must implement this for routing to work otherwise + /// the routing algorithm will treat the IRoutingInputsOutputs device as a passthrough + /// device. + /// + public interface IRouting : IRoutingInputsOutputs + { + void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType); + } + + /*public interface IRouting : IRoutingInputsOutputs + { + void ExecuteSwitch(TInputSelector inputSelector, TOutputSelector outputSelector, eRoutingSignalType signalType); + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingFeedback.cs new file mode 100644 index 00000000..b8b22915 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingFeedback.cs @@ -0,0 +1,16 @@ +using System; + +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines an event structure for reporting output route data + /// + public interface IRoutingFeedback : IKeyName + { + event EventHandler NumericSwitchChange; + //void OnSwitchChange(RoutingNumericEventArgs e); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingHasVideoInputSyncFeedbacks.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingHasVideoInputSyncFeedbacks.cs new file mode 100644 index 00000000..0f6d7834 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingHasVideoInputSyncFeedbacks.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DM; + +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + + public interface IRoutingHasVideoInputSyncFeedbacks + { + FeedbackCollection VideoInputSyncFeedbacks { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs new file mode 100644 index 00000000..ff96fded --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs @@ -0,0 +1,18 @@ +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines a class that has a collection of RoutingInputPorts + /// + public interface IRoutingInputs : IKeyed + { + RoutingPortCollection InputPorts { get; } + } + +/* public interface IRoutingInputs : IKeyed + { + RoutingPortCollection, TSelector> InputPorts { get; } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingInputsExtensions.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingInputsExtensions.cs deleted file mode 100644 index 605569d2..00000000 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingInputsExtensions.cs +++ /dev/null @@ -1,426 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharpPro; -using Crestron.SimplSharpPro.DM; - -using PepperDash.Core; -using Serilog.Events; - - -namespace PepperDash.Essentials.Core -{ - public class RouteRequest - { - public IRoutingSink Destination {get; set;} - public IRoutingOutputs Source {get; set;} - public eRoutingSignalType SignalType {get; set;} - - public void HandleCooldown(object sender, FeedbackEventArgs args) - { - var coolingDevice = sender as IWarmingCooling; - - if(args.BoolValue == false) - { - Destination.ReleaseAndMakeRoute(Source, SignalType); - - if(sender == null) return; - - coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; - } - } - } - - /// - /// Extensions added to any IRoutingInputs classes to provide discovery-based routing - /// on those destinations. - /// - public static class IRoutingInputsExtensions - { - private static Dictionary RouteRequests = new Dictionary(); - /// - /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute - /// and then attempts a new Route and if sucessful, stores that RouteDescriptor - /// in RouteDescriptorCollection.DefaultCollection - /// - public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) - { - var routeRequest = new RouteRequest { - Destination = destination, - Source = source, - SignalType = signalType - }; - - var coolingDevice = destination as IWarmingCooling; - - RouteRequest existingRouteRequest; - - //We already have a route request for this device, and it's a cooling device and is cooling - if (RouteRequests.TryGetValue(destination.Key, out existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) - { - coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown; - - coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; - - RouteRequests[destination.Key] = routeRequest; - - Debug.LogMessage(LogEventLevel.Verbose, "******************************************************** Device: {0} is cooling down and already has a routing request stored. Storing new route request to route to source key: {1}", destination.Key, routeRequest.Source.Key); - - return; - } - - //New Request - if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) - { - coolingDevice.IsCoolingDownFeedback.OutputChange -= routeRequest.HandleCooldown; - - coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; - - RouteRequests.Add(destination.Key, routeRequest); - - Debug.LogMessage(LogEventLevel.Verbose, "******************************************************** Device: {0} is cooling down. Storing route request to route to source key: {1}", destination.Key, routeRequest.Source.Key); - return; - } - - if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false) - { - RouteRequests.Remove(destination.Key); - Debug.LogMessage(LogEventLevel.Verbose, "******************************************************** Device: {0} is NOT cooling down. Removing stored route request and routing to source key: {1}", destination.Key, routeRequest.Source.Key); - } - - destination.ReleaseRoute(); - - RunRouteRequest(routeRequest); - } - - public static void RunRouteRequest(RouteRequest request) - { - if (request.Source == null) return; - var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType); - if (newRoute == null) return; - RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute); - Debug.LogMessage(LogEventLevel.Verbose, request.Destination, "Executing full route"); - newRoute.ExecuteRoutes(); - } - - /// - /// Will release the existing route on the destination, if it is found in - /// RouteDescriptorCollection.DefaultCollection - /// - /// - public static void ReleaseRoute(this IRoutingSink destination) - { - RouteRequest existingRequest; - - if (RouteRequests.TryGetValue(destination.Key, out existingRequest) && destination is IWarmingCooling) - { - var coolingDevice = destination as IWarmingCooling; - - coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown; - } - - RouteRequests.Remove(destination.Key); - - var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination); - if (current != null) - { - Debug.LogMessage(LogEventLevel.Debug, destination, "Releasing current route: {0}", current.Source.Key); - current.ReleaseRoutes(); - } - } - - /// - /// 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 - /// a route is discovered, a new RouteDescriptor is returned. If one or both parts - /// of an audio/video route are discovered a route descriptor is returned. If no route is - /// discovered, then null is returned - /// - public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) - { - var routeDescr = new RouteDescriptor(source, destination, signalType); - // if it's a single signal type, find the route - if ((signalType & (eRoutingSignalType.Audio & eRoutingSignalType.Video)) == (eRoutingSignalType.Audio & eRoutingSignalType.Video)) - { - Debug.LogMessage(LogEventLevel.Debug, destination, "Attempting to build source route from {0}", source.Key); - if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescr)) - routeDescr = null; - } - // otherwise, audioVideo needs to be handled as two steps. - else - { - Debug.LogMessage(LogEventLevel.Debug, destination, "Attempting to build audio and video routes from {0}", source.Key); - var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescr); - if (!audioSuccess) - Debug.LogMessage(LogEventLevel.Debug, destination, "Cannot find audio route to {0}", source.Key); - var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescr); - if (!videoSuccess) - Debug.LogMessage(LogEventLevel.Debug, destination, "Cannot find video route to {0}", source.Key); - if (!audioSuccess && !videoSuccess) - routeDescr = null; - } - - //Debug.LogMessage(LogEventLevel.Debug, destination, "Route{0} discovered", routeDescr == null ? " NOT" : ""); - return routeDescr; - } - - /// - /// 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 - /// hoping to find the source. - /// - /// - /// - /// The RoutingOutputPort whose link is being checked for a route - /// Prevents Devices from being twice-checked - /// This recursive function should not be called with AudioVideo - /// Just an informational counter - /// The RouteDescriptor being populated as the route is discovered - /// true if source is hit - static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, - RoutingOutputPort outputPortToUse, List alreadyCheckedDevices, - eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable) - { - cycle++; - Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", cycle, source.Key, destination.Key); - - RoutingInputPort goodInputPort = null; - var destDevInputTies = TieLineCollection.Default.Where(t => - t.DestinationPort.ParentDevice == destination && (t.Type == signalType || (t.Type & (eRoutingSignalType.Audio | eRoutingSignalType.Video)) == (eRoutingSignalType.Audio | eRoutingSignalType.Video))); - - // find a direct tie - var directTie = destDevInputTies.FirstOrDefault( - t => t.DestinationPort.ParentDevice == destination - && t.SourcePort.ParentDevice == source); - if (directTie != null) // Found a tie directly to the source - { - goodInputPort = directTie.DestinationPort; - } - else // no direct-connect. Walk back devices. - { - Debug.LogMessage(LogEventLevel.Verbose, destination, "is not directly connected to {0}. Walking down tie lines", source.Key); - - // No direct tie? Run back out on the inputs' attached devices... - // Only the ones that are routing devices - var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); - - //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration - if (alreadyCheckedDevices == null) - alreadyCheckedDevices = new List(); - alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs); - - foreach (var inputTieToTry in attachedMidpoints) - { - var upstreamDeviceOutputPort = inputTieToTry.SourcePort; - var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs; - Debug.LogMessage(LogEventLevel.Verbose, destination, "Trying to find route on {0}", upstreamRoutingDevice.Key); - - // Check if this previous device has already been walked - if (alreadyCheckedDevices.Contains(upstreamRoutingDevice)) - { - Debug.LogMessage(LogEventLevel.Verbose, destination, "Skipping input {0} on {1}, this was already checked", upstreamRoutingDevice.Key, destination.Key); - continue; - } - // haven't seen this device yet. Do it. Pass the output port to the next - // level to enable switching on success - var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort, - alreadyCheckedDevices, signalType, cycle, routeTable); - if (upstreamRoutingSuccess) - { - Debug.LogMessage(LogEventLevel.Verbose, destination, "Upstream device route found"); - goodInputPort = inputTieToTry.DestinationPort; - break; // Stop looping the inputs in this cycle - } - } - } - - // we have a route on corresponding inputPort. *** Do the route *** - if (goodInputPort != null) - { - //Debug.LogMessage(LogEventLevel.Verbose, destination, "adding RouteDescriptor"); - if (outputPortToUse == null) - { - // it's a sink device - routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort)); - } - else if (destination is IRouting) - { - routeTable.Routes.Add(new RouteSwitchDescriptor (outputPortToUse, goodInputPort)); - } - else // device is merely IRoutingInputOutputs - Debug.LogMessage(LogEventLevel.Verbose, destination, " No routing. Passthrough device"); - //Debug.LogMessage(LogEventLevel.Verbose, destination, "Exiting cycle {0}", cycle); - return true; - } - - Debug.LogMessage(LogEventLevel.Verbose, destination, "No route found to {0}", source.Key); - return false; - } - } - - - - - - // MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE - - - /// - /// A collection of RouteDescriptors - typically the static DefaultCollection is used - /// - public class RouteDescriptorCollection - { - public static RouteDescriptorCollection DefaultCollection - { - get - { - if (_DefaultCollection == null) - _DefaultCollection = new RouteDescriptorCollection(); - return _DefaultCollection; - } - } - static RouteDescriptorCollection _DefaultCollection; - - List RouteDescriptors = new List(); - - /// - /// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the - /// destination exists already, it will not be added - in order to preserve - /// proper route releasing. - /// - /// - public void AddRouteDescriptor(RouteDescriptor descriptor) - { - if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)) - { - Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination, - "Route to [{0}] already exists in global routes table", descriptor.Source.Key); - return; - } - RouteDescriptors.Add(descriptor); - } - - /// - /// Gets the RouteDescriptor for a destination - /// - /// null if no RouteDescriptor for a destination exists - public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination) - { - return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination); - } - - /// - /// Returns the RouteDescriptor for a given destination AND removes it from collection. - /// Returns null if no route with the provided destination exists. - /// - public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination) - { - var descr = GetRouteDescriptorForDestination(destination); - if (descr != null) - RouteDescriptors.Remove(descr); - return descr; - } - } - - /// - /// Represents an collection of individual route steps between Source and Destination - /// - public class RouteDescriptor - { - public IRoutingInputs Destination { get; private set; } - public IRoutingOutputs Source { get; private set; } - public eRoutingSignalType SignalType { get; private set; } - public List Routes { get; private set; } - - - public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) - { - Destination = destination; - Source = source; - SignalType = signalType; - Routes = new List(); - } - - /// - /// Executes all routes described in this collection. Typically called via - /// extension method IRoutingInputs.ReleaseAndMakeRoute() - /// - public void ExecuteRoutes() - { - foreach (var route in Routes) - { - Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", route.ToString()); - if (route.SwitchingDevice is IRoutingSink) - { - var device = route.SwitchingDevice as IRoutingSinkWithSwitching; - if (device == null) - continue; - - device.ExecuteSwitch(route.InputPort.Selector); - } - else if (route.SwitchingDevice is IRouting) - { - (route.SwitchingDevice as IRouting).ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType); - route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType); - Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); - } - } - } - - /// - /// Releases all routes in this collection. Typically called via - /// extension method IRoutingInputs.ReleaseAndMakeRoute() - /// - public void ReleaseRoutes() - { - foreach (var route in Routes) - { - if (route.SwitchingDevice is IRouting) - { - // Pull the route from the port. Whatever is watching the output's in use tracker is - // responsible for responding appropriately. - route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType); - Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); - } - } - } - - public override string ToString() - { - var routesText = Routes.Select(r => r.ToString()).ToArray(); - return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText)); - } - } - - /// - /// Represents an individual link for a route - /// - public class RouteSwitchDescriptor - { - public IRoutingInputs SwitchingDevice { get { return InputPort.ParentDevice; } } - public RoutingOutputPort OutputPort { get; set; } - public RoutingInputPort InputPort { get; set; } - - public RouteSwitchDescriptor(RoutingInputPort inputPort) - { - InputPort = inputPort; - } - - public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort) - { - InputPort = inputPort; - OutputPort = outputPort; - } - - public override string ToString() - { - if(SwitchingDevice is IRouting) - return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector); - else - return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector); - - } - } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingInputsOutputs.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingInputsOutputs.cs new file mode 100644 index 00000000..e202fa3c --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingInputsOutputs.cs @@ -0,0 +1,16 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// For devices like RMCs, baluns, other devices with no switching. + /// + public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs + { + } + +/* /// + /// For devices like RMCs, baluns, other devices with no switching. + /// + public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs + { + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingNumeric.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingNumeric.cs new file mode 100644 index 00000000..d41909f1 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingNumeric.cs @@ -0,0 +1,7 @@ +namespace PepperDash.Essentials.Core +{ + public interface IRoutingNumeric : IRouting + { + void ExecuteNumericSwitch(ushort input, ushort output, eRoutingSignalType type); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingNumericWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingNumericWithFeedback.cs new file mode 100644 index 00000000..e278a193 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingNumericWithFeedback.cs @@ -0,0 +1,9 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines an IRoutingNumeric with a feedback event + /// + public interface IRoutingNumericWithFeedback : IRoutingNumeric, IRoutingFeedback + { + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs new file mode 100644 index 00000000..d3bc70de --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs @@ -0,0 +1,19 @@ +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines a class that has a collection of RoutingOutputPorts + /// + + public interface IRoutingOutputs : IKeyed + { + RoutingPortCollection OutputPorts { get; } + } + +/* public interface IRoutingOutputs : IKeyed + { + RoutingPortCollection, TSelector> OutputPorts { get; } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs new file mode 100644 index 00000000..c51ed12f --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs @@ -0,0 +1,22 @@ +using PepperDash.Essentials.Core.Routing; + +namespace PepperDash.Essentials.Core +{ + /// + /// For fixed-source endpoint devices + /// + public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange + { + + } + + /*/// + /// For fixed-source endpoint devices + /// + public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange + { + void UpdateRouteRequest(RouteRequest request); + + RouteRequest GetRouteRequest(); + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs new file mode 100644 index 00000000..b31bd973 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs @@ -0,0 +1,20 @@ +using System; + +namespace PepperDash.Essentials.Core +{ + /// + /// Endpoint device like a display, that selects inputs + /// + public interface IRoutingSinkWithSwitching : IRoutingSink + { + void ExecuteSwitch(object inputSelector); + } + +/* /// + /// Endpoint device like a display, that selects inputs + /// + public interface IRoutingSinkWithSwitching : IRoutingSink + { + void ExecuteSwitch(TSelector inputSelector); + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs new file mode 100644 index 00000000..6ae51a2b --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs @@ -0,0 +1,9 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines an IRoutingOutputs devices as being a source - the start of the chain + /// + public interface IRoutingSource : IRoutingOutputs + { + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingWithClear.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingWithClear.cs new file mode 100644 index 00000000..077d4ecd --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingWithClear.cs @@ -0,0 +1,12 @@ +namespace PepperDash.Essentials.Core +{ + public interface IRoutingWithClear : IRouting + { + /// + /// Clears a route to an output, however a device needs to do that + /// + /// Output to clear + /// signal type to clear + void ClearRoute(object outputSelector, eRoutingSignalType signalType); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/ITxRouting.cs b/src/PepperDash.Essentials.Core/Routing/ITxRouting.cs new file mode 100644 index 00000000..0950e6a6 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/ITxRouting.cs @@ -0,0 +1,8 @@ +namespace PepperDash.Essentials.Core +{ + public interface ITxRouting : IRoutingNumeric + { + IntFeedback VideoSourceNumericFeedback { get; } + IntFeedback AudioSourceNumericFeedback { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/ITxRoutingWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/ITxRoutingWithFeedback.cs new file mode 100644 index 00000000..484fa134 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/ITxRoutingWithFeedback.cs @@ -0,0 +1,9 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines an IRmcRouting with a feedback event + /// + public interface ITxRoutingWithFeedback : ITxRouting + { + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs b/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs new file mode 100644 index 00000000..ebacc809 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs @@ -0,0 +1,152 @@ +using System.Collections.Generic; +using System.Linq; +using Crestron.SimplSharpPro; + +using PepperDash.Core; +using Serilog.Events; + + +namespace PepperDash.Essentials.Core +{ + /// + /// Represents an collection of individual route steps between Source and Destination + /// + public class RouteDescriptor + { + public IRoutingInputs Destination { get; private set; } + public IRoutingOutputs Source { get; private set; } + public eRoutingSignalType SignalType { get; private set; } + public List Routes { get; private set; } + + + public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) + { + Destination = destination; + Source = source; + SignalType = signalType; + Routes = new List(); + } + + /// + /// Executes all routes described in this collection. Typically called via + /// extension method IRoutingInputs.ReleaseAndMakeRoute() + /// + public void ExecuteRoutes() + { + foreach (var route in Routes) + { + Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}",null, route.ToString()); + + if (route.SwitchingDevice is IRoutingSinkWithSwitching sink) + { + sink.ExecuteSwitch(route.InputPort.Selector); + continue; + } + + if (route.SwitchingDevice is IRouting switchingDevice) + { + switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType); + + route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType); + + Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); + } + } + } + + /// + /// Releases all routes in this collection. Typically called via + /// extension method IRoutingInputs.ReleaseAndMakeRoute() + /// + public void ReleaseRoutes() + { + foreach (var route in Routes) + { + if (route.SwitchingDevice is IRouting) + { + // Pull the route from the port. Whatever is watching the output's in use tracker is + // responsible for responding appropriately. + route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType); + Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}",null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); + } + } + } + + public override string ToString() + { + var routesText = Routes.Select(r => r.ToString()).ToArray(); + return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText)); + } + } + + /*/// + /// Represents an collection of individual route steps between Source and Destination + /// + public class RouteDescriptor + { + public IRoutingInputs Destination { get; private set; } + public IRoutingOutputs Source { get; private set; } + public eRoutingSignalType SignalType { get; private set; } + public List> Routes { get; private set; } + + + public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) + { + Destination = destination; + Source = source; + SignalType = signalType; + Routes = new List>(); + } + + /// + /// Executes all routes described in this collection. Typically called via + /// extension method IRoutingInputs.ReleaseAndMakeRoute() + /// + public void ExecuteRoutes() + { + foreach (var route in Routes) + { + Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", null, route.ToString()); + + if (route.SwitchingDevice is IRoutingSinkWithSwitching sink) + { + sink.ExecuteSwitch(route.InputPort.Selector); + continue; + } + + if (route.SwitchingDevice is IRouting switchingDevice) + { + switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType); + + route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType); + + Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); + } + } + } + + /// + /// Releases all routes in this collection. Typically called via + /// extension method IRoutingInputs.ReleaseAndMakeRoute() + /// + public void ReleaseRoutes() + { + foreach (var route in Routes) + { + if (route.SwitchingDevice is IRouting) + { + // Pull the route from the port. Whatever is watching the output's in use tracker is + // responsible for responding appropriately. + route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType); + Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); + } + } + } + + public override string ToString() + { + var routesText = Routes.Select(r => r.ToString()).ToArray(); + return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText)); + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs b/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs new file mode 100644 index 00000000..c9e3da37 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using System.Linq; + +using PepperDash.Core; +using Serilog.Events; + + +namespace PepperDash.Essentials.Core +{ + /// + /// A collection of RouteDescriptors - typically the static DefaultCollection is used + /// + public class RouteDescriptorCollection + { + public static RouteDescriptorCollection DefaultCollection + { + get + { + if (_DefaultCollection == null) + _DefaultCollection = new RouteDescriptorCollection(); + return _DefaultCollection; + } + } + private static RouteDescriptorCollection _DefaultCollection; + + private readonly List RouteDescriptors = new List(); + + /// + /// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the + /// destination exists already, it will not be added - in order to preserve + /// proper route releasing. + /// + /// + public void AddRouteDescriptor(RouteDescriptor descriptor) + { + if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)) + { + Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination, + "Route to [{0}] already exists in global routes table", descriptor.Source.Key); + return; + } + RouteDescriptors.Add(descriptor); + } + + /// + /// Gets the RouteDescriptor for a destination + /// + /// null if no RouteDescriptor for a destination exists + public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination) + { + return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination); + } + + /// + /// Returns the RouteDescriptor for a given destination AND removes it from collection. + /// Returns null if no route with the provided destination exists. + /// + public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination) + { + var descr = GetRouteDescriptorForDestination(destination); + if (descr != null) + RouteDescriptors.Remove(descr); + return descr; + } + } + + /*/// + /// A collection of RouteDescriptors - typically the static DefaultCollection is used + /// + public class RouteDescriptorCollection + { + public static RouteDescriptorCollection DefaultCollection + { + get + { + if (_DefaultCollection == null) + _DefaultCollection = new RouteDescriptorCollection(); + return _DefaultCollection; + } + } + private static RouteDescriptorCollection _DefaultCollection; + + private readonly List RouteDescriptors = new List(); + + /// + /// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the + /// destination exists already, it will not be added - in order to preserve + /// proper route releasing. + /// + /// + public void AddRouteDescriptor(RouteDescriptor descriptor) + { + if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)) + { + Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination, + "Route to [{0}] already exists in global routes table", descriptor.Source.Key); + return; + } + RouteDescriptors.Add(descriptor); + } + + /// + /// Gets the RouteDescriptor for a destination + /// + /// null if no RouteDescriptor for a destination exists + public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination) + { + return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination); + } + + /// + /// Returns the RouteDescriptor for a given destination AND removes it from collection. + /// Returns null if no route with the provided destination exists. + /// + public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination) + { + var descr = GetRouteDescriptorForDestination(destination); + if (descr != null) + RouteDescriptors.Remove(descr); + return descr; + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs new file mode 100644 index 00000000..f4836c6d --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs @@ -0,0 +1,44 @@ +namespace PepperDash.Essentials.Core +{ + public class RouteRequest + { + public IRoutingSink Destination {get; set;} + public IRoutingOutputs Source {get; set;} + public eRoutingSignalType SignalType {get; set;} + + public void HandleCooldown(object sender, FeedbackEventArgs args) + { + var coolingDevice = sender as IWarmingCooling; + + if(args.BoolValue == false) + { + Destination.ReleaseAndMakeRoute(Source, SignalType); + + if(sender == null) return; + + coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; + } + } + } + + /*public class RouteRequest + { + public IRoutingSink Destination { get; set; } + public IRoutingOutputs Source { get; set; } + public eRoutingSignalType SignalType { get; set; } + + public void HandleCooldown(object sender, FeedbackEventArgs args) + { + var coolingDevice = sender as IWarmingCooling; + + if (args.BoolValue == false) + { + Destination.ReleaseAndMakeRoute(Source, SignalType); + + if (sender == null) return; + + coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; + } + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs b/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs new file mode 100644 index 00000000..7b9fcabe --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs @@ -0,0 +1,60 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Represents an individual link for a route + /// + public class RouteSwitchDescriptor + { + public IRoutingInputs SwitchingDevice { get { return InputPort.ParentDevice; } } + public RoutingOutputPort OutputPort { get; set; } + public RoutingInputPort InputPort { get; set; } + + public RouteSwitchDescriptor(RoutingInputPort inputPort) + { + InputPort = inputPort; + } + + public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort) + { + InputPort = inputPort; + OutputPort = outputPort; + } + + public override string ToString() + { + if(SwitchingDevice is IRouting) + return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector); + else + return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector); + } + } + + /*/// + /// Represents an individual link for a route + /// + public class RouteSwitchDescriptor + { + public IRoutingInputs SwitchingDevice { get { return InputPort.ParentDevice; } } + public RoutingOutputPort OutputPort { get; set; } + public RoutingInputPort InputPort { get; set; } + + public RouteSwitchDescriptor(RoutingInputPort inputPort) + { + InputPort = inputPort; + } + + public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort) + { + InputPort = inputPort; + OutputPort = outputPort; + } + + public override string ToString() + { + if (SwitchingDevice is IRouting) + return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector); + else + return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector); + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs b/src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs new file mode 100644 index 00000000..7da945cb --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs @@ -0,0 +1,77 @@ +using System; + + +namespace PepperDash.Essentials.Core +{ + /// + /// Basic RoutingInput with no statuses. + /// + public class RoutingInputPort : RoutingPort + { + /// + /// The IRoutingInputs object this lives on + /// + public IRoutingInputs ParentDevice { get; private set; } + + /// + /// Constructor for a basic RoutingInputPort + /// + /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. + /// May be string, number, whatever + /// The IRoutingInputs object this lives on + public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + object selector, IRoutingInputs parent) + : this (key, type, connType, selector, parent, false) + { + } + + /// + /// Constructor for a virtual routing input port that lives inside a device. For example + /// the ports that link a DM card to a DM matrix bus + /// + /// true for internal ports + public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + object selector, IRoutingInputs parent, bool isInternal) + : base(key, type, connType, selector, isInternal) + { + if (parent == null) + throw new ArgumentNullException(nameof(parent)); + ParentDevice = parent; + } + } + + /*/// + /// Basic RoutingInput with no statuses. + /// + public class RoutingInputPort : RoutingPort + { + /// + /// The IRoutingInputs object this lives on + /// + public IRoutingInputs ParentDevice { get; private set; } + + /// + /// Constructor for a basic RoutingInputPort + /// + /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. + /// May be string, number, whatever + /// The IRoutingInputs object this lives on + public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + TSelector selector, IRoutingInputs parent) + : this(key, type, connType, selector, parent, false) + { + } + + /// + /// Constructor for a virtual routing input port that lives inside a device. For example + /// the ports that link a DM card to a DM matrix bus + /// + /// true for internal ports + public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + TSelector selector, IRoutingInputs parent, bool isInternal) + : base(key, type, connType, selector, isInternal) + { + ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent)); + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingInputPortWithVideoStatuses.cs b/src/PepperDash.Essentials.Core/Routing/RoutingInputPortWithVideoStatuses.cs new file mode 100644 index 00000000..bd4dd489 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RoutingInputPortWithVideoStatuses.cs @@ -0,0 +1,30 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// A RoutingInputPort for devices like DM-TX and DM input cards. + /// Will provide video statistics on connected signals + /// + public class RoutingInputPortWithVideoStatuses : RoutingInputPort + { + /// + /// Video statuses attached to this port + /// + public VideoStatusOutputs VideoStatus { get; private set; } + + /// + /// Constructor + /// + /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. + /// May be string, number, whatever + /// The IRoutingInputs object this lives on + /// A VideoStatusFuncsWrapper used to assign the callback funcs that will get + /// the values for the various stats + public RoutingInputPortWithVideoStatuses(string key, + eRoutingSignalType type, eRoutingPortConnectionType connType, object selector, + IRoutingInputs parent, VideoStatusFuncsWrapper funcs) : + base(key, type, connType, selector, parent) + { + VideoStatus = new VideoStatusOutputs(funcs); + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingInterfaces.cs b/src/PepperDash.Essentials.Core/Routing/RoutingInterfaces.cs deleted file mode 100644 index bfaa27db..00000000 --- a/src/PepperDash.Essentials.Core/Routing/RoutingInterfaces.cs +++ /dev/null @@ -1,203 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharpPro; -using Crestron.SimplSharpPro.DM; - -using PepperDash.Core; - - -namespace PepperDash.Essentials.Core -{ - - /// - /// The handler type for a Room's SourceInfoChange - /// - public delegate void SourceInfoChangeHandler(/*EssentialsRoomBase room,*/ SourceListItem info, ChangeType type); - - - //******************************************************************************************* - // Interfaces - - /// - /// For rooms with a single presentation source, change event - /// - public interface IHasCurrentSourceInfoChange - { - string CurrentSourceInfoKey { get; set; } - SourceListItem CurrentSourceInfo { get; set; } - event SourceInfoChangeHandler CurrentSourceChange; - } - - /// - /// Defines a class that has a collection of RoutingInputPorts - /// - public interface IRoutingInputs : IKeyed - { - RoutingPortCollection InputPorts { get; } - } - - /// - /// Defines a class that has a collection of RoutingOutputPorts - /// - - public interface IRoutingOutputs : IKeyed - { - RoutingPortCollection OutputPorts { get; } - } - - /// - /// For fixed-source endpoint devices - /// - public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange - { - - } - - /// - /// Endpoint device like a display, that selects inputs - /// - public interface IRoutingSinkWithSwitching : IRoutingSink - { - //void ClearRoute(); - void ExecuteSwitch(object inputSelector); - } - - /// - /// For devices like RMCs, baluns, other devices with no switching. - /// - public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs - { - } - - /// - /// Defines a midpoint device as have internal routing. Any devices in the middle of the - /// signal chain, that do switching, must implement this for routing to work otherwise - /// the routing algorithm will treat the IRoutingInputsOutputs device as a passthrough - /// device. - /// - public interface IRouting : IRoutingInputsOutputs - { - void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType); - } - - public interface IRoutingWithClear : IRouting - { - /// - /// Clears a route to an output, however a device needs to do that - /// - /// Output to clear - /// signal type to clear - void ClearRoute(object outputSelector, eRoutingSignalType signalType); - } - - public interface IRoutingNumeric : IRouting - { - void ExecuteNumericSwitch(ushort input, ushort output, eRoutingSignalType type); - } - - public interface ITxRouting : IRoutingNumeric - { - IntFeedback VideoSourceNumericFeedback { get; } - IntFeedback AudioSourceNumericFeedback { get; } - } - - /// - /// Defines a receiver that has internal routing (DM-RMC-4K-Z-SCALER-C) - /// - public interface IRmcRouting : IRoutingNumeric - { - IntFeedback AudioVideoSourceNumericFeedback { get; } - } - - - /// - /// Defines an IRmcRouting with a feedback event - /// - public interface ITxRoutingWithFeedback : ITxRouting - { - } - - /// - /// Defines an IRmcRouting with a feedback event - /// - public interface IRmcRoutingWithFeedback : IRmcRouting - { - } - - /// - /// Defines an IRoutingOutputs devices as being a source - the start of the chain - /// - public interface IRoutingSource : IRoutingOutputs - { - } - - /// - /// Defines an event structure for reporting output route data - /// - public interface IRoutingFeedback : IKeyName - { - event EventHandler NumericSwitchChange; - //void OnSwitchChange(RoutingNumericEventArgs e); - } - - /// - /// Defines an IRoutingNumeric with a feedback event - /// - public interface IRoutingNumericWithFeedback : IRoutingNumeric, IRoutingFeedback - { - } - - /// - /// Defines an IRouting with a feedback event - /// - public interface IRoutingWithFeedback : IRouting, IRoutingFeedback - { - - } - - public class RoutingNumericEventArgs : EventArgs - { - - public uint? Output { get; set; } - public uint? Input { get; set; } - - public eRoutingSignalType SigType { get; set; } - public RoutingInputPort InputPort { get; set; } - public RoutingOutputPort OutputPort { get; set; } - - public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType) - { - } - - public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort, - eRoutingSignalType sigType) - : this(null, null, outputPort, inputPort, sigType) - { - } - - public RoutingNumericEventArgs() - : this(null, null, null, null, 0) - { - - } - - public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort, - RoutingInputPort inputPort, eRoutingSignalType sigType) - { - OutputPort = outputPort; - InputPort = inputPort; - - Output = output; - Input = input; - SigType = sigType; - } - } - - public interface IRoutingHasVideoInputSyncFeedbacks - { - FeedbackCollection VideoInputSyncFeedbacks { get; } - } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingNumericEventArgs.cs b/src/PepperDash.Essentials.Core/Routing/RoutingNumericEventArgs.cs new file mode 100644 index 00000000..7ede13c2 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RoutingNumericEventArgs.cs @@ -0,0 +1,43 @@ +using System; + + +namespace PepperDash.Essentials.Core +{ + public class RoutingNumericEventArgs : EventArgs + { + + public uint? Output { get; set; } + public uint? Input { get; set; } + + public eRoutingSignalType SigType { get; set; } + public RoutingInputPort InputPort { get; set; } + public RoutingOutputPort OutputPort { get; set; } + + public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType) + { + } + + public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort, + eRoutingSignalType sigType) + : this(null, null, outputPort, inputPort, sigType) + { + } + + public RoutingNumericEventArgs() + : this(null, null, null, null, 0) + { + + } + + public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort, + RoutingInputPort inputPort, eRoutingSignalType sigType) + { + OutputPort = outputPort; + InputPort = inputPort; + + Output = output; + Input = input; + SigType = sigType; + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs b/src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs new file mode 100644 index 00000000..445b3072 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs @@ -0,0 +1,75 @@ +using System; + + +namespace PepperDash.Essentials.Core +{ + public class RoutingOutputPort : RoutingPort + { + /// + /// The IRoutingOutputs object this port lives on + /// + public IRoutingOutputs ParentDevice { get; private set; } + + public InUseTracking InUseTracker { get; private set; } + + + /// + /// + /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. + /// May be string, number, whatever + /// The IRoutingOutputs object this port lives on + public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + object selector, IRoutingOutputs parent) + : this(key, type, connType, selector, parent, false) + { + } + + public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + object selector, IRoutingOutputs parent, bool isInternal) + : base(key, type, connType, selector, isInternal) + { + ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent)); + InUseTracker = new InUseTracking(); + } + + public override string ToString() + { + return ParentDevice.Key + ":" + Key; + } + } + + /*public class RoutingOutputPort : RoutingPort + { + /// + /// The IRoutingOutputs object this port lives on + /// + public IRoutingOutputs ParentDevice { get; private set; } + + public InUseTracking InUseTracker { get; private set; } + + + /// + /// + /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. + /// May be string, number, whatever + /// The IRoutingOutputs object this port lives on + public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + TSelector selector, IRoutingOutputs parent) + : this(key, type, connType, selector, parent, false) + { + } + + public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + TSelector selector, IRoutingOutputs parent, bool isInternal) + : base(key, type, connType, selector, isInternal) + { + ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent)); + InUseTracker = new InUseTracking(); + } + + public override string ToString() + { + return ParentDevice.Key + ":" + Key; + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingPort.cs b/src/PepperDash.Essentials.Core/Routing/RoutingPort.cs index b6ef0001..21ab0743 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingPort.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingPort.cs @@ -6,10 +6,10 @@ using PepperDash.Core; namespace PepperDash.Essentials.Core { - /// - /// Base class for RoutingInput and Output ports - /// - public abstract class RoutingPort : IKeyed + /// + /// Base class for RoutingInput and Output ports + /// + public abstract class RoutingPort : IKeyed { public string Key { get; private set; } public eRoutingSignalType Type { get; private set; } @@ -29,180 +29,23 @@ namespace PepperDash.Essentials.Core } } - [Flags] - public enum eRoutingSignalType - { - Audio = 1, - Video = 2, - AudioVideo = Audio | Video, - UsbOutput = 8, - UsbInput = 16, - SecondaryAudio = 32 - } + /*public abstract class RoutingPort:IKeyed + { + public string Key { get; private set; } + public eRoutingSignalType Type { get; private set; } + public eRoutingPortConnectionType ConnectionType { get; private set; } + public readonly TSelector Selector; + public bool IsInternal { get; private set; } + public object FeedbackMatchObject { get; set; } + public object Port { get; set; } - public enum eRoutingPortConnectionType - { - None, BackplaneOnly, DisplayPort, Dvi, Hdmi, Rgb, Vga, LineAudio, DigitalAudio, Sdi, - Composite, Component, DmCat, DmMmFiber, DmSmFiber, Speaker, Streaming, UsbC, HdBaseT - } - - /// - /// Basic RoutingInput with no statuses. - /// - public class RoutingInputPort : RoutingPort - { - /// - /// The IRoutingInputs object this lives on - /// - public IRoutingInputs ParentDevice { get; private set; } - - /// - /// Constructor for a basic RoutingInputPort - /// - /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. - /// May be string, number, whatever - /// The IRoutingInputs object this lives on - public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, - object selector, IRoutingInputs parent) - : this (key, type, connType, selector, parent, false) - { - } - - /// - /// Constructor for a virtual routing input port that lives inside a device. For example - /// the ports that link a DM card to a DM matrix bus - /// - /// true for internal ports - public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, - object selector, IRoutingInputs parent, bool isInternal) - : base(key, type, connType, selector, isInternal) - { - if (parent == null) - throw new ArgumentNullException("parent"); - ParentDevice = parent; - } - - - - - ///// - ///// Static method to get a named port from a named device - ///// - ///// Returns null if device or port doesn't exist - //public static RoutingInputPort GetDevicePort(string deviceKey, string portKey) - //{ - // var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as IRoutingInputs; - // if (sourceDev == null) - // return null; - // return sourceDev.InputPorts[portKey]; - //} - - ///// - ///// Static method to get a named port from a card in a named ICardPortsDevice device - ///// Uses ICardPortsDevice.GetChildInputPort - ///// - ///// 'input-N' - ///// null if device, card or port doesn't exist - //public static RoutingInputPort GetDeviceCardPort(string deviceKey, string cardKey, string portKey) - //{ - // var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as ICardPortsDevice; - // if (sourceDev == null) - // return null; - // return sourceDev.GetChildInputPort(cardKey, portKey); - //} - } - - /// - /// A RoutingInputPort for devices like DM-TX and DM input cards. - /// Will provide video statistics on connected signals - /// - public class RoutingInputPortWithVideoStatuses : RoutingInputPort - { - /// - /// Video statuses attached to this port - /// - public VideoStatusOutputs VideoStatus { get; private set; } - - /// - /// Constructor - /// - /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. - /// May be string, number, whatever - /// The IRoutingInputs object this lives on - /// A VideoStatusFuncsWrapper used to assign the callback funcs that will get - /// the values for the various stats - public RoutingInputPortWithVideoStatuses(string key, - eRoutingSignalType type, eRoutingPortConnectionType connType, object selector, - IRoutingInputs parent, VideoStatusFuncsWrapper funcs) : - base(key, type, connType, selector, parent) - { - VideoStatus = new VideoStatusOutputs(funcs); - } - } - - public class RoutingOutputPort : RoutingPort - { - /// - /// The IRoutingOutputs object this port lives on - /// - public IRoutingOutputs ParentDevice { get; private set; } - - public InUseTracking InUseTracker { get; private set; } - - - /// - /// - /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. - /// May be string, number, whatever - /// The IRoutingOutputs object this port lives on - public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, - object selector, IRoutingOutputs parent) - : this(key, type, connType, selector, parent, false) - { - } - - public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, - object selector, IRoutingOutputs parent, bool isInternal) - : base(key, type, connType, selector, isInternal) - { - if (parent == null) - throw new ArgumentNullException("parent"); - ParentDevice = parent; - InUseTracker = new InUseTracking(); - } - - public override string ToString() - { - return ParentDevice.Key + ":" + Key; - } - - ///// - ///// Static method to get a named port from a named device - ///// - ///// Returns null if device or port doesn't exist - //public static RoutingOutputPort GetDevicePort(string deviceKey, string portKey) - //{ - // var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as IRoutingOutputs; - // if (sourceDev == null) - // return null; - // var port = sourceDev.OutputPorts[portKey]; - // if (port == null) - // Debug.LogMessage(LogEventLevel.Information, "WARNING: Device '{0}' does does not contain output port '{1}'", deviceKey, portKey); - // return port; - //} - - ///// - ///// Static method to get a named port from a card in a named ICardPortsDevice device - ///// Uses ICardPortsDevice.GetChildOutputPort on that device - ///// - ///// 'input-N' or 'output-N' - ///// null if device, card or port doesn't exist - //public static RoutingOutputPort GetDeviceCardPort(string deviceKey, string cardKey, string portKey) - //{ - // var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as ICardPortsDevice; - // if (sourceDev == null) - // return null; - // var port = sourceDev.GetChildOutputPort(cardKey, portKey); - //} - } + public RoutingPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, TSelector selector, bool isInternal) + { + Key = key; + Type = type; + ConnectionType = connType; + Selector = selector; + IsInternal = isInternal; + }*/ + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingPortCollection.cs b/src/PepperDash.Essentials.Core/Routing/RoutingPortCollection.cs index ba972ab7..0b9d765c 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingPortCollection.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingPortCollection.cs @@ -23,4 +23,21 @@ namespace PepperDash.Essentials.Core } } } + +/* /// + /// Basically a List , with an indexer to find ports by key name + /// + public class RoutingPortCollection : List where T : RoutingPort + { + /// + /// Case-insensitive port lookup linked to ports' keys + /// + public T this[string key] + { + get + { + return this.FirstOrDefault(i => i.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); + } + } + }*/ } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/eRoutingPortConnectionType.cs b/src/PepperDash.Essentials.Core/Routing/eRoutingPortConnectionType.cs new file mode 100644 index 00000000..436ef9e6 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/eRoutingPortConnectionType.cs @@ -0,0 +1,8 @@ +namespace PepperDash.Essentials.Core +{ + public enum eRoutingPortConnectionType + { + None, BackplaneOnly, DisplayPort, Dvi, Hdmi, Rgb, Vga, LineAudio, DigitalAudio, Sdi, + Composite, Component, DmCat, DmMmFiber, DmSmFiber, Speaker, Streaming, UsbC, HdBaseT + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/eRoutingSignalType.cs b/src/PepperDash.Essentials.Core/Routing/eRoutingSignalType.cs new file mode 100644 index 00000000..b5f3d9f7 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/eRoutingSignalType.cs @@ -0,0 +1,16 @@ +using System; + + +namespace PepperDash.Essentials.Core +{ + [Flags] + public enum eRoutingSignalType + { + Audio = 1, + Video = 2, + AudioVideo = Audio | Video, + UsbOutput = 8, + UsbInput = 16, + SecondaryAudio = 32 + } +} \ No newline at end of file