Compare commits

..

No commits in common. "main" and "v2.40.0" have entirely different histories.

4 changed files with 41 additions and 94 deletions

View file

@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.42.1-local</Version>
<Version>2.36.6-local</Version>
<InformationalVersion>$(Version)</InformationalVersion>
<Authors>PepperDash Technology</Authors>
<Company>PepperDash Technology</Company>

View file

@ -151,8 +151,6 @@ namespace PepperDash.Core
// Thread-safety lock for state changes
private readonly object _stateLock = new object();
private volatile bool _isProgramStopping;
private bool disconnectLogged = false;
/// <summary>
@ -209,9 +207,11 @@ namespace PepperDash.Core
{
if (programEventType == eProgramStatusEventType.Stopping)
{
_isProgramStopping = true;
this.LogDebug("Program stopping. Closing connection");
Disconnect();
if (client != null)
{
this.LogDebug("Program stopping. Closing connection");
Disconnect();
}
}
}
@ -228,12 +228,6 @@ namespace PepperDash.Core
return;
}
if (_isProgramStopping)
{
this.LogDebug("Skipping connect because program is stopping");
return;
}
ConnectEnabled = true;
try
@ -293,7 +287,13 @@ namespace PepperDash.Core
}
catch (SshConnectionException e)
{
var ie = e.InnerException; // The details are inside, when present - remote can close the connection with no inner exception at all
var ie = e.InnerException; // The details are inside!!
if (ie is SocketException)
{
this.LogError("CONNECTION failure: Cannot reach host");
this.LogVerbose(ie, "Exception details: ");
}
if (ie is System.Net.Sockets.SocketException socketException)
{
@ -301,20 +301,20 @@ namespace PepperDash.Core
Hostname, Port);
this.LogVerbose(socketException, "SocketException details: ");
}
else if (ie is SshAuthenticationException)
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 ?? e.Message);
this.LogVerbose(ie ?? e, "Exception details: ");
this.LogError("Error on connect: {error}", ie.Message);
this.LogVerbose(ie, "Exception details: ");
}
disconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
if (AutoReconnect)
{
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
StartReconnectTimer();
@ -326,7 +326,7 @@ namespace PepperDash.Core
disconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
if (AutoReconnect)
{
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
StartReconnectTimer();
@ -338,7 +338,7 @@ namespace PepperDash.Core
this.LogVerbose(e, "Exception details: ");
disconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
if (AutoReconnect)
{
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
StartReconnectTimer();
@ -473,7 +473,7 @@ namespace PepperDash.Core
{
connectLock.Release();
}
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
if (AutoReconnect && ConnectEnabled)
{
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
StartReconnectTimer();
@ -516,10 +516,7 @@ namespace PepperDash.Core
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
{
StartReconnectTimer();
}
StartReconnectTimer();
}
catch (Exception ex)
{
@ -552,10 +549,7 @@ namespace PepperDash.Core
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect && ConnectEnabled && !_isProgramStopping)
{
StartReconnectTimer();
}
StartReconnectTimer();
}
catch (Exception ex)
{

View file

@ -25,28 +25,11 @@ namespace PepperDash.Essentials.Touchpanel
/// Mobile Control touchpanel controller that provides app control, Zoom integration,
/// and mobile control functionality for Crestron touchpanels.
/// </summary>
public class MobileControlTouchpanelController : TouchpanelBase, IHasFeedback, ITswAppControl, ITswZoomControl, IDeviceInfoProvider, IMobileControlCrestronTouchpanelController, ITheme, ICommunicationMonitor
public class MobileControlTouchpanelController : TouchpanelBase, IHasFeedback, ITswAppControl, ITswZoomControl, IDeviceInfoProvider, IMobileControlCrestronTouchpanelController, ITheme
{
private readonly MobileControlTouchpanelProperties localConfig;
private IMobileControlRoomMessenger _bridge;
/// <summary>
/// Gets the CommunicationMonitor tracking the panel's online/offline state
/// </summary>
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;
/// <summary>
@ -145,11 +128,6 @@ namespace PepperDash.Essentials.Touchpanel
{
localConfig = config;
if (panel != null)
{
CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, panel, 120000, 300000);
}
AddPostActivationAction(SubscribeForMobileControlUpdates);
ThemeFeedback = new StringFeedback($"{Key}-theme", () => Theme);
@ -388,8 +366,6 @@ namespace PepperDash.Essentials.Touchpanel
/// </summary>
public override bool CustomActivate()
{
CommunicationMonitor?.Start();
var appMessenger = new ITswAppControlMessenger($"appControlMessenger-{Key}", $"/device/{Key}", this);
var zoomMessenger = new ITswZoomControlMessenger($"zoomControlMessenger-{Key}", $"/device/{Key}", this);
@ -417,17 +393,6 @@ namespace PepperDash.Essentials.Touchpanel
return base.CustomActivate();
}
/// <summary>
/// Stops the CommunicationMonitor on deactivation.
/// </summary>
/// <returns>True if deactivation was successful; otherwise, false.</returns>
public override bool Deactivate()
{
CommunicationMonitor?.Stop();
return base.Deactivate();
}
/// <summary>
/// Handles device extender signal changes for system reserved signals.
/// </summary>
@ -554,8 +519,15 @@ namespace PepperDash.Essentials.Touchpanel
return false;
}) ? csIpAddress.ToString() : processorIp;
// replace the host but preserve whatever scheme (http/https) is already present in the URL
var updatedUrl = Regex.Replace(url, @"^(https?)://[^:/]+", $"$1://{ip}");
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);

View file

@ -127,16 +127,6 @@ namespace PepperDash.Essentials.WebSocketServer
/// </summary>
public int Port { get; private set; }
/// <summary>
/// Gets the HTTP scheme to use for generated URLs, based on whether the direct server is configured as secure
/// </summary>
private string HttpScheme => _parent.Config.DirectServer.Secure ? "https" : "http";
/// <summary>
/// Gets the WebSocket scheme to use for generated URLs, based on whether the direct server is configured as secure
/// </summary>
private string WsScheme => _parent.Config.DirectServer.Secure ? "wss" : "ws";
/// <summary>
/// Gets the user app URL prefix
/// </summary>
@ -144,8 +134,7 @@ namespace PepperDash.Essentials.WebSocketServer
{
get
{
return string.Format("{0}://{1}:{2}{3}?token=",
HttpScheme,
return string.Format("http://{0}:{1}{2}?token=",
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
Port,
_userAppBaseHref);
@ -284,7 +273,7 @@ namespace PepperDash.Essentials.WebSocketServer
{
base.Initialize();
_server = new HttpServer(Port, _parent.Config.DirectServer.Secure);
_server = new HttpServer(Port, false);
_server.OnGet += Server_OnGet;
@ -302,7 +291,7 @@ namespace PepperDash.Essentials.WebSocketServer
{
ClientCertificateRequired = false,
CheckCertificateRevocation = false,
EnabledSslProtocols = SslProtocols.Tls12
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11
};
}
@ -414,11 +403,11 @@ namespace PepperDash.Essentials.WebSocketServer
ip = csIpAddress.ToString();
}
var appUrl = $"{HttpScheme}://{ip}:{Port}/mc/app?token={touchpanel.Key}";
var appUrl = $"http://{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(appUrl);
touchpanel.Touchpanel.SetAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}");
}
}
@ -498,7 +487,7 @@ namespace PepperDash.Essentials.WebSocketServer
{
var config = new MobileControlApplicationConfig
{
ApiPath = string.Format("{0}://{1}:{2}/mc/api", HttpScheme, processorIp, Port),
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,
@ -1109,7 +1098,6 @@ namespace PepperDash.Essentials.WebSocketServer
res.StatusCode = 200;
res.Close();
// 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"),
@ -1162,11 +1150,6 @@ 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;
@ -1230,7 +1213,8 @@ 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 wsUrl = $"{WsScheme}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}";
var wsProtocol = "ws";
var wsUrl = $"{wsProtocol}://{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
@ -1242,8 +1226,7 @@ namespace PepperDash.Essentials.WebSocketServer
Config = _parent.GetConfigWithPluginVersion(),
CodeExpires = new DateTime().AddYears(1),
UserCode = bridge.UserCode,
UserAppUrl = string.Format("{0}://{1}:{2}/mc/app",
HttpScheme,
UserAppUrl = string.Format("http://{0}:{1}/mc/app",
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
Port),
WebSocketUrl = wsUrl,
@ -1268,8 +1251,6 @@ 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);