feat: implement feedback manager

This commit is contained in:
Andrew Welker
2024-04-12 10:55:45 -05:00
parent ad122bf39f
commit 3852daec2c
19 changed files with 231 additions and 69 deletions

View File

@@ -169,9 +169,15 @@ namespace PepperDash.Essentials.Core
[JsonProperty("sourceKey")] [JsonProperty("sourceKey")]
public string SourceKey { get; set; } public string SourceKey { get; set; }
[JsonProperty("sourcePortKey")]
public string SourcePortKey { get; set; }
[JsonProperty("destinationKey")] [JsonProperty("destinationKey")]
public string DestinationKey { get; set; } public string DestinationKey { get; set; }
[JsonProperty("destinationPortKey")]
public string DestinationPortKey { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public eRoutingSignalType Type { get; set; } public eRoutingSignalType Type { get; set; }
} }

View File

@@ -23,6 +23,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="Routing\GenericExtensions.cs" /> <Compile Remove="Routing\GenericExtensions.cs" />
<Compile Remove="Routing\IRoutingSinkWithFeedback.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.20.42" /> <PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.20.42" />

View File

@@ -5,6 +5,7 @@ using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core
@@ -41,6 +42,8 @@ namespace PepperDash.Essentials.Core
void RunRouteAction(string routeKey, string sourceListKey); void RunRouteAction(string routeKey, string sourceListKey);
void RunRouteAction(string routeKey, string sourceListKey, Action successCallback); void RunRouteAction(string routeKey, string sourceListKey, Action successCallback);
RoutingFeedbackManager RoutingFeedbackManager { get; }
} }
/// <summary> /// <summary>
@@ -49,6 +52,8 @@ namespace PepperDash.Essentials.Core
public interface IRunDirectRouteAction public interface IRunDirectRouteAction
{ {
void RunDirectRoute(string sourceKey, string destinationKey, eRoutingSignalType type = eRoutingSignalType.AudioVideo); void RunDirectRoute(string sourceKey, string destinationKey, eRoutingSignalType type = eRoutingSignalType.AudioVideo);
RoutingFeedbackManager RoutingFeedbackManager { get; }
} }
/// <summary> /// <summary>

View File

@@ -169,18 +169,17 @@ namespace PepperDash.Essentials.Core
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices, RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices,
eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable) eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable)
{ {
cycle++; cycle++;
Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", null, cycle, source.Key, destination.Key); Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", null, cycle, source.Key, destination.Key);
RoutingInputPort goodInputPort = null; 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))); t.DestinationPort.ParentDevice == destination && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo)));
// find a direct tie // find a direct tie
var directTie = destDevInputTies.FirstOrDefault( var directTie = destinationTieLines.FirstOrDefault(
t => t.DestinationPort.ParentDevice == destination t => t.DestinationPort.ParentDevice == destination
&& t.SourcePort.ParentDevice == source); && t.SourcePort.ParentDevice == source);
if (directTie != null) // Found a tie directly to the 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... // No direct tie? Run back out on the inputs' attached devices...
// Only the ones that are routing 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 //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration
if (alreadyCheckedDevices == null) if (alreadyCheckedDevices == null)

View File

@@ -7,7 +7,7 @@ namespace PepperDash.Essentials.Core
/// </summary> /// </summary>
public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange
{ {
RoutingInputPort CurrentInputPort { get; }
} }
/*/// <summary> /*/// <summary>

View File

@@ -4,14 +4,13 @@ using System.Collections.Generic;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core
{ {
/// <summary> /// <summary>
/// For fixed-source endpoint devices /// For fixed-source endpoint devices
/// </summary> /// </summary>
public interface IRoutingSinkWithFeedback : IRoutingSinkWithSwitching public interface IRoutingSinkWithFeedback : IRoutingSinkWithSwitching
{ {
RouteSwitchDescriptor CurrentRoute { get; }
event EventHandler InputChanged;
} }
/* /// <summary> /* /// <summary>

View File

@@ -2,12 +2,16 @@
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core
{ {
public delegate void InputChangedEventHandler(IRoutingSinkWithSwitching destination, RoutingInputPort currentPort);
/// <summary> /// <summary>
/// Endpoint device like a display, that selects inputs /// Endpoint device like a display, that selects inputs
/// </summary> /// </summary>
public interface IRoutingSinkWithSwitching : IRoutingSink public interface IRoutingSinkWithSwitching : IRoutingSink
{ {
void ExecuteSwitch(object inputSelector); void ExecuteSwitch(object inputSelector);
event InputChangedEventHandler InputChanged;
} }
/* /// <summary> /* /// <summary>

View File

@@ -3,6 +3,7 @@ using System;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core
{ {
public delegate void RouteChangedEventHandler(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute);
/// <summary> /// <summary>
/// Defines an IRouting with a feedback event /// Defines an IRouting with a feedback event
/// </summary> /// </summary>
@@ -10,6 +11,6 @@ namespace PepperDash.Essentials.Core
{ {
List<RouteSwitchDescriptor> CurrentRoutes { get; } List<RouteSwitchDescriptor> CurrentRoutes { get; }
event EventHandler RoutingChanged; event RouteChangedEventHandler RouteChanged;
} }
} }

View File

@@ -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<IRoutingWithFeedback>();
foreach (var device in midpointDevices)
{
device.RouteChanged += HandleMidpointUpdate;
}
}
private void SubscribeForSinkFeedback()
{
var sinkDevices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitching>();
foreach (var device in sinkDevices)
{
device.InputChanged += HandleSinkUpdate;
}
}
private void HandleMidpointUpdate(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute)
{
var devices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitching>();
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<IEssentialsRoom>().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;
}
}
}

View File

@@ -88,7 +88,7 @@ namespace PepperDash.Essentials.Core
public override string ToString() 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); DestinationPort.ParentDevice.Key, DestinationPort.Key);
} }
} }

View File

@@ -30,7 +30,7 @@ namespace PepperDash.Essentials.Core.Config
/// <returns>null if config data does not match ports, cards or devices</returns> /// <returns>null if config data does not match ports, cards or devices</returns>
public TieLine GetTieLine() public TieLine GetTieLine()
{ {
Debug.LogMessage(LogEventLevel.Information, "Build TieLine: {0}", this); Debug.LogMessage(LogEventLevel.Information, "Build TieLine: {0}",null, this);
// Get the source device // Get the source device
var sourceDev = DeviceManager.GetDeviceForKey(SourceKey) as IRoutingOutputs; var sourceDev = DeviceManager.GetDeviceForKey(SourceKey) as IRoutingOutputs;
if (sourceDev == null) if (sourceDev == null)
@@ -48,68 +48,29 @@ namespace PepperDash.Essentials.Core.Config
} }
//Get the source port //Get the source port
RoutingOutputPort sourceOutputPort = null; var sourceOutputPort = sourceDev.OutputPorts[SourcePort];
//// 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) if (sourceOutputPort == null)
{ {
LogError("Source does not contain port"); LogError("Source does not contain port");
return null; return null;
} }
//}
//Get the Destination port //Get the Destination port
RoutingInputPort destinationInputPort = null; var destinationInputPort = destDev.InputPorts[DestinationPort];
//// 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) if (destinationInputPort == null)
{ {
LogError("Destination does not contain port"); LogError("Destination does not contain port");
return null; return null;
} }
//}
return new TieLine(sourceOutputPort, destinationInputPort); return new TieLine(sourceOutputPort, destinationInputPort);
} }
void LogError(string msg) 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() public override string ToString()

View File

@@ -20,6 +20,24 @@ namespace PepperDash.Essentials.Devices.Common.Displays
, IWarmingCooling , IWarmingCooling
, IUsageTracking , 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 event SourceInfoChangeHandler CurrentSourceChange;
public string CurrentSourceInfoKey { get; set; } public string CurrentSourceInfoKey { get; set; }

View File

@@ -14,7 +14,7 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common 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; } } public uint DisplayUiType { get { return DisplayUiConstants.TypeNoControls; } }

View File

@@ -20,7 +20,7 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common namespace PepperDash.Essentials.Devices.Common
{ {
[Description("Wrapper class for an IR Set Top Box")] [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; } public IrOutputPortController IrPort { get; private set; }

View File

@@ -8,7 +8,7 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Sources 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 uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } }
public string IconName { get; set; } public string IconName { get; set; }

View File

@@ -8,7 +8,7 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Sources 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 uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } }
public string IconName { get; set; } public string IconName { get; set; }

View File

@@ -19,7 +19,7 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common namespace PepperDash.Essentials.Devices.Common
{ {
[Description("Wrapper class for an IR-Controlled AppleTV")] [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 IrOutputPortController IrPort { get; private set; }
public const string StandardDriverName = "Apple_AppleTV_4th_Gen_Essentials.ir"; public const string StandardDriverName = "Apple_AppleTV_4th_Gen_Essentials.ir";

View File

@@ -15,7 +15,7 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common namespace PepperDash.Essentials.Devices.Common
{ {
[Description("Wrapper class for an IR-Controlled Roku")] [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] [Api]
public IrOutputPortController IrPort { get; private set; } public IrOutputPortController IrPort { get; private set; }

View File

@@ -14,6 +14,7 @@ using PepperDash.Essentials.Core.Web;
using System; using System;
using System.Linq; using System.Linq;
using Serilog.Events; using Serilog.Events;
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials namespace PepperDash.Essentials
{ {
@@ -386,6 +387,8 @@ namespace PepperDash.Essentials
// Build the processor wrapper class // Build the processor wrapper class
DeviceManager.AddDevice(new Core.Devices.CrestronProcessor("processor")); DeviceManager.AddDevice(new Core.Devices.CrestronProcessor("processor"));
DeviceManager.AddDevice(new RoutingFeedbackManager($"routingFeedbackManager", "Routing Feedback Manager"));
// Add global System Monitor device // Add global System Monitor device
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance)
{ {