From 6949253b1a447f63dcb78a95dcf2e1920ec285be Mon Sep 17 00:00:00 2001 From: Jonathan Arndt Date: Wed, 29 Jul 2026 14:39:36 -0700 Subject: [PATCH 01/19] feat: add TS-1080 and TSW-1080 touchpanel support Add mctsw1080 and mcts1080 Mobile Control touchpanel types and map them to the Tsw1080 and Ts1080 hardware classes in the touchpanel controller factory. Bump the Crestron SimplSharp SDK packages (Library, ProgramLibrary, Program) from 2.21.90 to 2.21.274 across all projects. The Tsw1080 and Ts1080 device classes do not exist in the SDK prior to 2.21.274, so the SDK bump is required for the new panel types to compile. --- src/PepperDash.Core/PepperDash.Core.csproj | 2 +- .../PepperDash.Essentials.Core.csproj | 2 +- .../PepperDash.Essentials.Devices.Common.csproj | 2 +- .../PepperDash.Essentials.MobileControl.Messengers.csproj | 2 +- .../PepperDash.Essentials.MobileControl.csproj | 2 +- .../Touchpanel/MobileControlTouchpanelController.cs | 4 +++- src/PepperDash.Essentials/PepperDash.Essentials.csproj | 2 +- 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/PepperDash.Core/PepperDash.Core.csproj b/src/PepperDash.Core/PepperDash.Core.csproj index daa5c6da..747aefb6 100644 --- a/src/PepperDash.Core/PepperDash.Core.csproj +++ b/src/PepperDash.Core/PepperDash.Core.csproj @@ -43,7 +43,7 @@ - + diff --git a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj index 251ba316..b7f333bd 100644 --- a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj +++ b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj @@ -25,7 +25,7 @@ bin\$(Configuration)\PepperDash_Essentials_Core.xml - + 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 7be4372d..317fa2d3 100644 --- a/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj +++ b/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj @@ -29,6 +29,6 @@ - + \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj b/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj index d13d1a09..a9223061 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj +++ b/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj b/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj index 235e0899..b0e2dd9f 100644 --- a/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj +++ b/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj @@ -38,7 +38,7 @@ - + diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs index 5830782d..9e6230d3 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs @@ -740,7 +740,7 @@ namespace PepperDash.Essentials.Touchpanel /// public MobileControlTouchpanelControllerFactory() { - TypeNames = new List() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mcxpanel", "mcdge1000" }; + TypeNames = new List() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mctsw1080", "mcts1080", "mcxpanel", "mcdge1000" }; MinimumEssentialsFrameworkVersion = "2.0.0"; factories = new Dictionary> @@ -765,6 +765,8 @@ namespace PepperDash.Essentials.Touchpanel {"ts770", (id, controlSystem, projectName) => new Ts770(id, controlSystem)}, {"tsw1070", (id, controlSystem, projectName) => new Tsw1070(id, controlSystem)}, {"ts1070", (id, controlSystem, projectName) => new Ts1070(id, controlSystem)}, + {"tsw1080", (id, controlSystem, projectName) => new Tsw1080(id, controlSystem)}, + {"ts1080", (id, controlSystem, projectName) => new Ts1080(id, controlSystem)}, {"dge1000", (id, controlSystem, projectName) => new Dge1000(id, controlSystem)} }; } diff --git a/src/PepperDash.Essentials/PepperDash.Essentials.csproj b/src/PepperDash.Essentials/PepperDash.Essentials.csproj index 20a42ffd..cb83bf84 100644 --- a/src/PepperDash.Essentials/PepperDash.Essentials.csproj +++ b/src/PepperDash.Essentials/PepperDash.Essentials.csproj @@ -48,7 +48,7 @@ - + From 1719de195f00f2a20c7cc10f24db82741c256891 Mon Sep 17 00:00:00 2001 From: Jonathan Arndt Date: Thu, 30 Jul 2026 14:10:15 -0700 Subject: [PATCH 02/19] feat: implement ICommunicationMonitor on MobileControlTouchpanelController for Health page visibility --- .../MobileControlTouchpanelController.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs index 5830782d..d3350ac9 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs @@ -25,11 +25,16 @@ namespace PepperDash.Essentials.Touchpanel /// Mobile Control touchpanel controller that provides app control, Zoom integration, /// and mobile control functionality for Crestron touchpanels. /// - public class MobileControlTouchpanelController : TouchpanelBase, IHasFeedback, ITswAppControl, ITswZoomControl, IDeviceInfoProvider, IMobileControlCrestronTouchpanelController, ITheme + public class MobileControlTouchpanelController : TouchpanelBase, IHasFeedback, ITswAppControl, ITswZoomControl, IDeviceInfoProvider, IMobileControlCrestronTouchpanelController, ITheme, ICommunicationMonitor { private readonly MobileControlTouchpanelProperties localConfig; private IMobileControlRoomMessenger _bridge; + /// + /// Gets the CommunicationMonitor tracking the panel's online/offline state + /// + public StatusMonitorBase CommunicationMonitor { get; private set; } + private string _appUrl; /// @@ -128,6 +133,11 @@ namespace PepperDash.Essentials.Touchpanel { localConfig = config; + if (panel != null) + { + CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, panel, 120000, 300000); + } + AddPostActivationAction(SubscribeForMobileControlUpdates); ThemeFeedback = new StringFeedback($"{Key}-theme", () => Theme); @@ -366,6 +376,8 @@ namespace PepperDash.Essentials.Touchpanel /// public override bool CustomActivate() { + CommunicationMonitor?.Start(); + var appMessenger = new ITswAppControlMessenger($"appControlMessenger-{Key}", $"/device/{Key}", this); var zoomMessenger = new ITswZoomControlMessenger($"zoomControlMessenger-{Key}", $"/device/{Key}", this); @@ -393,6 +405,17 @@ namespace PepperDash.Essentials.Touchpanel return base.CustomActivate(); } + /// + /// Stops the CommunicationMonitor on deactivation. + /// + /// True if deactivation was successful; otherwise, false. + public override bool Deactivate() + { + CommunicationMonitor?.Stop(); + + return base.Deactivate(); + } + /// /// Handles device extender signal changes for system reserved signals. /// From d21cef0e60f25396a6adfb5d77543fb574884f80 Mon Sep 17 00:00:00 2001 From: Jonathan Arndt Date: Thu, 30 Jul 2026 16:22:30 -0700 Subject: [PATCH 03/19] fix: Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Touchpanel/MobileControlTouchpanelController.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs index d3350ac9..eef69015 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs @@ -35,6 +35,18 @@ namespace PepperDash.Essentials.Touchpanel /// public StatusMonitorBase CommunicationMonitor { get; private set; } + private sealed class NullCommunicationMonitor : StatusMonitorBase + { + public NullCommunicationMonitor(IKeyed parent) : base(parent, 120000, 300000) + { + Status = MonitorStatus.InError; + Message = "Panel is not initialized"; + } + + public override void Start() { } + public override void Stop() { } + } + private string _appUrl; /// From f5d6a076ad0becd0d73488a7f3befb6a00beedb1 Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Wed, 12 Aug 2026 11:22:03 -0400 Subject: [PATCH 04/19] feat: Add Mpc4TouchpanelController for MPC4 processors Introduced Mpc4TouchpanelController to manage touchpanel behavior for MPC4 class processors, including button initialization, feedback, and event handling. Updated ControlSystem.cs to detect MPC4 models, deserialize button configs, and register the controller with DeviceManager. --- .../Touchpanels/Mpc4Touchpanel.cs | 307 ++++++++++++++++++ src/PepperDash.Essentials/ControlSystem.cs | 19 ++ 2 files changed, 326 insertions(+) create mode 100644 src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs diff --git a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs new file mode 100644 index 00000000..cb9db34a --- /dev/null +++ b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs @@ -0,0 +1,307 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Crestron.SimplSharpPro; +using PepperDash.Core; +using PepperDash.Core.Logging; +using Serilog.Events; + +namespace PepperDash.Essentials.Core.Touchpanels +{ + /// + /// A wrapper class for the touchpanel portion of an MPC4 class process to allow for configurable + /// behavior of the keypad buttons + /// + public class Mpc4TouchpanelController : Device + { + readonly MPC4x3XXBase _touchpanel; + + readonly Dictionary _buttons; + + /// + /// Constructor + /// + /// device key + /// device name + /// control system processor + /// dictionary of keypad buttons + public Mpc4TouchpanelController(string key, string name, CrestronControlSystem processor, Dictionary buttons) + : base(key, name) + { + _touchpanel = processor.ControllerTouchScreenSlotDevice as MPC4x3XXBase; + if (_touchpanel == null) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Failed to construct MPC4 Touchpanel Controller with key {0}, check configuration, processor is type: {1}", key, processor.GetType().FullName); + return; + } + + if (_touchpanel.Registerable) + { + var registrationResponse = _touchpanel.Register(); + Debug.LogMessage(LogEventLevel.Information, this, "touchpanel registration response: {0}", registrationResponse); + } + + _touchpanel.BaseEvent += Touchpanel_BaseEvent; + _touchpanel.ButtonStateChange += Touchpanel_ButtonStateChange; + _touchpanel.PanelStateChange += Touchpanel_PanelStateChange; + + _buttons = buttons; + if (_buttons == null) + { + Debug.LogMessage(LogEventLevel.Debug, this, + "Button properties are null, failed to setup MPC4 Touch Controller, check configuration"); + return; + } + + AddPostActivationAction(() => + { + foreach (var button in _buttons) + { + var buttonKey = button.Key.ToLower(); + var buttonConfig = button.Value; + + InitializeButton(buttonKey, buttonConfig); + InitializeButtonFeedback(buttonKey, buttonConfig); + } + + ListButtons(); + }); + } + + /// + /// Enables/disables buttons based on event type configuration + /// + /// + /// + public void InitializeButton(string key, KeypadButton config) + { + if (config == null) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Button '{0}' config is null, unable to initialize", key); + return; + } + + TryParseInt(key, out int buttonNumber); + + var buttonEventTypes = config.EventTypes; + BoolOutputSig enabledFb = null; + BoolOutputSig disabledFb = null; + + switch (key) + { + case ("power"): + { + if (buttonEventTypes == null || buttonEventTypes.Keys == null) + _touchpanel.DisablePowerButton(); + else + _touchpanel.EnablePowerButton(); + + + enabledFb = _touchpanel.PowerButtonEnabledFeedBack; + disabledFb = _touchpanel.PowerButtonDisabledFeedBack; + + break; + } + case ("mute"): + { + if (buttonEventTypes == null || buttonEventTypes.Keys == null) + _touchpanel.DisableMuteButton(); + else + _touchpanel.EnableMuteButton(); + + + enabledFb = _touchpanel.MuteButtonEnabledFeedBack; + disabledFb = _touchpanel.MuteButtonDisabledFeedBack; + + break; + } + default: + { + if (buttonNumber == 0 || buttonNumber > 9) + break; + + if (buttonEventTypes == null || buttonEventTypes.Keys == null) + _touchpanel.DisableNumericalButton((uint)buttonNumber); + else + _touchpanel.EnableNumericalButton((uint)buttonNumber); + + + if (_touchpanel.NumericalButtonEnabledFeedBack != null) + enabledFb = _touchpanel.NumericalButtonEnabledFeedBack[(uint)buttonNumber]; + + if (_touchpanel.NumericalButtonDisabledFeedBack != null) + disabledFb = _touchpanel.NumericalButtonDisabledFeedBack[(uint)buttonNumber]; + + break; + } + } + + Debug.LogMessage(LogEventLevel.Information, this, "InitializeButton: key-'{0}' enabledFb-'{1}', disabledFb-'{2}'", + key, enabledFb ?? (object)"null", disabledFb ?? (object)"null"); + } + + /// + /// Links button feedback if configured + /// + /// + /// + public void InitializeButtonFeedback(string key, KeypadButton config) + { + if (config == null) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Button '{0}' config is null, skipping.", key); + return; + } + + TryParseInt(key, out int buttonNumber); + + // Link up the button feedbacks to the specified device feedback + var buttonFeedback = config.Feedback; + if (buttonFeedback == null || string.IsNullOrEmpty(buttonFeedback.DeviceKey)) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Button '{0}' feedback not configured, skipping.", + key); + return; + } + + Feedback deviceFeedback; + + try + { + if (!(DeviceManager.GetDeviceForKey(buttonFeedback.DeviceKey) is Device device)) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Button '{0}' feedback deviceKey '{1}' not found.", + key, buttonFeedback.DeviceKey); + return; + } + + deviceFeedback = device.GetFeedbackProperty(buttonFeedback.FeedbackName); + if (deviceFeedback == null) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Button '{0}' feedbackName property '{1}' not found.", + key, buttonFeedback.FeedbackName); + return; + } + } + catch (Exception ex) + { + Debug.LogMessage(LogEventLevel.Debug, this, "InitializeButtonFeedback (button '{1}', deviceKey '{2}') Exception Message: {0}", + ex.Message, key, buttonFeedback.DeviceKey); + Debug.LogMessage(LogEventLevel.Verbose, this, "InitializeButtonFeedback (button '{1}', deviceKey '{2}') Exception StackTrace: {0}", + ex.StackTrace, key, buttonFeedback.DeviceKey); + if (ex.InnerException != null) Debug.LogMessage(LogEventLevel.Verbose, this, "InitializeButtonFeedback (button '{1}', deviceKey '{2}') InnerException: {0}", + ex.InnerException, key, buttonFeedback.DeviceKey); + + return; + } + + var boolFeedback = deviceFeedback as BoolFeedback; + + switch (key) + { + case ("power"): + { + boolFeedback?.LinkCrestronFeedback(_touchpanel.FeedbackPower); + break; + } + case ("volumeup"): + case ("volumedown"): + case ("volumefeedback"): + { + if (deviceFeedback is IntFeedback intFeedback) + { + var volumeFeedback = intFeedback; + volumeFeedback.LinkInputSig(_touchpanel.VolumeBargraph); + } + break; + } + case ("mute"): + { + boolFeedback?.LinkCrestronFeedback(_touchpanel.FeedbackMute); + break; + } + default: + { + boolFeedback?.LinkCrestronFeedback(_touchpanel.Feedbacks[(uint)buttonNumber]); + break; + } + } + } + + /// + /// Try parse int helper method + /// + /// + /// + /// + public bool TryParseInt(string str, out int result) + { + try + { + result = int.Parse(str); + return true; + } + catch + { + result = 0; + return false; + } + } + + private void Touchpanel_BaseEvent(GenericBase device, BaseEventArgs args) + { + Debug.LogMessage(LogEventLevel.Debug, this, "BaseEvent: eventId-'{0}', index-'{1}'", args.EventId, args.Index); + } + + private void Touchpanel_ButtonStateChange(GenericBase device, Crestron.SimplSharpPro.DeviceSupport.ButtonEventArgs args) + { + Debug.LogMessage(LogEventLevel.Debug, this, "ButtonStateChange: buttonNumber-'{0}' buttonName-'{1}', buttonState-'{2}'", args.Button.Number, args.Button.Name, args.NewButtonState); + var type = args.NewButtonState.ToString(); + + if (_buttons.ContainsKey(args.Button.Number.ToString(CultureInfo.InvariantCulture))) + { + Press(args.Button.Number.ToString(CultureInfo.InvariantCulture), type); + } + else if (_buttons.ContainsKey(args.Button.Name.ToString())) + { + Press(args.Button.Name.ToString(), type); + } + } + + private void Touchpanel_PanelStateChange(GenericBase device, BaseEventArgs args) + { + Debug.LogMessage(LogEventLevel.Debug, this, "PanelStateChange: eventId-'{0}', index-'{1}'", args.EventId, args.Index); + } + + /// + /// Runs the function associated with this button/type. One of the following strings: + /// Pressed, Released, Tapped, DoubleTapped, Held, HeldReleased + /// + /// + /// + public void Press(string buttonKey, string type) + { + this.LogVerbose("Press: buttonKey-'{buttonKey}', type-'{type}'", buttonKey, type); + + if (!_buttons.ContainsKey(buttonKey)) return; + + var button = _buttons[buttonKey]; + if (!button.EventTypes.ContainsKey(type)) return; + + foreach (var eventType in button.EventTypes[type]) DeviceJsonApi.DoDeviceAction(eventType); + } + + /// + /// ListButtons method + /// + public void ListButtons() + { + this.LogVerbose("MPC4 Controller {0} - Available Buttons", Key); + + foreach (var button in _buttons) + { + this.LogVerbose("Key: {key}", button.Key); + } + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index 47de4d3e..f2888b8a 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -436,6 +436,25 @@ namespace PepperDash.Essentials "WARNING: Config file defines processor type as '{deviceType:l}' but actual processor is '{processorType:l}'! Some ports may not be available", devConf.Type.ToUpper(), Global.ControlSystem.ControllerPrompt.ToUpper()); + // Check if the processor is an MPC4 model + if (prompt.IndexOf("mpc4", StringComparison.OrdinalIgnoreCase) > -1) + { + Debug.LogMessage(LogEventLevel.Information, "MPC4 processor type detected. Adding Mpc4TouchpanelController."); + + var butToken = devConf.Properties["buttons"]; + if (butToken == null) + { + Debug.LogMessage(LogEventLevel.Warning, + "Error: Unable to deserialize buttons collection for device: {deviceKey}", devConf.Key); + continue; + } + + var buttons = Newtonsoft.Json.JsonConvert.DeserializeObject>(butToken.ToString()); + var tpController = new Core.Touchpanels.Mpc4TouchpanelController( + string.Format("{0}-keypadButtons", devConf.Key), devConf.Name, Global.ControlSystem, buttons); + + DeviceManager.AddDevice(tpController); + } continue; } From d713d65c7aeabcef5d93e707a45ab592c79e85ae Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Wed, 12 Aug 2026 12:35:21 -0400 Subject: [PATCH 05/19] fix: Refactor touchpanel init and error logging Change _touchpanel type to MPC3Basic. Update constructor to select touchscreen slot by TouchscreenType using switch. Improve error logging with error level and detailed type info. --- .../Touchpanels/Mpc4Touchpanel.cs | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs index cb9db34a..19eb0d87 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs @@ -14,7 +14,7 @@ namespace PepperDash.Essentials.Core.Touchpanels /// public class Mpc4TouchpanelController : Device { - readonly MPC4x3XXBase _touchpanel; + readonly MPC3Basic _touchpanel; readonly Dictionary _buttons; @@ -28,10 +28,32 @@ namespace PepperDash.Essentials.Core.Touchpanels public Mpc4TouchpanelController(string key, string name, CrestronControlSystem processor, Dictionary buttons) : base(key, name) { - _touchpanel = processor.ControllerTouchScreenSlotDevice as MPC4x3XXBase; + switch (processor.TouchscreenType) + { + case eTouchscreenType.MPC4x102: + _touchpanel = processor.MPC4x102TouchscreenSlot; + break; + + case eTouchscreenType.MPC4x201: + _touchpanel = processor.MPC4x201TouchscreenSlot; + break; + + case eTouchscreenType.MPC4x301: + _touchpanel = processor.MPC4x301TouchscreenSlot; + break; + + case eTouchscreenType.MPC4x302: + _touchpanel = processor.MPC4x302TouchscreenSlot; + break; + + default: + Debug.LogMessage(LogEventLevel.Error, this, "Unsupported touchpanel type '{0}' for MPC4 Touch Controller", processor.TouchscreenType); + break; + } + if (_touchpanel == null) { - Debug.LogMessage(LogEventLevel.Debug, this, "Failed to construct MPC4 Touchpanel Controller with key {0}, check configuration, processor is type: {1}", key, processor.GetType().FullName); + Debug.LogMessage(LogEventLevel.Error, this, "Failed to construct MPC4 Touchpanel Controller with key {0}, check configuration, processor is type: {1}", key, processor.ControllerTouchScreenSlotDevice?.GetType().FullName ?? "null"); return; } From 31fd66559411065e379a834f18441e215cd95b36 Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Wed, 12 Aug 2026 12:44:43 -0400 Subject: [PATCH 06/19] fix: Simplify touchscreen slot selection logic Replaced switch statement for touchscreen type selection with a single cast assignment to MPC3Basic. This streamlines initialization and removes explicit handling for each touchscreen type. Error logging is retained for failed casts. --- .../Touchpanels/Mpc4Touchpanel.cs | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs index 19eb0d87..4d16f184 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs @@ -28,28 +28,7 @@ namespace PepperDash.Essentials.Core.Touchpanels public Mpc4TouchpanelController(string key, string name, CrestronControlSystem processor, Dictionary buttons) : base(key, name) { - switch (processor.TouchscreenType) - { - case eTouchscreenType.MPC4x102: - _touchpanel = processor.MPC4x102TouchscreenSlot; - break; - - case eTouchscreenType.MPC4x201: - _touchpanel = processor.MPC4x201TouchscreenSlot; - break; - - case eTouchscreenType.MPC4x301: - _touchpanel = processor.MPC4x301TouchscreenSlot; - break; - - case eTouchscreenType.MPC4x302: - _touchpanel = processor.MPC4x302TouchscreenSlot; - break; - - default: - Debug.LogMessage(LogEventLevel.Error, this, "Unsupported touchpanel type '{0}' for MPC4 Touch Controller", processor.TouchscreenType); - break; - } + _touchpanel = processor.ControllerTouchScreenSlotDevice as MPC3Basic; if (_touchpanel == null) { From 8c56623641dc694ad04b8fbb053a958f96b0d686 Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Wed, 12 Aug 2026 14:17:50 -0400 Subject: [PATCH 07/19] fix: Support multiple MPC4 touchscreen slot types in ctor Updated Mpc4TouchpanelController constructor to assign _touchpanel from the first available slot among MPC4x102, MPC4x201, MPC4x301, or MPC4x302 touchscreen slots, improving compatibility with various MPC4 touchscreen models. --- .../Touchpanels/Mpc4Touchpanel.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs index 4d16f184..85a7b987 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs @@ -28,7 +28,22 @@ namespace PepperDash.Essentials.Core.Touchpanels public Mpc4TouchpanelController(string key, string name, CrestronControlSystem processor, Dictionary buttons) : base(key, name) { - _touchpanel = processor.ControllerTouchScreenSlotDevice as MPC3Basic; + if (processor.MPC4x102TouchscreenSlot != null) + { + _touchpanel = processor.MPC4x102TouchscreenSlot; + } + else if (processor.MPC4x201TouchscreenSlot != null) + { + _touchpanel = processor.MPC4x201TouchscreenSlot; + } + else if (processor.MPC4x301TouchscreenSlot != null) + { + _touchpanel = processor.MPC4x301TouchscreenSlot; + } + else if (processor.MPC4x302TouchscreenSlot != null) + { + _touchpanel = processor.MPC4x302TouchscreenSlot; + } if (_touchpanel == null) { From 25693c7071ab655fe3f63cba826c1bbc6b7cd7b5 Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Wed, 12 Aug 2026 14:40:54 -0400 Subject: [PATCH 08/19] fix: Add debug logs for touchscreen slot selection in controller Improved logging to indicate which touchscreen slot is used during Mpc4TouchpanelController initialization. Updated error log to report processor.TouchscreenType for clearer diagnostics. --- .../Touchpanels/Mpc4Touchpanel.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs index 85a7b987..5de2f69a 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs @@ -30,24 +30,33 @@ namespace PepperDash.Essentials.Core.Touchpanels { if (processor.MPC4x102TouchscreenSlot != null) { + Debug.LogMessage(LogEventLevel.Information, this, "Using MPC4x102TouchscreenSlot"); _touchpanel = processor.MPC4x102TouchscreenSlot; } else if (processor.MPC4x201TouchscreenSlot != null) { + Debug.LogMessage(LogEventLevel.Information, this, "Using MPC4x201TouchscreenSlot"); _touchpanel = processor.MPC4x201TouchscreenSlot; } else if (processor.MPC4x301TouchscreenSlot != null) { + Debug.LogMessage(LogEventLevel.Information, this, "Using MPC4x301TouchscreenSlot"); _touchpanel = processor.MPC4x301TouchscreenSlot; } else if (processor.MPC4x302TouchscreenSlot != null) { + Debug.LogMessage(LogEventLevel.Information, this, "Using MPC4x302TouchscreenSlot"); _touchpanel = processor.MPC4x302TouchscreenSlot; } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "Using ControllerTouchScreenSlotDevice"); + _touchpanel = processor.ControllerTouchScreenSlotDevice as MPC3Basic; + } if (_touchpanel == null) { - Debug.LogMessage(LogEventLevel.Error, this, "Failed to construct MPC4 Touchpanel Controller with key {0}, check configuration, processor is type: {1}", key, processor.ControllerTouchScreenSlotDevice?.GetType().FullName ?? "null"); + Debug.LogMessage(LogEventLevel.Error, this, "Failed to construct MPC4 Touchpanel Controller with key {0}, check configuration, processor is type: {1}", key, processor.TouchscreenType); return; } From 83bf40491b966a7252ba50e4d5ee7be9f74a7a43 Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Wed, 12 Aug 2026 14:55:04 -0400 Subject: [PATCH 09/19] fix: Refine touchpanel device selection and logging Improve logic to check for MPC3Basic type using pattern matching. Update debug and error messages to report actual device type. Clarify conditional structure for better readability. --- .../Touchpanels/Mpc4Touchpanel.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs index 5de2f69a..ef9df608 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs @@ -48,15 +48,14 @@ namespace PepperDash.Essentials.Core.Touchpanels Debug.LogMessage(LogEventLevel.Information, this, "Using MPC4x302TouchscreenSlot"); _touchpanel = processor.MPC4x302TouchscreenSlot; } + else if (processor.ControllerTouchScreenSlotDevice != null && processor.ControllerTouchScreenSlotDevice is MPC3Basic mpc) + { + Debug.LogMessage(LogEventLevel.Information, this, "Using ControllerTouchScreenSlotDevice:{0}", processor.ControllerTouchScreenSlotDevice.GetType().Name); + _touchpanel = mpc; + } else { - Debug.LogMessage(LogEventLevel.Information, this, "Using ControllerTouchScreenSlotDevice"); - _touchpanel = processor.ControllerTouchScreenSlotDevice as MPC3Basic; - } - - if (_touchpanel == null) - { - Debug.LogMessage(LogEventLevel.Error, this, "Failed to construct MPC4 Touchpanel Controller with key {0}, check configuration, processor is type: {1}", key, processor.TouchscreenType); + Debug.LogMessage(LogEventLevel.Error, this, "Failed to find MPC4 Touchpanel Controller with key {0}, check configuration, processor is type: {1}", key, processor.ControllerTouchScreenSlotDevice?.GetType().Name ?? "unknown"); return; } From 3c9f5e95e0b6c0029d17e4c64757fa19418870bd Mon Sep 17 00:00:00 2001 From: jkdevito Date: Tue, 21 Jul 2026 21:50:06 -0500 Subject: [PATCH 10/19] feat(mobile-control): support https/wss for direct server URLs when Secure is true - Add HttpScheme/WsScheme helpers driven by directServer.Secure - Replace hardcoded http/ws literals in UserAppUrlPrefix, touchpanel app URL, _config.local.json ApiPath, remote logging POST, join-response WebSocketUrl and UserAppUrl - Pass Secure flag into the HttpServer constructor so the listener actually negotiates TLS when a cert is configured - Drop TLS 1.1 from EnabledSslProtocols, keep TLS 1.2 (TLS 1.3 is not defined in the net472 SslProtocols enum used by this project) - MobileControlTouchpanelController: rewrite the app-URL IP regex to match and preserve either http or https instead of assuming http --- .../MobileControlTouchpanelController.cs | 8 ++--- .../MobileControlWebsocketServer.cs | 31 +++++++++++++------ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs index ec241a33..dcd0d56e 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs @@ -554,15 +554,15 @@ namespace PepperDash.Essentials.Touchpanel return false; }) ? csIpAddress.ToString() : processorIp; - var match = Regex.Match(url, @"^http://([^:/]+):\d+/mc/app\?token=.+$"); + var match = Regex.Match(url, @"^(https?)://([^:/]+):\d+/mc/app\?token=.+$"); if (match.Success) { - string ipa = match.Groups[1].Value; + string ipa = match.Groups[2].Value; // ip will be "192.168.1.100" } - // replace ipa with ip but leave the rest of the string intact - var updatedUrl = Regex.Replace(url, @"^http://[^:/]+", $"http://{ip}"); + // replace the host but preserve whatever scheme (http/https) is already present in the URL + var updatedUrl = Regex.Replace(url, @"^(https?)://[^:/]+", $"$1://{ip}"); this.LogVerbose("Updated URL: {updatedUrl}", updatedUrl); diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs index 1c9ed37a..3bcbcdf6 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs @@ -127,6 +127,16 @@ namespace PepperDash.Essentials.WebSocketServer /// public int Port { get; private set; } + /// + /// Gets the HTTP scheme to use for generated URLs, based on whether the direct server is configured as secure + /// + private string HttpScheme => _parent.Config.DirectServer.Secure ? "https" : "http"; + + /// + /// Gets the WebSocket scheme to use for generated URLs, based on whether the direct server is configured as secure + /// + private string WsScheme => _parent.Config.DirectServer.Secure ? "wss" : "ws"; + /// /// Gets the user app URL prefix /// @@ -134,7 +144,8 @@ namespace PepperDash.Essentials.WebSocketServer { get { - return string.Format("http://{0}:{1}{2}?token=", + return string.Format("{0}://{1}:{2}{3}?token=", + HttpScheme, CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0), Port, _userAppBaseHref); @@ -273,7 +284,7 @@ namespace PepperDash.Essentials.WebSocketServer { base.Initialize(); - _server = new HttpServer(Port, false); + _server = new HttpServer(Port, _parent.Config.DirectServer.Secure); _server.OnGet += Server_OnGet; @@ -291,7 +302,7 @@ namespace PepperDash.Essentials.WebSocketServer { ClientCertificateRequired = false, CheckCertificateRevocation = false, - EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 + EnabledSslProtocols = SslProtocols.Tls12 }; } @@ -403,11 +414,11 @@ namespace PepperDash.Essentials.WebSocketServer ip = csIpAddress.ToString(); } - var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"; + var appUrl = $"{HttpScheme}://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"; this.LogVerbose("Sending URL {appUrl} to touchpanel {touchpanelKey}", appUrl, touchpanel.Touchpanel.Key); - touchpanel.Touchpanel.SetAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"); + touchpanel.Touchpanel.SetAppUrl(appUrl); } } @@ -487,7 +498,7 @@ namespace PepperDash.Essentials.WebSocketServer { var config = new MobileControlApplicationConfig { - ApiPath = string.Format("http://{0}:{1}/mc/api", processorIp, _parent.Config.DirectServer.Port), + ApiPath = string.Format("{0}://{1}:{2}/mc/api", HttpScheme, processorIp, _parent.Config.DirectServer.Port), GatewayAppPath = "", LogoPath = _parent.Config.ApplicationConfig?.LogoPath ?? "logo/logo.png", EnableDev = _parent.Config.ApplicationConfig?.EnableDev ?? false, @@ -1098,7 +1109,7 @@ namespace PepperDash.Essentials.WebSocketServer res.StatusCode = 200; res.Close(); - var logRequest = new HttpRequestMessage(HttpMethod.Post, $"http://{_parent.Config.DirectServer.Logging.Host}:{_parent.Config.DirectServer.Logging.Port}/logs") + var logRequest = new HttpRequestMessage(HttpMethod.Post, $"{HttpScheme}://{_parent.Config.DirectServer.Logging.Host}:{_parent.Config.DirectServer.Logging.Port}/logs") { Content = new StringContent(body, Encoding.UTF8, "application/json"), }; @@ -1213,8 +1224,7 @@ namespace PepperDash.Essentials.WebSocketServer this.LogVerbose("Assigning ClientId: {clientId} for token: {token} at {timestamp}", clientId, token, now); // Construct WebSocket URL with clientId query parameter - var wsProtocol = "ws"; - var wsUrl = $"{wsProtocol}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}"; + var wsUrl = $"{WsScheme}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}"; // Construct the response object JoinResponse jRes = new JoinResponse @@ -1226,7 +1236,8 @@ namespace PepperDash.Essentials.WebSocketServer Config = _parent.GetConfigWithPluginVersion(), CodeExpires = new DateTime().AddYears(1), UserCode = bridge.UserCode, - UserAppUrl = string.Format("http://{0}:{1}/mc/app", + UserAppUrl = string.Format("{0}://{1}:{2}/mc/app", + HttpScheme, CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0), Port), WebSocketUrl = wsUrl, From 3afc73a4469cd9b3101d7a17f17406e724547964 Mon Sep 17 00:00:00 2001 From: jkdevito Date: Thu, 13 Aug 2026 17:50:26 -0500 Subject: [PATCH 11/19] fix(mobile-control): address PR review feedback - Use the actual listening Port instead of raw DirectServer.Port config when building the app URL and API path, so advertised URLs match the listener even when the configured port is 0. - Keep the remote logging endpoint on http:// since there is no dedicated secure flag for the log collector. - Remove dead Regex.Match/ipa variable in GetUrlWithCorrectIp; the scheme-preserving Regex.Replace below it already handles this. --- .../Touchpanel/MobileControlTouchpanelController.cs | 7 ------- .../WebSocketServer/MobileControlWebsocketServer.cs | 7 ++++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs index dcd0d56e..262cb34e 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs @@ -554,13 +554,6 @@ namespace PepperDash.Essentials.Touchpanel return false; }) ? csIpAddress.ToString() : processorIp; - var match = Regex.Match(url, @"^(https?)://([^:/]+):\d+/mc/app\?token=.+$"); - if (match.Success) - { - string ipa = match.Groups[2].Value; - // ip will be "192.168.1.100" - } - // replace the host but preserve whatever scheme (http/https) is already present in the URL var updatedUrl = Regex.Replace(url, @"^(https?)://[^:/]+", $"$1://{ip}"); diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs index 3bcbcdf6..a948d857 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs @@ -414,7 +414,7 @@ namespace PepperDash.Essentials.WebSocketServer ip = csIpAddress.ToString(); } - var appUrl = $"{HttpScheme}://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"; + var appUrl = $"{HttpScheme}://{ip}:{Port}/mc/app?token={touchpanel.Key}"; this.LogVerbose("Sending URL {appUrl} to touchpanel {touchpanelKey}", appUrl, touchpanel.Touchpanel.Key); @@ -498,7 +498,7 @@ namespace PepperDash.Essentials.WebSocketServer { var config = new MobileControlApplicationConfig { - ApiPath = string.Format("{0}://{1}:{2}/mc/api", HttpScheme, processorIp, _parent.Config.DirectServer.Port), + ApiPath = string.Format("{0}://{1}:{2}/mc/api", HttpScheme, processorIp, Port), GatewayAppPath = "", LogoPath = _parent.Config.ApplicationConfig?.LogoPath ?? "logo/logo.png", EnableDev = _parent.Config.ApplicationConfig?.EnableDev ?? false, @@ -1109,7 +1109,8 @@ namespace PepperDash.Essentials.WebSocketServer res.StatusCode = 200; res.Close(); - var logRequest = new HttpRequestMessage(HttpMethod.Post, $"{HttpScheme}://{_parent.Config.DirectServer.Logging.Host}:{_parent.Config.DirectServer.Logging.Port}/logs") + // remote log collector has no dedicated secure flag; keep it on http regardless of DirectServer.Secure + var logRequest = new HttpRequestMessage(HttpMethod.Post, $"http://{_parent.Config.DirectServer.Logging.Host}:{_parent.Config.DirectServer.Logging.Port}/logs") { Content = new StringContent(body, Encoding.UTF8, "application/json"), }; From cdabbdb164836c492bf2d936bee00b5ee08bfddc Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Fri, 14 Aug 2026 09:17:47 -0400 Subject: [PATCH 12/19] fix: Refactor Mpc4TouchpanelController initialization logic Refactored Mpc4TouchpanelController to store the processor as a private readonly field and moved button dictionary initialization to the constructor. Moved touchpanel slot selection logic from the constructor to an overridden Initialize() method, now using the _processor field and removing fallback to ControllerTouchScreenSlotDevice. Improved error logging by removing references to processor type and key variables. Changed button initialization to occur in Initialize() with a foreach loop, eliminating AddPostActivationAction and null checks for the button dictionary. --- .../Touchpanels/Mpc4Touchpanel.cs | 63 ++++++++----------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs index ef9df608..a8fa268a 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs @@ -1,10 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Globalization; using Crestron.SimplSharpPro; using PepperDash.Core; using PepperDash.Core.Logging; using Serilog.Events; +using System; +using System.Collections.Generic; +using System.Globalization; namespace PepperDash.Essentials.Core.Touchpanels { @@ -14,7 +14,8 @@ namespace PepperDash.Essentials.Core.Touchpanels /// public class Mpc4TouchpanelController : Device { - readonly MPC3Basic _touchpanel; + private readonly CrestronControlSystem _processor; + private MPC3Basic _touchpanel; readonly Dictionary _buttons; @@ -28,34 +29,35 @@ namespace PepperDash.Essentials.Core.Touchpanels public Mpc4TouchpanelController(string key, string name, CrestronControlSystem processor, Dictionary buttons) : base(key, name) { - if (processor.MPC4x102TouchscreenSlot != null) + _processor = processor; + _buttons = buttons ?? new Dictionary(); + } + + public override void Initialize() + { + if (_processor.MPC4x102TouchscreenSlot != null) { Debug.LogMessage(LogEventLevel.Information, this, "Using MPC4x102TouchscreenSlot"); - _touchpanel = processor.MPC4x102TouchscreenSlot; + _touchpanel = _processor.MPC4x102TouchscreenSlot; } - else if (processor.MPC4x201TouchscreenSlot != null) + else if (_processor.MPC4x201TouchscreenSlot != null) { Debug.LogMessage(LogEventLevel.Information, this, "Using MPC4x201TouchscreenSlot"); - _touchpanel = processor.MPC4x201TouchscreenSlot; + _touchpanel = _processor.MPC4x201TouchscreenSlot; } - else if (processor.MPC4x301TouchscreenSlot != null) + else if (_processor.MPC4x301TouchscreenSlot != null) { Debug.LogMessage(LogEventLevel.Information, this, "Using MPC4x301TouchscreenSlot"); - _touchpanel = processor.MPC4x301TouchscreenSlot; + _touchpanel = _processor.MPC4x301TouchscreenSlot; } - else if (processor.MPC4x302TouchscreenSlot != null) + else if (_processor.MPC4x302TouchscreenSlot != null) { Debug.LogMessage(LogEventLevel.Information, this, "Using MPC4x302TouchscreenSlot"); - _touchpanel = processor.MPC4x302TouchscreenSlot; - } - else if (processor.ControllerTouchScreenSlotDevice != null && processor.ControllerTouchScreenSlotDevice is MPC3Basic mpc) - { - Debug.LogMessage(LogEventLevel.Information, this, "Using ControllerTouchScreenSlotDevice:{0}", processor.ControllerTouchScreenSlotDevice.GetType().Name); - _touchpanel = mpc; + _touchpanel = _processor.MPC4x302TouchscreenSlot; } else { - Debug.LogMessage(LogEventLevel.Error, this, "Failed to find MPC4 Touchpanel Controller with key {0}, check configuration, processor is type: {1}", key, processor.ControllerTouchScreenSlotDevice?.GetType().Name ?? "unknown"); + Debug.LogMessage(LogEventLevel.Error, this, "Failed to find MPC4 Touchpanel Controller with key {0}, check configuration", Key); return; } @@ -69,27 +71,16 @@ namespace PepperDash.Essentials.Core.Touchpanels _touchpanel.ButtonStateChange += Touchpanel_ButtonStateChange; _touchpanel.PanelStateChange += Touchpanel_PanelStateChange; - _buttons = buttons; - if (_buttons == null) + foreach (var button in _buttons) { - Debug.LogMessage(LogEventLevel.Debug, this, - "Button properties are null, failed to setup MPC4 Touch Controller, check configuration"); - return; + var buttonKey = button.Key.ToLower(); + var buttonConfig = button.Value; + + InitializeButton(buttonKey, buttonConfig); + InitializeButtonFeedback(buttonKey, buttonConfig); } - AddPostActivationAction(() => - { - foreach (var button in _buttons) - { - var buttonKey = button.Key.ToLower(); - var buttonConfig = button.Value; - - InitializeButton(buttonKey, buttonConfig); - InitializeButtonFeedback(buttonKey, buttonConfig); - } - - ListButtons(); - }); + ListButtons(); } /// From 1eed8a5c34431b56223e1516d1583e19b44f9145 Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Fri, 14 Aug 2026 10:17:02 -0400 Subject: [PATCH 13/19] feat: Update Crestron.SimplSharp.SDK NuGet packages Upgraded Crestron.SimplSharp.SDK.Library, ProgramLibrary, and Program package versions from 2.21.90 to 2.21.274 across multiple project files. No other code or configuration changes were made. --- src/PepperDash.Core/PepperDash.Core.csproj | 2 +- .../PepperDash.Essentials.Core.csproj | 2 +- .../PepperDash.Essentials.Devices.Common.csproj | 2 +- .../PepperDash.Essentials.MobileControl.Messengers.csproj | 2 +- .../PepperDash.Essentials.MobileControl.csproj | 2 +- src/PepperDash.Essentials/PepperDash.Essentials.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PepperDash.Core/PepperDash.Core.csproj b/src/PepperDash.Core/PepperDash.Core.csproj index daa5c6da..747aefb6 100644 --- a/src/PepperDash.Core/PepperDash.Core.csproj +++ b/src/PepperDash.Core/PepperDash.Core.csproj @@ -43,7 +43,7 @@ - + diff --git a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj index 251ba316..b7f333bd 100644 --- a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj +++ b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj @@ -25,7 +25,7 @@ bin\$(Configuration)\PepperDash_Essentials_Core.xml - + 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 7be4372d..317fa2d3 100644 --- a/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj +++ b/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj @@ -29,6 +29,6 @@ - + \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj b/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj index d13d1a09..a9223061 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj +++ b/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj b/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj index 235e0899..b0e2dd9f 100644 --- a/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj +++ b/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj @@ -38,7 +38,7 @@ - + diff --git a/src/PepperDash.Essentials/PepperDash.Essentials.csproj b/src/PepperDash.Essentials/PepperDash.Essentials.csproj index 20a42ffd..cb83bf84 100644 --- a/src/PepperDash.Essentials/PepperDash.Essentials.csproj +++ b/src/PepperDash.Essentials/PepperDash.Essentials.csproj @@ -48,7 +48,7 @@ - + From 21faed417b91748827bc8f87248b51f23a34c3aa Mon Sep 17 00:00:00 2001 From: Nick Genovese Date: Fri, 14 Aug 2026 10:35:07 -0400 Subject: [PATCH 14/19] fix: Refactor activation logic and improve logging Replaced Initialize() with CustomActivate() returning a bool for activation success. Added logging for activation and slot usage. Now returns false if slot not found and returns registration status at the end. --- .../Touchpanels/Mpc4Touchpanel.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs index a8fa268a..3d1cb5fc 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/Mpc4Touchpanel.cs @@ -33,8 +33,10 @@ namespace PepperDash.Essentials.Core.Touchpanels _buttons = buttons ?? new Dictionary(); } - public override void Initialize() + public override bool CustomActivate() { + Debug.LogInformation(this, "Activating MPC4 Touchpanel Controller with key {0}", Key); + if (_processor.MPC4x102TouchscreenSlot != null) { Debug.LogMessage(LogEventLevel.Information, this, "Using MPC4x102TouchscreenSlot"); @@ -58,7 +60,7 @@ namespace PepperDash.Essentials.Core.Touchpanels else { Debug.LogMessage(LogEventLevel.Error, this, "Failed to find MPC4 Touchpanel Controller with key {0}, check configuration", Key); - return; + return false; } if (_touchpanel.Registerable) @@ -81,6 +83,8 @@ namespace PepperDash.Essentials.Core.Touchpanels } ListButtons(); + + return _touchpanel.Registered; } /// From 85a4b635f69e9427c14d3cac9a93188dad2c3f5a Mon Sep 17 00:00:00 2001 From: Jonathan Arndt Date: Wed, 19 Aug 2026 10:24:23 -0700 Subject: [PATCH 15/19] fix(mobile-control): update version and improve response caching for join requests --- src/Directory.Build.props | 2 +- .../WebSocketServer/MobileControlWebsocketServer.cs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ab9f2732..235fee48 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - 2.36.6-local + 2.42.1-local $(Version) PepperDash Technology PepperDash Technology diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs index a948d857..c96dc116 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs @@ -1162,6 +1162,11 @@ namespace PepperDash.Essentials.WebSocketServer var qp = req.QueryString; var token = qp["token"]; + // Each join mints a single-use clientId; the panel webview must never replay a cached + // response, or it reconnects forever with an already-consumed id (1008 loop). + res.Headers.Add("Cache-Control", "no-store"); + res.Headers.Add("Pragma", "no-cache"); + this.LogVerbose("Join Room Request with token: {token}", token); byte[] body; @@ -1263,6 +1268,8 @@ namespace PepperDash.Essentials.WebSocketServer { res.StatusCode = 200; res.ContentType = "application/json"; + res.Headers.Add("Cache-Control", "no-store"); + res.Headers.Add("Pragma", "no-cache"); var version = new Version() { ServerVersion = _parent.GetConfigWithPluginVersion().RuntimeInfo.PluginVersion }; var message = JsonConvert.SerializeObject(version); this.LogVerbose("{message}", message); From ec9753b19e824f5c9113004feca1397b489d46bc Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Thu, 20 Aug 2026 10:26:09 -0600 Subject: [PATCH 16/19] fix(ssh-client): improve error handling for SSH connection exceptions --- src/PepperDash.Core/Comm/GenericSshClient.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/PepperDash.Core/Comm/GenericSshClient.cs b/src/PepperDash.Core/Comm/GenericSshClient.cs index df44ab51..16406978 100644 --- a/src/PepperDash.Core/Comm/GenericSshClient.cs +++ b/src/PepperDash.Core/Comm/GenericSshClient.cs @@ -287,13 +287,7 @@ namespace PepperDash.Core } catch (SshConnectionException e) { - var ie = e.InnerException; // The details are inside!! - - if (ie is SocketException) - { - this.LogError("CONNECTION failure: Cannot reach host"); - this.LogVerbose(ie, "Exception details: "); - } + var ie = e.InnerException; // The details are inside, when present - remote can close the connection with no inner exception at all if (ie is System.Net.Sockets.SocketException socketException) { @@ -301,15 +295,15 @@ namespace PepperDash.Core Hostname, Port); this.LogVerbose(socketException, "SocketException details: "); } - if (ie is SshAuthenticationException) + else if (ie is SshAuthenticationException) { this.LogError("Authentication failure for username {userName}", Username); this.LogVerbose(ie, "AuthenticationException details: "); } else { - this.LogError("Error on connect: {error}", ie.Message); - this.LogVerbose(ie, "Exception details: "); + this.LogError("Error on connect: {error}", ie?.Message ?? e.Message); + this.LogVerbose(ie ?? (Exception)e, "Exception details: "); } disconnectLogged = true; From 44e5ff4c3e04b343c856cb4537048ba2cc4c4f47 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Thu, 20 Aug 2026 10:47:30 -0600 Subject: [PATCH 17/19] fix: Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/PepperDash.Core/Comm/GenericSshClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PepperDash.Core/Comm/GenericSshClient.cs b/src/PepperDash.Core/Comm/GenericSshClient.cs index 16406978..5a83fcad 100644 --- a/src/PepperDash.Core/Comm/GenericSshClient.cs +++ b/src/PepperDash.Core/Comm/GenericSshClient.cs @@ -303,7 +303,7 @@ namespace PepperDash.Core else { this.LogError("Error on connect: {error}", ie?.Message ?? e.Message); - this.LogVerbose(ie ?? (Exception)e, "Exception details: "); + this.LogVerbose(ie ?? e, "Exception details: "); } disconnectLogged = true; From a55e4811b3e64d43015d00d9e9d0cc8461844076 Mon Sep 17 00:00:00 2001 From: Robert Sanders Date: Thu, 20 Aug 2026 16:18:04 -0400 Subject: [PATCH 18/19] fix(core-ssh): stop reconnect loop during stopprog --- src/PepperDash.Core/Comm/GenericSshClient.cs | 35 ++++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/PepperDash.Core/Comm/GenericSshClient.cs b/src/PepperDash.Core/Comm/GenericSshClient.cs index 5a83fcad..a758378f 100644 --- a/src/PepperDash.Core/Comm/GenericSshClient.cs +++ b/src/PepperDash.Core/Comm/GenericSshClient.cs @@ -151,6 +151,8 @@ namespace PepperDash.Core // Thread-safety lock for state changes private readonly object _stateLock = new object(); + private volatile bool _isProgramStopping; + private bool disconnectLogged = false; /// @@ -207,11 +209,9 @@ namespace PepperDash.Core { if (programEventType == eProgramStatusEventType.Stopping) { - if (client != null) - { - this.LogDebug("Program stopping. Closing connection"); - Disconnect(); - } + _isProgramStopping = true; + this.LogDebug("Program stopping. Closing connection"); + Disconnect(); } } @@ -230,6 +230,13 @@ namespace PepperDash.Core ConnectEnabled = true; + if (_isProgramStopping) + { + this.LogDebug("Skipping connect because program is stopping"); + ConnectEnabled = false; + return; + } + try { connectLock.Wait(); @@ -308,7 +315,7 @@ namespace PepperDash.Core disconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect) + if (AutoReconnect && ConnectEnabled && !_isProgramStopping) { this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs); StartReconnectTimer(); @@ -320,7 +327,7 @@ namespace PepperDash.Core disconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect) + if (AutoReconnect && ConnectEnabled && !_isProgramStopping) { this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); StartReconnectTimer(); @@ -332,7 +339,7 @@ namespace PepperDash.Core this.LogVerbose(e, "Exception details: "); disconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect) + if (AutoReconnect && ConnectEnabled && !_isProgramStopping) { this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); StartReconnectTimer(); @@ -467,7 +474,7 @@ namespace PepperDash.Core { connectLock.Release(); } - if (AutoReconnect && ConnectEnabled) + if (AutoReconnect && ConnectEnabled && !_isProgramStopping) { this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); StartReconnectTimer(); @@ -510,7 +517,10 @@ namespace PepperDash.Core this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - StartReconnectTimer(); + if (ConnectEnabled && !_isProgramStopping) + { + StartReconnectTimer(); + } } catch (Exception ex) { @@ -543,7 +553,10 @@ namespace PepperDash.Core this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes)); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - StartReconnectTimer(); + if (ConnectEnabled && !_isProgramStopping) + { + StartReconnectTimer(); + } } catch (Exception ex) { From 4ca59e3186c6404ec47f75c6c78c6916efb5dfc9 Mon Sep 17 00:00:00 2001 From: Robert Sanders Date: Thu, 20 Aug 2026 16:39:20 -0400 Subject: [PATCH 19/19] fix(core-ssh): harden GenericSshClient shutdown reconnect guards --- src/PepperDash.Core/Comm/GenericSshClient.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/PepperDash.Core/Comm/GenericSshClient.cs b/src/PepperDash.Core/Comm/GenericSshClient.cs index a758378f..546a2a67 100644 --- a/src/PepperDash.Core/Comm/GenericSshClient.cs +++ b/src/PepperDash.Core/Comm/GenericSshClient.cs @@ -228,15 +228,14 @@ namespace PepperDash.Core return; } - ConnectEnabled = true; - if (_isProgramStopping) { this.LogDebug("Skipping connect because program is stopping"); - ConnectEnabled = false; return; } + ConnectEnabled = true; + try { connectLock.Wait(); @@ -517,7 +516,7 @@ namespace PepperDash.Core this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (ConnectEnabled && !_isProgramStopping) + if (AutoReconnect && ConnectEnabled && !_isProgramStopping) { StartReconnectTimer(); } @@ -553,7 +552,7 @@ namespace PepperDash.Core this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes)); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (ConnectEnabled && !_isProgramStopping) + if (AutoReconnect && ConnectEnabled && !_isProgramStopping) { StartReconnectTimer(); }