mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-02-06 08:16:11 +00:00
feat: implement feedback manager
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace PepperDash.Essentials.Core
|
||||
/// </summary>
|
||||
public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange
|
||||
{
|
||||
|
||||
RoutingInputPort CurrentInputPort { get; }
|
||||
}
|
||||
|
||||
/*/// <summary>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
165
src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs
Normal file
165
src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; } }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user