Compare commits

..

1 Commits

Author SHA1 Message Date
Andrew Welker
185410b802 fix: ignore case for appdebug {LogLevel} 2025-05-07 08:08:56 -05:00
7 changed files with 1388 additions and 1526 deletions

View File

@@ -353,7 +353,7 @@ namespace PepperDash.Core
return;
}
if(Enum.TryParse<LogEventLevel>(levelString, out var levelEnum))
if(Enum.TryParse<LogEventLevel>(levelString, true, out var levelEnum))
{
SetDebugLevel(levelEnum);
return;

View File

@@ -1,86 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Extensions for IPAddress to provide additional functionality such as getting broadcast address, network address, and checking if two addresses are in the same subnet.
/// </summary>
public static class IPAddressExtensions
{
/// <summary>
/// Get the broadcast address for a given IP address and subnet mask.
/// </summary>
/// <param name="address">Address to check</param>
/// <param name="subnetMask">Subnet mask in a.b.c.d format</param>
/// <returns>Broadcast address</returns>
/// <remarks>
/// If the input IP address is 192.168.1.100 and the subnet mask is 255.255.255.0, the broadcast address will be 192.168.1.255
/// </remarks>
/// <exception cref="ArgumentException"></exception>
public static IPAddress GetBroadcastAddress(this IPAddress address, IPAddress subnetMask)
{
byte[] ipAdressBytes = address.GetAddressBytes();
byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
if (ipAdressBytes.Length != subnetMaskBytes.Length)
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
for (int i = 0; i < broadcastAddress.Length; i++)
{
broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255));
}
return new IPAddress(broadcastAddress);
}
/// <summary>
/// Get the network address for a given IP address and subnet mask.
/// </summary>
/// <param name="address">Address to check</param>
/// <param name="subnetMask">Subnet mask in a.b.c.d</param>
/// <returns>Network Address</returns>
/// /// <remarks>
/// If the input IP address is 192.168.1.100 and the subnet mask is 255.255.255.0, the network address will be 192.168.1.0
/// </remarks>
/// <exception cref="ArgumentException"></exception>
public static IPAddress GetNetworkAddress(this IPAddress address, IPAddress subnetMask)
{
byte[] ipAdressBytes = address.GetAddressBytes();
byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
if (ipAdressBytes.Length != subnetMaskBytes.Length)
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
for (int i = 0; i < broadcastAddress.Length; i++)
{
broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
}
return new IPAddress(broadcastAddress);
}
/// <summary>
/// Determine if two IP addresses are in the same subnet.
/// </summary>
/// <param name="address2">Address to check</param>
/// <param name="address">Second address to check</param>
/// <param name="subnetMask">Subnet mask to use to compare the 2 IP Address</param>
/// <returns>True if addresses are in the same subnet</returns>
/// <remarks>
/// If the input IP addresses are 192.168.1.100 and 192.168.1.200, and the subnet mask is 255.255.255.0, this will return true.
/// If the input IP addresses are 10.1.1.100 and 192.168.1.100, and the subnet mask is 255.255.255.0, this will return false.
/// </remarks>
public static bool IsInSameSubnet(this IPAddress address2, IPAddress address, IPAddress subnetMask)
{
IPAddress network1 = address.GetNetworkAddress(subnetMask);
IPAddress network2 = address2.GetNetworkAddress(subnetMask);
return network1.Equals(network2);
}
}
}

View File

@@ -227,7 +227,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
SendCameraPresetNamesToApi(presetsCamera, joinMap, trilist);
for (int i = 0; i < joinMap.PresetRecallStart.JoinSpan; i++)
for (int i = 0; i < joinMap.NumberOfPresets.JoinSpan; i++)
{
int tempNum = i;

View File

@@ -21,10 +21,6 @@ namespace PepperDash.Essentials.Devices.Common.DSP
public DspBase(string key, string name) :
base(key, name)
{
LevelControlPoints = new Dictionary<string, IBasicVolumeWithFeedback>();
DialerControlPoints = new Dictionary<string, DspControlPoint>();
SwitcherControlPoints = new Dictionary<string, DspControlPoint>();
}

View File

@@ -491,7 +491,7 @@ namespace PepperDash.Essentials.Touchpanel
{
public MobileControlTouchpanelControllerFactory()
{
TypeNames = new List<string>() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mcxpanel", "mcdge1000" };
TypeNames = new List<string>() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mcxpanel" };
MinimumEssentialsFrameworkVersion = "2.0.0";
}
@@ -555,10 +555,7 @@ namespace PepperDash.Essentials.Touchpanel
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);
return null;

View File

@@ -1,8 +1,10 @@
using Crestron.SimplSharp;
using Crestron.SimplSharp.WebScripting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.AppServer.Messengers;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Core.Web;
@@ -15,13 +17,145 @@ using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using WebSocketSharp;
using WebSocketSharp.Net;
using WebSocketSharp.Server;
using ErrorEventArgs = WebSocketSharp.ErrorEventArgs;
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents the behaviour to associate with a UiClient for WebSocket communication
/// </summary>
public class UiClient : WebSocketBehavior
{
public MobileControlSystemController Controller { get; set; }
public string RoomKey { get; set; }
private string _clientId;
private DateTime _connectionTime;
public TimeSpan ConnectedDuration
{
get
{
if (Context.WebSocket.IsAlive)
{
return DateTime.Now - _connectionTime;
}
else
{
return new TimeSpan(0);
}
}
}
public UiClient()
{
}
protected override void OnOpen()
{
base.OnOpen();
var url = Context.WebSocket.Url;
Debug.LogMessage(LogEventLevel.Verbose, "New WebSocket Connection from: {0}", null, url);
var match = Regex.Match(url.AbsoluteUri, "(?:ws|wss):\\/\\/.*(?:\\/mc\\/api\\/ui\\/join\\/)(.*)");
if (!match.Success)
{
_connectionTime = DateTime.Now;
return;
}
var clientId = match.Groups[1].Value;
_clientId = clientId;
if (Controller == null)
{
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");
_connectionTime = DateTime.Now;
}
var clientJoinedMessage = new MobileControlMessage
{
Type = "/system/clientJoined",
Content = JToken.FromObject(new
{
clientId,
roomKey = RoomKey,
})
};
Controller.HandleClientMessage(JsonConvert.SerializeObject(clientJoinedMessage));
var bridge = Controller.GetRoomBridge(RoomKey);
if (bridge == null) return;
SendUserCodeToClient(bridge, clientId);
bridge.UserCodeChanged -= Bridge_UserCodeChanged;
bridge.UserCodeChanged += Bridge_UserCodeChanged;
// TODO: Future: Check token to see if there's already an open session using that token and reject/close the session
}
private void Bridge_UserCodeChanged(object sender, EventArgs e)
{
SendUserCodeToClient((MobileControlEssentialsRoomBridge)sender, _clientId);
}
private void SendUserCodeToClient(MobileControlBridgeBase bridge, string clientId)
{
var content = new
{
userCode = bridge.UserCode,
qrUrl = bridge.QrCodeUrl,
};
var message = new MobileControlMessage
{
Type = "/system/userCodeChanged",
ClientId = clientId,
Content = JToken.FromObject(content)
};
Controller.SendMessageObjectToDirectClient(message);
}
protected override void OnMessage(MessageEventArgs e)
{
base.OnMessage(e);
if (e.IsText && e.Data.Length > 0 && Controller != null)
{
// Forward the message to the controller to be put on the receive queue
Controller.HandleClientMessage(e.Data);
}
}
protected override void OnClose(CloseEventArgs e)
{
base.OnClose(e);
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Closing: {0} reason: {1}", null, e.Code, e.Reason);
}
protected override void OnError(ErrorEventArgs e)
{
base.OnError(e);
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Error: {exception} message: {message}", e.Exception, e.Message);
}
}
public class MobileControlWebsocketServer : EssentialsDevice
{
private readonly string userAppPath = Global.FilePathPrefix + "mcUserApp" + Global.DirectorySeparator;
@@ -29,7 +163,6 @@ namespace PepperDash.Essentials.WebSocketServer
private readonly string localConfigFolderName = "_local-config";
private readonly string appConfigFileName = "_config.local.json";
private readonly string appConfigCsFileName = "_config.cs.json";
/// <summary>
/// Where the key is the join token and the value is the room key
@@ -58,12 +191,6 @@ 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 csSubnetMask;
/// <summary>
/// The path for the WebSocket messaging
/// </summary>
@@ -82,7 +209,7 @@ namespace PepperDash.Essentials.WebSocketServer
private string _userAppBaseHref = "/mc/app";
/// <summary>
/// The port the server will run on
/// The prot the server will run on
/// </summary>
public int Port { get; private set; }
@@ -133,6 +260,9 @@ namespace PepperDash.Essentials.WebSocketServer
{
try
{
CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
Debug.LogMessage(LogEventLevel.Information, "Automatically forwarding port {0} to CS LAN", Port);
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
@@ -155,23 +285,6 @@ 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 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);
}
catch (ArgumentException)
{
if (parent.Config.DirectServer.AutomaticallyForwardPortToCSLAN == false)
{
Debug.LogMessage(LogEventLevel.Information, "This processor does not have a CS LAN", this);
}
}
UiClients = new Dictionary<string, UiClientContext>();
@@ -353,65 +466,59 @@ namespace PepperDash.Essentials.WebSocketServer
using (var sw = new StreamWriter(File.Open($"{userAppPath}{localConfigFolderName}{Global.DirectorySeparator}{appConfigFileName}", FileMode.Create, FileAccess.ReadWrite)))
{
// Write the LAN application configuration file. Used when a request comes in for the application config from the LAN
var lanAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter);
var config = GetApplicationConfig();
this.LogDebug("LAN Adapter ID: {lanAdapterId}", lanAdapterId);
var contents = JsonConvert.SerializeObject(config, Formatting.Indented);
sw.Write(contents);
}
}
private MobileControlApplicationConfig GetApplicationConfig()
{
MobileControlApplicationConfig config = null;
var lanAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter);
var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, lanAdapterId);
var config = GetApplicationConfig(processorIp);
var contents = JsonConvert.SerializeObject(config, Formatting.Indented);
sw.Write(contents);
}
short csAdapterId;
try
{
csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
}
catch (ArgumentException)
if (_parent.Config.ApplicationConfig == null)
{
this.LogDebug("This processor does not have a CS LAN");
return;
}
if(csAdapterId == -1)
{
this.LogDebug("CS LAN Adapter not found");
return;
}
this.LogDebug("CS LAN Adapter ID: {csAdapterId}. Adding CS Config", csAdapterId);
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 config = GetApplicationConfig(processorIp);
var contents = JsonConvert.SerializeObject(config, Formatting.Indented);
sw.Write(contents);
}
}
private MobileControlApplicationConfig GetApplicationConfig(string processorIp)
{
try
{
var config = new MobileControlApplicationConfig
config = new MobileControlApplicationConfig
{
ApiPath = string.Format("http://{0}:{1}/mc/api", processorIp, _parent.Config.DirectServer.Port),
GatewayAppPath = "",
LogoPath = _parent.Config.ApplicationConfig?.LogoPath ?? "logo/logo.png",
EnableDev = _parent.Config.ApplicationConfig?.EnableDev ?? false,
IconSet = _parent.Config.ApplicationConfig?.IconSet ?? MCIconSet.GOOGLE,
LoginMode = _parent.Config.ApplicationConfig?.LoginMode ?? "room-list",
Modes = _parent.Config.ApplicationConfig?.Modes ?? new Dictionary<string, McMode>
LogoPath = "logo/logo.png",
EnableDev = false,
IconSet = MCIconSet.GOOGLE,
LoginMode = "room-list",
Modes = new Dictionary<string, McMode>
{
{
"room-list",
new McMode{
ListPageText= "Please select your room",
LoginHelpText = "Please select your room from the list, then enter the code shown on the display.",
PasscodePageText = "Please enter the code shown on this room's display"
}
}
},
Logging = _parent.Config.DirectServer.Logging.EnableRemoteLogging,
};
}
else
{
config = new MobileControlApplicationConfig
{
ApiPath = string.Format("http://{0}:{1}/mc/api", processorIp, _parent.Config.DirectServer.Port),
GatewayAppPath = "",
LogoPath = _parent.Config.ApplicationConfig.LogoPath ?? "logo/logo.png",
EnableDev = _parent.Config.ApplicationConfig.EnableDev ?? false,
IconSet = _parent.Config.ApplicationConfig.IconSet ?? MCIconSet.GOOGLE,
LoginMode = _parent.Config.ApplicationConfig.LoginMode ?? "room-list",
Modes = _parent.Config.ApplicationConfig.Modes ?? new Dictionary<string, McMode>
{
{
"room-list",
@@ -422,18 +529,19 @@ namespace PepperDash.Essentials.WebSocketServer
}
}
},
Logging = _parent.Config.ApplicationConfig?.Logging ?? false,
PartnerMetadata = _parent.Config.ApplicationConfig?.PartnerMetadata ?? new List<MobileControlPartnerMetadata>()
Logging = _parent.Config.ApplicationConfig.Logging,
PartnerMetadata = _parent.Config.ApplicationConfig.PartnerMetadata ?? new List<MobileControlPartnerMetadata>()
};
return config;
}
}
catch (Exception ex)
{
this.LogError(ex, "Error getting application configuration");
Debug.LogMessage(ex, "Error getting application configuration", this);
return null;
Debug.LogMessage(LogEventLevel.Verbose, "Config Object: {config} from {parentConfig}", this, config, _parent.Config);
}
return config;
}
/// <summary>
@@ -1099,14 +1207,6 @@ namespace PepperDash.Essentials.WebSocketServer
this.LogVerbose("Attempting to serve file: {filePath}", filePath);
var remoteIp = req.RemoteEndPoint.Address;
// Check if the request is coming from the CS LAN and if so, send the CS config instead of the LAN config
if (csSubnetMask != null && csIpAddress != null && remoteIp.IsInSameSubnet(csIpAddress, csSubnetMask) && filePath.Contains(appConfigFileName))
{
filePath = filePath.Replace(appConfigFileName, appConfigCsFileName);
}
byte[] contents;
if (File.Exists(filePath))
{

View File

@@ -1,145 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
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;
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents the behaviour to associate with a UiClient for WebSocket communication
/// </summary>
public class UiClient : WebSocketBehavior
{
public MobileControlSystemController Controller { get; set; }
public string RoomKey { get; set; }
private string _clientId;
private DateTime _connectionTime;
public TimeSpan ConnectedDuration
{
get
{
if (Context.WebSocket.IsAlive)
{
return DateTime.Now - _connectionTime;
}
else
{
return new TimeSpan(0);
}
}
}
public UiClient()
{
}
protected override void OnOpen()
{
base.OnOpen();
var url = Context.WebSocket.Url;
Debug.LogMessage(LogEventLevel.Verbose, "New WebSocket Connection from: {0}", null, url);
var match = Regex.Match(url.AbsoluteUri, "(?:ws|wss):\\/\\/.*(?:\\/mc\\/api\\/ui\\/join\\/)(.*)");
if (!match.Success)
{
_connectionTime = DateTime.Now;
return;
}
var clientId = match.Groups[1].Value;
_clientId = clientId;
if (Controller == null)
{
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");
_connectionTime = DateTime.Now;
}
var clientJoinedMessage = new MobileControlMessage
{
Type = "/system/clientJoined",
Content = JToken.FromObject(new
{
clientId,
roomKey = RoomKey,
})
};
Controller.HandleClientMessage(JsonConvert.SerializeObject(clientJoinedMessage));
var bridge = Controller.GetRoomBridge(RoomKey);
if (bridge == null) return;
SendUserCodeToClient(bridge, clientId);
bridge.UserCodeChanged -= Bridge_UserCodeChanged;
bridge.UserCodeChanged += Bridge_UserCodeChanged;
// TODO: Future: Check token to see if there's already an open session using that token and reject/close the session
}
private void Bridge_UserCodeChanged(object sender, EventArgs e)
{
SendUserCodeToClient((MobileControlEssentialsRoomBridge)sender, _clientId);
}
private void SendUserCodeToClient(MobileControlBridgeBase bridge, string clientId)
{
var content = new
{
userCode = bridge.UserCode,
qrUrl = bridge.QrCodeUrl,
};
var message = new MobileControlMessage
{
Type = "/system/userCodeChanged",
ClientId = clientId,
Content = JToken.FromObject(content)
};
Controller.SendMessageObjectToDirectClient(message);
}
protected override void OnMessage(MessageEventArgs e)
{
base.OnMessage(e);
if (e.IsText && e.Data.Length > 0 && Controller != null)
{
// Forward the message to the controller to be put on the receive queue
Controller.HandleClientMessage(e.Data);
}
}
protected override void OnClose(CloseEventArgs e)
{
base.OnClose(e);
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Closing: {0} reason: {1}", null, e.Code, e.Reason);
}
protected override void OnError(ErrorEventArgs e)
{
base.OnError(e);
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Error: {exception} message: {message}", e.Exception, e.Message);
}
}
}