diff --git a/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpoint.cs b/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpoint.cs index 74853223..8a6ef2f6 100644 --- a/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpoint.cs +++ b/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpoint.cs @@ -11,14 +11,15 @@ using Serilog.Events; namespace PepperDash.Essentials.Devices.Common.Routing; /// -/// A mock midpoint routing device (e.g. a matrix switcher) that implements +/// A mock midpoint routing device (e.g. a matrix switcher) that implements /// without any real hardware communication. Its input and output ports are configured via /// , each with a name, signal type, and physical port -/// (connection) type - so it can stand in for a real switching device (such as a StreamSync matrix) for -/// development and testing of routing logic. +/// (connection) type - so it can stand in for a real switching device (such as a DM chassis or a StreamSync +/// matrix) for development and testing of routing logic, including named-routing-slot UI that a bare +/// device cannot support. /// [Description("A mock routing midpoint (e.g. matrix switcher) device for testing routing logic without real hardware")] -public class MockRoutingMidpoint : EssentialsDevice, IRoutingMidpointWithFeedback +public class MockRoutingMidpoint : EssentialsDevice, IHasNamedRoutingSlots { /// /// The configuration properties for this device. @@ -37,6 +38,14 @@ public class MockRoutingMidpoint : EssentialsDevice, IRoutingMidpointWithFeedbac /// public event RouteChangedEventHandler RouteChanged; + private readonly Dictionary _outputSlotsByKey = new Dictionary(); + + /// + public IReadOnlyDictionary InputSlots { get; private set; } + + /// + public IReadOnlyDictionary OutputSlots { get; private set; } + /// /// Initializes a new instance of the class from a . /// @@ -56,14 +65,20 @@ public class MockRoutingMidpoint : EssentialsDevice, IRoutingMidpointWithFeedbac } /// - /// Builds the input and output ports from . Each port's Key and Selector - /// are both set to the configured port name, so selectors passed to and - /// can simply be looked up by matching against the port's Selector. + /// Builds the input and output ports (and their slot info) from + /// . Each port's Key and Selector are both set to the configured port + /// name, so selectors passed to and can simply be + /// looked up by matching against the port's Selector. Slot number is the 1-based position of the port + /// within its input/output list. /// private void BuildPorts() { try { + var inputSlots = new Dictionary(); + var outputSlots = new Dictionary(); + var slotNumber = 0; + foreach (var portConfig in PropertiesConfig.InputPorts) { if (string.IsNullOrEmpty(portConfig.Name)) @@ -74,8 +89,13 @@ public class MockRoutingMidpoint : EssentialsDevice, IRoutingMidpointWithFeedbac var port = new RoutingInputPort(portConfig.Name, portConfig.SignalType, portConfig.PortType, portConfig.Name, this); InputPorts.Add(port); + + slotNumber++; + inputSlots[portConfig.Name] = new MockRoutingSlotInfo( + portConfig.Name, portConfig.Label ?? portConfig.Name, slotNumber, portConfig.SignalType); } + slotNumber = 0; foreach (var portConfig in PropertiesConfig.OutputPorts) { if (string.IsNullOrEmpty(portConfig.Name)) @@ -86,8 +106,17 @@ public class MockRoutingMidpoint : EssentialsDevice, IRoutingMidpointWithFeedbac var port = new RoutingOutputPort(portConfig.Name, portConfig.SignalType, portConfig.PortType, portConfig.Name, this); OutputPorts.Add(port); + + slotNumber++; + var outputSlot = new MockRoutingOutputSlotInfo( + portConfig.Name, portConfig.Label ?? portConfig.Name, slotNumber, portConfig.SignalType); + _outputSlotsByKey[portConfig.Name] = outputSlot; + outputSlots[portConfig.Name] = outputSlot; } + InputSlots = inputSlots; + OutputSlots = outputSlots; + this.LogInformation("Built {inputCount} input port(s) and {outputCount} output port(s) for mock midpoint {key}", InputPorts.Count, OutputPorts.Count, Key); } @@ -121,6 +150,11 @@ public class MockRoutingMidpoint : EssentialsDevice, IRoutingMidpointWithFeedbac { this.LogInformation("Clearing route to output {output} on {key}", outputPort.Key, Key); + if (_outputSlotsByKey.TryGetValue(outputPort.Key, out var clearedSlot)) + { + clearedSlot.ClearRoute(signalType); + } + var clearedDescriptor = new RouteSwitchDescriptor(outputPort, null); RouteChanged?.Invoke(this, clearedDescriptor); return; @@ -137,6 +171,11 @@ public class MockRoutingMidpoint : EssentialsDevice, IRoutingMidpointWithFeedbac var descriptor = new RouteSwitchDescriptor(outputPort, inputPort); CurrentRoutes.Add(descriptor); + if (_outputSlotsByKey.TryGetValue(outputPort.Key, out var routedSlot)) + { + routedSlot.SetRoute(signalType, inputPort.Key); + } + this.LogInformation("Executed switch: {input} -> {output} ({signalType}) on {key}", inputPort.Key, outputPort.Key, signalType, Key); @@ -155,6 +194,79 @@ public class MockRoutingMidpoint : EssentialsDevice, IRoutingMidpointWithFeedbac } } +/// +/// Named routing slot info for a input or output port. +/// +class MockRoutingSlotInfo : IRoutingSlotInfo +{ + /// + public string Key { get; } + + /// + public string Name { get; } + + /// + public int SlotNumber { get; } + + /// + public eRoutingSignalType SupportedSignalTypes { get; } + + /// + /// Initializes a new instance of the class. + /// + public MockRoutingSlotInfo(string key, string name, int slotNumber, eRoutingSignalType supportedSignalTypes) + { + Key = key; + Name = name; + SlotNumber = slotNumber; + SupportedSignalTypes = supportedSignalTypes; + } +} + +/// +/// Named output routing slot info for a output port, tracking the +/// currently routed input key per signal type since the mock's flat +/// list does not carry signal type. +/// +class MockRoutingOutputSlotInfo : MockRoutingSlotInfo, IRoutingOutputSlotInfo +{ + private readonly Dictionary _currentRouteInputKeys = new Dictionary(); + + /// + public IReadOnlyDictionary CurrentRouteInputKeys => _currentRouteInputKeys; + + /// + public event EventHandler OutputSlotChanged; + + /// + /// Initializes a new instance of the class. + /// + public MockRoutingOutputSlotInfo(string key, string name, int slotNumber, eRoutingSignalType supportedSignalTypes) + : base(key, name, slotNumber, supportedSignalTypes) + { + } + + /// + /// Records the input key routed to this output for the given signal type and raises . + /// + public void SetRoute(eRoutingSignalType signalType, string inputKey) + { + _currentRouteInputKeys[signalType] = inputKey; + OutputSlotChanged?.Invoke(this, EventArgs.Empty); + } + + /// + /// Clears the routed input key for the given signal type and raises if it changed. + /// + public void ClearRoute(eRoutingSignalType signalType) + { + if (_currentRouteInputKeys.Remove(signalType)) + { + OutputSlotChanged?.Invoke(this, EventArgs.Empty); + } + } +} + /// /// Factory for building devices. /// diff --git a/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpointPropertiesConfig.cs b/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpointPropertiesConfig.cs index 7c7d4bc3..b24e31aa 100644 --- a/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpointPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpointPropertiesConfig.cs @@ -50,4 +50,12 @@ public class MockRoutingMidpointPortConfig [JsonProperty("portType")] [JsonConverter(typeof(StringEnumConverter))] public eRoutingPortConnectionType PortType { get; set; } = eRoutingPortConnectionType.Hdmi; + + /// + /// Optional friendly display name for this port (e.g. "Rm A Codec"), surfaced via + /// for named-routing-slot UI. Defaults to if + /// not specified. + /// + [JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)] + public string Label { get; set; } }