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")]
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; }
}

View File

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

View File

@@ -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; }
}
/// <summary>
@@ -49,6 +52,8 @@ namespace PepperDash.Essentials.Core
public interface IRunDirectRouteAction
{
void RunDirectRoute(string sourceKey, string destinationKey, eRoutingSignalType type = eRoutingSignalType.AudioVideo);
RoutingFeedbackManager RoutingFeedbackManager { get; }
}
/// <summary>

View File

@@ -169,18 +169,17 @@ namespace PepperDash.Essentials.Core
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> 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)

View File

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

View File

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

View File

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

View File

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

View File

@@ -30,7 +30,7 @@ namespace PepperDash.Essentials.Core.Config
/// <returns>null if config data does not match ports, cards or devices</returns>
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()

View File

@@ -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; }

View File

@@ -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; } }

View File

@@ -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; }

View File

@@ -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; }

View File

@@ -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; }

View File

@@ -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";

View File

@@ -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; }

View File

@@ -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
{
@@ -386,6 +387,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)
{