Compare commits

..

7 Commits

Author SHA1 Message Date
Andrew Welker
d33fd56529 feat: add method to force panel reload
Other refactorings for factory method and log statements
2025-09-25 14:47:11 -05:00
Neil Dorin
8fc4d21f02 fix: Add DeviceInterfaceSupport property to JoinResponse
This commit introduces a new property, `DeviceInterfaceSupport`,
to the `JoinResponse` class in the `PepperDash.Essentials.WebSocketServer`
namespace. This property is a dictionary that maps strings to
`DeviceInterfaceInfo` objects, enhancing support for device interfaces.
A summary comment has also been added for clarity.
2025-09-25 11:50:42 -06:00
Andrew Welker
fd70377c7f fix: log errors and disconnects for UI Clients 2025-09-25 08:51:11 -05:00
Neil Dorin
06341b14f3 feat: Adds device interface support info to joinroom response in MC websocket server.
Enhance MessengerBase and WebSocketServer functionality

Updated MessengerBase with new methods for action management and message posting, along with improved documentation. Introduced DeviceMessageBase for better message representation.

Enhanced MobileControlWebsocketServer to support device interfaces, adding DeviceInterfaceSupport to JoinResponse and a new DeviceInterfaceInfo class for detailed device information.
2025-09-24 14:49:41 -06:00
Andrew Welker
b0a090062f Merge branch 'hotfix/check-for-cs-lan-on-mctp' into mc-messenger-improvements 2025-09-23 11:25:41 -05:00
Neil Dorin
258699fbcd fix: Enhance AppUrl change logging
Updated logging to include the new AppUrl value when it changes. This provides better context in the logs, making it easier to track the specific URL that was set.
2025-09-18 18:23:12 -06:00
Neil Dorin
738504e9fc fix: Add error handling for network parameter retrieval
Introduced a try-catch block to handle exceptions when fetching the Crestron Ethernet adapter's ID, subnet mask, and IP address. Added logging for cases where the processor lacks a CS LAN. Also included a new using directive for Serilog.Events.
2025-09-18 14:48:21 -06:00
10 changed files with 406 additions and 280 deletions

View File

@@ -15,12 +15,18 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// </summary>
public abstract class MessengerBase : EssentialsDevice, IMobileControlMessenger
{
/// <summary>
/// The device this messenger is associated with
/// </summary>
protected IKeyName _device;
private readonly List<string> _deviceInterfaces;
private readonly Dictionary<string, Action<string, JToken>> _actions = new Dictionary<string, Action<string, JToken>>();
/// <summary>
/// Gets the DeviceKey
/// </summary>
public string DeviceKey => _device?.Key ?? "";
@@ -50,6 +56,12 @@ namespace PepperDash.Essentials.AppServer.Messengers
MessagePath = messagePath;
}
/// <summary>
/// Constructor for a messenger associated with a device
/// </summary>
/// <param name="key"></param>
/// <param name="messagePath"></param>
/// <param name="device"></param>
protected MessengerBase(string key, string messagePath, IKeyName device)
: this(key, messagePath)
{
@@ -96,6 +108,11 @@ namespace PepperDash.Essentials.AppServer.Messengers
action(id, content);
}
/// <summary>
/// Adds an action for a given path
/// </summary>
/// <param name="path"></param>
/// <param name="action"></param>
protected void AddAction(string path, Action<string, JToken> action)
{
if (_actions.ContainsKey(path))
@@ -115,6 +132,10 @@ namespace PepperDash.Essentials.AppServer.Messengers
return _actions.Keys.ToList();
}
/// <summary>
/// Removes an action for a given path
/// </summary>
/// <param name="path"></param>
protected void RemoveAction(string path)
{
if (!_actions.ContainsKey(path))
@@ -128,7 +149,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// <summary>
/// Implemented in extending classes. Wire up API calls and feedback here
/// </summary>
/// <param name="appServerController"></param>
protected virtual void RegisterActions()
{
@@ -137,8 +157,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// <summary>
/// Helper for posting status message
/// </summary>
/// <param name="type"></param>
/// <param name="message"></param>
/// <param name="clientId">Optional client id that will direct the message back to only that client</param>
protected void PostStatusMessage(DeviceStateMessageBase message, string clientId = null)
{
try
@@ -169,6 +189,12 @@ namespace PepperDash.Essentials.AppServer.Messengers
}
}
/// <summary>
/// Helper for posting status message
/// </summary>
/// <param name="type"></param>
/// <param name="deviceState"></param>
/// <param name="clientId">Optional client id that will direct the message back to only that client</param>
protected void PostStatusMessage(string type, DeviceStateMessageBase deviceState, string clientId = null)
{
try
@@ -192,6 +218,12 @@ namespace PepperDash.Essentials.AppServer.Messengers
}
}
/// <summary>
/// Helper for posting status message
/// </summary>
/// <param name="content"></param>
/// <param name="type"></param>
/// <param name="clientId">Optional client id that will direct the message back to only that client</param>
protected void PostStatusMessage(JToken content, string type = "", string clientId = null)
{
try
@@ -204,6 +236,10 @@ namespace PepperDash.Essentials.AppServer.Messengers
}
}
/// <summary>
/// Helper for posting event message
/// </summary>
/// <param name="message"></param>
protected void PostEventMessage(DeviceEventMessageBase message)
{
message.Key = _device.Key;
@@ -217,6 +253,11 @@ namespace PepperDash.Essentials.AppServer.Messengers
});
}
/// <summary>
/// Helper for posting event message
/// </summary>
/// <param name="message"></param>
/// <param name="eventType"></param>
protected void PostEventMessage(DeviceEventMessageBase message, string eventType)
{
message.Key = _device.Key;
@@ -232,6 +273,10 @@ namespace PepperDash.Essentials.AppServer.Messengers
});
}
/// <summary>
/// Helper for posting event message with no content
/// </summary>
/// <param name="eventType"></param>
protected void PostEventMessage(string eventType)
{
AppServerController?.SendMessageObject(new MobileControlMessage
@@ -243,6 +288,9 @@ namespace PepperDash.Essentials.AppServer.Messengers
}
/// <summary>
/// Base class for device messages that include the type of message
/// </summary>
public abstract class DeviceMessageBase
{
/// <summary>
@@ -266,10 +314,11 @@ namespace PepperDash.Essentials.AppServer.Messengers
[JsonProperty("messageType")]
public string MessageType => GetType().Name;
[JsonProperty("messageBasePath")]
/// <summary>
/// Gets or sets the MessageBasePath
/// </summary>
[JsonProperty("messageBasePath")]
public string MessageBasePath { get; set; }
}
@@ -284,6 +333,10 @@ namespace PepperDash.Essentials.AppServer.Messengers
[JsonProperty("interfaces")]
public List<string> Interfaces { get; private set; }
/// <summary>
/// Sets the interfaces implemented by the device sending the message
/// </summary>
/// <param name="interfaces"></param>
public void SetInterfaces(List<string> interfaces)
{
Interfaces = interfaces;

View File

@@ -17,6 +17,7 @@ using PepperDash.Essentials.Core.DeviceInfo;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Core.UI;
using Feedback = PepperDash.Essentials.Core.Feedback;
using IPAddress = System.Net.IPAddress;
namespace PepperDash.Essentials.Touchpanel
{
@@ -106,11 +107,14 @@ namespace PepperDash.Essentials.Touchpanel
/// </summary>
public DeviceInfo DeviceInfo => new DeviceInfo();
/// <summary>
/// Gets the list of connected IPs for this IpId
/// </summary>
public ReadOnlyCollection<ConnectedIpInformation> ConnectedIps => Panel.ConnectedIpList;
private System.Net.IPAddress csIpAddress;
private readonly IPAddress csIpAddress;
private System.Net.IPAddress csSubnetMask;
private readonly IPAddress csSubnetMask;
/// <summary>
@@ -190,12 +194,19 @@ namespace PepperDash.Essentials.Touchpanel
RegisterForExtenders();
try
{
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);
this.csSubnetMask = IPAddress.Parse(csSubnetMask);
this.csIpAddress = IPAddress.Parse(csIpAddress);
}
catch
{
Debug.LogInformation("This processor does not have a CS LAN", this);
}
}
/// <summary>
@@ -226,7 +237,7 @@ namespace PepperDash.Essentials.Touchpanel
{
x70Panel.ExtenderApplicationControlReservedSigs.DeviceExtenderSigChange += (e, a) =>
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, this, $"X70 App Control Device Extender args: {a.Event}:{a.Sig}:{a.Sig.Type}:{a.Sig.BoolValue}:{a.Sig.UShortValue}:{a.Sig.StringValue}");
this.LogVerbose("X70 App Control Device Extender args: {event}:{sig}:{type}:{boolValue}:{ushortValue}:{stringValue}", a.Event, a.Sig, a.Sig.Type, a.Sig.BoolValue, a.Sig.UShortValue, a.Sig.StringValue);
UpdateZoomFeedbacks();
@@ -245,7 +256,7 @@ namespace PepperDash.Essentials.Touchpanel
x70Panel.ExtenderZoomRoomAppReservedSigs.DeviceExtenderSigChange += (e, a) =>
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, this, $"X70 Zoom Room Ap Device Extender args: {a.Event}:{a.Sig}:{a.Sig.Type}:{a.Sig.BoolValue}:{a.Sig.UShortValue}:{a.Sig.StringValue}");
this.LogVerbose("X70 Zoom Room App Device Extender args: {event}:{sig}:{type}:{boolValue}:{ushortValue}:{stringValue}", a.Event, a.Sig, a.Sig.Type, a.Sig.BoolValue, a.Sig.UShortValue, a.Sig.StringValue);
if (a.Sig.Number == x70Panel.ExtenderZoomRoomAppReservedSigs.ZoomRoomIncomingCallFeedback.Number)
{
@@ -263,7 +274,7 @@ namespace PepperDash.Essentials.Touchpanel
DeviceInfo.MacAddress = x70Panel.ExtenderEthernetReservedSigs.MacAddressFeedback.StringValue;
DeviceInfo.IpAddress = x70Panel.ExtenderEthernetReservedSigs.IpAddressFeedback.StringValue;
Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, this, $"MAC: {DeviceInfo.MacAddress} IP: {DeviceInfo.IpAddress}");
this.LogDebug("MAC: {macAddress} IP: {ipAddress}", DeviceInfo.MacAddress, DeviceInfo.IpAddress);
var handler = DeviceInfoChanged;
@@ -293,7 +304,7 @@ namespace PepperDash.Essentials.Touchpanel
{
x60withZoomApp.ExtenderApplicationControlReservedSigs.DeviceExtenderSigChange += (e, a) =>
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, this, $"X60 App Control Device Extender args: {a.Event}:{a.Sig}:{a.Sig.Type}:{a.Sig.BoolValue}:{a.Sig.UShortValue}:{a.Sig.StringValue}");
this.LogVerbose("X60 App Control Device Extender args: {event}:{sig}:{type}:{boolValue}:{ushortValue}:{stringValue}", a.Event, a.Sig, a.Sig.Type, a.Sig.BoolValue, a.Sig.UShortValue, a.Sig.StringValue);
if (a.Sig.Number == x60withZoomApp.ExtenderApplicationControlReservedSigs.HideOpenApplicationFeedback.Number)
{
@@ -302,7 +313,7 @@ namespace PepperDash.Essentials.Touchpanel
};
x60withZoomApp.ExtenderZoomRoomAppReservedSigs.DeviceExtenderSigChange += (e, a) =>
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, this, $"X60 Zoom Room App Device Extender args: {a.Event}:{a.Sig}:{a.Sig.Type}:{a.Sig.BoolValue}:{a.Sig.UShortValue}:{a.Sig.StringValue}");
this.LogVerbose("X60 Zoom Room App Device Extender args: {event}:{sig}:{type}:{boolValue}:{ushortValue}:{stringValue}", a.Event, a.Sig, a.Sig.Type, a.Sig.BoolValue, a.Sig.UShortValue, a.Sig.StringValue);
if (a.Sig.Number == x60withZoomApp.ExtenderZoomRoomAppReservedSigs.ZoomRoomIncomingCallFeedback.Number)
{
@@ -319,7 +330,7 @@ namespace PepperDash.Essentials.Touchpanel
DeviceInfo.MacAddress = x60withZoomApp.ExtenderEthernetReservedSigs.MacAddressFeedback.StringValue;
DeviceInfo.IpAddress = x60withZoomApp.ExtenderEthernetReservedSigs.IpAddressFeedback.StringValue;
Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, this, $"MAC: {DeviceInfo.MacAddress} IP: {DeviceInfo.IpAddress}");
this.LogDebug("MAC: {macAddress} IP: {ipAddress}", DeviceInfo.MacAddress, DeviceInfo.IpAddress);
var handler = DeviceInfoChanged;
@@ -381,7 +392,7 @@ namespace PepperDash.Essentials.Touchpanel
/// <param name="args">The signal event arguments containing the changed signal information.</param>
protected override void ExtenderSystemReservedSigs_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, this, $"System Device Extender args: ${args.Event}:${args.Sig}");
this.LogVerbose("System Device Extender args: {event}:{sig}", args.Event, args.Sig);
}
/// <summary>
@@ -447,7 +458,7 @@ namespace PepperDash.Essentials.Touchpanel
var ip = ConnectedIps.Any(ipInfo =>
{
if (System.Net.IPAddress.TryParse(ipInfo.DeviceIpAddress, out var parsedIp))
if (IPAddress.TryParse(ipInfo.DeviceIpAddress, out var parsedIp))
{
return csIpAddress.IsInSameSubnet(parsedIp, csSubnetMask);
}
@@ -481,7 +492,7 @@ namespace PepperDash.Essentials.Touchpanel
if (mcList.Count == 0)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, this, $"No Mobile Control controller found");
this.LogError("No Mobile Control controller found");
return;
}
@@ -493,7 +504,7 @@ namespace PepperDash.Essentials.Touchpanel
if (bridge == null)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, this, $"No Mobile Control bridge for {_config.DefaultRoomKey} found ");
this.LogInformation("No Mobile Control bridge for {roomKey} found", _config.DefaultRoomKey);
return;
}
@@ -502,7 +513,7 @@ namespace PepperDash.Essentials.Touchpanel
_bridge.UserCodeChanged += UpdateFeedbacks;
_bridge.AppUrlChanged += (s, a) =>
{
this.LogInformation("AppURL changed");
this.LogInformation("AppURL changed: {appURL}", _bridge.AppUrl);
SetAppUrl(_bridge.AppUrl);
UpdateFeedbacks(s, a);
};
@@ -538,7 +549,7 @@ namespace PepperDash.Essentials.Touchpanel
{
foreach (var feedback in ZoomFeedbacks)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, this, $"Updating {feedback.Key}");
this.LogDebug("Updating {feedbackKey}", feedback.Key);
feedback.FireUpdate();
}
}
@@ -574,7 +585,7 @@ namespace PepperDash.Essentials.Touchpanel
if (Panel is TswX60WithZoomRoomAppReservedSigs)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, this, $"X60 panel does not support zoom app");
this.LogInformation("X60 panel does not support zoom app");
return;
}
}
@@ -650,7 +661,16 @@ namespace PepperDash.Essentials.Touchpanel
handler(this, new DeviceInfoEventArgs(DeviceInfo));
}
Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, this, $"MAC: {DeviceInfo.MacAddress} IP: {DeviceInfo.IpAddress}");
this.LogDebug("MAC: {macAddress} IP: {ipAddress}", DeviceInfo.MacAddress, DeviceInfo.IpAddress);
}
/// <summary>
/// Force a reload of the iframe on the panel connected to this IP ID
/// </summary>
public void ReloadIframe()
{
this.LogInformation("Pulsing join 1");
Panel.PulseBool(1, 100);
}
}
@@ -659,6 +679,8 @@ namespace PepperDash.Essentials.Touchpanel
/// </summary>
public class MobileControlTouchpanelControllerFactory : EssentialsPluginDeviceFactory<MobileControlTouchpanelController>
{
private Dictionary<string, Func<uint, CrestronControlSystem, string, BasicTriListWithSmartObject>> factories;
/// <summary>
/// Initializes a new instance of the MobileControlTouchpanelControllerFactory class.
/// Sets up supported device type names and minimum framework version requirements.
@@ -667,6 +689,31 @@ namespace PepperDash.Essentials.Touchpanel
{
TypeNames = new List<string>() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mcxpanel", "mcdge1000" };
MinimumEssentialsFrameworkVersion = "2.0.0";
factories = new Dictionary<string, Func<uint, CrestronControlSystem, string, BasicTriListWithSmartObject>>
{
{"crestronapp", (id, controlSystem, projectName) => {
var app = new CrestronApp(id, Global.ControlSystem);
app.ParameterProjectName.Value = projectName;
return app;
}},
{"xpanel", (id, controlSystem, projectName) => new XpanelForHtml5(id, controlSystem)},
{"tsw550", (id, controlSystem, projectName) => new Tsw550(id, controlSystem)},
{"tsw552", (id, controlSystem, projectName) => new Tsw552(id, controlSystem)},
{"tsw560", (id, controlSystem, projectName) => new Tsw560(id, controlSystem)},
{"tsw750", (id, controlSystem, projectName) => new Tsw750(id, controlSystem)},
{"tsw752", (id, controlSystem, projectName) => new Tsw752(id, controlSystem)},
{"tsw760", (id, controlSystem, projectName) => new Tsw760(id, controlSystem)},
{"tsw1050", (id, controlSystem, projectName) => new Tsw1050(id, controlSystem)},
{"tsw1052", (id, controlSystem, projectName) => new Tsw1052(id, controlSystem)},
{"tsw1060", (id, controlSystem, projectName) => new Tsw1060(id, controlSystem)},
{"tsw570", (id, controlSystem, projectName) => new Tsw570(id, controlSystem)},
{"tsw770", (id, controlSystem, projectName) => new Tsw770(id, controlSystem)},
{"ts770", (id, controlSystem, projectName) => new Ts770(id, controlSystem)},
{"tsw1070", (id, controlSystem, projectName) => new Tsw1070(id, controlSystem)},
{"ts1070", (id, controlSystem, projectName) => new Ts1070(id, controlSystem)},
{"dge1000", (id, controlSystem, projectName) => new Dge1000(id, controlSystem)}
};
}
/// <summary>
@@ -686,10 +733,10 @@ namespace PepperDash.Essentials.Touchpanel
if (panel == null)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Unable to create Touchpanel for type {0}. Touchpanel Controller WILL NOT function correctly", dc.Type);
Debug.LogError("Unable to create Touchpanel for type {type}. Touchpanel Controller WILL NOT function correctly", dc.Type);
}
Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, "Factory Attempting to create new MobileControlTouchpanelController");
Debug.LogDebug("Factory Attempting to create new MobileControlTouchpanelController");
var panelController = new MobileControlTouchpanelController(dc.Key, dc.Name, panel, props);
@@ -699,56 +746,21 @@ namespace PepperDash.Essentials.Touchpanel
private BasicTriListWithSmartObject GetPanelForType(string type, uint id, string projectName)
{
type = type.ToLower().Replace("mc", "");
try
{
if (type == "crestronapp")
if (!factories.TryGetValue(type, out var buildCrestronHardwareDevice))
{
var app = new CrestronApp(id, Global.ControlSystem);
app.ParameterProjectName.Value = projectName;
return app;
}
else if (type == "xpanel")
return new XpanelForHtml5(id, Global.ControlSystem);
else if (type == "tsw550")
return new Tsw550(id, Global.ControlSystem);
else if (type == "tsw552")
return new Tsw552(id, Global.ControlSystem);
else if (type == "tsw560")
return new Tsw560(id, Global.ControlSystem);
else if (type == "tsw750")
return new Tsw750(id, Global.ControlSystem);
else if (type == "tsw752")
return new Tsw752(id, Global.ControlSystem);
else if (type == "tsw760")
return new Tsw760(id, Global.ControlSystem);
else if (type == "tsw1050")
return new Tsw1050(id, Global.ControlSystem);
else if (type == "tsw1052")
return new Tsw1052(id, Global.ControlSystem);
else if (type == "tsw1060")
return new Tsw1060(id, Global.ControlSystem);
else if (type == "tsw570")
return new Tsw570(id, Global.ControlSystem);
else if (type == "tsw770")
return new Tsw770(id, Global.ControlSystem);
else if (type == "ts770")
return new Ts770(id, Global.ControlSystem);
else if (type == "tsw1070")
return new Tsw1070(id, Global.ControlSystem);
else if (type == "ts1070")
return new Ts1070(id, Global.ControlSystem);
else if (type == "dge1000")
return new Dge1000(id, Global.ControlSystem);
else
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "WARNING: Cannot create TSW controller with type '{0}'", type);
Debug.LogError("Cannot create TSW controller with type {type}", type);
return null;
}
return buildCrestronHardwareDevice(id, Global.ControlSystem, projectName);
}
catch (Exception e)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "WARNING: Cannot create TSW base class. Panel will not function: {0}", e.Message);
Debug.LogError("Cannot create TSW base class. Panel will not function: {message}", e.Message);
Debug.LogDebug(e, "Stack Trace: ");
return null;
}
}

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using PepperDash.Core;
/// <summary>
/// Represents info about a device including supproted interfaces
/// </summary>
public class DeviceInterfaceInfo : IKeyName
{
/// <summary>
/// Gets or sets the Key
/// </summary>
[JsonProperty("key")]
public string Key { get; set; }
/// <summary>
/// Gets or sets the Name
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the Interfaces
/// </summary>
[JsonProperty("interfaces")]
public List<string> Interfaces { get; set; }
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents a JoinResponse
/// </summary>
public class JoinResponse
{
/// <summary>
/// Gets or sets the ClientId
/// </summary>
[JsonProperty("clientId")]
public string ClientId { get; set; }
[JsonProperty("roomKey")]
public string RoomKey { get; set; }
[JsonProperty("systemUUid")]
public string SystemUuid { get; set; }
/// <summary>
/// Gets or sets the RoomUuid
/// </summary>
[JsonProperty("roomUUid")]
public string RoomUuid { get; set; }
/// <summary>
/// Gets or sets the Config
/// </summary>
[JsonProperty("config")]
public object Config { get; set; }
/// <summary>
/// Gets or sets the CodeExpires
/// </summary>
[JsonProperty("codeExpires")]
public DateTime CodeExpires { get; set; }
/// <summary>
/// Gets or sets the UserCode
/// </summary>
[JsonProperty("userCode")]
public string UserCode { get; set; }
/// <summary>
/// Gets or sets the UserAppUrl
/// </summary>
[JsonProperty("userAppUrl")]
public string UserAppUrl { get; set; }
/// <summary>
/// Gets or sets the EnableDebug
/// </summary>
[JsonProperty("enableDebug")]
public bool EnableDebug { get; set; }
/// <summary>
/// Gets or sets the DeviceInterfaceSupport
/// </summary>
[JsonProperty("deviceInterfaceSupport")]
public Dictionary<string, DeviceInterfaceInfo> DeviceInterfaceSupport { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents a JoinToken
/// </summary>
public class JoinToken
{
/// <summary>
/// Gets or sets the Code
/// </summary>
public string Code { get; set; }
public string RoomKey { get; set; }
public string Uuid { get; set; }
public string TouchpanelKey { get; set; } = "";
/// <summary>
/// Gets or sets the Token
/// </summary>
public string Token { get; set; } = null;
}
}

View File

@@ -155,7 +155,7 @@ namespace PepperDash.Essentials.WebSocketServer
{
try
{
Debug.LogMessage(LogEventLevel.Information, "Automatically forwarding port {0} to CS LAN", Port);
this.LogInformation("Automatically forwarding port {port} to CS LAN", Port);
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
var csIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
@@ -164,16 +164,17 @@ namespace PepperDash.Essentials.WebSocketServer
if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr)
{
Debug.LogMessage(LogEventLevel.Error, "Error adding port forwarding: {0}", result);
this.LogError("Error adding port forwarding: {error}", result);
}
}
catch (ArgumentException)
{
Debug.LogMessage(LogEventLevel.Information, "This processor does not have a CS LAN", this);
this.LogInformation("This processor does not have a CS LAN", this);
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Error automatically forwarding port to CS LAN");
this.LogError("Error automatically forwarding port to CS LAN: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace");
}
}
@@ -190,7 +191,7 @@ namespace PepperDash.Essentials.WebSocketServer
{
if (parent.Config.DirectServer.AutomaticallyForwardPortToCSLAN == false)
{
Debug.LogMessage(LogEventLevel.Information, "This processor does not have a CS LAN", this);
this.LogInformation("This processor does not have a CS LAN");
}
}
@@ -259,13 +260,15 @@ namespace PepperDash.Essentials.WebSocketServer
_server.OnPost += Server_OnPost;
}
_server.Log.Output = (data, level) => this.LogInformation("WebSocket Server Log [{level}]: {data}", level, data);
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
_server.Start();
if (_server.IsListening)
{
Debug.LogMessage(LogEventLevel.Information, "Mobile Control WebSocket Server listening on port {port}", this, _server.Port);
this.LogInformation("Mobile Control WebSocket Server listening on port {port}", _server.Port);
}
CrestronEnvironment.ProgramStatusEventHandler += OnProgramStop;
@@ -278,7 +281,8 @@ namespace PepperDash.Essentials.WebSocketServer
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Exception intializing websocket server", this);
this.LogError("Exception initializing direct server: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace");
}
}
@@ -347,22 +351,6 @@ namespace PepperDash.Essentials.WebSocketServer
string ip = 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;
//}
if (_parent.Config.DirectServer.CSLanUiDeviceKeys != null && _parent.Config.DirectServer.CSLanUiDeviceKeys.Any(k => k.Equals(touchpanel.Touchpanel.Key, StringComparison.InvariantCultureIgnoreCase)) && csIpAddress != null)
{
ip = csIpAddress.ToString();
@@ -477,7 +465,8 @@ namespace PepperDash.Essentials.WebSocketServer
}
catch (Exception ex)
{
this.LogError(ex, "Error getting application configuration");
this.LogError("Error getting application configuration: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace");
return null;
}
@@ -513,15 +502,14 @@ namespace PepperDash.Essentials.WebSocketServer
{
if (token.Value == null)
{
Debug.LogMessage(LogEventLevel.Warning, "Token value is null", this);
this.LogWarning("Token value is null");
continue;
}
Debug.LogMessage(LogEventLevel.Information, "Adding token: {0} for room: {1}", this, token.Key, token.Value.RoomKey);
this.LogInformation("Adding token: {key} for room: {roomKey}", token.Key, token.Value.RoomKey);
if (UiClients == null)
{
Debug.LogMessage(LogEventLevel.Warning, "UiClients is null", this);
UiClients = new Dictionary<string, UiClientContext>();
}
@@ -531,7 +519,7 @@ namespace PepperDash.Essentials.WebSocketServer
if (UiClients.Count > 0)
{
Debug.LogMessage(LogEventLevel.Information, "Restored {uiClientCount} UiClients from secrets data", this, UiClients.Count);
this.LogInformation("Restored {uiClientCount} UiClients from secrets data", UiClients.Count);
foreach (var client in UiClients)
{
@@ -541,36 +529,28 @@ namespace PepperDash.Essentials.WebSocketServer
_server.AddWebSocketService(path, () =>
{
var c = new UiClient();
Debug.LogMessage(LogEventLevel.Debug, "Constructing UiClient with id: {key}", this, key);
var c = new UiClient($"uiclient-{key}-{roomKey}");
this.LogDebug("Constructing UiClient with id: {key}", key);
c.Controller = _parent;
c.RoomKey = roomKey;
UiClients[key].SetClient(c);
return c;
});
//_server.WebSocketServices.AddService<UiClient>(path, (c) =>
//{
// Debug.Console(2, this, "Constructing UiClient with id: {0}", key);
// c.Controller = _parent;
// c.RoomKey = roomKey;
// UiClients[key].SetClient(c);
//});
}
}
}
else
{
Debug.LogMessage(LogEventLevel.Warning, "No secret found");
this.LogWarning("No secret found");
}
Debug.LogMessage(LogEventLevel.Debug, "{uiClientCount} UiClients restored from secrets data", this, UiClients.Count);
this.LogDebug("{uiClientCount} UiClients restored from secrets data", UiClients.Count);
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Exception retrieving secret", this);
this.LogError("Exception retrieving secret: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace");
}
}
@@ -583,7 +563,7 @@ namespace PepperDash.Essentials.WebSocketServer
{
if (_secret == null)
{
Debug.LogMessage(LogEventLevel.Error, "Secret is null", this);
this.LogError("Secret is null");
_secret = new ServerTokenSecrets(string.Empty);
}
@@ -601,7 +581,8 @@ namespace PepperDash.Essentials.WebSocketServer
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Exception updating secret", this);
this.LogError("Exception updating secret: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace");
}
}
@@ -704,18 +685,18 @@ namespace PepperDash.Essentials.WebSocketServer
_server.AddWebSocketService(path, () =>
{
var c = new UiClient();
Debug.LogMessage(LogEventLevel.Verbose, "Constructing UiClient with id: {0}", this, key);
var c = new UiClient($"uiclient-{key}-{bridge.RoomKey}");
this.LogVerbose("Constructing UiClient with id: {key}", key);
c.Controller = _parent;
c.RoomKey = bridge.RoomKey;
UiClients[key].SetClient(c);
return c;
});
Debug.LogMessage(LogEventLevel.Information, "Added new WebSocket UiClient service at path: {path}", this, path);
Debug.LogMessage(LogEventLevel.Information, "Token: {@token}", this, token);
this.LogInformation("Added new WebSocket UiClient service at path: {path}", path);
this.LogInformation("Token: {@token}", token);
Debug.LogMessage(LogEventLevel.Verbose, "{serviceCount} websocket services present", this, _server.WebSocketServices.Count);
this.LogVerbose("{serviceCount} websocket services present", _server.WebSocketServices.Count);
UpdateSecret();
@@ -729,7 +710,7 @@ namespace PepperDash.Essentials.WebSocketServer
{
if (s == "?" || string.IsNullOrEmpty(s))
{
CrestronConsole.ConsoleCommandResponse(@"Removes all clients from the server. To execute add 'confirm' to command");
CrestronConsole.ConsoleCommandResponse(@"Remove all clients from the server. To execute add 'confirm' to command");
return;
}
@@ -883,7 +864,8 @@ namespace PepperDash.Essentials.WebSocketServer
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Caught an exception in the OnGet handler", this);
this.LogError("Exception in OnGet handler: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace");
}
}
@@ -972,6 +954,20 @@ namespace PepperDash.Essentials.WebSocketServer
res.StatusCode = 200;
res.ContentType = "application/json";
var devices = DeviceManager.GetDevices();
Dictionary<string, DeviceInterfaceInfo> deviceInterfaces = new Dictionary<string, DeviceInterfaceInfo>();
foreach (var device in devices)
{
var interfaces = device?.GetType().GetInterfaces().Select((i) => i.Name).ToList() ?? new List<string>();
deviceInterfaces.Add(device.Key, new DeviceInterfaceInfo
{
Key = device.Key,
Name = device is IKeyName ? (device as IKeyName).Name : "",
Interfaces = interfaces
});
}
// Construct the response object
JoinResponse jRes = new JoinResponse
{
@@ -985,7 +981,8 @@ namespace PepperDash.Essentials.WebSocketServer
UserAppUrl = string.Format("http://{0}:{1}/mc/app",
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
Port),
EnableDebug = false
EnableDebug = false,
DeviceInterfaceSupport = deviceInterfaces
};
// Serialize to JSON and convert to Byte[]
@@ -1171,7 +1168,7 @@ namespace PepperDash.Essentials.WebSocketServer
}
else
{
this.LogVerbose("File not found: {filePath}", filePath);
this.LogWarning("File not found: {filePath}", filePath);
res.StatusCode = (int)HttpStatusCode.NotFound;
res.Close();
return;
@@ -1241,152 +1238,4 @@ namespace PepperDash.Essentials.WebSocketServer
}
}
}
/// <summary>
/// Represents a Version
/// </summary>
public class Version
{
[JsonProperty("serverVersion")]
public string ServerVersion { get; set; }
[JsonProperty("serverIsRunningOnProcessorHardware")]
public bool ServerIsRunningOnProcessorHardware { get; private set; }
public Version()
{
ServerIsRunningOnProcessorHardware = true;
}
}
/// <summary>
/// Represents a UiClientContext
/// </summary>
public class UiClientContext
{
/// <summary>
/// Gets or sets the Client
/// </summary>
public UiClient Client { get; private set; }
/// <summary>
/// Gets or sets the Token
/// </summary>
public JoinToken Token { get; private set; }
public UiClientContext(JoinToken token)
{
Token = token;
}
/// <summary>
/// SetClient method
/// </summary>
public void SetClient(UiClient client)
{
Client = client;
}
}
/// <summary>
/// Represents a ServerTokenSecrets
/// </summary>
public class ServerTokenSecrets
{
/// <summary>
/// Gets or sets the GrantCode
/// </summary>
public string GrantCode { get; set; }
public Dictionary<string, JoinToken> Tokens { get; set; }
public ServerTokenSecrets(string grantCode)
{
GrantCode = grantCode;
Tokens = new Dictionary<string, JoinToken>();
}
}
/// <summary>
/// Represents a JoinToken
/// </summary>
public class JoinToken
{
/// <summary>
/// Gets or sets the Code
/// </summary>
public string Code { get; set; }
public string RoomKey { get; set; }
public string Uuid { get; set; }
public string TouchpanelKey { get; set; } = "";
/// <summary>
/// Gets or sets the Token
/// </summary>
public string Token { get; set; } = null;
}
/// <summary>
/// Represents a JoinResponse
/// </summary>
public class JoinResponse
{
/// <summary>
/// Gets or sets the ClientId
/// </summary>
[JsonProperty("clientId")]
public string ClientId { get; set; }
[JsonProperty("roomKey")]
public string RoomKey { get; set; }
[JsonProperty("systemUUid")]
public string SystemUuid { get; set; }
/// <summary>
/// Gets or sets the RoomUuid
/// </summary>
[JsonProperty("roomUUid")]
public string RoomUuid { get; set; }
/// <summary>
/// Gets or sets the Config
/// </summary>
[JsonProperty("config")]
public object Config { get; set; }
/// <summary>
/// Gets or sets the CodeExpires
/// </summary>
[JsonProperty("codeExpires")]
public DateTime CodeExpires { get; set; }
/// <summary>
/// Gets or sets the UserCode
/// </summary>
[JsonProperty("userCode")]
public string UserCode { get; set; }
/// <summary>
/// Gets or sets the UserAppUrl
/// </summary>
[JsonProperty("userAppUrl")]
public string UserAppUrl { get; set; }
/// <summary>
/// Gets or sets the EnableDebug
/// </summary>
[JsonProperty("enableDebug")]
public bool EnableDebug { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents a ServerTokenSecrets
/// </summary>
public class ServerTokenSecrets
{
/// <summary>
/// Gets or sets the GrantCode
/// </summary>
public string GrantCode { get; set; }
public Dictionary<string, JoinToken> Tokens { get; set; }
public ServerTokenSecrets(string grantCode)
{
GrantCode = grantCode;
Tokens = new Dictionary<string, JoinToken>();
}
}
}

View File

@@ -1,11 +1,12 @@
using Newtonsoft.Json;
using System;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.AppServer.Messengers;
using PepperDash.Essentials.RoomBridges;
using Serilog.Events;
using System;
using System.Text.RegularExpressions;
using WebSocketSharp;
using WebSocketSharp.Server;
using ErrorEventArgs = WebSocketSharp.ErrorEventArgs;
@@ -16,8 +17,11 @@ namespace PepperDash.Essentials.WebSocketServer
/// <summary>
/// Represents the behaviour to associate with a UiClient for WebSocket communication
/// </summary>
public class UiClient : WebSocketBehavior
public class UiClient : WebSocketBehavior, IKeyed
{
/// <inheritdoc />
public string Key { get; private set; }
public MobileControlSystemController Controller { get; set; }
public string RoomKey { get; set; }
@@ -41,17 +45,18 @@ namespace PepperDash.Essentials.WebSocketServer
}
}
public UiClient()
public UiClient(string key)
{
Key = key;
}
/// <inheritdoc />
protected override void OnOpen()
{
base.OnOpen();
var url = Context.WebSocket.Url;
Debug.LogMessage(LogEventLevel.Verbose, "New WebSocket Connection from: {0}", null, url);
this.LogInformation("New WebSocket Connection from: {url}", url);
var match = Regex.Match(url.AbsoluteUri, "(?:ws|wss):\\/\\/.*(?:\\/mc\\/api\\/ui\\/join\\/)(.*)");
@@ -117,6 +122,7 @@ namespace PepperDash.Essentials.WebSocketServer
Controller.SendMessageObjectToDirectClient(message);
}
/// <inheritdoc />
protected override void OnMessage(MessageEventArgs e)
{
base.OnMessage(e);
@@ -128,18 +134,21 @@ namespace PepperDash.Essentials.WebSocketServer
}
}
/// <inheritdoc />
protected override void OnClose(CloseEventArgs e)
{
base.OnClose(e);
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Closing: {0} reason: {1}", null, e.Code, e.Reason);
this.LogInformation("WebSocket UiClient Closing: {code} reason: {reason}", e.Code, e.Reason);
}
/// <inheritdoc />
protected override void OnError(ErrorEventArgs e)
{
base.OnError(e);
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Error: {exception} message: {message}", e.Exception, e.Message);
this.LogError("WebSocket UiClient Error: {message}", e.Message);
this.LogDebug(e.Exception, "Stack Trace");
}
}
}

View File

@@ -0,0 +1,31 @@
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents a UiClientContext
/// </summary>
public class UiClientContext
{
/// <summary>
/// Gets or sets the Client
/// </summary>
public UiClient Client { get; private set; }
/// <summary>
/// Gets or sets the Token
/// </summary>
public JoinToken Token { get; private set; }
public UiClientContext(JoinToken token)
{
Token = token;
}
/// <summary>
/// SetClient method
/// </summary>
public void SetClient(UiClient client)
{
Client = client;
}
}
}

View File

@@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents a Version
/// </summary>
public class Version
{
[JsonProperty("serverVersion")]
public string ServerVersion { get; set; }
[JsonProperty("serverIsRunningOnProcessorHardware")]
public bool ServerIsRunningOnProcessorHardware { get; private set; }
public Version()
{
ServerIsRunningOnProcessorHardware = true;
}
}
}