From fb4ba8ccfad247ac344d0faab7c1eacc39b040cc Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Wed, 1 Jul 2026 17:14:32 -0600 Subject: [PATCH] feat: enhance routing functionality with additional parameters in RouteDescriptor and update RouteDescriptorCollection for change notifications --- .../Routing/Extensions.cs | 15 +- .../Routing/RouteDescriptor.cs | 247 ++++++++++-------- .../Routing/RouteDescriptorCollection.cs | 3 + .../Routing/RouteSwitchDescriptor.cs | 1 + .../Routing/RoutingFeedbackManager.cs | 105 ++------ 5 files changed, 171 insertions(+), 200 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs index 1e31127d..f12f7f53 100644 --- a/src/PepperDash.Essentials.Core/Routing/Extensions.cs +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -222,7 +222,7 @@ public static class Extensions // if it's a single signal type, find the route if (!signalType.HasFlag(eRoutingSignalType.AudioVideo)) { - var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, signalType); + var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, signalType); Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key, signalType); if (!destination.GetRouteToSource(source, null, null, signalType, 0, singleTypeRouteDescriptor, destinationPort, sourcePort)) @@ -240,14 +240,14 @@ public static class Extensions Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key); - var audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Audio); + var audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.Audio); var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, audioRouteDescriptor, destinationPort, sourcePort); if (!audioSuccess) Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key); - var videoRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Video); + var videoRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.Video); var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, videoRouteDescriptor, destinationPort, sourcePort); @@ -366,7 +366,8 @@ public static class Extensions audioOrSingleRoute = audioCollection.Descriptors.FirstOrDefault(d => d.Source.Key == request.Source.Key && d.Destination.Key == request.Destination.Key && - (request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key)); + (request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) && + (request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key)); } if (RouteDescriptors.TryGetValue(eRoutingSignalType.Video, out RouteDescriptorCollection videoCollection)) @@ -374,7 +375,8 @@ public static class Extensions videoRoute = videoCollection.Descriptors.FirstOrDefault(d => d.Source.Key == request.Source.Key && d.Destination.Key == request.Destination.Key && - (request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key)); + (request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) && + (request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key)); } } else @@ -385,7 +387,8 @@ public static class Extensions audioOrSingleRoute = collection.Descriptors.FirstOrDefault(d => d.Source.Key == request.Source.Key && d.Destination.Key == request.Destination.Key && - (request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key)); + (request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) && + (request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key)); } } diff --git a/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs b/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs index 631aff62..32822d0d 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs @@ -7,137 +7,160 @@ using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -/// -/// Represents a collection of individual route steps between a Source and a Destination device for a specific signal type. -/// -public class RouteDescriptor +namespace PepperDash.Essentials.Core { /// - /// The destination device (sink or midpoint) for the route. + /// Represents a collection of individual route steps between a Source and a Destination device for a specific signal type. /// - public IRoutingInputs Destination { get; private set; } - - /// - /// The specific input port on the destination device used for this route. Can be null if not specified or applicable. - /// - public RoutingInputPort InputPort { get; private set; } - - /// - /// The source device for the route. - /// - public IRoutingOutputs Source { get; private set; } - - /// - /// The type of signal being routed (e.g., Audio, Video). This descriptor represents a single signal type. - /// - public eRoutingSignalType SignalType { get; private set; } - - /// - /// A list of individual switching steps required to establish the route. - /// - public List Routes { get; private set; } - - /// - /// Initializes a new instance of the class for a route without a specific destination input port. - /// - /// The source device. - /// The destination device. - /// The type of signal being routed. - public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) : this(source, destination, null, signalType) + public class RouteDescriptor { - } + /// + /// The destination device (sink or midpoint) for the route. + /// + public IRoutingInputs Destination { get; private set; } - /// - /// Initializes a new instance of the class for a route with a specific destination input port. - /// - /// The source device. - /// The destination device. - /// The destination input port (optional). - /// The signal type for this route. - public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType) - { - Destination = destination; - InputPort = inputPort; - Source = source; - SignalType = signalType; - Routes = new List(); - } + /// + /// The InputPort on the destination device for this route, if applicable. May be null if the route is not for a specific input port. + /// + public RoutingInputPort InputPort { get; private set; } - /// - /// Executes all the switching steps defined in the list. - /// - public void ExecuteRoutes() - { - foreach (var route in Routes) + /// + /// Gets the source device (sink or midpoint) for the route. + /// + public IRoutingOutputs Source { get; private set; } + + /// + /// Gets the OutputPort on the source device for this route, if applicable. May be null if the route is not for a specific output port. + /// + public RoutingOutputPort OutputPort { get; private set; } + + /// + /// Gets the signal type for this route. + /// + public eRoutingSignalType SignalType { get; private set; } + + /// + /// Gets the collection of route switch descriptors for this route. + /// + public List Routes { get; private set; } + + /// + /// Initializes a new instance of the class for a route without a specific destination input port. + /// + /// The source device. + /// The destination device. + /// The type of signal being routed. + public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) : this(source, destination, null, signalType) { - Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", null, route.ToString()); - - if (route.SwitchingDevice is IRoutingSinkWithFeedback sink) - { - sink.ExecuteSwitch(route.InputPort.Selector); - continue; - } - - if (route.SwitchingDevice is IRoutingMidpointWithFeedback 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 the usage tracking for the route and optionally clears the route on the switching devices. - /// - /// If true, attempts to clear the route on the switching devices (e.g., set input to null/0). - public void ReleaseRoutes(bool clearRoute = false) - { - foreach (var route in Routes.Where(r => r.SwitchingDevice is IRoutingMidpointWithFeedback)) + /// + /// Initializes a new instance of the class for a route with a specific destination input port. + /// + /// The source device. + /// The destination device. + /// The destination input port (optional). + /// The signal type for this route. + public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType) : this(source, destination, inputPort, null, signalType) { - if (route.SwitchingDevice is IRoutingMidpointWithFeedback switchingDevice) - { - if (clearRoute) - { - try - { - switchingDevice.ExecuteSwitch(null, route.OutputPort.Selector, SignalType); - } - catch (Exception e) - { - Debug.LogError("Error executing switch: {exception}", e.Message); - } - } + } - if (route.OutputPort == null) + /// + /// Initializes a new instance of the class for a route with specific destination input and source output ports. + /// + /// + /// + /// + /// + /// + public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, RoutingOutputPort outputPort, eRoutingSignalType signalType) + { + Destination = destination; + InputPort = inputPort; + Source = source; + OutputPort = outputPort; + SignalType = signalType; + Routes = new List(); + } + + /// + /// ExecuteRoutes method + /// + public void ExecuteRoutes() + { + foreach (var route in Routes) + { + Debug.LogVerbose("ExecuteRoutes: {0}", route.ToString()); + + if (route.SwitchingDevice is IRoutingSinkWithFeedback sink) { + sink.ExecuteSwitch(route.InputPort.Selector); continue; } - if (route.OutputPort.InUseTracker != null) + if (route.SwitchingDevice is IRoutingMidpointWithFeedback switchingDevice) { - 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); - } - else - { - Debug.LogMessage(LogEventLevel.Error, "InUseTracker is null for OutputPort {0}", null, route.OutputPort.Key); + switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType); + + route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType); + + Debug.LogVerbose("Output port {0} routing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); } } } + + /// + /// Releases the usage tracking for the route and optionally clears the route on the switching devices. + /// + /// If true, attempts to clear the route on the switching devices (e.g., set input to null/0). + + + public void ReleaseRoutes(bool clearRoute = false) + { + foreach (var route in Routes.Where(r => r.SwitchingDevice is IRoutingMidpointWithFeedback)) + { + if (route.SwitchingDevice is IRoutingMidpointWithFeedback switchingDevice) + { + if (clearRoute) + { + try + { + switchingDevice.ExecuteSwitch(null, route.OutputPort.Selector, SignalType); + } + catch (Exception e) + { + Debug.LogError("Error executing switch: {exception}", e.Message); + Debug.LogDebug(e, "Stack Trace: "); + } + } + + if (route.OutputPort == null) + { + continue; + } + + if (route.OutputPort.InUseTracker != null) + { + route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType); + Debug.LogVerbose("Port {0} releasing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); + } + else + { + Debug.LogVerbose("InUseTracker is null for OutputPort {0}", route.OutputPort.Key); + } + } + } + } + + /// + /// Returns a string representation of the route descriptor, including source, destination, and individual route steps. + /// + /// A string describing the route. + public override string ToString() + { + var routesText = Routes.Select(r => r.ToString()).ToArray(); + return $"Route table from {Source.Key} to {Destination.Key} for {SignalType}:\r\n {string.Join("\r\n ", routesText)}"; + } } - /// - /// Returns a string representation of the route descriptor, including source, destination, and individual route steps. - /// - /// A string describing the route. - public override string ToString() - { - var routesText = Routes.Select(r => r.ToString()).ToArray(); - return $"Route table from {Source.Key} to {Destination.Key} for {SignalType}:\r\n {string.Join("\r\n ", 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 index f75196a2..d17ca8fe 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs @@ -28,6 +28,9 @@ public class RouteDescriptorCollection private readonly List RouteDescriptors = new List(); + /// + /// Event raised when the collection of RouteDescriptors changes (add/remove). This is useful for updating routing status in the UI, for example. + /// public event EventHandler RouteDescriptorCollectionChanged; /// diff --git a/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs b/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs index 38d56b93..2e526589 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs @@ -2,6 +2,7 @@ { /// /// Represents a RouteSwitchDescriptor + /// This represents a switch to be made on an IRoutingInputs device, which could be a matrix switcher or a sink device. /// public class RouteSwitchDescriptor { diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs b/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs index 15855cf4..0eb741bb 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs @@ -225,6 +225,7 @@ namespace PepperDash.Essentials.Core.Routing try { UpdateDestination(sender, currentInputPort); + } catch (Exception ex) { @@ -340,11 +341,6 @@ namespace PepperDash.Essentials.Core.Routing inputPort ); - var tempSourceListItem = new SourceListItem - { - SourceKey = "$transient", - Name = inputPort.Key, - }; return; } @@ -371,12 +367,16 @@ namespace PepperDash.Essentials.Core.Routing inputPort ); - var tempSourceListItem = new SourceListItem - { - SourceKey = "$transient", - Name = "None", - }; + // determine all the tie lines between the source and destination to determine the signal type + // the type is the union of all the tie lines between the source and destination + + // For now we assume the type matches the tie line connected to the destination + destination.SetCurrentSource(firstTieLine.Type, null); + + // remove existing descriptor if any + RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPort.Key); + return; } } @@ -386,83 +386,24 @@ namespace PepperDash.Essentials.Core.Routing return; } - // Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root TieLine {tieLine}", this, sourceTieLine); - // Does not handle combinable scenarios or other scenarios where a display might be part of multiple rooms yet. - var room = DeviceManager - .AllDevices.OfType() - .FirstOrDefault( - (r) => - { + // Get the routes from the destination to the source using the existing GetRouteToSource method + var routes = destination.GetRouteToSource( + sourceTieLine.SourcePort.ParentDevice as IRoutingOutputs, + sourceTieLine.Type, + inputPort, + sourceTieLine.SourcePort + ); - if (r is IHasDefaultDisplay roomDefaultDisplay) - { - return roomDefaultDisplay.DefaultDisplay.Key == destination.Key; - } + // remove existing descriptor if any + RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPort.Key); - return false; - } - ); + // Add the new route descriptors to the collection + RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(routes.Item1); - if (room == null) + if(routes.Item2 != null) { - Debug.LogMessage( - Serilog.Events.LogEventLevel.Debug, - "No room found for display {destination}", - this, - destination.Key - ); - return; - } - - // Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found room {room} for destination {destination}", this, room.Key, destination.Key); - - var sourceList = ConfigReader.ConfigObject.GetSourceListForKey(room.SourceListKey); - - if (sourceList == null) - { - Debug.LogMessage( - Serilog.Events.LogEventLevel.Debug, - "No source list found for source list key {key}. Unable to find source for tieLine {sourceTieLine}", - this, - room.SourceListKey, - sourceTieLine - ); - return; - } - - // Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found sourceList for room {room}", this, room.Key); - - var sourceListItem = sourceList.FirstOrDefault(sli => - { - //// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, - // "SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}", - // this, - // sli.Key, - // sli.Value.SourceKey, - // sourceTieLine.SourcePort.ParentDevice.Key); - - return sli.Value.SourceKey.Equals( - sourceTieLine.SourcePort.ParentDevice.Key, - StringComparison.InvariantCultureIgnoreCase - ); - }); - - var source = sourceListItem.Value; - var sourceKey = sourceListItem.Key; - - if (source == null) - { - Debug.LogMessage( - Serilog.Events.LogEventLevel.Debug, - "No source found for device {key}. Creating transient source for {destination}", - this, - sourceTieLine.SourcePort.ParentDevice.Key, - destination - ); - - - return; + RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(routes.Item2); } }