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

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