Merge pull request #1265 from PepperDash/routing-clear-fix

This commit is contained in:
Neil Dorin
2025-05-02 11:30:35 -06:00
committed by GitHub
20 changed files with 487 additions and 59 deletions

View File

@@ -18,8 +18,15 @@ namespace PepperDash.Essentials.Core
/// </summary>
public static class Extensions
{
/// <summary>
/// Stores pending route requests, keyed by the destination device key.
/// Used primarily to handle routing requests while a device is cooling down.
/// </summary>
private static readonly Dictionary<string, RouteRequest> RouteRequests = new Dictionary<string, RouteRequest>();
/// <summary>
/// A queue to process route requests and releases sequentially.
/// </summary>
private static readonly GenericQueue routeRequestQueue = new GenericQueue("routingQueue");
/// <summary>
@@ -38,16 +45,49 @@ namespace PepperDash.Essentials.Core
ReleaseAndMakeRoute(destination, source, signalType, inputPort, outputPort);
}
/// <summary>
/// Will release the existing route to the destination, if a route is found. This does not CLEAR the route, only stop counting usage time on any output ports that have a usage tracker set
/// </summary>
/// <param name="destination">destination to clear</param>
public static void ReleaseRoute(this IRoutingInputs destination)
{
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty));
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty, false));
}
/// <summary>
/// Will release the existing route to the destination, if a route is found. This does not CLEAR the route, only stop counting usage time on any output ports that have a usage tracker set
/// </summary>
/// <param name="destination">destination to clear</param>
/// <param name="inputPortKey">Input to use to find existing route</param>
public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey)
{
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey));
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey, false));
}
/// <summary>
/// Clears the route on the destination. This will remove any routes that are currently in use
/// </summary>
/// <param name="destination">Destination</param>
public static void ClearRoute(this IRoutingInputs destination)
{
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty, true));
}
/// <summary>
/// Clears the route on the destination. This will remove any routes that are currently in use
/// </summary>
/// <param name="destination">destination</param>
/// <param name="inputPortKey">input to use to find existing route</param>
public static void ClearRoute(this IRoutingInputs destination, string inputPortKey)
{
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey, true));
}
/// <summary>
/// Removes the route request for the destination. This will remove any routes that are currently in use
/// </summary>
/// <param name="destinationKey">destination device key</param>
public static void RemoveRouteRequestForDestination(string destinationKey)
{
Debug.LogMessage(LogEventLevel.Information, "Removing route request for {destination}", null, destinationKey);
@@ -130,6 +170,15 @@ namespace PepperDash.Essentials.Core
return (audioRouteDescriptor, videoRouteDescriptor);
}
/// <summary>
/// Internal method to handle the logic for releasing an existing route and making a new one.
/// Handles devices with cooling states by queueing the request.
/// </summary>
/// <param name="destination">The destination device.</param>
/// <param name="source">The source device.</param>
/// <param name="signalType">The type of signal to route.</param>
/// <param name="destinationPort">The specific destination input port (optional).</param>
/// <param name="sourcePort">The specific source output port (optional).</param>
private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null)
{
if (destination == null) throw new ArgumentNullException(nameof(destination));
@@ -184,11 +233,16 @@ namespace PepperDash.Essentials.Core
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
}
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty));
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty, false));
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
}
/// <summary>
/// Executes the actual routing based on a <see cref="RouteRequest"/>.
/// Finds the route path, adds it to the collection, and executes the switches.
/// </summary>
/// <param name="request">The route request details.</param>
private static void RunRouteRequest(RouteRequest request)
{
try
@@ -216,14 +270,15 @@ namespace PepperDash.Essentials.Core
{
Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request);
}
}
}
/// <summary>
/// Will release the existing route on the destination, if it is found in
/// RouteDescriptorCollection.DefaultCollection
/// Will release the existing route on the destination, if it is found in RouteDescriptorCollection.DefaultCollection
/// </summary>
/// <param name="destination"></param>
private static void ReleaseRouteInternal(IRoutingInputs destination, string inputPortKey)
/// <param name="destination"></param>
/// <param name="inputPortKey"> The input port key to use to find the route. If empty, will use the first available input port</param>
/// <param name="clearRoute"> If true, will clear the route on the destination. This will remove any routes that are currently in use</param>
private static void ReleaseRouteInternal(IRoutingInputs destination, string inputPortKey, bool clearRoute)
{
try
{
@@ -242,7 +297,7 @@ namespace PepperDash.Essentials.Core
if (current != null)
{
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
current.ReleaseRoutes();
current.ReleaseRoutes(clearRoute);
}
} catch (Exception ex)
{

View File

@@ -1,7 +1,7 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines an IRoutingOutputs devices as being a source - the start of the chain
/// Marker interface to identify a device that acts as the origin of a signal path (<see cref="IRoutingOutputs"/>).
/// </summary>
public interface IRoutingSource : IRoutingOutputs
{

View File

@@ -1,5 +1,8 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines a routing device (<see cref="IRouting"/>) that supports explicitly clearing a route on an output.
/// </summary>
public interface IRoutingWithClear : IRouting
{
/// <summary>

View File

@@ -3,14 +3,25 @@ using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Delegate for handling route change events on devices implementing <see cref="IRoutingWithFeedback"/>.
/// </summary>
/// <param name="midpoint">The routing device where the change occurred.</param>
/// <param name="newRoute">A descriptor of the new route that was established.</param>
public delegate void RouteChangedEventHandler(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute);
/// <summary>
/// Defines an IRouting with a feedback event
/// Defines a routing device (<see cref="IRouting"/>) that provides feedback about its current routes.
/// </summary>
public interface IRoutingWithFeedback : IRouting
{
/// <summary>
/// Gets a list describing the currently active routes on this device.
/// </summary>
List<RouteSwitchDescriptor> CurrentRoutes { get; }
/// <summary>
/// Event triggered when a route changes on this device.
/// </summary>
event RouteChangedEventHandler RouteChanged;
}
}

View File

@@ -1,8 +1,18 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a routing device (typically a transmitter or source) that provides numeric feedback for its current route.
/// Extends <see cref="IRoutingNumeric"/>.
/// </summary>
public interface ITxRouting : IRoutingNumeric
{
/// <summary>
/// Feedback indicating the currently routed video source by its numeric identifier.
/// </summary>
IntFeedback VideoSourceNumericFeedback { get; }
/// <summary>
/// Feedback indicating the currently routed audio source by its numeric identifier.
/// </summary>
IntFeedback AudioSourceNumericFeedback { get; }
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharpPro;
@@ -9,35 +10,63 @@ using Serilog.Events;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents an collection of individual route steps between Source and Destination
/// Represents a collection of individual route steps between a Source and a Destination device for a specific signal type.
/// </summary>
public class RouteDescriptor
{
/// <summary>
/// The destination device (sink or midpoint) for the route.
/// </summary>
public IRoutingInputs Destination { get; private set; }
/// <summary>
/// The specific input port on the destination device used for this route. Can be null if not specified or applicable.
/// </summary>
public RoutingInputPort InputPort { get; private set; }
/// <summary>
/// The source device for the route.
/// </summary>
public IRoutingOutputs Source { get; private set; }
/// <summary>
/// The type of signal being routed (e.g., Audio, Video). This descriptor represents a single signal type.
/// </summary>
public eRoutingSignalType SignalType { get; private set; }
/// <summary>
/// A list of individual switching steps required to establish the route.
/// </summary>
public List<RouteSwitchDescriptor> Routes { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="RouteDescriptor"/> class for a route without a specific destination input port.
/// </summary>
/// <param name="source">The source device.</param>
/// <param name="destination">The destination device.</param>
/// <param name="signalType">The type of signal being routed.</param>
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) : this(source, destination, null, signalType)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RouteDescriptor"/> class for a route with a specific destination input port.
/// </summary>
/// <param name="source">The source device.</param>
/// <param name="destination">The destination device.</param>
/// <param name="inputPort">The destination input port (optional).</param>
/// <param name="signalType">The signal type for this route.</param>
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType)
{
Destination = destination;
InputPort = inputPort;
Source = source;
SignalType = signalType;
InputPort = inputPort;
Routes = new List<RouteSwitchDescriptor>();
}
/// <summary>
/// Executes all routes described in this collection. Typically called via
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
/// Executes all the switching steps defined in the <see cref="Routes"/> list.
/// </summary>
public void ExecuteRoutes()
{
@@ -63,15 +92,27 @@ namespace PepperDash.Essentials.Core
}
/// <summary>
/// Releases all routes in this collection. Typically called via
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
/// Releases the usage tracking for the route and optionally clears the route on the switching devices.
/// </summary>
public void ReleaseRoutes()
/// <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)
{
try
{
switchingDevice.ExecuteSwitch(null, route.OutputPort.Selector, SignalType);
}
catch (Exception e)
{
Debug.LogError("Error executing switch: {exception}", e.Message);
}
}
if (route.OutputPort == null)
{
continue;
@@ -90,6 +131,10 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// 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();

View File

@@ -4,15 +4,42 @@ using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a request to establish a route between a source and a destination device.
/// </summary>
public class RouteRequest
{
/// <summary>
/// The specific input port on the destination device to use for the route. Can be null if the port should be automatically determined or is not applicable.
/// </summary>
public RoutingInputPort DestinationPort { get; set; }
/// <summary>
/// The specific output port on the source device to use for the route. Can be null if the port should be automatically determined or is not applicable.
/// </summary>
public RoutingOutputPort SourcePort { get; set; }
/// <summary>
/// The destination device (sink or midpoint) for the route.
/// </summary>
public IRoutingInputs Destination { get; set; }
/// <summary>
/// The source device for the route.
/// </summary>
public IRoutingOutputs Source { get; set; }
/// <summary>
/// The type of signal being routed (e.g., Audio, Video, AudioVideo).
/// </summary>
public eRoutingSignalType SignalType { get; set; }
/// <summary>
/// Handles the route request after a device's cooldown period has finished.
/// This method is typically subscribed to the IsCoolingDownFeedback.OutputChange event.
/// </summary>
/// <param name="sender">The object that triggered the event (usually the cooling device).</param>
/// <param name="args">Event arguments indicating the cooldown state change.</param>
public void HandleCooldown(object sender, FeedbackEventArgs args)
{
try
@@ -39,6 +66,10 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Returns a string representation of the route request.
/// </summary>
/// <returns>A string describing the source and destination of the route request.</returns>
public override string ToString()
{
return $"Route {Source?.Key ?? "No Source Device"}:{SourcePort?.Key ?? "auto"} to {Destination?.Key ?? "No Destination Device"}:{DestinationPort?.Key ?? "auto"}";

View File

@@ -5,17 +5,34 @@ using Serilog.Events;
namespace PepperDash.Essentials.Core.Routing
{
/// <summary>
/// Represents an item in the route request queue.
/// </summary>
public class RouteRequestQueueItem : IQueueMessage
{
/// <summary>
/// The action to perform for the route request.
/// </summary>
private readonly Action<RouteRequest> action;
/// <summary>
/// The route request data.
/// </summary>
private readonly RouteRequest routeRequest;
/// <summary>
/// Initializes a new instance of the <see cref="RouteRequestQueueItem"/> class.
/// </summary>
/// <param name="routeAction">The action to perform.</param>
/// <param name="request">The route request data.</param>
public RouteRequestQueueItem(Action<RouteRequest> routeAction, RouteRequest request)
{
action = routeAction;
routeRequest = request;
}
/// <summary>
/// Dispatches the route request action.
/// </summary>
public void Dispatch()
{
Debug.LogMessage(LogEventLevel.Information, "Dispatching route request {routeRequest}", null, routeRequest);
@@ -23,23 +40,50 @@ namespace PepperDash.Essentials.Core.Routing
}
}
/// <summary>
/// Represents an item in the queue for releasing a route.
/// </summary>
public class ReleaseRouteQueueItem : IQueueMessage
{
private readonly Action<IRoutingInputs, string> action;
/// <summary>
/// The action to perform for releasing the route.
/// </summary>
private readonly Action<IRoutingInputs, string, bool> action;
/// <summary>
/// The destination device whose route is being released.
/// </summary>
private readonly IRoutingInputs destination;
/// <summary>
/// The specific input port key on the destination to release, or null/empty for any/default.
/// </summary>
private readonly string inputPortKey;
/// <summary>
/// Indicates whether to clear the route (send null) or just release the usage tracking.
/// </summary>
private readonly bool clearRoute;
public ReleaseRouteQueueItem(Action<IRoutingInputs, string> action, IRoutingInputs destination, string inputPortKey)
/// <summary>
/// Initializes a new instance of the <see cref="ReleaseRouteQueueItem"/> class.
/// </summary>
/// <param name="action">The action to perform.</param>
/// <param name="destination">The destination device.</param>
/// <param name="inputPortKey">The input port key.</param>
/// <param name="clearRoute">True to clear the route, false to just release.</param>
public ReleaseRouteQueueItem(Action<IRoutingInputs, string, bool> action, IRoutingInputs destination, string inputPortKey, bool clearRoute)
{
this.action = action;
this.destination = destination;
this.inputPortKey = inputPortKey;
this.clearRoute = clearRoute;
}
/// <summary>
/// Dispatches the release route action.
/// </summary>
public void Dispatch()
{
Debug.LogMessage(LogEventLevel.Information, "Dispatching release route request for {destination}:{inputPortKey}", null, destination?.Key ?? "no destination", string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
action(destination, inputPortKey);
action(destination, inputPortKey, clearRoute);
}
}
}

View File

@@ -1,25 +1,47 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents an individual link for a route
/// Represents a single switching step within a larger route, detailing the switching device, input port, and optionally the output port.
/// </summary>
public class RouteSwitchDescriptor
{
/// <summary>
/// The device performing the switch (derived from the InputPort's parent).
/// </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>
public override string ToString()
{
if (SwitchingDevice is IRouting)

View File

@@ -5,8 +5,17 @@ using System.Linq;
namespace PepperDash.Essentials.Core.Routing
{
/// <summary>
/// Manages routing feedback by subscribing to route changes on midpoint and sink devices,
/// tracing the route back to the original source, and updating the CurrentSourceInfo on sink devices.
/// </summary>
public class RoutingFeedbackManager:EssentialsDevice
{
/// <summary>
/// Initializes a new instance of the <see cref="RoutingFeedbackManager"/> class.
/// </summary>
/// <param name="key">The unique key for this manager device.</param>
/// <param name="name">The name of this manager device.</param>
public RoutingFeedbackManager(string key, string name): base(key, name)
{
AddPreActivationAction(SubscribeForMidpointFeedback);
@@ -14,6 +23,9 @@ namespace PepperDash.Essentials.Core.Routing
}
/// <summary>
/// Subscribes to the RouteChanged event on all devices implementing <see cref="IRoutingWithFeedback"/>.
/// </summary>
private void SubscribeForMidpointFeedback()
{
var midpointDevices = DeviceManager.AllDevices.OfType<IRoutingWithFeedback>();
@@ -24,6 +36,9 @@ namespace PepperDash.Essentials.Core.Routing
}
}
/// <summary>
/// Subscribes to the InputChanged event on all devices implementing <see cref="IRoutingSinkWithSwitchingWithInputPort"/>.
/// </summary>
private void SubscribeForSinkFeedback()
{
var sinkDevices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
@@ -34,6 +49,12 @@ namespace PepperDash.Essentials.Core.Routing
}
}
/// <summary>
/// Handles the RouteChanged event from a midpoint device.
/// Triggers an update for all sink devices.
/// </summary>
/// <param name="midpoint">The midpoint device that reported a route change.</param>
/// <param name="newRoute">The descriptor of the new route.</param>
private void HandleMidpointUpdate(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute)
{
try
@@ -51,6 +72,12 @@ namespace PepperDash.Essentials.Core.Routing
}
}
/// <summary>
/// Handles the InputChanged event from a sink device.
/// Triggers an update for the specific sink device.
/// </summary>
/// <param name="sender">The sink device that reported an input change.</param>
/// <param name="currentInputPort">The new input port selected on the sink device.</param>
private void HandleSinkUpdate(IRoutingSinkWithSwitching sender, RoutingInputPort currentInputPort)
{
try
@@ -63,6 +90,12 @@ namespace PepperDash.Essentials.Core.Routing
}
}
/// <summary>
/// Updates the CurrentSourceInfo and CurrentSourceInfoKey properties on a destination (sink) device
/// based on its currently selected input port by tracing the route back through tie lines.
/// </summary>
/// <param name="destination">The destination sink device to update.</param>
/// <param name="inputPort">The currently selected input port on the destination device.</param>
private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort)
{
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this,destination?.Key, inputPort?.Key);
@@ -199,6 +232,12 @@ namespace PepperDash.Essentials.Core.Routing
}
/// <summary>
/// Recursively traces a route back from a given tie line to find the root source tie line.
/// It navigates through midpoint devices (<see cref="IRoutingWithFeedback"/>) by checking their current routes.
/// </summary>
/// <param name="tieLine">The starting tie line (typically connected to a sink or midpoint).</param>
/// <returns>The <see cref="TieLine"/> connected to the original source device, or null if the source cannot be determined.</returns>
private TieLine GetRootTieLine(TieLine tieLine)
{
TieLine nextTieLine = null;

View File

@@ -5,7 +5,7 @@ using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Basic RoutingInput with no statuses.
/// Represents a basic routing input port on a device.
/// </summary>
public class RoutingInputPort : RoutingPort
{
@@ -41,6 +41,10 @@ namespace PepperDash.Essentials.Core
ParentDevice = parent;
}
/// <summary>
/// Returns a string representation of the input port.
/// </summary>
/// <returns>A string in the format "ParentDeviceKey|PortKey|SignalType|ConnectionType".</returns>
public override string ToString()
{
return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}";

View File

@@ -1,24 +1,25 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// A RoutingInputPort for devices like DM-TX and DM input cards.
/// Will provide video statistics on connected signals
/// Represents a routing input port that provides video status feedback (e.g., sync, resolution).
/// Suitable for devices like DM transmitters or DM input cards.
/// </summary>
public class RoutingInputPortWithVideoStatuses : RoutingInputPort
{
/// <summary>
/// Video statuses attached to this port
/// Provides feedback outputs for video statuses associated with this port.
/// </summary>
public VideoStatusOutputs VideoStatus { get; private set; }
/// <summary>
/// Constructor
/// Initializes a new instance of the <see cref="RoutingInputPortWithVideoStatuses"/> class.
/// </summary>
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
/// May be string, number, whatever</param>
/// <param name="parent">The IRoutingInputs object this lives on</param>
/// <param name="funcs">A VideoStatusFuncsWrapper used to assign the callback funcs that will get
/// the values for the various stats</param>
/// <param name="key">The unique key for this port.</param>
/// <param name="type">The signal type supported by this port.</param>
/// <param name="connType">The physical connection type of this port.</param>
/// <param name="selector">An object used to refer to this port in the parent device's ExecuteSwitch method.</param>
/// <param name="parent">The <see cref="IRoutingInputs"/> device this port belongs to.</param>
/// <param name="funcs">A <see cref="VideoStatusFuncsWrapper"/> containing delegates to retrieve video status values.</param>
public RoutingInputPortWithVideoStatuses(string key,
eRoutingSignalType type, eRoutingPortConnectionType connType, object selector,
IRoutingInputs parent, VideoStatusFuncsWrapper funcs) :

View File

@@ -3,32 +3,72 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Provides event arguments for routing changes, potentially including numeric or port object references.
/// </summary>
public class RoutingNumericEventArgs : EventArgs
{
/// <summary>
/// The numeric representation of the output, if applicable.
/// </summary>
public uint? Output { get; set; }
/// <summary>
/// The numeric representation of the input, if applicable.
/// </summary>
public uint? Input { get; set; }
/// <summary>
/// The type of signal involved in the routing change.
/// </summary>
public eRoutingSignalType SigType { get; set; }
/// <summary>
/// The input port involved in the routing change, if applicable.
/// </summary>
public RoutingInputPort InputPort { get; set; }
/// <summary>
/// The output port involved in the routing change, if applicable.
/// </summary>
public RoutingOutputPort OutputPort { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="RoutingNumericEventArgs"/> class using numeric identifiers.
/// </summary>
/// <param name="output">The numeric output identifier.</param>
/// <param name="input">The numeric input identifier.</param>
/// <param name="sigType">The signal type.</param>
public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RoutingNumericEventArgs"/> class using port objects.
/// </summary>
/// <param name="outputPort">The output port object.</param>
/// <param name="inputPort">The input port object.</param>
/// <param name="sigType">The signal type.</param>
public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort,
eRoutingSignalType sigType)
: this(null, null, outputPort, inputPort, sigType)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RoutingNumericEventArgs"/> class with default values.
/// </summary>
public RoutingNumericEventArgs()
: this(null, null, null, null, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RoutingNumericEventArgs"/> class with potentially mixed identifiers.
/// </summary>
/// <param name="output">The numeric output identifier (optional).</param>
/// <param name="input">The numeric input identifier (optional).</param>
/// <param name="outputPort">The output port object (optional).</param>
/// <param name="inputPort">The input port object (optional).</param>
/// <param name="sigType">The signal type.</param>
public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort,
RoutingInputPort inputPort, eRoutingSignalType sigType)
{

View File

@@ -4,29 +4,46 @@ using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a basic routing output port on a device.
/// </summary>
public class RoutingOutputPort : RoutingPort
{
/// <summary>
/// The IRoutingOutputs object this port lives on
/// The IRoutingOutputs object this port lives on.
/// </summary>
///
[JsonIgnore]
public IRoutingOutputs ParentDevice { get; private set; }
/// <summary>
/// Tracks which destinations are currently using this output port.
/// </summary>
public InUseTracking InUseTracker { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="RoutingOutputPort"/> class.
/// </summary>
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
/// May be string, number, whatever</param>
/// <param name="parent">The IRoutingOutputs object this port lives on</param>
/// <param name="key">The unique key for this port.</param>
/// <param name="type">The signal type supported by this port.</param>
/// <param name="connType">The physical connection type of this port.</param>
/// <param name="selector">An object used to refer to this port in the parent device's ExecuteSwitch method.</param>
/// <param name="parent">The <see cref="IRoutingOutputs"/> device this port belongs to.</param>
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
object selector, IRoutingOutputs parent)
: this(key, type, connType, selector, parent, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RoutingOutputPort"/> class, potentially marking it as internal.
/// </summary>
/// <param name="key">The unique key for this port.</param>
/// <param name="type">The signal type supported by this port.</param>
/// <param name="connType">The physical connection type of this port.</param>
/// <param name="selector">An object used to refer to this port in the parent device's ExecuteSwitch method.</param>
/// <param name="parent">The <see cref="IRoutingOutputs"/> device this port belongs to.</param>
/// <param name="isInternal">True if this port represents an internal connection within a device (e.g., card to backplane).</param>
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
object selector, IRoutingOutputs parent, bool isInternal)
: base(key, type, connType, selector, isInternal)
@@ -35,6 +52,10 @@ namespace PepperDash.Essentials.Core
InUseTracker = new InUseTracking();
}
/// <summary>
/// Returns a string representation of the output port.
/// </summary>
/// <returns>A string in the format "ParentDeviceKey|PortKey|SignalType|ConnectionType".</returns>
public override string ToString()
{
return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}";

View File

@@ -4,18 +4,47 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Base class for RoutingInput and Output ports
/// Base class for <see cref="RoutingInputPort"/> and <see cref="RoutingOutputPort"/>.
/// </summary>
public abstract class RoutingPort : IKeyed
{
/// <summary>
/// The unique key identifying this port within its parent device.
/// </summary>
public string Key { get; private set; }
/// <summary>
/// The type of signal this port handles (e.g., Audio, Video, AudioVideo).
/// </summary>
public eRoutingSignalType Type { get; private set; }
/// <summary>
/// The physical connection type of this port (e.g., Hdmi, Rca, Dm).
/// </summary>
public eRoutingPortConnectionType ConnectionType { get; private set; }
/// <summary>
/// An object (often a number or string) used by the parent routing device to select this port during switching.
/// </summary>
public readonly object Selector;
/// <summary>
/// Indicates if this port represents an internal connection within a device (e.g., card to backplane).
/// </summary>
public bool IsInternal { get; private set; }
/// <summary>
/// An object used to match feedback values to this port, if applicable.
/// </summary>
public object FeedbackMatchObject { get; set; }
/// <summary>
/// A reference to the underlying hardware port object (e.g., SimplSharpPro Port), if applicable.
/// </summary>
public object Port { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="RoutingPort"/> class.
/// </summary>
/// <param name="key">The unique key for this port.</param>
/// <param name="type">The signal type supported by this port.</param>
/// <param name="connType">The physical connection type of this port.</param>
/// <param name="selector">The selector object for switching.</param>
/// <param name="isInternal">True if this port is internal.</param>
public RoutingPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, object selector, bool isInternal)
{
Key = key;

View File

@@ -7,7 +7,8 @@ using Crestron.SimplSharp;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// These should correspond directly with the portNames var in the config tool.
/// Defines constant string values for common routing port keys.
/// These should correspond directly with the portNames var in the config tool.
/// </summary>
public class RoutingPortNames
{

View File

@@ -4,14 +4,23 @@ using System.Collections.Generic;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a connection (tie line) between a <see cref="RoutingOutputPort"/> and a <see cref="RoutingInputPort"/>.
/// </summary>
public class TieLine
{
/// <summary>
/// The source output port of the tie line.
/// </summary>
public RoutingOutputPort SourcePort { get; private set; }
/// <summary>
/// The destination input port of the tie line.
/// </summary>
public RoutingInputPort DestinationPort { get; private set; }
//public int InUseCount { get { return DestinationUsingThis.Count; } }
/// <summary>
/// Gets the type of this tie line. Will either be the type of the desination port
/// Gets the type of this tie line. Will either be the type of the destination port
/// or the type of OverrideType when it is set.
/// </summary>
public eRoutingSignalType Type
@@ -35,20 +44,27 @@ namespace PepperDash.Essentials.Core
//List<IRoutingInputs> DestinationUsingThis = new List<IRoutingInputs>();
/// <summary>
/// For tie lines that represent internal links, like from cards to the matrix in a DM.
/// This property is true if SourcePort and DestinationPort IsInternal
/// property are both true
/// Gets a value indicating whether this tie line represents an internal connection within a device (both source and destination ports are internal).
/// </summary>
public bool IsInternal { get { return SourcePort.IsInternal && DestinationPort.IsInternal; } }
/// <summary>
/// Gets a value indicating whether the signal types of the source and destination ports differ.
/// </summary>
public bool TypeMismatch { get { return SourcePort.Type != DestinationPort.Type; } }
/// <summary>
/// Gets a value indicating whether the connection types of the source and destination ports differ.
/// </summary>
public bool ConnectionTypeMismatch { get { return SourcePort.ConnectionType != DestinationPort.ConnectionType; } }
/// <summary>
/// A descriptive note about any type mismatch, if applicable.
/// </summary>
public string TypeMismatchNote { get; set; }
/// <summary>
///
/// Initializes a new instance of the <see cref="TieLine"/> class.
/// </summary>
/// <param name="sourcePort"></param>
/// <param name="destinationPort"></param>
/// <param name="sourcePort">The source output port.</param>
/// <param name="destinationPort">The destination input port.</param>
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort)
{
if (sourcePort == null || destinationPort == null)
@@ -58,9 +74,11 @@ namespace PepperDash.Essentials.Core
}
/// <summary>
/// Creates a tie line with an overriding Type. See help for OverrideType property for info
/// Creates a tie line with an overriding Type. See help for OverrideType property for info.
/// </summary>
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type</param>
/// <param name="sourcePort">The source output port.</param>
/// <param name="destinationPort">The destination input port.</param>
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type for routing calculations.</param>
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType? overrideType) :
this(sourcePort, destinationPort)
{
@@ -68,9 +86,11 @@ namespace PepperDash.Essentials.Core
}
/// <summary>
/// Creates a tie line with an overriding Type. See help for OverrideType property for info
/// Creates a tie line with an overriding Type. See help for OverrideType property for info.
/// </summary>
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type</param>
/// <param name="sourcePort">The source output port.</param>
/// <param name="destinationPort">The destination input port.</param>
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type for routing calculations.</param>
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType overrideType) :
this(sourcePort, destinationPort)
{
@@ -78,18 +98,25 @@ namespace PepperDash.Essentials.Core
}
/// <summary>
/// Will link up video status from supporting inputs to connected outputs
/// Will link up video status from supporting inputs to connected outputs.
/// </summary>
public void Activate()
{
// Now does nothing
}
/// <summary>
/// Deactivates the tie line.
/// </summary>
public void Deactivate()
{
// Now does nothing
}
/// <summary>
/// Returns a string representation of the tie line.
/// </summary>
/// <returns>A string describing the source, destination, and type of the tie line.</returns>
public override string ToString()
{
return string.Format("Tie line: {0}:{1} --> {2}:{3} {4}", SourcePort.ParentDevice.Key, SourcePort.Key,
@@ -99,8 +126,14 @@ namespace PepperDash.Essentials.Core
//********************************************************************************
/// <summary>
/// Represents a collection of <see cref="TieLine"/> objects.
/// </summary>
public class TieLineCollection : List<TieLine>
{
/// <summary>
/// Gets the default singleton instance of the <see cref="TieLineCollection"/>.
/// </summary>
public static TieLineCollection Default
{
get
@@ -111,6 +144,9 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Backing field for the singleton instance.
/// </summary>
[JsonIgnore]
private static TieLineCollection _Default;
}

View File

@@ -1,6 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
@@ -15,15 +13,44 @@ using Serilog.Events;
namespace PepperDash.Essentials.Core.Config
{
/// <summary>
/// Represents the configuration data for a single tie line between two routing ports.
/// </summary>
public class TieLineConfig
{
/// <summary>
/// The key of the source device.
/// </summary>
public string SourceKey { get; set; }
/// <summary>
/// The key of the source card (if applicable, e.g., in a modular chassis).
/// </summary>
public string SourceCard { get; set; }
/// <summary>
/// The key of the source output port.
/// </summary>
public string SourcePort { get; set; }
/// <summary>
/// The key of the destination device.
/// </summary>
public string DestinationKey { get; set; }
/// <summary>
/// The key of the destination card (if applicable).
/// </summary>
public string DestinationCard { get; set; }
/// <summary>
/// The key of the destination input port.
/// </summary>
public string DestinationPort { get; set; }
/// <summary>
/// Optional override for the signal type of the tie line. If set, this overrides the destination port's type for routing calculations.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
public eRoutingSignalType? OverrideType { get; set; }
@@ -73,11 +100,19 @@ namespace PepperDash.Essentials.Core.Config
return new TieLine(sourceOutputPort, destinationInputPort, OverrideType);
}
/// <summary>
/// Logs an error message related to creating this tie line configuration.
/// </summary>
/// <param name="msg">The specific error message.</param>
void LogError(string msg)
{
Debug.LogMessage(LogEventLevel.Error, "WARNING: Cannot create tie line: {message}:\r {tieLineConfig}",null, msg, this);
}
/// <summary>
/// Returns a string representation of the tie line configuration.
/// </summary>
/// <returns>A string describing the source and destination of the configured tie line.</returns>
public override string ToString()
{
return string.Format("{0}.{1}.{2} --> {3}.{4}.{5}", SourceKey, SourceCard, SourcePort,

View File

@@ -7,7 +7,7 @@ using System.Collections.Generic;
namespace PepperDash.Essentials.Devices.Common.Generic
{
public class GenericSink : EssentialsDevice, IRoutingSink
public class GenericSink : EssentialsDevice, IRoutingSinkWithInputPort
{
public GenericSink(string key, string name) : base(key, name)
{

View File

@@ -8,9 +8,10 @@ using System.Linq;
namespace PepperDash.Essentials.Devices.Common.SoftCodec
{
public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingOutputs, IRoutingSinkWithSwitching
public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSinkWithSwitchingWithInputPort
{
private RoutingInputPort _currentInputPort;
public RoutingInputPort CurrentInputPort {
get => _currentInputPort;
set