Compare commits

..

1 Commits

Author SHA1 Message Date
Andrew Welker
6cb4f6396b fix: change number of retained files to 7 instead of 30 for processors 2025-10-31 13:09:12 -05:00
33 changed files with 604 additions and 1161 deletions

View File

@@ -1,43 +0,0 @@
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace PepperDash.Core
{
/// <summary>
/// Helper class for formatting communication text and byte data for debugging purposes.
/// </summary>
public class ComTextHelper
{
/// <summary>
/// Gets escaped text for a byte array
/// </summary>
/// <param name="bytes"></param>
/// <returns>string with all bytes escaped</returns>
public static string GetEscapedText(byte[] bytes)
{
return string.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
}
/// <summary>
/// Gets escaped text for a string
/// </summary>
/// <param name="text"></param>
/// <returns>string with all bytes escaped</returns>
public static string GetEscapedText(string text)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
return string.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
}
/// <summary>
/// Gets debug text for a string
/// </summary>
/// <param name="text"></param>
/// <returns>string with all non-printable characters escaped</returns>
public static string GetDebugText(string text)
{
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
@@ -36,14 +37,14 @@ namespace PepperDash.Core
{
get
{
return _DebugTimeoutInMs / 60000;
return _DebugTimeoutInMs/60000;
}
}
/// <summary>
/// Gets or sets the RxStreamDebuggingIsEnabled
/// </summary>
public bool RxStreamDebuggingIsEnabled { get; private set; }
public bool RxStreamDebuggingIsEnabled{ get; private set; }
/// <summary>
/// Indicates that transmit stream debugging is enabled
@@ -107,7 +108,7 @@ namespace PepperDash.Core
TxStreamDebuggingIsEnabled = true;
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
}
/// <summary>
@@ -135,4 +136,51 @@ namespace PepperDash.Core
DebugExpiryPeriod = null;
}
}
/// <summary>
/// The available settings for stream debugging
/// </summary>
[Flags]
/// <summary>
/// Enumeration of eStreamDebuggingSetting values
/// </summary>
public enum eStreamDebuggingSetting
{
/// <summary>
/// Debug off
/// </summary>
Off = 0,
/// <summary>
/// Debug received data
/// </summary>
Rx = 1,
/// <summary>
/// Debug transmitted data
/// </summary>
Tx = 2,
/// <summary>
/// Debug both received and transmitted data
/// </summary>
Both = Rx | Tx
}
/// <summary>
/// The available settings for stream debugging response types
/// </summary>
[Flags]
public enum eStreamDebuggingDataTypeSettings
{
/// <summary>
/// Debug data in byte format
/// </summary>
Bytes = 0,
/// <summary>
/// Debug data in text format
/// </summary>
Text = 1,
/// <summary>
/// Debug data in both byte and text formats
/// </summary>
Both = Bytes | Text,
}
}

View File

@@ -278,7 +278,7 @@ namespace PepperDash.Core
if (shellStream.DataAvailable)
{
// empty the buffer if there is data
shellStream.Read();
string str = shellStream.Read();
}
shellStream.DataReceived += Stream_DataReceived;
this.LogInformation("Connected");
@@ -288,6 +288,7 @@ namespace PepperDash.Core
catch (SshConnectionException e)
{
var ie = e.InnerException; // The details are inside!!
var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
if (ie is SocketException)
{
@@ -334,6 +335,7 @@ namespace PepperDash.Core
}
catch (Exception e)
{
var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
this.LogError("Unhandled exception on connect: {error}", e.Message);
this.LogVerbose(e, "Exception details: ");
disconnectLogged = true;
@@ -437,14 +439,18 @@ namespace PepperDash.Core
if (bytesHandler != null)
{
var bytes = Encoding.UTF8.GetBytes(response);
this.PrintReceivedBytes(bytes);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
var textHandler = TextReceived;
if (textHandler != null)
{
this.PrintReceivedText(response);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response));
textHandler(this, new GenericCommMethodReceiveTextArgs(response));
}
@@ -501,7 +507,11 @@ namespace PepperDash.Core
{
if (client != null && shellStream != null && IsConnected)
{
this.PrintSentText(text);
if (StreamDebugging.TxStreamDebuggingIsEnabled)
this.LogInformation(
"Sending {length} characters of text: '{text}'",
text.Length,
ComTextHelper.GetDebugText(text));
shellStream.Write(text);
shellStream.Flush();
@@ -516,7 +526,7 @@ namespace PepperDash.Core
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
StartReconnectTimer();
StopReconnectTimer();
}
catch (Exception ex)
{
@@ -534,7 +544,8 @@ namespace PepperDash.Core
{
if (client != null && shellStream != null && IsConnected)
{
this.PrintSentBytes(bytes);
if (StreamDebugging.TxStreamDebuggingIsEnabled)
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
shellStream.Write(bytes, 0, bytes.Length);
shellStream.Flush();
@@ -549,7 +560,7 @@ namespace PepperDash.Core
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
StartReconnectTimer();
StopReconnectTimer();
}
catch (Exception ex)
{

View File

@@ -426,7 +426,10 @@ namespace PepperDash.Core
var bytesHandler = BytesReceived;
if (bytesHandler != null)
{
this.PrintReceivedBytes(bytes);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
var textHandler = TextReceived;
@@ -434,7 +437,10 @@ namespace PepperDash.Core
{
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
this.PrintReceivedText(str);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
}
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
}
@@ -450,7 +456,8 @@ namespace PepperDash.Core
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
// Check debug level before processing byte array
this.PrintSentText(text);
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
if (_client != null)
_client.SendData(bytes, bytes.Length);
}
@@ -477,7 +484,8 @@ namespace PepperDash.Core
/// </summary>
public void SendBytes(byte[] bytes)
{
this.PrintSentBytes(bytes);
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
if (_client != null)
_client.SendData(bytes, bytes.Length);
}

View File

@@ -124,7 +124,7 @@ namespace PepperDash.Core
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
}
/// <summary>
///
/// </summary>
@@ -135,7 +135,7 @@ namespace PepperDash.Core
public GenericUdpServer(string key, string address, int port, int bufferSize)
: base(key)
{
StreamDebugging = new CommunicationStreamDebugging(key);
StreamDebugging = new CommunicationStreamDebugging(key);
Hostname = address;
Port = port;
BufferSize = bufferSize;
@@ -180,7 +180,7 @@ namespace PepperDash.Core
/// <param name="programEventType"></param>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType != eProgramStatusEventType.Stopping)
if (programEventType != eProgramStatusEventType.Stopping)
return;
Debug.Console(1, this, "Program stopping. Disabling Server");
@@ -243,7 +243,7 @@ namespace PepperDash.Core
/// </summary>
public void Disconnect()
{
if (Server != null)
if(Server != null)
Server.DisableUDPServer();
IsConnected = false;
@@ -265,7 +265,7 @@ namespace PepperDash.Core
try
{
if (numBytes <= 0)
if (numBytes <= 0)
return;
var sourceIp = Server.IPAddressLastMessageReceivedFrom;
@@ -281,13 +281,17 @@ namespace PepperDash.Core
var bytesHandler = BytesReceived;
if (bytesHandler != null)
{
this.PrintReceivedBytes(bytes);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
var textHandler = TextReceived;
if (textHandler != null)
{
this.PrintReceivedText(str);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
}
}
@@ -314,7 +318,8 @@ namespace PepperDash.Core
if (IsConnected && Server != null)
{
this.PrintSentText(text);
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
Server.SendData(bytes, bytes.Length);
}
@@ -329,7 +334,8 @@ namespace PepperDash.Core
/// </summary>
public void SendBytes(byte[] bytes)
{
this.PrintSentBytes(bytes);
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
if (IsConnected && Server != null)
Server.SendData(bytes, bytes.Length);
@@ -337,11 +343,11 @@ namespace PepperDash.Core
}
/// <summary>
/// Represents a GenericUdpReceiveTextExtraArgs
/// </summary>
public class GenericUdpReceiveTextExtraArgs : EventArgs
{
/// <summary>
/// Represents a GenericUdpReceiveTextExtraArgs
/// </summary>
public class GenericUdpReceiveTextExtraArgs : EventArgs
{
/// <summary>
///
/// </summary>
@@ -353,7 +359,7 @@ namespace PepperDash.Core
/// <summary>
///
/// </summary>
public int Port { get; private set; }
public int Port { get; private set; }
/// <summary>
///
/// </summary>
@@ -367,18 +373,18 @@ namespace PepperDash.Core
/// <param name="port"></param>
/// <param name="bytes"></param>
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
{
Text = text;
IpAddress = ipAddress;
Port = port;
Bytes = bytes;
}
{
Text = text;
IpAddress = ipAddress;
Port = port;
Bytes = bytes;
}
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericUdpReceiveTextExtraArgs() { }
}
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericUdpReceiveTextExtraArgs() { }
}
/// <summary>
///

View File

@@ -1,69 +0,0 @@
using System;
using Crestron.SimplSharp;
namespace PepperDash.Core
{
/// <summary>
/// Extension methods for stream debugging
/// </summary>
public static class StreamDebuggingExtensions
{
private static readonly string app = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? $"App {InitialParametersClass.ApplicationNumber}" : $"{InitialParametersClass.RoomId}";
/// <summary>
/// Print the sent bytes to the console
/// </summary>
/// <param name="comms">comms device</param>
/// <param name="bytes">bytes to print</param>
public static void PrintSentBytes(this IStreamDebugging comms, byte[] bytes)
{
if (!comms.StreamDebugging.TxStreamDebuggingIsEnabled) return;
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Sending {bytes.Length} bytes: '{ComTextHelper.GetEscapedText(bytes)}'");
}
/// <summary>
/// Print the received bytes to the console
/// </summary>
/// <param name="comms">comms device</param>
/// <param name="bytes">bytes to print</param>
public static void PrintReceivedBytes(this IStreamDebugging comms, byte[] bytes)
{
if (!comms.StreamDebugging.RxStreamDebuggingIsEnabled) return;
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Received {bytes.Length} bytes: '{ComTextHelper.GetEscapedText(bytes)}'");
}
/// <summary>
/// Print the sent text to the console
/// </summary>
/// <param name="comms">comms device</param>
/// <param name="text">text to print</param>
public static void PrintSentText(this IStreamDebugging comms, string text)
{
if (!comms.StreamDebugging.TxStreamDebuggingIsEnabled) return;
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Sending Text: '{ComTextHelper.GetDebugText(text)}'");
}
/// <summary>
/// Print the received text to the console
/// </summary>
/// <param name="comms">comms device</param>
/// <param name="text">text to print</param>
public static void PrintReceivedText(this IStreamDebugging comms, string text)
{
if (!comms.StreamDebugging.RxStreamDebuggingIsEnabled) return;
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Received Text: '{ComTextHelper.GetDebugText(text)}'");
}
}
}

View File

@@ -1,24 +0,0 @@
using System;
namespace PepperDash.Core
{
/// <summary>
/// The available settings for stream debugging data format types
/// </summary>
[Flags]
public enum eStreamDebuggingDataTypeSettings
{
/// <summary>
/// Debug data in byte format
/// </summary>
Bytes = 0,
/// <summary>
/// Debug data in text format
/// </summary>
Text = 1,
/// <summary>
/// Debug data in both byte and text formats
/// </summary>
Both = Bytes | Text
}
}

View File

@@ -1,28 +0,0 @@
using System;
namespace PepperDash.Core
{
/// <summary>
/// The available settings for stream debugging
/// </summary>
[Flags]
public enum eStreamDebuggingSetting
{
/// <summary>
/// Debug off
/// </summary>
Off = 0,
/// <summary>
/// Debug received data
/// </summary>
Rx = 1,
/// <summary>
/// Debug transmitted data
/// </summary>
Tx = 2,
/// <summary>
/// Debug both received and transmitted data
/// </summary>
Both = Rx | Tx
}
}

View File

@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
namespace PepperDash.Core
@@ -39,7 +42,7 @@ namespace PepperDash.Core
/// Defines the contract for IBasicCommunication
/// </summary>
public interface IBasicCommunication : ICommunicationReceiver
{
{
/// <summary>
/// Send text to the device
/// </summary>
@@ -51,7 +54,7 @@ namespace PepperDash.Core
/// </summary>
/// <param name="bytes"></param>
void SendBytes(byte[] bytes);
}
}
/// <summary>
/// Represents a device that implements IBasicCommunication and IStreamDebugging
@@ -64,7 +67,7 @@ namespace PepperDash.Core
/// <summary>
/// Represents a device with stream debugging capablities
/// </summary>
public interface IStreamDebugging : IKeyed
public interface IStreamDebugging
{
/// <summary>
/// Object to enable stream debugging
@@ -73,12 +76,12 @@ namespace PepperDash.Core
CommunicationStreamDebugging StreamDebugging { get; }
}
/// <summary>
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
/// GenericTcpIpClient
/// </summary>
public interface ISocketStatus : IBasicCommunication
{
/// <summary>
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
/// GenericTcpIpClient
/// </summary>
public interface ISocketStatus : IBasicCommunication
{
/// <summary>
/// Notifies of socket status changes
/// </summary>
@@ -90,7 +93,7 @@ namespace PepperDash.Core
[JsonProperty("clientStatus")]
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
SocketStatus ClientStatus { get; }
}
}
/// <summary>
/// Describes a device that implements ISocketStatus and IStreamDebugging
@@ -104,24 +107,24 @@ namespace PepperDash.Core
/// Describes a device that can automatically attempt to reconnect
/// </summary>
public interface IAutoReconnect
{
{
/// <summary>
/// Enable automatic recconnect
/// </summary>
[JsonProperty("autoReconnect")]
bool AutoReconnect { get; set; }
bool AutoReconnect { get; set; }
/// <summary>
/// Interval in ms to attempt automatic recconnections
/// </summary>
[JsonProperty("autoReconnectIntervalMs")]
int AutoReconnectIntervalMs { get; set; }
}
int AutoReconnectIntervalMs { get; set; }
}
/// <summary>
///
/// </summary>
public enum eGenericCommMethodStatusChangeType
{
/// <summary>
///
/// </summary>
public enum eGenericCommMethodStatusChangeType
{
/// <summary>
/// Connected
/// </summary>
@@ -130,45 +133,45 @@ namespace PepperDash.Core
/// Disconnected
/// </summary>
Disconnected
}
}
/// <summary>
/// This delegate defines handler for IBasicCommunication status changes
/// </summary>
/// <param name="comm">Device firing the status change</param>
/// <param name="status"></param>
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
/// <summary>
/// This delegate defines handler for IBasicCommunication status changes
/// </summary>
/// <param name="comm">Device firing the status change</param>
/// <param name="status"></param>
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
/// <summary>
///
/// </summary>
public class GenericCommMethodReceiveBytesArgs : EventArgs
{
/// <summary>
/// Gets or sets the Bytes
/// </summary>
public byte[] Bytes { get; private set; }
/// <summary>
///
/// </summary>
public class GenericCommMethodReceiveBytesArgs : EventArgs
{
/// <summary>
/// Gets or sets the Bytes
/// </summary>
public byte[] Bytes { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="bytes"></param>
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
{
Bytes = bytes;
}
{
Bytes = bytes;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericCommMethodReceiveBytesArgs() { }
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericCommMethodReceiveBytesArgs() { }
}
/// <summary>
///
/// </summary>
public class GenericCommMethodReceiveTextArgs : EventArgs
{
/// <summary>
///
/// </summary>
public class GenericCommMethodReceiveTextArgs : EventArgs
{
/// <summary>
///
/// </summary>
@@ -182,9 +185,9 @@ namespace PepperDash.Core
/// </summary>
/// <param name="text"></param>
public GenericCommMethodReceiveTextArgs(string text)
{
Text = text;
}
{
Text = text;
}
/// <summary>
///
@@ -192,14 +195,59 @@ namespace PepperDash.Core
/// <param name="text"></param>
/// <param name="delimiter"></param>
public GenericCommMethodReceiveTextArgs(string text, string delimiter)
: this(text)
:this(text)
{
Delimiter = delimiter;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericCommMethodReceiveTextArgs() { }
}
/// <summary>
///
/// </summary>
public class ComTextHelper
{
/// <summary>
/// S+ Constructor
/// Gets escaped text for a byte array
/// </summary>
public GenericCommMethodReceiveTextArgs() { }
}
/// <param name="bytes"></param>
/// <returns></returns>
public static string GetEscapedText(byte[] bytes)
{
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
}
/// <summary>
/// Gets escaped text for a string
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
/// <summary>
/// GetEscapedText method
/// </summary>
public static string GetEscapedText(string text)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
}
/// <summary>
/// Gets debug text for a string
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
/// <summary>
/// GetDebugText method
/// </summary>
public static string GetDebugText(string text)
{
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
}
}
}

View File

@@ -2,29 +2,25 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport;
using Crestron.SimplSharpPro.EthernetCommunication;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core.Config;
using Serilog.Events;
//using PepperDash.Essentials.Devices.Common.Cameras;
namespace PepperDash.Essentials.Core.Bridges
{
/// <summary>
/// Base class for bridge API variants
/// </summary>
[Obsolete("Will be removed in v3.0.0")]
public abstract class BridgeApi : EssentialsDevice
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="key">Device key</param>
protected BridgeApi(string key) :
base(key)
{
@@ -33,36 +29,23 @@ namespace PepperDash.Essentials.Core.Bridges
}
/// <summary>
/// Class to link devices and rooms to an EISC Instance
/// Represents a EiscApiAdvanced
/// </summary>
public class EiscApiAdvanced : BridgeApi, ICommunicationMonitor
{
/// <summary>
/// Gets the PropertiesConfig
/// </summary>
public EiscApiPropertiesConfig PropertiesConfig { get; private set; }
/// <summary>
/// Gets the JoinMaps dictionary
/// </summary>
public Dictionary<string, JoinMapBaseAdvanced> JoinMaps { get; private set; }
/// <summary>
/// Gets the EISC instance
/// </summary>
public BasicTriList Eisc { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="dc">Device configuration</param>
/// <param name="eisc">EISC instance</param>
public EiscApiAdvanced(DeviceConfig dc, BasicTriList eisc) :
base(dc.Key)
{
JoinMaps = new Dictionary<string, JoinMapBaseAdvanced>();
PropertiesConfig = dc.Properties.ToObject<EiscApiPropertiesConfig>();
//PropertiesConfig = JsonConvert.DeserializeObject<EiscApiPropertiesConfig>(dc.Properties.ToString());
Eisc = eisc;
@@ -77,7 +60,8 @@ namespace PepperDash.Essentials.Core.Bridges
/// <summary>
/// CustomActivate method
/// </summary>
/// </summary>
/// <inheritdoc />
public override bool CustomActivate()
{
CommunicationMonitor.Start();
@@ -99,7 +83,7 @@ namespace PepperDash.Essentials.Core.Bridges
if (PropertiesConfig.Devices == null)
{
this.LogDebug("No devices linked to this bridge");
Debug.LogMessage(LogEventLevel.Debug, this, "No devices linked to this bridge");
return;
}
@@ -120,7 +104,9 @@ namespace PepperDash.Essentials.Core.Bridges
continue;
}
this.LogWarning("{deviceKey} is not compatible with this bridge type. Please update the device.", device.Key);
Debug.LogMessage(LogEventLevel.Information, this,
"{0} is not compatible with this bridge type. Please use 'eiscapi' instead, or updae the device.",
device.Key);
}
}
@@ -135,31 +121,34 @@ namespace PepperDash.Essentials.Core.Bridges
if (registerResult != eDeviceRegistrationUnRegistrationResponse.Success)
{
this.LogVerbose("Registration result: {registerResult}", registerResult);
Debug.LogMessage(LogEventLevel.Verbose, this, "Registration result: {0}", registerResult);
return;
}
this.LogDebug("EISC registration successful");
Debug.LogMessage(LogEventLevel.Debug, this, "EISC registration successful");
}
/// <summary>
/// Link rooms to this EISC. Rooms MUST implement IBridgeAdvanced
/// LinkRooms method
/// </summary>
public void LinkRooms()
{
this.LogDebug("Linking Rooms...");
Debug.LogMessage(LogEventLevel.Debug, this, "Linking Rooms...");
if (PropertiesConfig.Rooms == null)
{
this.LogDebug("No rooms linked to this bridge.");
Debug.LogMessage(LogEventLevel.Debug, this, "No rooms linked to this bridge.");
return;
}
foreach (var room in PropertiesConfig.Rooms)
{
if (!(DeviceManager.GetDeviceForKey(room.RoomKey) is IBridgeAdvanced rm))
var rm = DeviceManager.GetDeviceForKey(room.RoomKey) as IBridgeAdvanced;
if (rm == null)
{
this.LogDebug("Room {roomKey} does not implement IBridgeAdvanced. Skipping...", room.RoomKey);
Debug.LogMessage(LogEventLevel.Debug, this,
"Room {0} does not implement IBridgeAdvanced. Skipping...", room.RoomKey);
continue;
}
@@ -170,8 +159,11 @@ namespace PepperDash.Essentials.Core.Bridges
/// <summary>
/// Adds a join map
/// </summary>
/// <param name="deviceKey">The key of the device to add the join map for</param>
/// <param name="joinMap">The join map to add</param>
/// <param name="deviceKey"></param>
/// <param name="joinMap"></param>
/// <summary>
/// AddJoinMap method
/// </summary>
public void AddJoinMap(string deviceKey, JoinMapBaseAdvanced joinMap)
{
if (!JoinMaps.ContainsKey(deviceKey))
@@ -180,13 +172,14 @@ namespace PepperDash.Essentials.Core.Bridges
}
else
{
this.LogWarning("Unable to add join map with key '{deviceKey}'. Key already exists in JoinMaps dictionary", deviceKey);
Debug.LogMessage(LogEventLevel.Verbose, this, "Unable to add join map with key '{0}'. Key already exists in JoinMaps dictionary", deviceKey);
}
}
/// <summary>
/// PrintJoinMaps method
/// </summary>
/// </summary>
/// <inheritdoc />
public virtual void PrintJoinMaps()
{
CrestronConsole.ConsoleCommandResponse("Join Maps for EISC IPID: {0}\r\n", Eisc.ID.ToString("X"));
@@ -197,17 +190,17 @@ namespace PepperDash.Essentials.Core.Bridges
joinMap.Value.PrintJoinMapInfo();
}
}
/// <summary>
/// MarkdownForBridge method
/// </summary>
/// </summary>
/// <inheritdoc />
public virtual void MarkdownForBridge(string bridgeKey)
{
this.LogInformation("Writing Joinmaps to files for EISC IPID: {eiscId}", Eisc.ID.ToString("X"));
Debug.LogMessage(LogEventLevel.Information, this, "Writing Joinmaps to files for EISC IPID: {0}", Eisc.ID.ToString("X"));
foreach (var joinMap in JoinMaps)
{
this.LogInformation("Generating markdown for device '{deviceKey}':", joinMap.Key);
Debug.LogMessage(LogEventLevel.Information, "Generating markdown for device '{0}':", joinMap.Key);
joinMap.Value.MarkdownJoinMapInfo(joinMap.Key, bridgeKey);
}
}
@@ -215,45 +208,53 @@ namespace PepperDash.Essentials.Core.Bridges
/// <summary>
/// Prints the join map for a device by key
/// </summary>
/// <param name="deviceKey">The key of the device to print the join map for</param>
/// <param name="deviceKey"></param>
/// <summary>
/// PrintJoinMapForDevice method
/// </summary>
public void PrintJoinMapForDevice(string deviceKey)
{
var joinMap = JoinMaps[deviceKey];
if (joinMap == null)
{
this.LogInformation("Unable to find joinMap for device with key: '{deviceKey}'", deviceKey);
Debug.LogMessage(LogEventLevel.Information, this, "Unable to find joinMap for device with key: '{0}'", deviceKey);
return;
}
this.LogInformation("Join map for device '{deviceKey}' on EISC '{eiscKey}':", deviceKey, Key);
Debug.LogMessage(LogEventLevel.Information, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key);
joinMap.PrintJoinMapInfo();
}
/// <summary>
/// Prints the join map for a device by key in Markdown format
/// Prints the join map for a device by key
/// </summary>
/// <param name="deviceKey"></param>
/// <summary>
/// MarkdownJoinMapForDevice method
/// </summary>
/// <param name="deviceKey">The key of the device to print the join map for</param>
/// <param name="bridgeKey">The key of the bridge to use for the Markdown output</param>
public void MarkdownJoinMapForDevice(string deviceKey, string bridgeKey)
{
var joinMap = JoinMaps[deviceKey];
if (joinMap == null)
{
this.LogInformation("Unable to find joinMap for device with key: '{deviceKey}'", deviceKey);
Debug.LogMessage(LogEventLevel.Information, this, "Unable to find joinMap for device with key: '{0}'", deviceKey);
return;
}
this.LogInformation("Join map for device '{deviceKey}' on EISC '{eiscKey}':", deviceKey, Key);
Debug.LogMessage(LogEventLevel.Information, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key);
joinMap.MarkdownJoinMapInfo(deviceKey, bridgeKey);
}
/// <summary>
/// Used for debugging to trigger an action based on a join number and type
/// </summary>
/// <param name="join">The join number to execute the action for</param>
/// <param name="type">The type of join (digital, analog, serial)</param>
/// <param name="state">The state to pass to the action</param>
/// <param name="join"></param>
/// <param name="type"></param>
/// <param name="state"></param>
/// <summary>
/// ExecuteJoinAction method
/// </summary>
public void ExecuteJoinAction(uint join, string type, object state)
{
try
@@ -262,87 +263,78 @@ namespace PepperDash.Essentials.Core.Bridges
{
case "digital":
{
if (Eisc.BooleanOutput[join].UserObject is Action<bool> userObject)
var uo = Eisc.BooleanOutput[join].UserObject as Action<bool>;
if (uo != null)
{
this.LogVerbose("Executing Boolean Action");
userObject(Convert.ToBoolean(state));
Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
uo(Convert.ToBoolean(state));
}
else
this.LogVerbose("User Object is null. Nothing to Execute");
Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute");
break;
}
case "analog":
{
if (Eisc.UShortOutput[join].UserObject is Action<ushort> userObject)
var uo = Eisc.BooleanOutput[join].UserObject as Action<ushort>;
if (uo != null)
{
this.LogVerbose("Executing Analog Action");
userObject(Convert.ToUInt16(state));
Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
uo(Convert.ToUInt16(state));
}
else
this.LogVerbose("User Object is null. Nothing to Execute");
break;
Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute"); break;
}
case "serial":
{
if (Eisc.StringOutput[join].UserObject is Action<string> userObject)
var uo = Eisc.BooleanOutput[join].UserObject as Action<string>;
if (uo != null)
{
this.LogVerbose("Executing Serial Action");
userObject(Convert.ToString(state));
Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
uo(Convert.ToString(state));
}
else
this.LogVerbose("User Object is null. Nothing to Execute");
Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute");
break;
}
default:
{
this.LogVerbose("Unknown join type. Use digital/serial/analog");
Debug.LogMessage(LogEventLevel.Verbose, "Unknown join type. Use digital/serial/analog");
break;
}
}
}
catch (Exception e)
{
this.LogError("ExecuteJoinAction error: {message}", e.Message);
this.LogDebug(e, "Stack Trace: ");
Debug.LogMessage(LogEventLevel.Debug, this, "Error: {0}", e);
}
}
/// <summary>
/// Handle incoming sig changes
/// Handles incoming sig changes
/// </summary>
/// <param name="currentDevice">BasicTriList device that triggered the event</param>
/// <param name="args">Event arguments containing the signal information</param>
/// <param name="currentDevice"></param>
/// <param name="args"></param>
protected void Eisc_SigChange(object currentDevice, SigEventArgs args)
{
try
{
this.LogVerbose("EiscApiAdvanced change: {type} {number}={value}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
var userObject = args.Sig.UserObject;
Debug.LogMessage(LogEventLevel.Verbose, this, "EiscApiAdvanced change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
var uo = args.Sig.UserObject;
if (userObject == null) return;
if (uo == null) return;
if (userObject is Action<bool>)
{
this.LogDebug("Executing Boolean Action");
(userObject as Action<bool>)(args.Sig.BoolValue);
}
else if (userObject is Action<ushort>)
{
this.LogDebug("Executing Analog Action");
(userObject as Action<ushort>)(args.Sig.UShortValue);
}
else if (userObject is Action<string>)
{
this.LogDebug("Executing Serial Action");
(userObject as Action<string>)(args.Sig.StringValue);
}
Debug.LogMessage(LogEventLevel.Debug, this, "Executing Action: {0}", uo.ToString());
if (uo is Action<bool>)
(uo as Action<bool>)(args.Sig.BoolValue);
else if (uo is Action<ushort>)
(uo as Action<ushort>)(args.Sig.UShortValue);
else if (uo is Action<string>)
(uo as Action<string>)(args.Sig.StringValue);
}
catch (Exception e)
{
this.LogError("Eisc_SigChange handler error: {message}", e.Message);
this.LogDebug(e, "Stack Trace: ");
Debug.LogMessage(LogEventLevel.Verbose, this, "Error in Eisc_SigChange handler: {0}", e);
}
}
@@ -431,33 +423,22 @@ namespace PepperDash.Essentials.Core.Bridges
}
/// <summary>
/// Factory class for EiscApiAdvanced devices
/// Represents a EiscApiAdvancedFactory
/// </summary>
/// <remarks>
/// Supported types:
/// eiscapiadv - Create a standard EISC client over TCP/IP
/// eiscapiadvanced - Create a standard EISC client over TCP/IP
/// eiscapiadvancedserver - Create an EISC server
/// eiscapiadvancedclient - Create an EISC client
/// vceiscapiadv - Create a VC-4 EISC client
/// vceiscapiadvanced - Create a VC-4 EISC client
/// eiscapiadvudp - Create a standard EISC client over UDP
/// eiscapiadvancedudp - Create a standard EISC client over UDP
/// </remarks>
public class EiscApiAdvancedFactory : EssentialsDeviceFactory<EiscApiAdvanced>
{
/// <summary>
/// Constructor
/// </summary>
public EiscApiAdvancedFactory()
{
TypeNames = new List<string> { "eiscapiadv", "eiscapiadvanced", "eiscapiadvancedserver", "eiscapiadvancedclient", "vceiscapiadv", "vceiscapiadvanced", "eiscapiadvudp", "eiscapiadvancedudp" };
TypeNames = new List<string> { "eiscapiadv", "eiscapiadvanced", "eiscapiadvancedserver", "eiscapiadvancedclient", "vceiscapiadv", "vceiscapiadvanced" };
}
/// <summary>
/// BuildDevice method
/// </summary>
/// <inheritdoc />
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.LogDebug("Attempting to create new EiscApiAdvanced Device");
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new EiscApiAdvanced Device");
var controlProperties = CommFactory.GetControlPropertiesConfig(dc);
@@ -465,13 +446,6 @@ namespace PepperDash.Essentials.Core.Bridges
switch (dc.Type.ToLower())
{
case "eiscapiadvudp":
case "eiscapiadvancedudp":
{
eisc = new EthernetIntersystemCommunications(controlProperties.IpIdInt,
controlProperties.TcpSshProperties.Address, Global.ControlSystem);
break;
}
case "eiscapiadv":
case "eiscapiadvanced":
{
@@ -494,7 +468,7 @@ namespace PepperDash.Essentials.Core.Bridges
{
if (string.IsNullOrEmpty(controlProperties.RoomId))
{
Debug.LogInformation("Unable to build VC-4 EISC Client for device {deviceKey}. Room ID is missing or empty", dc.Key);
Debug.LogMessage(LogEventLevel.Information, "Unable to build VC-4 EISC Client for device {0}. Room ID is missing or empty", dc.Key);
eisc = null;
break;
}

View File

@@ -12,15 +12,15 @@ using Serilog.Events;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a CecPortController
/// </summary>
public class CecPortController : Device, IBasicCommunicationWithStreamDebugging
/// <summary>
/// Represents a CecPortController
/// </summary>
public class CecPortController : Device, IBasicCommunicationWithStreamDebugging
{
/// <summary>
/// Gets or sets the StreamDebugging
/// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; }
/// <summary>
/// Gets or sets the StreamDebugging
/// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; }
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
@@ -33,16 +33,16 @@ namespace PepperDash.Essentials.Core
ICec Port;
public CecPortController(string key, Func<EssentialsControlPropertiesConfig, ICec> postActivationFunc,
EssentialsControlPropertiesConfig config) : base(key)
EssentialsControlPropertiesConfig config):base(key)
{
StreamDebugging = new CommunicationStreamDebugging(key);
StreamDebugging = new CommunicationStreamDebugging(key);
AddPostActivationAction(() =>
{
Port = postActivationFunc(config);
Port.StreamCec.CecChange += StreamCec_CecChange;
});
});
}
public CecPortController(string key, ICec port)
@@ -58,25 +58,27 @@ namespace PepperDash.Essentials.Core
if (args.EventId == CecEventIds.CecMessageReceivedEventId)
OnDataReceived(cecDevice.Received.StringValue);
else if (args.EventId == CecEventIds.ErrorFeedbackEventId)
if (cecDevice.ErrorFeedback.BoolValue)
if(cecDevice.ErrorFeedback.BoolValue)
Debug.LogMessage(LogEventLevel.Verbose, this, "CEC NAK Error");
}
void OnDataReceived(string s)
{
var bytesHandler = BytesReceived;
var bytesHandler = BytesReceived;
if (bytesHandler != null)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(s);
this.PrintReceivedBytes(bytes);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes));
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
var textHandler = TextReceived;
if (textHandler != null)
{
this.PrintReceivedText(s);
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
}
if (textHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
}
}
#region IBasicCommunication Members
@@ -88,7 +90,8 @@ namespace PepperDash.Essentials.Core
{
if (Port == null)
return;
this.PrintSentText(text);
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text);
Port.StreamCec.Send.StringValue = text;
}
@@ -100,8 +103,8 @@ namespace PepperDash.Essentials.Core
if (Port == null)
return;
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
this.PrintSentBytes(bytes);
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
Port.StreamCec.Send.StringValue = text;
}

View File

@@ -1,9 +1,7 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.GeneralIO;
using PepperDash.Core;
using PepperDash.Core.Logging;
using Serilog.Events;
@@ -25,10 +23,10 @@ namespace PepperDash.Essentials.Core
/// Event fired when bytes are received
/// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Event fired when text is received
/// </summary>
/// Event fired when text is received
/// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary>
@@ -40,12 +38,12 @@ namespace PepperDash.Essentials.Core
ComPort.ComPortSpec Spec;
/// <summary>
/// Constructor
/// </summary>
/// <param name="key"></param>
/// <param name="postActivationFunc"></param>
/// <param name="spec"></param>
/// <param name="config"></param>
/// Constructor
/// </summary>
/// <param name="key"></param>
/// <param name="postActivationFunc"></param>
/// <param name="spec"></param>
/// <param name="config"></param>
public ComPortController(string key, Func<EssentialsControlPropertiesConfig, ComPort> postActivationFunc,
ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key)
{
@@ -87,29 +85,61 @@ namespace PepperDash.Essentials.Core
{
if (Port == null)
{
this.LogInformation($"Configured {Port.Parent.GetType().Name}-comport-{Port.ID} for {Key} does not exist.");
Debug.LogMessage(LogEventLevel.Information, this, "Configured com Port for this device does not exist.");
return;
}
if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102)
// TODO [ ] - Remove commented out code once verified working
//if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102)
if (Port.Parent is GenericBase genericDevice && genericDevice.Registerable)
{
var result = Port.Register();
//this.LogInformation($"INFO: Attempting to register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID}");
var result = genericDevice.Register();
if (result != eDeviceRegistrationUnRegistrationResponse.Success)
{
this.LogError($"Cannot register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
return;
this.LogError($"ERROR: Cannot register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
return; // false
}
this.LogInformation($"Successfully registered {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
}
var specResult = Port.SetComPortSpec(Spec);
if (specResult != 0)
{
this.LogError($"Cannot set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
this.LogError($"ERROR: Cannot set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
return;
}
this.LogInformation($"Successfully set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
//this.LogInformation($"INFO: Successfully set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
// TODO [ ] - Remove debug logging once verified working
// if (Port.Parent is CenIoCom102)
// {
// Port.PropertyChanged += (s, e) =>
// {
// this.LogInformation($@"RegisterAndConfigureComPort: PropertyChanged Fired >>
// comPort-'{Port.ID}',
// Property Changed-'{e.Property}',
// Value Changed-'{e.Value}',
// deviceName-'{Port.DeviceName}',
// parentDevice-'{Port.ParentDevice}',
// parent-`{Port.Parent}`,
// online-`{Port.IsOnline}`,
// present-`{Port.Present}`,
// supportedBaudRates-'{Port.SupportedBaudRates}'");
// };
// Port.ExtendedInformationChanged += (s, e) =>
// {
// this.LogInformation($@"RegisterAndConfigureComPort: ExtendedInformationChanged Fired >>
// comPort-'{Port.ID}',
// {e.Protocol},
// {e.BaudRate},
// {e.Parity},
// {e.DataBits},
// {e.StopBits},
// HW Handshake-'{e.HardwareHandshakeSetting}',
// SW Handshake-'{e.SoftwareHandshakeSetting}'");
// };
// }
Port.SerialDataReceived += Port_SerialDataReceived;
}
@@ -135,14 +165,16 @@ namespace PepperDash.Essentials.Core
if (bytesHandler != null)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(s);
this.PrintReceivedBytes(bytes);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes));
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
eventSubscribed = true;
}
var textHandler = TextReceived;
if (textHandler != null)
{
this.PrintReceivedText(s);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
eventSubscribed = true;
}
@@ -169,7 +201,8 @@ namespace PepperDash.Essentials.Core
if (Port == null)
return;
this.PrintSentText(text);
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text);
Port.Send(text);
}
@@ -181,7 +214,8 @@ namespace PepperDash.Essentials.Core
if (Port == null)
return;
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
this.PrintSentBytes(bytes);
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
Port.Send(text);
}

View File

@@ -11,11 +11,11 @@ using PepperDash.Core;
namespace PepperDash.Essentials.Core.Config
{
/// <summary>
/// Loads the ConfigObject from the file
/// </summary>
public class EssentialsConfig : BasicConfig
{
/// <summary>
/// Loads the ConfigObject from the file
/// </summary>
public class EssentialsConfig : BasicConfig
{
/// <summary>
/// Gets or sets the SystemUrl
/// </summary>
@@ -32,33 +32,24 @@ namespace PepperDash.Essentials.Core.Config
/// Gets the SystemUuid extracted from the SystemUrl
/// </summary>
[JsonProperty("systemUuid")]
public string SystemUuid
public string SystemUuid
{
get
{
string uuid;
if (string.IsNullOrEmpty(SystemUrl))
return "missing url";
if (string.IsNullOrEmpty(SystemUrl))
{
uuid = "missing url";
}
else if (SystemUrl.Contains("#"))
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
uuid = result.Groups[1].Value;
}
else if (SystemUrl.Contains("detail"))
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/detail\/(.*)\/.*");
uuid = result.Groups[1].Value;
}
else
{
if (SystemUrl.Contains("#"))
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
string uuid = result.Groups[1].Value;
return uuid;
} else
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*");
uuid = result.Groups[1].Value;
string uuid = result.Groups[1].Value;
return uuid;
}
return uuid;
}
}
@@ -66,33 +57,24 @@ namespace PepperDash.Essentials.Core.Config
/// Gets the TemplateUuid extracted from the TemplateUrl
/// </summary>
[JsonProperty("templateUuid")]
public string TemplateUuid
public string TemplateUuid
{
get
{
string uuid;
if (string.IsNullOrEmpty(TemplateUrl))
return "missing template url";
if (string.IsNullOrEmpty(TemplateUrl))
{
uuid = "missing template url";
}
else if (TemplateUrl.Contains("#"))
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
uuid = result.Groups[1].Value;
}
else if (TemplateUrl.Contains("detail"))
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/detail\/(.*)\/system-template-versions\/detail\/(.*)\/.*");
uuid = result.Groups[2].Value;
}
else
{
if (TemplateUrl.Contains("#"))
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
string uuid = result.Groups[1].Value;
return uuid;
} else
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*");
uuid = result.Groups[2].Value;
string uuid = result.Groups[2].Value;
return uuid;
}
return uuid;
}
}
@@ -115,7 +97,7 @@ namespace PepperDash.Essentials.Core.Config
{
Rooms = new List<DeviceConfig>();
}
}
}
/// <summary>
/// Represents version data for Essentials and its packages
@@ -165,7 +147,7 @@ namespace PepperDash.Essentials.Core.Config
/// Represents a SystemTemplateConfigs
/// </summary>
public class SystemTemplateConfigs
{
{
/// <summary>
/// Gets or sets the System
/// </summary>
@@ -175,5 +157,5 @@ namespace PepperDash.Essentials.Core.Config
/// Gets or sets the Template
/// </summary>
public EssentialsConfig Template { get; set; }
}
}
}

View File

@@ -77,6 +77,9 @@ namespace PepperDash.Essentials.Core
/// A name that will override the source's name on the UI
/// </summary>
[JsonProperty("name")]
/// <summary>
/// Gets or sets the Name
/// </summary>
public string Name { get; set; }
/// <summary>

View File

@@ -14,14 +14,13 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
namespace PepperDash.Essentials.Core.Fusion
{
/// <summary>
/// Represents a EssentialsHuddleSpaceFusionSystemControllerBase
/// </summary>
public class IEssentialsRoomFusionController : EssentialsDevice, IOccupancyStatusProvider, IFusionHelpRequest, IHasFeedback
public class IEssentialsRoomFusionController : EssentialsDevice, IOccupancyStatusProvider, IFusionHelpRequest
{
private IEssentialsRoomFusionControllerPropertiesConfig _config;
@@ -88,17 +87,15 @@ namespace PepperDash.Essentials.Core.Fusion
/// <inheritdoc />
public StringFeedback HelpRequestStatusFeedback { get; private set; }
private Timer _helpRequestTimeoutTimer;
/// <summary>
/// Gets the DefaultHelpRequestTimeoutMs
/// </summary>
public int HelpRequestTimeoutMs => _config.HelpRequestTimeoutMs;
#region System Info Sigs
/// <summary>
/// Gets whether to use a timer for help requests
/// </summary>
public bool UseHelpRequestTimer => _config.UseTimeoutForHelpRequests;
//StringSigData SystemName;
//StringSigData Model;
//StringSigData SerialNumber;
//StringSigData Uptime;
#endregion
#region Processor Info Sigs
@@ -243,19 +240,6 @@ namespace PepperDash.Essentials.Core.Fusion
this.LogDebug("Occupancy setup complete");
HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.OutputSig.StringValue);
HelpRequestSentFeedback = new BoolFeedback("HelpRequestSent", () => _helpRequestSent);
HelpRequestStatusFeedback = new StringFeedback("HelpRequestStatus", () => _helpRequestStatus.ToString());
Feedbacks.Add(HelpRequestResponseFeedback);
Feedbacks.Add(HelpRequestSentFeedback);
Feedbacks.Add(HelpRequestStatusFeedback);
if (RoomOccupancyRemoteStringFeedback != null)
Feedbacks.Add(RoomOccupancyRemoteStringFeedback);
if (RoomIsOccupiedFeedback != null)
Feedbacks.Add(RoomIsOccupiedFeedback);
}
catch (Exception e)
{
@@ -319,6 +303,10 @@ namespace PepperDash.Essentials.Core.Fusion
FusionRVI.GenerateFileForAllFusionDevices();
HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.OutputSig.StringValue);
HelpRequestSentFeedback = new BoolFeedback("HelpRequestSent", () => _helpRequestSent);
HelpRequestStatusFeedback = new StringFeedback("HelpRequestStatus", () => _helpRequestStatus.ToString());
}
/// <summary>
@@ -351,11 +339,6 @@ namespace PepperDash.Essentials.Core.Fusion
#endregion
/// <inheritdoc />
public FeedbackCollection<Feedback> Feedbacks { get; private set; } = new FeedbackCollection<Feedback>();
/// <summary>
/// ScheduleChange event
/// </summary>
@@ -1789,7 +1772,7 @@ namespace PepperDash.Essentials.Core.Fusion
{
if (args.EventId == FusionEventIds.HelpMessageReceivedEventId)
{
this.LogInformation("Help message received from Fusion for room '{0}'",
this.LogInformation( "Help message received from Fusion for room '{0}'",
Room.Name);
this.LogDebug("Help message content: {0}", FusionRoom.Help.OutputSig.StringValue);
@@ -1808,7 +1791,7 @@ namespace PepperDash.Essentials.Core.Fusion
break;
case "Please call the helpdesk.":
// this.LogInformation("Please call the helpdesk.");
_helpRequestStatus = eFusionHelpResponse.CallHelpDesk;
// _helpRequestStatus = eFusionHelpResponse.CallHelpDesk;
break;
case "Please wait, I will reschedule your meeting to a different room.":
// this.LogInformation("Please wait, I will reschedule your meeting to a different room.",
@@ -1835,21 +1818,13 @@ namespace PepperDash.Essentials.Core.Fusion
_helpRequestStatus = eFusionHelpResponse.None;
}
if (_helpRequestStatus == eFusionHelpResponse.None)
if(_helpRequestStatus == eFusionHelpResponse.None)
{
_helpRequestSent = false;
HelpRequestSentFeedback.FireUpdate();
}
HelpRequestStatusFeedback.FireUpdate();
if (_helpRequestTimeoutTimer != null)
{
_helpRequestTimeoutTimer.Stop();
_helpRequestTimeoutTimer.Elapsed -= OnTimedEvent;
_helpRequestTimeoutTimer.Dispose();
_helpRequestTimeoutTimer = null;
}
}
@@ -1920,34 +1895,10 @@ namespace PepperDash.Essentials.Core.Fusion
_helpRequestSent = true;
HelpRequestSentFeedback.FireUpdate();
if (UseHelpRequestTimer)
{
if (_helpRequestTimeoutTimer == null)
{
_helpRequestTimeoutTimer = new Timer(HelpRequestTimeoutMs);
_helpRequestTimeoutTimer.AutoReset = false;
_helpRequestTimeoutTimer.Enabled = true;
_helpRequestTimeoutTimer.Elapsed += OnTimedEvent;
}
_helpRequestTimeoutTimer.Interval = HelpRequestTimeoutMs;
_helpRequestTimeoutTimer.Start();
this.LogDebug("Help request timeout timer started for room '{0}' with timeout of {1} ms.",
Room.Name, HelpRequestTimeoutMs);
}
_helpRequestStatus = eFusionHelpResponse.HelpRequested;
HelpRequestStatusFeedback.FireUpdate();
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
this.LogInformation("Help request timeout reached for room '{0}'. Cancelling help request.", Room.Name);
CancelHelpRequest();
}
/// <inheritdoc />
public void CancelHelpRequest()
{
@@ -1958,16 +1909,7 @@ namespace PepperDash.Essentials.Core.Fusion
HelpRequestSentFeedback.FireUpdate();
_helpRequestStatus = eFusionHelpResponse.None;
HelpRequestStatusFeedback.FireUpdate();
Debug.LogMessage(LogEventLevel.Information, this, "Help request cancelled for room '{0}'", Room.Name);
}
if (_helpRequestTimeoutTimer != null)
{
_helpRequestTimeoutTimer.Stop();
_helpRequestTimeoutTimer.Elapsed -= OnTimedEvent;
_helpRequestTimeoutTimer.Dispose();
_helpRequestTimeoutTimer = null;
this.LogDebug("Help request timeout timer stopped for room '{0}'.", Room.Name);
Debug.LogMessage(LogEventLevel.Information, this, "Help request cancelled in Fusion for room '{0}'", Room.Name);
}
}

View File

@@ -56,16 +56,4 @@ public class IEssentialsRoomFusionControllerPropertiesConfig
/// </summary>
[JsonProperty("use24HourTimeFormat")]
public bool Use24HourTimeFormat { get; set; } = false;
/// <summary>
/// Gets or sets whether to use a timeout for help requests
/// </summary>
[JsonProperty("useTimeoutForHelpRequests")]
public bool UseTimeoutForHelpRequests { get; set; } = false;
/// <summary>
/// Gets or sets the timeout duration for help requests in milliseconds
/// </summary>
[JsonProperty("helpRequestTimeoutMs")]
public int HelpRequestTimeoutMs { get; set; } = 30000;
}

View File

@@ -105,21 +105,12 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsRoomPropertiesConfig
{
/// <summary>
/// Gets or sets the Addresses
/// </summary>
[JsonProperty("addresses")]
public EssentialsRoomAddressPropertiesConfig Addresses { get; set; }
/// <summary>
/// Gets or sets the Description
/// </summary>
[JsonProperty("description")]
public string Description { get; set; }
/// <summary>
/// Gets or sets the Emergency
/// </summary>
[JsonProperty("emergency")]
public EssentialsRoomEmergencyConfig Emergency { get; set; }
@@ -235,11 +226,11 @@ namespace PepperDash.Essentials.Room.Config
/// Indicates if this room represents a combination of other rooms
/// </summary>
[JsonProperty("isRoomCombinationScenario")]
/// <summary>
/// Gets or sets the IsRoomCombinationScenario
/// </summary>
public bool IsRoomCombinationScenario { get; set; }
/// <summary>
/// Constructor
/// </summary>
public EssentialsRoomPropertiesConfig()
{
LogoLight = new EssentialsLogoPropertiesConfig();
@@ -252,10 +243,10 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsRoomUiBehaviorConfig
{
[JsonProperty("disableActivityButtonsWhileWarmingCooling")]
/// <summary>
/// Gets or sets the DisableActivityButtonsWhileWarmingCooling
/// </summary>
[JsonProperty("disableActivityButtonsWhileWarmingCooling")]
public bool DisableActivityButtonsWhileWarmingCooling { get; set; }
}
@@ -264,86 +255,74 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsAvRoomPropertiesConfig : EssentialsRoomPropertiesConfig
{
[JsonProperty("defaultAudioKey")]
/// <summary>
/// Gets or sets the DefaultAudioKey
/// </summary>
[JsonProperty("defaultAudioKey")]
public string DefaultAudioKey { get; set; }
/// <summary>
/// Gets or sets the DefaultOnDspPresetKey
/// </summary>
[JsonProperty("defaultOnDspPresetKey")]
public string DefaultOnDspPresetKey { get; set; }
/// <summary>
/// Gets or sets the DefaultOffDspPresetKey
/// </summary>
[JsonProperty("defaultOffDspPresetKey")]
public string DefaultOffDspPresetKey { get; set; }
[JsonProperty("sourceListKey")]
/// <summary>
/// Gets or sets the SourceListKey
/// </summary>
/// </summary>
[JsonProperty("sourceListKey")]
public string SourceListKey { get; set; }
[JsonProperty("destinationListKey")]
/// <summary>
/// Gets or sets the DestinationListKey
/// </summary>
[JsonProperty("destinationListKey")]
public string DestinationListKey { get; set; }
[JsonProperty("audioControlPointListKey")]
/// <summary>
/// Gets or sets the AudioControlPointListKey
/// </summary>
[JsonProperty("audioControlPointListKey")]
public string AudioControlPointListKey { get; set; }
[JsonProperty("cameraListKey")]
/// <summary>
/// Gets or sets the CameraListKey
/// </summary>
[JsonProperty("cameraListKey")]
public string CameraListKey { get; set; }
[JsonProperty("defaultSourceItem")]
/// <summary>
/// Gets or sets the DefaultSourceItem
/// </summary>
[JsonProperty("defaultSourceItem")]
public string DefaultSourceItem { get; set; }
/// <summary>
/// Indicates if the room supports advanced sharing
/// </summary>
[JsonProperty("supportsAdvancedSharing")]
/// <summary>
/// Gets or sets the SupportsAdvancedSharing
/// </summary>
public bool SupportsAdvancedSharing { get; set; }
/// <summary>
/// Indicates if non-tech users can change the share mode
/// </summary>
[JsonProperty("userCanChangeShareMode")]
/// <summary>
/// Gets or sets the UserCanChangeShareMode
/// </summary>
public bool UserCanChangeShareMode { get; set; }
[JsonProperty("matrixRoutingKey", NullValueHandling = NullValueHandling.Ignore)]
/// <summary>
/// Gets or sets the MatrixRoutingKey
/// </summary>
[JsonProperty("matrixRoutingKey", NullValueHandling = NullValueHandling.Ignore)]
public string MatrixRoutingKey { get; set; }
}
/// <summary>
/// Represents a EssentialsConferenceRoomPropertiesConfig
/// </summary>
public class EssentialsConferenceRoomPropertiesConfig : EssentialsAvRoomPropertiesConfig
{
[JsonProperty("videoCodecKey")]
/// <summary>
/// Gets or sets the VideoCodecKey
/// </summary>
[JsonProperty("videoCodecKey")]
public string VideoCodecKey { get; set; }
[JsonProperty("audioCodecKey")]
/// <summary>
/// Gets or sets the AudioCodecKey
/// </summary>
[JsonProperty("audioCodecKey")]
public string AudioCodecKey { get; set; }
}
@@ -358,15 +337,12 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public bool Enabled { get; set; }
[JsonProperty("deviceKeys")]
/// <summary>
/// Gets or sets the DeviceKeys
/// </summary>
[JsonProperty("deviceKeys")]
public List<string> DeviceKeys { get; set; }
/// <summary>
/// Constructor
/// </summary>
public EssentialsEnvironmentPropertiesConfig()
{
DeviceKeys = new List<string>();
@@ -379,9 +355,6 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsRoomFusionConfig
{
/// <summary>
/// Gets the the IpId as a UInt16
/// </summary>
public uint IpIdInt
{
get
@@ -398,16 +371,16 @@ namespace PepperDash.Essentials.Room.Config
}
}
[JsonProperty("ipId")]
/// <summary>
/// Gets or sets the IpId
/// </summary>
[JsonProperty("ipId")]
public string IpId { get; set; }
[JsonProperty("joinMapKey")]
/// <summary>
/// Gets or sets the JoinMapKey
/// </summary>
[JsonProperty("joinMapKey")]
public string JoinMapKey { get; set; }
}
@@ -417,16 +390,16 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsRoomMicrophonePrivacyConfig
{
[JsonProperty("deviceKey")]
/// <summary>
/// Gets or sets the DeviceKey
/// </summary>
[JsonProperty("deviceKey")]
public string DeviceKey { get; set; }
[JsonProperty("behaviour")]
/// <summary>
/// Gets or sets the Behaviour
/// </summary>
[JsonProperty("behaviour")]
public string Behaviour { get; set; }
}
@@ -435,15 +408,12 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsHelpPropertiesConfig
{
[JsonProperty("message")]
/// <summary>
/// Gets or sets the Message
/// </summary>
[JsonProperty("message")]
public string Message { get; set; }
/// <summary>
/// Gets or sets the ShowCallButton
/// </summary>
[JsonProperty("showCallButton")]
public bool ShowCallButton { get; set; }
@@ -451,11 +421,11 @@ namespace PepperDash.Essentials.Room.Config
/// Defaults to "Call Help Desk"
/// </summary>
[JsonProperty("callButtonText")]
/// <summary>
/// Gets or sets the CallButtonText
/// </summary>
public string CallButtonText { get; set; }
/// <summary>
/// Constructor
/// </summary>
public EssentialsHelpPropertiesConfig()
{
CallButtonText = "Call Help Desk";
@@ -467,28 +437,22 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsOneButtonMeetingPropertiesConfig
{
[JsonProperty("enable")]
/// <summary>
/// Gets or sets the Enable
/// </summary>
[JsonProperty("enable")]
public bool Enable { get; set; }
}
/// <summary>
/// Represents a EssentialsRoomAddressPropertiesConfig
/// </summary>
public class EssentialsRoomAddressPropertiesConfig
{
/// <summary>
/// Gets or sets the PhoneNumber
/// </summary>
[JsonProperty("phoneNumber")]
public string PhoneNumber { get; set; }
[JsonProperty("sipAddress")]
/// <summary>
/// Gets or sets the SipAddress
/// </summary>
[JsonProperty("sipAddress")]
public string SipAddress { get; set; }
}
@@ -498,18 +462,14 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsLogoPropertiesConfig
{
[JsonProperty("type")]
/// <summary>
/// Gets or sets the Type
/// </summary>
[JsonProperty("type")]
public string Type { get; set; }
/// <summary>
/// Gets or sets the Url
/// </summary>
[JsonProperty("url")]
public string Url { get; set; }
/// <summary>
/// GetLogoUrlLight method
/// </summary>
@@ -542,28 +502,22 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsRoomOccSensorConfig
{
[JsonProperty("deviceKey")]
/// <summary>
/// Gets or sets the DeviceKey
/// </summary>
[JsonProperty("deviceKey")]
public string DeviceKey { get; set; }
/// <summary>
/// Gets or sets the TimeoutMinutes
/// </summary>
[JsonProperty("timeoutMinutes")]
public int TimeoutMinutes { get; set; }
}
/// <summary>
/// Represents a EssentialsRoomTechConfig
/// </summary>
public class EssentialsRoomTechConfig
{
[JsonProperty("password")]
/// <summary>
/// Gets or sets the Password
/// </summary>
[JsonProperty("password")]
public string Password { get; set; }
}
}

View File

@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Crestron.SimplSharpPro.Keypads;
using Crestron.SimplSharpPro.Keypads;
using PepperDash.Essentials.Core.Queues;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Debug = PepperDash.Core.Debug;
@@ -115,7 +115,7 @@ namespace PepperDash.Essentials.Core
public static (RouteDescriptor, RouteDescriptor) GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort, RoutingOutputPort sourcePort)
{
// if it's a single signal type, find the route
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo) &&
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo) &&
!(signalType.HasFlag(eRoutingSignalType.Video) && signalType.HasFlag(eRoutingSignalType.SecondaryAudio)))
{
var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, signalType);
@@ -134,15 +134,14 @@ namespace PepperDash.Essentials.Core
}
// otherwise, audioVideo needs to be handled as two steps.
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {destinationKey} to {sourceKey} of type {type}", destination, source.Key, signalType);
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key);
RouteDescriptor audioRouteDescriptor;
if (signalType.HasFlag(eRoutingSignalType.SecondaryAudio))
{
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.SecondaryAudio);
}
else
} else
{
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Audio);
}
@@ -200,13 +199,13 @@ namespace PepperDash.Essentials.Core
Source = source,
SourcePort = sourcePort,
SignalType = signalType
};
};
var coolingDevice = destination as IWarmingCooling;
//We already have a route request for this device, and it's a cooling device and is cooling
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
{
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
@@ -220,7 +219,7 @@ namespace PepperDash.Essentials.Core
//New Request
if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
{
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
RouteRequests.Add(destination.Key, routeRequest);
@@ -240,9 +239,9 @@ namespace PepperDash.Essentials.Core
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
}
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, destinationPort?.Key ?? string.Empty, false));
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty, false));
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
}
/// <summary>
@@ -273,8 +272,7 @@ namespace PepperDash.Essentials.Core
audioOrSingleRoute.ExecuteRoutes();
videoRoute?.ExecuteRoutes();
}
catch (Exception ex)
} catch(Exception ex)
{
Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request);
}
@@ -307,10 +305,9 @@ namespace PepperDash.Essentials.Core
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
current.ReleaseRoutes(clearRoute);
}
}
catch (Exception ex)
} catch (Exception ex)
{
Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'", null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'",null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
}
}

View File

@@ -98,7 +98,7 @@ namespace PepperDash.Essentials.Core.Routing
/// <param name="inputPort">The currently selected input port on the destination device.</param>
private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this, destination?.Key, inputPort?.Key);
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this,destination?.Key, inputPort?.Key);
if(inputPort == null)
{

View File

@@ -6,9 +6,9 @@ using PepperDash.Core.Web.RequestHandlers;
namespace PepperDash.Essentials.Core.Web.RequestHandlers
{
/// <summary>
/// Represents a SetDeviceStreamDebugRequestHandler
/// </summary>
/// <summary>
/// Represents a SetDeviceStreamDebugRequestHandler
/// </summary>
public class SetDeviceStreamDebugRequestHandler : WebApiBaseRequestHandler
{
/// <summary>
@@ -122,23 +122,23 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
return;
}
if (!(DeviceManager.GetDeviceForKey(body.DeviceKey) is IStreamDebugging device))
{
context.Response.StatusCode = 404;
context.Response.StatusDescription = "Not Found";
context.Response.End();
if (!(DeviceManager.GetDeviceForKey(body.DeviceKey) is IStreamDebugging device))
{
context.Response.StatusCode = 404;
context.Response.StatusDescription = "Not Found";
context.Response.End();
return;
}
return;
}
eStreamDebuggingSetting debugSetting;
eStreamDebuggingSetting debugSetting;
try
{
debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), body.Setting, true);
debugSetting = (eStreamDebuggingSetting) Enum.Parse(typeof (eStreamDebuggingSetting), body.Setting, true);
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Exception handling set debug request");
Debug.LogMessage(ex, "Exception handling set debug request");
context.Response.StatusCode = 500;
context.Response.StatusDescription = "Internal Server Error";
context.Response.End();
@@ -164,7 +164,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Exception handling set debug request");
Debug.LogMessage(ex, "Exception handling set debug request");
context.Response.StatusCode = 500;
context.Response.StatusDescription = "Internal Server Error";
context.Response.End();
@@ -198,21 +198,21 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
public class SetDeviceStreamDebugConfig
{
[JsonProperty("deviceKey", NullValueHandling = NullValueHandling.Include)]
/// <summary>
/// Gets or sets the DeviceKey
/// </summary>
/// <summary>
/// Gets or sets the DeviceKey
/// </summary>
public string DeviceKey { get; set; }
[JsonProperty("setting", NullValueHandling = NullValueHandling.Include)]
/// <summary>
/// Gets or sets the Setting
/// </summary>
/// <summary>
/// Gets or sets the Setting
/// </summary>
public string Setting { get; set; }
[JsonProperty("timeout")]
/// <summary>
/// Gets or sets the Timeout
/// </summary>
/// <summary>
/// Gets or sets the Timeout
/// </summary>
public int Timeout { get; set; }
public SetDeviceStreamDebugConfig()

View File

@@ -1,27 +1,15 @@
using System;
using System.Collections.Generic;
using System.Timers;
using Crestron.SimplSharp;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.CrestronIO;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Devices.Common.Displays;
using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Shades
{
/// <summary>
/// Enumeration for requested state
/// </summary>
enum RequestedState
{
None,
Raise,
Lower
}
/// <summary>
/// Controls a single shade using three relays
/// </summary>
@@ -32,16 +20,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
readonly ScreenLiftRelaysConfig LowerRelayConfig;
readonly ScreenLiftRelaysConfig LatchedRelayConfig;
DisplayBase DisplayDevice;
Displays.DisplayBase DisplayDevice;
ISwitchedOutput RaiseRelay;
ISwitchedOutput LowerRelay;
ISwitchedOutput LatchedRelay;
private bool _isMoving;
private RequestedState _requestedState;
private RequestedState _currentMovement;
private Timer _movementTimer;
/// <summary>
/// Gets or sets the InUpPosition
/// </summary>
@@ -97,11 +80,6 @@ namespace PepperDash.Essentials.Devices.Common.Shades
IsInUpPosition = new BoolFeedback("isInUpPosition", () => _isInUpPosition);
// Initialize movement timer for reuse
_movementTimer = new Timer();
_movementTimer.Elapsed += OnMovementComplete;
_movementTimer.AutoReset = false;
switch (Mode)
{
case eScreenLiftControlMode.momentary:
@@ -151,25 +129,25 @@ namespace PepperDash.Essentials.Devices.Common.Shades
{
case eScreenLiftControlMode.momentary:
{
this.LogDebug("Getting relays for {mode}", Mode);
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting relays for {Mode}");
RaiseRelay = GetSwitchedOutputFromDevice(RaiseRelayConfig.DeviceKey);
LowerRelay = GetSwitchedOutputFromDevice(LowerRelayConfig.DeviceKey);
break;
}
case eScreenLiftControlMode.latched:
{
this.LogDebug("Getting relays for {mode}", Mode);
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting relays for {Mode}");
LatchedRelay = GetSwitchedOutputFromDevice(LatchedRelayConfig.DeviceKey);
break;
}
}
this.LogDebug("Getting display with key {displayKey}", DisplayDeviceKey);
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting display with key {DisplayDeviceKey}");
DisplayDevice = GetDisplayBaseFromDevice(DisplayDeviceKey);
if (DisplayDevice != null)
{
this.LogDebug("Subscribing to {displayKey} feedbacks", DisplayDeviceKey);
Debug.LogMessage(LogEventLevel.Debug, this, $"Subscribing to {DisplayDeviceKey} feedbacks");
DisplayDevice.IsWarmingUpFeedback.OutputChange += IsWarmingUpFeedback_OutputChange;
DisplayDevice.IsCoolingDownFeedback.OutputChange += IsCoolingDownFeedback_OutputChange;
@@ -185,49 +163,22 @@ namespace PepperDash.Essentials.Devices.Common.Shades
{
if (RaiseRelay == null && LatchedRelay == null) return;
this.LogDebug("Raise called for {type}", Type);
// If device is moving, bank the command
if (_isMoving)
{
this.LogDebug("Device is moving, banking Raise command");
_requestedState = RequestedState.Raise;
return;
}
this.LogDebug("Raising {type}", Type);
Debug.LogMessage(LogEventLevel.Debug, this, $"Raising {Type}");
switch (Mode)
{
case eScreenLiftControlMode.momentary:
{
PulseOutput(RaiseRelay, RaiseRelayConfig.PulseTimeInMs);
// Set moving flag and start timer if movement time is configured
if (RaiseRelayConfig.MoveTimeInMs > 0)
{
_isMoving = true;
_currentMovement = RequestedState.Raise;
if (_movementTimer.Enabled)
{
_movementTimer.Stop();
}
_movementTimer.Interval = RaiseRelayConfig.MoveTimeInMs;
_movementTimer.Start();
}
else
{
InUpPosition = true;
}
break;
}
case eScreenLiftControlMode.latched:
{
LatchedRelay.Off();
InUpPosition = true;
break;
}
}
InUpPosition = true;
}
/// <summary>
@@ -237,145 +188,59 @@ namespace PepperDash.Essentials.Devices.Common.Shades
{
if (LowerRelay == null && LatchedRelay == null) return;
this.LogDebug("Lower called for {type}", Type);
// If device is moving, bank the command
if (_isMoving)
{
this.LogDebug("Device is moving, banking Lower command");
_requestedState = RequestedState.Lower;
return;
}
this.LogDebug("Lowering {type}", Type);
Debug.LogMessage(LogEventLevel.Debug, this, $"Lowering {Type}");
switch (Mode)
{
case eScreenLiftControlMode.momentary:
{
PulseOutput(LowerRelay, LowerRelayConfig.PulseTimeInMs);
// Set moving flag and start timer if movement time is configured
if (LowerRelayConfig.MoveTimeInMs > 0)
{
_isMoving = true;
_currentMovement = RequestedState.Lower;
if (_movementTimer.Enabled)
{
_movementTimer.Stop();
}
_movementTimer.Interval = LowerRelayConfig.MoveTimeInMs;
_movementTimer.Start();
}
else
{
InUpPosition = false;
}
break;
}
case eScreenLiftControlMode.latched:
{
LatchedRelay.On();
InUpPosition = false;
break;
}
}
InUpPosition = false;
}
private void DisposeMovementTimer()
void PulseOutput(ISwitchedOutput output, int pulseTime)
{
if (_movementTimer != null)
{
_movementTimer.Stop();
_movementTimer.Elapsed -= OnMovementComplete;
_movementTimer.Dispose();
_movementTimer = null;
}
output.On();
CTimer pulseTimer = new CTimer(new CTimerCallbackFunction((o) => output.Off()), pulseTime);
}
/// <summary>
/// Called when movement timer completes
/// Attempts to get the port on teh specified device from config
/// </summary>
private void OnMovementComplete(object sender, ElapsedEventArgs e)
/// <param name="relayKey"></param>
/// <returns></returns>
ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey)
{
this.LogDebug("Movement complete");
// Update position based on completed movement
if (_currentMovement == RequestedState.Raise)
{
InUpPosition = true;
}
else if (_currentMovement == RequestedState.Lower)
{
InUpPosition = false;
}
_isMoving = false;
_currentMovement = RequestedState.None;
// Execute banked command if one exists
if (_requestedState != RequestedState.None)
{
this.LogDebug("Executing next command: {command}", _requestedState);
var commandToExecute = _requestedState;
_requestedState = RequestedState.None;
// Check if current state matches what the banked command would do and execute if different
switch (commandToExecute)
{
case RequestedState.Raise:
Raise();
break;
case RequestedState.Lower:
Lower();
break;
}
}
}
private void PulseOutput(ISwitchedOutput output, int pulseTime)
{
output.On();
var timer = new Timer(pulseTime)
{
AutoReset = false
};
timer.Elapsed += (sender, e) =>
{
output.Off();
timer.Dispose();
};
timer.Start();
}
private ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey)
{
var portDevice = DeviceManager.GetDeviceForKey<ISwitchedOutput>(relayKey);
var portDevice = DeviceManager.GetDeviceForKey(relayKey);
if (portDevice != null)
{
return portDevice;
return portDevice as ISwitchedOutput;
}
else
{
this.LogWarning("Error: Unable to get relay device with key '{relayKey}'", relayKey);
Debug.LogMessage(LogEventLevel.Debug, this, "Error: Unable to get relay device with key '{0}'", relayKey);
return null;
}
}
private DisplayBase GetDisplayBaseFromDevice(string displayKey)
Displays.DisplayBase GetDisplayBaseFromDevice(string displayKey)
{
var displayDevice = DeviceManager.GetDeviceForKey<DisplayBase>(displayKey);
var displayDevice = DeviceManager.GetDeviceForKey(displayKey);
if (displayDevice != null)
{
return displayDevice;
return displayDevice as Displays.DisplayBase;
}
else
{
this.LogWarning("Error: Unable to get display device with key '{displayKey}'", displayKey);
Debug.LogMessage(LogEventLevel.Debug, this, "Error: Unable to get display device with key '{0}'", displayKey);
return null;
}
}
@@ -383,7 +248,7 @@ namespace PepperDash.Essentials.Devices.Common.Shades
}
/// <summary>
/// Factory for ScreenLiftController devices
/// Represents a ScreenLiftControllerFactory
/// </summary>
public class ScreenLiftControllerFactory : EssentialsDeviceFactory<RelayControlledShade>
{
@@ -395,11 +260,14 @@ namespace PepperDash.Essentials.Devices.Common.Shades
TypeNames = new List<string>() { "screenliftcontroller" };
}
/// <summary>
/// BuildDevice method
/// </summary>
/// <inheritdoc />
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.LogDebug("Factory Attempting to create new ScreenLiftController Device");
var props = dc.Properties.ToObject<ScreenLiftControllerConfigProperties>();
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Comm Device");
var props = Newtonsoft.Json.JsonConvert.DeserializeObject<ScreenLiftControllerConfigProperties>(dc.Properties.ToString());
return new ScreenLiftController(dc.Key, dc.Name, props);
}

View File

@@ -18,11 +18,5 @@ namespace PepperDash.Essentials.Devices.Common.Shades
/// </summary>
[JsonProperty("pulseTimeInMs")]
public int PulseTimeInMs { get; set; }
/// <summary>
/// Gets or sets the MoveTimeInMs - time in milliseconds for the movement to complete
/// </summary>
[JsonProperty("moveTimeInMs")]
public int MoveTimeInMs { get; set; }
}
}

View File

@@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Generic
@@ -12,17 +9,8 @@ namespace PepperDash.Essentials.Devices.Common.Generic
/// <summary>
/// Represents a GenericSink
/// </summary>
public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputPort, ICurrentSources
public class GenericSink : EssentialsDevice, IRoutingSinkWithInputPort
{
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; private set; }
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
/// <inheritdoc />
public event EventHandler CurrentSourcesChanged;
/// <summary>
/// Initializes a new instance of the GenericSink class
/// </summary>
@@ -32,52 +20,9 @@ namespace PepperDash.Essentials.Devices.Common.Generic
{
InputPorts = new RoutingPortCollection<RoutingInputPort>();
var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo | eRoutingSignalType.SecondaryAudio, eRoutingPortConnectionType.Hdmi, null, this);
var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this);
InputPorts.Add(inputPort);
CurrentSources = new Dictionary<eRoutingSignalType, SourceListItem>
{
{ eRoutingSignalType.Audio, null },
{ eRoutingSignalType.Video, null },
};
CurrentSourceKeys = new Dictionary<eRoutingSignalType, string>
{
{ eRoutingSignalType.Audio, string.Empty },
{ eRoutingSignalType.Video, string.Empty },
};
}
/// <inheritdoc />
public void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
{
foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType)))
{
var flagValue = Convert.ToInt32(type);
// Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag).
// (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set.
if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0)
{
this.LogDebug("Skipping {type}", type);
continue;
}
this.LogDebug("setting {type}", type);
if (signalType.HasFlag(type))
{
UpdateCurrentSources(type, sourceListKey, sourceListItem);
}
}
// Raise the CurrentSourcesChanged event
CurrentSourcesChanged?.Invoke(this, EventArgs.Empty);
}
private void UpdateCurrentSources(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
{
CurrentSources[signalType] = sourceListItem;
CurrentSourceKeys[signalType] = sourceListKey;
}
/// <summary>
@@ -121,15 +66,6 @@ namespace PepperDash.Essentials.Devices.Common.Generic
/// Event fired when the current source changes
/// </summary>
public event SourceInfoChangeHandler CurrentSourceChange;
/// <inheritdoc />
public event InputChangedEventHandler InputChanged;
/// <inheritdoc />
public void ExecuteSwitch(object inputSelector)
{
this.LogDebug("GenericSink Executing Switch to: {inputSelector}", inputSelector);
}
}
/// <summary>

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
@@ -40,14 +39,10 @@ namespace PepperDash.Essentials.AppServer.Messengers
sourceDevice.CurrentSourcesChanged += (sender, e) =>
{
// need to copy the dictionaries to avoid enumeration issues
var currentSourceKeys = sourceDevice.CurrentSourceKeys.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
var currentSources = sourceDevice.CurrentSources.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
PostStatusMessage(JToken.FromObject(new
{
currentSourceKeys,
currentSources,
currentSourceKeys = sourceDevice.CurrentSourceKeys,
currentSources = sourceDevice.CurrentSources
}));
};
}

View File

@@ -130,33 +130,34 @@ namespace PepperDash.Essentials.AppServer.Messengers
feedback.MuteFeedback.OutputChange += (sender, args) =>
{
var message = new VolumeStateMessage
{
Volume = new Volume
{
Muted = args.BoolValue
}
};
PostStatusMessage(JToken.FromObject(message));
PostStatusMessage(JToken.FromObject(
new
{
volume = new
{
muted = args.BoolValue
}
})
);
};
feedback.VolumeLevelFeedback.OutputChange += (sender, args) =>
{
var message = new VolumeStateMessage
var rawValue = "";
if (feedback is IBasicVolumeWithFeedbackAdvanced volumeAdvanced)
{
Volume = new Volume
rawValue = volumeAdvanced.RawVolumeLevel.ToString();
}
var message = new
{
volume = new
{
Level = args.IntValue,
level = args.IntValue,
rawValue
}
};
if (device is IBasicVolumeWithFeedbackAdvanced volumeAdvanced)
{
message.Volume.RawValue = volumeAdvanced.RawVolumeLevel.ToString();
message.Volume.Units = volumeAdvanced.Units;
}
PostStatusMessage(JToken.FromObject(message));
};
}

View File

@@ -31,12 +31,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// <remarks>
/// Unsoliciited feedback from a device in a messenger will ONLY be sent to devices in this subscription list. When a client disconnects, it's ID will be removed from the collection.
/// </remarks>
private readonly HashSet<string> subscriberIds = new HashSet<string>();
/// <summary>
/// Lock object for thread-safe access to SubscriberIds
/// </summary>
private readonly object _subscriberLock = new object();
protected HashSet<string> SubscriberIds = new HashSet<string>();
private readonly List<string> _deviceInterfaces;
@@ -194,18 +189,18 @@ namespace PepperDash.Essentials.AppServer.Messengers
{
if (!enableMessengerSubscriptions)
{
this.LogWarning("Messenger subscriptions not enabled");
return;
}
lock (_subscriberLock)
if (SubscriberIds.Any(id => id == clientId))
{
if (!subscriberIds.Add(clientId))
{
this.LogVerbose("Client {clientId} already subscribed", clientId);
return;
}
this.LogVerbose("Client {clientId} already subscribed", clientId);
return;
}
SubscriberIds.Add(clientId);
this.LogDebug("Client {clientId} subscribed", clientId);
}
@@ -217,26 +212,19 @@ namespace PepperDash.Essentials.AppServer.Messengers
{
if (!enableMessengerSubscriptions)
{
this.LogWarning("Messenger subscriptions not enabled");
return;
}
bool wasSubscribed;
lock (_subscriberLock)
{
wasSubscribed = subscriberIds.Contains(clientId);
if (wasSubscribed)
{
subscriberIds.Remove(clientId);
}
}
if (!wasSubscribed)
if (!SubscriberIds.Any(i => i == clientId))
{
this.LogVerbose("Client with ID {clientId} is not subscribed", clientId);
return;
}
this.LogDebug("Client with ID {clientId} unsubscribed", clientId);
SubscriberIds.RemoveWhere((i) => i == clientId);
this.LogInformation("Client with ID {clientId} unsubscribed", clientId);
}
/// <summary>
@@ -270,8 +258,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
}
catch (Exception ex)
{
this.LogError("Exception posting status message for {messagePath} to {clientId}: {message}", MessagePath, clientId ?? "all clients", ex.Message);
this.LogDebug(ex, "Stack trace: ");
this.LogError(ex, "Exception posting status message for {messagePath} to {clientId}", MessagePath, clientId ?? "all clients");
}
}
@@ -300,8 +287,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
}
catch (Exception ex)
{
this.LogError("Exception posting status message for {type} to {clientId}: {message}", type, clientId ?? "all clients", ex.Message);
this.LogDebug(ex, "Stack trace: ");
this.LogError(ex, "Exception posting status message for {type} to {clientId}", type, clientId ?? "all clients");
}
}
@@ -326,14 +312,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
// If client is null or empty, this message is unsolicited feedback. Iterate through the subscriber list and send to all interested parties
if (string.IsNullOrEmpty(clientId))
{
// Create a snapshot of subscribers to avoid collection modification during iteration
List<string> subscriberSnapshot;
lock (_subscriberLock)
{
subscriberSnapshot = new List<string>(subscriberIds);
}
foreach (var client in subscriberSnapshot)
foreach (var client in SubscriberIds)
{
AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = client, Content = content });
}

View File

@@ -1312,11 +1312,6 @@ namespace PepperDash.Essentials
/// <inheritdoc />
public override void Initialize()
{
if (!Config.EnableMessengerSubscriptions)
{
this.LogWarning("Messenger subscriptions disabled. add \"enableMessengerSubscriptions\": true to config for {key} to enable.", Key);
}
foreach (var messenger in _messengers)
{
try
@@ -1748,7 +1743,7 @@ namespace PepperDash.Essentials
var clientNo = 1;
foreach (var clientContext in _directServer.UiClientContexts)
{
var clients = _directServer.UiClients.Values.Where(c => c.TokenKey == clientContext.Key);
var clients = _directServer.UiClients.Values.Where(c => c.Token == clientContext.Value.Token.Token);
CrestronConsole.ConsoleCommandResponse(
$"\r\nClient {clientNo}:\r\n" +

View File

@@ -40,7 +40,7 @@ namespace PepperDash.Essentials.Touchpanel
this.LogInformation("Setting theme to {theme}", theme.Value);
_tpDevice.UpdateTheme(theme.Value);
PostStatusMessage(JToken.FromObject(new { theme = theme.Value }), clientId: id);
PostStatusMessage(JToken.FromObject(new { theme = theme.Value }), id);
});
}
}

View File

@@ -1,4 +1,3 @@
using System.Threading;
using PepperDash.Core;
using PepperDash.Core.Logging;
using WebSocketSharp;
@@ -13,12 +12,13 @@ namespace PepperDash.Essentials
private static int nextClientId = 0;
/// <summary>
/// Get the next unique client ID (thread-safe)
/// Get the next unique client ID
/// </summary>
/// <returns>Client ID</returns>
public static int GetNextClientId()
{
return Interlocked.Increment(ref nextClientId);
nextClientId++;
return nextClientId;
}
/// <summary>
/// Converts a WebSocketServer LogData object to Essentials logging calls.

View File

@@ -64,12 +64,6 @@ namespace PepperDash.Essentials.WebSocketServer
[JsonProperty("userAppUrl")]
public string UserAppUrl { get; set; }
/// <summary>
/// Gets or sets the WebSocketUrl with clientId query parameter
/// </summary>
[JsonProperty("webSocketUrl")]
public string WebSocketUrl { get; set; }
/// <summary>
/// Gets or sets the EnableDebug

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
@@ -60,24 +59,12 @@ namespace PepperDash.Essentials.WebSocketServer
/// </summary>
public Dictionary<string, UiClientContext> UiClientContexts { get; private set; }
private readonly ConcurrentDictionary<string, UiClient> uiClients = new ConcurrentDictionary<string, UiClient>();
/// <summary>
/// Stores pending client registrations using composite key: token-clientId
/// This ensures the correct client ID is matched even when connections establish out of order
/// </summary>
private readonly ConcurrentDictionary<string, string> pendingClientRegistrations = new ConcurrentDictionary<string, string>();
/// <summary>
/// Stores queues of pending client IDs per token for legacy clients (FIFO)
/// This ensures thread-safety when multiple legacy clients use the same token
/// </summary>
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> legacyClientIdQueues = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
private readonly Dictionary<string, UiClient> uiClients = new Dictionary<string, UiClient>();
/// <summary>
/// Gets the collection of UI clients
/// </summary>
public IReadOnlyDictionary<string, UiClient> UiClients => uiClients;
public ReadOnlyDictionary<string, UiClient> UiClients => new ReadOnlyDictionary<string, UiClient>(uiClients);
private readonly MobileControlSystemController _parent;
@@ -736,95 +723,23 @@ namespace PepperDash.Essentials.WebSocketServer
private UiClient BuildUiClient(string roomKey, JoinToken token, string key)
{
// Dequeue the next clientId for legacy client support (FIFO per token)
// New clients will override this ID in OnOpen with the validated query parameter value
var clientId = "pending";
if (legacyClientIdQueues.TryGetValue(key, out var queue) && queue.TryDequeue(out var dequeuedId))
{
clientId = dequeuedId;
this.LogVerbose("Dequeued legacy clientId {clientId} for token {token}", clientId, key);
}
var c = new UiClient($"uiclient-{key}-{roomKey}-{clientId}", clientId, token.Token, token.TouchpanelKey);
this.LogInformation("Constructing UiClient with key {key} and temporary ID (will be set from query param)", key);
var c = new UiClient($"uiclient-{key}-{roomKey}-{token.Id}", token.Id, token.Token, token.TouchpanelKey);
this.LogInformation("Constructing UiClient with key {key} and ID {id}", key, token.Id);
c.Controller = _parent;
c.RoomKey = roomKey;
c.TokenKey = key; // Store the URL token key for filtering
c.Server = this; // Give UiClient access to server for ID registration
// Don't add to uiClients yet - will be added in OnOpen after ID is set from query param
c.ConnectionClosed += (o, a) =>
if (uiClients.ContainsKey(token.Id))
{
uiClients.TryRemove(a.ClientId, out _);
// Clean up any pending registrations for this token
var keysToRemove = pendingClientRegistrations.Keys
.Where(k => k.StartsWith($"{key}-"))
.ToList();
foreach (var k in keysToRemove)
{
pendingClientRegistrations.TryRemove(k, out _);
}
// Clean up legacy queue if empty
if (legacyClientIdQueues.TryGetValue(key, out var legacyQueue) && legacyQueue.IsEmpty)
{
legacyClientIdQueues.TryRemove(key, out _);
}
};
this.LogWarning("removing client with duplicate id {id}", token.Id);
uiClients.Remove(token.Id);
}
uiClients.Add(token.Id, c);
// UiClients[key].SetClient(c);
c.ConnectionClosed += (o, a) => uiClients.Remove(a.ClientId);
token.Id = null;
return c;
}
/// <summary>
/// Registers a UiClient with its validated client ID after WebSocket connection
/// </summary>
/// <param name="client">The UiClient to register</param>
/// <param name="clientId">The validated client ID</param>
/// <param name="tokenKey">The token key for validation</param>
/// <returns>True if registration successful, false if validation failed</returns>
public bool RegisterUiClient(UiClient client, string clientId, string tokenKey)
{
var registrationKey = $"{tokenKey}-{clientId}";
// Verify this clientId was generated during a join request for this token
if (!pendingClientRegistrations.TryRemove(registrationKey, out _))
{
this.LogWarning("Client attempted to connect with unregistered or expired clientId {clientId} for token {token}", clientId, tokenKey);
return false;
}
// Registration is valid - add to active clients
uiClients.AddOrUpdate(clientId, client, (id, existingClient) =>
{
this.LogWarning("Replacing existing client with duplicate id {id}", id);
return client;
});
this.LogInformation("Successfully registered UiClient with ID {clientId} for token {token}", clientId, tokenKey);
return true;
}
/// <summary>
/// Registers a UiClient using legacy flow (for backwards compatibility with older clients)
/// </summary>
/// <param name="client">The UiClient to register</param>
public void RegisterLegacyUiClient(UiClient client)
{
if (string.IsNullOrEmpty(client.Id))
{
this.LogError("Cannot register client with null or empty ID");
return;
}
uiClients.AddOrUpdate(client.Id, client, (id, existingClient) =>
{
this.LogWarning("Replacing existing client with duplicate id {id} (legacy flow)", id);
return client;
});
this.LogInformation("Successfully registered UiClient with ID {clientId} using legacy flow", client.Id);
}
/// <summary>
/// Prints out the session data for each path
/// </summary>
@@ -1131,22 +1046,10 @@ namespace PepperDash.Essentials.WebSocketServer
});
}
// Generate a client ID for this join request
var clientId = $"{Utilities.GetNextClientId()}";
// Store in pending registrations for new clients that send clientId via query param
var registrationKey = $"{token}-{clientId}";
pendingClientRegistrations.TryAdd(registrationKey, clientId);
// Also enqueue for legacy clients (thread-safe FIFO per token)
var queue = legacyClientIdQueues.GetOrAdd(token, _ => new ConcurrentQueue<string>());
queue.Enqueue(clientId);
clientContext.Token.Id = clientId;
this.LogVerbose("Assigning ClientId: {clientId} for token: {token}", clientId, token);
// Construct WebSocket URL with clientId query parameter
var wsProtocol = "ws";
var wsUrl = $"{wsProtocol}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}";
this.LogVerbose("Assigning ClientId: {clientId}", clientId);
// Construct the response object
JoinResponse jRes = new JoinResponse
@@ -1161,7 +1064,6 @@ 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),
WebSocketUrl = wsUrl,
EnableDebug = false,
DeviceInterfaceSupport = deviceInterfaces
};

View File

@@ -31,11 +31,6 @@ namespace PepperDash.Essentials.WebSocketServer
/// </summary>
public string Token { get; private set; }
/// <summary>
/// The URL token key used to connect (from UiClientContexts dictionary key)
/// </summary>
public string TokenKey { get; set; }
/// <summary>
/// Touchpanel Key associated with this client
/// </summary>
@@ -46,11 +41,6 @@ namespace PepperDash.Essentials.WebSocketServer
/// </summary>
public MobileControlSystemController Controller { get; set; }
/// <summary>
/// Gets or sets the server instance for client registration
/// </summary>
public MobileControlWebsocketServer Server { get; set; }
/// <summary>
/// Gets or sets the room key that this client is associated with
/// </summary>
@@ -109,50 +99,6 @@ namespace PepperDash.Essentials.WebSocketServer
Log.Output = (data, message) => Utilities.ConvertWebsocketLog(data, message, this);
Log.Level = LogLevel.Trace;
// Get clientId from query parameter
var queryString = Context.QueryString;
var clientId = queryString["clientId"];
if (!string.IsNullOrEmpty(clientId))
{
// New behavior: Validate and register with the server using provided clientId
if (Server == null || !Server.RegisterUiClient(this, clientId, TokenKey))
{
this.LogError("Failed to register client with ID {clientId}. Invalid or expired registration.", clientId);
Context.WebSocket.Close(CloseStatusCode.PolicyViolation, "Invalid or expired clientId");
return;
}
// Update this client's ID to the validated one
Id = clientId;
Key = $"uiclient-{TokenKey}-{RoomKey}-{clientId}";
this.LogInformation("Client {clientId} successfully connected and registered (new flow)", clientId);
}
else
{
// Legacy behavior: Use clientId from Token.Id (generated in HandleJoinRequest)
this.LogInformation("Client connected without clientId query parameter. Using legacy registration flow.");
// Id is already set from Token in constructor, use it
if (string.IsNullOrEmpty(Id))
{
this.LogError("Legacy client has no ID from token. Connection will be closed.");
Context.WebSocket.Close(CloseStatusCode.PolicyViolation, "No client ID available");
return;
}
Key = $"uiclient-{TokenKey}-{RoomKey}-{Id}";
// Register directly to active clients (legacy flow)
if (Server != null)
{
Server.RegisterLegacyUiClient(this);
}
this.LogInformation("Client {clientId} registered using legacy flow", Id);
}
if (Controller == null)
{
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");

View File

@@ -662,7 +662,6 @@ namespace PepperDash.Essentials
if (jsonFiles.Length > 1)
{
Debug.LogError("Multiple configuration files found in application directory: {@jsonFiles}", jsonFiles.Select(f => f.FullName).ToArray());
throw new Exception("Multiple configuration files found. Cannot continue.");
}