Merge branch 'main' into hotfix/add-hdcp-control-to-airmedia

This commit is contained in:
Jason T Alborough
2024-05-16 12:18:08 -04:00
committed by GitHub
20 changed files with 1829 additions and 96 deletions

View File

@@ -91,8 +91,8 @@ we receive and the availability of resources to evaluate contributions, we antic
project remains dynamic and relevant. This may affect our responsiveness and ability to accept pull requests
quickly. This does not mean we are ignoring them.
- Not all innovative ideas need to be accepted as pull requests into this GitHub project to be valuable to the community.
There may be times when we recommend that you just share your code for some enhancement to Ghidra from your own
repository. As we identify and recognize extensions that are of general interest to the reverse engineering community, we
There may be times when we recommend that you just share your code for some enhancement to Essentials from your own
repository. As we identify and recognize extensions that are of general interest to Essentials, we
may seek to incorporate them with our baseline.
## Legal

View File

@@ -70,7 +70,7 @@ namespace PepperDash.Essentials.Bridges
catch (NullReferenceException)
{
Debug.ConsoleWithLog(0, this,
"Please update the bridge config to use EiscBridgeAdvanced with this device: {0}", device.Key);
"Please update the bridge config to use eiscApiAdvanced with this device: {0}", device.Key);
}
}
Debug.Console(1, this, "Devices Linked.");

View File

@@ -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
/// <summary>
/// Constructor to use when instantiating this join map without inheriting from it
/// </summary>
/// <param name="joinStart">Join this join map will start at</param>
public HdPsXxxControllerJoinMap(uint joinStart)
: this(joinStart, typeof(HdPsXxxControllerJoinMap))
{
}
/// <summary>
/// Constructor to use when extending this Join map
/// </summary>
/// <param name="joinStart">Join this join map will start at</param>
/// <param name="type">Type of the child join map</param>
protected HdPsXxxControllerJoinMap(uint joinStart, Type type)
: base(joinStart, type)
{
}
}
}

View File

@@ -124,41 +124,54 @@ namespace PepperDash.Essentials.Core
/// <returns></returns>
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;
}
/// <summary>

View File

@@ -0,0 +1,85 @@
using System.Collections.Generic;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.GeneralIO;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core.CrestronIO
{
/// <summary>
/// Wrapper class for CEN-IO-COM-Xxx expander module
/// </summary>
[Description("Wrapper class for the CEN-IO-COM-102 & CEN-IO-COM-202 expander module")]
public class CenIoComController : CrestronGenericBaseDevice, IComPorts
{
private readonly CenIoCom _cenIoCom;
public CenIoComController(string key, string name, CenIoCom cenIo)
:base(key, name, cenIo)
{
_cenIoCom = cenIo;
}
#region Implementation of IComPorts
public CrestronCollection<ComPort> ComPorts
{
get { return _cenIoCom.ComPorts; }
}
public int NumberOfComPorts
{
get { return _cenIoCom.NumberOfComPorts; }
}
#endregion
}
public class CenIoCom102ControllerFactory : EssentialsDeviceFactory<CenIoComController>
{
private const string CenIoCom102Type = "ceniocom102";
private const string CenIoCom202Type = "ceniocom202";
public CenIoCom102ControllerFactory()
{
TypeNames = new List<string> { CenIoCom102Type, CenIoCom202Type };
}
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.Console(1, "Factory Attempting to create new CEN-IO-COM-Xxx Device");
var control = CommFactory.GetControlPropertiesConfig(dc);
if (control == null)
{
Debug.Console(1, "Factory failed to create a new CEN-IO-COM-Xxx Device, control properties not found");
return null;
}
var ipid = control.IpIdInt;
if (ipid < 2)
{
Debug.Console(1, "Factory failed to create a new CEN-IO-COM-Xxx Device, invalid IP-ID found");
return null;
}
switch (dc.Type)
{
case CenIoCom102Type:
{
return new CenIoComController(dc.Key, dc.Name, new CenIoCom102(ipid, Global.ControlSystem));
}
case CenIoCom202Type:
{
return new CenIoComController(dc.Key, dc.Name, new CenIoCom202(ipid, Global.ControlSystem));
}
default:
{
Debug.Console(1, "Factory failed to create a new CEN-IO-COM-Xxx Device, invalid type '{0}'", dc.Type);
return null;
}
}
}
}
}

View File

@@ -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
{
/// <summary>
/// Event raised when ArpTable changes
/// </summary>
public static event ArpTableEventHandler ArpTableUpdated;
/// <summary>
/// Delegate called by ArpTableUpdated
/// </summary>
/// <param name="args">contains the entire ARP table and a bool to note if there was an error in retrieving the data</param>
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();
/// <summary>
/// Last resolved ARP table - it is recommended to refresh the arp before using this.
/// </summary>
public static List<ArpEntry> ArpTable { get; private set; }
/// <summary>
/// Force recheck of ARP table
/// </summary>
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<ArpEntry>();
}
/// <summary>
/// Removes leading zeros, leading whitespace, and trailing whitespace from an IPAddress string
/// </summary>
/// <param name="ipAddressIn">Ip Address to Santitize</param>
/// <returns>Sanitized Ip Address</returns>
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;
}
}
/// <summary>
/// Resolves a hostname by IP Address using DNS
/// </summary>
/// <param name="ipAddress">IP Address to resolve from</param>
/// <returns>Resolved Hostname - on failure to determine hostname, will return IP Address</returns>
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;
}
}
/// <summary>
/// Resolves an IP Address by hostname using DNS
/// </summary>
/// <param name="hostName">Hostname to resolve from</param>
/// <returns>Resolved IP Address - on a failure to determine IP Address, will return hostname</returns>
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;
}
}
}
/// <summary>
/// Object to hold data about an arp entry
/// </summary>
public class ArpEntry
{
public readonly IPAddress IpAddress;
public readonly string MacAddress;
/// <summary>
/// Constructs new ArpEntry object
/// </summary>
/// <param name="ipAddress">string formatted as ipv4 address</param>
/// <param name="macAddress">mac address string - format is unimportant</param>
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;
}
}
/// <summary>
/// Arguments passed by the ArpTableUpdated event
/// </summary>
public class ArpTableEventArgs : EventArgs
{
/// <summary>
/// The retrieved ARP Table
/// </summary>
public readonly List<ArpEntry> ArpTable;
/// <summary>
/// True if there was a problem retrieving the ARP Table
/// </summary>
public readonly bool Error;
/// <summary>
/// Constructor for ArpTableEventArgs
/// </summary>
/// <param name="arpTable">The entirety of the retrieved ARP table</param>
/// <param name="error">True of an error was encountered updating the ARP table</param>
public ArpTableEventArgs(List<ArpEntry> arpTable, bool error)
{
ArpTable = arpTable;
Error = error;
}
/// <summary>
/// Constructor for ArpTableEventArgs - assumes no error encountered in retrieving ARP Table
/// </summary>
/// <param name="arpTable">The entirety of the retrieved ARP table</param>
public ArpTableEventArgs(List<ArpEntry> arpTable)
{
ArpTable = arpTable;
Error = false;
}
}
}

View File

@@ -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
{
/// <summary>
/// Returns null if a string is empty, otherwise returns the string
/// </summary>
/// <param name="s">string input</param>
/// <returns>null if the string is emtpy, otherwise returns the string</returns>
public static string NullIfEmpty(this string s)
{
return string.IsNullOrEmpty(s) ? null : s;
}
/// <summary>
/// Returns null if a string is empty or made of only whitespace characters, otherwise returns the string
/// </summary>
/// <param name="s">string input</param>
/// <returns>null if the string is wempty or made of only whitespace characters, otherwise returns the string</returns>
public static string NullIfWhiteSpace(this string s)
{
return string.IsNullOrEmpty(s.Trim()) ? null : s;
}
/// <summary>
/// Returns a replacement string if the input string is empty or made of only whitespace characters, otherwise returns the input string
/// </summary>
/// <param name="s">input string</param>
/// <param name="newString">string to replace with if input string is empty or whitespace</param>
/// <returns>returns newString if s is null, emtpy, or made of whitespace characters, otherwise returns s</returns>
public static string ReplaceIfNullOrEmpty(this string s, string newString)
{
return string.IsNullOrEmpty(s) ? newString : s;
}
/// <summary>
/// Overload for Contains that allows setting an explicit String Comparison
/// </summary>
/// <param name="source">Source String</param>
/// <param name="toCheck">String to check in Source String</param>
/// <param name="comp">Comparison parameters</param>
/// <returns>true of string contains "toCheck"</returns>
public static bool Contains(this string source, string toCheck, StringComparison comp)
{
if (string.IsNullOrEmpty(source)) return false;
return source.IndexOf(toCheck, comp) >= 0;
}
/// <summary>
/// Performs TrimStart() and TrimEnd() on source string
/// </summary>
/// <param name="source">String to Trim</param>
/// <returns>Trimmed String</returns>
public static string TrimAll(this string source)
{
return string.IsNullOrEmpty(source) ? string.Empty : source.TrimStart().TrimEnd();
}
/// <summary>
/// Performs TrimStart(chars char[]) and TrimEnd(chars char[]) on source string.
/// </summary>
/// <param name="source">String to Trim</param>
/// <param name="chars">Char Array to trim from string</param>
/// <returns>Trimmed String</returns>
public static string TrimAll(this string source, char[] chars)
{
return string.IsNullOrEmpty(source) ? string.Empty : source.TrimStart(chars).TrimEnd(chars);
}
}
}

View File

@@ -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 = "";

View File

@@ -128,6 +128,7 @@
<Compile Include="Bridges\JoinMaps\AirMediaControllerJoinMap.cs" />
<Compile Include="Bridges\JoinMaps\AppleTvJoinMap.cs" />
<Compile Include="Bridges\JoinMaps\GenericIrControllerJoinMap.cs" />
<Compile Include="Bridges\JoinMaps\HdPsXxxControllerJoinMap.cs" />
<Compile Include="Bridges\JoinMaps\IAnalogInputJoinMap.cs" />
<Compile Include="Bridges\JoinMaps\IDigitalOutputJoinMap.cs" />
<Compile Include="Bridges\JoinMaps\PduJoinMapBase.cs" />
@@ -184,6 +185,7 @@
</Compile>
<Compile Include="Crestron IO\Cards\CenCi33Controller.cs" />
<Compile Include="Crestron IO\Cards\InternalCardCageController.cs" />
<Compile Include="Crestron IO\CenIoCom\CenIoComController.cs" />
<Compile Include="Crestron IO\DinCenCn\DinCenCnController.cs" />
<Compile Include="Crestron IO\DinCenCn\IHasCresnetBranches.cs" />
<Compile Include="Crestron IO\DinIo8\DinIo8Controller.cs" />
@@ -201,6 +203,7 @@
<Compile Include="Crestron IO\Relay\GenericRelayDevice.cs" />
<Compile Include="Crestron IO\Relay\ISwitchedOutput.cs" />
<Compile Include="Crestron IO\StatusSign\StatusSignController.cs" />
<Compile Include="Device Info\NetworkDeviceHelpers.cs" />
<Compile Include="Devices\PowerInterfaces.cs" />
<Compile Include="Web\RequestHandlers\AppDebugRequestHandler.cs" />
<Compile Include="Web\RequestHandlers\GetFeedbacksForDeviceRequestHandler.cs" />

View File

@@ -204,4 +204,9 @@ namespace PepperDash.Essentials.Core
SigType = sigType;
}
}
public interface IRoutingHasVideoInputSyncFeedbacks
{
FeedbackCollection<BoolFeedback> VideoInputSyncFeedbacks { get; }
}
}

View File

@@ -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
}
/// <summary>

View File

@@ -199,5 +199,45 @@ namespace PepperDash.Essentials.Core.Routing
/// MediaPlayer
/// </summary>
public const string MediaPlayer = "mediaPlayer";
}
/// <summary>
/// UsbCIn
/// </summary>
public const string UsbCIn = "usbCIn";
/// <summary>
/// UsbCIn1
/// </summary>
public const string UsbCIn1 = "usbCIn1";
/// <summary>
/// UsbCIn2
/// </summary>
public const string UsbCIn2 = "usbCIn2";
/// <summary>
/// UsbCIn3
/// </summary>
public const string UsbCIn3 = "usbCIn3";
/// <summary>
/// UsbCOut
/// </summary>
public const string UsbCOut = "usbCOut";
/// <summary>
/// UsbCOut1
/// </summary>
public const string UsbCOut1 = "usbCOut1";
/// <summary>
/// UsbCOut2
/// </summary>
public const string UsbCOut2 = "usbCOut2";
/// <summary>
/// UsbCOut3
/// </summary>
public const string UsbCOut3 = "usbCOut3";
/// <summary>
/// HdBaseTIn
/// </summary>
public const string HdBaseTIn = "hdBaseTIn";
/// <summary>
/// HdBaseTOut
/// </summary>
public const string HdBaseTOut = "hdBaseTOut";
}
}

View File

@@ -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");
}
/// <summary>
@@ -131,9 +152,11 @@ namespace PepperDash.Essentials.Core.Touchpanels
/// <param name="config"></param>
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);
}
/// <summary>
/// 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
/// <param name="type"></param>
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);
}
}
/// <summary>

View File

@@ -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
}
}

View File

@@ -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<RoutingInputPort> InputPorts { get; private set; }
public RoutingPortCollection<RoutingOutputPort> OutputPorts { get; private set; }
public Dictionary<uint, string> InputNames { get; set; }
public Dictionary<uint, string> OutputNames { get; set; }
public FeedbackCollection<StringFeedback> InputNameFeedbacks { get; private set; }
public FeedbackCollection<BoolFeedback> InputHdcpEnableFeedback { get; private set; }
public FeedbackCollection<StringFeedback> OutputNameFeedbacks { get; private set; }
public FeedbackCollection<StringFeedback> OutputRouteNameFeedback { get; private set; }
public FeedbackCollection<BoolFeedback> VideoInputSyncFeedbacks { get; private set; }
public FeedbackCollection<IntFeedback> VideoOutputRouteFeedbacks { get; private set; }
public StringFeedback DeviceNameFeedback { get; private set; }
public BoolFeedback AutoRouteFeedback { get; private set; }
public event EventHandler<RoutingNumericEventArgs> NumericSwitchChange;
public event EventHandler<DMInputEventArgs> DmInputChange;
/// <summary>
/// Constructor
/// </summary>
/// <param name="key"></param>
/// <param name="name"></param>
/// <param name="chassis">HdPs401 device instance</param>
/// <param name="props"></param>
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<RoutingInputPort>();
InputNameFeedbacks = new FeedbackCollection<StringFeedback>();
InputHdcpEnableFeedback = new FeedbackCollection<BoolFeedback>();
InputNames = new Dictionary<uint, string>();
OutputPorts = new RoutingPortCollection<RoutingOutputPort>();
OutputNameFeedbacks = new FeedbackCollection<StringFeedback>();
OutputRouteNameFeedback = new FeedbackCollection<StringFeedback>();
OutputNames = new Dictionary<uint, string>();
VideoInputSyncFeedbacks = new FeedbackCollection<BoolFeedback>();
VideoOutputRouteFeedbacks = new FeedbackCollection<IntFeedback>();
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<uint, string> 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<uint, string> 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
/// <summary>
/// Link device to API
/// </summary>
/// <param name="trilist"></param>
/// <param name="joinStart"></param>
/// <param name="joinMapKey"></param>
/// <param name="bridge"></param>
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
/// <summary>
/// Executes a device switch using objects
/// </summary>
/// <param name="inputSelector"></param>
/// <param name="outputSelector"></param>
/// <param name="signalType"></param>
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;
}
/// <summary>
/// Executes a device switch using numeric values
/// </summary>
/// <param name="inputSelector"></param>
/// <param name="outputSelector"></param>
/// <param name="signalType"></param>
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);
}
/// <summary>
/// Enables Hdcp on the provided port
/// </summary>
/// <param name="port"></param>
public void EnableHdcp(uint port)
{
if (port <= 0 || port > _chassis.NumberOfInputs) return;
_chassis.HdmiInputs[port].InputPort.HdcpSupportOn();
InputHdcpEnableFeedback[InputNames[port]].FireUpdate();
}
/// <summary>
/// Disables Hdcp on the provided port
/// </summary>
/// <param name="port"></param>
public void DisableHdcp(uint port)
{
if (port <= 0 || port > _chassis.NumberOfInputs) return;
_chassis.HdmiInputs[port].InputPort.HdcpSupportOff();
InputHdcpEnableFeedback[InputNames[port]].FireUpdate();
}
/// <summary>
/// Enables switcher auto route
/// </summary>
public void EnableAutoRoute()
{
if (_chassis.NumberOfInputs == 1) return;
_chassis.AutoRouteOn();
}
/// <summary>
/// Disables switcher auto route
/// </summary>
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<HdPsXxxController>
{
public HdPsXxxControllerFactory()
{
TypeNames = new List<string> { "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<HdPsXxxPropertiesConfig>(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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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<uint, string> Inputs { get; set; }
[JsonProperty("outputs")]
public Dictionary<uint, string> 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<uint, string>();
Outputs = new Dictionary<uint, string>();
}
}
}

View File

@@ -104,6 +104,10 @@
<Compile Include="Chassis\HdMd8xNController.cs" />
<Compile Include="Chassis\HdMdNxM4kEBridgeableController.cs" />
<Compile Include="Chassis\HdMdNxM4kEController.cs" />
<Compile Include="Chassis\HdPsXxxAnalogAuxMixerController.cs" />
<Compile Include="Chassis\HdPsXxxController.cs" />
<Compile Include="Chassis\HdPsXxxOutputAudioController.cs" />
<Compile Include="Config\HdPsXxxPropertiesConfig.cs" />
<Compile Include="Config\InputPropertiesConfig.cs" />
<Compile Include="Endpoints\EndpointInterfaces.cs" />
<Compile Include="Endpoints\DGEs\DgeJoinMap.cs" />

View File

@@ -994,7 +994,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
//Special Change for protected directory clear
trilist.SetBoolSigAction(joinMap.DirectoryClearSelected.JoinNumber, (b) => SelectDirectoryEntry(_directoryCodec, 0, _directoryTrilist, _directoryJoinmap));
trilist.SetBoolSigAction(joinMap.DirectoryClearSelected.JoinNumber, (b) => SelectDirectoryEntry(codec, 0, trilist, joinMap));
// Report feedback for number of contact methods for selected contact

View File

@@ -1,3 +1,3 @@
<packages>
<package id="PepperDashCore" version="1.3.1" targetFramework="net35" allowedVersions="[1.0,2.0)"/>
<package id="PepperDashCore" version="1.3.3-hotfix-390" targetFramework="net35" allowedVersions="[1.0,2.0)"/>
</packages>