feat: Refactor routing interfaces and feedback mechanisms

- Removed ITxRouting and ITxRoutingWithFeedback interfaces as they were redundant.
- Updated RouteDescriptor and RouteSwitchDescriptor to use new feedback interfaces.
- Modified RoutingFeedbackManager to utilize IRoutingSinkWithFeedback and IRoutingMidpointWithFeedback.
- Adjusted GenericAudioOut, BlueJeansPc, and other device classes to implement new feedback interfaces.
- Introduced new messenger classes for IRoutingSinkWithFeedback and IRoutingMidpointWithFeedback.
- Cleaned up unused messenger interfaces and consolidated routing logic.
- Updated MobileControlEssentialsRoomBridge to remove dependency on IHasCurrentSourceInfoChange.
This commit is contained in:
Neil Dorin 2026-05-14 14:36:30 -06:00
parent b9dcec587d
commit f64b595fd7
46 changed files with 324 additions and 872 deletions

View file

@ -155,10 +155,10 @@ namespace PepperDash.Essentials.Core;
}
var inputsOutputs = dev as IRoutingInputsOutputs;
var inputsOutputs = dev as IRoutingMidpoint;
if (inputsOutputs == null)
{
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not support IRoutingInputsOutputs, failed to get CEC port called '{1}'",
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not support IRoutingMidpoint, failed to get CEC port called '{1}'",
config.ControlPortDevKey, config.ControlPortName);
return null;

View file

@ -5,7 +5,7 @@ namespace PepperDash.Essentials.Core
/// <summary>
/// Identifies a device that contains audio zones
/// </summary>
public interface IAudioZones : IRouting
public interface IAudioZones : IRoutingMidpointWithFeedback
{
/// <summary>
/// Gets the collection of audio zones

View file

@ -9,6 +9,6 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces;
/// It is designed to be implemented by devices that require these capabilities,
/// such as projectors, displays, and other visual output devices.
/// </summary>
public interface IDisplay : IHasFeedback, IRoutingSinkWithSwitching, IHasPowerControl, IWarmingCooling, IUsageTracking, IKeyName
public interface IDisplay : IHasFeedback, IRoutingSinkWithFeedback, IHasPowerControl, IWarmingCooling, IUsageTracking, IKeyName
{
}

View file

@ -18,16 +18,16 @@ public class DestinationListItem
[JsonProperty("sinkKey")]
public string SinkKey { get; set; }
private IRoutingSink _sinkDevice;
private IRoutingSinkWithFeedback _sinkDevice;
/// <summary>
/// Gets the actual device instance for this destination.
/// Lazily loads the device from the DeviceManager using the SinkKey.
/// </summary>
[JsonIgnore]
public IRoutingSink SinkDevice
public IRoutingSinkWithFeedback SinkDevice
{
get { return _sinkDevice ?? (_sinkDevice = DeviceManager.GetDeviceForKey(SinkKey) as IRoutingSink); }
get { return _sinkDevice ?? (_sinkDevice = DeviceManager.GetDeviceForKey(SinkKey) as IRoutingSinkWithFeedback); }
}
/// <summary>

View file

@ -25,7 +25,7 @@ public interface IHasDefaultDisplay
/// <summary>
/// The default display for the room, used for presentation routing and other default routes
/// </summary>
IRoutingSink DefaultDisplay { get; }
IRoutingSinkWithFeedback DefaultDisplay { get; }
}
/// <summary>

View file

@ -465,8 +465,8 @@ public static class Extensions
IndexTieLines();
}
var sinks = DeviceManager.AllDevices.OfType<IRoutingInputs>().Where(d => !(d is IRoutingInputsOutputs));
var sources = DeviceManager.AllDevices.OfType<IRoutingOutputs>().Where(d => !(d is IRoutingInputsOutputs));
var sinks = DeviceManager.AllDevices.OfType<IRoutingInputs>().Where(d => !(d is IRoutingMidpoint));
var sources = DeviceManager.AllDevices.OfType<IRoutingOutputs>().Where(d => !(d is IRoutingMidpoint));
foreach (var sink in sinks)
{
@ -544,7 +544,7 @@ public static class Extensions
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
/// <returns>true if source is hit</returns>
private static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices,
RoutingOutputPort outputPortToUse, List<IRoutingMidpoint> alreadyCheckedDevices,
eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable, RoutingInputPort destinationPort, RoutingOutputPort sourcePort)
{
cycle++;
@ -608,16 +608,16 @@ public static class Extensions
// No direct tie? Run back out on the inputs' attached devices...
// Only the ones that are routing devices
var midpointTieLines = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs);
var midpointTieLines = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingMidpoint);
//Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration
if (alreadyCheckedDevices == null)
alreadyCheckedDevices = new List<IRoutingInputsOutputs>();
alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs);
alreadyCheckedDevices = new List<IRoutingMidpoint>();
alreadyCheckedDevices.Add(destination as IRoutingMidpoint);
foreach (var tieLine in midpointTieLines)
{
var midpointDevice = tieLine.SourcePort.ParentDevice as IRoutingInputsOutputs;
var midpointDevice = tieLine.SourcePort.ParentDevice as IRoutingMidpoint;
// Check if this previous device has already been walked
if (alreadyCheckedDevices.Contains(midpointDevice))
@ -659,12 +659,12 @@ public static class Extensions
// we have a route on corresponding inputPort. *** Do the route ***
if (destination is IRoutingSink)
if (destination is IRoutingSinkWithFeedback)
{
// it's a sink device
routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort));
}
else if (destination is IRouting)
else if (destination is IRoutingMidpointWithFeedback)
{
routeTable.Routes.Add(new RouteSwitchDescriptor(outputPortToUse, goodInputPort));
}

View file

@ -1,35 +0,0 @@
using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Delegate for SourceInfoChangeHandler
/// </summary>
public delegate void SourceInfoChangeHandler(SourceListItem info, ChangeType type);
//*******************************************************************************************
// Interfaces
/// <summary>
/// For rooms with a single presentation source, change event
/// </summary>
[Obsolete("Use ICurrentSources instead")]
public interface IHasCurrentSourceInfoChange
{
/// <summary>
/// The key for the current source info, used to look up the source in the SourceList
/// </summary>
string CurrentSourceInfoKey { get; set; }
/// <summary>
/// The current source info for the room, used to look up the source in the SourceList
/// </summary>
SourceListItem CurrentSourceInfo { get; set; }
/// <summary>
/// Event that is raised when the current source info changes.
/// This is used to notify the system of changes to the current source info.
/// The event handler receives the new source info and the type of change that occurred.
/// </summary>
event SourceInfoChangeHandler CurrentSourceChange;
}
}

View file

@ -1,28 +0,0 @@
using System.Collections.Generic;
namespace PepperDash.Essentials.Core.Routing
{
/// <summary>
/// Defines the contract for IMatrixRouting
/// </summary>
public interface IMatrixRouting
{
/// <summary>
/// Gets the input slots
/// </summary>
Dictionary<string, IRoutingInputSlot> InputSlots { get; }
/// <summary>
/// Gets the output slots
/// </summary>
Dictionary<string, IRoutingOutputSlot> OutputSlots { get; }
/// <summary>
/// Routes the specified input slot to the specified output slot for the specified signal type
/// </summary>
/// <param name="inputSlotKey">key of the input slot</param>
/// <param name="outputSlotKey">key of the output slot</param>
/// <param name="type">signal type</param>
void Route(string inputSlotKey, string outputSlotKey, eRoutingSignalType type);
}
}

View file

@ -1,13 +0,0 @@
namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines the contract for IRmcRouting
/// </summary>
public interface IRmcRouting : IRoutingNumeric
{
/// <summary>
/// Feedback for the current Audio/Video source as a number
/// </summary>
IntFeedback AudioVideoSourceNumericFeedback { get; }
}

View file

@ -1,8 +0,0 @@
namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines an IRmcRouting with a feedback event
/// </summary>
public interface IRmcRoutingWithFeedback : IRmcRouting
{
}

View file

@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines a midpoint device as have internal routing. Any devices in the middle of the
/// signal chain, that do switching, must implement this for routing to work otherwise
/// the routing algorithm will treat the IRoutingInputsOutputs device as a passthrough
/// device.
/// </summary>
public interface IRouting : IRoutingInputsOutputs
{
/// <summary>
/// Executes a switch on the device
/// </summary>
/// <param name="inputSelector">input selector</param>
/// <param name="outputSelector">output selector</param>
/// <param name="signalType">type of signal</param>
void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType);
}

View file

@ -1,15 +0,0 @@
using System;
using PepperDash.Core;
namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines the contract for IRoutingFeedback
/// </summary>
public interface IRoutingFeedback : IKeyName
{
/// <summary>
/// Event raised when a numeric switch changes
/// </summary>
event EventHandler<RoutingNumericEventArgs> NumericSwitchChange;
//void OnSwitchChange(RoutingNumericEventArgs e);
}

View file

@ -1,14 +0,0 @@
namespace PepperDash.Essentials.Core.Routing;
/// <summary>
/// Defines the contract for IRoutingInputSlot
/// </summary>
public interface IRoutingInputSlot : IRoutingSlot, IOnline, IVideoSync
{
/// <summary>
/// Gets the Tx device key
/// </summary>
string TxDeviceKey { get; }
}

View file

@ -1,9 +0,0 @@
namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines the contract for IRoutingInputsOutputs
/// </summary>
public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs
{
}

View file

@ -0,0 +1,10 @@
namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines a midpoint (passthrough) device that has both input and output routing ports
/// but does not perform active switching. For switching midpoints, see <see cref="IRoutingMidpointWithFeedback"/>.
/// </summary>
public interface IRoutingMidpoint : IRoutingInputs, IRoutingOutputs
{
}

View file

@ -0,0 +1,43 @@
using System.Collections.Generic;
using System;
namespace PepperDash.Essentials.Core;
/// <summary>
/// Delegate for RouteChangedEventHandler.
/// </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(IRoutingMidpointWithFeedback midpoint, RouteSwitchDescriptor newRoute);
/// <summary>
/// Defines a midpoint device that performs active switching and provides feedback about its current routes.
/// Combines the capabilities of the former IRouting, IRoutingWithFeedback, and IRoutingWithClear interfaces.
/// </summary>
public interface IRoutingMidpointWithFeedback : IRoutingMidpoint
{
/// <summary>
/// Executes a switch on the device.
/// </summary>
/// <param name="inputSelector">Input selector.</param>
/// <param name="outputSelector">Output selector.</param>
/// <param name="signalType">Type of signal.</param>
void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType);
/// <summary>
/// Clears a route to an output.
/// </summary>
/// <param name="outputSelector">Output to clear.</param>
/// <param name="signalType">Signal type to clear.</param>
void ClearRoute(object outputSelector, eRoutingSignalType signalType);
/// <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,16 +0,0 @@
namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines the contract for IRoutingNumeric
/// </summary>
public interface IRoutingNumeric : IRouting
{
/// <summary>
/// Executes a numeric switch on the device
/// </summary>
/// <param name="input">input selector</param>
/// <param name="output">output selector</param>
/// <param name="type">type of signal</param>
void ExecuteNumericSwitch(ushort input, ushort output, eRoutingSignalType type);
}

View file

@ -1,8 +0,0 @@
namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines an IRoutingNumeric with a feedback event
/// </summary>
public interface IRoutingNumericWithFeedback : IRoutingNumeric, IRoutingFeedback
{
}

View file

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
namespace PepperDash.Essentials.Core.Routing;
/// <summary>
/// Defines the contract for IRoutingOutputSlot
/// </summary>
public interface IRoutingOutputSlot : IRoutingSlot
{
/// <summary>
/// Event raised when output slot changes
/// </summary>
event EventHandler OutputSlotChanged;
/// <summary>
/// Gets the Rx device key
/// </summary>
string RxDeviceKey { get; }
/// <summary>
/// Gets the current routes
/// </summary>
Dictionary<eRoutingSignalType, IRoutingInputSlot> CurrentRoutes { get; }
}

View file

@ -1,24 +0,0 @@
using PepperDash.Core;
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines the contract for IRoutingSink
/// </summary>
public interface IRoutingSink : IRoutingInputs, IKeyName, ICurrentSources
{
}
/// <summary>
/// For fixed-source endpoint devices with an input port
/// </summary>
public interface IRoutingSinkWithInputPort : IRoutingSink
{
/// <summary>
/// Gets the current input port for this routing sink.
/// </summary>
RoutingInputPort CurrentInputPort { get; }
}

View file

@ -1,12 +1,37 @@
namespace PepperDash.Essentials.Core;
using PepperDash.Core;
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines the contract for IRoutingSinkWithFeedback
/// Delegate for InputChangedEventHandler.
/// </summary>
public interface IRoutingSinkWithFeedback : IRoutingSink
/// <param name="destination">The sink device that changed input.</param>
/// <param name="currentPort">The new input port selected on the sink device.</param>
public delegate void InputChangedEventHandler(IRoutingSinkWithFeedback destination, RoutingInputPort currentPort);
/// <summary>
/// Defines a routing sink (endpoint) device that can switch inputs and provides feedback.
/// Consolidates the former IRoutingSink, IRoutingSinkWithInputPort, IRoutingSinkWithSwitching,
/// IRoutingSinkWithSwitchingWithInputPort, and IRoutingSinkWithFeedback interfaces.
/// </summary>
public interface IRoutingSinkWithFeedback : IRoutingInputs, IKeyName, ICurrentSources
{
/// <summary>
/// Executes a switch on the device.
/// </summary>
/// <param name="inputSelector">Input selector.</param>
void ExecuteSwitch(object inputSelector);
/// <summary>
/// Gets the current input port for this routing sink.
/// </summary>
RoutingInputPort CurrentInputPort { get; }
/// <summary>
/// Event raised when the input changes.
/// </summary>
event InputChangedEventHandler InputChanged;
}

View file

@ -1,30 +0,0 @@

namespace PepperDash.Essentials.Core;
/// <summary>
/// Delegate for InputChangedEventHandler
/// </summary>
public delegate void InputChangedEventHandler(IRoutingSinkWithSwitching destination, RoutingInputPort currentPort);
/// <summary>
/// Defines the contract for IRoutingSinkWithSwitching
/// </summary>
public interface IRoutingSinkWithSwitching : IRoutingSink
{
/// <summary>
/// Executes a switch on the device
/// </summary>
/// <param name="inputSelector">input selector</param>
void ExecuteSwitch(object inputSelector);
}
/// <summary>
/// Defines the contract for IRoutingSinkWithSwitchingWithInputPort
/// </summary>
public interface IRoutingSinkWithSwitchingWithInputPort : IRoutingSinkWithSwitching, IRoutingSinkWithInputPort
{
/// <summary>
/// Event raised when the input changes
/// </summary>
event InputChangedEventHandler InputChanged;
}

View file

@ -1,20 +0,0 @@
using PepperDash.Core;
namespace PepperDash.Essentials.Core.Routing
{
/// <summary>
/// Defines the contract for IRoutingSlot
/// </summary>
public interface IRoutingSlot:IKeyName
{
/// <summary>
/// Gets the slot number
/// </summary>
int SlotNumber { get; }
/// <summary>
/// Gets the supported signal types
/// </summary>
eRoutingSignalType SupportedSignalTypes { get; }
}
}

View file

@ -1,14 +0,0 @@
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>
/// Clears a route to an output, however a device needs to do that
/// </summary>
/// <param name="outputSelector">Output to clear</param>
/// <param name="signalType">signal type to clear</param>
void ClearRoute(object outputSelector, eRoutingSignalType signalType);
}

View file

@ -1,29 +0,0 @@
using System.Collections.Generic;
using System;
namespace PepperDash.Essentials.Core;
/// <summary>
/// Gets a list describing the currently active routes on this device.
/// </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>
/// <summary>
/// Delegate for RouteChangedEventHandler
/// </summary>
public delegate void RouteChangedEventHandler(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute);
/// <summary>
/// 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,17 +0,0 @@
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,8 +0,0 @@
namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines an IRmcRouting with a feedback event
/// </summary>
public interface ITxRoutingWithFeedback : ITxRouting
{
}

View file

@ -74,13 +74,13 @@ public class RouteDescriptor
{
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", null, route.ToString());
if (route.SwitchingDevice is IRoutingSinkWithSwitching sink)
if (route.SwitchingDevice is IRoutingSinkWithFeedback sink)
{
sink.ExecuteSwitch(route.InputPort.Selector);
continue;
}
if (route.SwitchingDevice is IRouting switchingDevice)
if (route.SwitchingDevice is IRoutingMidpointWithFeedback switchingDevice)
{
switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType);
@ -97,9 +97,9 @@ public class RouteDescriptor
/// <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))
foreach (var route in Routes.Where(r => r.SwitchingDevice is IRoutingMidpointWithFeedback))
{
if (route.SwitchingDevice is IRouting switchingDevice)
if (route.SwitchingDevice is IRoutingMidpointWithFeedback switchingDevice)
{
if (clearRoute)
{

View file

@ -45,7 +45,7 @@
/// <inheritdoc />
public override string ToString()
{
if (SwitchingDevice is IRouting)
if (SwitchingDevice is IRoutingMidpointWithFeedback)
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")}";

View file

@ -49,8 +49,8 @@ namespace PepperDash.Essentials.Core.Routing
{
midpointToSinksMap = new Dictionary<string, HashSet<string>>();
var sinks = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
var midpoints = DeviceManager.AllDevices.OfType<IRoutingWithFeedback>();
var sinks = DeviceManager.AllDevices.OfType<IRoutingSinkWithFeedback>();
var midpoints = DeviceManager.AllDevices.OfType<IRoutingMidpointWithFeedback>();
foreach (var sink in sinks)
{
@ -80,7 +80,7 @@ namespace PepperDash.Essentials.Core.Routing
/// <summary>
/// Gets all upstream midpoint device keys for a given sink
/// </summary>
private HashSet<string> GetUpstreamMidpoints(IRoutingSinkWithSwitchingWithInputPort sink)
private HashSet<string> GetUpstreamMidpoints(IRoutingSinkWithFeedback sink)
{
var result = new HashSet<string>();
var visited = new HashSet<string>();
@ -109,7 +109,7 @@ namespace PepperDash.Essentials.Core.Routing
visited.Add(tieLine.SourcePort.ParentDevice.Key);
if (tieLine.SourcePort.ParentDevice is IRoutingWithFeedback midpoint)
if (tieLine.SourcePort.ParentDevice is IRoutingMidpointWithFeedback midpoint)
{
midpoints.Add(midpoint.Key);
@ -131,11 +131,11 @@ namespace PepperDash.Essentials.Core.Routing
}
/// <summary>
/// Subscribes to the RouteChanged event on all devices implementing <see cref="IRoutingWithFeedback"/>.
/// Subscribes to the RouteChanged event on all devices implementing <see cref="IRoutingMidpointWithFeedback"/>.
/// </summary>
private void SubscribeForMidpointFeedback()
{
var midpointDevices = DeviceManager.AllDevices.OfType<IRoutingWithFeedback>();
var midpointDevices = DeviceManager.AllDevices.OfType<IRoutingMidpointWithFeedback>();
foreach (var device in midpointDevices)
{
@ -144,12 +144,12 @@ namespace PepperDash.Essentials.Core.Routing
}
/// <summary>
/// Subscribes to the InputChanged event on all devices implementing <see cref="IRoutingSinkWithSwitchingWithInputPort"/>.
/// Subscribes to the InputChanged event on all devices implementing <see cref="IRoutingSinkWithFeedback"/>.
/// </summary>
private void SubscribeForSinkFeedback()
{
var sinkDevices =
DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
DeviceManager.AllDevices.OfType<IRoutingSinkWithFeedback>();
foreach (var device in sinkDevices)
{
@ -164,7 +164,7 @@ namespace PepperDash.Essentials.Core.Routing
/// <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,
IRoutingMidpointWithFeedback midpoint,
RouteSwitchDescriptor newRoute
)
{
@ -183,7 +183,7 @@ namespace PepperDash.Essentials.Core.Routing
foreach (var sinkKey in affectedSinkKeys)
{
if (DeviceManager.GetDeviceForKey(sinkKey) is IRoutingSinkWithSwitchingWithInputPort sink)
if (DeviceManager.GetDeviceForKey(sinkKey) is IRoutingSinkWithFeedback sink)
{
UpdateDestination(sink, sink.CurrentInputPort);
}
@ -218,7 +218,7 @@ namespace PepperDash.Essentials.Core.Routing
/// <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,
IRoutingSinkWithFeedback sender,
RoutingInputPort currentInputPort
)
{
@ -246,7 +246,7 @@ namespace PepperDash.Essentials.Core.Routing
/// <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,
IRoutingSinkWithFeedback destination,
RoutingInputPort inputPort
)
{
@ -298,7 +298,7 @@ namespace PepperDash.Essentials.Core.Routing
/// Called after debounce delay.
/// </summary>
private void UpdateDestinationImmediate(
IRoutingSinkWithSwitching destination,
IRoutingSinkWithFeedback destination,
RoutingInputPort inputPort
)
{
@ -491,7 +491,7 @@ namespace PepperDash.Essentials.Core.Routing
// Get all potential sources (devices that only have outputs, not inputs+outputs)
var sources = DeviceManager.AllDevices
.OfType<IRoutingOutputs>()
.Where(s => !(s is IRoutingInputsOutputs));
.Where(s => !(s is IRoutingMidpoint));
// Try each signal type that this TieLine supports
var signalTypes = new[]

View file

@ -62,8 +62,8 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
})];
}
// Check if device implements IRoutingInputsOutputs
if (device is IRoutingInputsOutputs)
// Check if device implements IRoutingMidpoint
if (device is IRoutingMidpoint)
{
deviceInfo.HasInputsAndOutputs = true;
}

View file

@ -12,11 +12,16 @@ namespace PepperDash.Essentials.Devices.Common;
/// <summary>
/// Represents and audio endpoint
/// </summary>
public class GenericAudioOut : EssentialsDevice, IRoutingSink
public class GenericAudioOut : EssentialsDevice, IRoutingSinkWithFeedback
{
/// <inheritdoc/>
public RoutingInputPort CurrentInputPort => AnyAudioIn;
/// <inheritdoc/>
public event InputChangedEventHandler InputChanged;
/// <inheritdoc/>
public void ExecuteSwitch(object inputSelector) { }
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, IRoutingSource> CurrentSources { get; private set; }

View file

@ -229,7 +229,7 @@ public class BasicIrDisplay : DisplayBase, IBasicVolumeControls, IBridgeAdvanced
t.Start();
}
#region IRoutingSink Members
#region IRoutingSinkWithFeedback Members
/// <summary>
/// Typically called by the discovery routing algorithm.

View file

@ -15,7 +15,7 @@ namespace PepperDash.Essentials.Devices.Common.Displays;
/// <summary>
/// Represents a mock display device for testing and simulation purposes.
/// </summary>
public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeAdvanced, IHasInputs<string>, IRoutingSinkWithSwitchingWithInputPort, IHasPowerControlWithFeedback
public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeAdvanced, IHasInputs<string>, IHasPowerControlWithFeedback
{
/// <inheritdoc />
public ISelectableItems<string> Inputs { get; private set; }

View file

@ -7,7 +7,7 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// Abstract base class for two-way display devices that provide feedback capabilities.
/// Extends DisplayBase with routing feedback and power control feedback functionality.
/// </summary>
public abstract class TwoWayDisplayBase : DisplayBase, IRoutingFeedback, IHasPowerControlWithFeedback
public abstract class TwoWayDisplayBase : DisplayBase, IHasPowerControlWithFeedback
{
/// <summary>
/// Gets feedback for the current input selection on the display.

View file

@ -12,7 +12,7 @@ namespace PepperDash.Essentials.Devices.Common.Generic;
/// <summary>
/// Represents a GenericSink
/// </summary>
public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputPort, ICurrentSources
public class GenericSink : EssentialsDevice, IRoutingSinkWithFeedback
{
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, IRoutingSource> CurrentSources { get; private set; }
@ -104,43 +104,13 @@ public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputP
/// </summary>
public RoutingPortCollection<RoutingInputPort> InputPorts { get; private set; }
/// <summary>
/// Gets or sets the CurrentSourceInfoKey
/// </summary>
public string CurrentSourceInfoKey { get; set; }
private SourceListItem _currentSource;
/// <summary>
/// Gets or sets the CurrentSourceInfo
/// </summary>
public SourceListItem CurrentSourceInfo
{
get => _currentSource;
set
{
if (value == _currentSource)
{
return;
}
CurrentSourceChange?.Invoke(_currentSource, ChangeType.WillChange);
_currentSource = value;
CurrentSourceChange?.Invoke(_currentSource, ChangeType.DidChange);
}
}
/// <summary>
/// Gets the current input port
/// </summary>
public RoutingInputPort CurrentInputPort => InputPorts[0];
/// <summary>
/// Event fired when the current source changes
/// </summary>
public event SourceInfoChangeHandler CurrentSourceChange;
/// <inheritdoc />
public event InputChangedEventHandler InputChanged;

View file

@ -15,7 +15,7 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec;
/// <summary>
/// Class representing a BlueJeans soft codec running on an in-room PC.
/// </summary>
public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink
public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSinkWithFeedback
{
/// <summary>
@ -29,6 +29,12 @@ public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink
/// </summary>
public RoutingInputPort CurrentInputPort => AnyVideoIn;
/// <inheritdoc/>
public event InputChangedEventHandler InputChanged;
/// <inheritdoc/>
public void ExecuteSwitch(object inputSelector) { }
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, IRoutingSource> CurrentSources { get; private set; }
@ -170,16 +176,7 @@ public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink
}
// store the name and UI info for routes
if (item.SourceKey == "none")
{
CurrentSourceInfoKey = routeKey;
CurrentSourceInfo = null;
}
else if (item.SourceKey != null)
{
CurrentSourceInfoKey = routeKey;
CurrentSourceInfo = item;
}
// CurrentSourceInfo tracking removed in v3 interface consolidation
// report back when done
if (successCallback != null)
@ -196,9 +193,9 @@ public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink
/// <returns></returns>
bool DoRoute(SourceRouteListItem route)
{
IRoutingSink dest = null;
IRoutingSinkWithFeedback dest = null;
dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSink;
dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSinkWithFeedback;
if (dest == null)
{
@ -227,42 +224,5 @@ public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink
#region IHasCurrentSourceInfoChange Members
/// <inheritdoc />
public string CurrentSourceInfoKey { get; set; }
/// <summary>
/// The SourceListItem last run - containing names and icons
/// </summary>
public SourceListItem CurrentSourceInfo
{
get { return _CurrentSourceInfo; }
set
{
if (value == _CurrentSourceInfo) return;
var handler = CurrentSourceChange;
// remove from in-use tracker, if so equipped
if (_CurrentSourceInfo != null && _CurrentSourceInfo.SourceDevice is IInUseTracking)
(_CurrentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.RemoveUser(this, "control");
if (handler != null)
handler(_CurrentSourceInfo, ChangeType.WillChange);
_CurrentSourceInfo = value;
// add to in-use tracking
if (_CurrentSourceInfo != null && _CurrentSourceInfo.SourceDevice is IInUseTracking)
(_CurrentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control");
if (handler != null)
handler(_CurrentSourceInfo, ChangeType.DidChange);
}
}
SourceListItem _CurrentSourceInfo;
/// <inheritdoc />
public event SourceInfoChangeHandler CurrentSourceChange;
#endregion
}

View file

@ -12,7 +12,7 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec;
/// <summary>
/// Represents a GenericSoftCodec
/// </summary>
public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSinkWithSwitchingWithInputPort
public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSinkWithFeedback
{
private RoutingInputPort _currentInputPort;
@ -141,46 +141,11 @@ public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSinkWi
/// </summary>
public RoutingPortCollection<RoutingInputPort> InputPorts { get; private set; }
/// <summary>
/// <summary>
/// Gets or sets the OutputPorts
/// </summary>
public RoutingPortCollection<RoutingOutputPort> OutputPorts { get; private set; }
/// <summary>
/// Gets or sets the CurrentSourceInfoKey
/// </summary>
public string CurrentSourceInfoKey { get; set; }
/// <summary>
/// Gets or sets the CurrentSourceInfo
/// </summary>
public SourceListItem CurrentSourceInfo
{
get
{
return _CurrentSourceInfo;
}
set
{
if (value == _CurrentSourceInfo) return;
var handler = CurrentSourceChange;
if (handler != null)
handler(_CurrentSourceInfo, ChangeType.WillChange);
_CurrentSourceInfo = value;
if (handler != null)
handler(_CurrentSourceInfo, ChangeType.DidChange);
}
}
SourceListItem _CurrentSourceInfo;
/// <summary>
/// Event fired when the current source changes
/// </summary>
public event SourceInfoChangeHandler CurrentSourceChange;
/// <summary>
/// Event fired when the input changes

View file

@ -27,7 +27,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec;
/// Base class for video codecs. Contains common properties, methods, and feedback for video codecs.
/// Also contains the logic to link commonly implemented interfaces to the API bridge.
/// </summary>
public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutputs,
public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingMidpoint,
IUsageTracking, ICodecCallControls, IHasContentSharing, ICodecAudio, IVideoCodecInfo, IBridgeAdvanced, IHasStandbyMode, IHasReady
{
private const int XSigEncoding = 28591;
@ -302,7 +302,7 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
#endregion
#region IRoutingInputsOutputs Members
#region IRoutingMidpoint Members
/// <summary>
/// Gets or sets the InputPorts

View file

@ -0,0 +1,72 @@
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.AppServer;
using PepperDash.Essentials.AppServer.Messengers;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Routing;
using System.Linq;
using DisplayBase = PepperDash.Essentials.Devices.Common.Displays.DisplayBase;
namespace PepperDash.Essentials.Room.MobileControl
{
/// <summary>
/// Represents a DisplayBaseMessenger
/// </summary>
public class IRoutingSinkWithFeedbackMessenger : MessengerBase
{
private readonly IRoutingSinkWithFeedback display;
/// <summary>
/// Create an instance of the <see cref="IRoutingSinkWithFeedbackMessenger"/> class.
/// </summary>
/// <param name="key"></param>
/// <param name="messagePath"></param>
/// <param name="device"></param>
public IRoutingSinkWithFeedbackMessenger(string key, string messagePath, IRoutingSinkWithFeedback device) : base(key, messagePath, device)
{
display = device;
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
/*AddAction("/powerOn", (id, content) => display.PowerOn());
AddAction("/powerOff", (id, content) => display.PowerOff());
AddAction("/powerToggle", (id, content) => display.PowerToggle());*/
AddAction("/inputSelect", (id, content) =>
{
var s = content.ToObject<MobileControlSimpleContent<string>>();
var inputPort = display.InputPorts.FirstOrDefault(i => i.Key == s.Value);
if (inputPort == null)
{
this.LogWarning("No input named {inputName} found for {deviceKey}", s, display.Key);
return;
}
display.ExecuteSwitch(inputPort.Selector);
});
AddAction("/inputs", (id, content) =>
{
var inputsList = display.InputPorts.Select(p => p.Key).ToList();
var messageObject = new MobileControlMessage
{
Type = MessagePath + "/inputs",
Content = JToken.FromObject(new
{
inputKeys = inputsList,
})
};
AppServerController.SendMessageObject(messageObject);
});
}
}
}

View file

@ -12,17 +12,17 @@ namespace PepperDash.Essentials.Room.MobileControl
/// <summary>
/// Represents a DisplayBaseMessenger
/// </summary>
public class IRoutingSinkWithSwitchingMessenger : MessengerBase
public class IRoutingSinkWithFeedbackMessenger : MessengerBase
{
private readonly IRoutingSinkWithSwitching display;
private readonly IRoutingSinkWithFeedback display;
/// <summary>
/// Create an instance of the <see cref="IRoutingSinkWithSwitchingMessenger"/> class.
/// Create an instance of the <see cref="IRoutingSinkWithFeedbackMessenger"/> class.
/// </summary>
/// <param name="key"></param>
/// <param name="messagePath"></param>
/// <param name="device"></param>
public IRoutingSinkWithSwitchingMessenger(string key, string messagePath, IRoutingSinkWithSwitching device) : base(key, messagePath, device)
public IRoutingSinkWithFeedbackMessenger(string key, string messagePath, IRoutingSinkWithFeedback device) : base(key, messagePath, device)
{
display = device;
}

View file

@ -1,76 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Represents a IHasCurrentSourceInfoMessenger
/// </summary>
public class IHasCurrentSourceInfoMessenger : MessengerBase
{
private readonly IHasCurrentSourceInfoChange sourceDevice;
public IHasCurrentSourceInfoMessenger(string key, string messagePath, IHasCurrentSourceInfoChange device) : base(key, messagePath, device as IKeyName)
{
sourceDevice = device;
}
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/currentSourceInfoStatus", (id, content) => SendFullStatus(id));
sourceDevice.CurrentSourceChange += (sender, e) =>
{
switch (e)
{
case ChangeType.DidChange:
{
PostStatusMessage(JToken.FromObject(new
{
currentSourceKey = string.IsNullOrEmpty(sourceDevice.CurrentSourceInfoKey) ? string.Empty : sourceDevice.CurrentSourceInfoKey,
currentSource = sourceDevice.CurrentSourceInfo
}));
break;
}
}
};
}
private void SendFullStatus(string id = null)
{
var message = new CurrentSourceStateMessage
{
CurrentSourceKey = sourceDevice.CurrentSourceInfoKey,
CurrentSource = sourceDevice.CurrentSourceInfo
};
PostStatusMessage(message, id);
}
}
/// <summary>
/// Represents a CurrentSourceStateMessage
/// </summary>
public class CurrentSourceStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Gets or sets the CurrentSourceKey
/// </summary>
[JsonProperty("currentSourceKey", NullValueHandling = NullValueHandling.Ignore)]
public string CurrentSourceKey { get; set; }
/// <summary>
/// Gets or sets the CurrentSource
/// </summary>
[JsonProperty("currentSource")]
public SourceListItem CurrentSource { get; set; }
}
}

View file

@ -1,267 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Messenger for devices that implment IMatrixRouting
/// </summary>
public class IMatrixRoutingMessenger : MessengerBase
{
private readonly IMatrixRouting matrixDevice;
/// <summary>
/// Initializes a new instance of the <see cref="IMatrixRoutingMessenger"/> class.
/// </summary>
/// <param name="key"></param>
/// <param name="messagePath"></param>
/// <param name="device"></param>
public IMatrixRoutingMessenger(string key, string messagePath, IMatrixRouting device) : base(key, messagePath, device as IKeyName)
{
matrixDevice = device;
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/matrixStatus", (id, content) => SendFullStatus(id));
AddAction("/route", (id, content) =>
{
var request = content.ToObject<MatrixRouteRequest>();
matrixDevice.Route(request.InputKey, request.OutputKey, request.RouteType);
});
foreach (var output in matrixDevice.OutputSlots)
{
var key = output.Key;
var outputSlot = output.Value;
outputSlot.OutputSlotChanged += (sender, args) =>
{
PostStatusMessage(JToken.FromObject(new
{
outputs = matrixDevice.OutputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingOutput(kvp.Value))
}));
};
}
foreach (var input in matrixDevice.InputSlots)
{
var key = input.Key;
var inputSlot = input.Value;
inputSlot.VideoSyncChanged += (sender, args) =>
{
PostStatusMessage(JToken.FromObject(new
{
inputs = matrixDevice.InputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingInput(kvp.Value))
}));
};
inputSlot.IsOnline.OutputChange += (sender, args) =>
{
PostStatusMessage(JToken.FromObject(new
{
inputs = matrixDevice.InputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingInput(kvp.Value))
}));
};
}
}
private void SendFullStatus(string id = null)
{
try
{
Debug.LogMessage(LogEventLevel.Verbose, "InputCount: {inputCount}, OutputCount: {outputCount}", this, matrixDevice.InputSlots.Count, matrixDevice.OutputSlots.Count);
var message = new MatrixStateMessage
{
Outputs = matrixDevice.OutputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingOutput(kvp.Value)),
Inputs = matrixDevice.InputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingInput(kvp.Value)),
};
PostStatusMessage(message, id);
}
catch (Exception e)
{
Debug.LogMessage(e, "Exception Getting full status: {@exception}", this, e);
}
}
}
/// <summary>
/// Represents a MatrixStateMessage
/// </summary>
public class MatrixStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Gets or sets the Outputs
/// </summary>
[JsonProperty("outputs")]
public Dictionary<string, RoutingOutput> Outputs;
/// <summary>
/// Gets or sets the Inputs
/// </summary>
[JsonProperty("inputs")]
public Dictionary<string, RoutingInput> Inputs;
}
/// <summary>
/// Represents a RoutingInput
/// </summary>
public class RoutingInput : IKeyName
{
private IRoutingInputSlot _input;
/// <summary>
/// Gets the TxDeviceKey of the input slot
/// </summary>
[JsonProperty("txDeviceKey", NullValueHandling = NullValueHandling.Ignore)]
public string TxDeviceKey => _input?.TxDeviceKey;
/// <summary>
/// Gets the SlotNumber of the input slot
///
/// </summary>
[JsonProperty("slotNumber", NullValueHandling = NullValueHandling.Ignore)]
public int? SlotNumber => _input?.SlotNumber;
/// <summary>
/// Gets the SupportedSignalTypes of the input slot
/// </summary>
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
[JsonProperty("supportedSignalTypes", NullValueHandling = NullValueHandling.Ignore)]
public eRoutingSignalType? SupportedSignalTypes => _input?.SupportedSignalTypes;
/// <summary>
/// Gets the Name of the input slot
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name => _input?.Name;
/// <summary>
/// Gets the IsOnline of the input slot
///
/// </summary>
[JsonProperty("isOnline", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsOnline => _input?.IsOnline.BoolValue;
/// <summary>
/// Gets the VideoSyncDetected of the input slot
/// </summary>
[JsonProperty("videoSyncDetected", NullValueHandling = NullValueHandling.Ignore)]
public bool? VideoSyncDetected => _input?.VideoSyncDetected;
/// <summary>
/// Gets the Key of the input slot
/// </summary>
[JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)]
public string Key => _input?.Key;
/// <summary>
/// Initializes a new instance of the <see cref="RoutingInput"/> class.
/// </summary>
/// <param name="input"></param>
public RoutingInput(IRoutingInputSlot input)
{
_input = input;
}
}
/// <summary>
/// Represents a RoutingOutput
/// </summary>
public class RoutingOutput : IKeyName
{
private IRoutingOutputSlot _output;
/// <summary>
/// Initializes a new instance of the <see cref="RoutingOutput"/> class.
/// </summary>
/// <param name="output"></param>
public RoutingOutput(IRoutingOutputSlot output)
{
_output = output;
}
/// <summary>
/// Gets the RxDeviceKey of the output slot
/// </summary>
[JsonProperty("rxDeviceKey")]
public string RxDeviceKey => _output.RxDeviceKey;
/// <summary>
/// Gets the CurrentRoutes of the output slot
/// </summary>
[JsonProperty("currentRoutes")]
public Dictionary<string, RoutingInput> CurrentRoutes => _output.CurrentRoutes.ToDictionary(kvp => kvp.Key.ToString(), kvp => new RoutingInput(kvp.Value));
/// <summary>
/// Gets the SlotNumber of the output slot
/// </summary>
[JsonProperty("slotNumber")]
public int SlotNumber => _output.SlotNumber;
/// <summary>
/// Gets the SupportedSignalTypes of the output slot
/// </summary>
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
[JsonProperty("supportedSignalTypes")]
public eRoutingSignalType SupportedSignalTypes => _output.SupportedSignalTypes;
/// <summary>
/// Gets the Name of the output slot
/// </summary>
[JsonProperty("name")]
public string Name => _output.Name;
/// <summary>
/// Gets the Key of the output slot
/// </summary>
[JsonProperty("key")]
public string Key => _output.Key;
}
/// <summary>
/// Represents a MatrixRouteRequest
/// </summary>
public class MatrixRouteRequest
{
/// <summary>
/// Gets or sets the OutputKey
/// </summary>
[JsonProperty("outputKey")]
public string OutputKey { get; set; }
/// <summary>
/// Gets or sets the InputKey
/// </summary>
[JsonProperty("inputKey")]
public string InputKey { get; set; }
/// <summary>
/// Gets or sets the RouteType
/// </summary>
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
[JsonProperty("routeType")]
public eRoutingSignalType RouteType { get; set; }
}
}

View file

@ -0,0 +1,95 @@
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Messenger for devices that implement IRoutingMidpointWithFeedback
/// </summary>
public class IRoutingMidpointWithFeedbackMessenger : MessengerBase
{
private readonly IRoutingMidpointWithFeedback _device;
public IRoutingMidpointWithFeedbackMessenger(string key, string messagePath, IRoutingMidpointWithFeedback device)
: base(key, messagePath, device as IKeyName)
{
_device = device;
}
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/route", (id, content) =>
{
var request = content.ToObject<MidpointRouteRequest>();
_device.ExecuteSwitch(request.InputSelector, request.OutputSelector, request.SignalType);
});
AddAction("/clearRoute", (id, content) =>
{
var request = content.ToObject<MidpointClearRouteRequest>();
_device.ClearRoute(request.OutputSelector, request.SignalType);
});
_device.RouteChanged += OnRouteChanged;
}
private void OnRouteChanged(IRoutingMidpointWithFeedback midpoint, RouteSwitchDescriptor newRoute)
{
PostStatusMessage(JToken.FromObject(new
{
currentRoutes = _device.CurrentRoutes.Select(r => new
{
inputPort = r.InputPort?.Key,
outputPort = r.OutputPort?.Key
})
}));
}
private void SendFullStatus(string id = null)
{
var message = JToken.FromObject(new
{
inputPorts = _device.InputPorts.Select(p => new { key = p.Key }),
outputPorts = _device.OutputPorts.Select(p => new { key = p.Key }),
currentRoutes = _device.CurrentRoutes.Select(r => new
{
inputPort = r.InputPort?.Key,
outputPort = r.OutputPort?.Key
})
});
PostStatusMessage(message, id);
}
}
public class MidpointRouteRequest
{
[JsonProperty("inputSelector")]
public object InputSelector { get; set; }
[JsonProperty("outputSelector")]
public object OutputSelector { get; set; }
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
[JsonProperty("signalType")]
public eRoutingSignalType SignalType { get; set; }
}
public class MidpointClearRouteRequest
{
[JsonProperty("outputSelector")]
public object OutputSelector { get; set; }
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
[JsonProperty("signalType")]
public eRoutingSignalType SignalType { get; set; }
}
}

View file

@ -116,9 +116,9 @@ namespace PepperDash.Essentials
// ── Displays ─────────────────────────────────────────────────────────────
new MessengerFactoryEntry(
typeof(IRoutingSinkWithSwitching),
(d, mp, ck) => new IRoutingSinkWithSwitchingMessenger(
$"{d.Key}-displayBase-{ck}", mp, (IRoutingSinkWithSwitching)d)
typeof(IRoutingSinkWithFeedback),
(d, mp, ck) => new IRoutingSinkWithFeedbackMessenger(
$"{d.Key}-displayBase-{ck}", mp, (IRoutingSinkWithFeedback)d)
),
new MessengerFactoryEntry(
typeof(IDisplayCurrentInput),
@ -337,9 +337,9 @@ namespace PepperDash.Essentials
// ── Matrix routing ────────────────────────────────────────────────────────
// Preserving original key format (no controller key suffix)
new MessengerFactoryEntry(
typeof(IMatrixRouting),
(d, mp, _) => new IMatrixRoutingMessenger(
$"{d.Key}-matrixRouting", mp, (IMatrixRouting)d)
typeof(IRoutingMidpointWithFeedback),
(d, mp, _) => new IRoutingMidpointWithFeedbackMessenger(
$"{d.Key}-matrixRouting", mp, (IRoutingMidpointWithFeedback)d)
),
// ── Environmental sensors ─────────────────────────────────────────────────

View file

@ -140,9 +140,6 @@ namespace PepperDash.Essentials.RoomBridges
if (Room is IRunDefaultPresentRoute defaultRoom)
AddAction("/defaultsource", (id, content) => defaultRoom.RunDefaultPresentRoute());
if (Room is IHasCurrentSourceInfoChange sscRoom)
sscRoom.CurrentSourceChange += Room_CurrentSingleSourceChange;
if (Room is IPrivacy privacyRoom)
{
@ -353,9 +350,9 @@ namespace PepperDash.Essentials.RoomBridges
string shareText;
bool isSharing;
if (Room is IHasCurrentSourceInfoChange srcInfoRoom && Room is IHasVideoCodec vcRoom && vcRoom.VideoCodec.SharingContentIsOnFeedback.BoolValue && srcInfoRoom.CurrentSourceInfo != null)
if (Room is IHasVideoCodec vcRoom && vcRoom.VideoCodec.SharingContentIsOnFeedback.BoolValue)
{
shareText = srcInfoRoom.CurrentSourceInfo.PreferredName;
shareText = "Sharing";
isSharing = true;
}
else
@ -470,18 +467,7 @@ namespace PepperDash.Essentials.RoomBridges
}
private void Room_CurrentSingleSourceChange(SourceListItem info, ChangeType type)
{
/* Example message
* {
"type":"/room/status",
"content": {
"selectedSourceKey": "off",
}
}
*/
}
/// <summary>
/// Sends the full status of the room to the server
@ -512,7 +498,7 @@ namespace PepperDash.Essentials.RoomBridges
{
this.LogVerbose("GetFullStatus");
var sourceKey = room is IHasCurrentSourceInfoChange ? (room as IHasCurrentSourceInfoChange).CurrentSourceInfoKey : null;
var sourceKey = (string)null;
var volumes = new Dictionary<string, Volume>();
if (room is IHasCurrentVolumeControls rmVc)