From 2048e3f65d02c8baf257e58eee35c85f1fe5bfca Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Wed, 3 Dec 2025 14:07:50 -0600 Subject: [PATCH 1/4] fix: GenericSink implements ICurrentSources --- .../Generic/GenericSink.cs | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs index 8f2fc1e2..a01daa2e 100644 --- a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs +++ b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs @@ -1,8 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using PepperDash.Core; using PepperDash.Core.Logging; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Core.Routing; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common.Generic @@ -10,8 +12,17 @@ namespace PepperDash.Essentials.Devices.Common.Generic /// /// Represents a GenericSink /// - public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputPort + public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputPort, ICurrentSources { + /// + public Dictionary CurrentSources { get; private set; } + + /// + public Dictionary CurrentSourceKeys { get; private set; } + + /// + public event EventHandler CurrentSourcesChanged; + /// /// Initializes a new instance of the GenericSink class /// @@ -26,6 +37,53 @@ namespace PepperDash.Essentials.Devices.Common.Generic InputPorts.Add(inputPort); } + /// + public void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem) + { + foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType))) + { + var flagValue = Convert.ToInt32(type); + // Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag). + // (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set. + if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0) + { + this.LogDebug("Skipping {type}", type); + continue; + } + + this.LogDebug("setting {type}", type); + + if (signalType.HasFlag(type)) + { + UpdateCurrentSources(type, sourceListKey, sourceListItem); + } + } + // Raise the CurrentSourcesChanged event + CurrentSourcesChanged?.Invoke(this, EventArgs.Empty); + } + + private void UpdateCurrentSources(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem) + { + if (CurrentSources.ContainsKey(signalType)) + { + CurrentSources[signalType] = sourceListItem; + } + else + { + CurrentSources.Add(signalType, sourceListItem); + } + + // Update the current source key for the specified signal type + if (CurrentSourceKeys.ContainsKey(signalType)) + { + CurrentSourceKeys[signalType] = sourceListKey; + } + else + { + CurrentSourceKeys.Add(signalType, sourceListKey); + } + } + /// /// Gets or sets the InputPorts /// From 6a33e7c99d705d939bd5df4fe5aa444e6268fc05 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Wed, 3 Dec 2025 14:19:26 -0600 Subject: [PATCH 2/4] fix: initialize current sources dictionaries --- .../Routing/Extensions.cs | 35 ++++++++++--------- .../Generic/GenericSink.cs | 12 +++++++ 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs index 17f1e50d..4346f3a9 100644 --- a/src/PepperDash.Essentials.Core/Routing/Extensions.cs +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -1,11 +1,11 @@ -using Crestron.SimplSharpPro.Keypads; -using PepperDash.Essentials.Core.Queues; -using PepperDash.Essentials.Core.Routing; -using Serilog.Events; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Crestron.SimplSharpPro.Keypads; +using PepperDash.Essentials.Core.Queues; +using PepperDash.Essentials.Core.Routing; +using Serilog.Events; using Debug = PepperDash.Core.Debug; @@ -115,7 +115,7 @@ namespace PepperDash.Essentials.Core public static (RouteDescriptor, RouteDescriptor) GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort, RoutingOutputPort sourcePort) { // if it's a single signal type, find the route - if (!signalType.HasFlag(eRoutingSignalType.AudioVideo) && + if (!signalType.HasFlag(eRoutingSignalType.AudioVideo) && !(signalType.HasFlag(eRoutingSignalType.Video) && signalType.HasFlag(eRoutingSignalType.SecondaryAudio))) { var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, signalType); @@ -134,14 +134,15 @@ namespace PepperDash.Essentials.Core } // otherwise, audioVideo needs to be handled as two steps. - Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key); + Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key, signalType); RouteDescriptor audioRouteDescriptor; if (signalType.HasFlag(eRoutingSignalType.SecondaryAudio)) { audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.SecondaryAudio); - } else + } + else { audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Audio); } @@ -199,13 +200,13 @@ namespace PepperDash.Essentials.Core Source = source, SourcePort = sourcePort, 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; @@ -219,7 +220,7 @@ namespace PepperDash.Essentials.Core //New Request if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) - { + { coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; RouteRequests.Add(destination.Key, routeRequest); @@ -239,9 +240,9 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); } - routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty, false)); + routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, destinationPort?.Key ?? string.Empty, false)); - routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest)); + routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest)); } /// @@ -272,7 +273,8 @@ namespace PepperDash.Essentials.Core audioOrSingleRoute.ExecuteRoutes(); videoRoute?.ExecuteRoutes(); - } catch(Exception ex) + } + catch (Exception ex) { Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request); } @@ -305,9 +307,10 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key); current.ReleaseRoutes(clearRoute); } - } catch (Exception ex) + } + catch (Exception ex) { - Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'",null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); + Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'", null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); } } diff --git a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs index a01daa2e..7e933b51 100644 --- a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs +++ b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs @@ -35,6 +35,18 @@ namespace PepperDash.Essentials.Devices.Common.Generic var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo | eRoutingSignalType.SecondaryAudio, eRoutingPortConnectionType.Hdmi, null, this); InputPorts.Add(inputPort); + + CurrentSources = new Dictionary + { + { eRoutingSignalType.Audio, null }, + { eRoutingSignalType.Video, null }, + }; + + CurrentSourceKeys = new Dictionary + { + { eRoutingSignalType.Audio, string.Empty }, + { eRoutingSignalType.Video, string.Empty }, + }; } /// From bce1e3610ea0c880e18f67e6dddcb686e18d4cd7 Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Wed, 10 Dec 2025 16:58:41 -0600 Subject: [PATCH 3/4] fix: copy dictionaries - fixed multiple enumeration exception --- .../Messengers/CurrentSourcesMessenger.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CurrentSourcesMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CurrentSourcesMessenger.cs index 9643a607..a7c4614b 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CurrentSourcesMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CurrentSourcesMessenger.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; @@ -39,10 +40,14 @@ namespace PepperDash.Essentials.AppServer.Messengers sourceDevice.CurrentSourcesChanged += (sender, e) => { + // need to copy the dictionaries to avoid enumeration issues + var currentSourceKeys = sourceDevice.CurrentSourceKeys.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + var currentSources = sourceDevice.CurrentSources.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + PostStatusMessage(JToken.FromObject(new { - currentSourceKeys = sourceDevice.CurrentSourceKeys, - currentSources = sourceDevice.CurrentSources + currentSourceKeys, + currentSources, })); }; } From 210b398a13f04e9cfb1dbcdc5f210e9e40307c0f Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Tue, 16 Dec 2025 10:04:01 -0600 Subject: [PATCH 4/4] fix: implement PR Review suggestions --- .../Routing/Extensions.cs | 2 +- .../Generic/GenericSink.cs | 20 ++----------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs index 4346f3a9..5d09176f 100644 --- a/src/PepperDash.Essentials.Core/Routing/Extensions.cs +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -134,7 +134,7 @@ namespace PepperDash.Essentials.Core } // otherwise, audioVideo needs to be handled as two steps. - Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key, signalType); + Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {destinationKey} to {sourceKey} of type {type}", destination, source.Key, signalType); RouteDescriptor audioRouteDescriptor; diff --git a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs index 7e933b51..64617770 100644 --- a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs +++ b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs @@ -76,24 +76,8 @@ namespace PepperDash.Essentials.Devices.Common.Generic private void UpdateCurrentSources(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem) { - if (CurrentSources.ContainsKey(signalType)) - { - CurrentSources[signalType] = sourceListItem; - } - else - { - CurrentSources.Add(signalType, sourceListItem); - } - - // Update the current source key for the specified signal type - if (CurrentSourceKeys.ContainsKey(signalType)) - { - CurrentSourceKeys[signalType] = sourceListKey; - } - else - { - CurrentSourceKeys.Add(signalType, sourceListKey); - } + CurrentSources[signalType] = sourceListItem; + CurrentSourceKeys[signalType] = sourceListKey; } ///