mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-02-09 01:35:02 +00:00
feat: map routes/tielines at startup and new console commands
* visualizeroutes allows visualizing configured routes based on tielines and signal type * can be filtered by source key, destination key, and type, along with partial matches for source & destination keys * visualizecurrentroutes visualizes what Essentials says is currently routed by type * uses same filtering as visualizeroutes * improvements to how the routing algorithm works
This commit is contained in:
@@ -18,6 +18,20 @@ namespace PepperDash.Essentials.Core
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A collection of RouteDescriptors for each signal type.
|
||||
/// </summary>
|
||||
public static readonly Dictionary<eRoutingSignalType, RouteDescriptorCollection> RouteDescriptors = new Dictionary<eRoutingSignalType, RouteDescriptorCollection>()
|
||||
{
|
||||
{ eRoutingSignalType.Audio, new RouteDescriptorCollection() },
|
||||
{ eRoutingSignalType.Video, new RouteDescriptorCollection() },
|
||||
{ eRoutingSignalType.SecondaryAudio, new RouteDescriptorCollection() },
|
||||
{ eRoutingSignalType.AudioVideo, new RouteDescriptorCollection() },
|
||||
{ eRoutingSignalType.UsbInput, new RouteDescriptorCollection() },
|
||||
{ eRoutingSignalType.UsbOutput, new RouteDescriptorCollection() }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Stores pending route requests, keyed by the destination device key.
|
||||
/// Used primarily to handle routing requests while a device is cooling down.
|
||||
@@ -29,6 +43,105 @@ namespace PepperDash.Essentials.Core
|
||||
/// </summary>
|
||||
private static readonly GenericQueue routeRequestQueue = new GenericQueue("routingQueue");
|
||||
|
||||
/// <summary>
|
||||
/// Indexed lookup of TieLines by destination device key for faster queries.
|
||||
/// </summary>
|
||||
private static Dictionary<string, List<TieLine>> _tieLinesByDestination;
|
||||
|
||||
/// <summary>
|
||||
/// Indexed lookup of TieLines by source device key for faster queries.
|
||||
/// </summary>
|
||||
private static Dictionary<string, List<TieLine>> _tieLinesBySource;
|
||||
|
||||
/// <summary>
|
||||
/// Cache of failed route attempts to avoid re-checking impossible paths.
|
||||
/// Format: "sourceKey|destKey|signalType"
|
||||
/// </summary>
|
||||
private static readonly HashSet<string> _impossibleRoutes = new HashSet<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Indexes all TieLines by source and destination device keys for faster lookups.
|
||||
/// Should be called once at system startup after all TieLines are created.
|
||||
/// </summary>
|
||||
public static void IndexTieLines()
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Indexing TieLines for faster route discovery");
|
||||
|
||||
_tieLinesByDestination = TieLineCollection.Default
|
||||
.GroupBy(t => t.DestinationPort.ParentDevice.Key)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
_tieLinesBySource = TieLineCollection.Default
|
||||
.GroupBy(t => t.SourcePort.ParentDevice.Key)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "TieLine indexing complete. {0} destination keys, {1} source keys",
|
||||
null, _tieLinesByDestination.Count, _tieLinesBySource.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError("Exception indexing TieLines: {exception}", ex.Message);
|
||||
Debug.LogDebug(ex, "Stack Trace: ");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets TieLines connected to a destination device.
|
||||
/// Uses indexed lookup if available, otherwise falls back to LINQ query.
|
||||
/// </summary>
|
||||
/// <param name="destinationKey">The destination device key</param>
|
||||
/// <returns>List of TieLines connected to the destination</returns>
|
||||
private static IEnumerable<TieLine> GetTieLinesForDestination(string destinationKey)
|
||||
{
|
||||
if (_tieLinesByDestination != null && _tieLinesByDestination.TryGetValue(destinationKey, out List<TieLine> tieLines))
|
||||
{
|
||||
return tieLines;
|
||||
}
|
||||
|
||||
// Fallback to LINQ if index not available
|
||||
return TieLineCollection.Default.Where(t => t.DestinationPort.ParentDevice.Key == destinationKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets TieLines connected to a source device.
|
||||
/// Uses indexed lookup if available, otherwise falls back to LINQ query.
|
||||
/// </summary>
|
||||
/// <param name="sourceKey">The source device key</param>
|
||||
/// <returns>List of TieLines connected to the source</returns>
|
||||
private static IEnumerable<TieLine> GetTieLinesForSource(string sourceKey)
|
||||
{
|
||||
if (_tieLinesBySource != null && _tieLinesBySource.TryGetValue(sourceKey, out List<TieLine> tieLines))
|
||||
{
|
||||
return tieLines;
|
||||
}
|
||||
|
||||
// Fallback to LINQ if index not available
|
||||
return TieLineCollection.Default.Where(t => t.SourcePort.ParentDevice.Key == sourceKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a cache key for route impossibility tracking.
|
||||
/// </summary>
|
||||
/// <param name="sourceKey">Source device key</param>
|
||||
/// <param name="destKey">Destination device key</param>
|
||||
/// <param name="type">Signal type</param>
|
||||
/// <returns>Cache key string</returns>
|
||||
private static string GetRouteKey(string sourceKey, string destKey, eRoutingSignalType type)
|
||||
{
|
||||
return string.Format("{0}|{1}|{2}", sourceKey, destKey, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the impossible routes cache. Should be called if TieLines are added/removed at runtime.
|
||||
/// </summary>
|
||||
public static void ClearImpossibleRoutesCache()
|
||||
{
|
||||
_impossibleRoutes.Clear();
|
||||
Debug.LogMessage(LogEventLevel.Information, "Impossible routes cache cleared");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
|
||||
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor
|
||||
@@ -173,8 +286,9 @@ namespace PepperDash.Essentials.Core
|
||||
if (!audioSuccess && !videoSuccess)
|
||||
return (null, null);
|
||||
|
||||
|
||||
return (audioRouteDescriptor, videoRouteDescriptor);
|
||||
// Return null for descriptors that have no routes
|
||||
return (audioSuccess && audioRouteDescriptor.Routes.Count > 0 ? audioRouteDescriptor : null,
|
||||
videoSuccess && videoRouteDescriptor.Routes.Count > 0 ? videoRouteDescriptor : null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -245,6 +359,90 @@ namespace PepperDash.Essentials.Core
|
||||
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps destination input ports to source output ports for all routing devices.
|
||||
/// </summary>
|
||||
public static void MapDestinationsToSources()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Index TieLines before mapping if not already done
|
||||
if (_tieLinesByDestination == null || _tieLinesBySource == null)
|
||||
{
|
||||
IndexTieLines();
|
||||
}
|
||||
|
||||
var sinks = DeviceManager.AllDevices.OfType<IRoutingInputs>().Where(d => !(d is IRoutingInputsOutputs));
|
||||
var sources = DeviceManager.AllDevices.OfType<IRoutingOutputs>().Where(d => !(d is IRoutingInputsOutputs));
|
||||
|
||||
foreach (var sink in sinks)
|
||||
{
|
||||
foreach (var source in sources)
|
||||
{
|
||||
foreach (var inputPort in sink.InputPorts)
|
||||
{
|
||||
foreach (var outputPort in source.OutputPorts)
|
||||
{
|
||||
var (audioOrSingleRoute, videoRoute) = sink.GetRouteToSource(source, inputPort.Type, inputPort, outputPort);
|
||||
|
||||
if (audioOrSingleRoute == null && videoRoute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (audioOrSingleRoute != null)
|
||||
{
|
||||
// Only add routes that have actual switching steps
|
||||
if (audioOrSingleRoute.Routes == null || audioOrSingleRoute.Routes.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add to the appropriate collection(s) based on signal type
|
||||
// Note: A single route descriptor with combined flags (e.g., AudioVideo) will be added once per matching signal type
|
||||
if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.Audio))
|
||||
{
|
||||
RouteDescriptors[eRoutingSignalType.Audio].AddRouteDescriptor(audioOrSingleRoute);
|
||||
}
|
||||
if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.Video))
|
||||
{
|
||||
RouteDescriptors[eRoutingSignalType.Video].AddRouteDescriptor(audioOrSingleRoute);
|
||||
}
|
||||
if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.SecondaryAudio))
|
||||
{
|
||||
RouteDescriptors[eRoutingSignalType.SecondaryAudio].AddRouteDescriptor(audioOrSingleRoute);
|
||||
}
|
||||
if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.UsbInput))
|
||||
{
|
||||
RouteDescriptors[eRoutingSignalType.UsbInput].AddRouteDescriptor(audioOrSingleRoute);
|
||||
}
|
||||
if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.UsbOutput))
|
||||
{
|
||||
RouteDescriptors[eRoutingSignalType.UsbOutput].AddRouteDescriptor(audioOrSingleRoute);
|
||||
}
|
||||
}
|
||||
if (videoRoute != null)
|
||||
{
|
||||
// Only add routes that have actual switching steps
|
||||
if (videoRoute.Routes == null || videoRoute.Routes.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RouteDescriptors[eRoutingSignalType.Video].AddRouteDescriptor(videoRoute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError("Exception mapping routes: {exception}", ex.Message);
|
||||
Debug.LogDebug(ex, "Stack Trace: ");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the actual routing based on a <see cref="RouteRequest"/>.
|
||||
/// Finds the route path, adds it to the collection, and executes the switches.
|
||||
@@ -257,7 +455,51 @@ namespace PepperDash.Essentials.Core
|
||||
if (request.Source == null)
|
||||
return;
|
||||
|
||||
var (audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort);
|
||||
RouteDescriptor audioOrSingleRoute = null;
|
||||
RouteDescriptor videoRoute = null;
|
||||
|
||||
// Try to use pre-loaded route descriptors first
|
||||
if (request.SignalType.HasFlag(eRoutingSignalType.AudioVideo))
|
||||
{
|
||||
// For AudioVideo routes, check both Audio and Video collections
|
||||
if (RouteDescriptors.TryGetValue(eRoutingSignalType.Audio, out RouteDescriptorCollection audioCollection))
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
if (RouteDescriptors.TryGetValue(eRoutingSignalType.Video, out RouteDescriptorCollection videoCollection))
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For single signal type routes
|
||||
var signalTypeToCheck = request.SignalType.HasFlag(eRoutingSignalType.SecondaryAudio)
|
||||
? eRoutingSignalType.SecondaryAudio
|
||||
: request.SignalType;
|
||||
|
||||
if (RouteDescriptors.TryGetValue(signalTypeToCheck, out RouteDescriptorCollection collection))
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// If no pre-loaded route found, build it dynamically
|
||||
if (audioOrSingleRoute == null && videoRoute == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "No pre-loaded route found, building dynamically", request.Destination);
|
||||
(audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort);
|
||||
}
|
||||
|
||||
if (audioOrSingleRoute == null && videoRoute == null)
|
||||
return;
|
||||
@@ -321,11 +563,13 @@ namespace PepperDash.Essentials.Core
|
||||
/// </summary>
|
||||
/// <param name="destination"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="destinationPort">The RoutingOutputPort whose link is being checked for a route</param>
|
||||
/// <param name="outputPortToUse">The RoutingOutputPort whose link is being checked for a route</param>
|
||||
/// <param name="alreadyCheckedDevices">Prevents Devices from being twice-checked</param>
|
||||
/// <param name="signalType">This recursive function should not be called with AudioVideo</param>
|
||||
/// <param name="cycle">Just an informational counter</param>
|
||||
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
|
||||
/// <param name="destinationPort">The RoutingOutputPort whose link is being checked for a route</param>
|
||||
/// <param name="sourcePort">The source output port (optional)</param>
|
||||
/// <returns>true if source is hit</returns>
|
||||
private static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
|
||||
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices,
|
||||
@@ -333,42 +577,54 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
cycle++;
|
||||
|
||||
// Check if this route has already been determined to be impossible
|
||||
var routeKey = GetRouteKey(source.Key, destination.Key, signalType);
|
||||
if (_impossibleRoutes.Contains(routeKey))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Route {0} is cached as impossible, skipping", null, routeKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {cycle} {sourceKey}:{sourcePortKey}--> {destinationKey}:{destinationPortKey} {type}", null, cycle, source.Key, sourcePort?.Key ?? "auto", destination.Key, destinationPort?.Key ?? "auto", signalType.ToString());
|
||||
|
||||
RoutingInputPort goodInputPort = null;
|
||||
|
||||
// Use indexed lookup instead of LINQ query
|
||||
var allDestinationTieLines = GetTieLinesForDestination(destination.Key);
|
||||
|
||||
IEnumerable<TieLine> destinationTieLines;
|
||||
TieLine directTie = null;
|
||||
|
||||
if (destinationPort == null)
|
||||
{
|
||||
destinationTieLines = TieLineCollection.Default.Where(t =>
|
||||
t.DestinationPort.ParentDevice.Key == destination.Key && (t.Type.HasFlag(signalType) || signalType == eRoutingSignalType.AudioVideo));
|
||||
destinationTieLines = allDestinationTieLines.Where(t =>
|
||||
t.Type.HasFlag(signalType) || signalType == eRoutingSignalType.AudioVideo);
|
||||
}
|
||||
else
|
||||
{
|
||||
destinationTieLines = TieLineCollection.Default.Where(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.DestinationPort.Key == destinationPort.Key && (t.Type.HasFlag(signalType)));
|
||||
destinationTieLines = allDestinationTieLines.Where(t =>
|
||||
t.DestinationPort.Key == destinationPort.Key && t.Type.HasFlag(signalType));
|
||||
}
|
||||
|
||||
// find the TieLine without a port
|
||||
if (destinationPort == null && sourcePort == null)
|
||||
{
|
||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.SourcePort.ParentDevice.Key == source.Key);
|
||||
directTie = destinationTieLines.FirstOrDefault(t => t.SourcePort.ParentDevice.Key == source.Key);
|
||||
}
|
||||
// find a tieLine to a specific destination port without a specific source port
|
||||
else if (destinationPort != null && sourcePort == null)
|
||||
{
|
||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key);
|
||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key);
|
||||
}
|
||||
// find a tieline to a specific source port without a specific destination port
|
||||
else if (destinationPort == null & sourcePort != null)
|
||||
{
|
||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key);
|
||||
directTie = destinationTieLines.FirstOrDefault(t => t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key);
|
||||
}
|
||||
// find a tieline to a specific source port and destination port
|
||||
else if (destinationPort != null && sourcePort != null)
|
||||
{
|
||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key);
|
||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key);
|
||||
}
|
||||
|
||||
if (directTie != null) // Found a tie directly to the source
|
||||
@@ -423,6 +679,10 @@ namespace PepperDash.Essentials.Core
|
||||
if (goodInputPort == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key);
|
||||
|
||||
// Cache this as an impossible route
|
||||
_impossibleRoutes.Add(routeKey);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,15 +95,15 @@ namespace PepperDash.Essentials.Core
|
||||
/// Releases the usage tracking for the route and optionally clears the route on the switching devices.
|
||||
/// </summary>
|
||||
/// <param name="clearRoute">If true, attempts to clear the route on the switching devices (e.g., set input to null/0).</param>
|
||||
|
||||
|
||||
|
||||
|
||||
public void ReleaseRoutes(bool clearRoute = false)
|
||||
{
|
||||
foreach (var route in Routes.Where(r => r.SwitchingDevice is IRouting))
|
||||
{
|
||||
if (route.SwitchingDevice is IRouting switchingDevice)
|
||||
{
|
||||
if(clearRoute)
|
||||
if (clearRoute)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -137,98 +137,11 @@ namespace PepperDash.Essentials.Core
|
||||
/// Returns a string representation of the route descriptor, including source, destination, and individual route steps.
|
||||
/// </summary>
|
||||
/// <returns>A string describing the route.</returns>
|
||||
|
||||
|
||||
|
||||
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));
|
||||
return $"Route table from {Source.Key} to {Destination.Key} for {SignalType}:\r\n {string.Join("\r\n ", routesText)}";
|
||||
}
|
||||
}
|
||||
|
||||
/*/// <summary>
|
||||
/// Represents an collection of individual route steps between Source and Destination
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Represents a RouteDescriptor
|
||||
/// </summary>
|
||||
public class RouteDescriptor<TInputSelector, TOutputSelector>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Destination
|
||||
/// </summary>
|
||||
public IRoutingInputs<TInputSelector> Destination { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the Source
|
||||
/// </summary>
|
||||
public IRoutingOutputs<TOutputSelector> Source { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the SignalType
|
||||
/// </summary>
|
||||
public eRoutingSignalType SignalType { get; private set; }
|
||||
public List<RouteSwitchDescriptor<TInputSelector, TOutputSelector>> Routes { get; private set; }
|
||||
|
||||
|
||||
public RouteDescriptor(IRoutingOutputs<TOutputSelector> source, IRoutingInputs<TInputSelector> destination, eRoutingSignalType signalType)
|
||||
{
|
||||
Destination = destination;
|
||||
Source = source;
|
||||
SignalType = signalType;
|
||||
Routes = new List<RouteSwitchDescriptor<TInputSelector, TOutputSelector>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ExecuteRoutes method
|
||||
/// </summary>
|
||||
public void ExecuteRoutes()
|
||||
{
|
||||
foreach (var route in Routes)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", null, route.ToString());
|
||||
|
||||
if (route.SwitchingDevice is IRoutingSinkWithSwitching<TInputSelector> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ReleaseRoutes method
|
||||
/// </summary>
|
||||
public void ReleaseRoutes()
|
||||
{
|
||||
foreach (var route in Routes)
|
||||
{
|
||||
if (route.SwitchingDevice is IRouting<TInputSelector, TOutputSelector>)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ToString method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
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));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
@@ -11,6 +11,9 @@ namespace PepperDash.Essentials.Core
|
||||
/// </summary>
|
||||
public class RouteDescriptorCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default collection of RouteDescriptors.
|
||||
/// </summary>
|
||||
public static RouteDescriptorCollection DefaultCollection
|
||||
{
|
||||
get
|
||||
@@ -24,6 +27,11 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
private readonly List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerable collection of all RouteDescriptors in this collection.
|
||||
/// </summary>
|
||||
public IEnumerable<RouteDescriptor> Descriptors => RouteDescriptors.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the
|
||||
/// destination exists already, it will not be added - in order to preserve
|
||||
@@ -37,13 +45,29 @@ namespace PepperDash.Essentials.Core
|
||||
return;
|
||||
}
|
||||
|
||||
if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)
|
||||
&& RouteDescriptors.Any(t => t.Destination == descriptor.Destination && t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key))
|
||||
// Check if a route already exists with the same source, destination, input port, AND signal type
|
||||
var existingRoute = RouteDescriptors.FirstOrDefault(t =>
|
||||
t.Source == descriptor.Source &&
|
||||
t.Destination == descriptor.Destination &&
|
||||
t.SignalType == descriptor.SignalType &&
|
||||
((t.InputPort == null && descriptor.InputPort == null) ||
|
||||
(t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key)));
|
||||
|
||||
if (existingRoute != null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination,
|
||||
"Route to [{0}] already exists in global routes table", descriptor?.Source?.Key);
|
||||
Debug.LogMessage(LogEventLevel.Information, descriptor.Destination,
|
||||
"Route from {0} to {1}:{2} ({3}) already exists in this collection",
|
||||
descriptor?.Source?.Key,
|
||||
descriptor?.Destination?.Key,
|
||||
descriptor?.InputPort?.Key ?? "auto",
|
||||
descriptor?.SignalType);
|
||||
return;
|
||||
}
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Adding route descriptor: {0} -> {1}:{2} ({3})",
|
||||
descriptor?.Source?.Key,
|
||||
descriptor?.Destination?.Key,
|
||||
descriptor?.InputPort?.Key ?? "auto",
|
||||
descriptor?.SignalType);
|
||||
RouteDescriptors.Add(descriptor);
|
||||
}
|
||||
|
||||
@@ -57,6 +81,12 @@ namespace PepperDash.Essentials.Core
|
||||
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the route descriptor for a specific destination and input port
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination device</param>
|
||||
/// <param name="inputPortKey">The input port key</param>
|
||||
/// <returns>The matching RouteDescriptor or null if not found</returns>
|
||||
public RouteDescriptor GetRouteDescriptorForDestinationAndInputPort(IRoutingInputs destination, string inputPortKey)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Getting route descriptor for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||
@@ -73,7 +103,7 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Removing route descriptor for '{destination}':'{inputPortKey}'", destination.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||
|
||||
var descr = string.IsNullOrEmpty(inputPortKey)
|
||||
var descr = string.IsNullOrEmpty(inputPortKey)
|
||||
? GetRouteDescriptorForDestination(destination)
|
||||
: GetRouteDescriptorForDestinationAndInputPort(destination, inputPortKey);
|
||||
if (descr != null)
|
||||
@@ -84,70 +114,4 @@ namespace PepperDash.Essentials.Core
|
||||
return descr;
|
||||
}
|
||||
}
|
||||
|
||||
/*/// <summary>
|
||||
/// A collection of RouteDescriptors - typically the static DefaultCollection is used
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Represents a RouteDescriptorCollection
|
||||
/// </summary>
|
||||
public class RouteDescriptorCollection<TInputSelector, TOutputSelector>
|
||||
{
|
||||
public static RouteDescriptorCollection<TInputSelector, TOutputSelector> DefaultCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_DefaultCollection == null)
|
||||
_DefaultCollection = new RouteDescriptorCollection<TInputSelector, TOutputSelector>();
|
||||
return _DefaultCollection;
|
||||
}
|
||||
}
|
||||
private static RouteDescriptorCollection<TInputSelector, TOutputSelector> _DefaultCollection;
|
||||
|
||||
private readonly List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="descriptor"></param>
|
||||
/// <summary>
|
||||
/// AddRouteDescriptor method
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RouteDescriptor for a destination
|
||||
/// </summary>
|
||||
/// <returns>null if no RouteDescriptor for a destination exists</returns>
|
||||
/// <summary>
|
||||
/// GetRouteDescriptorForDestination method
|
||||
/// </summary>
|
||||
public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs<TInputSelector> destination)
|
||||
{
|
||||
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the RouteDescriptor for a given destination AND removes it from collection.
|
||||
/// Returns null if no route with the provided destination exists.
|
||||
/// </summary>
|
||||
public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs<TInputSelector> destination)
|
||||
{
|
||||
var descr = GetRouteDescriptorForDestination(destination);
|
||||
if (descr != null)
|
||||
RouteDescriptors.Remove(descr);
|
||||
return descr;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -4,96 +4,51 @@
|
||||
/// Represents a RouteSwitchDescriptor
|
||||
/// </summary>
|
||||
public class RouteSwitchDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the SwitchingDevice
|
||||
/// </summary>
|
||||
public IRoutingInputs SwitchingDevice { get { return InputPort?.ParentDevice; } }
|
||||
/// <summary>
|
||||
/// The output port being switched from (relevant for matrix switchers). Null for sink devices.
|
||||
/// </summary>
|
||||
public RoutingOutputPort OutputPort { get; set; }
|
||||
/// <summary>
|
||||
/// The input port being switched to.
|
||||
/// </summary>
|
||||
public RoutingInputPort InputPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RouteSwitchDescriptor"/> class for sink devices (no output port).
|
||||
/// </summary>
|
||||
/// <param name="inputPort">The input port being switched to.</param>
|
||||
public RouteSwitchDescriptor(RoutingInputPort inputPort)
|
||||
{
|
||||
InputPort = inputPort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RouteSwitchDescriptor"/> class for matrix switchers.
|
||||
/// </summary>
|
||||
/// <param name="outputPort">The output port being switched from.</param>
|
||||
/// <param name="inputPort">The input port being switched to.</param>
|
||||
public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort)
|
||||
{
|
||||
InputPort = inputPort;
|
||||
OutputPort = outputPort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the route switch descriptor.
|
||||
/// </summary>
|
||||
/// <returns>A string describing the switch operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
if (SwitchingDevice is IRouting)
|
||||
return $"{(SwitchingDevice != null ? SwitchingDevice.Key : "No Device")} switches output {(OutputPort != null ? OutputPort.Key : "No output port")} to input {(InputPort != null ? InputPort.Key : "No input port")}";
|
||||
else
|
||||
return $"{(SwitchingDevice != null ? SwitchingDevice.Key : "No Device")} switches to input {(InputPort != null ? InputPort.Key : "No input port")}";
|
||||
}
|
||||
}
|
||||
|
||||
/*/// <summary>
|
||||
/// Represents an individual link for a route
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Represents a RouteSwitchDescriptor
|
||||
/// </summary>
|
||||
public class RouteSwitchDescriptor<TInputSelector, TOutputSelector>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the SwitchingDevice
|
||||
/// </summary>
|
||||
public IRoutingInputs<TInputSelector> SwitchingDevice { get { return InputPort.ParentDevice; } }
|
||||
public IRoutingInputs SwitchingDevice { get { return InputPort?.ParentDevice; } }
|
||||
/// <summary>
|
||||
/// Gets or sets the OutputPort
|
||||
/// The output port being switched from (relevant for matrix switchers). Null for sink devices.
|
||||
/// </summary>
|
||||
public RoutingOutputPort<TOutputSelector> OutputPort { get; set; }
|
||||
public RoutingOutputPort OutputPort { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the InputPort
|
||||
/// The input port being switched to.
|
||||
/// </summary>
|
||||
public RoutingInputPort<TInputSelector> InputPort { get; set; }
|
||||
public RoutingInputPort InputPort { get; set; }
|
||||
|
||||
public RouteSwitchDescriptor(RoutingInputPort<TInputSelector> inputPort)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RouteSwitchDescriptor"/> class for sink devices (no output port).
|
||||
/// </summary>
|
||||
/// <param name="inputPort">The input port being switched to.</param>
|
||||
public RouteSwitchDescriptor(RoutingInputPort inputPort)
|
||||
{
|
||||
InputPort = inputPort;
|
||||
}
|
||||
|
||||
public RouteSwitchDescriptor(RoutingOutputPort<TOutputSelector> outputPort, RoutingInputPort<TInputSelector> inputPort)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RouteSwitchDescriptor"/> class for matrix switchers.
|
||||
/// </summary>
|
||||
/// <param name="outputPort">The output port being switched from.</param>
|
||||
/// <param name="inputPort">The input port being switched to.</param>
|
||||
public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort)
|
||||
{
|
||||
InputPort = inputPort;
|
||||
OutputPort = outputPort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ToString method
|
||||
/// Returns a string representation of the route switch descriptor.
|
||||
/// </summary>
|
||||
/// <returns>A string describing the switch operation.</returns>
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
if (SwitchingDevice is IRouting)
|
||||
return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector);
|
||||
return $"{(SwitchingDevice != null ? SwitchingDevice.Key : "No Device")} switches output {(OutputPort != null ? OutputPort.Key : "No output port")} to input {(InputPort != null ? InputPort.Key : "No input port")}";
|
||||
else
|
||||
return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector);
|
||||
return $"{(SwitchingDevice != null ? SwitchingDevice.Key : "No Device")} switches to input {(InputPort != null ? InputPort.Key : "No input port")}";
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@@ -92,12 +93,16 @@ namespace PepperDash.Essentials
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand(s => Debug.LogMessage(LogEventLevel.Information, "CONSOLE MESSAGE: {0}", s), "appdebugmessage", "Writes message to log", ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand(s =>
|
||||
{
|
||||
foreach (var tl in TieLineCollection.Default)
|
||||
CrestronConsole.ConsoleCommandResponse(" {0}{1}", tl, CrestronEnvironment.NewLine);
|
||||
},
|
||||
"listtielines", "Prints out all tie lines", ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(ListTieLines,
|
||||
"listtielines", "Prints out all tie lines. Usage: listtielines [signaltype]", ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand(VisualizeRoutes, "visualizeroutes",
|
||||
"Visualizes routes by signal type",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand(VisualizeCurrentRoutes, "visualizecurrentroutes",
|
||||
"Visualizes current active routes from DefaultCollection",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand(s =>
|
||||
{
|
||||
@@ -443,6 +448,282 @@ namespace PepperDash.Essentials
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "All Tie Lines Loaded.");
|
||||
|
||||
Extensions.MapDestinationsToSources();
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "All Routes Mapped.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Visualizes routes in a tree format for better understanding of signal paths
|
||||
/// </summary>
|
||||
private void ListTieLines(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (args.Contains("?"))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Usage: listtielines [signaltype]\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse("Signal types: Audio, Video, SecondaryAudio, AudioVideo, UsbInput, UsbOutput\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
eRoutingSignalType? signalTypeFilter = null;
|
||||
if (!string.IsNullOrEmpty(args))
|
||||
{
|
||||
eRoutingSignalType parsedType;
|
||||
if (Enum.TryParse(args.Trim(), true, out parsedType))
|
||||
{
|
||||
signalTypeFilter = parsedType;
|
||||
}
|
||||
else
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Invalid signal type: {0}\r\n", args.Trim());
|
||||
CrestronConsole.ConsoleCommandResponse("Valid types: Audio, Video, SecondaryAudio, AudioVideo, UsbInput, UsbOutput\r\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var tielines = signalTypeFilter.HasValue
|
||||
? TieLineCollection.Default.Where(tl => tl.Type.HasFlag(signalTypeFilter.Value))
|
||||
: TieLineCollection.Default;
|
||||
|
||||
var count = 0;
|
||||
foreach (var tl in tielines)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse(" {0}{1}", tl, CrestronEnvironment.NewLine);
|
||||
count++;
|
||||
}
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse("\r\nTotal: {0} tieline{1}{2}", count, count == 1 ? "" : "s", CrestronEnvironment.NewLine);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Error listing tielines: {0}\r\n", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void VisualizeRoutes(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (args.Contains("?"))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Usage: visualizeroutes [signaltype] [-s source] [-d destination]\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse(" signaltype: Audio, Video, AudioVideo, etc.\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse(" -s: Filter by source key (partial match)\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse(" -d: Filter by destination key (partial match)\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ParseRouteFilters(args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter);
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse("\r\n+===========================================================================+\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse("| ROUTE VISUALIZATION |\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse("+===========================================================================+\r\n\r\n");
|
||||
|
||||
foreach (var descriptorCollection in Extensions.RouteDescriptors.Where(kv => kv.Value.Descriptors.Count() > 0))
|
||||
{
|
||||
// Filter by signal type if specified
|
||||
if (signalTypeFilter.HasValue && descriptorCollection.Key != signalTypeFilter.Value)
|
||||
continue;
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse("\r\n+--- Signal Type: {0} ({1} routes) ---\r\n",
|
||||
descriptorCollection.Key,
|
||||
descriptorCollection.Value.Descriptors.Count());
|
||||
|
||||
foreach (var descriptor in descriptorCollection.Value.Descriptors)
|
||||
{
|
||||
// Filter by source/dest if specified
|
||||
if (sourceFilter != null && !descriptor.Source.Key.ToLower().Contains(sourceFilter))
|
||||
continue;
|
||||
if (destFilter != null && !descriptor.Destination.Key.ToLower().Contains(destFilter))
|
||||
continue;
|
||||
|
||||
VisualizeRouteDescriptor(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse("\r\n");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Error visualizing routes: {0}\r\n", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void VisualizeCurrentRoutes(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (args.Contains("?"))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Usage: visualizecurrentroutes [signaltype] [-s source] [-d destination]\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse(" signaltype: Audio, Video, AudioVideo, etc.\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse(" -s: Filter by source key (partial match)\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse(" -d: Filter by destination key (partial match)\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ParseRouteFilters(args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter);
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse("\r\n+===========================================================================+\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse("| CURRENT ROUTES VISUALIZATION |\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse("+===========================================================================+\r\n\r\n");
|
||||
|
||||
var hasRoutes = false;
|
||||
|
||||
// Get all descriptors from DefaultCollection
|
||||
var allDescriptors = RouteDescriptorCollection.DefaultCollection.Descriptors;
|
||||
|
||||
// Group by signal type
|
||||
var groupedByType = allDescriptors.GroupBy(d => d.SignalType);
|
||||
|
||||
foreach (var group in groupedByType)
|
||||
{
|
||||
var signalType = group.Key;
|
||||
|
||||
// Filter by signal type if specified
|
||||
if (signalTypeFilter.HasValue && signalType != signalTypeFilter.Value)
|
||||
continue;
|
||||
|
||||
var filteredDescriptors = group.Where(d =>
|
||||
{
|
||||
if (sourceFilter != null && !d.Source.Key.ToLower().Contains(sourceFilter))
|
||||
return false;
|
||||
if (destFilter != null && !d.Destination.Key.ToLower().Contains(destFilter))
|
||||
return false;
|
||||
return true;
|
||||
}).ToList();
|
||||
|
||||
if (filteredDescriptors.Count == 0)
|
||||
continue;
|
||||
|
||||
hasRoutes = true;
|
||||
CrestronConsole.ConsoleCommandResponse("\r\n+--- Signal Type: {0} ({1} routes) ---\r\n",
|
||||
signalType,
|
||||
filteredDescriptors.Count);
|
||||
|
||||
foreach (var descriptor in filteredDescriptors)
|
||||
{
|
||||
VisualizeRouteDescriptor(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasRoutes)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("\r\nNo active routes found in current state.\r\n");
|
||||
}
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse("\r\n");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Error visualizing current state: {0}\r\n", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses route filter arguments from command line
|
||||
/// </summary>
|
||||
/// <param name="args">Command line arguments</param>
|
||||
/// <param name="signalTypeFilter">Parsed signal type filter (if any)</param>
|
||||
/// <param name="sourceFilter">Parsed source filter (if any)</param>
|
||||
/// <param name="destFilter">Parsed destination filter (if any)</param>
|
||||
private void ParseRouteFilters(string args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter)
|
||||
{
|
||||
signalTypeFilter = null;
|
||||
sourceFilter = null;
|
||||
destFilter = null;
|
||||
|
||||
if (string.IsNullOrEmpty(args))
|
||||
return;
|
||||
|
||||
var parts = args.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
for (int i = 0; i < parts.Length; i++)
|
||||
{
|
||||
var part = parts[i];
|
||||
|
||||
// Check for flags
|
||||
if (part == "-s" && i + 1 < parts.Length)
|
||||
{
|
||||
sourceFilter = parts[++i].ToLower();
|
||||
}
|
||||
else if (part == "-d" && i + 1 < parts.Length)
|
||||
{
|
||||
destFilter = parts[++i].ToLower();
|
||||
}
|
||||
// Try to parse as signal type if not a flag and no signal type set yet
|
||||
else if (!part.StartsWith("-") && !signalTypeFilter.HasValue)
|
||||
{
|
||||
if (Enum.TryParse(part, true, out eRoutingSignalType parsedType))
|
||||
{
|
||||
signalTypeFilter = parsedType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visualizes a single route descriptor in a tree format
|
||||
/// </summary>
|
||||
private void VisualizeRouteDescriptor(RouteDescriptor descriptor)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("|\r\n");
|
||||
CrestronConsole.ConsoleCommandResponse("|-- {0} --> {1}\r\n",
|
||||
descriptor.Source.Key,
|
||||
descriptor.Destination.Key);
|
||||
|
||||
if (descriptor.Routes == null || descriptor.Routes.Count == 0)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("| +-- (No switching steps)\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < descriptor.Routes.Count; i++)
|
||||
{
|
||||
var route = descriptor.Routes[i];
|
||||
var isLast = i == descriptor.Routes.Count - 1;
|
||||
var prefix = isLast ? "+" : "|";
|
||||
var continuation = isLast ? " " : "|";
|
||||
|
||||
if (route.SwitchingDevice != null)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("| {0}-- [{1}] {2}\r\n",
|
||||
prefix,
|
||||
route.SwitchingDevice.Key,
|
||||
GetSwitchDescription(route));
|
||||
|
||||
// Add visual connection line for non-last items
|
||||
if (!isLast)
|
||||
CrestronConsole.ConsoleCommandResponse("| {0} |\r\n", continuation);
|
||||
}
|
||||
else
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("| {0}-- {1}\r\n", prefix, route.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a readable description of the switching operation
|
||||
/// </summary>
|
||||
private string GetSwitchDescription(RouteSwitchDescriptor route)
|
||||
{
|
||||
if (route.OutputPort != null && route.InputPort != null)
|
||||
{
|
||||
return string.Format("{0} -> {1}", route.OutputPort.Key, route.InputPort.Key);
|
||||
}
|
||||
else if (route.InputPort != null)
|
||||
{
|
||||
return string.Format("-> {0}", route.InputPort.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
return "(passthrough)";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user