diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControl.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControl.cs index c30edebd..1a7fab25 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControl.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControl.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.ObjectModel; +using Crestron.SimplSharpPro; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PepperDash.Core; @@ -33,11 +35,11 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces string SystemUuid { get; } - BoolFeedback ApiOnlineAndAuthorized { get;} + BoolFeedback ApiOnlineAndAuthorized { get; } void SendMessageObject(IMobileControlMessage o); - void AddAction(T messenger, Action action) where T:IMobileControlMessenger; + void AddAction(T messenger, Action action) where T : IMobileControlMessenger; void RemoveAction(string key); @@ -45,14 +47,14 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces bool CheckForDeviceMessenger(string key); - IMobileControlRoomMessenger GetRoomMessenger(string key); + IMobileControlRoomMessenger GetRoomMessenger(string key); - } + } /// /// Describes a mobile control messenger /// - public interface IMobileControlMessenger: IKeyed + public interface IMobileControlMessenger : IKeyed { IMobileControl AppServerController { get; } string MessagePath { get; } @@ -104,9 +106,9 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces public interface IMobileControlAction { - IMobileControlMessenger Messenger { get; } + IMobileControlMessenger Messenger { get; } - Action Action { get; } + Action Action { get; } } public interface IMobileControlTouchpanelController : IKeyed @@ -115,5 +117,7 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces void SetAppUrl(string url); bool UseDirectServer { get; } bool ZoomRoomController { get; } + + ReadOnlyCollection ConnectedIps { get; } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs index 9acd04f4..24f5f8e5 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs @@ -1,4 +1,8 @@ -using Crestron.SimplSharpPro; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.UI; using Newtonsoft.Json; @@ -10,21 +14,14 @@ using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.DeviceInfo; using PepperDash.Essentials.Core.DeviceTypeInterfaces; using PepperDash.Essentials.Core.UI; -using System; -using System.Collections.Generic; -using System.Linq; using Feedback = PepperDash.Essentials.Core.Feedback; namespace PepperDash.Essentials.Touchpanel { - //public interface IMobileControlTouchpanelController - //{ - // StringFeedback AppUrlFeedback { get; } - // string DefaultRoomKey { get; } - // string DeviceKey { get; } - //} - - + /// + /// 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, IMobileControlTouchpanelController, ITheme { private readonly MobileControlTouchpanelProperties localConfig; @@ -32,42 +29,90 @@ namespace PepperDash.Essentials.Touchpanel private string _appUrl; + /// + /// Gets feedback for the current application URL. + /// public StringFeedback AppUrlFeedback { get; private set; } + private readonly StringFeedback QrCodeUrlFeedback; private readonly StringFeedback McServerUrlFeedback; private readonly StringFeedback UserCodeFeedback; private readonly BoolFeedback _appOpenFeedback; + /// + /// Gets feedback indicating whether an application is currently open on the touchpanel. + /// public BoolFeedback AppOpenFeedback => _appOpenFeedback; private readonly BoolFeedback _zoomIncomingCallFeedback; + /// + /// Gets feedback indicating whether there is an incoming Zoom call. + /// public BoolFeedback ZoomIncomingCallFeedback => _zoomIncomingCallFeedback; private readonly BoolFeedback _zoomInCallFeedback; + /// + /// Event that is raised when device information changes. + /// public event DeviceInfoChangeHandler DeviceInfoChanged; + /// + /// Gets feedback indicating whether a Zoom call is currently active. + /// public BoolFeedback ZoomInCallFeedback => _zoomInCallFeedback; - + /// + /// Gets the collection of feedback objects for this touchpanel controller. + /// public FeedbackCollection Feedbacks { get; private set; } + /// + /// Gets the collection of Zoom-related feedback objects. + /// public FeedbackCollection ZoomFeedbacks { get; private set; } + /// + /// Gets the default room key for this touchpanel controller. + /// public string DefaultRoomKey => _config.DefaultRoomKey; + /// + /// Gets a value indicating whether to use direct server communication. + /// public bool UseDirectServer => localConfig.UseDirectServer; + /// + /// Gets a value indicating whether this touchpanel acts as a Zoom Room controller. + /// public bool ZoomRoomController => localConfig.ZoomRoomController; + /// + /// Gets the current theme for the touchpanel interface. + /// public string Theme => localConfig.Theme; + /// + /// Gets feedback for the current theme setting. + /// public StringFeedback ThemeFeedback { get; private set; } + /// + /// Gets device information including MAC address and IP address. + /// public DeviceInfo DeviceInfo => new DeviceInfo(); + public ReadOnlyCollection ConnectedIps => Panel.ConnectedIpList; + + /// + /// Initializes a new instance of the MobileControlTouchpanelController class. + /// + /// The unique key identifier for this touchpanel controller. + /// The friendly name for this touchpanel controller. + /// The touchpanel hardware device. + /// The configuration properties for this controller. public MobileControlTouchpanelController(string key, string name, BasicTriListWithSmartObject panel, MobileControlTouchpanelProperties config) : base(key, name, panel, config) { localConfig = config; @@ -139,6 +184,10 @@ namespace PepperDash.Essentials.Touchpanel RegisterForExtenders(); } + /// + /// Updates the theme setting for this touchpanel controller and persists the change to configuration. + /// + /// The new theme identifier to apply. public void UpdateTheme(string theme) { localConfig.Theme = theme; @@ -271,6 +320,11 @@ namespace PepperDash.Essentials.Touchpanel } } + /// + /// Performs custom activation setup for the touchpanel controller, including + /// registering messengers and linking to mobile control. + /// + /// True if activation was successful; otherwise, false. public override bool CustomActivate() { var appMessenger = new ITswAppControlMessenger($"appControlMessenger-{Key}", $"/device/{Key}", this); @@ -300,12 +354,20 @@ namespace PepperDash.Essentials.Touchpanel return base.CustomActivate(); } - + /// + /// Handles device extender signal changes for system reserved signals. + /// + /// The device extender that generated the signal change. + /// The signal event arguments containing the changed signal information. protected override void ExtenderSystemReservedSigs_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args) { Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, this, $"System Device Extender args: ${args.Event}:${args.Sig}"); } + /// + /// Sets up the panel drivers and signal mappings for the specified room. + /// + /// The room key to configure the panel drivers for. protected override void SetupPanelDrivers(string roomKey) { AppUrlFeedback.LinkInputSig(Panel.StringInput[1]); @@ -366,6 +428,10 @@ namespace PepperDash.Essentials.Touchpanel SetAppUrl(_bridge.AppUrl); } + /// + /// Sets the application URL and updates the corresponding feedback. + /// + /// The new application URL to set. public void SetAppUrl(string url) { _appUrl = url; @@ -391,6 +457,9 @@ namespace PepperDash.Essentials.Touchpanel } } + /// + /// Hides the currently open application on the touchpanel. + /// public void HideOpenApp() { if (Panel is TswX70Base x70Panel) @@ -406,6 +475,9 @@ namespace PepperDash.Essentials.Touchpanel } } + /// + /// Opens an application on the touchpanel. Note: X60 panels do not support Zoom app opening. + /// public void OpenApp() { if (Panel is TswX70Base x70Panel) @@ -421,6 +493,9 @@ namespace PepperDash.Essentials.Touchpanel } } + /// + /// Closes the currently open application on the touchpanel. + /// public void CloseOpenApp() { if (Panel is TswX70Base x70Panel) @@ -436,6 +511,9 @@ namespace PepperDash.Essentials.Touchpanel } } + /// + /// Ends the current Zoom call on the touchpanel. + /// public void EndZoomCall() { if (Panel is TswX70Base x70Panel) @@ -451,6 +529,10 @@ namespace PepperDash.Essentials.Touchpanel } } + /// + /// Updates the device information (MAC address and IP address) from the touchpanel + /// and raises the DeviceInfoChanged event. + /// public void UpdateDeviceInfo() { if (Panel is TswXX70Base x70Panel) @@ -487,14 +569,27 @@ namespace PepperDash.Essentials.Touchpanel } } + /// + /// Factory class for creating MobileControlTouchpanelController instances from device configuration. + /// Supports various Crestron touchpanel models including TSW, TS, CrestronApp, XPanel, and DGE series. + /// public class MobileControlTouchpanelControllerFactory : EssentialsPluginDeviceFactory { + /// + /// Initializes a new instance of the MobileControlTouchpanelControllerFactory class. + /// Sets up supported device type names and minimum framework version requirements. + /// public MobileControlTouchpanelControllerFactory() { TypeNames = new List() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mcxpanel", "mcdge1000" }; MinimumEssentialsFrameworkVersion = "2.0.0"; } + /// + /// Builds a MobileControlTouchpanelController device from the provided device configuration. + /// + /// The device configuration containing the device properties and settings. + /// A configured MobileControlTouchpanelController instance. public override EssentialsDevice BuildDevice(DeviceConfig dc) { var comm = CommFactory.GetControlPropertiesConfig(dc); @@ -557,7 +652,7 @@ namespace PepperDash.Essentials.Touchpanel return new Ts1070(id, Global.ControlSystem); else if (type == "dge1000") return new Dge1000(id, Global.ControlSystem); - else + else { Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "WARNING: Cannot create TSW controller with type '{0}'", type); diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs index 859a7c7b..846a674a 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs @@ -1,4 +1,10 @@ -using Crestron.SimplSharp; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using Crestron.SimplSharp; using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; using PepperDash.Core; @@ -9,12 +15,6 @@ using PepperDash.Essentials.Core.Web; using PepperDash.Essentials.RoomBridges; using PepperDash.Essentials.WebApiHandlers; using Serilog.Events; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Text; using WebSocketSharp; using WebSocketSharp.Net; using WebSocketSharp.Server; @@ -60,7 +60,7 @@ namespace PepperDash.Essentials.WebSocketServer private string lanIpAddress => CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter)); - private System.Net.IPAddress csIpAddress; + private System.Net.IPAddress csIpAddress; private System.Net.IPAddress csSubnetMask; @@ -122,7 +122,7 @@ namespace PepperDash.Essentials.WebSocketServer _parent = parent; // Set the default port to be 50000 plus the slot number of the program - Port = 50000 + (int)Global.ControlSystem.ProgramNumber; + Port = 50000 + (int)Global.ControlSystem.ProgramNumber; if (customPort != 0) { @@ -156,9 +156,9 @@ namespace PepperDash.Essentials.WebSocketServer } try - { + { var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter); - var csSubnetMask = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, csAdapterId); + 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); @@ -298,8 +298,6 @@ namespace PepperDash.Essentials.WebSocketServer var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, lanAdapterId); - this.LogVerbose("Processor IP: {processorIp}", processorIp); - foreach (var touchpanel in touchpanels.Select(tp => { var token = _secret.Tokens.FirstOrDefault((t) => t.Value.TouchpanelKey.Equals(tp.Key, StringComparison.InvariantCultureIgnoreCase)); @@ -321,11 +319,13 @@ namespace PepperDash.Essentials.WebSocketServer continue; } - var appUrl = $"http://{processorIp}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"; + var ip = touchpanel.Touchpanel.ConnectedIps.Any(ipInfo => csIpAddress.IsInSameSubnet(System.Net.IPAddress.Parse(ipInfo.DeviceIpAddress), csSubnetMask)) ? csIpAddress.ToString() : processorIp; + + var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"; this.LogVerbose("Sending URL {appUrl}", appUrl); - touchpanel.Messenger.UpdateAppUrl($"http://{processorIp}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"); + touchpanel.Messenger.UpdateAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"); } } @@ -349,7 +349,7 @@ namespace PepperDash.Essentials.WebSocketServer if (!Directory.Exists($"{userAppPath}{localConfigFolderName}")) { Directory.CreateDirectory($"{userAppPath}{localConfigFolderName}"); - } + } using (var sw = new StreamWriter(File.Open($"{userAppPath}{localConfigFolderName}{Global.DirectorySeparator}{appConfigFileName}", FileMode.Create, FileAccess.ReadWrite))) { @@ -358,7 +358,7 @@ namespace PepperDash.Essentials.WebSocketServer this.LogDebug("LAN Adapter ID: {lanAdapterId}", lanAdapterId); - var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, lanAdapterId); + var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, lanAdapterId); var config = GetApplicationConfig(processorIp); @@ -378,7 +378,7 @@ namespace PepperDash.Essentials.WebSocketServer return; } - if(csAdapterId == -1) + if (csAdapterId == -1) { this.LogDebug("CS LAN Adapter not found"); return; @@ -389,8 +389,8 @@ namespace PepperDash.Essentials.WebSocketServer using (var sw = new StreamWriter(File.Open($"{userAppPath}{localConfigFolderName}{Global.DirectorySeparator}{appConfigCsFileName}", FileMode.Create, FileAccess.ReadWrite))) { // Write the CS application configuration file. Used when a request comes in for the application config from the CS - var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); - + var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); + var config = GetApplicationConfig(processorIp); var contents = JsonConvert.SerializeObject(config, Formatting.Indented); @@ -400,7 +400,7 @@ namespace PepperDash.Essentials.WebSocketServer } private MobileControlApplicationConfig GetApplicationConfig(string processorIp) - { + { try { var config = new MobileControlApplicationConfig @@ -430,10 +430,10 @@ namespace PepperDash.Essentials.WebSocketServer } catch (Exception ex) { - this.LogError(ex, "Error getting application configuration"); + this.LogError(ex, "Error getting application configuration"); return null; - } + } } /// @@ -572,7 +572,7 @@ namespace PepperDash.Essentials.WebSocketServer var values = s.Split(' '); - if(values.Length < 2) + if (values.Length < 2) { CrestronConsole.ConsoleCommandResponse("Invalid number of arguments. Please provide a room key and a grant code"); return;