diff --git a/src/PepperDash.Essentials.Core/Routing/IHasNamedRoutingSlots.cs b/src/PepperDash.Essentials.Core/Routing/IHasNamedRoutingSlots.cs new file mode 100644 index 00000000..84b5983e --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IHasNamedRoutingSlots.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using PepperDash.Core; + +namespace PepperDash.Essentials.Core; + +/// +/// Describes a single named routing slot (input or output) on a device implementing +/// . +/// +public interface IRoutingSlotInfo : IKeyName +{ + /// Matrix slot number. + int SlotNumber { get; } + + /// Signal types this slot can carry. + eRoutingSignalType SupportedSignalTypes { get; } +} + +/// +/// Describes a named output slot, adding per-signal-type current-route feedback (keyed by the +/// routed input slot's key) that does not +/// carry. +/// +public interface IRoutingOutputSlotInfo : IRoutingSlotInfo +{ + /// The key of the input slot currently routed to this output, per signal type. + IReadOnlyDictionary CurrentRouteInputKeys { get; } + + /// Raised when the routed input on this output changes. + event EventHandler OutputSlotChanged; +} + +/// +/// Optional extension to for matrix-style routing +/// devices that track named input/output slots (with per-signal-type routing feedback) internally, +/// e.g. plugin-local slot abstractions that replaced the removed core IRoutingInputSlot/ +/// IRoutingOutputSlot during the v3 routing refactor. Devices implementing this get a richer +/// mobile-control matrix routing message (names + per-signal-type feedback) instead of the bare +/// key-only ports from alone. +/// +public interface IHasNamedRoutingSlots : IRoutingMidpointWithFeedback +{ + /// Named input slots, keyed by slot key. + IReadOnlyDictionary InputSlots { get; } + + /// Named output slots, keyed by slot key. + IReadOnlyDictionary OutputSlots { get; } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasNamedRoutingSlotsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasNamedRoutingSlotsMessenger.cs new file mode 100644 index 00000000..855a4aa7 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasNamedRoutingSlotsMessenger.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + /// + /// Messenger for devices implementing — sends named + /// input/output slots with per-signal-type current-route feedback, which the bare + /// (registered for every + /// device, including this one) cannot provide. + /// + public class IHasNamedRoutingSlotsMessenger : MessengerBase + { + private readonly IHasNamedRoutingSlots _device; + + public IHasNamedRoutingSlotsMessenger(string key, string messagePath, IHasNamedRoutingSlots device) + : base(key, messagePath, device as IKeyName) + { + _device = device; + } + + protected override void RegisterActions() + { + base.RegisterActions(); + + AddAction("/fullStatus", (id, content) => SendFullStatus(id)); + + foreach (var output in _device.OutputSlots.Values) + { + output.OutputSlotChanged += (sender, args) => SendFullStatus(); + } + } + + private static RoutingSlotMessage BuildSlotMessage(IRoutingSlotInfo slot) => new RoutingSlotMessage + { + Key = slot.Key, + Name = slot.Name, + SlotNumber = slot.SlotNumber, + SupportedSignalTypes = slot.SupportedSignalTypes.ToString() + }; + + private void SendFullStatus(string id = null) + { + var inputSlots = _device.InputSlots.ToDictionary(kvp => kvp.Key, kvp => BuildSlotMessage(kvp.Value)); + + var outputSlots = _device.OutputSlots.ToDictionary(kvp => kvp.Key, kvp => + { + var message = BuildSlotMessage(kvp.Value); + message.CurrentRouteInputKeys = kvp.Value.CurrentRouteInputKeys + .ToDictionary(r => r.Key.ToString(), r => r.Value); + return message; + }); + + var content = JToken.FromObject(new + { + inputSlots, + outputSlots + }); + + PostStatusMessage(content, MessagePath, id); + } + } + + public class RoutingSlotMessage + { + [JsonProperty("key")] + public string Key { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("slotNumber")] + public int SlotNumber { get; set; } + + [JsonProperty("supportedSignalTypes")] + public string SupportedSignalTypes { get; set; } + + [JsonProperty("currentRouteInputKeys", NullValueHandling = NullValueHandling.Ignore)] + public Dictionary CurrentRouteInputKeys { get; set; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl/MessengerFactoryRegistry.cs b/src/PepperDash.Essentials.MobileControl/MessengerFactoryRegistry.cs index d2b5c73e..29ede86d 100644 --- a/src/PepperDash.Essentials.MobileControl/MessengerFactoryRegistry.cs +++ b/src/PepperDash.Essentials.MobileControl/MessengerFactoryRegistry.cs @@ -374,6 +374,14 @@ namespace PepperDash.Essentials (d, mp, _) => new IRoutingMidpointWithFeedbackMessenger( $"{d.Key}-matrixRouting", mp, (IRoutingMidpointWithFeedback)d) ), + // Devices that also expose named slots (plugin-local IDmInputSlot/INvxInputSlot- + // style abstractions) get a second, richer message with names + per-signal-type + // route feedback the bare messenger above cannot provide. + new MessengerFactoryEntry( + typeof(IHasNamedRoutingSlots), + (d, mp, _) => new IHasNamedRoutingSlotsMessenger( + $"{d.Key}-namedRoutingSlots", mp, (IHasNamedRoutingSlots)d) + ), // ── Environmental sensors ───────────────────────────────────────────────── // Preserving original key format (no controller key suffix)