diff --git a/PepperDash.Essentials.DM.4Series.sln b/PepperDash.Essentials.DM.4Series.sln index 4a7c666..58f6bcf 100644 --- a/PepperDash.Essentials.DM.4Series.sln +++ b/PepperDash.Essentials.DM.4Series.sln @@ -1,5 +1,4 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.7.34202.233 MinimumVisualStudioVersion = 10.0.40219.1 diff --git a/src/Chassis/DmCardAudioOutput.cs b/src/Chassis/DmCardAudioOutput.cs index 3d2469d..ef4beeb 100644 --- a/src/Chassis/DmCardAudioOutput.cs +++ b/src/Chassis/DmCardAudioOutput.cs @@ -14,6 +14,9 @@ public class DmCardAudioOutputController : IBasicVolumeWithFeedback { public Audio.Output Output { get; private set; } + public string Key { get; private set; } + public string Name { get; private set; } + public IntFeedback VolumeLevelFeedback { get; private set; } public BoolFeedback MuteFeedback { get; private set; } @@ -24,6 +27,8 @@ public class DmCardAudioOutputController : IBasicVolumeWithFeedback public DmCardAudioOutputController(Audio.Output output) { Output = output; + Key = string.Format("DmCardAudioOutput-{0}", output.Number); + Name = Key; VolumeLevelFeedback = new IntFeedback(() => Output.VolumeFeedback.UShortValue); MuteFeedback = new BoolFeedback(() => IsMuted); } diff --git a/src/Chassis/DmpsAudioOutputController.cs b/src/Chassis/DmpsAudioOutputController.cs index 995984b..f83c598 100644 --- a/src/Chassis/DmpsAudioOutputController.cs +++ b/src/Chassis/DmpsAudioOutputController.cs @@ -352,6 +352,8 @@ public class DmpsAudioOutput : IBasicVolumeWithFeedback protected short MinLevel { get; set; } protected short MaxLevel { get; set; } + public string Key { get; private set; } + public string Name { get; private set; } public eDmpsLevelType Type { get; private set; } public BoolFeedback MuteFeedback { get; private set; } public IntFeedback VolumeLevelFeedback { get; private set; } @@ -367,6 +369,8 @@ public DmpsAudioOutput(Dmps3AudioOutputBase output, eDmpsLevelType type) VolumeLevelInput = 0; EnableVolumeSend = false; Type = type; + Key = string.Format("DmpsAudioOutput-{0}", type); + Name = Key; MinLevel = -800; MaxLevel = 100; diff --git a/src/Chassis/HdMdNxM4KzEController.cs b/src/Chassis/HdMdNxM4KzEController.cs new file mode 100644 index 0000000..9d9dd68 --- /dev/null +++ b/src/Chassis/HdMdNxM4KzEController.cs @@ -0,0 +1,722 @@ +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DeviceSupport; +using Crestron.SimplSharpPro.DM; +using Newtonsoft.Json; +using PepperDash.Core; +using PepperDash.Core.Logging; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Bridges; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.DM.Config; +using System; +using System.Collections.Generic; +using System.Linq; + + +namespace PepperDash.Essentials.DM.Chassis +{ + [Description("Wrapper class for all HdMdNxM4ZE switchers")] + public class HdMdNxM4kZEController : CrestronGenericBridgeableBaseDevice, IRoutingNumericWithFeedback, IHasFeedback + { + private HdMdNxM4kzE _Chassis; + + public event EventHandler NumericSwitchChange; + + public Dictionary InputNames { get; set; } + public Dictionary OutputNames { get; set; } + + public RoutingPortCollection InputPorts { get; private set; } + public RoutingPortCollection OutputPorts { get; private set; } + + public FeedbackCollection VideoInputSyncFeedbacks { get; private set; } + public FeedbackCollection VideoOutputRouteFeedbacks { get; private set; } + public FeedbackCollection InputNameFeedbacks { get; private set; } + public FeedbackCollection OutputNameFeedbacks { get; private set; } + public FeedbackCollection OutputRouteNameFeedbacks { get; private set; } + public FeedbackCollection InputHdcpEnableFeedback { get; private set; } + public StringFeedback DeviceNameFeedback { get; private set; } + public BoolFeedback AutoRouteFeedback { get; private set; } + public BoolFeedback PriorityRouteFeedback { get; private set; } + public string NoRouteText { get; private set; } + + #region Constructor + + /// + /// Constructor for the HdMdNxM4kZEController + /// + /// The device key. + /// The device name. + /// The HdMdNxM4kzE chassis instance. + /// The HdMdNxM4kE properties config. + public HdMdNxM4kZEController(string key, string name, HdMdNxM4kzE chassis, + HdMdNxM4kEPropertiesConfig props) + : base(key, name, chassis) + { + Name = name; + _Chassis = chassis; + if (_Chassis == null) + { + Debug.LogDebug(this, "HdMdNxM4kZEController chassis is null, failed to build the device"); + return; + } + + if (props == null) + { + Debug.LogDebug(this, "HdMdNxM4kZEController properties are null, failed to build the device"); + return; + } + + NoRouteText = props.NoRouteText ?? "None"; + + InputNames = new Dictionary(); + foreach(var inputNames in props.Inputs) + { + InputNames.Add(inputNames.Key, inputNames.Value ?? string.Format("Input {0}", inputNames.Key)); + } + + OutputNames = new Dictionary(); + foreach(var outputName in props.Outputs) + { + OutputNames.Add(outputName.Key, outputName.Value ?? string.Format("Output {0}", outputName.Key)); + } + + InputPorts = new RoutingPortCollection(); + OutputPorts = new RoutingPortCollection(); + + DeviceNameFeedback = new StringFeedback("DeviceName", () => + { + try { return Name; } + catch { this.LogError("Error getting DeviceNameFeedback"); return ""; } + }); + + InputNameFeedbacks = new FeedbackCollection(); + OutputNameFeedbacks = new FeedbackCollection(); + + VideoInputSyncFeedbacks = new FeedbackCollection(); + InputHdcpEnableFeedback = new FeedbackCollection(); + VideoOutputRouteFeedbacks = new FeedbackCollection(); + OutputRouteNameFeedbacks = new FeedbackCollection(); + + AutoRouteFeedback = new BoolFeedback("AutoRoute", () =>_Chassis.AutoRouteOnFeedback?.BoolValue ?? false); + + if (_Chassis is HdMd4xX4kzE _chassis) + { + PriorityRouteFeedback = new BoolFeedback("PriorityRoute", () => + { + try { return _chassis.PriorityRouteOnFeedback?.BoolValue ?? false; } + catch { this.LogError("Error getting PriorityRouteFeedback"); return false; } + }); + } + + SetupInputs(); + SetupOutputs(); + + _Chassis.BaseEvent += Chassis_BaseEvent; + _Chassis.DMInputChange += Chassis_DMInputChange; + _Chassis.DMOutputChange += Chassis_DMOutputChange; + + AddPostActivationAction(AddFeedbackCollections); + } + + private void SetupInputs() + { + if (_Chassis == null) + { + this.LogError("SetupInputs: Chassis is null. Cannot setup VideoSync feedbacks."); + return; + } + + foreach(var input in _Chassis.Inputs) + { + var inputNumber = input.Number; + this.LogError("SetupInputs: _Chassis.Inputs[{inputNumber}]", inputNumber); + + var inputFriendlyName = InputNames[inputNumber]; + + // Set Input Name from config + input.Name.StringValue = inputFriendlyName; + + // Feedbacks + InputNameFeedbacks.Add(new StringFeedback(inputFriendlyName, () => input.NameFeedback.StringValue)); + VideoInputSyncFeedbacks.Add(new BoolFeedback(inputFriendlyName, () => input.VideoDetectedFeedback.BoolValue)); + } + + foreach(var hdmiInput in _Chassis.HdmiInputs) + { + var hdmiInputNumber = hdmiInput.Number; + this.LogError("SetupInputs: _Chassis.HdmiInputs[{hdmiInputNumber}]", hdmiInputNumber); + + var hdmiInputName = string.Format("hdmiInput{0}", hdmiInputNumber); + var hdmiInputFriendlyName = InputNames[hdmiInputNumber] ?? hdmiInputName; + + // Routing Input Port + InputPorts.Add(new RoutingInputPort(hdmiInputName, eRoutingSignalType.AudioVideo, + eRoutingPortConnectionType.Hdmi, hdmiInput, this) + { + FeedbackMatchObject = hdmiInput + }); + + // Feedbacks + InputHdcpEnableFeedback.Add(new BoolFeedback(hdmiInputFriendlyName, () => hdmiInput.HdmiInputPort.HdcpSupportOnFeedback.BoolValue)); + } + } + + private void SetupOutputs() + { + if (_Chassis == null) + { + this.LogError("SetupOutputs: Chassis is null. Cannot setup VideoSync feedbacks."); + return; + } + + foreach(var output in _Chassis.Outputs) + { + var outputNumber = output.Number; + this.LogError("SetupOutputs: _Chassis.Outputs[{outputNumber}]", outputNumber); + + var outputFriendlyName = OutputNames[outputNumber]; + + // Set Output Name from config + output.Name.StringValue = outputFriendlyName; + + // Feedbacks + OutputNameFeedbacks.Add(new StringFeedback(outputFriendlyName, () => output.NameFeedback.StringValue)); + VideoOutputRouteFeedbacks.Add(new IntFeedback(outputFriendlyName, () => (int)(output.VideoOutFeedback == null ? 0 : output.VideoOutFeedback.Number))); + OutputRouteNameFeedbacks.Add(new StringFeedback(outputFriendlyName, () => output.VideoOutFeedback?.NameFeedback.StringValue ?? NoRouteText)); + } + + foreach(var hdmiOutput in _Chassis.HdmiOutputs) + { + var hdmiOutputNumber = hdmiOutput.Number; + this.LogError("SetupOutputs: _Chassis.HdmiOutputs[{hdmiOutputNumber}]", hdmiOutputNumber); + + var hdmiOutputName = string.Format("hdmiOutput{0}", hdmiOutputNumber); + var hdmiOutputFriendlyName = OutputNames[hdmiOutputNumber]; + + // Set Output Name from config + hdmiOutput.Name.StringValue = hdmiOutputFriendlyName; + + // Routing Output Port + OutputPorts.Add(new RoutingOutputPort(hdmiOutputName, eRoutingSignalType.AudioVideo, + eRoutingPortConnectionType.Hdmi, hdmiOutput, this) + { + FeedbackMatchObject = hdmiOutput + }); + } + } + + #endregion + + #region Methods + + /// + /// Raise an event when the status of a switch object changes. + /// + /// Arguments defined as IKeyName sender, output, input, and eRoutingSignalType + private void OnSwitchChange(RoutingNumericEventArgs e) + { + NumericSwitchChange?.Invoke(this, e); + } + + /// + /// Enables HDCP on the specified input port. + /// + /// The input port number to enable HDCP on. + public void EnableHdcp(uint port) + { + if (port <= 0 || port > _Chassis.NumberOfInputs) return; + + _Chassis.HdmiInputs[port].HdmiInputPort.HdcpSupportOn(); + InputHdcpEnableFeedback[InputNames[port]]?.FireUpdate(); + } + + /// + /// Disables HDCP on the specified input port. + /// + /// The input port number to disable HDCP on. + public void DisableHdcp(uint port) + { + if (port <= 0 || port > _Chassis.NumberOfInputs) return; + + _Chassis.HdmiInputs[port].HdmiInputPort.HdcpSupportOff(); + InputHdcpEnableFeedback[InputNames[port]]?.FireUpdate(); + } + + /// + /// Enables AutoRoute on the chassis if supported. Auto route is supported by HdMdNxM4kzE + /// + public void EnableAutoRoute() + { + if (_Chassis.NumberOfOutputs > 1) return; + if (!(_Chassis is HdMdNxM4kzE _chassis)) + { + this.LogVerbose("EnableAutoRoute: AutoRoute is not supported on this chassis."); + return; + } + + _chassis.AutoRouteOn(); + } + + /// + /// Disables AutoRoute on the chassis if supported. Auto route is supported by HdMdNxM4kzE + /// + public void DisableAutoRoute() + { + if (_Chassis.NumberOfOutputs > 1) return; + if (!(_Chassis is HdMdNxM4kzE _chassis)) + { + this.LogVerbose("DisableAutoRoute: AutoRoute is not supported on this chassis."); + return; + } + + _chassis.AutoRouteOff(); + } + + /// + /// Enables Priority Route on the chassis if supported. Priority route is support by HdMd4xX4kzE + /// + public void EnablePriorityRoute() + { + //if (!(_Chassis is HdMd4xX4kzE _chassis)) + if(!(_Chassis is HdMd4xX4kzE _chassis)) + { + this.LogVerbose("EnablePriorityRoute: Priority Route is not supported on {key}.", Key); + return; + } + + _chassis.PriorityRouteOn(); + } + + /// + /// Disables Priority Route on the chassis if supported. Priority route is support by HdMd4xX4kzE + /// + public void DisablePriorityRoute() + { + if (_Chassis is HdMd4xX4kzE _chassis_X4kzE) + { + _chassis_X4kzE.PriorityRouteOff(); + return; + } + + this.LogVerbose("DisablePriorityRoute: Priority Route is not supported on this chassis."); + } + + + #region FeedbackCollection Methods + + + /// + /// Adds all feedback collections to the Feedbacks collection. + /// + public void AddFeedbackCollections() + { + if (IsOnline != null) + AddFeedbackToList(IsOnline); + + AddFeedbackToList(DeviceNameFeedback); + + if (AutoRouteFeedback != null) + { + AddFeedbackToList(AutoRouteFeedback); + } + + foreach (var fb in VideoInputSyncFeedbacks) + { + AddFeedbackToList(fb); + } + foreach (var fb in InputHdcpEnableFeedback) + { + AddFeedbackToList(fb); + } + foreach (var fb in VideoOutputRouteFeedbacks) + { + AddFeedbackToList(fb); + } + foreach (var fb in InputNameFeedbacks) + { + AddFeedbackToList(fb); + } + foreach (var fb in OutputNameFeedbacks) + { + AddFeedbackToList(fb); + } + foreach (var fb in OutputRouteNameFeedbacks) + { + AddFeedbackToList(fb); + } + + // TODO - Remove after testing + this.LogInformation("AddFeedbackCollections: Feedbacks contains {feedbacksCount} items", Feedbacks.Count); + foreach (var fb in Feedbacks) + { + // TODO - Remove after testing + this.LogInformation("AddFeedbackCollections: Feedbacks = {feedbackKey}", fb.Key); + } + } + + /// + /// Adds a feedback to the Feedbacks collection if it does not already exist. + /// + public void AddFeedbackToList(Core.Feedback newFb) + { + if (newFb == null) return; + + //if (Feedbacks.Any(f => f.Key == newFb.Key)) return; + + // TODO - Remove after testing + this.LogVerbose("AddFeedbackToList: adding {feedbackKey} to Feedbacks collection", newFb.Key); + Feedbacks.Add(newFb); + } + + #endregion + + #region IRouting Members + + /// + /// Executes a switch from input to output for the specified signal type. + /// + /// The input selector object. + /// The output selector object. + /// The type of signal to switch. + public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) + { + var input = inputSelector as HdMdNxM4kzEHdmiInput; + var output = outputSelector as HdMdNxM4kzEHdmiOutput; + + if (output == null) + { + this.LogError("ExecuteSwitch: Unable to make switch. output selector is not HdMdNxM4kzEHdmiOutput"); + return; + } + + this.LogVerbose("ExecuteSwitch: input={input} output={output}", input, output); + + // Try to make switch only when necessary. The unit appears to toggle when already selected. + var current = output.VideoOut; + if (current != input) + output.VideoOut = input; + } + + #endregion + + #region IRoutingNumeric Members + + /// + /// Executes a numeric switch from input to output for the specified signal type. + /// + /// The input selector number. + /// The output selector number. + /// The type of signal to switch. + public void ExecuteNumericSwitch(ushort inputSelector, ushort outputSelector, eRoutingSignalType signalType) + { + var input = inputSelector == 0 ? null : _Chassis.HdmiInputs[inputSelector]; + var output = _Chassis.HdmiOutputs[outputSelector]; + + this.LogVerbose("ExecuteNumericSwitch: input={input} output={output}", input, output); + + ExecuteSwitch(input, output, signalType); + } + + #endregion + + #endregion + + #region Bridge Linking + + /// + /// Links the device to the API bridge. + /// + /// The trilist to link to. + /// The join start number. + /// The join map key. + /// The EISC API bridge. + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new HdMdNxM4kEControllerJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + this.LogInformation("Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } + + DeviceNameFeedback?.LinkInputSig(trilist.StringInput[joinMap.Name.JoinNumber]); + IsOnline?.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); + + if (_Chassis is HdMdNxM4kzE _chassis_M4kzE) + { + this.LogInformation("LinkToApi: _Chassis is HdMdNxM4kzE, setting up AutoRoute links"); + + trilist.SetSigTrueAction(joinMap.EnableAutoRoute.JoinNumber, () => _chassis_M4kzE.AutoRouteOn()); + trilist.SetSigFalseAction(joinMap.EnableAutoRoute.JoinNumber, () => _chassis_M4kzE.AutoRouteOff()); + AutoRouteFeedback?.LinkInputSig(trilist.BooleanInput[joinMap.EnableAutoRoute.JoinNumber]); + } + + if (_Chassis is HdMd4xX4kzE _chassis_X4kzE) + { + this.LogInformation("LinkToApi: _Chassis is HdMd4xX4kzE, setting up PriorityRoute links - not implemented"); + // trilist.SetSigTrueAction(joinMap.EnablePriorityRoute.JoinNumber, () => _chassis_X4kzE.PriorityRouteOn()); + // trilist.SetSigFalseAction(joinMap.EnablePriorityRoute.JoinNumber, () => _chassis_X4kzE.PriorityRouteOff()); + // PriorityRouteFeedback?.LinkInputSig(trilist.BooleanInput[joinMap.EnablePriorityRoute.JoinNumber]); + } + + foreach(var input in _Chassis.Inputs) + { + uint inputNumber = input.Number; + var joinOffset = inputNumber - 1; + + this.LogInformation("LinkToApi: _Chassis.Inputs[{inputNumber}].Name = {inputName}", input.Number, input.Name.StringValue); + + trilist.SetSigTrueAction(joinMap.EnableInputHdcp.JoinNumber + joinOffset, () => EnableHdcp(inputNumber)); + trilist.SetSigTrueAction(joinMap.DisableInputHdcp.JoinNumber + joinOffset, () => DisableHdcp(inputNumber)); + + InputNameFeedbacks[InputNames[inputNumber]]?.LinkInputSig(trilist.StringInput[joinMap.InputName.JoinNumber + joinOffset]); + + InputHdcpEnableFeedback[InputNames[inputNumber]]?.LinkInputSig(trilist.BooleanInput[joinMap.EnableInputHdcp.JoinNumber + joinOffset]); + InputHdcpEnableFeedback[InputNames[inputNumber]]?.LinkComplementInputSig(trilist.BooleanInput[joinMap.DisableInputHdcp.JoinNumber + joinOffset]); + + VideoInputSyncFeedbacks[InputNames[inputNumber]]?.LinkInputSig(trilist.BooleanInput[joinMap.InputSync.JoinNumber + joinOffset]); + } + + foreach (var output in _Chassis.Outputs) + { + uint outputNumber = output.Number; + var joinOffset = outputNumber - 1; + + this.LogInformation("LinkToApi: _Chassis.Outputs[{outputNumber}].Name = {outputName}", output.Number, output.Name.StringValue); + + trilist.SetUShortSigAction(joinMap.OutputRoute.JoinNumber + joinOffset, (a) => ExecuteNumericSwitch(a, (ushort)outputNumber, eRoutingSignalType.AudioVideo)); + + OutputNameFeedbacks[OutputNames[outputNumber]]?.LinkInputSig(trilist.StringInput[joinMap.OutputName.JoinNumber + joinOffset]); + OutputRouteNameFeedbacks[OutputNames[outputNumber]]?.LinkInputSig(trilist.StringInput[joinMap.OutputRoutedName.JoinNumber + joinOffset]); + + VideoOutputRouteFeedbacks[OutputNames[outputNumber]]?.LinkInputSig(trilist.UShortInput[joinMap.OutputRoute.JoinNumber + joinOffset]); + } + + _Chassis.OnlineStatusChange += Chassis_OnlineStatusChange; + + trilist.OnlineStatusChange += (d, args) => + { + if (!args.DeviceOnLine) return; + + DeviceNameFeedback?.FireUpdate(); + + // feedback updates was moved to the Chassis_OnlineStatusChange + // due to the amount of time it takes for the device to come online + }; + } + + #endregion + + #region Events + + private void Chassis_BaseEvent(GenericBase device, BaseEventArgs args) + { + var eventName = typeof(BaseEventArgs) + .GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static) + .FirstOrDefault(f => f.IsLiteral && (int)f.GetValue(null) == args.EventId)?.Name ?? args.EventId.ToString(); + + this.LogInformation("Chassis_BaseEvent: received {eventName} (id-{eventId}) received from device {deviceName}", eventName, args.EventId, device.GetType().Name); + } + + + void Chassis_OnlineStatusChange(Crestron.SimplSharpPro.GenericBase currentDevice, Crestron.SimplSharpPro.OnlineOfflineEventArgs args) + { + // TODO - Remove after testing + this.LogInformation("Chassis_OnlineStatusChange: DeviceOnline = {deviceOnline}", args.DeviceOnLine); + + IsOnline?.FireUpdate(); + + if (!args.DeviceOnLine) return; + + // TODO - Remove after testing + this.LogInformation("Chassis_OnlineStatusChange: Feedbacks has {feedbackCount} items in the collection", Feedbacks.Count); + + foreach (var feedback in Feedbacks) + { + // TODO - Remove after testing + this.LogInformation("Chassis_OnlineStatusChange: Firing update for {feedbackKey}", feedback?.Key); + feedback?.FireUpdate(); + } + + AutoRouteFeedback?.FireUpdate(); + + if (_Chassis is HdMd4xX4kzE) + { + PriorityRouteFeedback?.FireUpdate(); + } + } + + void Chassis_DMInputChange(Switch device, DMInputEventArgs args) + { + var eventName = typeof(DMInputEventIds) + .GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static) + .FirstOrDefault(f => f.IsLiteral && (int)f.GetValue(null) == args.EventId)?.Name ?? args.EventId.ToString(); + + switch (args.EventId) + { + case DMInputEventIds.SourceSyncEventId: + case DMInputEventIds.VideoDetectedEventId: + { + this.LogInformation("Chassis_DMInputChange: received {eventName} (id-{eventId}) | Updating VideoInputSyncFeedbacks", eventName, args.EventId); + foreach (var item in VideoInputSyncFeedbacks) + { + this.LogInformation("Chassis_DMInputChange: Updating VideoInputSyncFeedbacks for HDMI {itemKey} to {itemValue}", item.Key, item.BoolValue); + item.FireUpdate(); + } + break; + } + case DMInputEventIds.InputNameFeedbackEventId: + case DMInputEventIds.InputNameEventId: + case DMInputEventIds.NameFeedbackEventId: + { + this.LogInformation("Chassis_DMInputChange: received {eventName} (id-{eventId}) | Input {number} Name {name}, updating InputNameFeedbacks", eventName, args.EventId, args.Number, _Chassis.HdmiInputs[args.Number].NameFeedback.StringValue); + foreach (var item in InputNameFeedbacks) + { + item.FireUpdate(); + } + break; + } + case DMInputEventIds.PriorityEventId: + { + this.LogInformation("Chassis_DMInputChange: received {eventName} (id-{eventId}) | Updating PriorityRouteFeedback", eventName, args.EventId); + + PriorityRouteFeedback?.FireUpdate(); + + break; + } + default: + { + this.LogInformation("Chassis_DMInputChange: Unhandled DM Input Event {eventName} (id-{eventId}), ignoring.", eventName, args.EventId); + break; + } + } + } + + + void Chassis_DMOutputChange(Switch device, DMOutputEventArgs args) + { + // TODO - Remove after testing + var eventName = typeof(DMOutputEventIds) + .GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static) + .FirstOrDefault(f => f.IsLiteral && (int)f.GetValue(null) == args.EventId)?.Name ?? args.EventId.ToString(); + + switch (args.EventId) + { + case DMOutputEventIds.VideoOutEventId: + { + var outputNumber = args.Number; + var outputName = OutputNames[outputNumber]; + var inputNumber = _Chassis.HdmiOutputs[outputNumber].VideoOutFeedback?.Number ?? 0; + + this.LogInformation("Chassis_DMOutputChange: received {eventName} (id-{eventId}) | Input {inputNumber} routed to Output {outputNumber}", eventName, args.EventId, inputNumber, outputNumber); + + var feedback = VideoOutputRouteFeedbacks[outputName]; + if(feedback == null) + { + this.LogInformation("Chassis_DMOutputChange: VideoOutputRouteFeedbacks does not contain key {outputName}", outputName); + break; + } + + var inPort = InputPorts.FirstOrDefault(p => p.FeedbackMatchObject == _Chassis.HdmiOutputs[outputNumber].VideoOutFeedback); + var outPort = OutputPorts.FirstOrDefault(p => p.FeedbackMatchObject == _Chassis.HdmiOutputs[outputNumber]); + + feedback.FireUpdate(); + OnSwitchChange(new RoutingNumericEventArgs(outputNumber, inputNumber, outPort, inPort, eRoutingSignalType.AudioVideo)); + + break; + } + case DMOutputEventIds.AutoModeOffEventId: + case DMOutputEventIds.AutoModeOnEventId: + { + this.LogInformation("Chassis_DMOutputChange: received {eventName} (id-{eventId}) | Updating AutoRouteFeedback", eventName, args.EventId); + AutoRouteFeedback?.FireUpdate(); + + break; + } + case DMOutputEventIds.InputPrioritiesFeedbackEventId: + { + this.LogInformation("Chassis_DMOutputChange: received {eventName} (id-{eventId}) | Updating PriorityRouteFeedback", eventName, args.EventId); + PriorityRouteFeedback?.FireUpdate(); + + break; + } + case DMOutputEventIds.OutputNameEventId: + case DMOutputEventIds.NameFeedbackEventId: + { + this.LogInformation("Chassis_DMOutputChange: received {eventName} (id-{eventId}) | Output {number} Name {name}, updating OutputNameFeedbacks and OutputRouteNameFeedbacks", eventName, args.EventId, args.Number, _Chassis.HdmiOutputs[args.Number].NameFeedback.StringValue); + foreach (var item in OutputNameFeedbacks) + { + item.FireUpdate(); + } + break; + } + default: + { + this.LogInformation("Chassis_DMOutputChange: Unhandled DM Output Event {eventName} (id-{eventId}), ignoring.", eventName, args.EventId); + break; + } + } + } + + #endregion + + #region Factory + + /// + /// Factory for creating HdMdNxM4kZEController devices + /// + public class HdMdNxM4kZEControllerFactory : EssentialsPluginDeviceFactory + { + /// + /// Constructor + /// + public HdMdNxM4kZEControllerFactory() + { + MinimumEssentialsFrameworkVersion = "2.24.4"; + TypeNames = new List() { "hdmd4x14kze", "hdmd4x24kze", "hdmd8x84kze" }; + } + + /// + /// Builds a HdMdNxM4kZEController device + /// + /// The device config + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogDebug("Factory Attempting to create new HD-MD-NxM-4KZ-E Device"); + + var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); + + var type = dc.Type.ToLower(); + var control = props.Control; + var ipid = control.IpIdInt; + var address = control.TcpSshProperties.Address; + + switch (type) + { + case ("hdmd4x14kze"): + return new HdMdNxM4kZEController(dc.Key, dc.Name, new HdMd4x14kzE(ipid, Global.ControlSystem), props); + case ("hdmd4x24kze"): + return new HdMdNxM4kZEController(dc.Key, dc.Name, new HdMd4x24kzE(ipid, Global.ControlSystem), props); + case ("hdmd8x84kze"): + return new HdMdNxM4kZEController(dc.Key, dc.Name, new HdMd8x84kzE(ipid, Global.ControlSystem), props); + default: + return null; + } + } + } + + #endregion + + + + } +} \ No newline at end of file diff --git a/src/Chassis/HdMdNxM4kEBridgeableController.cs b/src/Chassis/HdMdNxM4kEBridgeableController.cs deleted file mode 100644 index e416443..0000000 --- a/src/Chassis/HdMdNxM4kEBridgeableController.cs +++ /dev/null @@ -1,504 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Newtonsoft.Json; -using Crestron.SimplSharp; -using Crestron.SimplSharpPro.DeviceSupport; -using Crestron.SimplSharpPro.DM; -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.DM.Config; -using PepperDash.Essentials.Core.Bridges; -using PepperDash.Essentials.Core.Config; - -namespace PepperDash.Essentials.DM.Chassis -{ - [Description("Wrapper class for all HdMdNxM4E switchers")] - public class HdMdNxM4kEBridgeableController : CrestronGenericBridgeableBaseDevice, IRoutingNumericWithFeedback, IHasFeedback - { - private HdMdNxM _Chassis; - private HdMd4x14kE _Chassis4x1; - - //IroutingNumericEvent - public event EventHandler NumericSwitchChange; - - public Dictionary InputNames { get; set; } - public Dictionary OutputNames { get; set; } - - public RoutingPortCollection InputPorts { get; private set; } - public RoutingPortCollection OutputPorts { get; private set; } - - public FeedbackCollection VideoInputSyncFeedbacks { get; private set; } - public FeedbackCollection VideoOutputRouteFeedbacks { get; private set; } - public FeedbackCollection InputNameFeedbacks { get; private set; } - public FeedbackCollection OutputNameFeedbacks { get; private set; } - public FeedbackCollection OutputRouteNameFeedbacks { get; private set; } - public FeedbackCollection InputHdcpEnableFeedback { get; private set; } - public StringFeedback DeviceNameFeedback { get; private set; } - public BoolFeedback AutoRouteFeedback { get; private set; } - - #region Constructor - - public HdMdNxM4kEBridgeableController(string key, string name, HdMdNxM chassis, - HdMdNxM4kEBridgeablePropertiesConfig props) - : base(key, name, chassis) - { - _Chassis = chassis; - Name = name; - - if (props == null) - { - Debug.LogDebug(this, "HdMdNx4keBridgeableController properties are null, failed to build the device"); - return; - } - - - if (props.Inputs != null) - { - foreach (var kvp in props.Inputs) - { - Debug.LogDebug(this, "props.Inputs: {0}-{1}", kvp.Key, kvp.Value); - } - InputNames = props.Inputs; - } - if (props.Outputs != null) - { - foreach (var kvp in props.Outputs) - { - Debug.LogDebug(this, "props.Outputs: {0}-{1}", kvp.Key, kvp.Value); - } - OutputNames = props.Outputs; - } - - DeviceNameFeedback = new StringFeedback(()=>Name); - - VideoInputSyncFeedbacks = new FeedbackCollection(); - VideoOutputRouteFeedbacks = new FeedbackCollection(); - InputNameFeedbacks = new FeedbackCollection(); - OutputNameFeedbacks = new FeedbackCollection(); - OutputRouteNameFeedbacks = new FeedbackCollection(); - InputHdcpEnableFeedback = new FeedbackCollection(); - - InputPorts = new RoutingPortCollection(); - OutputPorts = new RoutingPortCollection(); - - if (_Chassis.NumberOfInputs == 1) - { - _Chassis4x1 = _Chassis as HdMd4x14kE; - AutoRouteFeedback = new BoolFeedback(() => _Chassis4x1.AutoModeOnFeedback.BoolValue); - } - - for (uint i = 1; i <= _Chassis.NumberOfInputs; i++) - { - var index = i; - var inputName = InputNames[index]; - //_Chassis.Inputs[index].Name.StringValue = inputName; - _Chassis.HdmiInputs[index].Name.StringValue = inputName; - - InputPorts.Add(new RoutingInputPort(inputName, eRoutingSignalType.AudioVideo, - eRoutingPortConnectionType.Hdmi, _Chassis.HdmiInputs[index], this) - { - FeedbackMatchObject = _Chassis.HdmiInputs[index] - }); - VideoInputSyncFeedbacks.Add(new BoolFeedback(inputName, () => _Chassis.Inputs[index].VideoDetectedFeedback.BoolValue)); - //InputNameFeedbacks.Add(new StringFeedback(inputName, () => _Chassis.Inputs[index].NameFeedback.StringValue)); - InputNameFeedbacks.Add(new StringFeedback(inputName, () => InputNames[index])); - InputHdcpEnableFeedback.Add(new BoolFeedback(inputName, () => _Chassis.HdmiInputs[index].HdmiInputPort.HdcpSupportOnFeedback.BoolValue)); - } - - for (uint i = 1; i <= _Chassis.NumberOfOutputs; i++) - { - var index = i; - var outputName = OutputNames[index]; - //_Chassis.Outputs[index].Name.StringValue = outputName; - //_Chassis.HdmiOutputs[index].Name.StringValue = outputName; - - OutputPorts.Add(new RoutingOutputPort(outputName, eRoutingSignalType.AudioVideo, - eRoutingPortConnectionType.Hdmi, _Chassis.HdmiOutputs[index], this) - { - FeedbackMatchObject = _Chassis.HdmiOutputs[index] - }); - VideoOutputRouteFeedbacks.Add(new IntFeedback(outputName, () => _Chassis.Outputs[index].VideoOutFeedback == null ? 0 : (int)_Chassis.Outputs[index].VideoOutFeedback.Number)); - OutputNameFeedbacks.Add(new StringFeedback(outputName, () => OutputNames[index])); - OutputRouteNameFeedbacks.Add(new StringFeedback(outputName, () => _Chassis.Outputs[index].VideoOutFeedback.NameFeedback.StringValue)); - } - - _Chassis.DMInputChange += Chassis_DMInputChange; - _Chassis.DMOutputChange += Chassis_DMOutputChange; - - AddPostActivationAction(AddFeedbackCollections); - } - - #endregion - - #region Methods - - /// - /// Raise an event when the status of a switch object changes. - /// - /// Arguments defined as IKeyName sender, output, input, and eRoutingSignalType - private void OnSwitchChange(RoutingNumericEventArgs e) - { - var newEvent = NumericSwitchChange; - if (newEvent != null) newEvent(this, e); - } - - public void EnableHdcp(uint port) - { - if (port > _Chassis.NumberOfInputs) return; - if (port <= 0) return; - - _Chassis.HdmiInputs[port].HdmiInputPort.HdcpSupportOn(); - InputHdcpEnableFeedback[InputNames[port]].FireUpdate(); - } - - public void DisableHdcp(uint port) - { - if (port > _Chassis.NumberOfInputs) return; - if (port <= 0) return; - - _Chassis.HdmiInputs[port].HdmiInputPort.HdcpSupportOff(); - InputHdcpEnableFeedback[InputNames[port]].FireUpdate(); - } - - public void EnableAutoRoute() - { - if (_Chassis.NumberOfInputs != 1) return; - - if (_Chassis4x1 == null) return; - - _Chassis4x1.AutoModeOn(); - } - - public void DisableAutoRoute() - { - if (_Chassis.NumberOfInputs != 1) return; - - if (_Chassis4x1 == null) return; - - _Chassis4x1.AutoModeOff(); - } - - #region PostActivate - - public void AddFeedbackCollections() - { - AddFeedbackToList(DeviceNameFeedback); - AddCollectionsToList(VideoInputSyncFeedbacks, InputHdcpEnableFeedback); - AddCollectionsToList(VideoOutputRouteFeedbacks); - AddCollectionsToList(InputNameFeedbacks, OutputNameFeedbacks, OutputRouteNameFeedbacks); - } - - #endregion - - #region FeedbackCollection Methods - - //Add arrays of collections - public void AddCollectionsToList(params FeedbackCollection[] newFbs) - { - foreach (FeedbackCollection fbCollection in newFbs) - { - foreach (var item in newFbs) - { - AddCollectionToList(item); - } - } - } - public void AddCollectionsToList(params FeedbackCollection[] newFbs) - { - foreach (FeedbackCollection fbCollection in newFbs) - { - foreach (var item in newFbs) - { - AddCollectionToList(item); - } - } - } - - public void AddCollectionsToList(params FeedbackCollection[] newFbs) - { - foreach (FeedbackCollection fbCollection in newFbs) - { - foreach (var item in newFbs) - { - AddCollectionToList(item); - } - } - } - - //Add Collections - public void AddCollectionToList(FeedbackCollection newFbs) - { - foreach (var f in newFbs) - { - if (f == null) continue; - - AddFeedbackToList(f); - } - } - - public void AddCollectionToList(FeedbackCollection newFbs) - { - foreach (var f in newFbs) - { - if (f == null) continue; - - AddFeedbackToList(f); - } - } - - public void AddCollectionToList(FeedbackCollection newFbs) - { - foreach (var f in newFbs) - { - if (f == null) continue; - - AddFeedbackToList(f); - } - } - - //Add Individual Feedbacks - public void AddFeedbackToList(PepperDash.Essentials.Core.Feedback newFb) - { - if (newFb == null) return; - - if (!Feedbacks.Contains(newFb)) - { - Feedbacks.Add(newFb); - } - } - - #endregion - - #region IRouting Members - - public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) - { - var input = inputSelector as HdMdNxMHdmiInput; //changed from HdMdNxM4kzEHdmiInput; - var output = outputSelector as HdMdNxMHdmiOutput; - Debug.LogVerbose(this, "ExecuteSwitch: input={0} output={1}", input, output); - - if (output == null) - { - Debug.LogInformation(this, "Unable to make switch. output selector is not HdMdNxMHdmiOutput"); - return; - } - - // Try to make switch only when necessary. The unit appears to toggle when already selected. - var current = output.VideoOut; - if (current != input) - output.VideoOut = input; - } - - #endregion - - #region IRoutingNumeric Members - - public void ExecuteNumericSwitch(ushort inputSelector, ushort outputSelector, eRoutingSignalType signalType) - { - var input = inputSelector == 0 ? null : _Chassis.HdmiInputs[inputSelector]; - var output = _Chassis.HdmiOutputs[outputSelector]; - - Debug.LogVerbose(this, "ExecuteNumericSwitch: input={0} output={1}", input, output); - - ExecuteSwitch(input, output, signalType); - } - - #endregion - - #endregion - - #region Bridge Linking - - public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) - { - var joinMap = new HdMdNxM4kEControllerJoinMap(joinStart); - - var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); - - if (!string.IsNullOrEmpty(joinMapSerialized)) - joinMap = JsonConvert.DeserializeObject(joinMapSerialized); - - if (bridge != null) - { - bridge.AddJoinMap(Key, joinMap); - } - else - { - Debug.LogInformation(this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); - } - - IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); - DeviceNameFeedback.LinkInputSig(trilist.StringInput[joinMap.Name.JoinNumber]); - - if (_Chassis4x1 != null) - { - trilist.SetSigTrueAction(joinMap.EnableAutoRoute.JoinNumber, () => _Chassis4x1.AutoModeOn()); - trilist.SetSigFalseAction(joinMap.EnableAutoRoute.JoinNumber, () => _Chassis4x1.AutoModeOff()); - AutoRouteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.EnableAutoRoute.JoinNumber]); - } - - for (uint i = 1; i <= _Chassis.NumberOfInputs; i++) - { - var joinIndex = i - 1; - var input = i; - //Digital - VideoInputSyncFeedbacks[InputNames[input]].LinkInputSig(trilist.BooleanInput[joinMap.InputSync.JoinNumber + joinIndex]); - InputHdcpEnableFeedback[InputNames[input]].LinkInputSig(trilist.BooleanInput[joinMap.EnableInputHdcp.JoinNumber + joinIndex]); - InputHdcpEnableFeedback[InputNames[input]].LinkComplementInputSig(trilist.BooleanInput[joinMap.DisableInputHdcp.JoinNumber + joinIndex]); - trilist.SetSigTrueAction(joinMap.EnableInputHdcp.JoinNumber + joinIndex, () => EnableHdcp(input)); - trilist.SetSigTrueAction(joinMap.DisableInputHdcp.JoinNumber + joinIndex, () => DisableHdcp(input)); - - //Serial - InputNameFeedbacks[InputNames[input]].LinkInputSig(trilist.StringInput[joinMap.InputName.JoinNumber + joinIndex]); - } - - for (uint i = 1; i <= _Chassis.NumberOfOutputs; i++) - { - var joinIndex = i - 1; - var output = i; - //Analog - VideoOutputRouteFeedbacks[OutputNames[output]].LinkInputSig(trilist.UShortInput[joinMap.OutputRoute.JoinNumber + joinIndex]); - trilist.SetUShortSigAction(joinMap.OutputRoute.JoinNumber + joinIndex, (a) => ExecuteNumericSwitch(a, (ushort) output, eRoutingSignalType.AudioVideo)); - - //Serial - OutputNameFeedbacks[OutputNames[output]].LinkInputSig(trilist.StringInput[joinMap.OutputName.JoinNumber + joinIndex]); - OutputRouteNameFeedbacks[OutputNames[output]].LinkInputSig(trilist.StringInput[joinMap.OutputRoutedName.JoinNumber + joinIndex]); - } - - _Chassis.OnlineStatusChange += Chassis_OnlineStatusChange; - - trilist.OnlineStatusChange += (d, args) => - { - if (!args.DeviceOnLine) return; - - // feedback updates was moved to the Chassis_OnlineStatusChange - // due to the amount of time it takes for the device to come online - }; - } - - - #endregion - - #region Events - - void Chassis_OnlineStatusChange(Crestron.SimplSharpPro.GenericBase currentDevice, Crestron.SimplSharpPro.OnlineOfflineEventArgs args) - { - IsOnline.FireUpdate(); - - if (!args.DeviceOnLine) return; - - foreach (var feedback in Feedbacks) - { - feedback.FireUpdate(); - } - - if (_Chassis4x1 != null) - AutoRouteFeedback.FireUpdate(); - } - - void Chassis_DMOutputChange(Switch device, DMOutputEventArgs args) - { - if (args.EventId != DMOutputEventIds.VideoOutEventId) return; - - var output = args.Number; - - var inputNumber = _Chassis.HdmiOutputs[output].VideoOutFeedback == null - ? 0 - : _Chassis.HdmiOutputs[output].VideoOutFeedback.Number; - - var outputName = OutputNames[output]; - - var feedback = VideoOutputRouteFeedbacks[outputName]; - - if (feedback == null) - { - return; - } - var inPort = - InputPorts.FirstOrDefault(p => p.FeedbackMatchObject == _Chassis.HdmiOutputs[output].VideoOutFeedback); - var outPort = OutputPorts.FirstOrDefault(p => p.FeedbackMatchObject == _Chassis.HdmiOutputs[output]); - - feedback.FireUpdate(); - OnSwitchChange(new RoutingNumericEventArgs(output, inputNumber, outPort, inPort, eRoutingSignalType.AudioVideo)); - } - - void Chassis_DMInputChange(Switch device, DMInputEventArgs args) - { - switch (args.EventId) - { - case DMInputEventIds.VideoDetectedEventId: - { - Debug.LogDebug(this, "Event ID {0}: Updating VideoInputSyncFeedbacks", args.EventId); - foreach (var item in VideoInputSyncFeedbacks) - { - item.FireUpdate(); - } - break; - } - case DMInputEventIds.InputNameFeedbackEventId: - case DMInputEventIds.InputNameEventId: - case DMInputEventIds.NameFeedbackEventId: - { - Debug.LogDebug(this, "Event ID {0}: Updating name feedbacks.", args.EventId); - Debug.LogDebug(this, "Input {0} Name {1}", args.Number, - _Chassis.HdmiInputs[args.Number].NameFeedback.StringValue); - foreach (var item in InputNameFeedbacks) - { - item.FireUpdate(); - } - break; - } - default: - { - Debug.LogDebug(this, "Unhandled DM Input Event ID {0}", args.EventId); - break; - } - } - } - - #endregion - - #region Factory - - public class HdMdNxM4kEControllerFactory : EssentialsPluginDeviceFactory - { - public HdMdNxM4kEControllerFactory() - { - MinimumEssentialsFrameworkVersion = "2.4.5"; - TypeNames = new List() { "hdmd4x14ke-bridgeable", "hdmd4x24ke", "hdmd6x24ke" }; - } - - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogDebug("Factory Attempting to create new HD-MD-NxM-4K-E Device"); - - var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); - - var type = dc.Type.ToLower(); - var control = props.Control; - var ipid = control.IpIdInt; - var address = control.TcpSshProperties.Address; - - switch (type) - { - case ("hdmd4x14ke-bridgeable"): - return new HdMdNxM4kEBridgeableController(dc.Key, dc.Name, new HdMd4x14kE(ipid, address, Global.ControlSystem), props); - case ("hdmd4x24ke"): - return new HdMdNxM4kEBridgeableController(dc.Key, dc.Name, new HdMd4x24kE(ipid, address, Global.ControlSystem), props); - case ("hdmd6x24ke"): - return new HdMdNxM4kEBridgeableController(dc.Key, dc.Name, new HdMd6x24kE(ipid, address, Global.ControlSystem), props); - default: - return null; - } - } - } - - #endregion - - - - } -} \ No newline at end of file diff --git a/src/Chassis/HdMdNxM4kEController.cs b/src/Chassis/HdMdNxM4kEController.cs index 9eb53d8..c9de40d 100644 --- a/src/Chassis/HdMdNxM4kEController.cs +++ b/src/Chassis/HdMdNxM4kEController.cs @@ -3,166 +3,560 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; +using Newtonsoft.Json; using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DM; -using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.DM.Config; +using PepperDash.Essentials.Core.Bridges; +using PepperDash.Essentials.Core.Config; namespace PepperDash.Essentials.DM.Chassis { - [Obsolete("Please use HdMdNxM4kEBridgeable Controller")] - public class HdMdNxM4kEController : CrestronGenericBaseDevice, IRoutingInputsOutputs, IRouting - { - public HdMdNxM Chassis { get; private set; } - - public RoutingPortCollection InputPorts { get; private set; } - public RoutingPortCollection OutputPorts { get; private set; } - - - /// - /// - /// - /// - /// - /// - public HdMdNxM4kEController(string key, string name, HdMdNxM chassis, - HdMdNxM4kEPropertiesConfig props) - : base(key, name, chassis) - { - Debug.LogInformation(this, "Type hdmd4x14ke is obsolete. Please use hdmd4x14ke-bridgeable"); - Chassis = chassis; - - // logical ports - InputPorts = new RoutingPortCollection(); - for (uint i = 1; i <= 4; i++) - { - InputPorts.Add(new RoutingInputPort("hdmiIn" + i, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, i, this)); - } - OutputPorts = new RoutingPortCollection(); - OutputPorts.Add(new RoutingOutputPort(DmPortName.HdmiOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, null, this)); - - // physical settings - if (props != null && props.Inputs != null) - { - var inputRegex = new Regex(@"(?\d)", RegexOptions.IgnoreCase); - foreach (var kvp in props.Inputs) - { - // get numnbers from key and convert to int - //var inputNum = Convert.ToUInt32(kvp.Key.Substring(6)); - var inputMatch = inputRegex.Match(kvp.Key); - if (inputMatch == null) continue; - - var inputNum = Convert.ToUInt32(inputMatch.Groups["InputNum"].Value); - - var port = chassis.HdmiInputs[inputNum].HdmiInputPort; - // set hdcp disables - if (kvp.Value.DisableHdcp) + [Description("Wrapper class for all HdMdNxM4E switchers")] + public class HdMdNxM4kEController : CrestronGenericBridgeableBaseDevice, IRoutingNumericWithFeedback, IHasFeedback + { + private readonly HdMdNxM _Chassis; + + //IroutingNumericEvent + public event EventHandler NumericSwitchChange; + + public Dictionary InputNames { get; set; } + public Dictionary OutputNames { get; set; } + + public RoutingPortCollection InputPorts { get; private set; } + public RoutingPortCollection OutputPorts { get; private set; } + + public FeedbackCollection VideoInputSyncFeedbacks { get; private set; } + public FeedbackCollection VideoOutputRouteFeedbacks { get; private set; } + public FeedbackCollection InputNameFeedbacks { get; private set; } + public FeedbackCollection OutputNameFeedbacks { get; private set; } + public FeedbackCollection OutputRouteNameFeedbacks { get; private set; } + public FeedbackCollection InputHdcpEnableFeedback { get; private set; } + public StringFeedback DeviceNameFeedback { get; private set; } + public BoolFeedback AutoRouteFeedback { get; private set; } + public string NoRouteText { get; private set; } + + #region Constructor + + public HdMdNxM4kEController(string key, string name, HdMdNxM chassis, + HdMdNxM4kEPropertiesConfig props) + : base(key, name, chassis) + { + Name = name; + _Chassis = chassis; + if(_Chassis == null) + { + Debug.LogDebug(this, "HdMdNxM4kEBridgeableController chassis is null, failed to build the device"); + return; + } + + if (props == null) + { + Debug.LogDebug(this, "HdMdNxM4kEBridgeableController properties are null, failed to build the device"); + return; + } + + NoRouteText = props.NoRouteText ?? "None"; + + + if (props.Inputs != null) + { + foreach (var kvp in props.Inputs) + { + Debug.LogDebug(this, "props.Inputs: {0}-{1}", kvp.Key, kvp.Value); + } + InputNames = props.Inputs; + } + if (props.Outputs != null) + { + foreach (var kvp in props.Outputs) + { + Debug.LogDebug(this, "props.Outputs: {0}-{1}", kvp.Key, kvp.Value); + } + OutputNames = props.Outputs; + } + + DeviceNameFeedback = new StringFeedback("DeviceNameFeedback",()=>Name); + + VideoInputSyncFeedbacks = new FeedbackCollection(); + VideoOutputRouteFeedbacks = new FeedbackCollection(); + InputNameFeedbacks = new FeedbackCollection(); + OutputNameFeedbacks = new FeedbackCollection(); + OutputRouteNameFeedbacks = new FeedbackCollection(); + InputHdcpEnableFeedback = new FeedbackCollection(); + + InputPorts = new RoutingPortCollection(); + OutputPorts = new RoutingPortCollection(); + + if(_Chassis is HdMd4x14kE _chssis) + { + AutoRouteFeedback = new BoolFeedback("AutoRouteFeedback", () => _chssis.AutoModeOnFeedback.BoolValue); + } + + if (InputNames == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Error, "InputNames is null. Ensure 'inputs' is defined in the device configuration.", this); + return; + } + + if (OutputNames == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Error, "OutputNames is null. Ensure 'outputs' is defined in the device configuration.", this); + return; + } + + for (uint i = 1; i <= _Chassis.NumberOfInputs; i++) + { + var index = i; + if (!InputNames.TryGetValue(index, out var inputName)) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No input name defined for input {index}. Using default name.", this, index); + inputName = $"Input {index}"; + InputNames[index] = inputName; + } + //_Chassis.Inputs[index].Name.StringValue = inputName; + _Chassis.HdmiInputs[index].Name.StringValue = inputName; + + InputPorts.Add(new RoutingInputPort(inputName, eRoutingSignalType.AudioVideo, + eRoutingPortConnectionType.Hdmi, _Chassis.HdmiInputs[index], this) + { + FeedbackMatchObject = _Chassis.HdmiInputs[index] + }); + + VideoInputSyncFeedbacks.Add(new BoolFeedback(inputName, () => _Chassis.Inputs[index].VideoDetectedFeedback.BoolValue)); + //InputNameFeedbacks.Add(new StringFeedback(inputName, () => _Chassis.Inputs[index].NameFeedback.StringValue)); + InputNameFeedbacks.Add(new StringFeedback(inputName, () => InputNames[index])); + InputHdcpEnableFeedback.Add(new BoolFeedback(inputName, () => _Chassis.HdmiInputs[index].HdmiInputPort.HdcpSupportOnFeedback.BoolValue)); + } + + for (uint i = 1; i <= _Chassis.NumberOfOutputs; i++) + { + var index = i; + if (!OutputNames.TryGetValue(index, out var outputName)) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No output name defined for output {index}. Using default name.", this, index); + outputName = $"Output {index}"; + OutputNames[index] = outputName; + } + //_Chassis.Outputs[index].Name.StringValue = outputName; + //_Chassis.HdmiOutputs[index].Name.StringValue = outputName; + + OutputPorts.Add(new RoutingOutputPort(outputName, eRoutingSignalType.AudioVideo, + eRoutingPortConnectionType.Hdmi, _Chassis.HdmiOutputs[index], this) + { + FeedbackMatchObject = _Chassis.HdmiOutputs[index] + }); + VideoOutputRouteFeedbacks.Add(new IntFeedback(outputName, () => _Chassis.Outputs[index].VideoOutFeedback == null ? 0 : (int)_Chassis.Outputs[index].VideoOutFeedback.Number)); + OutputNameFeedbacks.Add(new StringFeedback(outputName, () => OutputNames[index])); + OutputRouteNameFeedbacks.Add(new StringFeedback(outputName, () => _Chassis.Outputs[index].VideoOutFeedback == null ? NoRouteText : _Chassis.Outputs[index].VideoOutFeedback.NameFeedback.StringValue)); + } + + _Chassis.DMInputChange += Chassis_DMInputChange; + _Chassis.DMOutputChange += Chassis_DMOutputChange; + + AddPostActivationAction(AddFeedbackCollections); + } + + #endregion + + #region Methods + + /// + /// Raise an event when the status of a switch object changes. + /// + /// Arguments defined as IKeyName sender, output, input, and eRoutingSignalType + private void OnSwitchChange(RoutingNumericEventArgs e) + { + var newEvent = NumericSwitchChange; + if (newEvent != null) newEvent(this, e); + } + + public void EnableHdcp(uint port) + { + if (port > _Chassis.NumberOfInputs) return; + if (port <= 0) return; + + _Chassis.HdmiInputs[port].HdmiInputPort.HdcpSupportOn(); + InputHdcpEnableFeedback[InputNames[port]].FireUpdate(); + } + + public void DisableHdcp(uint port) + { + if (port > _Chassis.NumberOfInputs) return; + if (port <= 0) return; + + _Chassis.HdmiInputs[port].HdmiInputPort.HdcpSupportOff(); + InputHdcpEnableFeedback[InputNames[port]].FireUpdate(); + } + + public void EnableAutoRoute() + { + if (_Chassis.NumberOfOutputs > 1) return; + + if(!(_Chassis is HdMd4x14kE _chassis)) return; + + _chassis.AutoModeOn(); + } + + public void DisableAutoRoute() + { + if (_Chassis.NumberOfOutputs > 1) return; + + if (!(_Chassis is HdMd4x14kE _chassis)) return; + + _chassis.AutoModeOff(); + } + + #region PostActivate + + public void AddFeedbackCollections() + { + // AddFeedbackToList(DeviceNameFeedback); + // AddCollectionsToList(VideoInputSyncFeedbacks, InputHdcpEnableFeedback); + // AddCollectionsToList(VideoOutputRouteFeedbacks); + // AddCollectionsToList(InputNameFeedbacks, OutputNameFeedbacks, OutputRouteNameFeedbacks); + + AddFeedbackToList(DeviceNameFeedback); + foreach (var fb in VideoInputSyncFeedbacks) + { + AddFeedbackToList(fb); + } + foreach (var fb in InputHdcpEnableFeedback) + { + AddFeedbackToList(fb); + } + foreach (var fb in VideoOutputRouteFeedbacks) + { + AddFeedbackToList(fb); + } + foreach (var fb in InputNameFeedbacks) + { + AddFeedbackToList(fb); + } + foreach (var fb in OutputNameFeedbacks) + { + AddFeedbackToList(fb); + } + foreach (var fb in OutputRouteNameFeedbacks) + { + AddFeedbackToList(fb); + } + } + + #endregion + + #region FeedbackCollection Methods +/* + //Add arrays of collections + public void AddCollectionsToList(params FeedbackCollection[] newFbs) + { + foreach (FeedbackCollection fbCollection in newFbs) + { + foreach (var item in newFbs) + { + AddFeedbackToList(item); + } + } + } + public void AddCollectionsToList(params FeedbackCollection[] newFbs) + { + foreach (FeedbackCollection fbCollection in newFbs) + { + foreach (var item in newFbs) + { + AddFeedbackToList(item); + } + } + } + + public void AddCollectionsToList(params FeedbackCollection[] newFbs) + { + foreach (FeedbackCollection fbCollection in newFbs) + { + foreach (var item in newFbs) + { + AddFeedbackToList(item); + } + } + } + + //Add Collections + public void AddCollectionToList(FeedbackCollection newFbs) + { + foreach (var f in newFbs.Where(f => f != null)) + { + //if (f == null) continue; + + AddFeedbackToList(f); + } + } + + public void AddCollectionToList(FeedbackCollection newFbs) + { + foreach (var f in newFbs.Where(f => f != null)) + { + //if (f == null) continue; + + AddFeedbackToList(f); + } + } + + public void AddCollectionToList(FeedbackCollection newFbs) + { + foreach (var f in newFbs.Where(f => f != null)) + { + //if (f == null) continue; + + AddFeedbackToList(f); + } + } +*/ + //Add Individual Feedbacks + public void AddFeedbackToList(PepperDash.Essentials.Core.Feedback newFb) + { + if (newFb == null) return; + + if (!Feedbacks.Contains(newFb)) + { + Feedbacks.Add(newFb); + } + } + + #endregion + + #region IRouting Members + + public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) + { + var input = inputSelector as HdMdNxMHdmiInput; //changed from HdMdNxM4kzEHdmiInput; + var output = outputSelector as HdMdNxMHdmiOutput; + Debug.LogVerbose(this, "ExecuteSwitch: input={0} output={1}", input, output); + + if (output == null) + { + Debug.LogInformation(this, "Unable to make switch. output selector is not HdMdNxMHdmiOutput"); + return; + } + + // Try to make switch only when necessary. The unit appears to toggle when already selected. + var current = output.VideoOut; + if (current != input) + output.VideoOut = input; + } + + #endregion + + #region IRoutingNumeric Members + + public void ExecuteNumericSwitch(ushort inputSelector, ushort outputSelector, eRoutingSignalType signalType) + { + var input = inputSelector == 0 ? null : _Chassis.HdmiInputs[inputSelector]; + var output = _Chassis.HdmiOutputs[outputSelector]; + + Debug.LogVerbose(this, "ExecuteNumericSwitch: input={0} output={1}", input, output); + + ExecuteSwitch(input, output, signalType); + } + + #endregion + + #endregion + + #region Bridge Linking + + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new HdMdNxM4kEControllerJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.LogInformation(this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } + + IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); + DeviceNameFeedback.LinkInputSig(trilist.StringInput[joinMap.Name.JoinNumber]); + + if (_Chassis is HdMd4x14kE _chassis) + { + trilist.SetSigTrueAction(joinMap.EnableAutoRoute.JoinNumber, () => _chassis.AutoModeOn()); + trilist.SetSigFalseAction(joinMap.EnableAutoRoute.JoinNumber, () => _chassis.AutoModeOff()); + AutoRouteFeedback?.LinkInputSig(trilist.BooleanInput[joinMap.EnableAutoRoute.JoinNumber]); + } + + for (uint i = 1; i <= _Chassis.NumberOfInputs; i++) + { + var joinIndex = i - 1; + var input = i; + //Digital + VideoInputSyncFeedbacks[InputNames[input]].LinkInputSig(trilist.BooleanInput[joinMap.InputSync.JoinNumber + joinIndex]); + InputHdcpEnableFeedback[InputNames[input]].LinkInputSig(trilist.BooleanInput[joinMap.EnableInputHdcp.JoinNumber + joinIndex]); + InputHdcpEnableFeedback[InputNames[input]].LinkComplementInputSig(trilist.BooleanInput[joinMap.DisableInputHdcp.JoinNumber + joinIndex]); + trilist.SetSigTrueAction(joinMap.EnableInputHdcp.JoinNumber + joinIndex, () => EnableHdcp(input)); + trilist.SetSigTrueAction(joinMap.DisableInputHdcp.JoinNumber + joinIndex, () => DisableHdcp(input)); + + //Serial + InputNameFeedbacks[InputNames[input]].LinkInputSig(trilist.StringInput[joinMap.InputName.JoinNumber + joinIndex]); + } + + for (uint i = 1; i <= _Chassis.NumberOfOutputs; i++) + { + var joinIndex = i - 1; + var output = i; + //Analog + VideoOutputRouteFeedbacks[OutputNames[output]].LinkInputSig(trilist.UShortInput[joinMap.OutputRoute.JoinNumber + joinIndex]); + trilist.SetUShortSigAction(joinMap.OutputRoute.JoinNumber + joinIndex, (a) => ExecuteNumericSwitch(a, (ushort) output, eRoutingSignalType.AudioVideo)); + + //Serial + OutputNameFeedbacks[OutputNames[output]].LinkInputSig(trilist.StringInput[joinMap.OutputName.JoinNumber + joinIndex]); + OutputRouteNameFeedbacks[OutputNames[output]].LinkInputSig(trilist.StringInput[joinMap.OutputRoutedName.JoinNumber + joinIndex]); + } + + _Chassis.OnlineStatusChange += Chassis_OnlineStatusChange; + + trilist.OnlineStatusChange += (d, args) => + { + if (!args.DeviceOnLine) return; + + // feedback updates was moved to the Chassis_OnlineStatusChange + // due to the amount of time it takes for the device to come online + }; + } + + + #endregion + + #region Events + + void Chassis_OnlineStatusChange(Crestron.SimplSharpPro.GenericBase currentDevice, Crestron.SimplSharpPro.OnlineOfflineEventArgs args) + { + IsOnline.FireUpdate(); + + if (!args.DeviceOnLine) return; + + foreach (var feedback in Feedbacks) + { + feedback.FireUpdate(); + } + + if(_Chassis is HdMd4x14kE) + { + AutoRouteFeedback?.FireUpdate(); + } + } + + void Chassis_DMOutputChange(Switch device, DMOutputEventArgs args) + { + if (args.EventId != DMOutputEventIds.VideoOutEventId) return; + + var output = args.Number; + + var inputNumber = _Chassis.HdmiOutputs[output].VideoOutFeedback == null + ? 0 + : _Chassis.HdmiOutputs[output].VideoOutFeedback.Number; + + var outputName = OutputNames[output]; + + var feedback = VideoOutputRouteFeedbacks[outputName]; + + if (feedback == null) + { + return; + } + var inPort = + InputPorts.FirstOrDefault(p => p.FeedbackMatchObject == _Chassis.HdmiOutputs[output].VideoOutFeedback); + var outPort = OutputPorts.FirstOrDefault(p => p.FeedbackMatchObject == _Chassis.HdmiOutputs[output]); + + feedback.FireUpdate(); + OnSwitchChange(new RoutingNumericEventArgs(output, inputNumber, outPort, inPort, eRoutingSignalType.AudioVideo)); + } + + void Chassis_DMInputChange(Switch device, DMInputEventArgs args) + { + switch (args.EventId) + { + case DMInputEventIds.VideoDetectedEventId: + { + Debug.LogDebug(this, "Event ID {0}: Updating VideoInputSyncFeedbacks", args.EventId); + foreach (var item in VideoInputSyncFeedbacks) { - Debug.LogInformation(this, "Configuration disables HDCP support on {0}", kvp.Key); - port.HdcpSupportOff(); + item.FireUpdate(); } - else - port.HdcpSupportOn(); - } - } - } - - public override bool CustomActivate() - { - var result = Chassis.Register(); - if (result != Crestron.SimplSharpPro.eDeviceRegistrationUnRegistrationResponse.Success) - { - Debug.LogInformation(this, "Device registration failed: {0}", result); - return false; - } - - return base.CustomActivate(); - } - - - - #region IRouting Members - - public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) - { - // Try to make switch only when necessary. The unit appears to toggle when already selected. - var current = Chassis.HdmiOutputs[1].VideoOut; - if (current != Chassis.HdmiInputs[(uint)inputSelector]) - Chassis.HdmiOutputs[1].VideoOut = Chassis.HdmiInputs[(uint)inputSelector]; - } - - #endregion - - ///////////////////////////////////////////////////// - - /// - /// - /// - /// - /// - /// - /// - /// - /// /* - /* - public static HdMdNxM4kEController GetController(string key, string name, - string type, HdMdNxM4kEPropertiesConfig properties) - { - try - { - var ipid = properties.Control.IpIdInt; - var address = properties.Control.TcpSshProperties.Address; - - type = type.ToLower(); - if (type == "hdmd4x14ke") - { - Debug.Console(0, @"The 'hdmd4x14ke' device is not an Essentials Bridgeable device. - If an essentials Bridgeable Device is required, use the 'hdmd4x14ke-bridgeable' type"); - - var chassis = new HdMd4x14kE(ipid, address, Global.ControlSystem); - return new HdMdNxM4kEController(key, name, chassis, properties); - } - return null; - } - catch (Exception e) - { - Debug.LogInformation("ERROR Creating device key {0}: \r{1}", key, e); - return null; - } - }*/ - - #region Factory - - public class HdMdNxM4kEFactory : EssentialsPluginDeviceFactory - { - public HdMdNxM4kEFactory() - { - TypeNames = new List() {"hdmd4x14ke"}; - } - - - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogDebug("Factory Attempting to create new HD-MD-NxM-4K-E Device"); - - var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); - - var type = dc.Type.ToLower(); - var control = props.Control; - var ipid = control.IpIdInt; - var address = control.TcpSshProperties.Address; - - return new HdMdNxM4kEController(dc.Key, dc.Name, new HdMd4x14kE(ipid, address, Global.ControlSystem), props); - - } - } - - #endregion - - } + break; + } + case DMInputEventIds.InputNameFeedbackEventId: + case DMInputEventIds.InputNameEventId: + case DMInputEventIds.NameFeedbackEventId: + { + Debug.LogDebug(this, "Event ID {0}: Updating name feedbacks.", args.EventId); + Debug.LogDebug(this, "Input {0} Name {1}", args.Number, + _Chassis.HdmiInputs[args.Number].NameFeedback.StringValue); + foreach (var item in InputNameFeedbacks) + { + item.FireUpdate(); + } + break; + } + default: + { + Debug.LogDebug(this, "Unhandled DM Input Event ID {0}", args.EventId); + break; + } + } + } + + #endregion + + #region Factory + + public class HdMdNxM4kEControllerFactory : EssentialsPluginDeviceFactory + { + public HdMdNxM4kEControllerFactory() + { + MinimumEssentialsFrameworkVersion = "2.4.5"; + TypeNames = new List() { "hdmd4x14ke-bridgeable", "hdmd4x14ke", "hdmd4x24ke", "hdmd6x24ke" }; + } + + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogDebug("Factory Attempting to create new HD-MD-NxM-4K-E Device"); + + var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); + + var type = dc.Type.ToLower(); + var control = props.Control; + var ipid = control.IpIdInt; + var address = control.TcpSshProperties.Address; + + switch (type) + { + case ("hdmd4x14ke-bridgeable"): + case ("hdmd4x14ke"): + return new HdMdNxM4kEController(dc.Key, dc.Name, new HdMd4x14kE(ipid, address, Global.ControlSystem), props); + case ("hdmd4x24ke"): + return new HdMdNxM4kEController(dc.Key, dc.Name, new HdMd4x24kE(ipid, address, Global.ControlSystem), props); + case ("hdmd6x24ke"): + return new HdMdNxM4kEController(dc.Key, dc.Name, new HdMd6x24kE(ipid, address, Global.ControlSystem), props); + default: + return null; + } + } + } + + #endregion + + + + } } \ No newline at end of file diff --git a/src/Config/HdMdNxM4kEPropertiesConfig.cs b/src/Config/HdMdNxM4kEPropertiesConfig.cs index 2873e2b..d7f43d5 100644 --- a/src/Config/HdMdNxM4kEPropertiesConfig.cs +++ b/src/Config/HdMdNxM4kEPropertiesConfig.cs @@ -17,14 +17,8 @@ public class HdMdNxM4kEPropertiesConfig [JsonProperty("control")] public ControlPropertiesConfig Control { get; set; } - [JsonProperty("inputs")] - public Dictionary Inputs { get; set; } - } - - public class HdMdNxM4kEBridgeablePropertiesConfig - { - [JsonProperty("control")] - public ControlPropertiesConfig Control { get; set; } + [JsonProperty("noRouteText")] + public string NoRouteText { get; set; } [JsonProperty("inputs")] public Dictionary Inputs { get; set; } diff --git a/src/PepperDash.Essentials.DM.csproj b/src/PepperDash.Essentials.DM.csproj index 3faf164..11240b0 100644 --- a/src/PepperDash.Essentials.DM.csproj +++ b/src/PepperDash.Essentials.DM.csproj @@ -1,21 +1,21 @@ - - ProgramLibrary - + + ProgramLibrary + net472 PepperDash.Essentials.DM false PepperDash.Essentials.DM - This software is a PepperDash Essentials Plugin for PepperDash.Essentials.DM. + This software is a PepperDash Essentials Plugin for PepperDash.Essentials.DM. true - 4Series\bin\$(Configuration)\ + 4Series\bin\$(Configuration)\ PepperDash.Essentials.Plugin.4Series.DM https://github.com/PepperDash/PepperDash.Essentials.DM.git crestron 4series essentials plugin PepperDash Essentials DM True - PepperDash Essentials DM 4Series Plugin + PepperDash Essentials DM 4Series Plugin git https://github.com/PepperDash/PepperDash.Essentials.DM crestron;4series;