From ff609dfecd795b5da012481a0f4e00744e4b1580 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Fri, 31 Oct 2025 09:01:54 -0500 Subject: [PATCH 1/5] fix: add config option to turn echo off for SSH In addition, removed CTimer in favor of System.Threading.Timers Timer in the SSH class, and modified the class to be thread-safe. --- src/PepperDash.Core/Comm/GenericSshClient.cs | 250 ++++++++---- .../Comm/GenericTcpIpClient.cs | 365 +++++++++--------- .../Comm and IR/CommFactory.cs | 19 +- 3 files changed, 373 insertions(+), 261 deletions(-) diff --git a/src/PepperDash.Core/Comm/GenericSshClient.cs b/src/PepperDash.Core/Comm/GenericSshClient.cs index 13192ed5..2277a21c 100644 --- a/src/PepperDash.Core/Comm/GenericSshClient.cs +++ b/src/PepperDash.Core/Comm/GenericSshClient.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text; using System.Threading; using Crestron.SimplSharp; @@ -11,11 +12,12 @@ using Renci.SshNet.Common; namespace PepperDash.Core { /// - /// + /// SSH Client /// public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect { private const string SPlusKey = "Uninitialized SshClient"; + /// /// Object to enable stream debugging /// @@ -36,11 +38,6 @@ namespace PepperDash.Core /// public event EventHandler ConnectionChange; - /// - ///// - ///// - //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; - /// /// Gets or sets the Hostname /// @@ -67,7 +64,7 @@ namespace PepperDash.Core public bool IsConnected { // returns false if no client or not connected - get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + get { return client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } /// @@ -83,16 +80,26 @@ namespace PepperDash.Core /// public SocketStatus ClientStatus { - get { return _ClientStatus; } + get { lock (_stateLock) { return _ClientStatus; } } private set { - if (_ClientStatus == value) - return; - _ClientStatus = value; - OnConnectionChange(); + bool shouldFireEvent = false; + lock (_stateLock) + { + if (_ClientStatus != value) + { + _ClientStatus = value; + shouldFireEvent = true; + } + } + // Fire event outside lock to avoid deadlock + if (shouldFireEvent) + OnConnectionChange(); } } - SocketStatus _ClientStatus; + + private SocketStatus _ClientStatus; + private bool _ConnectEnabled; /// /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event @@ -100,7 +107,7 @@ namespace PepperDash.Core /// public ushort UStatus { - get { return (ushort)_ClientStatus; } + get { lock (_stateLock) { return (ushort)_ClientStatus; } } } /// @@ -111,7 +118,11 @@ namespace PepperDash.Core /// /// Will be set and unset by connect and disconnect only /// - public bool ConnectEnabled { get; private set; } + public bool ConnectEnabled + { + get { lock (_stateLock) { return _ConnectEnabled; } } + private set { lock (_stateLock) { _ConnectEnabled = value; } } + } /// /// S+ helper for AutoReconnect @@ -127,17 +138,25 @@ namespace PepperDash.Core /// public int AutoReconnectIntervalMs { get; set; } - SshClient Client; + private SshClient client; - ShellStream TheStream; + private ShellStream shellStream; - CTimer ReconnectTimer; + private readonly Timer reconnectTimer; //Lock object to prevent simulatneous connect/disconnect operations //private CCriticalSection connectLock = new CCriticalSection(); - private SemaphoreSlim connectLock = new SemaphoreSlim(1); + private readonly SemaphoreSlim connectLock = new SemaphoreSlim(1); - private bool DisconnectLogged = false; + // Thread-safety lock for state changes + private readonly object _stateLock = new object(); + + private bool disconnectLogged = false; + + /// + /// When true, turns off echo for the SSH session + /// + public bool DisableEcho { get; set; } /// /// Typical constructor. @@ -154,13 +173,13 @@ namespace PepperDash.Core Password = password; AutoReconnectIntervalMs = 5000; - ReconnectTimer = new CTimer(o => + reconnectTimer = new Timer(o => { - if (ConnectEnabled) + if (ConnectEnabled) // Now thread-safe property access { Connect(); } - }, System.Threading.Timeout.Infinite); + }, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); } /// @@ -172,23 +191,23 @@ namespace PepperDash.Core CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); AutoReconnectIntervalMs = 5000; - ReconnectTimer = new CTimer(o => + reconnectTimer = new Timer(o => { - if (ConnectEnabled) + if (ConnectEnabled) // Now thread-safe property access { Connect(); } - }, System.Threading.Timeout.Infinite); + }, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); } /// /// Handles closing this up when the program shuts down /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + private void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) { if (programEventType == eProgramStatusEventType.Stopping) { - if (Client != null) + if (client != null) { this.LogDebug("Program stopping. Closing connection"); Disconnect(); @@ -223,10 +242,10 @@ namespace PepperDash.Core this.LogDebug("Attempting connect"); // Cancel reconnect if running. - ReconnectTimer?.Stop(); + StopReconnectTimer(); // Cleanup the old client if it already exists - if (Client != null) + if (client != null) { this.LogDebug("Cleaning up disconnected client"); KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); @@ -239,29 +258,37 @@ namespace PepperDash.Core this.LogDebug("Creating new SshClient"); ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); - Client = new SshClient(connectionInfo); - Client.ErrorOccurred += Client_ErrorOccurred; + client = new SshClient(connectionInfo); + client.ErrorOccurred += Client_ErrorOccurred; //Attempt to connect ClientStatus = SocketStatus.SOCKET_STATUS_WAITING; try { - Client.Connect(); - TheStream = Client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534); - if (TheStream.DataAvailable) + client.Connect(); + + var modes = new Dictionary(); + + if (DisableEcho) + { + modes.Add(TerminalModes.ECHO, 0); + } + + shellStream = client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534, modes); + if (shellStream.DataAvailable) { // empty the buffer if there is data - string str = TheStream.Read(); + string str = shellStream.Read(); } - TheStream.DataReceived += Stream_DataReceived; + shellStream.DataReceived += Stream_DataReceived; this.LogInformation("Connected"); ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; - DisconnectLogged = false; + disconnectLogged = false; } catch (SshConnectionException e) { var ie = e.InnerException; // The details are inside!! - var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; + var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; if (ie is SocketException) { @@ -286,37 +313,37 @@ namespace PepperDash.Core this.LogVerbose(ie, "Exception details: "); } - DisconnectLogged = true; + disconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); if (AutoReconnect) { this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Reset(AutoReconnectIntervalMs); + StartReconnectTimer(); } } catch (SshOperationTimeoutException ex) { this.LogWarning("Connection attempt timed out: {message}", ex.Message); - DisconnectLogged = true; + disconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); if (AutoReconnect) { this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Reset(AutoReconnectIntervalMs); + StartReconnectTimer(); } } catch (Exception e) { - var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; + 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; + disconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); if (AutoReconnect) { this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Reset(AutoReconnectIntervalMs); + StartReconnectTimer(); } } } @@ -334,11 +361,7 @@ namespace PepperDash.Core { ConnectEnabled = false; // Stop trying reconnects, if we are - if (ReconnectTimer != null) - { - ReconnectTimer.Stop(); - // ReconnectTimer = null; - } + StopReconnectTimer(); KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); } @@ -352,12 +375,12 @@ namespace PepperDash.Core try { - if (Client != null) + if (client != null) { - Client.ErrorOccurred -= Client_ErrorOccurred; - Client.Disconnect(); - Client.Dispose(); - Client = null; + client.ErrorOccurred -= Client_ErrorOccurred; + client.Disconnect(); + client.Dispose(); + client = null; ClientStatus = status; this.LogDebug("Disconnected"); } @@ -371,16 +394,16 @@ namespace PepperDash.Core /// /// Kills the stream /// - void KillStream() + private void KillStream() { try { - if (TheStream != null) + if (shellStream != null) { - TheStream.DataReceived -= Stream_DataReceived; - TheStream.Close(); - TheStream.Dispose(); - TheStream = null; + shellStream.DataReceived -= Stream_DataReceived; + shellStream.Close(); + shellStream.Dispose(); + shellStream = null; this.LogDebug("Disconnected stream"); } } @@ -393,7 +416,7 @@ namespace PepperDash.Core /// /// Handles the keyboard interactive authentication, should it be required. /// - void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) + private void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) { foreach (AuthenticationPrompt prompt in e.Prompts) if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) @@ -403,7 +426,7 @@ namespace PepperDash.Core /// /// Handler for data receive on ShellStream. Passes data across to queue for line parsing. /// - void Stream_DataReceived(object sender, ShellDataEventArgs e) + private void Stream_DataReceived(object sender, ShellDataEventArgs e) { if (((ShellStream)sender).Length <= 0L) { @@ -439,7 +462,7 @@ namespace PepperDash.Core /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange /// event /// - void Client_ErrorOccurred(object sender, ExceptionEventArgs e) + private void Client_ErrorOccurred(object sender, ExceptionEventArgs e) { CrestronInvoke.BeginInvoke(o => { @@ -459,7 +482,7 @@ namespace PepperDash.Core if (AutoReconnect && ConnectEnabled) { this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Reset(AutoReconnectIntervalMs); + StartReconnectTimer(); } }); } @@ -467,7 +490,7 @@ namespace PepperDash.Core /// /// Helper for ConnectionChange event /// - void OnConnectionChange() + private void OnConnectionChange() { ConnectionChange?.Invoke(this, new GenericSocketStatusChageEventArgs(this)); } @@ -482,7 +505,7 @@ namespace PepperDash.Core { try { - if (Client != null && TheStream != null && IsConnected) + if (client != null && shellStream != null && IsConnected) { if (StreamDebugging.TxStreamDebuggingIsEnabled) this.LogInformation( @@ -490,8 +513,8 @@ namespace PepperDash.Core text.Length, ComTextHelper.GetDebugText(text)); - TheStream.Write(text); - TheStream.Flush(); + shellStream.Write(text); + shellStream.Flush(); } else { @@ -503,7 +526,7 @@ namespace PepperDash.Core this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - ReconnectTimer.Reset(); + StopReconnectTimer(); } catch (Exception ex) { @@ -519,13 +542,13 @@ namespace PepperDash.Core { try { - if (Client != null && TheStream != null && IsConnected) + if (client != null && shellStream != null && IsConnected) { if (StreamDebugging.TxStreamDebuggingIsEnabled) this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); - TheStream.Write(bytes, 0, bytes.Length); - TheStream.Flush(); + shellStream.Write(bytes, 0, bytes.Length); + shellStream.Flush(); } else { @@ -537,7 +560,7 @@ namespace PepperDash.Core this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes)); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - ReconnectTimer.Reset(); + StopReconnectTimer(); } catch (Exception ex) { @@ -546,6 +569,83 @@ namespace PepperDash.Core } #endregion + /// + /// Safely starts the reconnect timer with exception handling + /// + private void StartReconnectTimer() + { + try + { + reconnectTimer?.Change(AutoReconnectIntervalMs, System.Threading.Timeout.Infinite); + } + catch (ObjectDisposedException) + { + // Timer was disposed, ignore + this.LogDebug("Attempted to start timer but it was already disposed"); + } + } + + /// + /// Safely stops the reconnect timer with exception handling + /// + private void StopReconnectTimer() + { + try + { + reconnectTimer?.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); + } + catch (ObjectDisposedException) + { + // Timer was disposed, ignore + this.LogDebug("Attempted to stop timer but it was already disposed"); + } + } + + /// + /// Deactivate method - properly dispose of resources + /// + public override bool Deactivate() + { + try + { + this.LogDebug("Deactivating SSH client - disposing resources"); + + // Stop trying reconnects + ConnectEnabled = false; + StopReconnectTimer(); + + // Disconnect and cleanup client + KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); + + // Dispose timer + try + { + reconnectTimer?.Dispose(); + } + catch (ObjectDisposedException) + { + // Already disposed, ignore + } + + // Dispose semaphore + try + { + connectLock?.Dispose(); + } + catch (ObjectDisposedException) + { + // Already disposed, ignore + } + + return base.Deactivate(); + } + catch (Exception ex) + { + this.LogException(ex, "Error during SSH client deactivation"); + return false; + } + } + } //***************************************************************************************************** diff --git a/src/PepperDash.Core/Comm/GenericTcpIpClient.cs b/src/PepperDash.Core/Comm/GenericTcpIpClient.cs index c9235411..51b5871f 100644 --- a/src/PepperDash.Core/Comm/GenericTcpIpClient.cs +++ b/src/PepperDash.Core/Comm/GenericTcpIpClient.cs @@ -19,44 +19,44 @@ namespace PepperDash.Core /// public CommunicationStreamDebugging StreamDebugging { get; private set; } - /// - /// Fires when data is received from the server and returns it as a Byte array - /// - public event EventHandler BytesReceived; + /// + /// Fires when data is received from the server and returns it as a Byte array + /// + public event EventHandler BytesReceived; - /// - /// Fires when data is received from the server and returns it as text - /// - public event EventHandler TextReceived; + /// + /// Fires when data is received from the server and returns it as text + /// + public event EventHandler TextReceived; - /// - /// - /// - //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; - public event EventHandler ConnectionChange; + /// + /// + /// + //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; + public event EventHandler ConnectionChange; - private string _hostname; + private string _hostname; /// /// Address of server /// public string Hostname { - get - { - return _hostname; - } + get + { + return _hostname; + } - set - { - _hostname = value; - if (_client != null) - { - _client.AddressClientConnectedTo = _hostname; - } - } - } + set + { + _hostname = value; + if (_client != null) + { + _client.AddressClientConnectedTo = _hostname; + } + } + } /// /// Gets or sets the Port @@ -78,19 +78,19 @@ namespace PepperDash.Core /// public int BufferSize { get; set; } - /// - /// The actual client class - /// - private TCPClient _client; + /// + /// The actual client class + /// + private TCPClient _client; - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } - + /// /// S+ helper for IsConnected /// @@ -99,15 +99,15 @@ namespace PepperDash.Core get { return (ushort)(IsConnected ? 1 : 0); } } - /// - /// _client socket status Read only - /// - public SocketStatus ClientStatus - { - get + /// + /// _client socket status Read only + /// + public SocketStatus ClientStatus + { + get { - return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; - } + return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; + } } /// @@ -119,26 +119,26 @@ namespace PepperDash.Core get { return (ushort)ClientStatus; } } - /// + /// /// Status text shows the message associated with socket status - /// - public string ClientStatusText { get { return ClientStatus.ToString(); } } + /// + public string ClientStatusText { get { return ClientStatus.ToString(); } } - /// - /// Ushort representation of client status - /// + /// + /// Ushort representation of client status + /// [Obsolete] - public ushort UClientStatus { get { return (ushort)ClientStatus; } } + public ushort UClientStatus { get { return (ushort)ClientStatus; } } - /// - /// Connection failure reason - /// - public string ConnectionFailure { get { return ClientStatus.ToString(); } } + /// + /// Connection failure reason + /// + public string ConnectionFailure { get { return ClientStatus.ToString(); } } - /// - /// Gets or sets the AutoReconnect - /// - public bool AutoReconnect { get; set; } + /// + /// Gets or sets the AutoReconnect + /// + public bool AutoReconnect { get; set; } /// /// S+ helper for AutoReconnect @@ -149,29 +149,29 @@ namespace PepperDash.Core set { AutoReconnect = value == 1; } } - /// - /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 - /// - public int AutoReconnectIntervalMs { get; set; } + /// + /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 + /// + public int AutoReconnectIntervalMs { get; set; } - /// - /// Set only when the disconnect method is called - /// - bool DisconnectCalledByUser; + /// + /// Set only when the disconnect method is called + /// + bool DisconnectCalledByUser; - /// - /// - /// - public bool Connected - { - get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } - } + /// + /// + /// + public bool Connected + { + get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + } //Lock object to prevent simulatneous connect/disconnect operations private CCriticalSection connectLock = new CCriticalSection(); // private Timer for auto reconnect - private CTimer RetryTimer; + private CTimer RetryTimer; /// /// Constructor @@ -181,8 +181,8 @@ namespace PepperDash.Core /// /// public GenericTcpIpClient(string key, string address, int port, int bufferSize) - : base(key) - { + : base(key) + { StreamDebugging = new CommunicationStreamDebugging(key); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); AutoReconnectIntervalMs = 5000; @@ -218,18 +218,18 @@ namespace PepperDash.Core /// Default constructor for S+ /// public GenericTcpIpClient() - : base(SplusKey) + : base(SplusKey) { StreamDebugging = new CommunicationStreamDebugging(SplusKey); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; BufferSize = 2000; RetryTimer = new CTimer(o => { Reconnect(); }, Timeout.Infinite); - } + } /// /// Initialize method @@ -255,26 +255,26 @@ namespace PepperDash.Core /// /// /// - /// - /// Deactivate method - /// - public override bool Deactivate() - { + /// + /// Deactivate method + /// + public override bool Deactivate() + { RetryTimer.Stop(); RetryTimer.Dispose(); if (_client != null) { - _client.SocketStatusChange -= this.Client_SocketStatusChange; + _client.SocketStatusChange -= this.Client_SocketStatusChange; DisconnectClient(); } - return true; - } + return true; + } - /// - /// Connect method - /// - public void Connect() - { + /// + /// Connect method + /// + public void Connect() + { if (string.IsNullOrEmpty(Hostname)) { Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key); @@ -310,7 +310,7 @@ namespace PepperDash.Core { connectLock.Leave(); } - } + } private void Reconnect() { @@ -337,11 +337,11 @@ namespace PepperDash.Core } } - /// - /// Disconnect method - /// - public void Disconnect() - { + /// + /// Disconnect method + /// + public void Disconnect() + { try { connectLock.Enter(); @@ -355,7 +355,7 @@ namespace PepperDash.Core { connectLock.Leave(); } - } + } /// /// DisconnectClient method @@ -375,7 +375,7 @@ namespace PepperDash.Core /// /// void ConnectToServerCallback(TCPClient c) - { + { if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) { Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus); @@ -385,13 +385,13 @@ namespace PepperDash.Core { Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus); } - } + } /// /// Disconnects, waits and attemtps to connect again /// void WaitAndTryReconnect() - { + { CrestronInvoke.BeginInvoke(o => { try @@ -409,7 +409,7 @@ namespace PepperDash.Core connectLock.Leave(); } }); - } + } /// /// Recieves incoming data @@ -417,7 +417,7 @@ namespace PepperDash.Core /// /// void Receive(TCPClient client, int numBytes) - { + { if (client != null) { if (numBytes > 0) @@ -443,52 +443,52 @@ namespace PepperDash.Core } textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } + } } client.ReceiveDataAsync(Receive); } - } + } - /// - /// SendText method - /// - public void SendText(string text) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - // Check debug level before processing byte array + /// + /// SendText method + /// + public void SendText(string text) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + // Check debug level before processing byte array 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); - } + _client.SendData(bytes, bytes.Length); + } - /// - /// SendEscapedText method - /// - public void SendEscapedText(string text) - { - var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => - { - var hex = s.Groups[1].Value; - return ((char)Convert.ToByte(hex, 16)).ToString(); - }); - SendText(unescapedText); - } + /// + /// SendEscapedText method + /// + public void SendEscapedText(string text) + { + var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => + { + var hex = s.Groups[1].Value; + return ((char)Convert.ToByte(hex, 16)).ToString(); + }); + SendText(unescapedText); + } /// /// Sends Bytes to the server /// /// - /// - /// SendBytes method - /// - public void SendBytes(byte[] bytes) - { + /// + /// SendBytes method + /// + public void SendBytes(byte[] 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); - } + _client.SendData(bytes, bytes.Length); + } /// /// Socket Status Change Handler @@ -496,7 +496,7 @@ namespace PepperDash.Core /// /// void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) - { + { if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) { Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); @@ -505,68 +505,73 @@ namespace PepperDash.Core else { Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); - _client.ReceiveDataAsync(Receive); + _client.ReceiveDataAsync(Receive); } - var handler = ConnectionChange; - if (handler != null) - ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); - } - } + var handler = ConnectionChange; + if (handler != null) + ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); + } + } - /// - /// Represents a TcpSshPropertiesConfig - /// - public class TcpSshPropertiesConfig - { + /// + /// Represents a TcpSshPropertiesConfig + /// + public class TcpSshPropertiesConfig + { /// /// Address to connect to /// [JsonProperty(Required = Required.Always)] - public string Address { get; set; } - + public string Address { get; set; } + /// /// Port to connect to /// - [JsonProperty(Required = Required.Always)] - public int Port { get; set; } - + [JsonProperty(Required = Required.Always)] + public int Port { get; set; } + /// /// Username credential /// - public string Username { get; set; } - /// - /// Gets or sets the Password - /// - public string Password { get; set; } + public string Username { get; set; } + /// + /// Gets or sets the Password + /// + public string Password { get; set; } - /// - /// Defaults to 32768 - /// - public int BufferSize { get; set; } + /// + /// Defaults to 32768 + /// + public int BufferSize { get; set; } - /// - /// Gets or sets the AutoReconnect - /// - public bool AutoReconnect { get; set; } + /// + /// Gets or sets the AutoReconnect + /// + public bool AutoReconnect { get; set; } - /// - /// Gets or sets the AutoReconnectIntervalMs - /// - public int AutoReconnectIntervalMs { get; set; } + /// + /// Gets or sets the AutoReconnectIntervalMs + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// When true, turns off echo for the SSH session + /// + [JsonProperty("disableSshEcho")] + public bool DisableSshEcho { get; set; } /// /// Default constructor /// public TcpSshPropertiesConfig() - { - BufferSize = 32768; - AutoReconnect = true; - AutoReconnectIntervalMs = 5000; + { + BufferSize = 32768; + AutoReconnect = true; + AutoReconnectIntervalMs = 5000; Username = ""; Password = ""; - } - - } - + DisableSshEcho = false; + } + } } diff --git a/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs b/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs index a0723c4b..fbc46579 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs @@ -64,8 +64,11 @@ namespace PepperDash.Essentials.Core break; case eControlMethod.Ssh: { - var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password); - ssh.AutoReconnect = c.AutoReconnect; + var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password) + { + AutoReconnect = c.AutoReconnect, + DisableEcho = c.DisableSshEcho + }; if (ssh.AutoReconnect) ssh.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; comm = ssh; @@ -73,8 +76,10 @@ namespace PepperDash.Essentials.Core } case eControlMethod.Tcpip: { - var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize); - tcp.AutoReconnect = c.AutoReconnect; + var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize) + { + AutoReconnect = c.AutoReconnect + }; if (tcp.AutoReconnect) tcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; comm = tcp; @@ -90,8 +95,10 @@ namespace PepperDash.Essentials.Core break; case eControlMethod.SecureTcpIp: { - var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize); - secureTcp.AutoReconnect = c.AutoReconnect; + var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize) + { + AutoReconnect = c.AutoReconnect + }; if (secureTcp.AutoReconnect) secureTcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; comm = secureTcp; From 314570d6c3d78c7a92a362f3ec3a4a06bdbebd28 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Fri, 31 Oct 2025 13:09:12 -0500 Subject: [PATCH 2/5] fix: change number of retained files to 7 instead of 30 for processors --- src/PepperDash.Core/Logging/Debug.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/PepperDash.Core/Logging/Debug.cs b/src/PepperDash.Core/Logging/Debug.cs index 62a95f2c..877d8997 100644 --- a/src/PepperDash.Core/Logging/Debug.cs +++ b/src/PepperDash.Core/Logging/Debug.cs @@ -168,7 +168,7 @@ namespace PepperDash.Core .WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath, rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: LogEventLevel.Debug, - retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60, + retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 7 : 14, levelSwitch: _fileLogLevelSwitch ); @@ -1081,9 +1081,6 @@ namespace PepperDash.Core /// Logs to Console when at-level, and all messages to error log /// [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] - /// - /// Console method - /// public static void Console(uint level, ErrorLogLevel errorLogLevel, string format, params object[] items) { @@ -1096,9 +1093,6 @@ namespace PepperDash.Core /// it will only be written to the log. /// [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] - /// - /// ConsoleWithLog method - /// public static void ConsoleWithLog(uint level, string format, params object[] items) { LogMessage(level, format, items); From 6ed7c96ec71c8d074bcdc3c84da49a21461647c3 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Mon, 3 Nov 2025 10:53:21 -0600 Subject: [PATCH 3/5] fix: centralize debug printing into extension methods Stream debugging now uses CrestronConsole instead of debug methods, so that the debug statements will be printed regardless of console debug level. This also means that comm debug statements will NOT be in the Crestron Error log or in the files created by the logging system --- src/PepperDash.Core/ComTextHelper.cs | 43 ++ .../Comm/CommunicationStreamDebugging.cs | 56 +-- src/PepperDash.Core/Comm/GenericSshClient.cs | 17 +- .../Comm/GenericTcpIpClient.cs | 369 +++++++++--------- src/PepperDash.Core/Comm/GenericUdpServer.cs | 58 ++- .../Comm/StreamDebuggingExtensionMethods.cs | 69 ++++ .../Comm/eStreamDebuggingDataTypeSettings.cs | 24 ++ .../Comm/eStreamDebuggingSetting.cs | 28 ++ src/PepperDash.Core/CommunicationExtras.cs | 158 +++----- .../Comm and IR/CecPortController.cs | 47 ++- .../Comm and IR/ComPortController.cs | 32 +- .../SetDeviceStreamDebugRequestHandler.cs | 46 +-- 12 files changed, 493 insertions(+), 454 deletions(-) create mode 100644 src/PepperDash.Core/ComTextHelper.cs create mode 100644 src/PepperDash.Core/Comm/StreamDebuggingExtensionMethods.cs create mode 100644 src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs create mode 100644 src/PepperDash.Core/Comm/eStreamDebuggingSetting.cs diff --git a/src/PepperDash.Core/ComTextHelper.cs b/src/PepperDash.Core/ComTextHelper.cs new file mode 100644 index 00000000..06b996be --- /dev/null +++ b/src/PepperDash.Core/ComTextHelper.cs @@ -0,0 +1,43 @@ +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace PepperDash.Core +{ + /// + /// + /// + public class ComTextHelper + { + /// + /// Gets escaped text for a byte array + /// + /// + /// string with all bytes escaped + public static string GetEscapedText(byte[] bytes) + { + return string.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); + } + + /// + /// Gets escaped text for a string + /// + /// + /// string with all bytes escaped + 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()); + } + + /// + /// Gets debug text for a string + /// + /// + /// string with all non-printable characters escaped + public static string GetDebugText(string text) + { + return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value)); + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/CommunicationStreamDebugging.cs b/src/PepperDash.Core/Comm/CommunicationStreamDebugging.cs index 33141f0c..780e65d0 100644 --- a/src/PepperDash.Core/Comm/CommunicationStreamDebugging.cs +++ b/src/PepperDash.Core/Comm/CommunicationStreamDebugging.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text; using Crestron.SimplSharp; @@ -37,14 +36,14 @@ namespace PepperDash.Core { get { - return _DebugTimeoutInMs/60000; + return _DebugTimeoutInMs / 60000; } } /// /// Gets or sets the RxStreamDebuggingIsEnabled /// - public bool RxStreamDebuggingIsEnabled{ get; private set; } + public bool RxStreamDebuggingIsEnabled { get; private set; } /// /// Indicates that transmit stream debugging is enabled @@ -108,7 +107,7 @@ namespace PepperDash.Core TxStreamDebuggingIsEnabled = true; Debug.SetDeviceDebugSettings(ParentDeviceKey, setting); - + } /// @@ -136,51 +135,4 @@ namespace PepperDash.Core DebugExpiryPeriod = null; } } - - /// - /// The available settings for stream debugging - /// - [Flags] - /// - /// Enumeration of eStreamDebuggingSetting values - /// - public enum eStreamDebuggingSetting - { - /// - /// Debug off - /// - Off = 0, - /// - /// Debug received data - /// - Rx = 1, - /// - /// Debug transmitted data - /// - Tx = 2, - /// - /// Debug both received and transmitted data - /// - Both = Rx | Tx - } - - /// - /// The available settings for stream debugging response types - /// - [Flags] - public enum eStreamDebuggingDataTypeSettings - { - /// - /// Debug data in byte format - /// - Bytes = 0, - /// - /// Debug data in text format - /// - Text = 1, - /// - /// Debug data in both byte and text formats - /// - Both = Bytes | Text, - } } diff --git a/src/PepperDash.Core/Comm/GenericSshClient.cs b/src/PepperDash.Core/Comm/GenericSshClient.cs index 13192ed5..8ea5138e 100644 --- a/src/PepperDash.Core/Comm/GenericSshClient.cs +++ b/src/PepperDash.Core/Comm/GenericSshClient.cs @@ -416,18 +416,14 @@ namespace PepperDash.Core if (bytesHandler != null) { var bytes = Encoding.UTF8.GetBytes(response); - if (StreamDebugging.RxStreamDebuggingIsEnabled) - { - this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); - } + this.PrintReceivedBytes(bytes); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); } var textHandler = TextReceived; if (textHandler != null) { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response)); + this.PrintReceivedText(response); textHandler(this, new GenericCommMethodReceiveTextArgs(response)); } @@ -484,11 +480,7 @@ namespace PepperDash.Core { if (Client != null && TheStream != null && IsConnected) { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - this.LogInformation( - "Sending {length} characters of text: '{text}'", - text.Length, - ComTextHelper.GetDebugText(text)); + this.PrintSentText(text); TheStream.Write(text); TheStream.Flush(); @@ -521,8 +513,7 @@ namespace PepperDash.Core { if (Client != null && TheStream != null && IsConnected) { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + this.PrintSentBytes(bytes); TheStream.Write(bytes, 0, bytes.Length); TheStream.Flush(); diff --git a/src/PepperDash.Core/Comm/GenericTcpIpClient.cs b/src/PepperDash.Core/Comm/GenericTcpIpClient.cs index c9235411..17507851 100644 --- a/src/PepperDash.Core/Comm/GenericTcpIpClient.cs +++ b/src/PepperDash.Core/Comm/GenericTcpIpClient.cs @@ -19,44 +19,44 @@ namespace PepperDash.Core /// public CommunicationStreamDebugging StreamDebugging { get; private set; } - /// - /// Fires when data is received from the server and returns it as a Byte array - /// - public event EventHandler BytesReceived; + /// + /// Fires when data is received from the server and returns it as a Byte array + /// + public event EventHandler BytesReceived; - /// - /// Fires when data is received from the server and returns it as text - /// - public event EventHandler TextReceived; + /// + /// Fires when data is received from the server and returns it as text + /// + public event EventHandler TextReceived; - /// - /// - /// - //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; - public event EventHandler ConnectionChange; + /// + /// + /// + //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; + public event EventHandler ConnectionChange; - private string _hostname; + private string _hostname; /// /// Address of server /// public string Hostname { - get - { - return _hostname; - } + get + { + return _hostname; + } - set - { - _hostname = value; - if (_client != null) - { - _client.AddressClientConnectedTo = _hostname; - } - } - } + set + { + _hostname = value; + if (_client != null) + { + _client.AddressClientConnectedTo = _hostname; + } + } + } /// /// Gets or sets the Port @@ -78,19 +78,19 @@ namespace PepperDash.Core /// public int BufferSize { get; set; } - /// - /// The actual client class - /// - private TCPClient _client; + /// + /// The actual client class + /// + private TCPClient _client; - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } - + /// /// S+ helper for IsConnected /// @@ -99,15 +99,15 @@ namespace PepperDash.Core get { return (ushort)(IsConnected ? 1 : 0); } } - /// - /// _client socket status Read only - /// - public SocketStatus ClientStatus - { - get + /// + /// _client socket status Read only + /// + public SocketStatus ClientStatus + { + get { - return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; - } + return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; + } } /// @@ -119,26 +119,26 @@ namespace PepperDash.Core get { return (ushort)ClientStatus; } } - /// + /// /// Status text shows the message associated with socket status - /// - public string ClientStatusText { get { return ClientStatus.ToString(); } } + /// + public string ClientStatusText { get { return ClientStatus.ToString(); } } - /// - /// Ushort representation of client status - /// + /// + /// Ushort representation of client status + /// [Obsolete] - public ushort UClientStatus { get { return (ushort)ClientStatus; } } + public ushort UClientStatus { get { return (ushort)ClientStatus; } } - /// - /// Connection failure reason - /// - public string ConnectionFailure { get { return ClientStatus.ToString(); } } + /// + /// Connection failure reason + /// + public string ConnectionFailure { get { return ClientStatus.ToString(); } } - /// - /// Gets or sets the AutoReconnect - /// - public bool AutoReconnect { get; set; } + /// + /// Gets or sets the AutoReconnect + /// + public bool AutoReconnect { get; set; } /// /// S+ helper for AutoReconnect @@ -149,29 +149,29 @@ namespace PepperDash.Core set { AutoReconnect = value == 1; } } - /// - /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 - /// - public int AutoReconnectIntervalMs { get; set; } + /// + /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 + /// + public int AutoReconnectIntervalMs { get; set; } - /// - /// Set only when the disconnect method is called - /// - bool DisconnectCalledByUser; + /// + /// Set only when the disconnect method is called + /// + bool DisconnectCalledByUser; - /// - /// - /// - public bool Connected - { - get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } - } + /// + /// + /// + public bool Connected + { + get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + } //Lock object to prevent simulatneous connect/disconnect operations private CCriticalSection connectLock = new CCriticalSection(); // private Timer for auto reconnect - private CTimer RetryTimer; + private CTimer RetryTimer; /// /// Constructor @@ -181,8 +181,8 @@ namespace PepperDash.Core /// /// public GenericTcpIpClient(string key, string address, int port, int bufferSize) - : base(key) - { + : base(key) + { StreamDebugging = new CommunicationStreamDebugging(key); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); AutoReconnectIntervalMs = 5000; @@ -218,18 +218,18 @@ namespace PepperDash.Core /// Default constructor for S+ /// public GenericTcpIpClient() - : base(SplusKey) + : base(SplusKey) { StreamDebugging = new CommunicationStreamDebugging(SplusKey); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; BufferSize = 2000; RetryTimer = new CTimer(o => { Reconnect(); }, Timeout.Infinite); - } + } /// /// Initialize method @@ -255,26 +255,26 @@ namespace PepperDash.Core /// /// /// - /// - /// Deactivate method - /// - public override bool Deactivate() - { + /// + /// Deactivate method + /// + public override bool Deactivate() + { RetryTimer.Stop(); RetryTimer.Dispose(); if (_client != null) { - _client.SocketStatusChange -= this.Client_SocketStatusChange; + _client.SocketStatusChange -= this.Client_SocketStatusChange; DisconnectClient(); } - return true; - } + return true; + } - /// - /// Connect method - /// - public void Connect() - { + /// + /// Connect method + /// + public void Connect() + { if (string.IsNullOrEmpty(Hostname)) { Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key); @@ -310,7 +310,7 @@ namespace PepperDash.Core { connectLock.Leave(); } - } + } private void Reconnect() { @@ -337,11 +337,11 @@ namespace PepperDash.Core } } - /// - /// Disconnect method - /// - public void Disconnect() - { + /// + /// Disconnect method + /// + public void Disconnect() + { try { connectLock.Enter(); @@ -355,7 +355,7 @@ namespace PepperDash.Core { connectLock.Leave(); } - } + } /// /// DisconnectClient method @@ -375,7 +375,7 @@ namespace PepperDash.Core /// /// void ConnectToServerCallback(TCPClient c) - { + { if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) { Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus); @@ -385,13 +385,13 @@ namespace PepperDash.Core { Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus); } - } + } /// /// Disconnects, waits and attemtps to connect again /// void WaitAndTryReconnect() - { + { CrestronInvoke.BeginInvoke(o => { try @@ -409,7 +409,7 @@ namespace PepperDash.Core connectLock.Leave(); } }); - } + } /// /// Recieves incoming data @@ -417,7 +417,7 @@ namespace PepperDash.Core /// /// void Receive(TCPClient client, int numBytes) - { + { if (client != null) { if (numBytes > 0) @@ -426,10 +426,7 @@ namespace PepperDash.Core var bytesHandler = BytesReceived; if (bytesHandler != null) { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - { - Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); - } + this.PrintReceivedBytes(bytes); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); } var textHandler = TextReceived; @@ -437,58 +434,54 @@ namespace PepperDash.Core { var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - if (StreamDebugging.RxStreamDebuggingIsEnabled) - { - Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length); - } + this.PrintReceivedText(str); textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } + } } client.ReceiveDataAsync(Receive); } - } + } - /// - /// SendText method - /// - public void SendText(string text) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - // Check debug level before processing byte array - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text)); + /// + /// SendText method + /// + public void SendText(string text) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + // Check debug level before processing byte array + this.PrintSentText(text); if (_client != null) - _client.SendData(bytes, bytes.Length); - } + _client.SendData(bytes, bytes.Length); + } - /// - /// SendEscapedText method - /// - public void SendEscapedText(string text) - { - var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => - { - var hex = s.Groups[1].Value; - return ((char)Convert.ToByte(hex, 16)).ToString(); - }); - SendText(unescapedText); - } + /// + /// SendEscapedText method + /// + public void SendEscapedText(string text) + { + var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => + { + var hex = s.Groups[1].Value; + return ((char)Convert.ToByte(hex, 16)).ToString(); + }); + SendText(unescapedText); + } /// /// Sends Bytes to the server /// /// - /// - /// SendBytes method - /// - public void SendBytes(byte[] bytes) - { + /// + /// SendBytes method + /// + public void SendBytes(byte[] 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); - } + _client.SendData(bytes, bytes.Length); + } /// /// Socket Status Change Handler @@ -496,7 +489,7 @@ namespace PepperDash.Core /// /// void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) - { + { if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) { Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); @@ -505,68 +498,68 @@ namespace PepperDash.Core else { Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); - _client.ReceiveDataAsync(Receive); + _client.ReceiveDataAsync(Receive); } - var handler = ConnectionChange; - if (handler != null) - ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); - } - } + var handler = ConnectionChange; + if (handler != null) + ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); + } + } - /// - /// Represents a TcpSshPropertiesConfig - /// - public class TcpSshPropertiesConfig - { + /// + /// Represents a TcpSshPropertiesConfig + /// + public class TcpSshPropertiesConfig + { /// /// Address to connect to /// [JsonProperty(Required = Required.Always)] - public string Address { get; set; } - + public string Address { get; set; } + /// /// Port to connect to /// - [JsonProperty(Required = Required.Always)] - public int Port { get; set; } - + [JsonProperty(Required = Required.Always)] + public int Port { get; set; } + /// /// Username credential /// - public string Username { get; set; } - /// - /// Gets or sets the Password - /// - public string Password { get; set; } + public string Username { get; set; } + /// + /// Gets or sets the Password + /// + public string Password { get; set; } - /// - /// Defaults to 32768 - /// - public int BufferSize { get; set; } + /// + /// Defaults to 32768 + /// + public int BufferSize { get; set; } - /// - /// Gets or sets the AutoReconnect - /// - public bool AutoReconnect { get; set; } + /// + /// Gets or sets the AutoReconnect + /// + public bool AutoReconnect { get; set; } - /// - /// Gets or sets the AutoReconnectIntervalMs - /// - public int AutoReconnectIntervalMs { get; set; } + /// + /// Gets or sets the AutoReconnectIntervalMs + /// + public int AutoReconnectIntervalMs { get; set; } /// /// Default constructor /// public TcpSshPropertiesConfig() - { - BufferSize = 32768; - AutoReconnect = true; - AutoReconnectIntervalMs = 5000; + { + BufferSize = 32768; + AutoReconnect = true; + AutoReconnectIntervalMs = 5000; Username = ""; Password = ""; - } + } - } + } } diff --git a/src/PepperDash.Core/Comm/GenericUdpServer.cs b/src/PepperDash.Core/Comm/GenericUdpServer.cs index 52ac627a..a713872a 100644 --- a/src/PepperDash.Core/Comm/GenericUdpServer.cs +++ b/src/PepperDash.Core/Comm/GenericUdpServer.cs @@ -124,7 +124,7 @@ namespace PepperDash.Core CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); } - + /// /// /// @@ -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 /// 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 /// 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,17 +281,13 @@ namespace PepperDash.Core var bytesHandler = BytesReceived; if (bytesHandler != null) { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - { - Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); - } + this.PrintReceivedBytes(bytes); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); } var textHandler = TextReceived; if (textHandler != null) { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length); + this.PrintReceivedText(str); textHandler(this, new GenericCommMethodReceiveTextArgs(str)); } } @@ -318,8 +314,7 @@ namespace PepperDash.Core if (IsConnected && Server != null) { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text)); + this.PrintSentText(text); Server.SendData(bytes, bytes.Length); } @@ -334,8 +329,7 @@ namespace PepperDash.Core /// public void SendBytes(byte[] bytes) { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + this.PrintSentBytes(bytes); if (IsConnected && Server != null) Server.SendData(bytes, bytes.Length); @@ -343,11 +337,11 @@ namespace PepperDash.Core } - /// - /// Represents a GenericUdpReceiveTextExtraArgs - /// - public class GenericUdpReceiveTextExtraArgs : EventArgs - { + /// + /// Represents a GenericUdpReceiveTextExtraArgs + /// + public class GenericUdpReceiveTextExtraArgs : EventArgs + { /// /// /// @@ -359,7 +353,7 @@ namespace PepperDash.Core /// /// /// - public int Port { get; private set; } + public int Port { get; private set; } /// /// /// @@ -373,18 +367,18 @@ namespace PepperDash.Core /// /// 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; + } - /// - /// Stupid S+ Constructor - /// - public GenericUdpReceiveTextExtraArgs() { } - } + /// + /// Stupid S+ Constructor + /// + public GenericUdpReceiveTextExtraArgs() { } + } /// /// diff --git a/src/PepperDash.Core/Comm/StreamDebuggingExtensionMethods.cs b/src/PepperDash.Core/Comm/StreamDebuggingExtensionMethods.cs new file mode 100644 index 00000000..9fd7544d --- /dev/null +++ b/src/PepperDash.Core/Comm/StreamDebuggingExtensionMethods.cs @@ -0,0 +1,69 @@ +using System; +using Crestron.SimplSharp; + +namespace PepperDash.Core +{ + /// + /// Extension methods for stream debugging + /// + public static class StreamDebuggingExtensions + { + private static readonly string app = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? $"App {InitialParametersClass.ApplicationNumber}" : $"{InitialParametersClass.RoomId}"; + + /// + /// Print the sent bytes to the console + /// + /// comms device + /// bytes to print + 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)}'"); + } + + /// + /// Print the received bytes to the console + /// + /// comms device + /// bytes to print + 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)}'"); + } + + /// + /// Print the sent text to the console + /// + /// comms device + /// text to print + 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)}'"); + } + + /// + /// Print the received text to the console + /// + /// comms device + /// text to print + 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)}'"); + } + } +} diff --git a/src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs b/src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs new file mode 100644 index 00000000..24bd18d5 --- /dev/null +++ b/src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs @@ -0,0 +1,24 @@ +using System; + +namespace PepperDash.Core +{ + /// + /// The available settings for stream debugging response types + /// + [Flags] + public enum eStreamDebuggingDataTypeSettings + { + /// + /// Debug data in byte format + /// + Bytes = 0, + /// + /// Debug data in text format + /// + Text = 1, + /// + /// Debug data in both byte and text formats + /// + Both = Bytes | Text, + } +} diff --git a/src/PepperDash.Core/Comm/eStreamDebuggingSetting.cs b/src/PepperDash.Core/Comm/eStreamDebuggingSetting.cs new file mode 100644 index 00000000..f9f7eb3f --- /dev/null +++ b/src/PepperDash.Core/Comm/eStreamDebuggingSetting.cs @@ -0,0 +1,28 @@ +using System; + +namespace PepperDash.Core +{ + /// + /// The available settings for stream debugging + /// + [Flags] + public enum eStreamDebuggingSetting + { + /// + /// Debug off + /// + Off = 0, + /// + /// Debug received data + /// + Rx = 1, + /// + /// Debug transmitted data + /// + Tx = 2, + /// + /// Debug both received and transmitted data + /// + Both = Rx | Tx + } +} diff --git a/src/PepperDash.Core/CommunicationExtras.cs b/src/PepperDash.Core/CommunicationExtras.cs index d16ea761..a8bc57d3 100644 --- a/src/PepperDash.Core/CommunicationExtras.cs +++ b/src/PepperDash.Core/CommunicationExtras.cs @@ -1,10 +1,7 @@ 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 @@ -42,7 +39,7 @@ namespace PepperDash.Core /// Defines the contract for IBasicCommunication /// public interface IBasicCommunication : ICommunicationReceiver - { + { /// /// Send text to the device /// @@ -54,7 +51,7 @@ namespace PepperDash.Core /// /// void SendBytes(byte[] bytes); - } + } /// /// Represents a device that implements IBasicCommunication and IStreamDebugging @@ -67,7 +64,7 @@ namespace PepperDash.Core /// /// Represents a device with stream debugging capablities /// - public interface IStreamDebugging + public interface IStreamDebugging : IKeyed { /// /// Object to enable stream debugging @@ -76,12 +73,12 @@ namespace PepperDash.Core CommunicationStreamDebugging StreamDebugging { get; } } - /// - /// For IBasicCommunication classes that have SocketStatus. GenericSshClient, - /// GenericTcpIpClient - /// - public interface ISocketStatus : IBasicCommunication - { + /// + /// For IBasicCommunication classes that have SocketStatus. GenericSshClient, + /// GenericTcpIpClient + /// + public interface ISocketStatus : IBasicCommunication + { /// /// Notifies of socket status changes /// @@ -93,7 +90,7 @@ namespace PepperDash.Core [JsonProperty("clientStatus")] [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] SocketStatus ClientStatus { get; } - } + } /// /// Describes a device that implements ISocketStatus and IStreamDebugging @@ -107,24 +104,24 @@ namespace PepperDash.Core /// Describes a device that can automatically attempt to reconnect /// public interface IAutoReconnect - { + { /// /// Enable automatic recconnect /// [JsonProperty("autoReconnect")] - bool AutoReconnect { get; set; } + bool AutoReconnect { get; set; } /// /// Interval in ms to attempt automatic recconnections /// [JsonProperty("autoReconnectIntervalMs")] - int AutoReconnectIntervalMs { get; set; } - } + int AutoReconnectIntervalMs { get; set; } + } - /// - /// - /// - public enum eGenericCommMethodStatusChangeType - { + /// + /// + /// + public enum eGenericCommMethodStatusChangeType + { /// /// Connected /// @@ -133,45 +130,45 @@ namespace PepperDash.Core /// Disconnected /// Disconnected - } + } - /// - /// This delegate defines handler for IBasicCommunication status changes - /// - /// Device firing the status change - /// - public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status); + /// + /// This delegate defines handler for IBasicCommunication status changes + /// + /// Device firing the status change + /// + public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status); - /// - /// - /// - public class GenericCommMethodReceiveBytesArgs : EventArgs - { - /// - /// Gets or sets the Bytes - /// - public byte[] Bytes { get; private set; } + /// + /// + /// + public class GenericCommMethodReceiveBytesArgs : EventArgs + { + /// + /// Gets or sets the Bytes + /// + public byte[] Bytes { get; private set; } /// /// /// /// public GenericCommMethodReceiveBytesArgs(byte[] bytes) - { - Bytes = bytes; - } + { + Bytes = bytes; + } - /// - /// S+ Constructor - /// - public GenericCommMethodReceiveBytesArgs() { } - } + /// + /// S+ Constructor + /// + public GenericCommMethodReceiveBytesArgs() { } + } - /// - /// - /// - public class GenericCommMethodReceiveTextArgs : EventArgs - { + /// + /// + /// + public class GenericCommMethodReceiveTextArgs : EventArgs + { /// /// /// @@ -185,9 +182,9 @@ namespace PepperDash.Core /// /// public GenericCommMethodReceiveTextArgs(string text) - { - Text = text; - } + { + Text = text; + } /// /// @@ -195,59 +192,14 @@ namespace PepperDash.Core /// /// public GenericCommMethodReceiveTextArgs(string text, string delimiter) - :this(text) + : this(text) { Delimiter = delimiter; } - /// - /// S+ Constructor - /// - public GenericCommMethodReceiveTextArgs() { } - } - - - - /// - /// - /// - public class ComTextHelper - { /// - /// Gets escaped text for a byte array + /// S+ Constructor /// - /// - /// - public static string GetEscapedText(byte[] bytes) - { - return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); - } - - /// - /// Gets escaped text for a string - /// - /// - /// - /// - /// GetEscapedText method - /// - 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()); - } - - /// - /// Gets debug text for a string - /// - /// - /// - /// - /// GetDebugText method - /// - public static string GetDebugText(string text) - { - return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value)); - } - } + public GenericCommMethodReceiveTextArgs() { } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs index 7dacce5c..2dd4f2ba 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs @@ -12,15 +12,15 @@ using Serilog.Events; namespace PepperDash.Essentials.Core { - /// - /// Represents a CecPortController - /// - public class CecPortController : Device, IBasicCommunicationWithStreamDebugging + /// + /// Represents a CecPortController + /// + public class CecPortController : Device, IBasicCommunicationWithStreamDebugging { - /// - /// Gets or sets the StreamDebugging - /// - public CommunicationStreamDebugging StreamDebugging { get; private set; } + /// + /// Gets or sets the StreamDebugging + /// + public CommunicationStreamDebugging StreamDebugging { get; private set; } public event EventHandler BytesReceived; public event EventHandler TextReceived; @@ -33,16 +33,16 @@ namespace PepperDash.Essentials.Core ICec Port; public CecPortController(string key, Func 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,27 +58,25 @@ 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); - if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes)); + this.PrintReceivedBytes(bytes); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); } var textHandler = TextReceived; - if (textHandler != null) - { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s); - textHandler(this, new GenericCommMethodReceiveTextArgs(s)); - } + if (textHandler != null) + { + this.PrintReceivedText(s); + textHandler(this, new GenericCommMethodReceiveTextArgs(s)); + } } #region IBasicCommunication Members @@ -90,8 +88,7 @@ namespace PepperDash.Essentials.Core { if (Port == null) return; - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text); + this.PrintSentText(text); Port.StreamCec.Send.StringValue = text; } @@ -103,8 +100,8 @@ namespace PepperDash.Essentials.Core if (Port == null) return; var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + this.PrintSentBytes(bytes); + Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); Port.StreamCec.Send.StringValue = text; } diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs index ba599ead..d3c46196 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs @@ -23,10 +23,10 @@ namespace PepperDash.Essentials.Core /// Event fired when bytes are received /// public event EventHandler BytesReceived; - + /// - /// Event fired when text is received - /// + /// Event fired when text is received + /// public event EventHandler TextReceived; /// @@ -38,12 +38,12 @@ namespace PepperDash.Essentials.Core ComPort.ComPortSpec Spec; /// - /// Constructor - /// - /// - /// - /// - /// + /// Constructor + /// + /// + /// + /// + /// public ComPortController(string key, Func postActivationFunc, ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key) { @@ -100,7 +100,7 @@ namespace PepperDash.Essentials.Core return; // false } } - + var specResult = Port.SetComPortSpec(Spec); if (specResult != 0) { @@ -165,16 +165,14 @@ namespace PepperDash.Essentials.Core if (bytesHandler != null) { var bytes = Encoding.GetEncoding(28591).GetBytes(s); - if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes)); + this.PrintReceivedBytes(bytes); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); eventSubscribed = true; } var textHandler = TextReceived; if (textHandler != null) { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s); + this.PrintReceivedText(s); textHandler(this, new GenericCommMethodReceiveTextArgs(s)); eventSubscribed = true; } @@ -201,8 +199,7 @@ namespace PepperDash.Essentials.Core if (Port == null) return; - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text); + this.PrintSentText(text); Port.Send(text); } @@ -214,8 +211,7 @@ namespace PepperDash.Essentials.Core if (Port == null) return; var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + this.PrintSentBytes(bytes); Port.Send(text); } diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs index 72758b0e..7c6aea26 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs @@ -6,9 +6,9 @@ using PepperDash.Core.Web.RequestHandlers; namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Represents a SetDeviceStreamDebugRequestHandler - /// + /// + /// Represents a SetDeviceStreamDebugRequestHandler + /// public class SetDeviceStreamDebugRequestHandler : WebApiBaseRequestHandler { /// @@ -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)] - /// - /// Gets or sets the DeviceKey - /// + /// + /// Gets or sets the DeviceKey + /// public string DeviceKey { get; set; } [JsonProperty("setting", NullValueHandling = NullValueHandling.Include)] - /// - /// Gets or sets the Setting - /// + /// + /// Gets or sets the Setting + /// public string Setting { get; set; } [JsonProperty("timeout")] - /// - /// Gets or sets the Timeout - /// + /// + /// Gets or sets the Timeout + /// public int Timeout { get; set; } public SetDeviceStreamDebugConfig() From 9426dff5df0c5adabd42c74382835d7468e59e0a Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Mon, 3 Nov 2025 11:02:39 -0600 Subject: [PATCH 4/5] fix: copilot suggestions from PR review --- src/PepperDash.Core/Comm/GenericSshClient.cs | 10 ++++------ src/PepperDash.Core/Comm/GenericTcpIpClient.cs | 3 +-- ...xtensionMethods.cs => StreamDebuggingExtensions.cs} | 0 .../Comm/eStreamDebuggingDataTypeSettings.cs | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) rename src/PepperDash.Core/Comm/{StreamDebuggingExtensionMethods.cs => StreamDebuggingExtensions.cs} (100%) diff --git a/src/PepperDash.Core/Comm/GenericSshClient.cs b/src/PepperDash.Core/Comm/GenericSshClient.cs index c24ea829..b95aeb86 100644 --- a/src/PepperDash.Core/Comm/GenericSshClient.cs +++ b/src/PepperDash.Core/Comm/GenericSshClient.cs @@ -278,7 +278,7 @@ namespace PepperDash.Core if (shellStream.DataAvailable) { // empty the buffer if there is data - string str = shellStream.Read(); + shellStream.Read(); } shellStream.DataReceived += Stream_DataReceived; this.LogInformation("Connected"); @@ -287,8 +287,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; + var ie = e.InnerException; // The details are inside!! if (ie is SocketException) { @@ -335,7 +334,6 @@ 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; @@ -518,7 +516,7 @@ namespace PepperDash.Core this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - StopReconnectTimer(); + StartReconnectTimer(); } catch (Exception ex) { @@ -551,7 +549,7 @@ namespace PepperDash.Core this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes)); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - StopReconnectTimer(); + StartReconnectTimer(); } catch (Exception ex) { diff --git a/src/PepperDash.Core/Comm/GenericTcpIpClient.cs b/src/PepperDash.Core/Comm/GenericTcpIpClient.cs index 96d0387a..d13eed3f 100644 --- a/src/PepperDash.Core/Comm/GenericTcpIpClient.cs +++ b/src/PepperDash.Core/Comm/GenericTcpIpClient.cs @@ -477,8 +477,7 @@ namespace PepperDash.Core /// public void SendBytes(byte[] bytes) { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + this.PrintSentBytes(bytes); if (_client != null) _client.SendData(bytes, bytes.Length); } diff --git a/src/PepperDash.Core/Comm/StreamDebuggingExtensionMethods.cs b/src/PepperDash.Core/Comm/StreamDebuggingExtensions.cs similarity index 100% rename from src/PepperDash.Core/Comm/StreamDebuggingExtensionMethods.cs rename to src/PepperDash.Core/Comm/StreamDebuggingExtensions.cs diff --git a/src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs b/src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs index 24bd18d5..44626203 100644 --- a/src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs +++ b/src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs @@ -19,6 +19,6 @@ namespace PepperDash.Core /// /// Debug data in both byte and text formats /// - Both = Bytes | Text, + Both = Bytes | Text } } From fd95f5fed17c0b88f6cccd61ba615084d81138a2 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Mon, 3 Nov 2025 11:11:00 -0600 Subject: [PATCH 5/5] docs: update XML docs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/PepperDash.Core/ComTextHelper.cs | 2 +- src/PepperDash.Core/Comm/GenericSshClient.cs | 2 +- src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PepperDash.Core/ComTextHelper.cs b/src/PepperDash.Core/ComTextHelper.cs index 06b996be..28a76975 100644 --- a/src/PepperDash.Core/ComTextHelper.cs +++ b/src/PepperDash.Core/ComTextHelper.cs @@ -5,7 +5,7 @@ using System.Text.RegularExpressions; namespace PepperDash.Core { /// - /// + /// Helper class for formatting communication text and byte data for debugging purposes. /// public class ComTextHelper { diff --git a/src/PepperDash.Core/Comm/GenericSshClient.cs b/src/PepperDash.Core/Comm/GenericSshClient.cs index b95aeb86..df44ab51 100644 --- a/src/PepperDash.Core/Comm/GenericSshClient.cs +++ b/src/PepperDash.Core/Comm/GenericSshClient.cs @@ -287,7 +287,7 @@ namespace PepperDash.Core } catch (SshConnectionException e) { - var ie = e.InnerException; // The details are inside!! + var ie = e.InnerException; // The details are inside!! if (ie is SocketException) { diff --git a/src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs b/src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs index 44626203..f5fbfa2b 100644 --- a/src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs +++ b/src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs @@ -3,7 +3,7 @@ using System; namespace PepperDash.Core { /// - /// The available settings for stream debugging response types + /// The available settings for stream debugging data format types /// [Flags] public enum eStreamDebuggingDataTypeSettings