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 { /// /// Extensions added to any IRoutingInputs classes to provide discovery-based routing /// on those destinations. /// public static class IRoutingInputsExtensions { /// /// 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 IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType) { destination.ReleaseRoute(); if (source == null) return; var newRoute = destination.GetRouteToSource(source, signalType); if (newRoute == null) return; RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute); //Debug.Console(1, destination, "Executing new route"); newRoute.ExecuteRoutes(); } /// /// Will release the existing route on the destination, if it is found in /// RouteDescriptorCollection.DefaultCollection /// /// public static void ReleaseRoute(this IRoutingInputs destination) { var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination); if (current != null) { Debug.Console(1, 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 IRoutingInputs 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.AudioVideo) { Debug.Console(1, 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.Console(1, 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.Console(1, destination, "Cannot find audio route to {0}", source.Key); var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescr); if (!videoSuccess) Debug.Console(1, destination, "Cannot find video route to {0}", source.Key); if (!audioSuccess && !videoSuccess) routeDescr = null; } //Debug.Console(1, 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.Console(2, destination, "*#* SelectInput-cycle {1}. Finding {2} route back to {0}", source.Key, cycle, signalType); var destDevInputTies = TieLineCollection.Default.Where(t => t.DestinationPort.ParentDevice == destination && (t.Type == signalType || t.Type == eRoutingSignalType.AudioVideo)); // find a direct tie var directTie = destDevInputTies.FirstOrDefault( t => !(t.SourcePort.ParentDevice is IRoutingInputsOutputs) && t.DestinationPort.ParentDevice == destination && t.SourcePort.ParentDevice == source); RoutingInputPort goodInputPort = null; if (directTie != null) // Found a tie directly to the source { Debug.Console(2, destination, "*#* Found direct tie to {0}**", source.Key); goodInputPort = directTie.DestinationPort; } else // no direct-connect. Walk back devices. { Debug.Console(2, 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); foreach (var inputTieToTry in attachedMidpoints) { Debug.Console(2, destination, "Trying to find route on {0}", inputTieToTry.SourcePort.ParentDevice.Key); var upstreamDeviceOutputPort = inputTieToTry.SourcePort; var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs; // Check if this previous device has already been walked if (!(alreadyCheckedDevices != null && alreadyCheckedDevices.Contains(upstreamRoutingDevice))) { // 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.Console(2, 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.Console(2, 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.Console(2, destination, " No routing. Passthrough device"); //Debug.Console(2, destination, "Exiting cycle {0}", cycle); return true; } if(alreadyCheckedDevices == null) alreadyCheckedDevices = new List(); alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs); Debug.Console(2, 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.Console(1, 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.Console(2, route.ToString()); if (route.SwitchingDevice is IRoutingSinkWithSwitching) (route.SwitchingDevice as IRoutingSinkWithSwitching).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.Console(2, "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.Console(2, "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(OutputPort == null) // IRoutingSink return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector); return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector); } } }