From 8cf195b262816cec40d4d7aef239c3d5e8cf8ad0 Mon Sep 17 00:00:00 2001 From: aknous Date: Tue, 19 Aug 2025 11:37:39 -0400 Subject: [PATCH 1/4] feat: adds IBasicVideoMuteWithFeedbackMessenger --- .../IBasicVideoMuteWithFeedbackMessenger.cs | 77 +++++++++++++++++++ .../MobileControlSystemController.cs | 19 +++++ 2 files changed, 96 insertions(+) create mode 100644 src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IBasicVideoMuteWithFeedbackMessenger.cs diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IBasicVideoMuteWithFeedbackMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IBasicVideoMuteWithFeedbackMessenger.cs new file mode 100644 index 00000000..70d59aa4 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IBasicVideoMuteWithFeedbackMessenger.cs @@ -0,0 +1,77 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core; +using PepperDash.Essentials.Core; +using System.Collections.Generic; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + /// + /// Represents a IBasicVideoMuteWithFeedbackMessenger + /// + public class IBasicVideoMuteWithFeedbackMessenger : MessengerBase + { + private readonly IBasicVideoMuteWithFeedback device; + + public IBasicVideoMuteWithFeedbackMessenger(string key, string messagePath, IBasicVideoMuteWithFeedback device) + : base(key, messagePath, device as IKeyName) + { + this.device = device; + } + + /// + /// SendFullStatus method + /// + public void SendFullStatus() + { + var messageObj = new IBasicVideoMuteWithFeedbackMessage + { + VideoMuteState = device.VideoMuteIsOn.BoolValue + }; + + PostStatusMessage(messageObj); + } + + protected override void RegisterActions() + { + base.RegisterActions(); + + AddAction("/fullStatus", (id, content) => SendFullStatus()); + + AddAction("/videoMuteToggle", (id, content) => + { + device.VideoMuteToggle(); + }); + + AddAction("/videoMuteOn", (id, content) => + { + device.VideoMuteOn(); + }); + + AddAction("/videoMuteOff", (id, content) => + { + device.VideoMuteOff(); + }); + + device.VideoMuteIsOn.OutputChange += VideoMuteIsOnFeedback_OutputChange; + } + + private void VideoMuteIsOnFeedback_OutputChange(object sender, FeedbackEventArgs args) + { + PostStatusMessage(JToken.FromObject(new + { + videoMuteState = args.BoolValue + }) + ); + } + } + + /// + /// Represents a IBasicVideoMuteWithFeedbackMessage + /// + public class IBasicVideoMuteWithFeedbackMessage : DeviceStateMessageBase + { + [JsonProperty("videoMuteState")] + public bool VideoMuteState { get; set; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs index f5854516..49e71010 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs @@ -505,6 +505,25 @@ namespace PepperDash.Essentials messengerAdded = true; } + if (device is IBasicVideoMuteWithFeedback) + { + var deviceKey = device.Key; + this.LogVerbose( + "Adding IBasicVideoMuteWithFeedback for {deviceKey}", + deviceKey + ); + + var videoMuteControlDevice = device as IBasicVideoMuteWithFeedback; + var messenger = new IBasicVideoMuteWithFeedbackMessenger( + $"{device.Key}-videoMute-{Key}", + string.Format("/device/{0}", deviceKey), + videoMuteControlDevice + ); + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + if (device is ILightingScenes || device is LightingBase) { var deviceKey = device.Key; From fe33443b259d99465db7a92b6abf2b7cbfdcc500 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Tue, 19 Aug 2025 13:28:45 -0600 Subject: [PATCH 2/4] fix: Update IP handling in MobileControlTouchpanelController Updates logic to handle setting the URL sent to the CH5 wrapper app to use the CS LAN IP based on the actual IpInformationChange event on the panel itself. - Added `._PepperDash.Essentials.4Series.sln` to .gitignore. - Introduced new using directives for Regex and Crestron libraries. - Added `csIpAddress` and `csSubnetMask` fields to store device IP info. - Modified constructor to retrieve and assign current IP and subnet mask. - Updated `Panel.IpInformationChange` event handler for logging and URL setting. - Created `GetUrlWithCorrectIp` method to determine the correct URL based on IP. - Refactored `SetAppUrl` to utilize the new URL method. - Commented out old IP determination logic in `MobileControlWebsocketServer.cs` as it was moved to the touchpanel controller. --- .gitignore | 1 + .../MobileControlTouchpanelController.cs | 85 ++++++++++++++++++- .../MobileControlWebsocketServer.cs | 28 +++--- 3 files changed, 98 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index bd60ff8d..db1e92a3 100644 --- a/.gitignore +++ b/.gitignore @@ -395,3 +395,4 @@ essentials-framework/Essentials Interfaces/PepperDash_Essentials_Interfaces/Pepp _site/ api/ *.DS_Store +/._PepperDash.Essentials.4Series.sln diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs index ab059244..a4476cd8 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Text.RegularExpressions; +using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.UI; @@ -106,6 +108,11 @@ namespace PepperDash.Essentials.Touchpanel public ReadOnlyCollection ConnectedIps => Panel.ConnectedIpList; + private System.Net.IPAddress csIpAddress; + + private System.Net.IPAddress csSubnetMask; + + /// /// Initializes a new instance of the MobileControlTouchpanelController class. /// @@ -182,6 +189,13 @@ namespace PepperDash.Essentials.Touchpanel }; RegisterForExtenders(); + + var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter); + var csSubnetMask = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, csAdapterId); + var csIpAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); + + this.csSubnetMask = System.Net.IPAddress.Parse(csSubnetMask); + this.csIpAddress = System.Net.IPAddress.Parse(csIpAddress); } /// @@ -381,19 +395,81 @@ namespace PepperDash.Essentials.Touchpanel McServerUrlFeedback.LinkInputSig(Panel.StringInput[3]); UserCodeFeedback.LinkInputSig(Panel.StringInput[4]); + Panel.IpInformationChange += (sender, args) => + { + if (args.Connected) + { + this.LogVerbose("Connection from IP: {ip}", args.DeviceIpAddress); + this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue); + + var appUrl = GetUrlWithCorrectIp(_appUrl); + Panel.StringInput[1].StringValue = appUrl; + + SetAppUrl(appUrl); + } + else + { + this.LogVerbose("Disconnection from IP: {ip}", args.DeviceIpAddress); + } + }; + Panel.OnlineStatusChange += (sender, args) => { - UpdateFeedbacks(); - this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue); - Panel.StringInput[1].StringValue = AppUrlFeedback.StringValue; + UpdateFeedbacks(); + Panel.StringInput[1].StringValue = _appUrl; Panel.StringInput[2].StringValue = QrCodeUrlFeedback.StringValue; Panel.StringInput[3].StringValue = McServerUrlFeedback.StringValue; Panel.StringInput[4].StringValue = UserCodeFeedback.StringValue; }; } + /// + /// Gets the URL with the correct IP address based on the connected devices and the Crestron processor's IP address. + /// + /// + /// + private string GetUrlWithCorrectIp(string url) + { + var lanAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter); + + var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, lanAdapterId); + + if(csIpAddress == null || csSubnetMask == null || url == null) + { + this.LogWarning("CS IP Address Subnet Mask or url is null, cannot determine correct IP for URL"); + return url; + } + + this.LogVerbose("Processor IP: {processorIp}, CS IP: {csIpAddress}, CS Subnet Mask: {csSubnetMask}", processorIp, csIpAddress, csSubnetMask); + this.LogVerbose("Connected IP Count: {connectedIps}", ConnectedIps.Count); + + var ip = ConnectedIps.Any(ipInfo => + { + if (System.Net.IPAddress.TryParse(ipInfo.DeviceIpAddress, out var parsedIp)) + { + return csIpAddress.IsInSameSubnet(parsedIp, csSubnetMask); + } + this.LogWarning("Invalid IP address: {deviceIpAddress}", ipInfo.DeviceIpAddress); + return false; + }) ? csIpAddress.ToString() : processorIp; + + var match = Regex.Match(url, @"^http://([^:/]+):\d+/mc/app\?token=.+$"); + if (match.Success) + { + string ipa = match.Groups[1].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}"); + + this.LogVerbose("Updated URL: {updatedUrl}", updatedUrl); + + return updatedUrl; + } + private void SubscribeForMobileControlUpdates() { foreach (var dev in DeviceManager.AllDevices) @@ -443,7 +519,8 @@ namespace PepperDash.Essentials.Touchpanel /// public void SetAppUrl(string url) { - _appUrl = url; + _appUrl = GetUrlWithCorrectIp(url); + AppUrlFeedback.FireUpdate(); } diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs index 0ebb0707..8fcfd11f 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs @@ -327,18 +327,22 @@ namespace PepperDash.Essentials.WebSocketServer } string ip = processorIp; - if (touchpanel.Touchpanel is IMobileControlCrestronTouchpanelController crestronTouchpanel && csIpAddress != null) - { - ip = crestronTouchpanel.ConnectedIps.Any(ipInfo => - { - if (System.Net.IPAddress.TryParse(ipInfo.DeviceIpAddress, out var parsedIp)) - { - return csIpAddress.IsInSameSubnet(parsedIp, csSubnetMask); - } - this.LogWarning("Invalid IP address: {deviceIpAddress}", ipInfo.DeviceIpAddress); - return false; - }) ? csIpAddress.ToString() : processorIp; - } + + // Moved to the MobileControlTouchpanelController class in the GetUrlWithCorrectIp method + // triggered by the Panel.IpInformationChange event so that we know we have the necessary info + // to make the determination of which IP to use. + //if (touchpanel.Touchpanel is IMobileControlCrestronTouchpanelController crestronTouchpanel && csIpAddress != null) + //{ + // ip = crestronTouchpanel.ConnectedIps.Any(ipInfo => + // { + // if (System.Net.IPAddress.TryParse(ipInfo.DeviceIpAddress, out var parsedIp)) + // { + // return csIpAddress.IsInSameSubnet(parsedIp, csSubnetMask); + // } + // this.LogWarning("Invalid IP address: {deviceIpAddress}", ipInfo.DeviceIpAddress); + // return false; + // }) ? csIpAddress.ToString() : processorIp; + //} var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"; From 6830efe42a4c4d36f8f22eb895e632a8291013ad Mon Sep 17 00:00:00 2001 From: aknous Date: Tue, 19 Aug 2025 18:34:16 -0400 Subject: [PATCH 3/4] fix: fixes CameraBaseMessenger hold timer for PTZ controls, adds storePreset messenger --- .../Messengers/CameraBaseMessenger.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs index e2e16b3e..7c4763c1 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs @@ -140,17 +140,19 @@ namespace PepperDash.Essentials.AppServer.Messengers if (Camera is IHasCameraPresets presetsCamera) { - for (int i = 1; i <= 6; i++) + AddAction("/recallPreset", (id, content) => { - var preset = i; - AddAction("/cameraPreset" + i, (id, content) => - { - var msg = content.ToObject>(); + var msg = content.ToObject>(); - presetsCamera.PresetSelect(msg.Value); - }); + presetsCamera.PresetSelect(msg.Value); + }); - } + AddAction("/storePreset", (id, content) => + { + var msg = content.ToObject>(); + + presetsCamera.PresetStore(msg.Value, string.Empty); + }); } } @@ -164,9 +166,9 @@ namespace PepperDash.Essentials.AppServer.Messengers return; } - timerHandler(state.Value, cameraAction); + timerHandler(Camera.Key, cameraAction); - cameraAction(state.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase)); + //cameraAction(state.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase)); } /// From 49abec5eeaec9a073818b925510ed5aa7fb1e9b7 Mon Sep 17 00:00:00 2001 From: aknous <61518011+aknous@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:23:43 -0400 Subject: [PATCH 4/4] Update src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Messengers/CameraBaseMessenger.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs index 7c4763c1..cb3f77a5 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs @@ -168,7 +168,6 @@ namespace PepperDash.Essentials.AppServer.Messengers timerHandler(Camera.Key, cameraAction); - //cameraAction(state.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase)); } ///