diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/HdPsXxxControllerJoinMap.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/HdPsXxxControllerJoinMap.cs new file mode 100644 index 00000000..3f2901c9 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/HdPsXxxControllerJoinMap.cs @@ -0,0 +1,190 @@ +using System; +using PepperDash.Essentials.Core; + +namespace PepperDash_Essentials_Core.Bridges +{ + public class HdPsXxxControllerJoinMap : JoinMapBaseAdvanced + { + + #region Digital + + [JoinName("EnableAutoRoute")] + public JoinDataComplete EnableAutoRoute = new JoinDataComplete( + new JoinData + { + JoinNumber = 1, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Enable Automatic Routing on Xx1 Switchers", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + + [JoinName("InputSync")] + public JoinDataComplete InputSync = new JoinDataComplete( + new JoinData + { + JoinNumber = 2, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "Device Input Sync", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + + [JoinName("EnableInputHdcp")] + public JoinDataComplete EnableInputHdcp = new JoinDataComplete( + new JoinData + { + JoinNumber = 11, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "Device Enable Input Hdcp", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + + [JoinName("DisableInputHdcp")] + public JoinDataComplete DisableInputHdcp = new JoinDataComplete( + new JoinData + { + JoinNumber = 21, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "Device Disnable Input Hdcp", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete( + new JoinData + { + JoinNumber = 30, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Device Onlne", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + #endregion + + + #region Analog + + [JoinName("OutputRoute")] + public JoinDataComplete OutputRoute = new JoinDataComplete( + new JoinData + { + JoinNumber = 11, + JoinSpan = 2 + }, + new JoinMetadata + { + Description = "Device Output Route Set/Get", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + + #endregion + + + #region Serial + + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete( + new JoinData + { + JoinNumber = 1, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Device Name", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + + [JoinName("InputName")] + public JoinDataComplete InputName = new JoinDataComplete( + new JoinData + { + JoinNumber = 2, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "Device Input Name", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + + [JoinName("OutputName")] + public JoinDataComplete OutputName = new JoinDataComplete( + new JoinData + { + JoinNumber = 11, + JoinSpan = 2 + }, + new JoinMetadata + { + Description = "Device Output Name", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + + [JoinName("OutputRoutedName")] + public JoinDataComplete OutputRoutedName = new JoinDataComplete( + new JoinData + { + JoinNumber = 16, + JoinSpan = 2 + }, + new JoinMetadata + { + Description = "Device Output Route Name", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + + #endregion + + /// + /// Constructor to use when instantiating this join map without inheriting from it + /// + /// Join this join map will start at + public HdPsXxxControllerJoinMap(uint joinStart) + : this(joinStart, typeof(HdPsXxxControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected HdPsXxxControllerJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Comm and IR/CommFactory.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Comm and IR/CommFactory.cs index 1742fac4..d5bc56fe 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Comm and IR/CommFactory.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Comm and IR/CommFactory.cs @@ -124,41 +124,54 @@ namespace PepperDash.Essentials.Core /// public static ICec GetCecPort(ControlPropertiesConfig config) { - var dev = DeviceManager.GetDeviceForKey(config.ControlPortDevKey); + try + { + var dev = DeviceManager.GetDeviceForKey(config.ControlPortDevKey); - if (dev != null) - { - if (!String.IsNullOrEmpty(config.ControlPortName)) - { + Debug.Console(0, "GetCecPort: device '{0}' {1}", config.ControlPortDevKey, dev == null + ? "is not valid, failed to get cec port" + : "found in device manager, attempting to get cec port"); - var inputPort = (dev as IRoutingInputsOutputs).InputPorts[config.ControlPortName]; + if (dev == null) + return null; - if (inputPort != null) - { - if (inputPort.Port is ICec) - return inputPort.Port as ICec; - } + if (String.IsNullOrEmpty(config.ControlPortName)) + { + Debug.Console(0, "GetCecPort: '{0}' - Configuration missing 'ControlPortName'", config.ControlPortDevKey); + return null; + } - var outputPort = (dev as IRoutingInputsOutputs).OutputPorts[config.ControlPortName]; - if (outputPort != null) - { - if (outputPort.Port is ICec) - return outputPort.Port as ICec; - } + var inputsOutputs = dev as IRoutingInputsOutputs; + if (inputsOutputs == null) + { + Debug.Console(0, "GetCecPort: Device '{0}' does not support IRoutingInputsOutputs, failed to get CEC port called '{1}'", + config.ControlPortDevKey, config.ControlPortName); - else - Debug.Console(0, "GetCecPort: Device '{0}' does not have a CEC port called: '{1}'", - config.ControlPortDevKey, config.ControlPortName); - } - else - { - Debug.Console(0, "GetCecPort: '{0}' - Configuration missing 'ControlPortName'", config.ControlPortDevKey); - } - } - Debug.Console(0, "GetCecPort: Device '{0}' is not a valid device.", config.ControlPortDevKey); + return null; + } - return null; + var inputPort = inputsOutputs.InputPorts[config.ControlPortName]; + if (inputPort != null && inputPort.Port is ICec) + return inputPort.Port as ICec; + + + var outputPort = inputsOutputs.OutputPorts[config.ControlPortName]; + if (outputPort != null && outputPort.Port is ICec) + return outputPort.Port as ICec; + } + catch (Exception ex) + { + Debug.Console(1, "GetCecPort Exception Message: {0}", ex.Message); + Debug.Console(2, "GetCecPort Exception StackTrace: {0}", ex.StackTrace); + if (ex.InnerException != null) + Debug.Console(0, "GetCecPort Exception InnerException: {0}", ex.InnerException); + } + + Debug.Console(0, "GetCecPort: Device '{0}' does not have a CEC port called '{1}'", + config.ControlPortDevKey, config.ControlPortName); + + return null; } /// diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Device Info/NetworkDeviceHelpers.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Device Info/NetworkDeviceHelpers.cs new file mode 100644 index 00000000..04e697c9 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Device Info/NetworkDeviceHelpers.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using PepperDash.Core; +using Crestron.SimplSharp; +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials.Core.DeviceInfo +{ + public static class NetworkDeviceHelpers + { + /// + /// Event raised when ArpTable changes + /// + public static event ArpTableEventHandler ArpTableUpdated; + + /// + /// Delegate called by ArpTableUpdated + /// + /// contains the entire ARP table and a bool to note if there was an error in retrieving the data + public delegate void ArpTableEventHandler(ArpTableEventArgs args); + + private static readonly char NewLineSplitter = CrestronEnvironment.NewLine.ToCharArray().First(); + private static readonly string NewLine = CrestronEnvironment.NewLine; + + private static readonly CCriticalSection Lock = new CCriticalSection(); + + /// + /// Last resolved ARP table - it is recommended to refresh the arp before using this. + /// + public static List ArpTable { get; private set; } + + /// + /// Force recheck of ARP table + /// + public static void RefreshArp() + { + var error = false; + try + { + Lock.Enter(); + var consoleResponse = string.Empty; + if (!CrestronConsole.SendControlSystemCommand("showarptable", ref consoleResponse)) return; + if (string.IsNullOrEmpty(consoleResponse)) + { + error = true; + return; + } + ArpTable.Clear(); + + Debug.Console(2, "ConsoleResponse of 'showarptable' : {0}{1}", NewLine, consoleResponse); + + var myLines = + consoleResponse.Split(NewLineSplitter) + .ToList() + .Where(o => (o.Contains(':') && !o.Contains("Type", StringComparison.OrdinalIgnoreCase))) + .ToList(); + foreach (var line in myLines) + { + var item = line; + var seperator = item.Contains('\t') ? '\t' : ' '; + var dataPoints = item.Split(seperator); + if (dataPoints == null || dataPoints.Length < 2) continue; + var ipAddress = SanitizeIpAddress(dataPoints.First().TrimAll()); + var macAddress = dataPoints.Last(); + ArpTable.Add(new ArpEntry(ipAddress, macAddress)); + } + } + catch (Exception ex) + { + Debug.Console(0, "Exception in \"RefreshArp\" : {0}", ex.Message); + error = true; + } + finally + { + Lock.Leave(); + OnArpTableUpdated(new ArpTableEventArgs(ArpTable, error)); + } + } + + + private static void OnArpTableUpdated(ArpTableEventArgs args) + { + if (args == null) return; + var handler = ArpTableUpdated; + if (handler == null) return; + handler.Invoke(args); + } + + static NetworkDeviceHelpers() + { + ArpTable = new List(); + } + + /// + /// Removes leading zeros, leading whitespace, and trailing whitespace from an IPAddress string + /// + /// Ip Address to Santitize + /// Sanitized Ip Address + public static string SanitizeIpAddress(string ipAddressIn) + { + try + { + var ipAddress = IPAddress.Parse(ipAddressIn.TrimStart('0')); + return ipAddress.ToString(); + } + catch (Exception ex) + { + Debug.Console(0, "Unable to Santize Ip : {0}", ex.Message); + return ipAddressIn; + } + } + + /// + /// Resolves a hostname by IP Address using DNS + /// + /// IP Address to resolve from + /// Resolved Hostname - on failure to determine hostname, will return IP Address + public static string ResolveHostnameFromIp(string ipAddress) + { + try + { + var santitizedIp = SanitizeIpAddress(ipAddress); + var hostEntry = Dns.GetHostEntry(santitizedIp); + return hostEntry == null ? ipAddress : hostEntry.HostName; + } + catch (Exception ex) + { + Debug.Console(0, "Exception Resolving Hostname from IP Address : {0}", ex.Message); + return ipAddress; + } + } + + /// + /// Resolves an IP Address by hostname using DNS + /// + /// Hostname to resolve from + /// Resolved IP Address - on a failure to determine IP Address, will return hostname + public static string ResolveIpFromHostname(string hostName) + { + try + { + var hostEntry = Dns.GetHostEntry(hostName); + return hostEntry == null ? hostName : hostEntry.AddressList.First().ToString(); + } + catch (Exception ex) + { + Debug.Console(0, "Exception Resolving IP Address from Hostname : {0}", ex.Message); + return hostName; + } + } + + } + + /// + /// Object to hold data about an arp entry + /// + public class ArpEntry + { + public readonly IPAddress IpAddress; + public readonly string MacAddress; + + /// + /// Constructs new ArpEntry object + /// + /// string formatted as ipv4 address + /// mac address string - format is unimportant + public ArpEntry(string ipAddress, string macAddress) + { + if (string.IsNullOrEmpty(ipAddress)) + { + throw new ArgumentException("\"ipAddress\" cannot be null or empty"); + } + if (string.IsNullOrEmpty(macAddress)) + { + throw new ArgumentException("\"macAddress\" cannot be null or empty"); + } + IpAddress = IPAddress.Parse(ipAddress.TrimStart().TrimStart('0').TrimEnd()); + MacAddress = macAddress; + } + } + + /// + /// Arguments passed by the ArpTableUpdated event + /// + public class ArpTableEventArgs : EventArgs + { + /// + /// The retrieved ARP Table + /// + public readonly List ArpTable; + /// + /// True if there was a problem retrieving the ARP Table + /// + public readonly bool Error; + + /// + /// Constructor for ArpTableEventArgs + /// + /// The entirety of the retrieved ARP table + /// True of an error was encountered updating the ARP table + public ArpTableEventArgs(List arpTable, bool error) + { + ArpTable = arpTable; + Error = error; + } + + /// + /// Constructor for ArpTableEventArgs - assumes no error encountered in retrieving ARP Table + /// + /// The entirety of the retrieved ARP table + public ArpTableEventArgs(List arpTable) + { + ArpTable = arpTable; + Error = false; + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Extensions/StringExtensions.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Extensions/StringExtensions.cs index 39501387..7bf8d5a5 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Extensions/StringExtensions.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Extensions/StringExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,17 +9,70 @@ namespace PepperDash.Essentials.Core { public static class StringExtensions { + /// + /// Returns null if a string is empty, otherwise returns the string + /// + /// string input + /// null if the string is emtpy, otherwise returns the string public static string NullIfEmpty(this string s) { return string.IsNullOrEmpty(s) ? null : s; } + + /// + /// Returns null if a string is empty or made of only whitespace characters, otherwise returns the string + /// + /// string input + /// null if the string is wempty or made of only whitespace characters, otherwise returns the string public static string NullIfWhiteSpace(this string s) { return string.IsNullOrEmpty(s.Trim()) ? null : s; } + + /// + /// Returns a replacement string if the input string is empty or made of only whitespace characters, otherwise returns the input string + /// + /// input string + /// string to replace with if input string is empty or whitespace + /// returns newString if s is null, emtpy, or made of whitespace characters, otherwise returns s public static string ReplaceIfNullOrEmpty(this string s, string newString) { return string.IsNullOrEmpty(s) ? newString : s; } + + /// + /// Overload for Contains that allows setting an explicit String Comparison + /// + /// Source String + /// String to check in Source String + /// Comparison parameters + /// true of string contains "toCheck" + public static bool Contains(this string source, string toCheck, StringComparison comp) + { + if (string.IsNullOrEmpty(source)) return false; + return source.IndexOf(toCheck, comp) >= 0; + } + + /// + /// Performs TrimStart() and TrimEnd() on source string + /// + /// String to Trim + /// Trimmed String + public static string TrimAll(this string source) + { + return string.IsNullOrEmpty(source) ? string.Empty : source.TrimStart().TrimEnd(); + } + + /// + /// Performs TrimStart(chars char[]) and TrimEnd(chars char[]) on source string. + /// + /// String to Trim + /// Char Array to trim from string + /// Trimmed String + public static string TrimAll(this string source, char[] chars) + { + return string.IsNullOrEmpty(source) ? string.Empty : source.TrimStart(chars).TrimEnd(chars); + } + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Monitoring/SystemMonitorController.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Monitoring/SystemMonitorController.cs index 319a0a3c..8a7f379a 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Monitoring/SystemMonitorController.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Monitoring/SystemMonitorController.cs @@ -563,11 +563,11 @@ namespace PepperDash.Essentials.Core.Monitoring ProgramUnregisteredFeedback = new BoolFeedback(() => Program.RegistrationState == eProgramRegistrationState.Unregister); ProgramUnregisteredFeedback.FireUpdate(); - - ProgramNameFeedback = new StringFeedback(() => ProgramInfo.ProgramFile); + + ProgramNameFeedback = new StringFeedback(() => ProgramInfo.ProgramFile); + CrestronDataBaseVersionFeedback = new StringFeedback(() => ProgramInfo.CrestronDb); + EnvironmentVersionFeedback = new StringFeedback(() => ProgramInfo.Environment); ProgramCompileTimeFeedback = new StringFeedback(() => ProgramInfo.CompileTime); - CrestronDataBaseVersionFeedback = new StringFeedback(() => ProgramInfo.CrestronDb); - EnvironmentVersionFeedback = new StringFeedback(() => ProgramInfo.Environment); AggregatedProgramInfoFeedback = new StringFeedback(() => JsonConvert.SerializeObject(ProgramInfo)); GetProgramInfo(); @@ -620,9 +620,9 @@ namespace PepperDash.Essentials.Core.Monitoring // Assume no valid program info. Constructing a new object will wipe all properties ProgramInfo = new ProgramInfo(Program.Number) { - OperatingState = Program.OperatingState, + OperatingState = Program.OperatingState, RegistrationState = Program.RegistrationState - }; + }; UpdateFeedbacks(); @@ -639,13 +639,20 @@ namespace PepperDash.Essentials.Core.Monitoring if (ProgramInfo.ProgramFile.Contains(".dll")) { - // SSP Program + // SSP Program ProgramInfo.FriendlyName = ParseConsoleData(response, "Friendly Name", ": ", "\n"); ProgramInfo.ApplicationName = ParseConsoleData(response, "Application Name", ": ", "\n"); ProgramInfo.ProgramTool = ParseConsoleData(response, "Program Tool", ": ", "\n"); ProgramInfo.MinFirmwareVersion = ParseConsoleData(response, "Min Firmware Version", ": ", "\n"); ProgramInfo.PlugInVersion = ParseConsoleData(response, "PlugInVersion", ": ", "\n"); + + ProgramInfo.ProgramFile += string.Format(" {0}.{1}.{2}", + ProgramInfo.CompilerRevisionInfo.Major, + ProgramInfo.CompilerRevisionInfo.Minor, + ProgramInfo.CompilerRevisionInfo.Build); + + ProgramInfo.Environment = ProgramInfo.ProgramTool; } else if (ProgramInfo.ProgramFile.Contains(".smw")) { @@ -736,6 +743,15 @@ namespace PepperDash.Essentials.Core.Monitoring [JsonProperty("compilerRevision")] public string CompilerRevision { get; set; } + [JsonIgnore] + public Version CompilerRevisionInfo + { + get + { + return new Version(CompilerRevision); + } + } + [JsonProperty("compileTime")] public string CompileTime { get; set; } @@ -776,7 +792,7 @@ namespace PepperDash.Essentials.Core.Monitoring ProgramFile = ""; FriendlyName = ""; CompilerRevision = ""; - CompileTime = ""; + CompileTime = ""; Include4Dat = ""; SystemName = ""; diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj index 4c01cc6a..6b7a3ad9 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj @@ -128,6 +128,7 @@ + @@ -202,6 +203,7 @@ + diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingInterfaces.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingInterfaces.cs index 467bf045..45245066 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingInterfaces.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingInterfaces.cs @@ -204,4 +204,9 @@ namespace PepperDash.Essentials.Core SigType = sigType; } } + + public interface IRoutingHasVideoInputSyncFeedbacks + { + FeedbackCollection VideoInputSyncFeedbacks { get; } + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingPort.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingPort.cs index 79dd4eda..ab64f15e 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingPort.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingPort.cs @@ -42,7 +42,7 @@ namespace PepperDash.Essentials.Core public enum eRoutingPortConnectionType { None, BackplaneOnly, DisplayPort, Dvi, Hdmi, Rgb, Vga, LineAudio, DigitalAudio, Sdi, - Composite, Component, DmCat, DmMmFiber, DmSmFiber, Speaker, Streaming + Composite, Component, DmCat, DmMmFiber, DmSmFiber, Speaker, Streaming, UsbC, HdBaseT } /// diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingPortNames.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingPortNames.cs index 00e85191..7029443b 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingPortNames.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingPortNames.cs @@ -199,5 +199,45 @@ namespace PepperDash.Essentials.Core.Routing /// MediaPlayer /// public const string MediaPlayer = "mediaPlayer"; - } + /// + /// UsbCIn + /// + public const string UsbCIn = "usbCIn"; + /// + /// UsbCIn1 + /// + public const string UsbCIn1 = "usbCIn1"; + /// + /// UsbCIn2 + /// + public const string UsbCIn2 = "usbCIn2"; + /// + /// UsbCIn3 + /// + public const string UsbCIn3 = "usbCIn3"; + /// + /// UsbCOut + /// + public const string UsbCOut = "usbCOut"; + /// + /// UsbCOut1 + /// + public const string UsbCOut1 = "usbCOut1"; + /// + /// UsbCOut2 + /// + public const string UsbCOut2 = "usbCOut2"; + /// + /// UsbCOut3 + /// + public const string UsbCOut3 = "usbCOut3"; + /// + /// HdBaseTIn + /// + public const string HdBaseTIn = "hdBaseTIn"; + /// + /// HdBaseTOut + /// + public const string HdBaseTOut = "hdBaseTOut"; + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Touchpanels/Mpc3Touchpanel.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Touchpanels/Mpc3Touchpanel.cs index f4611a70..44a758f8 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Touchpanels/Mpc3Touchpanel.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Touchpanels/Mpc3Touchpanel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; using Crestron.SimplSharpPro; using Newtonsoft.Json; @@ -26,7 +27,16 @@ namespace PepperDash.Essentials.Core.Touchpanels return; } + if (_touchpanel.Registerable) + { + var registrationResponse = _touchpanel.Register(); + Debug.Console(0, this, "touchpanel registration response: {0}", registrationResponse); + } + + _touchpanel.BaseEvent += _touchpanel_BaseEvent; _touchpanel.ButtonStateChange += _touchpanel_ButtonStateChange; + _touchpanel.PanelStateChange += _touchpanel_PanelStateChange; + _buttons = buttons; if (_buttons == null) { @@ -45,6 +55,8 @@ namespace PepperDash.Essentials.Core.Touchpanels InitializeButton(buttonKey, buttonConfig); InitializeButtonFeedback(buttonKey, buttonConfig); } + + ListButtons(); }); } @@ -65,63 +77,72 @@ namespace PepperDash.Essentials.Core.Touchpanels TryParseInt(key, out buttonNumber); var buttonEventTypes = config.EventTypes; + BoolOutputSig enabledFb = null; + BoolOutputSig disabledFb = null; switch (key) { case ("power"): { - if (buttonEventTypes == null) + if (buttonEventTypes == null || buttonEventTypes.Keys == null) _touchpanel.DisablePowerButton(); else _touchpanel.EnablePowerButton(); - break; - } - case ("volumeup"): - { - if (buttonEventTypes == null) - _touchpanel.DisableVolumeUpButton(); - - break; - } - case ("volumedown"): - { - if (buttonEventTypes == null) - _touchpanel.DisableVolumeDownButton(); - - break; - } - case ("volumefeedback"): - { + + enabledFb = _touchpanel.PowerButtonEnabledFeedBack; + disabledFb = _touchpanel.PowerButtonDisabledFeedBack; break; } + //case ("volumeup"): + // { + // break; + // } + //case ("volumedown"): + // { + // break; + // } + //case ("volumefeedback"): + // { + // break; + // } case ("mute"): { - if (buttonEventTypes == null) + if (buttonEventTypes == null || buttonEventTypes.Keys == null) _touchpanel.DisableMuteButton(); else _touchpanel.EnableMuteButton(); + + enabledFb = _touchpanel.MuteButtonEnabledFeedBack; + disabledFb = _touchpanel.MuteButtonDisabledFeedBack; + break; } default: { - if (buttonNumber == 0) + if (buttonNumber == 0 || buttonNumber > 9) break; - if (buttonEventTypes == null) + if (buttonEventTypes == null || buttonEventTypes.Keys == null) _touchpanel.DisableNumericalButton((uint)buttonNumber); else _touchpanel.EnableNumericalButton((uint)buttonNumber); + + if (_touchpanel.NumericalButtonEnabledFeedBack != null) + enabledFb = _touchpanel.NumericalButtonEnabledFeedBack[(uint)buttonNumber]; + + if (_touchpanel.NumericalButtonDisabledFeedBack != null) + disabledFb = _touchpanel.NumericalButtonDisabledFeedBack[(uint)buttonNumber]; + break; } } - Debug.Console(1, this, "Button '{0}' {1}", key, buttonEventTypes == null - ? "is disabled, verify eventTypes are configured." - : "is enabled"); + Debug.Console(0, this, "InitializeButton: key-'{0}' enabledFb-'{1}', disabledFb-'{2}'", + key, enabledFb ?? (object)"null", disabledFb ?? (object)"null"); } /// @@ -131,9 +152,11 @@ namespace PepperDash.Essentials.Core.Touchpanels /// public void InitializeButtonFeedback(string key, KeypadButton config) { + //Debug.Console(1, this, "Initializing button '{0}' feedback...", key); + if (config == null) { - Debug.Console(1, this, "Button '{0}' config is null, unable to initialize feedback", key); + Debug.Console(1, this, "Button '{0}' config is null, skipping.", key); return; } @@ -142,39 +165,63 @@ namespace PepperDash.Essentials.Core.Touchpanels // Link up the button feedbacks to the specified device feedback var buttonFeedback = config.Feedback; - if (buttonFeedback == null) + if (buttonFeedback == null || string.IsNullOrEmpty(buttonFeedback.DeviceKey)) { - Debug.Console(1, this, "Button '{0}' feedback not configured and will not be implemented. Verify feedback is configured if required.", key); + Debug.Console(1, this, "Button '{0}' feedback not configured, skipping.", + key); return; } - var device = DeviceManager.GetDeviceForKey(buttonFeedback.DeviceKey) as Device; - if (device == null) + Feedback deviceFeedback; + + try { - Debug.Console(1, this, "Button '{0}' feedback device with key '{0}' not found, feedback will not be implemented. Verify feedback deviceKey is properly configured.", - buttonFeedback.DeviceKey); + var device = DeviceManager.GetDeviceForKey(buttonFeedback.DeviceKey) as Device; + if (device == null) + { + Debug.Console(1, this, "Button '{0}' feedback deviceKey '{1}' not found.", + key, buttonFeedback.DeviceKey); + return; + } + + deviceFeedback = device.GetFeedbackProperty(buttonFeedback.FeedbackName); + if (deviceFeedback == null) + { + Debug.Console(1, this, "Button '{0}' feedbackName property '{1}' not found.", + key, buttonFeedback.FeedbackName); + return; + } + + // TODO [ ] verify if this can replace the current method + //Debug.Console(0, this, "deviceFeedback.GetType().Name: '{0}'", deviceFeedback.GetType().Name); + //switch (feedback.GetType().Name.ToLower()) + //{ + // case("boolfeedback"): + // { + // break; + // } + // case("intfeedback"): + // { + // break; + // } + // case("stringfeedback"): + // { + // break; + // } + //} + } + catch (Exception ex) + { + Debug.Console(1, this, "InitializeButtonFeedback (button '{1}', deviceKey '{2}') Exception Message: {0}", + ex.Message, key, buttonFeedback.DeviceKey); + Debug.Console(2, this, "InitializeButtonFeedback (button '{1}', deviceKey '{2}') Exception StackTrace: {0}", + ex.StackTrace, key, buttonFeedback.DeviceKey); + if (ex.InnerException != null) Debug.Console(2, this, "InitializeButtonFeedback (button '{1}', deviceKey '{2}') InnerException: {0}", + ex.InnerException, key, buttonFeedback.DeviceKey); + return; } - // TODO [ ] verify if this can replace the current method - var deviceFeedback = device.GetFeedbackProperty(buttonFeedback.FeedbackName); - Debug.Console(0, this, "deviceFeedback.GetType().Name: '{0}'", deviceFeedback.GetType().Name); - //switch (feedback.GetType().Name.ToLower()) - //{ - // case("boolfeedback"): - // { - // break; - // } - // case("intfeedback"): - // { - // break; - // } - // case("stringfeedback"): - // { - // break; - // } - //} - var boolFeedback = deviceFeedback as BoolFeedback; var intFeedback = deviceFeedback as IntFeedback; @@ -229,9 +276,14 @@ namespace PepperDash.Essentials.Core.Touchpanels } } + private void _touchpanel_BaseEvent(GenericBase device, BaseEventArgs args) + { + Debug.Console(1, this, "BaseEvent: eventId-'{0}', index-'{1}'", args.EventId, args.Index); + } + private void _touchpanel_ButtonStateChange(GenericBase device, Crestron.SimplSharpPro.DeviceSupport.ButtonEventArgs args) { - Debug.Console(1, this, "Button {0} ({1}), {2}", args.Button.Number, args.Button.Name, args.NewButtonState); + Debug.Console(1, this, "ButtonStateChange: buttonNumber-'{0}' buttonName-'{1}', buttonState-'{2}'", args.Button.Number, args.Button.Name, args.NewButtonState); var type = args.NewButtonState.ToString(); if (_buttons.ContainsKey(args.Button.Number.ToString(CultureInfo.InvariantCulture))) @@ -244,6 +296,11 @@ namespace PepperDash.Essentials.Core.Touchpanels } } + private void _touchpanel_PanelStateChange(GenericBase device, BaseEventArgs args) + { + Debug.Console(1, this, "PanelStateChange: eventId-'{0}', index-'{1}'", args.EventId, args.Index); + } + /// /// Runs the function associated with this button/type. One of the following strings: /// Pressed, Released, Tapped, DoubleTapped, Held, HeldReleased @@ -252,6 +309,8 @@ namespace PepperDash.Essentials.Core.Touchpanels /// public void Press(string buttonKey, string type) { + Debug.Console(2, this, "Press: buttonKey-'{0}', type-'{1}'", buttonKey, type); + // TODO: In future, consider modifying this to generate actions at device activation time // to prevent the need to dynamically call the method via reflection on each button press if (!_buttons.ContainsKey(buttonKey)) return; @@ -261,6 +320,23 @@ namespace PepperDash.Essentials.Core.Touchpanels foreach (var eventType in button.EventTypes[type]) DeviceJsonApi.DoDeviceAction(eventType); } + + + public void ListButtons() + { + var line = new string('-', 35); + + Debug.Console(0, this, line); + + Debug.Console(0, this, "MPC3 Controller {0} - Available Butons", Key); + + foreach (var button in _buttons) + { + Debug.Console(0, this, "Key: {0}", button.Key); + } + + Debug.Console(0, this, line); + } } /// diff --git a/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdPsXxxAnalogAuxMixerController.cs b/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdPsXxxAnalogAuxMixerController.cs new file mode 100644 index 00000000..7d27d35b --- /dev/null +++ b/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdPsXxxAnalogAuxMixerController.cs @@ -0,0 +1,185 @@ +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DeviceSupport; +using Crestron.SimplSharpPro.DM; +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace PepperDash_Essentials_DM.Chassis +{ + public class HdPsXxxAnalogAuxMixerController : IKeyed, + IHasVolumeControlWithFeedback, IHasMuteControlWithFeedback + { + public string Key { get; private set; } + + private readonly HdPsXxxAnalogAuxMixer _mixer; + + public HdPsXxxAnalogAuxMixerController(string parent, uint mixer, HdPsXxx chassis) + { + Key = string.Format("{0}-analogMixer{1}", parent, mixer); + + _mixer = chassis.AnalogAuxiliaryMixer[mixer]; + + _mixer.AuxMixerPropertyChange += OnAuxMixerPropertyChange; + _mixer.AuxiliaryMuteControl.MuteAndVolumeControlPropertyChange += OnMuteAndVolumeControlPropertyChange; + + VolumeLevelFeedback = new IntFeedback(() => VolumeLevel); + MuteFeedback = new BoolFeedback(() => IsMuted); + } + + #region Volume + + private void OnAuxMixerPropertyChange(object sender, GenericEventArgs args) + { + Debug.Console(2, this, "OnAuxMixerPropertyChange: {0} > Index-{1}, EventId-{2}", sender.ToString(), args.Index, args.EventId); + + switch (args.EventId) + { + case MuteAndVolumeContorlEventIds.VolumeFeedbackEventId: + { + VolumeLevel = _mixer.VolumeFeedback.ShortValue; + break; + } + case MuteAndVolumeContorlEventIds.MuteOnEventId: + case MuteAndVolumeContorlEventIds.MuteOffEventId: + { + IsMuted = _mixer.AuxiliaryMuteControl.MuteOnFeedback.BoolValue; + break; + } + default: + { + Debug.Console(1, this, "OnAuxMixerPropertyChange: {0} > Index-{1}, EventId-{2} - unhandled eventId", sender.ToString(), args.Index, args.EventId); + break; + } + } + } + + private const ushort CrestronLevelMin = 0; + private const ushort CrestronLevelMax = 65535; + + private const int DeviceLevelMin = -800; + private const int DeviceLevelMax = 200; + + private const int RampTime = 5000; + + private int _volumeLevel; + + public int VolumeLevel + { + get { return _volumeLevel; } + private set + { + var level = value; + + _volumeLevel = CrestronEnvironment.ScaleWithLimits(level, DeviceLevelMax, DeviceLevelMin, CrestronLevelMax, CrestronLevelMin); + + Debug.Console(1, this, "VolumeFeedback: level-'{0}', scaled-'{1}'", level, _volumeLevel); + + VolumeLevelFeedback.FireUpdate(); + } + } + + public IntFeedback VolumeLevelFeedback { get; private set; } + + public void SetVolume(ushort level) + { + var levelScaled = CrestronEnvironment.ScaleWithLimits(level, CrestronLevelMax, CrestronLevelMin, DeviceLevelMax, DeviceLevelMin); + + Debug.Console(1, this, "SetVolume: level-'{0}', levelScaled-'{1}'", level, levelScaled); + + _mixer.Volume.ShortValue = (short)levelScaled; + } + + public void VolumeUp(bool pressRelease) + { + if (pressRelease) + { + _mixer.Volume.CreateSignedRamp(DeviceLevelMax, RampTime); + } + else + { + _mixer.Volume.StopRamp(); + } + } + + public void VolumeDown(bool pressRelease) + { + if (pressRelease) + { + _mixer.Volume.CreateSignedRamp(DeviceLevelMin, RampTime); + } + else + { + _mixer.Volume.StopRamp(); + } + } + + #endregion + + + + + #region Mute + + private void OnMuteAndVolumeControlPropertyChange(MuteControl device, GenericEventArgs args) + { + Debug.Console(2, this, "OnMuteAndVolumeControlPropertyChange: {0} > Index-{1}, EventId-{2}", device.ToString(), args.Index, args.EventId); + + switch (args.EventId) + { + case MuteAndVolumeContorlEventIds.VolumeFeedbackEventId: + { + VolumeLevel = _mixer.VolumeFeedback.ShortValue; + break; + } + case MuteAndVolumeContorlEventIds.MuteOnEventId: + case MuteAndVolumeContorlEventIds.MuteOffEventId: + { + IsMuted = _mixer.AuxiliaryMuteControl.MuteOnFeedback.BoolValue; + break; + } + default: + { + Debug.Console(1, this, "OnMuteAndVolumeControlPropertyChange: {0} > Index-{1}, EventId-{2} - unhandled eventId", device.ToString(), args.Index, args.EventId); + break; + } + } + } + + private bool _isMuted; + + public bool IsMuted + { + get { return _isMuted; } + set + { + _isMuted = value; + + Debug.Console(1, this, "IsMuted: _isMuted-'{0}'", _isMuted); + + MuteFeedback.FireUpdate(); + } + } + + public BoolFeedback MuteFeedback { get; private set; } + + public void MuteOn() + { + _mixer.AuxiliaryMuteControl.MuteOn(); + } + + public void MuteOff() + { + _mixer.AuxiliaryMuteControl.MuteOff(); + } + + public void MuteToggle() + { + if (IsMuted) + MuteOff(); + else + MuteOn(); + } + + #endregion + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdPsXxxController.cs b/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdPsXxxController.cs new file mode 100644 index 00000000..c267acea --- /dev/null +++ b/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdPsXxxController.cs @@ -0,0 +1,646 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DeviceSupport; +using Crestron.SimplSharpPro.DM; +using Newtonsoft.Json; +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Bridges; +using PepperDash.Essentials.Core.Config; +using PepperDash_Essentials_Core.Bridges; +using PepperDash_Essentials_DM.Config; + +namespace PepperDash_Essentials_DM.Chassis +{ + [Description("Wrapper class for all HdPsXxx switchers")] + public class HdPsXxxController : CrestronGenericBridgeableBaseDevice, IRoutingNumericWithFeedback, IRoutingHasVideoInputSyncFeedbacks + { + private readonly HdPsXxx _chassis; + + public RoutingPortCollection InputPorts { get; private set; } + public RoutingPortCollection OutputPorts { get; private set; } + + public Dictionary InputNames { get; set; } + public Dictionary OutputNames { get; set; } + + public FeedbackCollection InputNameFeedbacks { get; private set; } + public FeedbackCollection InputHdcpEnableFeedback { get; private set; } + + public FeedbackCollection OutputNameFeedbacks { get; private set; } + public FeedbackCollection OutputRouteNameFeedback { get; private set; } + + public FeedbackCollection VideoInputSyncFeedbacks { get; private set; } + public FeedbackCollection VideoOutputRouteFeedbacks { get; private set; } + + public StringFeedback DeviceNameFeedback { get; private set; } + public BoolFeedback AutoRouteFeedback { get; private set; } + + public event EventHandler NumericSwitchChange; + public event EventHandler DmInputChange; + + + /// + /// Constructor + /// + /// + /// + /// HdPs401 device instance + /// + public HdPsXxxController(string key, string name, HdPsXxx chassis, HdPsXxxPropertiesConfig props) + : base(key, name, chassis) + { + _chassis = chassis; + Name = name; + + if (props == null) + { + Debug.Console(1, this, "HdPsXxxController properties are null, failed to build device"); + return; + } + + InputPorts = new RoutingPortCollection(); + InputNameFeedbacks = new FeedbackCollection(); + InputHdcpEnableFeedback = new FeedbackCollection(); + InputNames = new Dictionary(); + + OutputPorts = new RoutingPortCollection(); + OutputNameFeedbacks = new FeedbackCollection(); + OutputRouteNameFeedback = new FeedbackCollection(); + OutputNames = new Dictionary(); + + VideoInputSyncFeedbacks = new FeedbackCollection(); + VideoOutputRouteFeedbacks = new FeedbackCollection(); + + if (_chassis.NumberOfOutputs == 1) + AutoRouteFeedback = new BoolFeedback(() => _chassis.PriorityRouteOnFeedback.BoolValue); + + InputNames = props.Inputs; + SetupInputs(InputNames); + + OutputNames = props.Outputs; + SetupOutputs(OutputNames); + + foreach (var item in _chassis.HdmiDmLiteOutputs) + { + var audioDevice = new HdPsXxxOutputAudioController(Key, item.Number, _chassis); + Debug.Console(2, this, "Adding HdPsXxxOutputAudioController '{0}' for output '{1}'", audioDevice.Key, item.Number); + DeviceManager.AddDevice(audioDevice); + } + foreach (var item in _chassis.AnalogAuxiliaryMixer) + { + var audioDevice = new HdPsXxxAnalogAuxMixerController(Key, item.MixerNumber, _chassis); + Debug.Console(2, this, "Adding HdPsXxAnalogAuxMixerCOntorller '{0}' for output '{1}'", audioDevice.Key, item.MixerNumber); + DeviceManager.AddDevice(audioDevice); + } + } + + // get input priorities + private byte[] SetInputPriorities(HdPsXxxPropertiesConfig props) + { + throw new NotImplementedException(); + } + + // input setup + private void SetupInputs(Dictionary dict) + { + if (dict == null) + { + Debug.Console(1, this, "Failed to setup inputs, properties are null"); + return; + } + + // iterate through HDMI inputs + foreach (var item in _chassis.HdmiInputs) + { + var input = item; + var index = item.Number; + var key = string.Format("hdmiIn{0}", index); + var name = string.IsNullOrEmpty(InputNames[index]) + ? string.Format("HDMI Input {0}", index) + : InputNames[index]; + + input.Name.StringValue = name; + + InputNameFeedbacks.Add(new StringFeedback(index.ToString(CultureInfo.InvariantCulture), + () => InputNames[index])); + + var port = new RoutingInputPort(key, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, input, this) + { + FeedbackMatchObject = input + }; + Debug.Console(1, this, "Adding Input port: {0} - {1}", port.Key, name); + InputPorts.Add(port); + + InputHdcpEnableFeedback.Add(new BoolFeedback(index.ToString(CultureInfo.InvariantCulture), + () => input.InputPort.HdcpSupportOnFeedback.BoolValue)); + + VideoInputSyncFeedbacks.Add(new BoolFeedback(index.ToString(CultureInfo.InvariantCulture), + () => input.InputPort.SyncDetectedFeedback.BoolValue)); + } + + // iterate through DM Lite inputs + foreach (var item in _chassis.DmLiteInputs) + { + var input = item; + var index = item.Number; + var key = string.Format("dmLiteIn{0}", index); + var name = string.IsNullOrEmpty(InputNames[index]) + ? string.Format("DM Input {0}", index) + : InputNames[index]; + + input.Name.StringValue = name; + + InputNameFeedbacks.Add(new StringFeedback(index.ToString(CultureInfo.InvariantCulture), + () => InputNames[index])); + + var port = new RoutingInputPort(key, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, input, this) + { + FeedbackMatchObject = input + }; + Debug.Console(0, this, "Adding Input port: {0} - {1}", port.Key, name); + InputPorts.Add(port); + + InputHdcpEnableFeedback.Add(new BoolFeedback(index.ToString(CultureInfo.InvariantCulture), + () => input.InputPort.HdcpSupportOnFeedback.BoolValue)); + + VideoInputSyncFeedbacks.Add(new BoolFeedback(index.ToString(CultureInfo.InvariantCulture), + () => input.InputPort.SyncDetectedFeedback.BoolValue)); + } + + _chassis.DMInputChange += _chassis_InputChange; + } + + // output setup + private void SetupOutputs(Dictionary dict) + { + if (dict == null) + { + Debug.Console(1, this, "Failed to setup outputs, properties are null"); + return; + } + + foreach (var item in _chassis.HdmiDmLiteOutputs) + { + var output = item; + var index = item.Number; + var name = string.IsNullOrEmpty(OutputNames[index]) + ? string.Format("Port {0}", index) + : OutputNames[index]; + + output.Name.StringValue = name; + + var hdmiKey = string.Format("hdmiOut{0}", index); + var hdmiPort = new RoutingOutputPort(hdmiKey, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, output, this) + { + FeedbackMatchObject = output, + Port = output.HdmiOutput.HdmiOutputPort + }; + Debug.Console(1, this, "Adding Port port: {0} - {1}", hdmiPort.Key, name); + OutputPorts.Add(hdmiPort); + + var dmLiteKey = string.Format("dmLiteOut{0}", index); + var dmLitePort = new RoutingOutputPort(dmLiteKey, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.DmCat, output, this) + { + FeedbackMatchObject = output, + Port = output.DmLiteOutput.DmLiteOutputPort + }; + Debug.Console(1, this, "Adding Port port: {0} - {1}", dmLitePort.Key, name); + OutputPorts.Add(dmLitePort); + + OutputRouteNameFeedback.Add(new StringFeedback(index.ToString(CultureInfo.InvariantCulture), + () => output.VideoOutFeedback.NameFeedback.StringValue)); + + VideoOutputRouteFeedbacks.Add(new IntFeedback(index.ToString(CultureInfo.InvariantCulture), + () => output.VideoOutFeedback == null ? 0 : (int)output.VideoOutFeedback.Number)); + } + /* + Debug.Console(2, this, "----> AnalogAuxillaryMixer.Count-{0}", _chassis.AnalogAuxiliaryMixer.Count); + foreach (var item in _chassis.AnalogAuxiliaryMixer) + { + Debug.Console(2, this, "----> AnalogAuxillaryMixer[{0}].LineMuteVolumeControl.Count-{1}", item.MixerNumber, item.LineMuteVolumeControl.Count); + Debug.Console(2, this, "----> AnalogAuxillaryMixer[{0}].SourceMuteVolumeControl.Count-{1}", item.MixerNumber, item.SourceMuteVolumeControl.Count); + } + */ + _chassis.DMOutputChange += _chassis_OutputChange; + } + + + public void ListRoutingPorts() + { + try + { + foreach (var port in InputPorts) + { + Debug.Console(0, this, @"Input Port Key: {0} +Port: {1} +Type: {2} +ConnectionType: {3} +Selector: {4} +", port.Key, port.Port, port.Type, port.ConnectionType, port.Selector); + } + + foreach (var port in OutputPorts) + { + Debug.Console(0, this, @"Port Port Key: {0} +Port: {1} +Type: {2} +ConnectionType: {3} +Selector: {4} +", port.Key, port.Port, port.Type, port.ConnectionType, port.Selector); + } + } + catch (Exception ex) + { + Debug.Console(0, this, "ListRoutingPorts Exception Message: {0}", ex.Message); + Debug.Console(0, this, "ListRoutingPorts Exception StackTrace: {0}", ex.StackTrace); + if (ex.InnerException != null) Debug.Console(0, this, "ListRoutingPorts InnerException: {0}", ex.InnerException); + } + } + + #region BridgeLinking + + /// + /// Link device to API + /// + /// + /// + /// + /// + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new HdPsXxxControllerJoinMap(joinStart); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.Console(0, 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]); + + _chassis.OnlineStatusChange += _chassis_OnlineStatusChange; + + LinkChassisInputsToApi(trilist, joinMap); + LinkChassisOutputsToApi(trilist, joinMap); + + trilist.OnlineStatusChange += (sender, args) => + { + if (!args.DeviceOnLine) return; + }; + } + + + // links inputs to API + private void LinkChassisInputsToApi(BasicTriList trilist, HdPsXxxControllerJoinMap joinMap) + { + for (uint i = 1; i <= _chassis.NumberOfInputs; i++) + { + var input = i; + var inputName = InputNames[input]; + var indexWithOffset = input - 1; + + trilist.SetSigTrueAction(joinMap.EnableInputHdcp.JoinNumber + indexWithOffset, () => EnableHdcp(input)); + trilist.SetSigTrueAction(joinMap.DisableInputHdcp.JoinNumber + indexWithOffset, () => DisableHdcp(input)); + + InputHdcpEnableFeedback[inputName].LinkInputSig(trilist.BooleanInput[joinMap.EnableInputHdcp.JoinNumber + indexWithOffset]); + InputHdcpEnableFeedback[inputName].LinkComplementInputSig(trilist.BooleanInput[joinMap.EnableInputHdcp.JoinNumber + indexWithOffset]); + + VideoInputSyncFeedbacks[inputName].LinkInputSig(trilist.BooleanInput[joinMap.InputSync.JoinNumber + indexWithOffset]); + + InputNameFeedbacks[inputName].LinkInputSig(trilist.StringInput[joinMap.InputName.JoinNumber + indexWithOffset]); + } + } + + + // links outputs to API + private void LinkChassisOutputsToApi(BasicTriList trilist, HdPsXxxControllerJoinMap joinMap) + { + for (uint i = 1; i <= _chassis.NumberOfOutputs; i++) + { + var output = i; + var outputName = OutputNames[output]; + var indexWithOffset = output - 1; + + trilist.SetUShortSigAction(joinMap.OutputRoute.JoinNumber + indexWithOffset, (a) => + ExecuteNumericSwitch(a, (ushort)output, eRoutingSignalType.AudioVideo)); + + OutputNameFeedbacks[outputName].LinkInputSig(trilist.StringInput[joinMap.OutputName.JoinNumber + indexWithOffset]); + OutputRouteNameFeedback[outputName].LinkInputSig(trilist.StringInput[joinMap.OutputRoutedName.JoinNumber + indexWithOffset]); + + VideoOutputRouteFeedbacks[outputName].LinkInputSig(trilist.UShortInput[joinMap.OutputRoute.JoinNumber + indexWithOffset]); + } + + AutoRouteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.EnableAutoRoute.JoinNumber]); + } + + #endregion + + + /// + /// Executes a device switch using objects + /// + /// + /// + /// + public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) + { + var input = inputSelector as HdPsXxxInput; + var output = outputSelector as HdPsXxxOutput; + + Debug.Console(2, this, "ExecuteSwitch: input={0}, output={1}", input, output); + + if (output == null) + { + Debug.Console(0, this, "Unable to make switch, output selector is not HdPsXxxHdmiOutput"); + return; + } + + // TODO [ ] Validate if sending the same input toggles the switch + var current = output.VideoOut; + if (current != input) + output.VideoOut = input; + } + + + /// + /// Executes a device switch using numeric values + /// + /// + /// + /// + public void ExecuteNumericSwitch(ushort inputSelector, ushort outputSelector, eRoutingSignalType signalType) + { + var input = inputSelector == 0 ? null : _chassis.Inputs[inputSelector]; + var output = _chassis.Outputs[outputSelector]; + + Debug.Console(2, this, "ExecuteNumericSwitch: input={0}, output={1}", input, output); + + ExecuteSwitch(input, output, signalType); + } + + + /// + /// Enables Hdcp on the provided port + /// + /// + public void EnableHdcp(uint port) + { + if (port <= 0 || port > _chassis.NumberOfInputs) return; + + _chassis.HdmiInputs[port].InputPort.HdcpSupportOn(); + InputHdcpEnableFeedback[InputNames[port]].FireUpdate(); + } + + + /// + /// Disables Hdcp on the provided port + /// + /// + public void DisableHdcp(uint port) + { + if (port <= 0 || port > _chassis.NumberOfInputs) return; + + _chassis.HdmiInputs[port].InputPort.HdcpSupportOff(); + InputHdcpEnableFeedback[InputNames[port]].FireUpdate(); + } + + + /// + /// Enables switcher auto route + /// + public void EnableAutoRoute() + { + if (_chassis.NumberOfInputs == 1) return; + + _chassis.AutoRouteOn(); + } + + + /// + /// Disables switcher auto route + /// + public void DisableAutoRoute() + { + if (_chassis.NumberOfInputs == 1) return; + + _chassis.AutoRouteOff(); + } + + + + #region Events + + + // _chassis online/offline event + private void _chassis_OnlineStatusChange(GenericBase currentDevice, + OnlineOfflineEventArgs args) + { + IsOnline.FireUpdate(); + + if (!args.DeviceOnLine) return; + + foreach (var feedback in Feedbacks) + { + feedback.FireUpdate(); + } + } + + + // _chassis input change event + private void _chassis_InputChange(Switch device, DMInputEventArgs args) + { + switch (args.EventId) + { + case DMInputEventIds.RemoteTransmitterDetectedEventId: + { + // signal found on HD-PSXxx > Inputs > Inputs DM Lite X + Debug.Console(2, this, "{0} DM Input Event ID {1}-RemoteTransmitterDetected | Number {2}", + device.ToString(), args.EventId, args.Number); + break; + } + case DMInputEventIds.SourceSyncEventId: // id-14 + case DMInputEventIds.VideoDetectedEventId: // id-9 + { + // signal found on HD-PSXxx > Inputs > HDMI/DM Lite X + Debug.Console(1, this, "{0} DM Input Event ID {1} | Number {2}: Updating VideoInputSyncFeedbacks", + device.Name, args.EventId, args.Number); + + var input = args.Number; + + var feedback = VideoInputSyncFeedbacks[(int)input]; + if (feedback == null) return; + + feedback.FireUpdate(); + + break; + } + case DMInputEventIds.InputNameFeedbackEventId: + case DMInputEventIds.InputNameEventId: + case DMInputEventIds.NameFeedbackEventId: + { + Debug.Console(1, this, "{0} DM Input Event ID {1}-Name | Number {2}: Updating name feedbacks", + device.Name, args.EventId, args.Number); + + var input = args.Number; + var name = _chassis.HdmiInputs[input].NameFeedback.StringValue; + + Debug.Console(1, this, "Input {0} Name {1}", input, name); + break; + } + default: + { + Debug.Console(1, this, "{0} DM Input Event ID {1} | Number {2}: Uhandled", + device.Name, args.EventId, args.Number); + break; + } + } + + OnDmInputChange(args); + } + + // _chassis output change event + private void _chassis_OutputChange(Switch device, DMOutputEventArgs args) + { + switch (args.EventId) + { + case DMOutputEventIds.VideoOutEventId: + { + Debug.Console(2, this, "{0} DM Output Event Id {1} | Number {2} | Index {3}: VideoOutEventId", + device.Name, args.EventId, args.Number, args.Index); + + var output = args.Number; + + var input = _chassis.HdmiDmLiteOutputs[output].VideoOutFeedback == null + ? 0 + : _chassis.HdmiDmLiteOutputs[output].VideoOutFeedback.Number; + + var outputName = OutputNames[output]; + + var feedback = VideoOutputRouteFeedbacks[outputName]; + if (feedback == null) return; + + var inputPort = InputPorts.FirstOrDefault( + p => p.FeedbackMatchObject == _chassis.HdmiDmLiteOutputs[output].VideoOutFeedback); + + var outputPort = OutputPorts.FirstOrDefault( + p => p.FeedbackMatchObject == _chassis.HdmiDmLiteOutputs[output]); + + feedback.FireUpdate(); + + OnSwitchChange(new RoutingNumericEventArgs(output, input, outputPort, inputPort, eRoutingSignalType.AudioVideo)); + + break; + } + case DMOutputEventIds.RemoteReceiverDetectedEventId: + { + // signal found on HD-PSXxx > Output[s] > Output [X] > DM Lite [X] + Debug.Console(2, this, "{0} DM Output Event Id {1} | Number {2} | Index {3}: RemoteRecevierDetectedEventId", + device.Name, args.EventId, args.Number, args.Index); + break; + } + default: + { + Debug.Console(2, this, "{0} DM Output Event Id {1} | Number {2} | Index:{3}: Unhandled", + device.Name, args.EventId, args.Number, args.Index); + break; + } + } + } + + + // Raise an event when the status of a switch object changes. + private void OnSwitchChange(RoutingNumericEventArgs args) + { + var newEvent = NumericSwitchChange; + if (newEvent != null) newEvent(this, args); + } + + // Raise an event when the DM input changes. + private void OnDmInputChange(DMInputEventArgs args) + { + var newEvent = DmInputChange; + if (newEvent != null) newEvent(this, args); + } + + + #endregion + + + + #region Factory + + + public class HdPsXxxControllerFactory : EssentialsDeviceFactory + { + public HdPsXxxControllerFactory() + { + TypeNames = new List { "hdps401", "hdps402", "hdps621", "hdps622" }; + } + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + var key = dc.Key; + var name = dc.Name; + var type = dc.Type.ToLower(); + + Debug.Console(1, "Factory Attempting to create new {0} device", type); + + var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); + if (props == null) + { + Debug.Console(1, "Factory failed to create new HD-PSXxx device, properties config was null"); + return null; + } + + var ipid = props.Control.IpIdInt; + + switch (type) + { + case ("hdps401"): + { + return new HdPsXxxController(key, name, new HdPs401(ipid, Global.ControlSystem), props); + } + case ("hdps402"): + { + return new HdPsXxxController(key, name, new HdPs402(ipid, Global.ControlSystem), props); + } + case ("hdps621"): + { + return new HdPsXxxController(key, name, new HdPs621(ipid, Global.ControlSystem), props); + } + case ("hdps622"): + { + return new HdPsXxxController(key, name, new HdPs622(ipid, Global.ControlSystem), props); + } + default: + { + Debug.Console(1, "Factory failed to create new {0} device", type); + return null; + } + } + } + } + + + #endregion + } + + + public class StreamCecWrapper : IKeyed, ICec + { + public string Key { get; private set; } + public Cec StreamCec { get; private set; } + + public StreamCecWrapper(string key, Cec streamCec) + { + Key = key; + StreamCec = streamCec; + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdPsXxxOutputAudioController.cs b/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdPsXxxOutputAudioController.cs new file mode 100644 index 00000000..57067bde --- /dev/null +++ b/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdPsXxxOutputAudioController.cs @@ -0,0 +1,167 @@ +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DM; +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace PepperDash_Essentials_DM.Chassis +{ + public class HdPsXxxOutputAudioController : IKeyed, + IHasVolumeControlWithFeedback, IHasMuteControlWithFeedback + { + public string Key { get; private set; } + + private readonly HdPsXxxHdmiDmLiteOutputMixer _mixer; // volume/volumeFeedback + private readonly HdPsXxxOutputPort _port; // mute/muteFeedback + + public HdPsXxxOutputAudioController(string parent, uint output, HdPsXxx chassis) + { + Key = string.Format("{0}-audioOut{1}", parent, output); + + _port = chassis.HdmiDmLiteOutputs[output].OutputPort; + _mixer = chassis.HdmiDmLiteOutputs[output].Mixer; + + chassis.DMOutputChange += ChassisOnDmOutputChange; + + VolumeLevelFeedback = new IntFeedback(() => VolumeLevel); + MuteFeedback = new BoolFeedback(() => IsMuted); + } + + private void ChassisOnDmOutputChange(Switch device, DMOutputEventArgs args) + { + switch (args.EventId) + { + case (DMOutputEventIds.VolumeEventId): + { + Debug.Console(2, this, "HdPsXxxOutputAudioController: {0} > Index-{1}, Number-{3}, EventId-{2} - AudioMute/UnmuteEventId", + device.ToString(), args.Index, args.EventId, args.Number); + + VolumeLevel = _mixer.VolumeFeedback.ShortValue; + + break; + } + case DMOutputEventIds.MuteOnEventId: + case DMOutputEventIds.MuteOffEventId: + { + Debug.Console(2, this, "HdPsXxxOutputAudioController: {0} > Index-{1}, Number-{3}, EventId-{2} - MuteOnEventId/MuteOffEventId", + device.ToString(), args.Index, args.EventId, args.Number); + + IsMuted = _port.MuteOnFeedback.BoolValue; + + break; + } + default: + { + Debug.Console(1, this, "HdPsXxxOutputAudioController: {0} > Index-{1}, Number-{3}, EventId-{2} - unhandled eventId", + device.ToString(), args.Index, args.EventId, args.Number); + break; + } + } + } + + #region Volume + + private const ushort CrestronLevelMin = 0; + private const ushort CrestronLevelMax = 65535; + + private const int DeviceLevelMin = -800; + private const int DeviceLevelMax = 200; + + private const int RampTime = 5000; + + private int _volumeLevel; + + public int VolumeLevel + { + get { return _volumeLevel; } + private set + { + var level = value; + + _volumeLevel = CrestronEnvironment.ScaleWithLimits(level, DeviceLevelMax, DeviceLevelMin, CrestronLevelMax, CrestronLevelMin); + + Debug.Console(2, this, "VolumeFeedback: level-'{0}', scaled-'{1}'", level, _volumeLevel); + + VolumeLevelFeedback.FireUpdate(); + } + } + + public IntFeedback VolumeLevelFeedback { get; private set; } + + public void SetVolume(ushort level) + { + var levelScaled = CrestronEnvironment.ScaleWithLimits(level, CrestronLevelMax, CrestronLevelMin, DeviceLevelMax, DeviceLevelMin); + + Debug.Console(1, this, "SetVolume: level-'{0}', levelScaled-'{1}'", level, levelScaled); + + _mixer.Volume.ShortValue = (short)levelScaled; + } + + public void VolumeUp(bool pressRelease) + { + if (pressRelease) + { + _mixer.Volume.CreateSignedRamp(DeviceLevelMax, RampTime); + } + else + { + _mixer.Volume.StopRamp(); + } + } + + public void VolumeDown(bool pressRelease) + { + if (pressRelease) + { + _mixer.Volume.CreateSignedRamp(DeviceLevelMin, RampTime); + } + else + { + _mixer.Volume.StopRamp(); + } + } + + #endregion + + + + + #region Mute + + private bool _isMuted; + + public bool IsMuted + { + get { return _isMuted; } + set + { + _isMuted = value; + + Debug.Console(1, this, "IsMuted: _isMuted-'{0}'", _isMuted); + + MuteFeedback.FireUpdate(); + } + } + + public BoolFeedback MuteFeedback { get; private set; } + + public void MuteOn() + { + _port.MuteOn(); + } + + public void MuteOff() + { + _port.MuteOff(); + } + + public void MuteToggle() + { + if (IsMuted) + MuteOff(); + else + MuteOn(); + } + + #endregion + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials DM/Essentials_DM/Config/HdPsXxxPropertiesConfig.cs b/essentials-framework/Essentials DM/Essentials_DM/Config/HdPsXxxPropertiesConfig.cs new file mode 100644 index 00000000..f5eb9d39 --- /dev/null +++ b/essentials-framework/Essentials DM/Essentials_DM/Config/HdPsXxxPropertiesConfig.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using PepperDash.Core; + +namespace PepperDash_Essentials_DM.Config +{ + public class HdPsXxxPropertiesConfig + { + [JsonProperty("control")] + public ControlPropertiesConfig Control { get; set; } + + [JsonProperty("inputs")] + public Dictionary Inputs { get; set; } + + [JsonProperty("outputs")] + public Dictionary Outputs { get; set; } + + [JsonProperty("volumeMixerId")] + public uint VolumeMixerId { get; set; } + + // "inputPriorities": "1,4,3,2" + [JsonProperty("inputPriorities")] + public string InputPriorities { get; set; } + + public HdPsXxxPropertiesConfig() + { + Inputs = new Dictionary(); + Outputs = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials DM/Essentials_DM/PepperDash_Essentials_DM.csproj b/essentials-framework/Essentials DM/Essentials_DM/PepperDash_Essentials_DM.csproj index adfddbe3..41136d0d 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/PepperDash_Essentials_DM.csproj +++ b/essentials-framework/Essentials DM/Essentials_DM/PepperDash_Essentials_DM.csproj @@ -104,6 +104,10 @@ + + + + diff --git a/packages.config b/packages.config index fa36e07d..eff6d8ce 100644 --- a/packages.config +++ b/packages.config @@ -1,3 +1,3 @@ - +