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/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj index 75d4626d..a0da04f9 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj @@ -127,6 +127,7 @@ + @@ -200,6 +201,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..296965f9 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; @@ -27,6 +28,7 @@ namespace PepperDash.Essentials.Core.Touchpanels } _touchpanel.ButtonStateChange += _touchpanel_ButtonStateChange; + _buttons = buttons; if (_buttons == null) { @@ -55,6 +57,8 @@ namespace PepperDash.Essentials.Core.Touchpanels /// public void InitializeButton(string key, KeypadButton config) { + Debug.Console(1, this, "Initializing button '{0}'...", key); + if (config == null) { Debug.Console(1, this, "Button '{0}' config is null, unable to initialize", key); @@ -131,6 +135,8 @@ 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); @@ -144,37 +150,61 @@ namespace PepperDash.Essentials.Core.Touchpanels var buttonFeedback = config.Feedback; if (buttonFeedback == null) { - 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 and will not be implemented. If feedback is required, verify configuration.", + 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 with deviceKey '{1}' not found, feedback will not be implemented. Verify feedback deviceKey is properly configured.", + key, buttonFeedback.DeviceKey); + return; + } + + deviceFeedback = device.GetFeedbackProperty(buttonFeedback.FeedbackName); + if (deviceFeedback == null) + { + Debug.Console(1, this, "Button '{0}' feedback failed to get feedback property for '{1}', feedback will not be implemented. Verify feedback deviceKey is properly configured.", + 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(0, this, "Failed to initialize button '{0}' feedback with deviceKey '{1}'. If feedback is required, verify configuration.", + key, buttonFeedback.DeviceKey); + + Debug.Console(1, this, "InitializeButtonFeedback Exception Message: {0}", ex.Message); + Debug.Console(2, this, "InitializeButtonFeedback Exception StackTrace: {0}", ex.StackTrace); + if (ex.InnerException != null) Debug.Console(2, this, "InitializeButtonFeedback Exception InnerExceptioni: {0}", ex.InnerException); + 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; 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..bf9f72cc --- /dev/null +++ b/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdPsXxxController.cs @@ -0,0 +1,557 @@ +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); + } + + // 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.VideoDetectedFeedback.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.VideoDetectedFeedback.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("Output {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 Output 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 Output 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)); + } + + _chassis.DMOutputChange += _chassis_OutputChange; + } + + + #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 HdPsXxxHdmiInput; + var output = outputSelector as HdPsXxxHdmiOutput; + + 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) + { + var eventId = args.EventId; + + switch (eventId) + { + case DMInputEventIds.VideoDetectedEventId: + { + Debug.Console(1, this, "Event ID {0}: Updating VideoInputSyncFeedbacks", eventId); + foreach (var item in VideoInputSyncFeedbacks) + { + item.FireUpdate(); + } + break; + } + case DMInputEventIds.InputNameFeedbackEventId: + case DMInputEventIds.InputNameEventId: + case DMInputEventIds.NameFeedbackEventId: + { + Debug.Console(1, this, "Event ID {0}: Updating name feedbacks", eventId); + + 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, "Uhandled DM Input Event ID {0}", eventId); + break; + } + } + + OnDmInputChange(args); + } + + + // _chassis output change event + private void _chassis_OutputChange(Switch device, DMOutputEventArgs args) + { + if (args.EventId != DMOutputEventIds.VideoOutEventId) return; + + 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)); + } + + + // 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 HdSp401ControllerFactory : EssentialsDeviceFactory + { + public HdSp401ControllerFactory() + { + 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/Config/HdPsXxxPropertiesConfig.cs b/essentials-framework/Essentials DM/Essentials_DM/Config/HdPsXxxPropertiesConfig.cs new file mode 100644 index 00000000..576b74f0 --- /dev/null +++ b/essentials-framework/Essentials DM/Essentials_DM/Config/HdPsXxxPropertiesConfig.cs @@ -0,0 +1,28 @@ +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; } + + // "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..0011c305 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,8 @@ + +