diff --git a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs index 3f770f91..afe3ef24 100644 --- a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs @@ -169,9 +169,15 @@ namespace PepperDash.Essentials.Core [JsonProperty("sourceKey")] public string SourceKey { get; set; } + [JsonProperty("sourcePortKey")] + public string SourcePortKey { get; set; } + [JsonProperty("destinationKey")] public string DestinationKey { get; set; } + [JsonProperty("destinationPortKey")] + public string DestinationPortKey { get; set; } + [JsonProperty("type")] public eRoutingSignalType Type { get; set; } } diff --git a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj index c38c0fbd..79891444 100644 --- a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj +++ b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj @@ -23,6 +23,7 @@ + diff --git a/src/PepperDash.Essentials.Core/Room/Interfaces.cs b/src/PepperDash.Essentials.Core/Room/Interfaces.cs index 640dc91f..eb0b6f25 100644 --- a/src/PepperDash.Essentials.Core/Room/Interfaces.cs +++ b/src/PepperDash.Essentials.Core/Room/Interfaces.cs @@ -5,6 +5,7 @@ using System.Text; using Crestron.SimplSharp; using PepperDash.Core; +using PepperDash.Essentials.Core.Routing; namespace PepperDash.Essentials.Core @@ -41,6 +42,8 @@ namespace PepperDash.Essentials.Core void RunRouteAction(string routeKey, string sourceListKey); void RunRouteAction(string routeKey, string sourceListKey, Action successCallback); + + RoutingFeedbackManager RoutingFeedbackManager { get; } } /// @@ -49,6 +52,8 @@ namespace PepperDash.Essentials.Core public interface IRunDirectRouteAction { void RunDirectRoute(string sourceKey, string destinationKey, eRoutingSignalType type = eRoutingSignalType.AudioVideo); + + RoutingFeedbackManager RoutingFeedbackManager { get; } } /// diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs index 4d33ed4a..03c59bf0 100644 --- a/src/PepperDash.Essentials.Core/Routing/Extensions.cs +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -169,18 +169,17 @@ namespace PepperDash.Essentials.Core RoutingOutputPort outputPortToUse, List alreadyCheckedDevices, eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable) { - cycle++; Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", null, cycle, source.Key, destination.Key); RoutingInputPort goodInputPort = null; - var destDevInputTies = TieLineCollection.Default.Where(t => + var destinationTieLines = TieLineCollection.Default.Where(t => t.DestinationPort.ParentDevice == destination && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo))); // find a direct tie - var directTie = destDevInputTies.FirstOrDefault( + var directTie = destinationTieLines.FirstOrDefault( t => t.DestinationPort.ParentDevice == destination && t.SourcePort.ParentDevice == source); if (directTie != null) // Found a tie directly to the source @@ -193,7 +192,7 @@ namespace PepperDash.Essentials.Core // No direct tie? Run back out on the inputs' attached devices... // Only the ones that are routing devices - var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); + var attachedMidpoints = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration if (alreadyCheckedDevices == null) diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs index c51ed12f..773fab77 100644 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs @@ -7,7 +7,7 @@ namespace PepperDash.Essentials.Core /// public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange { - + RoutingInputPort CurrentInputPort { get; } } /*/// diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithFeedback.cs index c7e99746..d3d3e600 100644 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithFeedback.cs +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithFeedback.cs @@ -4,14 +4,13 @@ using System.Collections.Generic; namespace PepperDash.Essentials.Core { + /// /// For fixed-source endpoint devices /// public interface IRoutingSinkWithFeedback : IRoutingSinkWithSwitching { - RouteSwitchDescriptor CurrentRoute { get; } - - event EventHandler InputChanged; + } /* /// diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs index b31bd973..68e4632c 100644 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs @@ -2,13 +2,17 @@ namespace PepperDash.Essentials.Core { + public delegate void InputChangedEventHandler(IRoutingSinkWithSwitching destination, RoutingInputPort currentPort); + /// /// Endpoint device like a display, that selects inputs /// public interface IRoutingSinkWithSwitching : IRoutingSink { void ExecuteSwitch(object inputSelector); - } + + event InputChangedEventHandler InputChanged; + } /* /// /// Endpoint device like a display, that selects inputs diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingWithFeedback.cs index c4abf0c6..dcf4e423 100644 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingWithFeedback.cs +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingWithFeedback.cs @@ -3,6 +3,7 @@ using System; namespace PepperDash.Essentials.Core { + public delegate void RouteChangedEventHandler(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute); /// /// Defines an IRouting with a feedback event /// @@ -10,6 +11,6 @@ namespace PepperDash.Essentials.Core { List CurrentRoutes { get; } - event EventHandler RoutingChanged; + event RouteChangedEventHandler RouteChanged; } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs b/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs new file mode 100644 index 00000000..bf796d5b --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs @@ -0,0 +1,165 @@ +using Org.BouncyCastle.Crypto.Prng; +using PepperDash.Core; +using PepperDash.Essentials.Core.Config; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace PepperDash.Essentials.Core.Routing +{ + public class RoutingFeedbackManager:EssentialsDevice + { + public RoutingFeedbackManager(string key, string name): base(key, name) + { + AddPostActivationAction(SubscribeForMidpointFeedback); + AddPostActivationAction(SubscribeForSinkFeedback); + } + + private void SubscribeForMidpointFeedback() + { + var midpointDevices = DeviceManager.AllDevices.OfType(); + + foreach (var device in midpointDevices) + { + device.RouteChanged += HandleMidpointUpdate; + } + } + + private void SubscribeForSinkFeedback() + { + var sinkDevices = DeviceManager.AllDevices.OfType(); + + foreach (var device in sinkDevices) + { + device.InputChanged += HandleSinkUpdate; + } + } + + private void HandleMidpointUpdate(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute) + { + var devices = DeviceManager.AllDevices.OfType(); + + foreach(var device in devices) + { + UpdateDestination(device, device.CurrentInputPort); + } + } + + private void HandleSinkUpdate(IRoutingSinkWithSwitching sender, RoutingInputPort currentInputPort) + { + UpdateDestination(sender, currentInputPort); + } + + private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort) + { + var tieLines = TieLineCollection.Default; + + var firstTieLine = tieLines.FirstOrDefault(tl => tl.DestinationPort.Key == inputPort.Key); + + if (firstTieLine == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No tieline found for inputPort {inputPort}. Clearing current source", this, inputPort); + + destination.CurrentSourceInfo = null; + destination.CurrentSourceInfoKey = string.Empty; + return; + } + + var sourceTieLine = GetRootTieLine(firstTieLine); + + if (sourceTieLine == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route found to source for inputPort {inputPort}. Clearing current source", this, inputPort); + + destination.CurrentSourceInfo = null; + destination.CurrentSourceInfoKey = string.Empty; + } + + + // Does not handle combinable scenarios or other scenarios where a display might be part of multiple rooms yet. + var room = DeviceManager.AllDevices.OfType().FirstOrDefault((r) => { + if(r is IHasMultipleDisplays roomMultipleDisplays) + { + return roomMultipleDisplays.Displays.Any(d => d.Value.Key == destination.Key); + } + + if(r is IHasDefaultDisplay roomDefaultDisplay) + { + return roomDefaultDisplay.DefaultDisplay.Key == destination.Key; + } + + return false; + }) ; + + if(room == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No room found for display {destination}", this, destination.Key); + return; + } + + var sourceList = ConfigReader.ConfigObject.GetSourceListForKey(room.SourceListKey); + + if (sourceList == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No source list found for source list key {key}. Unable to find source for tieLine {sourceTieLine}", this, room.SourceListKey, sourceTieLine); + return; + } + + var sourceListItem = sourceList.FirstOrDefault(sli => sli.Value.SourceKey == sourceTieLine.SourcePort.ParentDevice.Key); + + var source = sourceListItem.Value; + + if (source == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No source found for device {key}. Clearing current source on {destination}", this, sourceTieLine.SourcePort.ParentDevice.Key, destination); + + destination.CurrentSourceInfo = null; + destination.CurrentSourceInfoKey = string.Empty; + return; + } + + destination.CurrentSourceInfo = source; + destination.CurrentSourceInfoKey = source.SourceKey; + } + + private TieLine GetRootTieLine(TieLine tieLine) + { + TieLine nextTieLine = null; + + if(tieLine.SourcePort.ParentDevice is IRoutingWithFeedback midpoint) + { + var currentRoute = midpoint.CurrentRoutes.FirstOrDefault(route => route.OutputPort.Key == tieLine.SourcePort.Key); + + if(currentRoute == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route through midpoint {midpoint} for outputPort {outputPort}", this, midpoint.Key, tieLine.SourcePort); + return null; + } + + nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => tl.DestinationPort.Key == currentRoute.InputPort.Key); + + if(tieLine != null) + { + return GetRootTieLine(nextTieLine); + } + + return tieLine; + } + + if(tieLine.SourcePort.ParentDevice is IRoutingSource) //end of the chain + { + return tieLine; + } + + nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => tl.SourcePort.Key == tieLine.SourcePort.Key); + + if(nextTieLine != null) + { + return GetRootTieLine(nextTieLine); + } + + return nextTieLine; + } + } +} diff --git a/src/PepperDash.Essentials.Core/Routing/TieLine.cs b/src/PepperDash.Essentials.Core/Routing/TieLine.cs index 25bf4ea3..65804eb3 100644 --- a/src/PepperDash.Essentials.Core/Routing/TieLine.cs +++ b/src/PepperDash.Essentials.Core/Routing/TieLine.cs @@ -88,7 +88,7 @@ namespace PepperDash.Essentials.Core public override string ToString() { - return string.Format("Tie line: [{0}]{1} --> [{2}]{3}", SourcePort.ParentDevice.Key, SourcePort.Key, + return string.Format("Tie line: {0}:{1} --> {2}:{3}", SourcePort.ParentDevice.Key, SourcePort.Key, DestinationPort.ParentDevice.Key, DestinationPort.Key); } } diff --git a/src/PepperDash.Essentials.Core/Routing/TieLineConfig.cs b/src/PepperDash.Essentials.Core/Routing/TieLineConfig.cs index 1728c20f..922cc61d 100644 --- a/src/PepperDash.Essentials.Core/Routing/TieLineConfig.cs +++ b/src/PepperDash.Essentials.Core/Routing/TieLineConfig.cs @@ -30,7 +30,7 @@ namespace PepperDash.Essentials.Core.Config /// null if config data does not match ports, cards or devices public TieLine GetTieLine() { - Debug.LogMessage(LogEventLevel.Information, "Build TieLine: {0}", this); + Debug.LogMessage(LogEventLevel.Information, "Build TieLine: {0}",null, this); // Get the source device var sourceDev = DeviceManager.GetDeviceForKey(SourceKey) as IRoutingOutputs; if (sourceDev == null) @@ -48,68 +48,29 @@ namespace PepperDash.Essentials.Core.Config } //Get the source port - RoutingOutputPort sourceOutputPort = null; - //// If it's a card-based device, get the card and then the source port - //if (sourceDev is ICardPortsDevice) - //{ - // if (SourceCard == null) - // { - // LogError("Card missing from source device config"); - // return null; - // } - // sourceOutputPort = (sourceDev as ICardPortsDevice).GetChildOutputPort(SourceCard, SourcePort); - // if (sourceOutputPort == null) - // { - // LogError("Source card does not contain port"); - // return null; - // } - //} - //// otherwise it's a normal port device, get the source port - //else - //{ - sourceOutputPort = sourceDev.OutputPorts[SourcePort]; - if (sourceOutputPort == null) - { - LogError("Source does not contain port"); - return null; - } - //} + var sourceOutputPort = sourceDev.OutputPorts[SourcePort]; + if (sourceOutputPort == null) + { + LogError("Source does not contain port"); + return null; + } - //Get the Destination port - RoutingInputPort destinationInputPort = null; - //// If it's a card-based device, get the card and then the Destination port - //if (destDev is ICardPortsDevice) - //{ - // if (DestinationCard == null) - // { - // LogError("Card missing from destination device config"); - // return null; - // } - // destinationInputPort = (destDev as ICardPortsDevice).GetChildInputPort(DestinationCard, DestinationPort); - // if (destinationInputPort == null) - // { - // LogError("Destination card does not contain port"); - // return null; - // } - //} - //// otherwise it's a normal port device, get the Destination port - //else - //{ - destinationInputPort = destDev.InputPorts[DestinationPort]; - if (destinationInputPort == null) + //Get the Destination port + var destinationInputPort = destDev.InputPorts[DestinationPort]; + + if (destinationInputPort == null) { LogError("Destination does not contain port"); return null; - } - //} + } return new TieLine(sourceOutputPort, destinationInputPort); } void LogError(string msg) { - Debug.LogMessage(LogEventLevel.Debug, "WARNING: Cannot create tie line: {0}:\r {1}", msg, this); + Debug.LogMessage(LogEventLevel.Error, "WARNING: Cannot create tie line: {message}:\r {tieLineConfig}",null, msg, this); } public override string ToString() diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/DisplayBase.cs b/src/PepperDash.Essentials.Devices.Common/Displays/DisplayBase.cs index 8dfa2f2b..33d5632a 100644 --- a/src/PepperDash.Essentials.Devices.Common/Displays/DisplayBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/Displays/DisplayBase.cs @@ -20,6 +20,24 @@ namespace PepperDash.Essentials.Devices.Common.Displays , IWarmingCooling , IUsageTracking { + private RoutingInputPort _currentInputPort; + public RoutingInputPort CurrentInputPort + { + get + { + return _currentInputPort; + } + + private set + { + _currentInputPort = value; + + InputChanged?.Invoke(this, _currentInputPort); + } + } + + public event InputChangedEventHandler InputChanged; + public event SourceInfoChangeHandler CurrentSourceChange; public string CurrentSourceInfoKey { get; set; } diff --git a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSource.cs b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSource.cs index f4062c98..c34f7d12 100644 --- a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSource.cs +++ b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSource.cs @@ -14,7 +14,7 @@ using Serilog.Events; namespace PepperDash.Essentials.Devices.Common { - public class GenericSource : EssentialsDevice, IUiDisplayInfo, IRoutingOutputs, IUsageTracking + public class GenericSource : EssentialsDevice, IUiDisplayInfo, IRoutingSource, IUsageTracking { public uint DisplayUiType { get { return DisplayUiConstants.TypeNoControls; } } diff --git a/src/PepperDash.Essentials.Devices.Common/SetTopBox/IRSetTopBoxBase.cs b/src/PepperDash.Essentials.Devices.Common/SetTopBox/IRSetTopBoxBase.cs index 1728939d..9e334244 100644 --- a/src/PepperDash.Essentials.Devices.Common/SetTopBox/IRSetTopBoxBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/SetTopBox/IRSetTopBoxBase.cs @@ -20,7 +20,7 @@ using Serilog.Events; namespace PepperDash.Essentials.Devices.Common { [Description("Wrapper class for an IR Set Top Box")] - public class IRSetTopBoxBase : EssentialsBridgeableDevice, ISetTopBoxControls, IRoutingOutputs, IUsageTracking, IHasPowerControl, ITvPresetsProvider + public class IRSetTopBoxBase : EssentialsBridgeableDevice, ISetTopBoxControls, IRoutingSource, IUsageTracking, IHasPowerControl, ITvPresetsProvider { public IrOutputPortController IrPort { get; private set; } diff --git a/src/PepperDash.Essentials.Devices.Common/Sources/InRoomPc.cs b/src/PepperDash.Essentials.Devices.Common/Sources/InRoomPc.cs index 565956f4..b3d8fbee 100644 --- a/src/PepperDash.Essentials.Devices.Common/Sources/InRoomPc.cs +++ b/src/PepperDash.Essentials.Devices.Common/Sources/InRoomPc.cs @@ -8,7 +8,7 @@ using Serilog.Events; namespace PepperDash.Essentials.Devices.Common.Sources { - public class InRoomPc : EssentialsDevice, IHasFeedback, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking + public class InRoomPc : EssentialsDevice, IHasFeedback, IRoutingSource, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking { public uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } } public string IconName { get; set; } diff --git a/src/PepperDash.Essentials.Devices.Common/Sources/Laptop.cs b/src/PepperDash.Essentials.Devices.Common/Sources/Laptop.cs index 7aac6267..c2ffb726 100644 --- a/src/PepperDash.Essentials.Devices.Common/Sources/Laptop.cs +++ b/src/PepperDash.Essentials.Devices.Common/Sources/Laptop.cs @@ -8,7 +8,7 @@ using Serilog.Events; namespace PepperDash.Essentials.Devices.Common.Sources { - public class Laptop : EssentialsDevice, IHasFeedback, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking + public class Laptop : EssentialsDevice, IHasFeedback, IRoutingSource, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking { public uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } } public string IconName { get; set; } diff --git a/src/PepperDash.Essentials.Devices.Common/Streaming/AppleTV.cs b/src/PepperDash.Essentials.Devices.Common/Streaming/AppleTV.cs index 5c8f9163..c4a9b5a7 100644 --- a/src/PepperDash.Essentials.Devices.Common/Streaming/AppleTV.cs +++ b/src/PepperDash.Essentials.Devices.Common/Streaming/AppleTV.cs @@ -19,7 +19,7 @@ using Serilog.Events; namespace PepperDash.Essentials.Devices.Common { [Description("Wrapper class for an IR-Controlled AppleTV")] - public class AppleTV : EssentialsBridgeableDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingOutputs + public class AppleTV : EssentialsBridgeableDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingSource { public IrOutputPortController IrPort { get; private set; } public const string StandardDriverName = "Apple_AppleTV_4th_Gen_Essentials.ir"; diff --git a/src/PepperDash.Essentials.Devices.Common/Streaming/Roku.cs b/src/PepperDash.Essentials.Devices.Common/Streaming/Roku.cs index 4364610b..9d08f53a 100644 --- a/src/PepperDash.Essentials.Devices.Common/Streaming/Roku.cs +++ b/src/PepperDash.Essentials.Devices.Common/Streaming/Roku.cs @@ -15,7 +15,7 @@ using Serilog.Events; namespace PepperDash.Essentials.Devices.Common { [Description("Wrapper class for an IR-Controlled Roku")] - public class Roku2 : EssentialsDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingOutputs + public class Roku2 : EssentialsDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingSource { [Api] public IrOutputPortController IrPort { get; private set; } diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index af87158b..d2b7a2b9 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -14,6 +14,7 @@ using PepperDash.Essentials.Core.Web; using System; using System.Linq; using Serilog.Events; +using PepperDash.Essentials.Core.Routing; namespace PepperDash.Essentials { @@ -388,6 +389,8 @@ namespace PepperDash.Essentials // Build the processor wrapper class DeviceManager.AddDevice(new Core.Devices.CrestronProcessor("processor")); + DeviceManager.AddDevice(new RoutingFeedbackManager($"routingFeedbackManager", "Routing Feedback Manager")); + // Add global System Monitor device if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) {