diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ComSpecJsonConverter.cs b/src/PepperDash.Essentials.Core/Comm and IR/ComSpecJsonConverter.cs index 62296bd8..bfdccb65 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ComSpecJsonConverter.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ComSpecJsonConverter.cs @@ -23,12 +23,12 @@ namespace PepperDash.Essentials.Core { public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - if (objectType == typeof(ComPort.ComPortSpec)) + if (objectType == typeof(ComPort.ComPortSpec?)) { var newSer = new JsonSerializer(); newSer.Converters.Add(new ComSpecPropsJsonConverter()); newSer.ObjectCreationHandling = ObjectCreationHandling.Replace; - return newSer.Deserialize(reader); + return newSer.Deserialize(reader); } return null; } @@ -38,7 +38,7 @@ namespace PepperDash.Essentials.Core /// public override bool CanConvert(Type objectType) { - return objectType == typeof(ComPort.ComPortSpec); + return objectType == typeof(ComPort.ComPortSpec?); } public override bool CanRead { get { return true; } } diff --git a/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs b/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs index f54f4bfd..8fa4076a 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs @@ -51,7 +51,7 @@ namespace PepperDash.Essentials.Core switch (controlConfig.Method) { case eControlMethod.Com: - comm = new ComPortController(deviceConfig.Key + "-com", GetComPort, controlConfig.ComParams, controlConfig); + comm = new ComPortController(deviceConfig.Key + "-com", GetComPort, controlConfig.ComParams.Value, controlConfig); break; case eControlMethod.Cec: comm = new CecPortController(deviceConfig.Key + "-cec", GetCecPort, controlConfig); @@ -115,7 +115,7 @@ namespace PepperDash.Essentials.Core var comPar = config.ComParams; var dev = GetIComPortsDeviceFromManagedDevice(config.ControlPortDevKey); if (dev != null && config.ControlPortNumber <= dev.NumberOfComPorts) - return dev.ComPorts[config.ControlPortNumber]; + return dev.ComPorts[config.ControlPortNumber.Value]; Debug.LogMessage(LogEventLevel.Information, "GetComPort: Device '{0}' does not have com port {1}", config.ControlPortDevKey, config.ControlPortNumber); return null; } @@ -201,23 +201,26 @@ namespace PepperDash.Essentials.Core /// /// /// - public class EssentialsControlPropertiesConfig : - PepperDash.Core.ControlPropertiesConfig + public class EssentialsControlPropertiesConfig : + ControlPropertiesConfig { + [JsonProperty("comParams", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(ComSpecJsonConverter))] - public ComPort.ComPortSpec ComParams { get; set; } + public ComPort.ComPortSpec? ComParams { get; set; } - public string CresnetId { get; set; } + [JsonProperty("cresnetId", NullValueHandling = NullValueHandling.Ignore)] + public string CresnetId { get; set; } /// /// Attempts to provide uint conversion of string CresnetId /// + [JsonIgnore] public uint CresnetIdInt { get { - try + try { return Convert.ToUInt32(CresnetId, 16); } @@ -228,11 +231,13 @@ namespace PepperDash.Essentials.Core } } + [JsonProperty("infinetId", NullValueHandling = NullValueHandling.Ignore)] public string InfinetId { get; set; } /// /// Attepmts to provide uiont conversion of string InifinetId /// + [JsonIgnore] public uint InfinetIdInt { get diff --git a/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs b/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs index 9d0b9616..7c16021a 100644 --- a/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs +++ b/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading.Tasks; using Crestron.SimplSharp; using System.Reflection; using Newtonsoft.Json; @@ -82,7 +83,8 @@ namespace PepperDash.Essentials.Core var convertedParams = mParams .Select((p, i) => ConvertType(action.Params[i], p.ParameterType)) .ToArray(); - method.Invoke(obj, convertedParams); + + Task.Run(() => method.Invoke(obj, convertedParams)); CrestronConsole.ConsoleCommandResponse("Method {0} successfully called on device {1}", method.Name, action.DeviceKey); diff --git a/src/PepperDash.Essentials.Core/Devices/PC/InRoomPc.cs b/src/PepperDash.Essentials.Core/Devices/PC/InRoomPc.cs index 0b9e5815..31e4be22 100644 --- a/src/PepperDash.Essentials.Core/Devices/PC/InRoomPc.cs +++ b/src/PepperDash.Essentials.Core/Devices/PC/InRoomPc.cs @@ -5,7 +5,6 @@ using Crestron.SimplSharpPro; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; using PepperDash.Core; using Serilog.Events; diff --git a/src/PepperDash.Essentials.Core/Devices/PC/Laptop.cs b/src/PepperDash.Essentials.Core/Devices/PC/Laptop.cs index bfc09292..921d52c2 100644 --- a/src/PepperDash.Essentials.Core/Devices/PC/Laptop.cs +++ b/src/PepperDash.Essentials.Core/Devices/PC/Laptop.cs @@ -5,7 +5,6 @@ using Crestron.SimplSharpPro; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; using PepperDash.Core; using Serilog.Events; @@ -70,7 +69,7 @@ namespace PepperDash.Essentials.Core.Devices { public LaptopFactory() { - TypeNames = new List() { "laptop" }; + TypeNames = new List() { "deprecated" }; } public override EssentialsDevice BuildDevice(DeviceConfig dc) diff --git a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs index 491f76a9..b0372b51 100644 --- a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs @@ -144,22 +144,43 @@ namespace PepperDash.Essentials.Core [JsonProperty("isAudioSource")] public bool IsAudioSource { get; set; } + /// + /// Hide source on UI when Avanced Sharing is enabled + /// + [JsonProperty("disableAdvancedRouting")] + public bool DisableAdvancedRouting { get; set; } + + /// + /// Hide source on UI when Simpl Sharing is enabled + /// + [JsonProperty("disableSimpleRouting")] + public bool DisableSimpleRouting { get; set; } + public SourceListItem() { Icon = "Blank"; } - - } + public override string ToString() + { + return $"{SourceKey}:{Name}"; + } + } public class SourceRouteListItem { [JsonProperty("sourceKey")] public string SourceKey { get; set; } + [JsonProperty("sourcePortKey")] + public string SourcePortKey { get; set; } + [JsonProperty("destinationKey")] public string DestinationKey { get; set; } + [JsonProperty("destinationPortKey")] + public string DestinationPortKey { get; set; } + [JsonProperty("type")] public eRoutingSignalType Type { get; set; } } diff --git a/src/PepperDash.Essentials.Core/Display/BasicIrDisplay.cs b/src/PepperDash.Essentials.Core/Display/BasicIrDisplay.cs index a921f07b..394fa17a 100644 --- a/src/PepperDash.Essentials.Core/Display/BasicIrDisplay.cs +++ b/src/PepperDash.Essentials.Core/Display/BasicIrDisplay.cs @@ -10,7 +10,6 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Bridges; -using PepperDash.Essentials.Core.Routing; using Serilog.Events; namespace PepperDash.Essentials.Core diff --git a/src/PepperDash.Essentials.Core/Display/DisplayBase.cs b/src/PepperDash.Essentials.Core/Display/DisplayBase.cs index edfe8f1d..040e9d84 100644 --- a/src/PepperDash.Essentials.Core/Display/DisplayBase.cs +++ b/src/PepperDash.Essentials.Core/Display/DisplayBase.cs @@ -1,25 +1,23 @@  -using System; -using System.Collections.Generic; -using System.Linq; using Crestron.SimplSharp; using Crestron.SimplSharpPro.DeviceSupport; -using Crestron.SimplSharpPro.DM; -using Crestron.SimplSharpPro.DM.Endpoints; -using Crestron.SimplSharpPro.DM.Endpoints.Transmitters; using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Essentials.Core.Bridges; using Serilog.Events; +using System; +using System.Collections.Generic; +using System.Linq; namespace PepperDash.Essentials.Core { - [Obsolete("Please use PepperDash.Essentials.Devices.Common, this will be removed in 2.1")] + [Obsolete("Please use PepperDash.Essentials.Devices.Common, this will be removed in 2.1")] public abstract class DisplayBase : EssentialsDevice, IHasFeedback, IRoutingSinkWithSwitching, IHasPowerControl, IWarmingCooling, IUsageTracking { public event SourceInfoChangeHandler CurrentSourceChange; + public event InputChangedEventHandler InputChanged; public string CurrentSourceInfoKey { get; set; } public SourceListItem CurrentSourceInfo @@ -96,7 +94,9 @@ namespace PepperDash.Essentials.Core } } - public abstract void ExecuteSwitch(object selector); + public RoutingInputPort CurrentInputPort => throw new NotImplementedException(); + + public abstract void ExecuteSwitch(object selector); protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) diff --git a/src/PepperDash.Essentials.Core/Display/MockDisplay.cs b/src/PepperDash.Essentials.Core/Display/MockDisplay.cs index dcd9e949..973e550b 100644 --- a/src/PepperDash.Essentials.Core/Display/MockDisplay.cs +++ b/src/PepperDash.Essentials.Core/Display/MockDisplay.cs @@ -11,7 +11,6 @@ using Crestron.SimplSharpPro.DM.Endpoints.Transmitters; using PepperDash.Core; using PepperDash.Essentials.Core.Bridges; -using PepperDash.Essentials.Core.Routing; using PepperDash.Essentials.Core.Config; using Serilog.Events; diff --git a/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs b/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs index 63c6b224..07bcc56a 100644 --- a/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs @@ -149,18 +149,7 @@ namespace PepperDash.Essentials.Core } catch (Exception ex) { - Debug.LogMessage(LogEventLevel.Error, "Exception occurred while creating device {0}: {1}", dc.Key, ex.Message); - - Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.StackTrace); - - if (ex.InnerException == null) - { - return null; - } - - Debug.LogMessage(LogEventLevel.Error, "Inner exception while creating device {0}: {1}", dc.Key, - ex.InnerException.Message); - Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.InnerException.StackTrace); + Debug.LogMessage(ex, "Exception occurred while creating device {0}: {1}", null, dc.Key, ex.Message); return null; } } diff --git a/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs b/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs index 050d6cd8..04f2490a 100644 --- a/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs +++ b/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs @@ -1,9 +1,5 @@  -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronXml; @@ -14,6 +10,10 @@ using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Essentials.Core.Config; using Serilog.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; namespace PepperDash.Essentials.Core.Fusion { diff --git a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj index 72dbb279..c074651a 100644 --- a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj +++ b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj @@ -25,8 +25,8 @@ pdbonly - - + + diff --git a/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs b/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs index 96c96e58..4759b2cd 100644 --- a/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs +++ b/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs @@ -66,7 +66,7 @@ namespace PepperDash.Essentials Debug.LogMessage(LogEventLevel.Verbose, "Found {0} Assemblies", assemblyFiles.Length); - foreach (var fi in assemblyFiles) + foreach (var fi in assemblyFiles.Where(fi => fi.Name.Contains("Essentials") || fi.Name.Contains("PepperDash"))) { string version = string.Empty; Assembly assembly = null; @@ -130,24 +130,31 @@ namespace PepperDash.Essentials /// static LoadedAssembly LoadAssembly(string filePath) { - //Debug.LogMessage(LogEventLevel.Verbose, "Attempting to load {0}", filePath); - var assembly = Assembly.LoadFrom(filePath); - if (assembly != null) + try { - var assyVersion = GetAssemblyVersion(assembly); + //Debug.LogMessage(LogEventLevel.Verbose, "Attempting to load {0}", filePath); + var assembly = Assembly.LoadFrom(filePath); + if (assembly != null) + { + var assyVersion = GetAssemblyVersion(assembly); - var loadedAssembly = new LoadedAssembly(assembly.GetName().Name, assyVersion, assembly); - LoadedAssemblies.Add(loadedAssembly); - Debug.LogMessage(LogEventLevel.Information, "Loaded assembly '{0}', version {1}", loadedAssembly.Name, loadedAssembly.Version); - return loadedAssembly; - } - else + var loadedAssembly = new LoadedAssembly(assembly.GetName().Name, assyVersion, assembly); + LoadedAssemblies.Add(loadedAssembly); + Debug.LogMessage(LogEventLevel.Information, "Loaded assembly '{0}', version {1}", loadedAssembly.Name, loadedAssembly.Version); + return loadedAssembly; + } + else + { + Debug.LogMessage(LogEventLevel.Information, "Unable to load assembly: '{0}'", filePath); + } + + return null; + } catch(Exception ex) { - Debug.LogMessage(LogEventLevel.Information, "Unable to load assembly: '{0}'", filePath); + Debug.LogMessage(ex, "Error loading assembly from {path}", null, filePath); + return null; } - return null; - } /// diff --git a/src/PepperDash.Essentials.Core/Room/Interfaces.cs b/src/PepperDash.Essentials.Core/Room/Interfaces.cs index 36e785cd..c7635d7a 100644 --- a/src/PepperDash.Essentials.Core/Room/Interfaces.cs +++ b/src/PepperDash.Essentials.Core/Room/Interfaces.cs @@ -40,7 +40,7 @@ namespace PepperDash.Essentials.Core { void RunRouteAction(string routeKey, string sourceListKey); - void RunRouteAction(string routeKey, string sourceListKey, Action successCallback); + void RunRouteAction(string routeKey, string sourceListKey, Action successCallback); } /// diff --git a/src/PepperDash.Essentials.Core/Routing/DummyRoutingInputsDevice.cs b/src/PepperDash.Essentials.Core/Routing/DummyRoutingInputsDevice.cs index c6f96c74..d8b50a9b 100644 --- a/src/PepperDash.Essentials.Core/Routing/DummyRoutingInputsDevice.cs +++ b/src/PepperDash.Essentials.Core/Routing/DummyRoutingInputsDevice.cs @@ -8,7 +8,7 @@ using PepperDash.Core; namespace PepperDash.Essentials.Core.Routing { - public class DummyRoutingInputsDevice : Device, IRoutingSource + public class DummyRoutingInputsDevice : Device, IRoutingSource, IRoutingOutputs { /// /// A single output port, backplane, audioVideo diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs new file mode 100644 index 00000000..03c59bf0 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -0,0 +1,251 @@ +using PepperDash.Core; +using Serilog.Events; +using System.Collections.Generic; +using System.Linq; + + +namespace PepperDash.Essentials.Core +{ + + /// + /// Extensions added to any IRoutingInputs classes to provide discovery-based routing + /// on those destinations. + /// + public static class Extensions + { + private static readonly Dictionary RouteRequests = new Dictionary(); + /// + /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute + /// and then attempts a new Route and if sucessful, stores that RouteDescriptor + /// in RouteDescriptorCollection.DefaultCollection + /// + public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) + { + var routeRequest = new RouteRequest { + Destination = destination, + Source = source, + SignalType = signalType + }; + + var coolingDevice = destination as IWarmingCooling; + + + //We already have a route request for this device, and it's a cooling device and is cooling + if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) + { + coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown; + + coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; + + RouteRequests[destination.Key] = routeRequest; + + Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down and already has a routing request stored. Storing new route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key); + + return; + } + + //New Request + if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) + { + coolingDevice.IsCoolingDownFeedback.OutputChange -= routeRequest.HandleCooldown; + + coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; + + RouteRequests.Add(destination.Key, routeRequest); + + Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down. Storing route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key); + return; + } + + if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false) + { + RouteRequests.Remove(destination.Key); + Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is NOT cooling down. Removing stored route request and routing to source key: {1}", null, destination.Key, routeRequest.Source.Key); + } + + destination.ReleaseRoute(); + + RunRouteRequest(routeRequest); + } + + private static void RunRouteRequest(RouteRequest request) + { + if (request.Source == null) + return; + + var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType); + + if (newRoute == null) + return; + + RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute); + + Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination); + + newRoute.ExecuteRoutes(); + } + + /// + /// Will release the existing route on the destination, if it is found in + /// RouteDescriptorCollection.DefaultCollection + /// + /// + public static void ReleaseRoute(this IRoutingSink destination) + { + + if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling) + { + var coolingDevice = destination as IWarmingCooling; + + coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown; + } + + RouteRequests.Remove(destination.Key); + + var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination); + if (current != null) + { + Debug.LogMessage(LogEventLevel.Debug, "Releasing current route: {0}", destination, current.Source.Key); + current.ReleaseRoutes(); + } + } + + /// + /// Builds a RouteDescriptor that contains the steps necessary to make a route between devices. + /// Routes of type AudioVideo will be built as two separate routes, audio and video. If + /// a route is discovered, a new RouteDescriptor is returned. If one or both parts + /// of an audio/video route are discovered a route descriptor is returned. If no route is + /// discovered, then null is returned + /// + public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) + { + var routeDescriptor = new RouteDescriptor(source, destination, signalType); + + // if it's a single signal type, find the route + if (!signalType.HasFlag(eRoutingSignalType.AudioVideo)) + { + Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {0}", null, source.Key); + + if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescriptor)) + routeDescriptor = null; + + return routeDescriptor; + } + // otherwise, audioVideo needs to be handled as two steps. + + Debug.LogMessage(LogEventLevel.Debug, "Attempting to build audio and video routes from {0}", destination, source.Key); + + var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescriptor); + + if (!audioSuccess) + Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key); + + var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescriptor); + + if (!videoSuccess) + Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key); + + if (!audioSuccess && !videoSuccess) + routeDescriptor = null; + + + return routeDescriptor; + } + + /// + /// The recursive part of this. Will stop on each device, search its inputs for the + /// desired source and if not found, invoke this function for the each input port + /// hoping to find the source. + /// + /// + /// + /// The RoutingOutputPort whose link is being checked for a route + /// Prevents Devices from being twice-checked + /// This recursive function should not be called with AudioVideo + /// Just an informational counter + /// The RouteDescriptor being populated as the route is discovered + /// true if source is hit + static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, + RoutingOutputPort outputPortToUse, List alreadyCheckedDevices, + eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable) + { + cycle++; + + Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", null, cycle, source.Key, destination.Key); + + RoutingInputPort goodInputPort = null; + + var destinationTieLines = TieLineCollection.Default.Where(t => + t.DestinationPort.ParentDevice == destination && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo))); + + // find a direct tie + var directTie = destinationTieLines.FirstOrDefault( + t => t.DestinationPort.ParentDevice == destination + && t.SourcePort.ParentDevice == source); + if (directTie != null) // Found a tie directly to the source + { + goodInputPort = directTie.DestinationPort; + } + else // no direct-connect. Walk back devices. + { + Debug.LogMessage(LogEventLevel.Verbose, "is not directly connected to {0}. Walking down tie lines", destination, source.Key); + + // No direct tie? Run back out on the inputs' attached devices... + // Only the ones that are routing devices + var attachedMidpoints = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); + + //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration + if (alreadyCheckedDevices == null) + alreadyCheckedDevices = new List(); + alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs); + + foreach (var inputTieToTry in attachedMidpoints) + { + var upstreamDeviceOutputPort = inputTieToTry.SourcePort; + var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs; + Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {0}", destination, upstreamRoutingDevice.Key); + + // Check if this previous device has already been walked + if (alreadyCheckedDevices.Contains(upstreamRoutingDevice)) + { + Debug.LogMessage(LogEventLevel.Verbose, "Skipping input {0} on {1}, this was already checked", destination, upstreamRoutingDevice.Key, destination.Key); + continue; + } + // haven't seen this device yet. Do it. Pass the output port to the next + // level to enable switching on success + var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort, + alreadyCheckedDevices, signalType, cycle, routeTable); + if (upstreamRoutingSuccess) + { + Debug.LogMessage(LogEventLevel.Verbose, "Upstream device route found", destination); + goodInputPort = inputTieToTry.DestinationPort; + break; // Stop looping the inputs in this cycle + } + } + } + + + if (goodInputPort == null) + { + Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key); + return false; + } + + // we have a route on corresponding inputPort. *** Do the route *** + + if (outputPortToUse == null) + { + // it's a sink device + routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort)); + } + else if (destination is IRouting) + { + routeTable.Routes.Add(new RouteSwitchDescriptor(outputPortToUse, goodInputPort)); + } + else // device is merely IRoutingInputOutputs + Debug.LogMessage(LogEventLevel.Verbose, "No routing. Passthrough device", destination); + + return true; + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IHasCurrentSourceInfoChange.cs b/src/PepperDash.Essentials.Core/Routing/IHasCurrentSourceInfoChange.cs new file mode 100644 index 00000000..505a8652 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IHasCurrentSourceInfoChange.cs @@ -0,0 +1,30 @@ +/* Unmerged change from project 'PepperDash.Essentials.Core (net6)' +Before: +namespace PepperDash.Essentials.Core.Routing.Interfaces +After: +using PepperDash; +using PepperDash.Essentials; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Routing; +using PepperDash.Essentials.Core.Routing; +using PepperDash.Essentials.Core.Routing.Interfaces +*/ +namespace PepperDash.Essentials.Core +{ + /// + /// The handler type for a Room's SourceInfoChange + /// + public delegate void SourceInfoChangeHandler(SourceListItem info, ChangeType type); + //******************************************************************************************* + // Interfaces + + /// + /// For rooms with a single presentation source, change event + /// + public interface IHasCurrentSourceInfoChange + { + string CurrentSourceInfoKey { get; set; } + SourceListItem CurrentSourceInfo { get; set; } + event SourceInfoChangeHandler CurrentSourceChange; + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IInputSync.cs b/src/PepperDash.Essentials.Core/Routing/IInputSync.cs index 465b48a2..accb70a7 100644 --- a/src/PepperDash.Essentials.Core/Routing/IInputSync.cs +++ b/src/PepperDash.Essentials.Core/Routing/IInputSync.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace PepperDash.Essentials.Core.Routing { - public interface IVideoSync: IKeyed + public interface IVideoSync : IKeyed { bool VideoSyncDetected { get; } diff --git a/src/PepperDash.Essentials.Core/Routing/IRmcRouting.cs b/src/PepperDash.Essentials.Core/Routing/IRmcRouting.cs new file mode 100644 index 00000000..52290bd2 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRmcRouting.cs @@ -0,0 +1,10 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines a receiver that has internal routing (DM-RMC-4K-Z-SCALER-C) + /// + public interface IRmcRouting : IRoutingNumeric + { + IntFeedback AudioVideoSourceNumericFeedback { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRmcRoutingWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRmcRoutingWithFeedback.cs new file mode 100644 index 00000000..a8e6d5f0 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRmcRoutingWithFeedback.cs @@ -0,0 +1,9 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines an IRmcRouting with a feedback event + /// + public interface IRmcRoutingWithFeedback : IRmcRouting + { + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRouting.cs b/src/PepperDash.Essentials.Core/Routing/IRouting.cs new file mode 100644 index 00000000..e3231858 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRouting.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines a midpoint device as have internal routing. Any devices in the middle of the + /// signal chain, that do switching, must implement this for routing to work otherwise + /// the routing algorithm will treat the IRoutingInputsOutputs device as a passthrough + /// device. + /// + public interface IRouting : IRoutingInputsOutputs + { + void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType); + } + + /*public interface IRouting : IRoutingInputsOutputs + { + void ExecuteSwitch(TInputSelector inputSelector, TOutputSelector outputSelector, eRoutingSignalType signalType); + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingFeedback.cs new file mode 100644 index 00000000..b8b22915 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingFeedback.cs @@ -0,0 +1,16 @@ +using System; + +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines an event structure for reporting output route data + /// + public interface IRoutingFeedback : IKeyName + { + event EventHandler NumericSwitchChange; + //void OnSwitchChange(RoutingNumericEventArgs e); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingHasVideoInputSyncFeedbacks.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingHasVideoInputSyncFeedbacks.cs new file mode 100644 index 00000000..0f6d7834 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingHasVideoInputSyncFeedbacks.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DM; + +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + + public interface IRoutingHasVideoInputSyncFeedbacks + { + FeedbackCollection VideoInputSyncFeedbacks { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs new file mode 100644 index 00000000..ff96fded --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs @@ -0,0 +1,18 @@ +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines a class that has a collection of RoutingInputPorts + /// + public interface IRoutingInputs : IKeyed + { + RoutingPortCollection InputPorts { get; } + } + +/* public interface IRoutingInputs : IKeyed + { + RoutingPortCollection, TSelector> InputPorts { get; } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingInputsExtensions.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingInputsExtensions.cs deleted file mode 100644 index 605569d2..00000000 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingInputsExtensions.cs +++ /dev/null @@ -1,426 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharpPro; -using Crestron.SimplSharpPro.DM; - -using PepperDash.Core; -using Serilog.Events; - - -namespace PepperDash.Essentials.Core -{ - public class RouteRequest - { - public IRoutingSink Destination {get; set;} - public IRoutingOutputs Source {get; set;} - public eRoutingSignalType SignalType {get; set;} - - public void HandleCooldown(object sender, FeedbackEventArgs args) - { - var coolingDevice = sender as IWarmingCooling; - - if(args.BoolValue == false) - { - Destination.ReleaseAndMakeRoute(Source, SignalType); - - if(sender == null) return; - - coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; - } - } - } - - /// - /// Extensions added to any IRoutingInputs classes to provide discovery-based routing - /// on those destinations. - /// - public static class IRoutingInputsExtensions - { - private static Dictionary RouteRequests = new Dictionary(); - /// - /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute - /// and then attempts a new Route and if sucessful, stores that RouteDescriptor - /// in RouteDescriptorCollection.DefaultCollection - /// - public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) - { - var routeRequest = new RouteRequest { - Destination = destination, - Source = source, - SignalType = signalType - }; - - var coolingDevice = destination as IWarmingCooling; - - RouteRequest existingRouteRequest; - - //We already have a route request for this device, and it's a cooling device and is cooling - if (RouteRequests.TryGetValue(destination.Key, out existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) - { - coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown; - - coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; - - RouteRequests[destination.Key] = routeRequest; - - Debug.LogMessage(LogEventLevel.Verbose, "******************************************************** Device: {0} is cooling down and already has a routing request stored. Storing new route request to route to source key: {1}", destination.Key, routeRequest.Source.Key); - - return; - } - - //New Request - if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) - { - coolingDevice.IsCoolingDownFeedback.OutputChange -= routeRequest.HandleCooldown; - - coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; - - RouteRequests.Add(destination.Key, routeRequest); - - Debug.LogMessage(LogEventLevel.Verbose, "******************************************************** Device: {0} is cooling down. Storing route request to route to source key: {1}", destination.Key, routeRequest.Source.Key); - return; - } - - if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false) - { - RouteRequests.Remove(destination.Key); - Debug.LogMessage(LogEventLevel.Verbose, "******************************************************** Device: {0} is NOT cooling down. Removing stored route request and routing to source key: {1}", destination.Key, routeRequest.Source.Key); - } - - destination.ReleaseRoute(); - - RunRouteRequest(routeRequest); - } - - public static void RunRouteRequest(RouteRequest request) - { - if (request.Source == null) return; - var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType); - if (newRoute == null) return; - RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute); - Debug.LogMessage(LogEventLevel.Verbose, request.Destination, "Executing full route"); - newRoute.ExecuteRoutes(); - } - - /// - /// Will release the existing route on the destination, if it is found in - /// RouteDescriptorCollection.DefaultCollection - /// - /// - public static void ReleaseRoute(this IRoutingSink destination) - { - RouteRequest existingRequest; - - if (RouteRequests.TryGetValue(destination.Key, out existingRequest) && destination is IWarmingCooling) - { - var coolingDevice = destination as IWarmingCooling; - - coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown; - } - - RouteRequests.Remove(destination.Key); - - var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination); - if (current != null) - { - Debug.LogMessage(LogEventLevel.Debug, destination, "Releasing current route: {0}", current.Source.Key); - current.ReleaseRoutes(); - } - } - - /// - /// Builds a RouteDescriptor that contains the steps necessary to make a route between devices. - /// Routes of type AudioVideo will be built as two separate routes, audio and video. If - /// a route is discovered, a new RouteDescriptor is returned. If one or both parts - /// of an audio/video route are discovered a route descriptor is returned. If no route is - /// discovered, then null is returned - /// - public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) - { - var routeDescr = new RouteDescriptor(source, destination, signalType); - // if it's a single signal type, find the route - if ((signalType & (eRoutingSignalType.Audio & eRoutingSignalType.Video)) == (eRoutingSignalType.Audio & eRoutingSignalType.Video)) - { - Debug.LogMessage(LogEventLevel.Debug, destination, "Attempting to build source route from {0}", source.Key); - if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescr)) - routeDescr = null; - } - // otherwise, audioVideo needs to be handled as two steps. - else - { - Debug.LogMessage(LogEventLevel.Debug, destination, "Attempting to build audio and video routes from {0}", source.Key); - var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescr); - if (!audioSuccess) - Debug.LogMessage(LogEventLevel.Debug, destination, "Cannot find audio route to {0}", source.Key); - var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescr); - if (!videoSuccess) - Debug.LogMessage(LogEventLevel.Debug, destination, "Cannot find video route to {0}", source.Key); - if (!audioSuccess && !videoSuccess) - routeDescr = null; - } - - //Debug.LogMessage(LogEventLevel.Debug, destination, "Route{0} discovered", routeDescr == null ? " NOT" : ""); - return routeDescr; - } - - /// - /// The recursive part of this. Will stop on each device, search its inputs for the - /// desired source and if not found, invoke this function for the each input port - /// hoping to find the source. - /// - /// - /// - /// The RoutingOutputPort whose link is being checked for a route - /// Prevents Devices from being twice-checked - /// This recursive function should not be called with AudioVideo - /// Just an informational counter - /// The RouteDescriptor being populated as the route is discovered - /// true if source is hit - static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, - RoutingOutputPort outputPortToUse, List alreadyCheckedDevices, - eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable) - { - cycle++; - Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", cycle, source.Key, destination.Key); - - RoutingInputPort goodInputPort = null; - var destDevInputTies = TieLineCollection.Default.Where(t => - t.DestinationPort.ParentDevice == destination && (t.Type == signalType || (t.Type & (eRoutingSignalType.Audio | eRoutingSignalType.Video)) == (eRoutingSignalType.Audio | eRoutingSignalType.Video))); - - // find a direct tie - var directTie = destDevInputTies.FirstOrDefault( - t => t.DestinationPort.ParentDevice == destination - && t.SourcePort.ParentDevice == source); - if (directTie != null) // Found a tie directly to the source - { - goodInputPort = directTie.DestinationPort; - } - else // no direct-connect. Walk back devices. - { - Debug.LogMessage(LogEventLevel.Verbose, destination, "is not directly connected to {0}. Walking down tie lines", source.Key); - - // No direct tie? Run back out on the inputs' attached devices... - // Only the ones that are routing devices - var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); - - //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration - if (alreadyCheckedDevices == null) - alreadyCheckedDevices = new List(); - alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs); - - foreach (var inputTieToTry in attachedMidpoints) - { - var upstreamDeviceOutputPort = inputTieToTry.SourcePort; - var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs; - Debug.LogMessage(LogEventLevel.Verbose, destination, "Trying to find route on {0}", upstreamRoutingDevice.Key); - - // Check if this previous device has already been walked - if (alreadyCheckedDevices.Contains(upstreamRoutingDevice)) - { - Debug.LogMessage(LogEventLevel.Verbose, destination, "Skipping input {0} on {1}, this was already checked", upstreamRoutingDevice.Key, destination.Key); - continue; - } - // haven't seen this device yet. Do it. Pass the output port to the next - // level to enable switching on success - var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort, - alreadyCheckedDevices, signalType, cycle, routeTable); - if (upstreamRoutingSuccess) - { - Debug.LogMessage(LogEventLevel.Verbose, destination, "Upstream device route found"); - goodInputPort = inputTieToTry.DestinationPort; - break; // Stop looping the inputs in this cycle - } - } - } - - // we have a route on corresponding inputPort. *** Do the route *** - if (goodInputPort != null) - { - //Debug.LogMessage(LogEventLevel.Verbose, destination, "adding RouteDescriptor"); - if (outputPortToUse == null) - { - // it's a sink device - routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort)); - } - else if (destination is IRouting) - { - routeTable.Routes.Add(new RouteSwitchDescriptor (outputPortToUse, goodInputPort)); - } - else // device is merely IRoutingInputOutputs - Debug.LogMessage(LogEventLevel.Verbose, destination, " No routing. Passthrough device"); - //Debug.LogMessage(LogEventLevel.Verbose, destination, "Exiting cycle {0}", cycle); - return true; - } - - Debug.LogMessage(LogEventLevel.Verbose, destination, "No route found to {0}", source.Key); - return false; - } - } - - - - - - // MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE - - - /// - /// A collection of RouteDescriptors - typically the static DefaultCollection is used - /// - public class RouteDescriptorCollection - { - public static RouteDescriptorCollection DefaultCollection - { - get - { - if (_DefaultCollection == null) - _DefaultCollection = new RouteDescriptorCollection(); - return _DefaultCollection; - } - } - static RouteDescriptorCollection _DefaultCollection; - - List RouteDescriptors = new List(); - - /// - /// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the - /// destination exists already, it will not be added - in order to preserve - /// proper route releasing. - /// - /// - public void AddRouteDescriptor(RouteDescriptor descriptor) - { - if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)) - { - Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination, - "Route to [{0}] already exists in global routes table", descriptor.Source.Key); - return; - } - RouteDescriptors.Add(descriptor); - } - - /// - /// Gets the RouteDescriptor for a destination - /// - /// null if no RouteDescriptor for a destination exists - public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination) - { - return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination); - } - - /// - /// Returns the RouteDescriptor for a given destination AND removes it from collection. - /// Returns null if no route with the provided destination exists. - /// - public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination) - { - var descr = GetRouteDescriptorForDestination(destination); - if (descr != null) - RouteDescriptors.Remove(descr); - return descr; - } - } - - /// - /// Represents an collection of individual route steps between Source and Destination - /// - public class RouteDescriptor - { - public IRoutingInputs Destination { get; private set; } - public IRoutingOutputs Source { get; private set; } - public eRoutingSignalType SignalType { get; private set; } - public List Routes { get; private set; } - - - public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) - { - Destination = destination; - Source = source; - SignalType = signalType; - Routes = new List(); - } - - /// - /// Executes all routes described in this collection. Typically called via - /// extension method IRoutingInputs.ReleaseAndMakeRoute() - /// - public void ExecuteRoutes() - { - foreach (var route in Routes) - { - Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", route.ToString()); - if (route.SwitchingDevice is IRoutingSink) - { - var device = route.SwitchingDevice as IRoutingSinkWithSwitching; - if (device == null) - continue; - - device.ExecuteSwitch(route.InputPort.Selector); - } - else if (route.SwitchingDevice is IRouting) - { - (route.SwitchingDevice as IRouting).ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType); - route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType); - Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); - } - } - } - - /// - /// Releases all routes in this collection. Typically called via - /// extension method IRoutingInputs.ReleaseAndMakeRoute() - /// - public void ReleaseRoutes() - { - foreach (var route in Routes) - { - if (route.SwitchingDevice is IRouting) - { - // Pull the route from the port. Whatever is watching the output's in use tracker is - // responsible for responding appropriately. - route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType); - Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); - } - } - } - - public override string ToString() - { - var routesText = Routes.Select(r => r.ToString()).ToArray(); - return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText)); - } - } - - /// - /// Represents an individual link for a route - /// - public class RouteSwitchDescriptor - { - public IRoutingInputs SwitchingDevice { get { return InputPort.ParentDevice; } } - public RoutingOutputPort OutputPort { get; set; } - public RoutingInputPort InputPort { get; set; } - - public RouteSwitchDescriptor(RoutingInputPort inputPort) - { - InputPort = inputPort; - } - - public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort) - { - InputPort = inputPort; - OutputPort = outputPort; - } - - public override string ToString() - { - if(SwitchingDevice is IRouting) - return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector); - else - return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector); - - } - } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingInputsOutputs.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingInputsOutputs.cs new file mode 100644 index 00000000..e202fa3c --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingInputsOutputs.cs @@ -0,0 +1,16 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// For devices like RMCs, baluns, other devices with no switching. + /// + public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs + { + } + +/* /// + /// For devices like RMCs, baluns, other devices with no switching. + /// + public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs + { + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingNumeric.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingNumeric.cs new file mode 100644 index 00000000..d41909f1 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingNumeric.cs @@ -0,0 +1,7 @@ +namespace PepperDash.Essentials.Core +{ + public interface IRoutingNumeric : IRouting + { + void ExecuteNumericSwitch(ushort input, ushort output, eRoutingSignalType type); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingNumericWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingNumericWithFeedback.cs new file mode 100644 index 00000000..e278a193 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingNumericWithFeedback.cs @@ -0,0 +1,9 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines an IRoutingNumeric with a feedback event + /// + public interface IRoutingNumericWithFeedback : IRoutingNumeric, IRoutingFeedback + { + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs new file mode 100644 index 00000000..d3bc70de --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs @@ -0,0 +1,19 @@ +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines a class that has a collection of RoutingOutputPorts + /// + + public interface IRoutingOutputs : IKeyed + { + RoutingPortCollection OutputPorts { get; } + } + +/* public interface IRoutingOutputs : IKeyed + { + RoutingPortCollection, TSelector> OutputPorts { get; } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs new file mode 100644 index 00000000..b05c7744 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs @@ -0,0 +1,23 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// For fixed-source endpoint devices + /// + public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange + { + } + + public interface IRoutingSinkWithInputPort :IRoutingSink + { + RoutingInputPort CurrentInputPort { get; } + } + /*/// + /// For fixed-source endpoint devices + /// + public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange + { + void UpdateRouteRequest(RouteRequest request); + + RouteRequest GetRouteRequest(); + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithFeedback.cs new file mode 100644 index 00000000..d3d3e600 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithFeedback.cs @@ -0,0 +1,25 @@ +using PepperDash.Essentials.Core.Routing; +using System; +using System.Collections.Generic; + +namespace PepperDash.Essentials.Core +{ + + /// + /// For fixed-source endpoint devices + /// + public interface IRoutingSinkWithFeedback : IRoutingSinkWithSwitching + { + + } + +/* /// + /// For fixed-source endpoint devices + /// + public interface IRoutingSinkWithFeedback : IRoutingSinkWithSwitching + { + RouteSwitchDescriptor CurrentRoute { get; } + + event EventHandler InputChanged; + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs new file mode 100644 index 00000000..d6f97c07 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs @@ -0,0 +1,27 @@ +using System; + +namespace PepperDash.Essentials.Core +{ + public delegate void InputChangedEventHandler(IRoutingSinkWithSwitching destination, RoutingInputPort currentPort); + + /// + /// Endpoint device like a display, that selects inputs + /// + public interface IRoutingSinkWithSwitching : IRoutingSink + { + void ExecuteSwitch(object inputSelector); + } + + public interface IRoutingSinkWithSwitchingWithInputPort:IRoutingSinkWithSwitching, IRoutingSinkWithInputPort + { + event InputChangedEventHandler InputChanged; + } + +/* /// + /// Endpoint device like a display, that selects inputs + /// + public interface IRoutingSinkWithSwitching : IRoutingSink + { + void ExecuteSwitch(TSelector inputSelector); + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs new file mode 100644 index 00000000..f4705b8d --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs @@ -0,0 +1,9 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines an IRoutingOutputs devices as being a source - the start of the chain + /// + public interface IRoutingSource + { + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingWithClear.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingWithClear.cs new file mode 100644 index 00000000..077d4ecd --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingWithClear.cs @@ -0,0 +1,12 @@ +namespace PepperDash.Essentials.Core +{ + public interface IRoutingWithClear : IRouting + { + /// + /// Clears a route to an output, however a device needs to do that + /// + /// Output to clear + /// signal type to clear + void ClearRoute(object outputSelector, eRoutingSignalType signalType); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingWithFeedback.cs new file mode 100644 index 00000000..dcf4e423 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingWithFeedback.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System; + +namespace PepperDash.Essentials.Core +{ + public delegate void RouteChangedEventHandler(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute); + /// + /// Defines an IRouting with a feedback event + /// + public interface IRoutingWithFeedback : IRouting + { + List CurrentRoutes { get; } + + event RouteChangedEventHandler RouteChanged; + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/ITxRouting.cs b/src/PepperDash.Essentials.Core/Routing/ITxRouting.cs new file mode 100644 index 00000000..0950e6a6 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/ITxRouting.cs @@ -0,0 +1,8 @@ +namespace PepperDash.Essentials.Core +{ + public interface ITxRouting : IRoutingNumeric + { + IntFeedback VideoSourceNumericFeedback { get; } + IntFeedback AudioSourceNumericFeedback { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/ITxRoutingWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/ITxRoutingWithFeedback.cs new file mode 100644 index 00000000..484fa134 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/ITxRoutingWithFeedback.cs @@ -0,0 +1,9 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines an IRmcRouting with a feedback event + /// + public interface ITxRoutingWithFeedback : ITxRouting + { + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs b/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs new file mode 100644 index 00000000..ebacc809 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs @@ -0,0 +1,152 @@ +using System.Collections.Generic; +using System.Linq; +using Crestron.SimplSharpPro; + +using PepperDash.Core; +using Serilog.Events; + + +namespace PepperDash.Essentials.Core +{ + /// + /// Represents an collection of individual route steps between Source and Destination + /// + public class RouteDescriptor + { + public IRoutingInputs Destination { get; private set; } + public IRoutingOutputs Source { get; private set; } + public eRoutingSignalType SignalType { get; private set; } + public List Routes { get; private set; } + + + public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) + { + Destination = destination; + Source = source; + SignalType = signalType; + Routes = new List(); + } + + /// + /// Executes all routes described in this collection. Typically called via + /// extension method IRoutingInputs.ReleaseAndMakeRoute() + /// + public void ExecuteRoutes() + { + foreach (var route in Routes) + { + Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}",null, route.ToString()); + + if (route.SwitchingDevice is IRoutingSinkWithSwitching sink) + { + sink.ExecuteSwitch(route.InputPort.Selector); + continue; + } + + if (route.SwitchingDevice is IRouting switchingDevice) + { + switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType); + + route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType); + + Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); + } + } + } + + /// + /// Releases all routes in this collection. Typically called via + /// extension method IRoutingInputs.ReleaseAndMakeRoute() + /// + public void ReleaseRoutes() + { + foreach (var route in Routes) + { + if (route.SwitchingDevice is IRouting) + { + // Pull the route from the port. Whatever is watching the output's in use tracker is + // responsible for responding appropriately. + route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType); + Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}",null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); + } + } + } + + public override string ToString() + { + var routesText = Routes.Select(r => r.ToString()).ToArray(); + return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText)); + } + } + + /*/// + /// Represents an collection of individual route steps between Source and Destination + /// + public class RouteDescriptor + { + public IRoutingInputs Destination { get; private set; } + public IRoutingOutputs Source { get; private set; } + public eRoutingSignalType SignalType { get; private set; } + public List> Routes { get; private set; } + + + public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) + { + Destination = destination; + Source = source; + SignalType = signalType; + Routes = new List>(); + } + + /// + /// Executes all routes described in this collection. Typically called via + /// extension method IRoutingInputs.ReleaseAndMakeRoute() + /// + public void ExecuteRoutes() + { + foreach (var route in Routes) + { + Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", null, route.ToString()); + + if (route.SwitchingDevice is IRoutingSinkWithSwitching sink) + { + sink.ExecuteSwitch(route.InputPort.Selector); + continue; + } + + if (route.SwitchingDevice is IRouting switchingDevice) + { + switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType); + + route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType); + + Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); + } + } + } + + /// + /// Releases all routes in this collection. Typically called via + /// extension method IRoutingInputs.ReleaseAndMakeRoute() + /// + public void ReleaseRoutes() + { + foreach (var route in Routes) + { + if (route.SwitchingDevice is IRouting) + { + // Pull the route from the port. Whatever is watching the output's in use tracker is + // responsible for responding appropriately. + route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType); + Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); + } + } + } + + public override string ToString() + { + var routesText = Routes.Select(r => r.ToString()).ToArray(); + return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText)); + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs b/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs new file mode 100644 index 00000000..c9e3da37 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using System.Linq; + +using PepperDash.Core; +using Serilog.Events; + + +namespace PepperDash.Essentials.Core +{ + /// + /// A collection of RouteDescriptors - typically the static DefaultCollection is used + /// + public class RouteDescriptorCollection + { + public static RouteDescriptorCollection DefaultCollection + { + get + { + if (_DefaultCollection == null) + _DefaultCollection = new RouteDescriptorCollection(); + return _DefaultCollection; + } + } + private static RouteDescriptorCollection _DefaultCollection; + + private readonly List RouteDescriptors = new List(); + + /// + /// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the + /// destination exists already, it will not be added - in order to preserve + /// proper route releasing. + /// + /// + public void AddRouteDescriptor(RouteDescriptor descriptor) + { + if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)) + { + Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination, + "Route to [{0}] already exists in global routes table", descriptor.Source.Key); + return; + } + RouteDescriptors.Add(descriptor); + } + + /// + /// Gets the RouteDescriptor for a destination + /// + /// null if no RouteDescriptor for a destination exists + public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination) + { + return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination); + } + + /// + /// Returns the RouteDescriptor for a given destination AND removes it from collection. + /// Returns null if no route with the provided destination exists. + /// + public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination) + { + var descr = GetRouteDescriptorForDestination(destination); + if (descr != null) + RouteDescriptors.Remove(descr); + return descr; + } + } + + /*/// + /// A collection of RouteDescriptors - typically the static DefaultCollection is used + /// + public class RouteDescriptorCollection + { + public static RouteDescriptorCollection DefaultCollection + { + get + { + if (_DefaultCollection == null) + _DefaultCollection = new RouteDescriptorCollection(); + return _DefaultCollection; + } + } + private static RouteDescriptorCollection _DefaultCollection; + + private readonly List RouteDescriptors = new List(); + + /// + /// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the + /// destination exists already, it will not be added - in order to preserve + /// proper route releasing. + /// + /// + public void AddRouteDescriptor(RouteDescriptor descriptor) + { + if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)) + { + Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination, + "Route to [{0}] already exists in global routes table", descriptor.Source.Key); + return; + } + RouteDescriptors.Add(descriptor); + } + + /// + /// Gets the RouteDescriptor for a destination + /// + /// null if no RouteDescriptor for a destination exists + public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination) + { + return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination); + } + + /// + /// Returns the RouteDescriptor for a given destination AND removes it from collection. + /// Returns null if no route with the provided destination exists. + /// + public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination) + { + var descr = GetRouteDescriptorForDestination(destination); + if (descr != null) + RouteDescriptors.Remove(descr); + return descr; + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs new file mode 100644 index 00000000..f4836c6d --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs @@ -0,0 +1,44 @@ +namespace PepperDash.Essentials.Core +{ + public class RouteRequest + { + public IRoutingSink Destination {get; set;} + public IRoutingOutputs Source {get; set;} + public eRoutingSignalType SignalType {get; set;} + + public void HandleCooldown(object sender, FeedbackEventArgs args) + { + var coolingDevice = sender as IWarmingCooling; + + if(args.BoolValue == false) + { + Destination.ReleaseAndMakeRoute(Source, SignalType); + + if(sender == null) return; + + coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; + } + } + } + + /*public class RouteRequest + { + public IRoutingSink Destination { get; set; } + public IRoutingOutputs Source { get; set; } + public eRoutingSignalType SignalType { get; set; } + + public void HandleCooldown(object sender, FeedbackEventArgs args) + { + var coolingDevice = sender as IWarmingCooling; + + if (args.BoolValue == false) + { + Destination.ReleaseAndMakeRoute(Source, SignalType); + + if (sender == null) return; + + coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; + } + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs b/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs new file mode 100644 index 00000000..080b932e --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs @@ -0,0 +1,60 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Represents an individual link for a route + /// + public class RouteSwitchDescriptor + { + public IRoutingInputs SwitchingDevice { get { return InputPort.ParentDevice; } } + public RoutingOutputPort OutputPort { get; set; } + public RoutingInputPort InputPort { get; set; } + + public RouteSwitchDescriptor(RoutingInputPort inputPort) + { + InputPort = inputPort; + } + + public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort) + { + InputPort = inputPort; + OutputPort = outputPort; + } + + public override string ToString() + { + if (SwitchingDevice is IRouting) + return $"{SwitchingDevice?.Key} switches output {OutputPort.Key} to input {InputPort.Key}"; + else + return $"{SwitchingDevice.Key} switches to input {InputPort.Key}"; + } + } + + /*/// + /// Represents an individual link for a route + /// + public class RouteSwitchDescriptor + { + public IRoutingInputs SwitchingDevice { get { return InputPort.ParentDevice; } } + public RoutingOutputPort OutputPort { get; set; } + public RoutingInputPort InputPort { get; set; } + + public RouteSwitchDescriptor(RoutingInputPort inputPort) + { + InputPort = inputPort; + } + + public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort) + { + InputPort = inputPort; + OutputPort = outputPort; + } + + public override string ToString() + { + if (SwitchingDevice is IRouting) + return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector); + else + return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector); + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs b/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs new file mode 100644 index 00000000..28fefa96 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs @@ -0,0 +1,269 @@ +using PepperDash.Core; +using PepperDash.Essentials.Core.Config; +using System; +using System.Linq; + +namespace PepperDash.Essentials.Core.Routing +{ + public class RoutingFeedbackManager:EssentialsDevice + { + public RoutingFeedbackManager(string key, string name): base(key, name) + { + AddPreActivationAction(SubscribeForMidpointFeedback); + AddPreActivationAction(SubscribeForSinkFeedback); + } + + + private void SubscribeForMidpointFeedback() + { + var midpointDevices = DeviceManager.AllDevices.OfType(); + + foreach (var device in midpointDevices) + { + device.RouteChanged += HandleMidpointUpdate; + } + } + + private void SubscribeForSinkFeedback() + { + var sinkDevices = DeviceManager.AllDevices.OfType(); + + foreach (var device in sinkDevices) + { + device.InputChanged += HandleSinkUpdate; + } + } + + private void HandleMidpointUpdate(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute) + { + try + { + var devices = DeviceManager.AllDevices.OfType(); + + foreach (var device in devices) + { + UpdateDestination(device, device.CurrentInputPort); + } + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Error handling midpoint update from {midpointKey}:{Exception}", this, midpoint.Key, ex); + } + } + + private void HandleSinkUpdate(IRoutingSinkWithSwitching sender, RoutingInputPort currentInputPort) + { + try + { + UpdateDestination(sender, currentInputPort); + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Error handling Sink update from {senderKey}:{Exception}", this, sender.Key, ex); + } + } + + private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this,destination?.Key, inputPort?.Key); + + if(inputPort == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "Destination {destination} has not reported an input port yet", this,destination.Key); + return; + } + + TieLine firstTieLine; + try + { + var tieLines = TieLineCollection.Default; + + firstTieLine = tieLines.FirstOrDefault(tl => tl.DestinationPort.Key == inputPort.Key && tl.DestinationPort.ParentDevice.Key == inputPort.ParentDevice.Key); + + if (firstTieLine == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No tieline found for inputPort {inputPort}. Clearing current source", this, inputPort); + + var tempSourceListItem = new SourceListItem + { + SourceKey = "$transient", + Name = inputPort.Key, + }; + + + destination.CurrentSourceInfo = tempSourceListItem; ; + destination.CurrentSourceInfoKey = "$transient"; + return; + } + } catch (Exception ex) + { + Debug.LogMessage(ex, "Error getting first tieline: {Exception}", this, ex); + return; + } + + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Getting source for first TieLine {tieLine}", this, firstTieLine); + + TieLine sourceTieLine; + try + { + sourceTieLine = GetRootTieLine(firstTieLine); + + if (sourceTieLine == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route found to source for inputPort {inputPort}. Clearing current source", this, inputPort); + + var tempSourceListItem = new SourceListItem + { + SourceKey = "$transient", + Name = "None", + }; + + destination.CurrentSourceInfo = tempSourceListItem; + destination.CurrentSourceInfoKey = string.Empty; + return; + } + } catch(Exception ex) + { + Debug.LogMessage(ex, "Error getting sourceTieLine: {Exception}", this, ex); + return; + } + + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root TieLine {tieLine}", this, sourceTieLine); + + // Does not handle combinable scenarios or other scenarios where a display might be part of multiple rooms yet. + var room = DeviceManager.AllDevices.OfType().FirstOrDefault((r) => { + if(r is IHasMultipleDisplays roomMultipleDisplays) + { + return roomMultipleDisplays.Displays.Any(d => d.Value.Key == destination.Key); + } + + if(r is IHasDefaultDisplay roomDefaultDisplay) + { + return roomDefaultDisplay.DefaultDisplay.Key == destination.Key; + } + + return false; + }); + + if(room == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No room found for display {destination}", this, destination.Key); + return; + } + + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found room {room} for destination {destination}", this, room.Key, destination.Key); + + var sourceList = ConfigReader.ConfigObject.GetSourceListForKey(room.SourceListKey); + + if (sourceList == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No source list found for source list key {key}. Unable to find source for tieLine {sourceTieLine}", this, room.SourceListKey, sourceTieLine); + return; + } + + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found sourceList for room {room}", this, room.Key); + + var sourceListItem = sourceList.FirstOrDefault(sli => { + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, + "SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}", + this, + sli.Key, + sli.Value.SourceKey, + sourceTieLine.SourcePort.ParentDevice.Key); + + return sli.Value.SourceKey.Equals(sourceTieLine.SourcePort.ParentDevice.Key,StringComparison.InvariantCultureIgnoreCase); + }); + + var source = sourceListItem.Value; + + if (source == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No source found for device {key}. Creating transient source for {destination}", this, sourceTieLine.SourcePort.ParentDevice.Key, destination); + + var tempSourceListItem = new SourceListItem + { + SourceKey = "$transient", + Name = sourceTieLine.SourcePort.Key, + }; + + destination.CurrentSourceInfo = tempSourceListItem; ; + destination.CurrentSourceInfoKey = "$transient"; + return; + } + + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Got Source {source}", this, source); + + destination.CurrentSourceInfo = source; + destination.CurrentSourceInfoKey = source.SourceKey; + } + + private TieLine GetRootTieLine(TieLine tieLine) + { + TieLine nextTieLine = null; + try + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "**Following tieLine {tieLine}**", this, tieLine); + + if (tieLine.SourcePort.ParentDevice is IRoutingWithFeedback midpoint) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLine Source device {sourceDevice} is midpoint", this, midpoint); + + if(midpoint.CurrentRoutes == null || midpoint.CurrentRoutes.Count == 0) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Midpoint {midpointKey} has no routes",this, midpoint.Key); + return null; + } + + var currentRoute = midpoint.CurrentRoutes.FirstOrDefault(route => { + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Checking {route} against {tieLine}", this, route, tieLine); + + return route.OutputPort.Key == tieLine.SourcePort.Key && route.OutputPort.ParentDevice.Key == tieLine.SourcePort.ParentDevice.Key; + }); + + if (currentRoute == null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route through midpoint {midpoint} for outputPort {outputPort}", this, midpoint.Key, tieLine.SourcePort); + return null; + } + + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found currentRoute {currentRoute} through {midpoint}", this, currentRoute, midpoint); + + nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => { + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Checking {route} against {tieLine}", tl.DestinationPort.Key, currentRoute.InputPort.Key); + return tl.DestinationPort.Key == currentRoute.InputPort.Key && tl.DestinationPort.ParentDevice.Key == currentRoute.InputPort.ParentDevice.Key; }); + + if (nextTieLine != null) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found next tieLine {tieLine}. Walking the chain", this, nextTieLine); + return GetRootTieLine(nextTieLine); + } + + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root tieLine {tieLine}", this,nextTieLine); + return nextTieLine; + } + + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLIne Source Device {sourceDeviceKey} is IRoutingSource: {isIRoutingSource}", this, tieLine.SourcePort.ParentDevice.Key, tieLine.SourcePort.ParentDevice is IRoutingSource); + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLine Source Device interfaces: {typeFullName}:{interfaces}", this, tieLine.SourcePort.ParentDevice.GetType().FullName, tieLine.SourcePort.ParentDevice.GetType().GetInterfaces().Select(i => i.Name)); + + if (tieLine.SourcePort.ParentDevice is IRoutingSource || tieLine.SourcePort.ParentDevice is IRoutingOutputs) //end of the chain + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root: {tieLine}", this, tieLine); + return tieLine; + } + + nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => tl.DestinationPort.Key == tieLine.SourcePort.Key && tl.DestinationPort.ParentDevice.Key == tieLine.SourcePort.ParentDevice.Key ); + + if (nextTieLine != null) + { + return GetRootTieLine(nextTieLine); + } + } catch (Exception ex) + { + Debug.LogMessage(ex, "Error walking tieLines: {Exception}", this, ex); + return null; + } + + return null; + } + } +} diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs b/src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs new file mode 100644 index 00000000..9e3bb1d9 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs @@ -0,0 +1,82 @@ +using System; + + +namespace PepperDash.Essentials.Core +{ + /// + /// Basic RoutingInput with no statuses. + /// + public class RoutingInputPort : RoutingPort + { + /// + /// The IRoutingInputs object this lives on + /// + public IRoutingInputs ParentDevice { get; private set; } + + /// + /// Constructor for a basic RoutingInputPort + /// + /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. + /// May be string, number, whatever + /// The IRoutingInputs object this lives on + public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + object selector, IRoutingInputs parent) + : this (key, type, connType, selector, parent, false) + { + } + + /// + /// Constructor for a virtual routing input port that lives inside a device. For example + /// the ports that link a DM card to a DM matrix bus + /// + /// true for internal ports + public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + object selector, IRoutingInputs parent, bool isInternal) + : base(key, type, connType, selector, isInternal) + { + if (parent == null) + throw new ArgumentNullException(nameof(parent)); + ParentDevice = parent; + } + + public override string ToString() + { + return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}"; + } + } + + /*/// + /// Basic RoutingInput with no statuses. + /// + public class RoutingInputPort : RoutingPort + { + /// + /// The IRoutingInputs object this lives on + /// + public IRoutingInputs ParentDevice { get; private set; } + + /// + /// Constructor for a basic RoutingInputPort + /// + /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. + /// May be string, number, whatever + /// The IRoutingInputs object this lives on + public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + TSelector selector, IRoutingInputs parent) + : this(key, type, connType, selector, parent, false) + { + } + + /// + /// Constructor for a virtual routing input port that lives inside a device. For example + /// the ports that link a DM card to a DM matrix bus + /// + /// true for internal ports + public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + TSelector selector, IRoutingInputs parent, bool isInternal) + : base(key, type, connType, selector, isInternal) + { + ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent)); + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingInputPortWithVideoStatuses.cs b/src/PepperDash.Essentials.Core/Routing/RoutingInputPortWithVideoStatuses.cs new file mode 100644 index 00000000..bd4dd489 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RoutingInputPortWithVideoStatuses.cs @@ -0,0 +1,30 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// A RoutingInputPort for devices like DM-TX and DM input cards. + /// Will provide video statistics on connected signals + /// + public class RoutingInputPortWithVideoStatuses : RoutingInputPort + { + /// + /// Video statuses attached to this port + /// + public VideoStatusOutputs VideoStatus { get; private set; } + + /// + /// Constructor + /// + /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. + /// May be string, number, whatever + /// The IRoutingInputs object this lives on + /// A VideoStatusFuncsWrapper used to assign the callback funcs that will get + /// the values for the various stats + public RoutingInputPortWithVideoStatuses(string key, + eRoutingSignalType type, eRoutingPortConnectionType connType, object selector, + IRoutingInputs parent, VideoStatusFuncsWrapper funcs) : + base(key, type, connType, selector, parent) + { + VideoStatus = new VideoStatusOutputs(funcs); + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingInterfaces.cs b/src/PepperDash.Essentials.Core/Routing/RoutingInterfaces.cs deleted file mode 100644 index bfaa27db..00000000 --- a/src/PepperDash.Essentials.Core/Routing/RoutingInterfaces.cs +++ /dev/null @@ -1,203 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharpPro; -using Crestron.SimplSharpPro.DM; - -using PepperDash.Core; - - -namespace PepperDash.Essentials.Core -{ - - /// - /// The handler type for a Room's SourceInfoChange - /// - public delegate void SourceInfoChangeHandler(/*EssentialsRoomBase room,*/ SourceListItem info, ChangeType type); - - - //******************************************************************************************* - // Interfaces - - /// - /// For rooms with a single presentation source, change event - /// - public interface IHasCurrentSourceInfoChange - { - string CurrentSourceInfoKey { get; set; } - SourceListItem CurrentSourceInfo { get; set; } - event SourceInfoChangeHandler CurrentSourceChange; - } - - /// - /// Defines a class that has a collection of RoutingInputPorts - /// - public interface IRoutingInputs : IKeyed - { - RoutingPortCollection InputPorts { get; } - } - - /// - /// Defines a class that has a collection of RoutingOutputPorts - /// - - public interface IRoutingOutputs : IKeyed - { - RoutingPortCollection OutputPorts { get; } - } - - /// - /// For fixed-source endpoint devices - /// - public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange - { - - } - - /// - /// Endpoint device like a display, that selects inputs - /// - public interface IRoutingSinkWithSwitching : IRoutingSink - { - //void ClearRoute(); - void ExecuteSwitch(object inputSelector); - } - - /// - /// For devices like RMCs, baluns, other devices with no switching. - /// - public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs - { - } - - /// - /// Defines a midpoint device as have internal routing. Any devices in the middle of the - /// signal chain, that do switching, must implement this for routing to work otherwise - /// the routing algorithm will treat the IRoutingInputsOutputs device as a passthrough - /// device. - /// - public interface IRouting : IRoutingInputsOutputs - { - void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType); - } - - public interface IRoutingWithClear : IRouting - { - /// - /// Clears a route to an output, however a device needs to do that - /// - /// Output to clear - /// signal type to clear - void ClearRoute(object outputSelector, eRoutingSignalType signalType); - } - - public interface IRoutingNumeric : IRouting - { - void ExecuteNumericSwitch(ushort input, ushort output, eRoutingSignalType type); - } - - public interface ITxRouting : IRoutingNumeric - { - IntFeedback VideoSourceNumericFeedback { get; } - IntFeedback AudioSourceNumericFeedback { get; } - } - - /// - /// Defines a receiver that has internal routing (DM-RMC-4K-Z-SCALER-C) - /// - public interface IRmcRouting : IRoutingNumeric - { - IntFeedback AudioVideoSourceNumericFeedback { get; } - } - - - /// - /// Defines an IRmcRouting with a feedback event - /// - public interface ITxRoutingWithFeedback : ITxRouting - { - } - - /// - /// Defines an IRmcRouting with a feedback event - /// - public interface IRmcRoutingWithFeedback : IRmcRouting - { - } - - /// - /// Defines an IRoutingOutputs devices as being a source - the start of the chain - /// - public interface IRoutingSource : IRoutingOutputs - { - } - - /// - /// Defines an event structure for reporting output route data - /// - public interface IRoutingFeedback : IKeyName - { - event EventHandler NumericSwitchChange; - //void OnSwitchChange(RoutingNumericEventArgs e); - } - - /// - /// Defines an IRoutingNumeric with a feedback event - /// - public interface IRoutingNumericWithFeedback : IRoutingNumeric, IRoutingFeedback - { - } - - /// - /// Defines an IRouting with a feedback event - /// - public interface IRoutingWithFeedback : IRouting, IRoutingFeedback - { - - } - - public class RoutingNumericEventArgs : EventArgs - { - - public uint? Output { get; set; } - public uint? Input { get; set; } - - public eRoutingSignalType SigType { get; set; } - public RoutingInputPort InputPort { get; set; } - public RoutingOutputPort OutputPort { get; set; } - - public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType) - { - } - - public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort, - eRoutingSignalType sigType) - : this(null, null, outputPort, inputPort, sigType) - { - } - - public RoutingNumericEventArgs() - : this(null, null, null, null, 0) - { - - } - - public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort, - RoutingInputPort inputPort, eRoutingSignalType sigType) - { - OutputPort = outputPort; - InputPort = inputPort; - - Output = output; - Input = input; - SigType = sigType; - } - } - - public interface IRoutingHasVideoInputSyncFeedbacks - { - FeedbackCollection VideoInputSyncFeedbacks { get; } - } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingNumericEventArgs.cs b/src/PepperDash.Essentials.Core/Routing/RoutingNumericEventArgs.cs new file mode 100644 index 00000000..7ede13c2 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RoutingNumericEventArgs.cs @@ -0,0 +1,43 @@ +using System; + + +namespace PepperDash.Essentials.Core +{ + public class RoutingNumericEventArgs : EventArgs + { + + public uint? Output { get; set; } + public uint? Input { get; set; } + + public eRoutingSignalType SigType { get; set; } + public RoutingInputPort InputPort { get; set; } + public RoutingOutputPort OutputPort { get; set; } + + public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType) + { + } + + public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort, + eRoutingSignalType sigType) + : this(null, null, outputPort, inputPort, sigType) + { + } + + public RoutingNumericEventArgs() + : this(null, null, null, null, 0) + { + + } + + public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort, + RoutingInputPort inputPort, eRoutingSignalType sigType) + { + OutputPort = outputPort; + InputPort = inputPort; + + Output = output; + Input = input; + SigType = sigType; + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs b/src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs new file mode 100644 index 00000000..bebcb287 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs @@ -0,0 +1,75 @@ +using System; + + +namespace PepperDash.Essentials.Core +{ + public class RoutingOutputPort : RoutingPort + { + /// + /// The IRoutingOutputs object this port lives on + /// + public IRoutingOutputs ParentDevice { get; private set; } + + public InUseTracking InUseTracker { get; private set; } + + + /// + /// + /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. + /// May be string, number, whatever + /// The IRoutingOutputs object this port lives on + public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + object selector, IRoutingOutputs parent) + : this(key, type, connType, selector, parent, false) + { + } + + public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + object selector, IRoutingOutputs parent, bool isInternal) + : base(key, type, connType, selector, isInternal) + { + ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent)); + InUseTracker = new InUseTracking(); + } + + public override string ToString() + { + return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}"; + } + } + + /*public class RoutingOutputPort : RoutingPort + { + /// + /// The IRoutingOutputs object this port lives on + /// + public IRoutingOutputs ParentDevice { get; private set; } + + public InUseTracking InUseTracker { get; private set; } + + + /// + /// + /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. + /// May be string, number, whatever + /// The IRoutingOutputs object this port lives on + public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + TSelector selector, IRoutingOutputs parent) + : this(key, type, connType, selector, parent, false) + { + } + + public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + TSelector selector, IRoutingOutputs parent, bool isInternal) + : base(key, type, connType, selector, isInternal) + { + ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent)); + InUseTracker = new InUseTracking(); + } + + public override string ToString() + { + return ParentDevice.Key + ":" + Key; + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingPort.cs b/src/PepperDash.Essentials.Core/Routing/RoutingPort.cs index b6ef0001..04e804f1 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingPort.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingPort.cs @@ -1,15 +1,12 @@ -using System; -using System.Collections.Generic; - -using PepperDash.Core; +using PepperDash.Core; namespace PepperDash.Essentials.Core { - /// - /// Base class for RoutingInput and Output ports - /// - public abstract class RoutingPort : IKeyed + /// + /// Base class for RoutingInput and Output ports + /// + public abstract class RoutingPort : IKeyed { public string Key { get; private set; } public eRoutingSignalType Type { get; private set; } @@ -26,183 +23,26 @@ namespace PepperDash.Essentials.Core ConnectionType = connType; Selector = selector; IsInternal = isInternal; - } - } + } + } - [Flags] - public enum eRoutingSignalType - { - Audio = 1, - Video = 2, - AudioVideo = Audio | Video, - UsbOutput = 8, - UsbInput = 16, - SecondaryAudio = 32 - } + /*public abstract class RoutingPort:IKeyed + { + public string Key { get; private set; } + public eRoutingSignalType Type { get; private set; } + public eRoutingPortConnectionType ConnectionType { get; private set; } + public readonly TSelector Selector; + public bool IsInternal { get; private set; } + public object FeedbackMatchObject { get; set; } + public object Port { get; set; } - public enum eRoutingPortConnectionType - { - None, BackplaneOnly, DisplayPort, Dvi, Hdmi, Rgb, Vga, LineAudio, DigitalAudio, Sdi, - Composite, Component, DmCat, DmMmFiber, DmSmFiber, Speaker, Streaming, UsbC, HdBaseT - } - - /// - /// Basic RoutingInput with no statuses. - /// - public class RoutingInputPort : RoutingPort - { - /// - /// The IRoutingInputs object this lives on - /// - public IRoutingInputs ParentDevice { get; private set; } - - /// - /// Constructor for a basic RoutingInputPort - /// - /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. - /// May be string, number, whatever - /// The IRoutingInputs object this lives on - public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, - object selector, IRoutingInputs parent) - : this (key, type, connType, selector, parent, false) - { - } - - /// - /// Constructor for a virtual routing input port that lives inside a device. For example - /// the ports that link a DM card to a DM matrix bus - /// - /// true for internal ports - public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, - object selector, IRoutingInputs parent, bool isInternal) - : base(key, type, connType, selector, isInternal) - { - if (parent == null) - throw new ArgumentNullException("parent"); - ParentDevice = parent; - } - - - - - ///// - ///// Static method to get a named port from a named device - ///// - ///// Returns null if device or port doesn't exist - //public static RoutingInputPort GetDevicePort(string deviceKey, string portKey) - //{ - // var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as IRoutingInputs; - // if (sourceDev == null) - // return null; - // return sourceDev.InputPorts[portKey]; - //} - - ///// - ///// Static method to get a named port from a card in a named ICardPortsDevice device - ///// Uses ICardPortsDevice.GetChildInputPort - ///// - ///// 'input-N' - ///// null if device, card or port doesn't exist - //public static RoutingInputPort GetDeviceCardPort(string deviceKey, string cardKey, string portKey) - //{ - // var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as ICardPortsDevice; - // if (sourceDev == null) - // return null; - // return sourceDev.GetChildInputPort(cardKey, portKey); - //} - } - - /// - /// A RoutingInputPort for devices like DM-TX and DM input cards. - /// Will provide video statistics on connected signals - /// - public class RoutingInputPortWithVideoStatuses : RoutingInputPort - { - /// - /// Video statuses attached to this port - /// - public VideoStatusOutputs VideoStatus { get; private set; } - - /// - /// Constructor - /// - /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. - /// May be string, number, whatever - /// The IRoutingInputs object this lives on - /// A VideoStatusFuncsWrapper used to assign the callback funcs that will get - /// the values for the various stats - public RoutingInputPortWithVideoStatuses(string key, - eRoutingSignalType type, eRoutingPortConnectionType connType, object selector, - IRoutingInputs parent, VideoStatusFuncsWrapper funcs) : - base(key, type, connType, selector, parent) - { - VideoStatus = new VideoStatusOutputs(funcs); - } - } - - public class RoutingOutputPort : RoutingPort - { - /// - /// The IRoutingOutputs object this port lives on - /// - public IRoutingOutputs ParentDevice { get; private set; } - - public InUseTracking InUseTracker { get; private set; } - - - /// - /// - /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. - /// May be string, number, whatever - /// The IRoutingOutputs object this port lives on - public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, - object selector, IRoutingOutputs parent) - : this(key, type, connType, selector, parent, false) - { - } - - public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, - object selector, IRoutingOutputs parent, bool isInternal) - : base(key, type, connType, selector, isInternal) - { - if (parent == null) - throw new ArgumentNullException("parent"); - ParentDevice = parent; - InUseTracker = new InUseTracking(); - } - - public override string ToString() - { - return ParentDevice.Key + ":" + Key; - } - - ///// - ///// Static method to get a named port from a named device - ///// - ///// Returns null if device or port doesn't exist - //public static RoutingOutputPort GetDevicePort(string deviceKey, string portKey) - //{ - // var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as IRoutingOutputs; - // if (sourceDev == null) - // return null; - // var port = sourceDev.OutputPorts[portKey]; - // if (port == null) - // Debug.LogMessage(LogEventLevel.Information, "WARNING: Device '{0}' does does not contain output port '{1}'", deviceKey, portKey); - // return port; - //} - - ///// - ///// Static method to get a named port from a card in a named ICardPortsDevice device - ///// Uses ICardPortsDevice.GetChildOutputPort on that device - ///// - ///// 'input-N' or 'output-N' - ///// null if device, card or port doesn't exist - //public static RoutingOutputPort GetDeviceCardPort(string deviceKey, string cardKey, string portKey) - //{ - // var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as ICardPortsDevice; - // if (sourceDev == null) - // return null; - // var port = sourceDev.GetChildOutputPort(cardKey, portKey); - //} - } + public RoutingPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, TSelector selector, bool isInternal) + { + Key = key; + Type = type; + ConnectionType = connType; + Selector = selector; + IsInternal = isInternal; + } + }*/ } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingPortCollection.cs b/src/PepperDash.Essentials.Core/Routing/RoutingPortCollection.cs index ba972ab7..0b9d765c 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingPortCollection.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingPortCollection.cs @@ -23,4 +23,21 @@ namespace PepperDash.Essentials.Core } } } + +/* /// + /// Basically a List , with an indexer to find ports by key name + /// + public class RoutingPortCollection : List where T : RoutingPort + { + /// + /// Case-insensitive port lookup linked to ports' keys + /// + public T this[string key] + { + get + { + return this.FirstOrDefault(i => i.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); + } + } + }*/ } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingPortNames.cs b/src/PepperDash.Essentials.Core/Routing/RoutingPortNames.cs index 7029443b..1605c631 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingPortNames.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingPortNames.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core.Routing +namespace PepperDash.Essentials.Core { /// /// These should correspond directly with the portNames var in the config tool. diff --git a/src/PepperDash.Essentials.Core/Routing/TieLine.cs b/src/PepperDash.Essentials.Core/Routing/TieLine.cs index 25bf4ea3..245194a9 100644 --- a/src/PepperDash.Essentials.Core/Routing/TieLine.cs +++ b/src/PepperDash.Essentials.Core/Routing/TieLine.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Newtonsoft.Json; using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DM; @@ -88,24 +89,26 @@ namespace PepperDash.Essentials.Core public override string ToString() { - return string.Format("Tie line: [{0}]{1} --> [{2}]{3}", SourcePort.ParentDevice.Key, SourcePort.Key, + return string.Format("Tie line: {0}:{1} --> {2}:{3}", SourcePort.ParentDevice.Key, SourcePort.Key, DestinationPort.ParentDevice.Key, DestinationPort.Key); } } - //******************************************************************************** + //******************************************************************************** - public class TieLineCollection : List - { - public static TieLineCollection Default - { - get - { - if (_Default == null) - _Default = new TieLineCollection(); - return _Default; - } - } + public class TieLineCollection : List + { + public static TieLineCollection Default + { + get + { + if (_Default == null) + _Default = new TieLineCollection(); + return _Default; + } + } + + [JsonIgnore] static TieLineCollection _Default; } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/TieLineConfig.cs b/src/PepperDash.Essentials.Core/Routing/TieLineConfig.cs index 1728c20f..922cc61d 100644 --- a/src/PepperDash.Essentials.Core/Routing/TieLineConfig.cs +++ b/src/PepperDash.Essentials.Core/Routing/TieLineConfig.cs @@ -30,7 +30,7 @@ namespace PepperDash.Essentials.Core.Config /// null if config data does not match ports, cards or devices public TieLine GetTieLine() { - Debug.LogMessage(LogEventLevel.Information, "Build TieLine: {0}", this); + Debug.LogMessage(LogEventLevel.Information, "Build TieLine: {0}",null, this); // Get the source device var sourceDev = DeviceManager.GetDeviceForKey(SourceKey) as IRoutingOutputs; if (sourceDev == null) @@ -48,68 +48,29 @@ namespace PepperDash.Essentials.Core.Config } //Get the source port - RoutingOutputPort sourceOutputPort = null; - //// If it's a card-based device, get the card and then the source port - //if (sourceDev is ICardPortsDevice) - //{ - // if (SourceCard == null) - // { - // LogError("Card missing from source device config"); - // return null; - // } - // sourceOutputPort = (sourceDev as ICardPortsDevice).GetChildOutputPort(SourceCard, SourcePort); - // if (sourceOutputPort == null) - // { - // LogError("Source card does not contain port"); - // return null; - // } - //} - //// otherwise it's a normal port device, get the source port - //else - //{ - sourceOutputPort = sourceDev.OutputPorts[SourcePort]; - if (sourceOutputPort == null) - { - LogError("Source does not contain port"); - return null; - } - //} + var sourceOutputPort = sourceDev.OutputPorts[SourcePort]; + if (sourceOutputPort == null) + { + LogError("Source does not contain port"); + return null; + } - //Get the Destination port - RoutingInputPort destinationInputPort = null; - //// If it's a card-based device, get the card and then the Destination port - //if (destDev is ICardPortsDevice) - //{ - // if (DestinationCard == null) - // { - // LogError("Card missing from destination device config"); - // return null; - // } - // destinationInputPort = (destDev as ICardPortsDevice).GetChildInputPort(DestinationCard, DestinationPort); - // if (destinationInputPort == null) - // { - // LogError("Destination card does not contain port"); - // return null; - // } - //} - //// otherwise it's a normal port device, get the Destination port - //else - //{ - destinationInputPort = destDev.InputPorts[DestinationPort]; - if (destinationInputPort == null) + //Get the Destination port + var destinationInputPort = destDev.InputPorts[DestinationPort]; + + if (destinationInputPort == null) { LogError("Destination does not contain port"); return null; - } - //} + } return new TieLine(sourceOutputPort, destinationInputPort); } void LogError(string msg) { - Debug.LogMessage(LogEventLevel.Debug, "WARNING: Cannot create tie line: {0}:\r {1}", msg, this); + Debug.LogMessage(LogEventLevel.Error, "WARNING: Cannot create tie line: {message}:\r {tieLineConfig}",null, msg, this); } public override string ToString() diff --git a/src/PepperDash.Essentials.Core/Routing/eRoutingPortConnectionType.cs b/src/PepperDash.Essentials.Core/Routing/eRoutingPortConnectionType.cs new file mode 100644 index 00000000..436ef9e6 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/eRoutingPortConnectionType.cs @@ -0,0 +1,8 @@ +namespace PepperDash.Essentials.Core +{ + public enum eRoutingPortConnectionType + { + None, BackplaneOnly, DisplayPort, Dvi, Hdmi, Rgb, Vga, LineAudio, DigitalAudio, Sdi, + Composite, Component, DmCat, DmMmFiber, DmSmFiber, Speaker, Streaming, UsbC, HdBaseT + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/eRoutingSignalType.cs b/src/PepperDash.Essentials.Core/Routing/eRoutingSignalType.cs new file mode 100644 index 00000000..b5f3d9f7 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/eRoutingSignalType.cs @@ -0,0 +1,16 @@ +using System; + + +namespace PepperDash.Essentials.Core +{ + [Flags] + public enum eRoutingSignalType + { + Audio = 1, + Video = 2, + AudioVideo = Audio | Video, + UsbOutput = 8, + UsbInput = 16, + SecondaryAudio = 32 + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Touchpanels/CrestronTouchpanelPropertiesConfig.cs b/src/PepperDash.Essentials.Core/Touchpanels/CrestronTouchpanelPropertiesConfig.cs index 73b66bce..aa4dac6d 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/CrestronTouchpanelPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/CrestronTouchpanelPropertiesConfig.cs @@ -1,38 +1,74 @@ -namespace PepperDash.Essentials.Core +using Newtonsoft.Json; + +namespace PepperDash.Essentials.Core { public class CrestronTouchpanelPropertiesConfig { + [JsonProperty("control")] + public EssentialsControlPropertiesConfig ControlProperties { get; set; } + + [JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)] public string IpId { get; set; } + + [JsonProperty("defaultRoomKey", NullValueHandling = NullValueHandling.Ignore)] public string DefaultRoomKey { get; set; } + + [JsonProperty("roomListKey", NullValueHandling = NullValueHandling.Ignore)] public string RoomListKey { get; set; } + + [JsonProperty("sgdFile", NullValueHandling = NullValueHandling.Ignore)] public string SgdFile { get; set; } + + [JsonProperty("projectName", NullValueHandling = NullValueHandling.Ignore)] public string ProjectName { get; set; } - public bool ShowVolumeGauge { get; set; } - public bool UsesSplashPage { get; set; } - public bool ShowDate { get; set; } - public bool ShowTime { get; set; } + + [JsonProperty("showVolumeGauge", NullValueHandling = NullValueHandling.Ignore)] + public bool? ShowVolumeGauge { get; set; } + + [JsonProperty("usesSplashPage", NullValueHandling = NullValueHandling.Ignore)] + public bool? UsesSplashPage { get; set; } + + [JsonProperty("showDate", NullValueHandling = NullValueHandling.Ignore)] + public bool? ShowDate { get; set; } + + [JsonProperty("showTime", NullValueHandling = NullValueHandling.Ignore)] + public bool? ShowTime { get; set; } + + [JsonProperty("setup", NullValueHandling = NullValueHandling.Ignore)] public UiSetupPropertiesConfig Setup { get; set; } + + [JsonProperty("headerStyle", NullValueHandling = NullValueHandling.Ignore)] public string HeaderStyle { get; set; } - public bool IncludeInFusionRoomHealth { get; set; } - public uint ScreenSaverTimeoutMin { get; set; } - public uint ScreenSaverMovePositionIntervalMs { get; set; } + + [JsonProperty("includeInFusionRoomHealth", NullValueHandling = NullValueHandling.Ignore)] + public bool? IncludeInFusionRoomHealth { get; set; } + + [JsonProperty("screenSaverTimeoutMin", NullValueHandling = NullValueHandling.Ignore)] + public uint? ScreenSaverTimeoutMin { get; set; } + + [JsonProperty("screenSaverMovePositionIntervalMs", NullValueHandling = NullValueHandling.Ignore)] + public uint? ScreenSaverMovePositionIntervalMs { get; set; } /// /// The count of sources that will trigger the "additional" arrows to show on the SRL. /// Defaults to 5 /// - public int SourcesOverflowCount { get; set; } + [JsonProperty("sourcesOverflowCount", NullValueHandling = NullValueHandling.Ignore)] + public int? SourcesOverflowCount { get; set; } - public CrestronTouchpanelPropertiesConfig() + public CrestronTouchpanelPropertiesConfig() : this(false) { } + + public CrestronTouchpanelPropertiesConfig(bool setDefaultValues = false) { + if(!setDefaultValues) { return; } SourcesOverflowCount = 5; - HeaderStyle = CrestronTouchpanelPropertiesConfig.Habanero; + HeaderStyle = Habanero; // Default values ScreenSaverTimeoutMin = 5; ScreenSaverMovePositionIntervalMs = 15000; - } + } /// /// "habanero" @@ -49,6 +85,7 @@ /// public class UiSetupPropertiesConfig { + [JsonProperty("isVisible", NullValueHandling = NullValueHandling.Ignore)] public bool IsVisible { get; set; } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs b/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs index eb5fee37..a37a78c0 100644 --- a/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs +++ b/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs @@ -89,7 +89,7 @@ namespace PepperDash.Essentials.Core.Web Name = "DevList", RouteHandler = new DevListRequestHandler() }, - new HttpCwsRoute("deviceCommands") + new HttpCwsRoute("deviceCommands/{deviceKey}") { Name = "DevJson", RouteHandler = new DevJsonRequestHandler() @@ -163,6 +163,11 @@ namespace PepperDash.Essentials.Core.Web { Name = "Load Config", RouteHandler = new LoadConfigRequestHandler() + }, + new HttpCwsRoute("getTielines") + { + Name = "Get TieLines", + RouteHandler = new GetTieLinesRequestHandler() } }; @@ -233,32 +238,32 @@ namespace PepperDash.Essentials.Core.Web /// public void GetPaths() { - Debug.LogMessage(LogEventLevel.Verbose, this, "{0}", new String('-', 50)); + Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50)); var currentIp = CrestronEthernetHelper.GetEthernetParameter( CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); var hostname = CrestronEthernetHelper.GetEthernetParameter( CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); + + var path = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server + ? $"http(s)://{hostname}/VirtualControl/Rooms/{InitialParametersClass.RoomId}/cws{BasePath}" + : $"http(s)://{currentIp}/cws{BasePath}"; - var path = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server - ? string.Format("http(s)://{0}/VirtualControl/Rooms/{1}/cws{2}", hostname, InitialParametersClass.RoomId, BasePath) - : string.Format("http(s)://{0}/cws{1}", currentIp, BasePath); - - Debug.LogMessage(LogEventLevel.Verbose, this, "Server:{0}", path); + Debug.LogMessage(LogEventLevel.Information, this, "Server:{path:l}", path); var routeCollection = _server.GetRouteCollection(); if (routeCollection == null) { - Debug.LogMessage(LogEventLevel.Verbose, this, "Server route collection is null"); + Debug.LogMessage(LogEventLevel.Information, this, "Server route collection is null"); return; } - Debug.LogMessage(LogEventLevel.Verbose, this, "Configured Routes:"); + Debug.LogMessage(LogEventLevel.Information, this, "Configured Routes:"); foreach (var route in routeCollection) { - Debug.LogMessage(LogEventLevel.Verbose, this, "{0}: {1}/{2}", route.Name, path, route.Url); + Debug.LogMessage(LogEventLevel.Information, this, "{routeName:l}: {routePath:l}/{routeUrl:l}", route.Name, path, route.Url); } - Debug.LogMessage(LogEventLevel.Verbose, this, "{0}", new String('-', 50)); + Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50)); } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/EssentialsWebApiHelpers.cs b/src/PepperDash.Essentials.Core/Web/EssentialsWebApiHelpers.cs index 3a3dd756..d5f5af36 100644 --- a/src/PepperDash.Essentials.Core/Web/EssentialsWebApiHelpers.cs +++ b/src/PepperDash.Essentials.Core/Web/EssentialsWebApiHelpers.cs @@ -7,11 +7,11 @@ using PepperDash.Core; namespace PepperDash.Essentials.Core.Web { - public class EssentialsWebApiHelpers + public static class EssentialsWebApiHelpers { - public static string GetRequestBody(HttpCwsRequest request) + public static string GetRequestBody(this HttpCwsRequest request) { - var bytes = new Byte[request.ContentLength]; + var bytes = new byte[request.ContentLength]; request.InputStream.Read(bytes, 0, request.ContentLength); @@ -22,8 +22,8 @@ namespace PepperDash.Essentials.Core.Web { return new { - Name = assembly.Name, - Version = assembly.Version + assembly.Name, + assembly.Version }; } @@ -31,7 +31,7 @@ namespace PepperDash.Essentials.Core.Web { return new { - Key = device.Key, + device.Key, Name = (device is IKeyName) ? (device as IKeyName).Name : "---" diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/AppDebugRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/AppDebugRequestHandler.cs index 1157c5e1..0b7497d2 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/AppDebugRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/AppDebugRequestHandler.cs @@ -52,7 +52,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers return; } - var data = EssentialsWebApiHelpers.GetRequestBody(context.Request); + var data = context.Request.GetRequestBody(); if (string.IsNullOrEmpty(data)) { context.Response.StatusCode = 400; diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevJsonRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevJsonRequestHandler.cs index a78b88ce..d14ffb83 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevJsonRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevJsonRequestHandler.cs @@ -1,5 +1,6 @@ using System; using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; using Serilog.Events; @@ -25,28 +26,55 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers /// protected override void HandlePost(HttpCwsContext context) { + var routeData = context.Request.RouteData; + + if(routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + if(!routeData.Values.TryGetValue("deviceKey", out var deviceKey)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + if (context.Request.ContentLength < 0) { context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + context.Response.StatusDescription = "Bad Request: no body"; context.Response.End(); return; } - var data = EssentialsWebApiHelpers.GetRequestBody(context.Request); + var data = context.Request.GetRequestBody(); + if (string.IsNullOrEmpty(data)) { context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + context.Response.StatusDescription = "Bad Request: no body"; context.Response.End(); return; } try - { - DeviceJsonApi.DoDeviceActionWithJson(data); + { + var daw = new DeviceActionWrapper { DeviceKey = (string) deviceKey}; + + JsonConvert.PopulateObject(data, daw); + + Debug.LogMessage(LogEventLevel.Verbose, "Device Action Wrapper: {@wrapper}", null, daw); + + DeviceJsonApi.DoDeviceAction(daw); context.Response.StatusCode = 200; context.Response.StatusDescription = "OK"; @@ -54,12 +82,11 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers } catch (Exception ex) { - Debug.LogMessage(LogEventLevel.Error, "Exception Message: {0}", ex.Message); - Debug.LogMessage(LogEventLevel.Verbose, "Exception Stack Trace: {0}", ex.StackTrace); - if(ex.InnerException != null) Debug.LogMessage(LogEventLevel.Error, "Exception Inner: {0}", ex.InnerException); + Debug.LogMessage(ex, "Error handling device command: {Exception}"); context.Response.StatusCode = 400; context.Response.StatusDescription = "Bad Request"; + context.Response.Write(JsonConvert.SerializeObject(new { error = ex.Message }), false); context.Response.End(); } } diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevListRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevListRequestHandler.cs index e9df4347..a83685fb 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevListRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevListRequestHandler.cs @@ -34,7 +34,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers return; } - allDevices.Sort((a, b) => System.String.Compare(a.Key, b.Key, System.StringComparison.Ordinal)); + allDevices.Sort((a, b) => string.Compare(a.Key, b.Key, System.StringComparison.Ordinal)); var deviceList = allDevices.Select(d => EssentialsWebApiHelpers.MapToDeviceListObject(d)).ToList(); diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevMethodsRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevMethodsRequestHandler.cs index c08544cb..263eb161 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevMethodsRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevMethodsRequestHandler.cs @@ -1,6 +1,7 @@ using System.Text; using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; +using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; namespace PepperDash.Essentials.Core.Web.RequestHandlers @@ -25,6 +26,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers protected override void HandleGet(HttpCwsContext context) { var routeData = context.Request.RouteData; + Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, "Getting DevMethods: {@routeData}", routeData); if (routeData == null) { context.Response.StatusCode = 400; diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DoNotLoadConfigOnNextBootRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DoNotLoadConfigOnNextBootRequestHandler.cs index 4ec3bc97..fdcad73c 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DoNotLoadConfigOnNextBootRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DoNotLoadConfigOnNextBootRequestHandler.cs @@ -52,7 +52,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers return; } - var data = EssentialsWebApiHelpers.GetRequestBody(context.Request); + var data = context.Request.GetRequestBody(); if (string.IsNullOrEmpty(data)) { context.Response.StatusCode = 400; diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTieLinesRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTieLinesRequestHandler.cs new file mode 100644 index 00000000..d5de5ad0 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTieLinesRequestHandler.cs @@ -0,0 +1,34 @@ +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core.Web.RequestHandlers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class GetTieLinesRequestHandler:WebApiBaseRequestHandler + { + public GetTieLinesRequestHandler() : base(true) { } + + protected override void HandleGet(HttpCwsContext context) + { + var tieLineString = JsonConvert.SerializeObject(TieLineCollection.Default.Select((tl) => new { + sourceKey = tl.SourcePort.ParentDevice.Key, + sourcePort = tl.SourcePort.Key, + destinationKey = tl.DestinationPort.ParentDevice.Key, + destinationPort = tl.DestinationPort.Key + })); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.Write(tieLineString, false); + context.Response.End(); + + } + } +} diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs index a08fda2b..6378f1b2 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs @@ -89,7 +89,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers return; } - var data = EssentialsWebApiHelpers.GetRequestBody(context.Request); + var data = context.Request.GetRequestBody(); if (data == null) { context.Response.StatusCode = 500; diff --git a/src/PepperDash.Essentials.Devices.Common/Audio/GenericAudioOut.cs b/src/PepperDash.Essentials.Devices.Common/Audio/GenericAudioOut.cs index e9eeb5b5..150b7818 100644 --- a/src/PepperDash.Essentials.Devices.Common/Audio/GenericAudioOut.cs +++ b/src/PepperDash.Essentials.Devices.Common/Audio/GenericAudioOut.cs @@ -8,7 +8,6 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common @@ -18,6 +17,8 @@ namespace PepperDash.Essentials.Devices.Common /// public class GenericAudioOut : EssentialsDevice, IRoutingSink { + public RoutingInputPort CurrentInputPort => AnyAudioIn; + public event SourceInfoChangeHandler CurrentSourceChange; public string CurrentSourceInfoKey { get; set; } diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/BasicIrDisplay.cs b/src/PepperDash.Essentials.Devices.Common/Displays/BasicIrDisplay.cs index 654b2fcf..d7e19d00 100644 --- a/src/PepperDash.Essentials.Devices.Common/Displays/BasicIrDisplay.cs +++ b/src/PepperDash.Essentials.Devices.Common/Displays/BasicIrDisplay.cs @@ -7,7 +7,6 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common.Displays diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/DisplayBase.cs b/src/PepperDash.Essentials.Devices.Common/Displays/DisplayBase.cs index 1d2d3589..9796599b 100644 --- a/src/PepperDash.Essentials.Devices.Common/Displays/DisplayBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/Displays/DisplayBase.cs @@ -1,15 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Crestron.SimplSharp; +using Crestron.SimplSharp; using Crestron.SimplSharpPro.DeviceSupport; +using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; -using Feedback = PepperDash.Essentials.Core.Feedback; -using Newtonsoft.Json; -using PepperDash.Essentials.Core.DeviceTypeInterfaces; using Serilog.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using Feedback = PepperDash.Essentials.Core.Feedback; namespace PepperDash.Essentials.Devices.Common.Displays { @@ -20,6 +19,26 @@ namespace PepperDash.Essentials.Devices.Common.Displays , IWarmingCooling , IUsageTracking { + private RoutingInputPort _currentInputPort; + public RoutingInputPort CurrentInputPort + { + get + { + return _currentInputPort; + } + + protected set + { + if (_currentInputPort == value) return; + + _currentInputPort = value; + + InputChanged?.Invoke(this, _currentInputPort); + } + } + + public event InputChangedEventHandler InputChanged; + public event SourceInfoChangeHandler CurrentSourceChange; public string CurrentSourceInfoKey { get; set; } diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/MockDisplay.cs b/src/PepperDash.Essentials.Devices.Common/Displays/MockDisplay.cs index ff4c451b..0a2a11b5 100644 --- a/src/PepperDash.Essentials.Devices.Common/Displays/MockDisplay.cs +++ b/src/PepperDash.Essentials.Devices.Common/Displays/MockDisplay.cs @@ -8,12 +8,11 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.DeviceTypeInterfaces; -using PepperDash.Essentials.Core.Routing; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common.Displays { - public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeAdvanced, IHasInputs + public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeAdvanced, IHasInputs, IRoutingSinkWithSwitchingWithInputPort, IHasPowerControlWithFeedback { public ISelectableItems Inputs { get; private set; } @@ -80,7 +79,7 @@ namespace PepperDash.Essentials.Devices.Common.Displays eRoutingPortConnectionType.Hdmi, "HDMI2", this); var hdmiIn3 = new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, "HDMI3", this); - var hdmiIn4 = new RoutingInputPort(RoutingPortNames.ComponentIn, eRoutingSignalType.AudioVideo, + var hdmiIn4 = new RoutingInputPort(RoutingPortNames.HdmiIn4, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, "HDMI4", this); var dpIn = new RoutingInputPort(RoutingPortNames.DisplayPortIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.DisplayPort, "DP", this); @@ -140,18 +139,65 @@ namespace PepperDash.Essentials.Devices.Common.Displays public override void ExecuteSwitch(object selector) { - Debug.LogMessage(LogEventLevel.Verbose, this, "ExecuteSwitch: {0}", selector); + try + { + Debug.LogMessage(LogEventLevel.Verbose, "ExecuteSwitch: {0}", this, selector); if (!_PowerIsOn) { PowerOn(); } - if (!Inputs.Items.TryGetValue(selector.ToString(), out var input)) - return; + if (!Inputs.Items.TryGetValue(selector.ToString(), out var input)) + return; - input.Select(); - } + Debug.LogMessage(LogEventLevel.Verbose, "Selected input: {input}", this, input.Key); + input.Select(); + + var inputPort = InputPorts.FirstOrDefault(port => + { + Debug.LogMessage(LogEventLevel.Verbose, "Checking input port {inputPort} with selector {portSelector} against {selector}", this, port, port.Selector, selector); + return port.Selector.ToString() == selector.ToString(); + }); + + if (inputPort == null) + { + Debug.LogMessage(LogEventLevel.Verbose, "Unable to find input port for selector {selector}", this, selector); + return; + } + + Debug.LogMessage(LogEventLevel.Verbose, "Setting current input port to {inputPort}", this, inputPort); + CurrentInputPort = inputPort; + } catch (Exception ex) + { + Debug.LogMessage(ex, "Error making switch: {Exception}", this, ex); + } + } + + public void SetInput(string selector) + { + ISelectableItem currentInput = null; + + try + { + currentInput = Inputs.Items.SingleOrDefault(Inputs => Inputs.Value.IsSelected).Value; + } + catch { } + + + if (currentInput != null) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "SetInput: {0}", selector); + currentInput.IsSelected = false; + } + + if (!Inputs.Items.TryGetValue(selector, out var input)) + return; + + input.IsSelected = true; + + Inputs.CurrentItem = selector; + } #region IBasicVolumeWithFeedback Members @@ -230,7 +276,7 @@ namespace PepperDash.Essentials.Devices.Common.Displays { public MockDisplayFactory() { - TypeNames = new List() { "mockdisplay" }; + TypeNames = new List() { "mockdisplay, mockdisplay2" }; } public override EssentialsDevice BuildDevice(DeviceConfig dc) diff --git a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs index 940a519e..13878321 100644 --- a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs +++ b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs @@ -1,7 +1,6 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; using Serilog.Events; using System; using System.Collections.Generic; @@ -44,6 +43,8 @@ namespace PepperDash.Essentials.Devices.Common.Generic } } + public RoutingInputPort CurrentInputPort => InputPorts[0]; + public event SourceInfoChangeHandler CurrentSourceChange; } diff --git a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSource.cs b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSource.cs index f4062c98..80823156 100644 --- a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSource.cs +++ b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSource.cs @@ -9,12 +9,11 @@ using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common { - public class GenericSource : EssentialsDevice, IUiDisplayInfo, IRoutingOutputs, IUsageTracking + public class GenericSource : EssentialsDevice, IUiDisplayInfo, IRoutingSource, IRoutingOutputs, IUsageTracking { public uint DisplayUiType { get { return DisplayUiConstants.TypeNoControls; } } diff --git a/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj b/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj index ac5d9790..69f6d24e 100644 --- a/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj +++ b/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj @@ -29,7 +29,7 @@ - - + + \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/SetTopBox/IRSetTopBoxBase.cs b/src/PepperDash.Essentials.Devices.Common/SetTopBox/IRSetTopBoxBase.cs index 1728939d..32885aec 100644 --- a/src/PepperDash.Essentials.Devices.Common/SetTopBox/IRSetTopBoxBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/SetTopBox/IRSetTopBoxBase.cs @@ -13,14 +13,13 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Presets; -using PepperDash.Essentials.Core.Routing; using PepperDash.Essentials.Core.DeviceTypeInterfaces; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common { [Description("Wrapper class for an IR Set Top Box")] - public class IRSetTopBoxBase : EssentialsBridgeableDevice, ISetTopBoxControls, IRoutingOutputs, IUsageTracking, IHasPowerControl, ITvPresetsProvider + public class IRSetTopBoxBase : EssentialsBridgeableDevice, ISetTopBoxControls, IRoutingSource, IRoutingOutputs, IUsageTracking, IHasPowerControl, ITvPresetsProvider { public IrOutputPortController IrPort { get; private set; } diff --git a/src/PepperDash.Essentials.Devices.Common/SoftCodec/BlueJeansPc.cs b/src/PepperDash.Essentials.Devices.Common/SoftCodec/BlueJeansPc.cs index 4dcb663a..891f92b1 100644 --- a/src/PepperDash.Essentials.Devices.Common/SoftCodec/BlueJeansPc.cs +++ b/src/PepperDash.Essentials.Devices.Common/SoftCodec/BlueJeansPc.cs @@ -5,7 +5,6 @@ using Crestron.SimplSharp; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; using PepperDash.Essentials.Devices.Common.Sources; using Serilog.Events; @@ -17,6 +16,8 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec public RoutingInputPort AnyVideoIn { get; private set; } + public RoutingInputPort CurrentInputPort => AnyVideoIn; + #region IRoutingInputs Members public RoutingPortCollection InputPorts { get; private set; } @@ -26,8 +27,10 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec public BlueJeansPc(string key, string name) : base(key, name) { - InputPorts = new RoutingPortCollection(); - InputPorts.Add(AnyVideoIn = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.None, 0, this)); + InputPorts = new RoutingPortCollection + { + (AnyVideoIn = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.None, 0, this)) + }; } #region IRunRouteAction Members diff --git a/src/PepperDash.Essentials.Devices.Common/SoftCodec/GenericSoftCodec.cs b/src/PepperDash.Essentials.Devices.Common/SoftCodec/GenericSoftCodec.cs index a4155692..f623c539 100644 --- a/src/PepperDash.Essentials.Devices.Common/SoftCodec/GenericSoftCodec.cs +++ b/src/PepperDash.Essentials.Devices.Common/SoftCodec/GenericSoftCodec.cs @@ -3,16 +3,24 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; using Serilog.Events; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace PepperDash.Essentials.Devices.Common.SoftCodec { - public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSink + public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingOutputs, IRoutingSinkWithSwitching { + private RoutingInputPort _currentInputPort; + public RoutingInputPort CurrentInputPort { + get => _currentInputPort; + set + { + _currentInputPort = value; + + InputChanged?.Invoke(this, _currentInputPort); + } + } + public GenericSoftCodec(string key, string name, GenericSoftCodecProperties props) : base(key, name) { InputPorts = new RoutingPortCollection(); @@ -27,7 +35,7 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec for(var i = 1; i<= props.ContentInputCount; i++) { - var inputPort = new RoutingInputPort($"{Key}-contentInput{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this); + var inputPort = new RoutingInputPort($"{Key}-contentInput{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, $"contentInput{i}", this); InputPorts.Add(inputPort); } @@ -39,7 +47,7 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec for(var i = 1; i <=props.CameraInputCount; i++) { - var cameraPort = new RoutingInputPort($"{Key}-cameraInput{i}", eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, null, this); + var cameraPort = new RoutingInputPort($"{Key}-cameraInput{i}", eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, $"cameraInput{i}", this); InputPorts.Add(cameraPort); } @@ -74,6 +82,20 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec SourceListItem _CurrentSourceInfo; public event SourceInfoChangeHandler CurrentSourceChange; + public event InputChangedEventHandler InputChanged; + + public void ExecuteSwitch(object inputSelector) + { + var inputPort = InputPorts.FirstOrDefault(p => p.Selector == inputSelector); + + if(inputPort == null) + { + Debug.LogMessage(LogEventLevel.Warning, "No input port found for selector {inputSelector}", inputSelector); + return; + } + + CurrentInputPort = inputPort; + } } public class GenericSoftCodecProperties diff --git a/src/PepperDash.Essentials.Devices.Common/Sources/InRoomPc.cs b/src/PepperDash.Essentials.Devices.Common/Sources/InRoomPc.cs index 565956f4..41ad7a43 100644 --- a/src/PepperDash.Essentials.Devices.Common/Sources/InRoomPc.cs +++ b/src/PepperDash.Essentials.Devices.Common/Sources/InRoomPc.cs @@ -3,12 +3,11 @@ using System.Collections.Generic; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common.Sources { - public class InRoomPc : EssentialsDevice, IHasFeedback, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking + public class InRoomPc : EssentialsDevice, IHasFeedback, IRoutingSource, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking { public uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } } public string IconName { get; set; } diff --git a/src/PepperDash.Essentials.Devices.Common/Sources/Laptop.cs b/src/PepperDash.Essentials.Devices.Common/Sources/Laptop.cs index 7aac6267..38898209 100644 --- a/src/PepperDash.Essentials.Devices.Common/Sources/Laptop.cs +++ b/src/PepperDash.Essentials.Devices.Common/Sources/Laptop.cs @@ -3,12 +3,11 @@ using System.Collections.Generic; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common.Sources { - public class Laptop : EssentialsDevice, IHasFeedback, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking + public class Laptop : EssentialsDevice, IHasFeedback, IRoutingSource, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking { public uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } } public string IconName { get; set; } @@ -29,11 +28,15 @@ namespace PepperDash.Essentials.Devices.Common.Sources : base(key, name) { IconName = "Laptop"; + HasPowerOnFeedback = new BoolFeedback("HasPowerFeedback", () => this.GetVideoStatuses() != VideoStatusOutputs.NoStatus); - OutputPorts = new RoutingPortCollection(); - OutputPorts.Add(AnyVideoOut = new RoutingOutputPort(RoutingPortNames.AnyOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.None, 0, this)); + + OutputPorts = new RoutingPortCollection + { + (AnyVideoOut = new RoutingOutputPort(RoutingPortNames.AnyOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.None, 0, this)) + }; } #region IHasFeedback Members diff --git a/src/PepperDash.Essentials.Devices.Common/Streaming/AppleTV.cs b/src/PepperDash.Essentials.Devices.Common/Streaming/AppleTV.cs index 643db30d..7a0b57f1 100644 --- a/src/PepperDash.Essentials.Devices.Common/Streaming/AppleTV.cs +++ b/src/PepperDash.Essentials.Devices.Common/Streaming/AppleTV.cs @@ -13,14 +13,14 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Bridges; -using PepperDash.Essentials.Core.Routing; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common { [Description("Wrapper class for an IR-Controlled AppleTV")] - public class AppleTV : EssentialsBridgeableDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingOutputs - { + public class AppleTV : EssentialsBridgeableDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingSource, IRoutingOutputs + + { public IrOutputPortController IrPort { get; private set; } public const string StandardDriverName = "Apple_AppleTV_4th_Gen_Essentials.ir"; public uint DisplayUiType { get { return DisplayUiConstants.TypeAppleTv; } } diff --git a/src/PepperDash.Essentials.Devices.Common/Streaming/Roku.cs b/src/PepperDash.Essentials.Devices.Common/Streaming/Roku.cs index 4364610b..213e5835 100644 --- a/src/PepperDash.Essentials.Devices.Common/Streaming/Roku.cs +++ b/src/PepperDash.Essentials.Devices.Common/Streaming/Roku.cs @@ -9,13 +9,12 @@ using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common { [Description("Wrapper class for an IR-Controlled Roku")] - public class Roku2 : EssentialsDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingOutputs + public class Roku2 : EssentialsDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingSource, IRoutingOutputs { [Api] public IrOutputPortController IrPort { get; private set; } diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs index f2a98a8d..374efedf 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs @@ -14,13 +14,13 @@ using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Devices; using PepperDash.Essentials.Core.DeviceTypeInterfaces; -using PepperDash.Essentials.Core.Routing; using PepperDash.Essentials.Devices.Common.Cameras; using PepperDash.Essentials.Devices.Common.Codec; using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; using PepperDash.Essentials.Core.Bridges.JoinMaps; using Feedback = PepperDash.Essentials.Core.Feedback; using Serilog.Events; +using PepperDash.Essentials.Core.Routing; namespace PepperDash.Essentials.Devices.Common.VideoCodec { diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index f084e6a5..ddc60c09 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -14,6 +14,7 @@ using PepperDash.Essentials.Core.Web; using System; using System.Linq; using Serilog.Events; +using PepperDash.Essentials.Core.Routing; namespace PepperDash.Essentials { @@ -34,6 +35,8 @@ namespace PepperDash.Essentials SecretsManager.Initialize(); SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; + Debug.SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? LogEventLevel.Warning : LogEventLevel.Verbose); + // AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; } @@ -89,6 +92,8 @@ namespace PepperDash.Essentials private void StartSystem(object preventInitialization) { + Debug.SetErrorLogMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose); + DeterminePlatform(); if (Debug.DoNotLoadConfigOnNextBoot) @@ -162,7 +167,7 @@ namespace PepperDash.Essentials { try { - Debug.LogMessage(LogEventLevel.Information, "Determining Platform..."); + Debug.LogMessage(LogEventLevel.Information, "Determining Platform..."); string filePathPrefix; @@ -236,7 +241,7 @@ namespace PepperDash.Essentials } catch (Exception e) { - Debug.LogMessage(LogEventLevel.Error, "Unable to determin platform due to exception: {exception}", e.Message); + Debug.LogMessage(e, "Unable to determine platform due to exception"); } } @@ -292,7 +297,7 @@ namespace PepperDash.Essentials } catch (Exception e) { - Debug.LogMessage(LogEventLevel.Information, "FATAL INITIALIZE ERROR. System is in an inconsistent state: {exception}", e); + Debug.LogMessage(e, "FATAL INITIALIZE ERROR. System is in an inconsistent state"); } finally { @@ -382,6 +387,8 @@ namespace PepperDash.Essentials // Build the processor wrapper class DeviceManager.AddDevice(new Core.Devices.CrestronProcessor("processor")); + DeviceManager.AddDevice(new RoutingFeedbackManager($"routingFeedbackManager", "Routing Feedback Manager")); + // Add global System Monitor device if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) { @@ -424,7 +431,7 @@ namespace PepperDash.Essentials } catch (Exception e) { - Debug.LogMessage(LogEventLevel.Information, "ERROR: Creating device {deviceKey:l}. Skipping device. \r\n{exception}", devConf.Key, e); + Debug.LogMessage(e, "ERROR: Creating device {deviceKey:l}. Skipping device.",args: new[] { devConf.Key }); } } Debug.LogMessage(LogEventLevel.Information, "All Devices Loaded."); diff --git a/src/PepperDash.Essentials/PepperDash.Essentials.csproj b/src/PepperDash.Essentials/PepperDash.Essentials.csproj index 4600ef2c..274b257f 100644 --- a/src/PepperDash.Essentials/PepperDash.Essentials.csproj +++ b/src/PepperDash.Essentials/PepperDash.Essentials.csproj @@ -48,8 +48,8 @@ - - + +