Compare commits

..

3 Commits

641 changed files with 11755 additions and 17042 deletions

2
.gitignore vendored
View File

@@ -395,5 +395,3 @@ essentials-framework/Essentials Interfaces/PepperDash_Essentials_Interfaces/Pepp
_site/ _site/
api/ api/
*.DS_Store *.DS_Store
/._PepperDash.Essentials.4Series.sln
dotnet

View File

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

View File

@@ -23,32 +23,23 @@
<FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz</FileName> <FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz</FileName>
</PropertyGroup> </PropertyGroup>
<Target Name="DeleteCLZ" BeforeTargets="CoreBuild" Condition="$(ProjectType) == 'Library' And $(TargetDir) != ''"> <Target Name="DeleteCLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'Library' And $(TargetDir) != '' And Exists($(FileName))">
<ItemGroup> <Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz">
<OldCLZFiles Include="$(TargetDir)$(TargetName).*.$(TargetFramework).clz" />
</ItemGroup>
<Delete Files="@(OldCLZFiles)" Condition="@(OldCLZFiles) != ''">
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/> <Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
</Delete> </Delete>
<Message Text="Deleted old CLZ files: '@(DeletedList)'" Condition="@(DeletedList) != ''" /> <Message Text="Deleted files: '@(DeletedList)'" />
</Target> </Target>
<Target Name="DeleteCPZ" BeforeTargets="CoreBuild" Condition="$(ProjectType) == 'Program' And $(TargetDir) != ''"> <Target Name="DeleteCPZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'Program' And $(TargetDir) != '' And Exists($(FileName))">
<ItemGroup> <Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz">
<OldCPZFiles Include="$(TargetDir)$(TargetName).*.$(TargetFramework).cpz" />
</ItemGroup>
<Delete Files="@(OldCPZFiles)" Condition="@(OldCPZFiles) != ''">
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/> <Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
</Delete> </Delete>
<Message Text="Deleted old CPZ files: '@(DeletedList)'" Condition="@(DeletedList) != ''" /> <Message Text="Deleted files: '@(DeletedList)'" />
</Target> </Target>
<Target Name="DeleteCPLZ" BeforeTargets="CoreBuild" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''"> <Target Name="DeleteCPLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != '' And Exists($(FileName))">
<ItemGroup> <Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz">
<OldCPLZFiles Include="$(TargetDir)$(TargetName).*.$(TargetFramework).cplz" />
</ItemGroup>
<Delete Files="@(OldCPLZFiles)" Condition="@(OldCPLZFiles) != ''">
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/> <Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
</Delete> </Delete>
<Message Text="Deleted old CPLZ files: '@(DeletedList)'" Condition="@(DeletedList) != ''" /> <Message Text="Deleted files: '@(DeletedList)'" />
</Target> </Target>
<Target Name="CreateCPLZ" AfterTargets="Build" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''" DependsOnTargets="DeleteCPLZ"> <Target Name="CreateCPLZ" AfterTargets="Build" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''" DependsOnTargets="DeleteCPLZ">

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,14 +1,11 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using PepperDash.Core; using PepperDash.Core;
namespace PepperDash.Core namespace PepperDash.Essentials.Core.Communications
{ {
/// <summary> /// <summary>
/// Defines the string event handler for line events on the gather /// Defines the string event handler for line events on the gather

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using PepperDash.Core;
namespace PepperDash.Core namespace PepperDash.Core
{ {
@@ -36,14 +36,14 @@ namespace PepperDash.Core
{ {
get get
{ {
return _DebugTimeoutInMs / 60000; return _DebugTimeoutInMs/60000;
} }
} }
/// <summary> /// <summary>
/// Gets or sets the RxStreamDebuggingIsEnabled /// Gets or sets the RxStreamDebuggingIsEnabled
/// </summary> /// </summary>
public bool RxStreamDebuggingIsEnabled { get; private set; } public bool RxStreamDebuggingIsEnabled{ get; private set; }
/// <summary> /// <summary>
/// Indicates that transmit stream debugging is enabled /// Indicates that transmit stream debugging is enabled
@@ -107,7 +107,7 @@ namespace PepperDash.Core
TxStreamDebuggingIsEnabled = true; TxStreamDebuggingIsEnabled = true;
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting); Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
} }
/// <summary> /// <summary>
@@ -135,4 +135,51 @@ namespace PepperDash.Core
DebugExpiryPeriod = null; 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

@@ -1,9 +1,8 @@
using System; using System;
using Crestron.SimplSharp;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
namespace PepperDash.Core namespace PepperDash.Essentials.Core.Communications
{ {
/// <summary> /// <summary>
/// Represents a ControlPropertiesConfig /// Represents a ControlPropertiesConfig

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using Crestron.SimplSharp; using Crestron.SimplSharp;
@@ -12,12 +11,11 @@ using Renci.SshNet.Common;
namespace PepperDash.Core namespace PepperDash.Core
{ {
/// <summary> /// <summary>
/// SSH Client ///
/// </summary> /// </summary>
public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
{ {
private const string SPlusKey = "Uninitialized SshClient"; private const string SPlusKey = "Uninitialized SshClient";
/// <summary> /// <summary>
/// Object to enable stream debugging /// Object to enable stream debugging
/// </summary> /// </summary>
@@ -38,6 +36,11 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange; public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
/// <summary>
/////
///// </summary>
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
/// <summary> /// <summary>
/// Gets or sets the Hostname /// Gets or sets the Hostname
/// </summary> /// </summary>
@@ -64,7 +67,7 @@ namespace PepperDash.Core
public bool IsConnected public bool IsConnected
{ {
// returns false if no client or not connected // 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; }
} }
/// <summary> /// <summary>
@@ -80,26 +83,16 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public SocketStatus ClientStatus public SocketStatus ClientStatus
{ {
get { lock (_stateLock) { return _ClientStatus; } } get { return _ClientStatus; }
private set private set
{ {
bool shouldFireEvent = false; if (_ClientStatus == value)
lock (_stateLock) return;
{ _ClientStatus = value;
if (_ClientStatus != value) OnConnectionChange();
{
_ClientStatus = value;
shouldFireEvent = true;
}
}
// Fire event outside lock to avoid deadlock
if (shouldFireEvent)
OnConnectionChange();
} }
} }
SocketStatus _ClientStatus;
private SocketStatus _ClientStatus;
private bool _ConnectEnabled;
/// <summary> /// <summary>
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
@@ -107,7 +100,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public ushort UStatus public ushort UStatus
{ {
get { lock (_stateLock) { return (ushort)_ClientStatus; } } get { return (ushort)_ClientStatus; }
} }
/// <summary> /// <summary>
@@ -118,11 +111,7 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Will be set and unset by connect and disconnect only /// Will be set and unset by connect and disconnect only
/// </summary> /// </summary>
public bool ConnectEnabled public bool ConnectEnabled { get; private set; }
{
get { lock (_stateLock) { return _ConnectEnabled; } }
private set { lock (_stateLock) { _ConnectEnabled = value; } }
}
/// <summary> /// <summary>
/// S+ helper for AutoReconnect /// S+ helper for AutoReconnect
@@ -138,25 +127,17 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public int AutoReconnectIntervalMs { get; set; } public int AutoReconnectIntervalMs { get; set; }
private SshClient client; SshClient Client;
private ShellStream shellStream; ShellStream TheStream;
private readonly Timer reconnectTimer; CTimer ReconnectTimer;
//Lock object to prevent simulatneous connect/disconnect operations //Lock object to prevent simulatneous connect/disconnect operations
//private CCriticalSection connectLock = new CCriticalSection(); //private CCriticalSection connectLock = new CCriticalSection();
private readonly SemaphoreSlim connectLock = new SemaphoreSlim(1); private SemaphoreSlim connectLock = new SemaphoreSlim(1);
// Thread-safety lock for state changes private bool DisconnectLogged = false;
private readonly object _stateLock = new object();
private bool disconnectLogged = false;
/// <summary>
/// When true, turns off echo for the SSH session
/// </summary>
public bool DisableEcho { get; set; }
/// <summary> /// <summary>
/// Typical constructor. /// Typical constructor.
@@ -173,13 +154,13 @@ namespace PepperDash.Core
Password = password; Password = password;
AutoReconnectIntervalMs = 5000; AutoReconnectIntervalMs = 5000;
reconnectTimer = new Timer(o => ReconnectTimer = new CTimer(o =>
{ {
if (ConnectEnabled) // Now thread-safe property access if (ConnectEnabled)
{ {
Connect(); Connect();
} }
}, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); }, System.Threading.Timeout.Infinite);
} }
/// <summary> /// <summary>
@@ -191,23 +172,23 @@ namespace PepperDash.Core
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000; AutoReconnectIntervalMs = 5000;
reconnectTimer = new Timer(o => ReconnectTimer = new CTimer(o =>
{ {
if (ConnectEnabled) // Now thread-safe property access if (ConnectEnabled)
{ {
Connect(); Connect();
} }
}, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); }, System.Threading.Timeout.Infinite);
} }
/// <summary> /// <summary>
/// Handles closing this up when the program shuts down /// Handles closing this up when the program shuts down
/// </summary> /// </summary>
private void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{ {
if (programEventType == eProgramStatusEventType.Stopping) if (programEventType == eProgramStatusEventType.Stopping)
{ {
if (client != null) if (Client != null)
{ {
this.LogDebug("Program stopping. Closing connection"); this.LogDebug("Program stopping. Closing connection");
Disconnect(); Disconnect();
@@ -242,10 +223,10 @@ namespace PepperDash.Core
this.LogDebug("Attempting connect"); this.LogDebug("Attempting connect");
// Cancel reconnect if running. // Cancel reconnect if running.
StopReconnectTimer(); ReconnectTimer?.Stop();
// Cleanup the old client if it already exists // Cleanup the old client if it already exists
if (client != null) if (Client != null)
{ {
this.LogDebug("Cleaning up disconnected client"); this.LogDebug("Cleaning up disconnected client");
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
@@ -258,36 +239,29 @@ namespace PepperDash.Core
this.LogDebug("Creating new SshClient"); this.LogDebug("Creating new SshClient");
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
client = new SshClient(connectionInfo); Client = new SshClient(connectionInfo);
client.ErrorOccurred += Client_ErrorOccurred; Client.ErrorOccurred += Client_ErrorOccurred;
//Attempt to connect //Attempt to connect
ClientStatus = SocketStatus.SOCKET_STATUS_WAITING; ClientStatus = SocketStatus.SOCKET_STATUS_WAITING;
try try
{ {
client.Connect(); Client.Connect();
TheStream = Client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534);
var modes = new Dictionary<TerminalModes, uint>(); if (TheStream.DataAvailable)
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 // empty the buffer if there is data
shellStream.Read(); string str = TheStream.Read();
} }
shellStream.DataReceived += Stream_DataReceived; TheStream.DataReceived += Stream_DataReceived;
this.LogInformation("Connected"); this.LogInformation("Connected");
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
disconnectLogged = false; DisconnectLogged = false;
} }
catch (SshConnectionException e) catch (SshConnectionException e)
{ {
var ie = e.InnerException; // The details are inside!! var ie = e.InnerException; // The details are inside!!
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
if (ie is SocketException) if (ie is SocketException)
{ {
@@ -312,36 +286,37 @@ namespace PepperDash.Core
this.LogVerbose(ie, "Exception details: "); this.LogVerbose(ie, "Exception details: ");
} }
disconnectLogged = true; DisconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect) if (AutoReconnect)
{ {
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs); this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
StartReconnectTimer(); ReconnectTimer.Reset(AutoReconnectIntervalMs);
} }
} }
catch (SshOperationTimeoutException ex) catch (SshOperationTimeoutException ex)
{ {
this.LogWarning("Connection attempt timed out: {message}", ex.Message); this.LogWarning("Connection attempt timed out: {message}", ex.Message);
disconnectLogged = true; DisconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect) if (AutoReconnect)
{ {
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
StartReconnectTimer(); ReconnectTimer.Reset(AutoReconnectIntervalMs);
} }
} }
catch (Exception e) catch (Exception e)
{ {
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
this.LogError("Unhandled exception on connect: {error}", e.Message); this.LogError("Unhandled exception on connect: {error}", e.Message);
this.LogVerbose(e, "Exception details: "); this.LogVerbose(e, "Exception details: ");
disconnectLogged = true; DisconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect) if (AutoReconnect)
{ {
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
StartReconnectTimer(); ReconnectTimer.Reset(AutoReconnectIntervalMs);
} }
} }
} }
@@ -359,7 +334,11 @@ namespace PepperDash.Core
{ {
ConnectEnabled = false; ConnectEnabled = false;
// Stop trying reconnects, if we are // Stop trying reconnects, if we are
StopReconnectTimer(); if (ReconnectTimer != null)
{
ReconnectTimer.Stop();
// ReconnectTimer = null;
}
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
} }
@@ -373,12 +352,12 @@ namespace PepperDash.Core
try try
{ {
if (client != null) if (Client != null)
{ {
client.ErrorOccurred -= Client_ErrorOccurred; Client.ErrorOccurred -= Client_ErrorOccurred;
client.Disconnect(); Client.Disconnect();
client.Dispose(); Client.Dispose();
client = null; Client = null;
ClientStatus = status; ClientStatus = status;
this.LogDebug("Disconnected"); this.LogDebug("Disconnected");
} }
@@ -392,16 +371,16 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Kills the stream /// Kills the stream
/// </summary> /// </summary>
private void KillStream() void KillStream()
{ {
try try
{ {
if (shellStream != null) if (TheStream != null)
{ {
shellStream.DataReceived -= Stream_DataReceived; TheStream.DataReceived -= Stream_DataReceived;
shellStream.Close(); TheStream.Close();
shellStream.Dispose(); TheStream.Dispose();
shellStream = null; TheStream = null;
this.LogDebug("Disconnected stream"); this.LogDebug("Disconnected stream");
} }
} }
@@ -414,7 +393,7 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Handles the keyboard interactive authentication, should it be required. /// Handles the keyboard interactive authentication, should it be required.
/// </summary> /// </summary>
private void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
{ {
foreach (AuthenticationPrompt prompt in e.Prompts) foreach (AuthenticationPrompt prompt in e.Prompts)
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
@@ -424,7 +403,7 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing. /// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
/// </summary> /// </summary>
private void Stream_DataReceived(object sender, ShellDataEventArgs e) void Stream_DataReceived(object sender, ShellDataEventArgs e)
{ {
if (((ShellStream)sender).Length <= 0L) if (((ShellStream)sender).Length <= 0L)
{ {
@@ -437,14 +416,18 @@ namespace PepperDash.Core
if (bytesHandler != null) if (bytesHandler != null)
{ {
var bytes = Encoding.UTF8.GetBytes(response); 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)); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
} }
var textHandler = TextReceived; var textHandler = TextReceived;
if (textHandler != null) if (textHandler != null)
{ {
this.PrintReceivedText(response); if (StreamDebugging.RxStreamDebuggingIsEnabled)
this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response));
textHandler(this, new GenericCommMethodReceiveTextArgs(response)); textHandler(this, new GenericCommMethodReceiveTextArgs(response));
} }
@@ -456,7 +439,7 @@ namespace PepperDash.Core
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
/// event /// event
/// </summary> /// </summary>
private void Client_ErrorOccurred(object sender, ExceptionEventArgs e) void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
{ {
CrestronInvoke.BeginInvoke(o => CrestronInvoke.BeginInvoke(o =>
{ {
@@ -476,7 +459,7 @@ namespace PepperDash.Core
if (AutoReconnect && ConnectEnabled) if (AutoReconnect && ConnectEnabled)
{ {
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
StartReconnectTimer(); ReconnectTimer.Reset(AutoReconnectIntervalMs);
} }
}); });
} }
@@ -484,7 +467,7 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Helper for ConnectionChange event /// Helper for ConnectionChange event
/// </summary> /// </summary>
private void OnConnectionChange() void OnConnectionChange()
{ {
ConnectionChange?.Invoke(this, new GenericSocketStatusChageEventArgs(this)); ConnectionChange?.Invoke(this, new GenericSocketStatusChageEventArgs(this));
} }
@@ -499,12 +482,16 @@ namespace PepperDash.Core
{ {
try try
{ {
if (client != null && shellStream != null && IsConnected) if (Client != null && TheStream != null && IsConnected)
{ {
this.PrintSentText(text); if (StreamDebugging.TxStreamDebuggingIsEnabled)
this.LogInformation(
"Sending {length} characters of text: '{text}'",
text.Length,
ComTextHelper.GetDebugText(text));
shellStream.Write(text); TheStream.Write(text);
shellStream.Flush(); TheStream.Flush();
} }
else else
{ {
@@ -516,7 +503,7 @@ namespace PepperDash.Core
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
StartReconnectTimer(); ReconnectTimer.Reset();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -532,12 +519,13 @@ namespace PepperDash.Core
{ {
try try
{ {
if (client != null && shellStream != null && IsConnected) if (Client != null && TheStream != 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); TheStream.Write(bytes, 0, bytes.Length);
shellStream.Flush(); TheStream.Flush();
} }
else else
{ {
@@ -549,7 +537,7 @@ namespace PepperDash.Core
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes)); this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
StartReconnectTimer(); ReconnectTimer.Reset();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -558,83 +546,6 @@ namespace PepperDash.Core
} }
#endregion #endregion
/// <summary>
/// Safely starts the reconnect timer with exception handling
/// </summary>
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");
}
}
/// <summary>
/// Safely stops the reconnect timer with exception handling
/// </summary>
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");
}
}
/// <summary>
/// Deactivate method - properly dispose of resources
/// </summary>
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;
}
}
} }
//***************************************************************************************************** //*****************************************************************************************************

View File

@@ -19,44 +19,44 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; } public CommunicationStreamDebugging StreamDebugging { get; private set; }
/// <summary> /// <summary>
/// Fires when data is received from the server and returns it as a Byte array /// Fires when data is received from the server and returns it as a Byte array
/// </summary> /// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived; public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary> /// <summary>
/// Fires when data is received from the server and returns it as text /// Fires when data is received from the server and returns it as text
/// </summary> /// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived; public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange; //public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange; public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
private string _hostname; private string _hostname;
/// <summary> /// <summary>
/// Address of server /// Address of server
/// </summary> /// </summary>
public string Hostname public string Hostname
{ {
get get
{ {
return _hostname; return _hostname;
} }
set set
{ {
_hostname = value; _hostname = value;
if (_client != null) if (_client != null)
{ {
_client.AddressClientConnectedTo = _hostname; _client.AddressClientConnectedTo = _hostname;
} }
} }
} }
/// <summary> /// <summary>
/// Gets or sets the Port /// Gets or sets the Port
@@ -78,19 +78,19 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public int BufferSize { get; set; } public int BufferSize { get; set; }
/// <summary> /// <summary>
/// The actual client class /// The actual client class
/// </summary> /// </summary>
private TCPClient _client; private TCPClient _client;
/// <summary> /// <summary>
/// Bool showing if socket is connected /// Bool showing if socket is connected
/// </summary> /// </summary>
public bool IsConnected public bool IsConnected
{ {
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
} }
/// <summary> /// <summary>
/// S+ helper for IsConnected /// S+ helper for IsConnected
/// </summary> /// </summary>
@@ -99,15 +99,15 @@ namespace PepperDash.Core
get { return (ushort)(IsConnected ? 1 : 0); } get { return (ushort)(IsConnected ? 1 : 0); }
} }
/// <summary> /// <summary>
/// _client socket status Read only /// _client socket status Read only
/// </summary> /// </summary>
public SocketStatus ClientStatus public SocketStatus ClientStatus
{ {
get get
{ {
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
} }
} }
/// <summary> /// <summary>
@@ -119,26 +119,26 @@ namespace PepperDash.Core
get { return (ushort)ClientStatus; } get { return (ushort)ClientStatus; }
} }
/// <summary> /// <summary>
/// Status text shows the message associated with socket status /// Status text shows the message associated with socket status
/// </summary> /// </summary>
public string ClientStatusText { get { return ClientStatus.ToString(); } } public string ClientStatusText { get { return ClientStatus.ToString(); } }
/// <summary> /// <summary>
/// Ushort representation of client status /// Ushort representation of client status
/// </summary> /// </summary>
[Obsolete] [Obsolete]
public ushort UClientStatus { get { return (ushort)ClientStatus; } } public ushort UClientStatus { get { return (ushort)ClientStatus; } }
/// <summary> /// <summary>
/// Connection failure reason /// Connection failure reason
/// </summary> /// </summary>
public string ConnectionFailure { get { return ClientStatus.ToString(); } } public string ConnectionFailure { get { return ClientStatus.ToString(); } }
/// <summary> /// <summary>
/// Gets or sets the AutoReconnect /// Gets or sets the AutoReconnect
/// </summary> /// </summary>
public bool AutoReconnect { get; set; } public bool AutoReconnect { get; set; }
/// <summary> /// <summary>
/// S+ helper for AutoReconnect /// S+ helper for AutoReconnect
@@ -149,29 +149,29 @@ namespace PepperDash.Core
set { AutoReconnect = value == 1; } set { AutoReconnect = value == 1; }
} }
/// <summary> /// <summary>
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000 /// Milliseconds to wait before attempting to reconnect. Defaults to 5000
/// </summary> /// </summary>
public int AutoReconnectIntervalMs { get; set; } public int AutoReconnectIntervalMs { get; set; }
/// <summary> /// <summary>
/// Set only when the disconnect method is called /// Set only when the disconnect method is called
/// </summary> /// </summary>
bool DisconnectCalledByUser; bool DisconnectCalledByUser;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public bool Connected public bool Connected
{ {
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
} }
//Lock object to prevent simulatneous connect/disconnect operations //Lock object to prevent simulatneous connect/disconnect operations
private CCriticalSection connectLock = new CCriticalSection(); private CCriticalSection connectLock = new CCriticalSection();
// private Timer for auto reconnect // private Timer for auto reconnect
private CTimer RetryTimer; private CTimer RetryTimer;
/// <summary> /// <summary>
/// Constructor /// Constructor
@@ -181,8 +181,8 @@ namespace PepperDash.Core
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
public GenericTcpIpClient(string key, string address, int port, int bufferSize) public GenericTcpIpClient(string key, string address, int port, int bufferSize)
: base(key) : base(key)
{ {
StreamDebugging = new CommunicationStreamDebugging(key); StreamDebugging = new CommunicationStreamDebugging(key);
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000; AutoReconnectIntervalMs = 5000;
@@ -218,18 +218,18 @@ namespace PepperDash.Core
/// Default constructor for S+ /// Default constructor for S+
/// </summary> /// </summary>
public GenericTcpIpClient() public GenericTcpIpClient()
: base(SplusKey) : base(SplusKey)
{ {
StreamDebugging = new CommunicationStreamDebugging(SplusKey); StreamDebugging = new CommunicationStreamDebugging(SplusKey);
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000; AutoReconnectIntervalMs = 5000;
BufferSize = 2000; BufferSize = 2000;
RetryTimer = new CTimer(o => RetryTimer = new CTimer(o =>
{ {
Reconnect(); Reconnect();
}, Timeout.Infinite); }, Timeout.Infinite);
} }
/// <summary> /// <summary>
/// Initialize method /// Initialize method
@@ -255,26 +255,26 @@ namespace PepperDash.Core
/// ///
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
/// <summary> /// <summary>
/// Deactivate method /// Deactivate method
/// </summary> /// </summary>
public override bool Deactivate() public override bool Deactivate()
{ {
RetryTimer.Stop(); RetryTimer.Stop();
RetryTimer.Dispose(); RetryTimer.Dispose();
if (_client != null) if (_client != null)
{ {
_client.SocketStatusChange -= this.Client_SocketStatusChange; _client.SocketStatusChange -= this.Client_SocketStatusChange;
DisconnectClient(); DisconnectClient();
} }
return true; return true;
} }
/// <summary> /// <summary>
/// Connect method /// Connect method
/// </summary> /// </summary>
public void Connect() public void Connect()
{ {
if (string.IsNullOrEmpty(Hostname)) if (string.IsNullOrEmpty(Hostname))
{ {
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key); Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key);
@@ -310,7 +310,7 @@ namespace PepperDash.Core
{ {
connectLock.Leave(); connectLock.Leave();
} }
} }
private void Reconnect() private void Reconnect()
{ {
@@ -337,11 +337,11 @@ namespace PepperDash.Core
} }
} }
/// <summary> /// <summary>
/// Disconnect method /// Disconnect method
/// </summary> /// </summary>
public void Disconnect() public void Disconnect()
{ {
try try
{ {
connectLock.Enter(); connectLock.Enter();
@@ -355,7 +355,7 @@ namespace PepperDash.Core
{ {
connectLock.Leave(); connectLock.Leave();
} }
} }
/// <summary> /// <summary>
/// DisconnectClient method /// DisconnectClient method
@@ -375,7 +375,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
/// <param name="c"></param> /// <param name="c"></param>
void ConnectToServerCallback(TCPClient c) void ConnectToServerCallback(TCPClient c)
{ {
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{ {
Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus); 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); Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus);
} }
} }
/// <summary> /// <summary>
/// Disconnects, waits and attemtps to connect again /// Disconnects, waits and attemtps to connect again
/// </summary> /// </summary>
void WaitAndTryReconnect() void WaitAndTryReconnect()
{ {
CrestronInvoke.BeginInvoke(o => CrestronInvoke.BeginInvoke(o =>
{ {
try try
@@ -409,7 +409,7 @@ namespace PepperDash.Core
connectLock.Leave(); connectLock.Leave();
} }
}); });
} }
/// <summary> /// <summary>
/// Recieves incoming data /// Recieves incoming data
@@ -417,7 +417,7 @@ namespace PepperDash.Core
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="numBytes"></param> /// <param name="numBytes"></param>
void Receive(TCPClient client, int numBytes) void Receive(TCPClient client, int numBytes)
{ {
if (client != null) if (client != null)
{ {
if (numBytes > 0) if (numBytes > 0)
@@ -426,7 +426,10 @@ namespace PepperDash.Core
var bytesHandler = BytesReceived; var bytesHandler = BytesReceived;
if (bytesHandler != null) 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)); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
} }
var textHandler = TextReceived; var textHandler = TextReceived;
@@ -434,53 +437,58 @@ namespace PepperDash.Core
{ {
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); 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)); textHandler(this, new GenericCommMethodReceiveTextArgs(str));
} }
} }
client.ReceiveDataAsync(Receive); client.ReceiveDataAsync(Receive);
} }
} }
/// <summary> /// <summary>
/// SendText method /// SendText method
/// </summary> /// </summary>
public void SendText(string text) public void SendText(string text)
{ {
var bytes = Encoding.GetEncoding(28591).GetBytes(text); var bytes = Encoding.GetEncoding(28591).GetBytes(text);
// Check debug level before processing byte array // 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) if (_client != null)
_client.SendData(bytes, bytes.Length); _client.SendData(bytes, bytes.Length);
} }
/// <summary> /// <summary>
/// SendEscapedText method /// SendEscapedText method
/// </summary> /// </summary>
public void SendEscapedText(string text) public void SendEscapedText(string text)
{ {
var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s =>
{ {
var hex = s.Groups[1].Value; var hex = s.Groups[1].Value;
return ((char)Convert.ToByte(hex, 16)).ToString(); return ((char)Convert.ToByte(hex, 16)).ToString();
}); });
SendText(unescapedText); SendText(unescapedText);
} }
/// <summary> /// <summary>
/// Sends Bytes to the server /// Sends Bytes to the server
/// </summary> /// </summary>
/// <param name="bytes"></param> /// <param name="bytes"></param>
/// <summary> /// <summary>
/// SendBytes method /// SendBytes method
/// </summary> /// </summary>
public void SendBytes(byte[] bytes) 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) if (_client != null)
_client.SendData(bytes, bytes.Length); _client.SendData(bytes, bytes.Length);
} }
/// <summary> /// <summary>
/// Socket Status Change Handler /// Socket Status Change Handler
@@ -488,7 +496,7 @@ namespace PepperDash.Core
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="clientSocketStatus"></param> /// <param name="clientSocketStatus"></param>
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
{ {
if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{ {
Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
@@ -497,73 +505,68 @@ namespace PepperDash.Core
else else
{ {
Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
_client.ReceiveDataAsync(Receive); _client.ReceiveDataAsync(Receive);
} }
var handler = ConnectionChange; var handler = ConnectionChange;
if (handler != null) if (handler != null)
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
} }
} }
/// <summary> /// <summary>
/// Represents a TcpSshPropertiesConfig /// Represents a TcpSshPropertiesConfig
/// </summary> /// </summary>
public class TcpSshPropertiesConfig public class TcpSshPropertiesConfig
{ {
/// <summary> /// <summary>
/// Address to connect to /// Address to connect to
/// </summary> /// </summary>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public string Address { get; set; } public string Address { get; set; }
/// <summary> /// <summary>
/// Port to connect to /// Port to connect to
/// </summary> /// </summary>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public int Port { get; set; } public int Port { get; set; }
/// <summary> /// <summary>
/// Username credential /// Username credential
/// </summary> /// </summary>
public string Username { get; set; } public string Username { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Password /// Gets or sets the Password
/// </summary> /// </summary>
public string Password { get; set; } public string Password { get; set; }
/// <summary> /// <summary>
/// Defaults to 32768 /// Defaults to 32768
/// </summary> /// </summary>
public int BufferSize { get; set; } public int BufferSize { get; set; }
/// <summary> /// <summary>
/// Gets or sets the AutoReconnect /// Gets or sets the AutoReconnect
/// </summary> /// </summary>
public bool AutoReconnect { get; set; } public bool AutoReconnect { get; set; }
/// <summary> /// <summary>
/// Gets or sets the AutoReconnectIntervalMs /// Gets or sets the AutoReconnectIntervalMs
/// </summary> /// </summary>
public int AutoReconnectIntervalMs { get; set; } public int AutoReconnectIntervalMs { get; set; }
/// <summary>
/// When true, turns off echo for the SSH session
/// </summary>
[JsonProperty("disableSshEcho")]
public bool DisableSshEcho { get; set; }
/// <summary> /// <summary>
/// Default constructor /// Default constructor
/// </summary> /// </summary>
public TcpSshPropertiesConfig() public TcpSshPropertiesConfig()
{ {
BufferSize = 32768; BufferSize = 32768;
AutoReconnect = true; AutoReconnect = true;
AutoReconnectIntervalMs = 5000; AutoReconnectIntervalMs = 5000;
Username = ""; Username = "";
Password = ""; Password = "";
DisableSshEcho = false; }
}
} }
} }

View File

@@ -124,21 +124,21 @@ namespace PepperDash.Core
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="address"></param> /// <param name="address"></param>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="bufferSize"></param> /// <param name="buffefSize"></param>
public GenericUdpServer(string key, string address, int port, int bufferSize) public GenericUdpServer(string key, string address, int port, int buffefSize)
: base(key) : base(key)
{ {
StreamDebugging = new CommunicationStreamDebugging(key); StreamDebugging = new CommunicationStreamDebugging(key);
Hostname = address; Hostname = address;
Port = port; Port = port;
BufferSize = bufferSize; BufferSize = buffefSize;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
@@ -180,7 +180,7 @@ namespace PepperDash.Core
/// <param name="programEventType"></param> /// <param name="programEventType"></param>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{ {
if (programEventType != eProgramStatusEventType.Stopping) if (programEventType != eProgramStatusEventType.Stopping)
return; return;
Debug.Console(1, this, "Program stopping. Disabling Server"); Debug.Console(1, this, "Program stopping. Disabling Server");
@@ -194,21 +194,7 @@ namespace PepperDash.Core
{ {
if (Server == null) if (Server == null)
{ {
try Server = new UDPServer();
{
var address = IPAddress.Parse(Hostname);
Server = new UDPServer(address, Port, BufferSize);
}
catch (Exception ex)
{
this.LogError("Error parsing IP Address '{ipAddress}': message: {message}", Hostname, ex.Message);
this.LogInformation("Creating UDPServer with default buffersize");
Server = new UDPServer();
}
} }
if (string.IsNullOrEmpty(Hostname)) if (string.IsNullOrEmpty(Hostname))
@@ -243,7 +229,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public void Disconnect() public void Disconnect()
{ {
if (Server != null) if(Server != null)
Server.DisableUDPServer(); Server.DisableUDPServer();
IsConnected = false; IsConnected = false;
@@ -265,7 +251,7 @@ namespace PepperDash.Core
try try
{ {
if (numBytes <= 0) if (numBytes <= 0)
return; return;
var sourceIp = Server.IPAddressLastMessageReceivedFrom; var sourceIp = Server.IPAddressLastMessageReceivedFrom;
@@ -281,13 +267,17 @@ namespace PepperDash.Core
var bytesHandler = BytesReceived; var bytesHandler = BytesReceived;
if (bytesHandler != null) 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)); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
} }
var textHandler = TextReceived; var textHandler = TextReceived;
if (textHandler != null) 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)); textHandler(this, new GenericCommMethodReceiveTextArgs(str));
} }
} }
@@ -314,7 +304,8 @@ namespace PepperDash.Core
if (IsConnected && Server != null) 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); Server.SendData(bytes, bytes.Length);
} }
@@ -329,7 +320,8 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public void SendBytes(byte[] bytes) 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) if (IsConnected && Server != null)
Server.SendData(bytes, bytes.Length); Server.SendData(bytes, bytes.Length);
@@ -337,11 +329,11 @@ namespace PepperDash.Core
} }
/// <summary> /// <summary>
/// Represents a GenericUdpReceiveTextExtraArgs /// Represents a GenericUdpReceiveTextExtraArgs
/// </summary> /// </summary>
public class GenericUdpReceiveTextExtraArgs : EventArgs public class GenericUdpReceiveTextExtraArgs : EventArgs
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
@@ -353,7 +345,7 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int Port { get; private set; } public int Port { get; private set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
@@ -367,18 +359,18 @@ namespace PepperDash.Core
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="bytes"></param> /// <param name="bytes"></param>
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes) public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
{ {
Text = text; Text = text;
IpAddress = ipAddress; IpAddress = ipAddress;
Port = port; Port = port;
Bytes = bytes; Bytes = bytes;
} }
/// <summary> /// <summary>
/// Stupid S+ Constructor /// Stupid S+ Constructor
/// </summary> /// </summary>
public GenericUdpReceiveTextExtraArgs() { } public GenericUdpReceiveTextExtraArgs() { }
} }
/// <summary> /// <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,10 +1,4 @@
using System; namespace PepperDash.Essentials.Core.Communications
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Core
{ {
/// <summary> /// <summary>
/// Crestron Control Methods for a comm object /// Crestron Control Methods for a comm object
@@ -74,14 +68,6 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Secure TCP/IP /// Secure TCP/IP
/// </summary> /// </summary>
SecureTcpIp, SecureTcpIp
/// <summary>
/// Used when comms needs to be handled in SIMPL and bridged opposite the normal direction
/// </summary>
ComBridge,
/// <summary>
/// InfinetEX control
/// </summary>
InfinetEx
} }
} }

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

@@ -9,59 +9,40 @@ using Serilog.Events;
namespace PepperDash.Core.Config namespace PepperDash.Core.Config
{ {
/// <summary> /// <summary>
/// Reads a Portal formatted config file /// Reads a Portal formatted config file
/// </summary> /// </summary>
public class PortalConfigReader public class PortalConfigReader
{ {
const string template = "template"; /// <summary>
const string system = "system"; /// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
const string systemUrl = "system_url"; /// </summary>
const string templateUrl = "template_url"; /// <returns>JObject of config file</returns>
const string info = "info"; public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
const string devices = "devices";
const string rooms = "rooms";
const string sourceLists = "sourceLists";
const string destinationLists = "destinationLists";
const string cameraLists = "cameraLists";
const string audioControlPointLists = "audioControlPointLists";
const string tieLines = "tieLines";
const string joinMaps = "joinMaps";
const string global = "global";
/// <summary>
/// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
/// </summary>
/// <returns>JObject of config file</returns>
public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
{ {
try try
{ {
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
Debug.LogError( Debug.Console(1, Debug.ErrorLogLevel.Error,
"ERROR: Configuration file not present. Please load file to {0} and reset program", filePath); "ERROR: Configuration file not present. Please load file to {0} and reset program", filePath);
} }
using (StreamReader fs = new StreamReader(filePath)) using (StreamReader fs = new StreamReader(filePath))
{ {
var jsonObj = JObject.Parse(fs.ReadToEnd()); var jsonObj = JObject.Parse(fs.ReadToEnd());
if(jsonObj[template] != null && jsonObj[system] != null) if(jsonObj["template"] != null && jsonObj["system"] != null)
{ {
// it's a double-config, merge it. // it's a double-config, merge it.
var merged = MergeConfigs(jsonObj); var merged = MergeConfigs(jsonObj);
if (jsonObj[systemUrl] != null) if (jsonObj["system_url"] != null)
{ {
merged[systemUrl] = jsonObj[systemUrl].Value<string>(); merged["systemUrl"] = jsonObj["system_url"].Value<string>();
} }
if (jsonObj[templateUrl] != null) if (jsonObj["template_url"] != null)
{ {
merged[templateUrl] = jsonObj[templateUrl].Value<string>(); merged["templateUrl"] = jsonObj["template_url"].Value<string>();
} }
jsonObj = merged; jsonObj = merged;
@@ -96,62 +77,62 @@ namespace PepperDash.Core.Config
var merged = new JObject(); var merged = new JObject();
// Put together top-level objects // Put together top-level objects
if (system[info] != null) if (system["info"] != null)
merged.Add(info, Merge(template[info], system[info], info)); merged.Add("info", Merge(template["info"], system["info"], "infO"));
else else
merged.Add(info, template[info]); merged.Add("info", template["info"]);
merged.Add(devices, MergeArraysOnTopLevelProperty(template[devices] as JArray, merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray,
system[devices] as JArray, "key", devices)); system["devices"] as JArray, "key", "devices"));
if (system[rooms] == null) if (system["rooms"] == null)
merged.Add(rooms, template[rooms]); merged.Add("rooms", template["rooms"]);
else else
merged.Add(rooms, MergeArraysOnTopLevelProperty(template[rooms] as JArray, merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray,
system[rooms] as JArray, "key", rooms)); system["rooms"] as JArray, "key", "rooms"));
if (system[sourceLists] == null) if (system["sourceLists"] == null)
merged.Add(sourceLists, template[sourceLists]); merged.Add("sourceLists", template["sourceLists"]);
else else
merged.Add(sourceLists, Merge(template[sourceLists], system[sourceLists], sourceLists)); merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"], "sourceLists"));
if (system[destinationLists] == null) if (system["destinationLists"] == null)
merged.Add(destinationLists, template[destinationLists]); merged.Add("destinationLists", template["destinationLists"]);
else else
merged.Add(destinationLists, merged.Add("destinationLists",
Merge(template[destinationLists], system[destinationLists], destinationLists)); Merge(template["destinationLists"], system["destinationLists"], "destinationLists"));
if (system[cameraLists] == null) if (system["cameraLists"] == null)
merged.Add(cameraLists, template[cameraLists]); merged.Add("cameraLists", template["cameraLists"]);
else else
merged.Add(cameraLists, Merge(template[cameraLists], system[cameraLists], cameraLists)); merged.Add("cameraLists", Merge(template["cameraLists"], system["cameraLists"], "cameraLists"));
if (system[audioControlPointLists] == null) if (system["audioControlPointLists"] == null)
merged.Add(audioControlPointLists, template[audioControlPointLists]); merged.Add("audioControlPointLists", template["audioControlPointLists"]);
else else
merged.Add(audioControlPointLists, merged.Add("audioControlPointLists",
Merge(template[audioControlPointLists], system[audioControlPointLists], audioControlPointLists)); Merge(template["audioControlPointLists"], system["audioControlPointLists"], "audioControlPointLists"));
// Template tie lines take precedence. Config tool doesn't do them at system // Template tie lines take precedence. Config tool doesn't do them at system
// level anyway... // level anyway...
if (template[tieLines] != null) if (template["tieLines"] != null)
merged.Add(tieLines, template[tieLines]); merged.Add("tieLines", template["tieLines"]);
else if (system[tieLines] != null) else if (system["tieLines"] != null)
merged.Add(tieLines, system[tieLines]); merged.Add("tieLines", system["tieLines"]);
else else
merged.Add(tieLines, new JArray()); merged.Add("tieLines", new JArray());
if (template[joinMaps] != null) if (template["joinMaps"] != null)
merged.Add(joinMaps, template[joinMaps]); merged.Add("joinMaps", template["joinMaps"]);
else else
merged.Add(joinMaps, new JObject()); merged.Add("joinMaps", new JObject());
if (system[global] != null) if (system["global"] != null)
merged.Add(global, Merge(template[global], system[global], global)); merged.Add("global", Merge(template["global"], system["global"], "global"));
else else
merged.Add(global, template[global]); merged.Add("global", template["global"]);
//Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged); //Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
return merged; return merged;
@@ -247,7 +228,7 @@ namespace PepperDash.Core.Config
} }
catch (Exception e) catch (Exception e)
{ {
Debug.LogError($"Cannot merge items at path {propPath}: \r{e}"); Debug.Console(1, Debug.ErrorLogLevel.Warning, "Cannot merge items at path {0}: \r{1}", propPath, e);
} }
} }
} }

View File

@@ -162,7 +162,7 @@ namespace PepperDash.Core.JsonToSimpl
/// <returns></returns> /// <returns></returns>
public static JObject ParseObject(string json) public static JObject ParseObject(string json)
{ {
#if NET6_0 #if NET8_0
using (var reader = new JsonTextReader(new System.IO.StringReader(json))) using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
#else #else
using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json))) using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json)))
@@ -186,7 +186,7 @@ namespace PepperDash.Core.JsonToSimpl
/// </summary> /// </summary>
public static JArray ParseArray(string json) public static JArray ParseArray(string json)
{ {
#if NET6_0 #if NET8_0
using (var reader = new JsonTextReader(new System.IO.StringReader(json))) using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
#else #else
using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json))) using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json)))

View File

@@ -1,9 +1,4 @@
using System; using Crestron.SimplSharp;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronDataStore; using Crestron.SimplSharp.CrestronDataStore;
using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharp.CrestronLogger; using Crestron.SimplSharp.CrestronLogger;
@@ -16,10 +11,15 @@ using Serilog.Events;
using Serilog.Formatting.Compact; using Serilog.Formatting.Compact;
using Serilog.Formatting.Json; using Serilog.Formatting.Json;
using Serilog.Templates; using Serilog.Templates;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
namespace PepperDash.Core namespace PepperDash.Core
{ {
/// <summary> /// <summary>
/// Contains debug commands for use in various situations
/// </summary> /// </summary>
public static class Debug public static class Debug
{ {
@@ -40,27 +40,21 @@ namespace PepperDash.Core
private static ILogger _logger; private static ILogger _logger;
private static readonly LoggingLevelSwitch _consoleLogLevelSwitch; private static readonly LoggingLevelSwitch _consoleLoggingLevelSwitch;
private static readonly LoggingLevelSwitch _websocketLogLevelSwitch; private static readonly LoggingLevelSwitch _websocketLoggingLevelSwitch;
private static readonly LoggingLevelSwitch _errorLogLevelSwitch; private static readonly LoggingLevelSwitch _errorLogLevelSwitch;
private static readonly LoggingLevelSwitch _fileLogLevelSwitch; private static readonly LoggingLevelSwitch _fileLevelSwitch;
/// <summary>
/// Gets the minimum log level for the websocket sink.
/// </summary>
public static LogEventLevel WebsocketMinimumLogLevel public static LogEventLevel WebsocketMinimumLogLevel
{ {
get { return _websocketLogLevelSwitch.MinimumLevel; } get { return _websocketLoggingLevelSwitch.MinimumLevel; }
} }
private static readonly DebugWebsocketSink _websocketSink; private static readonly DebugWebsocketSink _websocketSink;
/// <summary>
/// Gets the websocket sink for debug logging.
/// </summary>
public static DebugWebsocketSink WebsocketSink public static DebugWebsocketSink WebsocketSink
{ {
get { return _websocketSink; } get { return _websocketSink; }
@@ -97,33 +91,29 @@ namespace PepperDash.Core
private const int SaveTimeoutMs = 30000; private const int SaveTimeoutMs = 30000;
/// <summary>
/// Indicates whether the system is running on an appliance.
/// </summary>
public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance; public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance;
/// <summary> /// <summary>
/// Gets or sets the PepperDashCoreVersion /// Gets or sets the PepperDashCoreVersion
/// </summary> /// </summary>
public static string PepperDashCoreVersion { get; private set; } public static string PepperDashCoreVersion { get; private set; }
private static CTimer _saveTimer; private static CTimer _saveTimer;
/// <summary> /// <summary>
/// When true, the IncludedExcludedKeys dict will contain keys to include. /// When true, the IncludedExcludedKeys dict will contain keys to include.
/// When false (default), IncludedExcludedKeys will contain keys to exclude. /// When false (default), IncludedExcludedKeys will contain keys to exclude.
/// </summary> /// </summary>
private static bool _excludeAllMode; private static bool _excludeAllMode;
private static readonly Dictionary<string, object> IncludedExcludedKeys; //static bool ExcludeNoKeyMessages;
private static readonly Dictionary<string, object> IncludedExcludedKeys;
private static readonly LoggerConfiguration _defaultLoggerConfiguration; private static readonly LoggerConfiguration _defaultLoggerConfiguration;
private static LoggerConfiguration _loggerConfiguration; private static LoggerConfiguration _loggerConfiguration;
/// <summary>
/// Gets the logger configuration for the debug logging.
/// </summary>
public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration; public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration;
static Debug() static Debug()
@@ -138,13 +128,13 @@ namespace PepperDash.Core
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey); var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey);
_consoleLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel); _consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
_websocketLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel); _websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
_errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel); _errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
_fileLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel); _fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
_websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true)); _websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
@@ -154,7 +144,7 @@ namespace PepperDash.Core
CrestronConsole.PrintLine($"Saving log files to {logFilePath}"); CrestronConsole.PrintLine($"Saving log files to {logFilePath}");
var errorLogTemplate = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance var errorLogTemplate = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}" ? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"
: "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"; : "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}";
@@ -162,14 +152,14 @@ namespace PepperDash.Core
.MinimumLevel.Verbose() .MinimumLevel.Verbose()
.Enrich.FromLogContext() .Enrich.FromLogContext()
.Enrich.With(new CrestronEnricher()) .Enrich.With(new CrestronEnricher())
.WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLogLevelSwitch) .WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLoggingLevelSwitch)
.WriteTo.Sink(_websocketSink, levelSwitch: _websocketLogLevelSwitch) .WriteTo.Sink(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch)
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch) .WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch)
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath, .WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
rollingInterval: RollingInterval.Day, rollingInterval: RollingInterval.Day,
restrictedToMinimumLevel: LogEventLevel.Debug, restrictedToMinimumLevel: LogEventLevel.Debug,
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 7 : 14, retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
levelSwitch: _fileLogLevelSwitch levelSwitch: _fileLevelSwitch
); );
try try
@@ -203,27 +193,27 @@ namespace PepperDash.Core
CrestronConsole.PrintLine(msg); CrestronConsole.PrintLine(msg);
LogMessage(LogEventLevel.Information, msg); LogMessage(LogEventLevel.Information,msg);
IncludedExcludedKeys = new Dictionary<string, object>();
IncludedExcludedKeys = new Dictionary<string, object>();
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro) if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
{ {
// Add command to console // Add command to console
CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot", CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot",
"donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator); "donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug", CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
"appdebug:P [0-5]: Sets the application's console debug message level", "appdebug:P [0-5]: Sets the application's console debug message level",
ConsoleAccessLevelEnum.AccessOperator); ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog", CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
"appdebuglog:P [all] Use \"all\" for full log.", "appdebuglog:P [all] Use \"all\" for full log.",
ConsoleAccessLevelEnum.AccessOperator); ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear", CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
"appdebugclear:P Clears the current custom log", "appdebugclear:P Clears the current custom log",
ConsoleAccessLevelEnum.AccessOperator); ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter", CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
"appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator); "appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator);
} }
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
@@ -234,16 +224,13 @@ namespace PepperDash.Core
Level = context.Level; Level = context.Level;
DoNotLoadConfigOnNextBoot = context.DoNotLoadOnNextBoot; DoNotLoadConfigOnNextBoot = context.DoNotLoadOnNextBoot;
if (DoNotLoadConfigOnNextBoot) if(DoNotLoadConfigOnNextBoot)
CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber)); CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber));
_errorLogLevelSwitch.MinimumLevelChanged += (sender, args) => _consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
{ {
LogMessage(LogEventLevel.Information, "Error log debug level set to {minimumLevel}", _errorLogLevelSwitch.MinimumLevel); LogMessage(LogEventLevel.Information, "Console debug level set to {minimumLevel}", _consoleLoggingLevelSwitch.MinimumLevel);
}; };
// Set initial error log level based on platform && stored level. If appliance, use stored level, otherwise default to verbose
SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? _errorLogLevelSwitch.MinimumLevel : LogEventLevel.Verbose);
} }
/// <summary> /// <summary>
@@ -270,28 +257,22 @@ namespace PepperDash.Core
{ {
try try
{ {
var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel); var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel);
if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
{ {
CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n"); CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n");
return LogEventLevel.Information;
CrestronDataStoreStatic.SetLocalIntValue(levelStoreKey, levelStoreKey == ErrorLogLevelStoreKey ? (int)LogEventLevel.Warning : (int)LogEventLevel.Information);
return levelStoreKey == ErrorLogLevelStoreKey ? LogEventLevel.Warning : LogEventLevel.Information;
} }
if (logLevel < 0 || logLevel > 5) if(logLevel < 0 || logLevel > 5)
{ {
CrestronConsole.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}"); CrestronConsole.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}");
return LogEventLevel.Information; return LogEventLevel.Information;
} }
CrestronConsole.PrintLine($"Stored log level for {levelStoreKey} is {logLevel}");
return (LogEventLevel)logLevel; return (LogEventLevel)logLevel;
} } catch (Exception ex)
catch (Exception ex)
{ {
CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}"); CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}");
return LogEventLevel.Information; return LogEventLevel.Information;
@@ -303,7 +284,7 @@ namespace PepperDash.Core
var assembly = Assembly.GetExecutingAssembly(); var assembly = Assembly.GetExecutingAssembly();
var ver = var ver =
assembly assembly
.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false); .GetCustomAttributes(typeof (AssemblyInformationalVersionAttribute), false);
if (ver != null && ver.Length > 0) if (ver != null && ver.Length > 0)
{ {
@@ -354,104 +335,44 @@ namespace PepperDash.Core
if (levelString.Trim() == "?") if (levelString.Trim() == "?")
{ {
CrestronConsole.ConsoleCommandResponse( CrestronConsole.ConsoleCommandResponse(
"Used to set the minimum level of debug messages:\r\n" + $@"Used to set the minimum level of debug messages to be printed to the console:
"Usage: appdebug:P [sink] [level]\r\n" + {_logLevels[0]} = 0
" sink: console (default), errorlog, file, all\r\n" + {_logLevels[1]} = 1
" all: sets all sinks to the specified level\r\n" + {_logLevels[2]} = 2
" level: 0-5 or LogEventLevel name\r\n" + {_logLevels[3]} = 3
$"{_logLevels[0]} = 0\r\n" + {_logLevels[4]} = 4
$"{_logLevels[1]} = 1\r\n" + {_logLevels[5]} = 5");
$"{_logLevels[2]} = 2\r\n" +
$"{_logLevels[3]} = 3\r\n" +
$"{_logLevels[4]} = 4\r\n" +
$"{_logLevels[5]} = 5");
return; return;
} }
if (string.IsNullOrEmpty(levelString.Trim())) if (string.IsNullOrEmpty(levelString.Trim()))
{ {
CrestronConsole.ConsoleCommandResponse("Console log level = {0}\r\n", _consoleLogLevelSwitch.MinimumLevel); CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", _consoleLoggingLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse("File log level = {0}\r\n", _fileLogLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse("Error log level = {0}\r\n", _errorLogLevelSwitch.MinimumLevel);
return; return;
} }
// Parse tokens: first token is sink (defaults to console), second token is level if(int.TryParse(levelString, out var levelInt))
var tokens = levelString.Trim().Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
string sinkName;
string levelToken;
if (tokens.Length == 1)
{ {
// Single token - assume it's a level for console sink if(levelInt < 0 || levelInt > 5)
sinkName = "console";
levelToken = tokens[0];
}
else if (tokens.Length == 2)
{
// Two tokens - first is sink, second is level
sinkName = tokens[0].ToLowerInvariant();
levelToken = tokens[1];
}
else
{
CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [sink] [level]");
return;
}
// Parse the level using the same logic as before
LogEventLevel level;
if (int.TryParse(levelToken, out var levelInt))
{
if (levelInt < 0 || levelInt > 5)
{ {
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelToken} to valid log level. If using a number, value must be between 0-5"); CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level. If using a number, value must be between 0-5");
return; return;
} }
SetDebugLevel((uint) levelInt);
if (!_logLevels.TryGetValue((uint)levelInt, out level))
{
level = LogEventLevel.Information;
CrestronConsole.ConsoleCommandResponse($"{levelInt} not valid. Setting level to {level}");
}
}
else if (Enum.TryParse(levelToken, true, out level))
{
// Successfully parsed as LogEventLevel enum
}
else
{
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelToken} to valid log level");
return; return;
} }
// Set the level for the specified sink if(Enum.TryParse<LogEventLevel>(levelString, out var levelEnum))
switch (sinkName)
{ {
case "console": SetDebugLevel(levelEnum);
SetDebugLevel(level); return;
break;
case "errorlog":
SetErrorLogMinimumDebugLevel(level);
break;
case "file":
SetFileMinimumDebugLevel(level);
break;
case "all":
SetDebugLevel(level);
SetErrorLogMinimumDebugLevel(level);
SetFileMinimumDebugLevel(level);
break;
default:
CrestronConsole.ConsoleCommandResponse($"Error: Unknown sink '{sinkName}'. Valid sinks: console, errorlog, file");
break;
} }
}
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
}
catch catch
{ {
CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [sink] [level]"); CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5]");
} }
} }
@@ -464,7 +385,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void SetDebugLevel(uint level) public static void SetDebugLevel(uint level)
{ {
if (!_logLevels.TryGetValue(level, out var logLevel)) if(!_logLevels.TryGetValue(level, out var logLevel))
{ {
logLevel = LogEventLevel.Information; logLevel = LogEventLevel.Information;
@@ -481,14 +402,14 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void SetDebugLevel(LogEventLevel level) public static void SetDebugLevel(LogEventLevel level)
{ {
_consoleLogLevelSwitch.MinimumLevel = level; _consoleLoggingLevelSwitch.MinimumLevel = level;
CrestronConsole.ConsoleCommandResponse("[Application {0}] Debug level set to {1}\r\n", CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}\r\n",
InitialParametersClass.ApplicationNumber, _consoleLogLevelSwitch.MinimumLevel); InitialParametersClass.ApplicationNumber, _consoleLoggingLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}"); CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int) level}");
var err = CrestronDataStoreStatic.SetLocalIntValue(LevelStoreKey, (int)level); var err = CrestronDataStoreStatic.SetLocalIntValue(LevelStoreKey, (int) level);
CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}"); CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}");
@@ -501,14 +422,14 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void SetWebSocketMinimumDebugLevel(LogEventLevel level) public static void SetWebSocketMinimumDebugLevel(LogEventLevel level)
{ {
_websocketLogLevelSwitch.MinimumLevel = level; _websocketLoggingLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint)level); var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint) level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err); LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err);
LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", _websocketLogLevelSwitch.MinimumLevel); LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
} }
/// <summary> /// <summary>
@@ -518,17 +439,12 @@ namespace PepperDash.Core
{ {
_errorLogLevelSwitch.MinimumLevel = level; _errorLogLevelSwitch.MinimumLevel = level;
CrestronConsole.ConsoleCommandResponse("[Application {0}] Error log level set to {1}\r\n", var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
InitialParametersClass.ApplicationNumber, _errorLogLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
var err = CrestronDataStoreStatic.SetLocalIntValue(ErrorLogLevelStoreKey, (int)level);
CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}");
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
CrestronConsole.PrintLine($"Error saving error log debug level setting: {err}"); LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting: {error}", err);
LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
} }
/// <summary> /// <summary>
@@ -536,19 +452,14 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void SetFileMinimumDebugLevel(LogEventLevel level) public static void SetFileMinimumDebugLevel(LogEventLevel level)
{ {
_fileLogLevelSwitch.MinimumLevel = level; _errorLogLevelSwitch.MinimumLevel = level;
CrestronConsole.ConsoleCommandResponse("[Application {0}] File log level set to {1}\r\n", var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
InitialParametersClass.ApplicationNumber, _fileLogLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
var err = CrestronDataStoreStatic.SetLocalIntValue(FileLevelStoreKey, (int)level);
CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}");
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
CrestronConsole.PrintLine($"Error saving file debug level setting: {err}"); LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err);
LogMessage(LogEventLevel.Information, "File debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
} }
/// <summary> /// <summary>
@@ -580,83 +491,83 @@ namespace PepperDash.Core
/// Callback for console command /// Callback for console command
/// </summary> /// </summary>
/// <param name="items"></param> /// <param name="items"></param>
/// <summary> /// <summary>
/// SetDebugFilterFromConsole method /// SetDebugFilterFromConsole method
/// </summary> /// </summary>
public static void SetDebugFilterFromConsole(string items) public static void SetDebugFilterFromConsole(string items)
{ {
var str = items.Trim(); var str = items.Trim();
if (str == "?") if (str == "?")
{ {
CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " + CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " +
"+all: at beginning puts filter into 'default include' mode\r" + "+all: at beginning puts filter into 'default include' mode\r" +
" All keys that follow will be excluded from output.\r" + " All keys that follow will be excluded from output.\r" +
"-all: at beginning puts filter into 'default exclude all' mode.\r" + "-all: at beginning puts filter into 'default exclude all' mode.\r" +
" All keys that follow will be the only keys that are shown\r" + " All keys that follow will be the only keys that are shown\r" +
"+nokey: Enables messages with no key (default)\r" + "+nokey: Enables messages with no key (default)\r" +
"-nokey: Disables messages with no key.\r" + "-nokey: Disables messages with no key.\r" +
"(nokey settings are independent of all other settings)"); "(nokey settings are independent of all other settings)");
return; return;
} }
var keys = Regex.Split(str, @"\s*"); var keys = Regex.Split(str, @"\s*");
foreach (var keyToken in keys) foreach (var keyToken in keys)
{ {
var lkey = keyToken.ToLower(); var lkey = keyToken.ToLower();
if (lkey == "+all") if (lkey == "+all")
{ {
IncludedExcludedKeys.Clear(); IncludedExcludedKeys.Clear();
_excludeAllMode = false; _excludeAllMode = false;
} }
else if (lkey == "-all") else if (lkey == "-all")
{ {
IncludedExcludedKeys.Clear(); IncludedExcludedKeys.Clear();
_excludeAllMode = true; _excludeAllMode = true;
} }
//else if (lkey == "+nokey") //else if (lkey == "+nokey")
//{ //{
// ExcludeNoKeyMessages = false; // ExcludeNoKeyMessages = false;
//} //}
//else if (lkey == "-nokey") //else if (lkey == "-nokey")
//{ //{
// ExcludeNoKeyMessages = true; // ExcludeNoKeyMessages = true;
//} //}
else else
{ {
string key; string key;
if (lkey.StartsWith("-")) if (lkey.StartsWith("-"))
{ {
key = lkey.Substring(1); key = lkey.Substring(1);
// if in exclude all mode, we need to remove this from the inclusions // if in exclude all mode, we need to remove this from the inclusions
if (_excludeAllMode) if (_excludeAllMode)
{ {
if (IncludedExcludedKeys.ContainsKey(key)) if (IncludedExcludedKeys.ContainsKey(key))
IncludedExcludedKeys.Remove(key); IncludedExcludedKeys.Remove(key);
} }
// otherwise include all mode, add to the exclusions // otherwise include all mode, add to the exclusions
else else
{ {
IncludedExcludedKeys[key] = new object(); IncludedExcludedKeys[key] = new object();
} }
} }
else if (lkey.StartsWith("+")) else if (lkey.StartsWith("+"))
{ {
key = lkey.Substring(1); key = lkey.Substring(1);
// if in exclude all mode, we need to add this as inclusion // if in exclude all mode, we need to add this as inclusion
if (_excludeAllMode) if (_excludeAllMode)
{ {
IncludedExcludedKeys[key] = new object(); IncludedExcludedKeys[key] = new object();
} }
// otherwise include all mode, remove this from exclusions // otherwise include all mode, remove this from exclusions
else else
{ {
if (IncludedExcludedKeys.ContainsKey(key)) if (IncludedExcludedKeys.ContainsKey(key))
IncludedExcludedKeys.Remove(key); IncludedExcludedKeys.Remove(key);
} }
} }
} }
} }
} }
@@ -684,7 +595,7 @@ namespace PepperDash.Core
public static object GetDeviceDebugSettingsForKey(string deviceKey) public static object GetDeviceDebugSettingsForKey(string deviceKey)
{ {
return _contexts.GetDebugSettingsForKey(deviceKey); return _contexts.GetDebugSettingsForKey(deviceKey);
} }
/// <summary> /// <summary>
/// Sets the flag to prevent application starting on next boot /// Sets the flag to prevent application starting on next boot
@@ -743,15 +654,9 @@ namespace PepperDash.Core
} }
} }
/// <summary>
/// Logs a message at the specified log level.
/// </summary>
/// <param name="level">Level to log at</param>
/// <param name="message">Message template</param>
/// <param name="args">Args to put into message template</param>
public static void LogMessage(LogEventLevel level, string message, params object[] args) public static void LogMessage(LogEventLevel level, string message, params object[] args)
{ {
_logger.Write(level, message, args); _logger.Write(level, message, args);
} }
/// <summary> /// <summary>
@@ -787,7 +692,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void LogVerbose(IKeyed keyed, string message, params object[] args) public static void LogVerbose(IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) using(LogContext.PushProperty("Key", keyed?.Key))
{ {
_logger.Write(LogEventLevel.Verbose, message, args); _logger.Write(LogEventLevel.Verbose, message, args);
} }
@@ -798,7 +703,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args) public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) using(LogContext.PushProperty("Key", keyed?.Key))
{ {
_logger.Write(LogEventLevel.Verbose, ex, message, args); _logger.Write(LogEventLevel.Verbose, ex, message, args);
} }
@@ -817,7 +722,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void LogVerbose(Exception ex, string message, params object[] args) public static void LogVerbose(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Verbose, ex, message, args); _logger.Write(LogEventLevel.Verbose, ex, null, message, args);
} }
/// <summary> /// <summary>
@@ -893,7 +798,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void LogInformation(Exception ex, string message, params object[] args) public static void LogInformation(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Information, ex, message, args); _logger.Write(LogEventLevel.Information, ex, null, message, args);
} }
/// <summary> /// <summary>
@@ -931,7 +836,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void LogWarning(Exception ex, string message, params object[] args) public static void LogWarning(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Warning, ex, message, args); _logger.Write(LogEventLevel.Warning, ex, null, message, args);
} }
/// <summary> /// <summary>
@@ -969,7 +874,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void LogError(Exception ex, string message, params object[] args) public static void LogError(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Error, ex, message, args); _logger.Write(LogEventLevel.Error, ex, null, message, args);
} }
/// <summary> /// <summary>
@@ -1007,7 +912,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void LogFatal(Exception ex, string message, params object[] args) public static void LogFatal(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Fatal, ex, message, args); _logger.Write(LogEventLevel.Fatal, ex, null, message, args);
} }
#endregion #endregion
@@ -1018,7 +923,7 @@ namespace PepperDash.Core
if (!_logLevels.ContainsKey(level)) return; if (!_logLevels.ContainsKey(level)) return;
var logLevel = _logLevels[level]; var logLevel = _logLevels[level];
LogMessage(logLevel, format, items); LogMessage(logLevel, format, items);
} }
@@ -1073,7 +978,7 @@ namespace PepperDash.Core
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel, public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel,
string format, params object[] items) string format, params object[] items)
{ {
LogMessage(level, dev, format, items); LogMessage(level, dev, format, items);
} }
@@ -1081,6 +986,9 @@ namespace PepperDash.Core
/// Logs to Console when at-level, and all messages to error log /// Logs to Console when at-level, and all messages to error log
/// </summary> /// </summary>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
/// <summary>
/// Console method
/// </summary>
public static void Console(uint level, ErrorLogLevel errorLogLevel, public static void Console(uint level, ErrorLogLevel errorLogLevel,
string format, params object[] items) string format, params object[] items)
{ {
@@ -1093,6 +1001,9 @@ namespace PepperDash.Core
/// it will only be written to the log. /// it will only be written to the log.
/// </summary> /// </summary>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
/// <summary>
/// ConsoleWithLog method
/// </summary>
public static void ConsoleWithLog(uint level, string format, params object[] items) public static void ConsoleWithLog(uint level, string format, params object[] items)
{ {
LogMessage(level, format, items); LogMessage(level, format, items);
@@ -1211,7 +1122,7 @@ namespace PepperDash.Core
return string.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); return string.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber);
} }
return string.Format("{0}{1}user{1}debugSettings{1}{2}.json", Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId); return string.Format("{0}{1}user{1}debugSettings{1}{2}.json",Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId);
} }
/// <summary> /// <summary>
@@ -1222,15 +1133,15 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Error /// Error
/// </summary> /// </summary>
Error, Error,
/// <summary> /// <summary>
/// Warning /// Warning
/// </summary> /// </summary>
Warning, Warning,
/// <summary> /// <summary>
/// Notice /// Notice
/// </summary> /// </summary>
Notice, Notice,
/// <summary> /// <summary>
/// None /// None
/// </summary> /// </summary>

View File

@@ -1,5 +1,5 @@
using System; using Serilog.Events;
using Serilog.Events; using System;
using Log = PepperDash.Core.Debug; using Log = PepperDash.Core.Debug;
namespace PepperDash.Core.Logging namespace PepperDash.Core.Logging
@@ -11,7 +11,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogException(this IKeyed device, Exception ex, string message, params object[] args) public static void LogException(this IKeyed device, Exception ex, string message, params object[] args)
{ {
Log.LogMessage(ex, message, device: device, args); Log.LogMessage(ex, message, device, args);
} }
/// <summary> /// <summary>
@@ -19,7 +19,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogVerbose(this IKeyed device, Exception ex, string message, params object[] args) public static void LogVerbose(this IKeyed device, Exception ex, string message, params object[] args)
{ {
Log.LogVerbose(ex, device, message, args); Log.LogMessage(LogEventLevel.Verbose, ex, message, device, args);
} }
/// <summary> /// <summary>
@@ -27,7 +27,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogVerbose(this IKeyed device, string message, params object[] args) public static void LogVerbose(this IKeyed device, string message, params object[] args)
{ {
Log.LogVerbose(device, message, args); Log.LogMessage(LogEventLevel.Verbose, device, message, args);
} }
/// <summary> /// <summary>
@@ -35,7 +35,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogDebug(this IKeyed device, Exception ex, string message, params object[] args) public static void LogDebug(this IKeyed device, Exception ex, string message, params object[] args)
{ {
Log.LogDebug(ex, device, message, args); Log.LogMessage(LogEventLevel.Debug, ex, message, device, args);
} }
/// <summary> /// <summary>
@@ -43,7 +43,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogDebug(this IKeyed device, string message, params object[] args) public static void LogDebug(this IKeyed device, string message, params object[] args)
{ {
Log.LogDebug(device, message, args); Log.LogMessage(LogEventLevel.Debug, device, message, args);
} }
/// <summary> /// <summary>
@@ -51,7 +51,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogInformation(this IKeyed device, Exception ex, string message, params object[] args) public static void LogInformation(this IKeyed device, Exception ex, string message, params object[] args)
{ {
Log.LogInformation(ex, device, message, args); Log.LogMessage(LogEventLevel.Information, ex, message, device, args);
} }
/// <summary> /// <summary>
@@ -59,7 +59,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogInformation(this IKeyed device, string message, params object[] args) public static void LogInformation(this IKeyed device, string message, params object[] args)
{ {
Log.LogInformation(device, message, args); Log.LogMessage(LogEventLevel.Information, device, message, args);
} }
/// <summary> /// <summary>
@@ -67,7 +67,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogWarning(this IKeyed device, Exception ex, string message, params object[] args) public static void LogWarning(this IKeyed device, Exception ex, string message, params object[] args)
{ {
Log.LogWarning(ex, device, message, args); Log.LogMessage(LogEventLevel.Warning, ex, message, device, args);
} }
/// <summary> /// <summary>
@@ -75,7 +75,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogWarning(this IKeyed device, string message, params object[] args) public static void LogWarning(this IKeyed device, string message, params object[] args)
{ {
Log.LogWarning(device, message, args); Log.LogMessage(LogEventLevel.Warning, device, message, args);
} }
/// <summary> /// <summary>
@@ -83,7 +83,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogError(this IKeyed device, Exception ex, string message, params object[] args) public static void LogError(this IKeyed device, Exception ex, string message, params object[] args)
{ {
Log.LogError(ex, device, message, args); Log.LogMessage(LogEventLevel.Error, ex, message, device, args);
} }
/// <summary> /// <summary>
@@ -91,7 +91,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogError(this IKeyed device, string message, params object[] args) public static void LogError(this IKeyed device, string message, params object[] args)
{ {
Log.LogError(device, message, args); Log.LogMessage(LogEventLevel.Error, device, message, args);
} }
/// <summary> /// <summary>
@@ -99,7 +99,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogFatal(this IKeyed device, Exception ex, string message, params object[] args) public static void LogFatal(this IKeyed device, Exception ex, string message, params object[] args)
{ {
Log.LogFatal(ex, device, message, args); Log.LogMessage(LogEventLevel.Fatal, ex, message, device, args);
} }
/// <summary> /// <summary>
@@ -107,7 +107,7 @@ namespace PepperDash.Core.Logging
/// </summary> /// </summary>
public static void LogFatal(this IKeyed device, string message, params object[] args) public static void LogFatal(this IKeyed device, string message, params object[] args)
{ {
Log.LogFatal(device, message, args); Log.LogMessage(LogEventLevel.Fatal, device, message, args);
} }
} }
} }

View File

@@ -5,7 +5,7 @@
<PropertyGroup> <PropertyGroup>
<RootNamespace>PepperDash.Core</RootNamespace> <RootNamespace>PepperDash.Core</RootNamespace>
<AssemblyName>PepperDashCore</AssemblyName> <AssemblyName>PepperDashCore</AssemblyName>
<TargetFramework>net472</TargetFramework> <TargetFramework>net8</TargetFramework>
<Deterministic>true</Deterministic> <Deterministic>true</Deterministic>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<OutputPath>bin\$(Configuration)\</OutputPath> <OutputPath>bin\$(Configuration)\</OutputPath>
@@ -44,6 +44,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" /> <PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" />
<PackageReference Include="Crestron.SimplSharp.SDK.Library" Version="2.21.90" /> <PackageReference Include="Crestron.SimplSharp.SDK.Library" Version="2.21.90" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.1.1" /> <PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.Expressions" Version="4.0.0" /> <PackageReference Include="Serilog.Expressions" Version="4.0.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="2.0.0" /> <PackageReference Include="Serilog.Formatting.Compact" Version="2.0.0" />
@@ -52,9 +53,6 @@
<PackageReference Include="SSH.NET" Version="2024.2.0" /> <PackageReference Include="SSH.NET" Version="2024.2.0" />
<PackageReference Include="WebSocketSharp" Version="1.0.3-rc11" /> <PackageReference Include="WebSocketSharp" Version="1.0.3-rc11" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6'">
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="Comm\._GenericSshClient.cs" /> <Compile Remove="Comm\._GenericSshClient.cs" />
<Compile Remove="Comm\._GenericTcpIpClient.cs" /> <Compile Remove="Comm\._GenericTcpIpClient.cs" />

View File

@@ -6,25 +6,24 @@ using Crestron.SimplSharp;
using Crestron.SimplSharpPro; using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using Crestron.SimplSharpPro.EthernetCommunication; using Crestron.SimplSharpPro.EthernetCommunication;
using Newtonsoft.Json; using Newtonsoft.Json;
using PepperDash.Core; using PepperDash.Essentials.Core.Communications;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Devices;
using PepperDash.Essentials.Core.JoinMaps;
using PepperDash.Essentials.Core.Monitoring;
using Serilog.Events; using Serilog.Events;
//using PepperDash.Essentials.Devices.Common.Cameras;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges
{ {
/// <summary> /// <summary>
/// Base class for bridge API variants /// Base class for bridge API variants
/// </summary> /// </summary>
[Obsolete("Will be removed in v3.0.0")]
public abstract class BridgeApi : EssentialsDevice public abstract class BridgeApi : EssentialsDevice
{ {
/// <summary>
/// Constructor
/// </summary>
/// <param name="key">Device key</param>
protected BridgeApi(string key) : protected BridgeApi(string key) :
base(key) base(key)
{ {
@@ -33,36 +32,23 @@ namespace PepperDash.Essentials.Core.Bridges
} }
/// <summary> /// <summary>
/// Class to link devices and rooms to an EISC Instance /// Represents a EiscApiAdvanced
/// </summary> /// </summary>
public class EiscApiAdvanced : BridgeApi, ICommunicationMonitor public class EiscApiAdvanced : BridgeApi, ICommunicationMonitor
{ {
/// <summary>
/// Gets the PropertiesConfig
/// </summary>
public EiscApiPropertiesConfig PropertiesConfig { get; private set; } public EiscApiPropertiesConfig PropertiesConfig { get; private set; }
/// <summary>
/// Gets the JoinMaps dictionary
/// </summary>
public Dictionary<string, JoinMapBaseAdvanced> JoinMaps { get; private set; } public Dictionary<string, JoinMapBaseAdvanced> JoinMaps { get; private set; }
/// <summary>
/// Gets the EISC instance
/// </summary>
public BasicTriList Eisc { get; private set; } 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) : public EiscApiAdvanced(DeviceConfig dc, BasicTriList eisc) :
base(dc.Key) base(dc.Key)
{ {
JoinMaps = new Dictionary<string, JoinMapBaseAdvanced>(); JoinMaps = new Dictionary<string, JoinMapBaseAdvanced>();
PropertiesConfig = dc.Properties.ToObject<EiscApiPropertiesConfig>(); PropertiesConfig = dc.Properties.ToObject<EiscApiPropertiesConfig>();
//PropertiesConfig = JsonConvert.DeserializeObject<EiscApiPropertiesConfig>(dc.Properties.ToString());
Eisc = eisc; Eisc = eisc;
@@ -77,7 +63,8 @@ namespace PepperDash.Essentials.Core.Bridges
/// <summary> /// <summary>
/// CustomActivate method /// CustomActivate method
/// </summary> /// </summary>
/// <inheritdoc />
public override bool CustomActivate() public override bool CustomActivate()
{ {
CommunicationMonitor.Start(); CommunicationMonitor.Start();
@@ -99,7 +86,7 @@ namespace PepperDash.Essentials.Core.Bridges
if (PropertiesConfig.Devices == null) if (PropertiesConfig.Devices == null)
{ {
this.LogDebug("No devices linked to this bridge"); Debug.LogMessage(LogEventLevel.Debug, this, "No devices linked to this bridge");
return; return;
} }
@@ -120,7 +107,9 @@ namespace PepperDash.Essentials.Core.Bridges
continue; 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 +124,34 @@ namespace PepperDash.Essentials.Core.Bridges
if (registerResult != eDeviceRegistrationUnRegistrationResponse.Success) if (registerResult != eDeviceRegistrationUnRegistrationResponse.Success)
{ {
this.LogVerbose("Registration result: {registerResult}", registerResult); Debug.LogMessage(LogEventLevel.Verbose, this, "Registration result: {0}", registerResult);
return; return;
} }
this.LogDebug("EISC registration successful"); Debug.LogMessage(LogEventLevel.Debug, this, "EISC registration successful");
} }
/// <summary> /// <summary>
/// Link rooms to this EISC. Rooms MUST implement IBridgeAdvanced /// LinkRooms method
/// </summary> /// </summary>
public void LinkRooms() public void LinkRooms()
{ {
this.LogDebug("Linking Rooms..."); Debug.LogMessage(LogEventLevel.Debug, this, "Linking Rooms...");
if (PropertiesConfig.Rooms == null) if (PropertiesConfig.Rooms == null)
{ {
this.LogDebug("No rooms linked to this bridge."); Debug.LogMessage(LogEventLevel.Debug, this, "No rooms linked to this bridge.");
return; return;
} }
foreach (var room in PropertiesConfig.Rooms) 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; continue;
} }
@@ -170,8 +162,11 @@ namespace PepperDash.Essentials.Core.Bridges
/// <summary> /// <summary>
/// Adds a join map /// Adds a join map
/// </summary> /// </summary>
/// <param name="deviceKey">The key of the device to add the join map for</param> /// <param name="deviceKey"></param>
/// <param name="joinMap">The join map to add</param> /// <param name="joinMap"></param>
/// <summary>
/// AddJoinMap method
/// </summary>
public void AddJoinMap(string deviceKey, JoinMapBaseAdvanced joinMap) public void AddJoinMap(string deviceKey, JoinMapBaseAdvanced joinMap)
{ {
if (!JoinMaps.ContainsKey(deviceKey)) if (!JoinMaps.ContainsKey(deviceKey))
@@ -180,13 +175,14 @@ namespace PepperDash.Essentials.Core.Bridges
} }
else 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> /// <summary>
/// PrintJoinMaps method /// PrintJoinMaps method
/// </summary> /// </summary>
/// <inheritdoc />
public virtual void PrintJoinMaps() public virtual void PrintJoinMaps()
{ {
CrestronConsole.ConsoleCommandResponse("Join Maps for EISC IPID: {0}\r\n", Eisc.ID.ToString("X")); CrestronConsole.ConsoleCommandResponse("Join Maps for EISC IPID: {0}\r\n", Eisc.ID.ToString("X"));
@@ -197,17 +193,17 @@ namespace PepperDash.Essentials.Core.Bridges
joinMap.Value.PrintJoinMapInfo(); joinMap.Value.PrintJoinMapInfo();
} }
} }
/// <summary> /// <summary>
/// MarkdownForBridge method /// MarkdownForBridge method
/// </summary> /// </summary>
/// <inheritdoc />
public virtual void MarkdownForBridge(string bridgeKey) 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) 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); joinMap.Value.MarkdownJoinMapInfo(joinMap.Key, bridgeKey);
} }
} }
@@ -215,45 +211,53 @@ namespace PepperDash.Essentials.Core.Bridges
/// <summary> /// <summary>
/// Prints the join map for a device by key /// Prints the join map for a device by key
/// </summary> /// </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) public void PrintJoinMapForDevice(string deviceKey)
{ {
var joinMap = JoinMaps[deviceKey]; var joinMap = JoinMaps[deviceKey];
if (joinMap == null) 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; 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(); joinMap.PrintJoinMapInfo();
} }
/// <summary> /// <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> /// </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) public void MarkdownJoinMapForDevice(string deviceKey, string bridgeKey)
{ {
var joinMap = JoinMaps[deviceKey]; var joinMap = JoinMaps[deviceKey];
if (joinMap == null) 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; 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); joinMap.MarkdownJoinMapInfo(deviceKey, bridgeKey);
} }
/// <summary> /// <summary>
/// Used for debugging to trigger an action based on a join number and type /// Used for debugging to trigger an action based on a join number and type
/// </summary> /// </summary>
/// <param name="join">The join number to execute the action for</param> /// <param name="join"></param>
/// <param name="type">The type of join (digital, analog, serial)</param> /// <param name="type"></param>
/// <param name="state">The state to pass to the action</param> /// <param name="state"></param>
/// <summary>
/// ExecuteJoinAction method
/// </summary>
public void ExecuteJoinAction(uint join, string type, object state) public void ExecuteJoinAction(uint join, string type, object state)
{ {
try try
@@ -262,87 +266,78 @@ namespace PepperDash.Essentials.Core.Bridges
{ {
case "digital": 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"); Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
userObject(Convert.ToBoolean(state)); uo(Convert.ToBoolean(state));
} }
else else
this.LogVerbose("User Object is null. Nothing to Execute"); Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute");
break; break;
} }
case "analog": 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"); Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
userObject(Convert.ToUInt16(state)); uo(Convert.ToUInt16(state));
} }
else else
this.LogVerbose("User Object is null. Nothing to Execute"); Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute"); break;
break;
} }
case "serial": 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"); Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
userObject(Convert.ToString(state)); uo(Convert.ToString(state));
} }
else else
this.LogVerbose("User Object is null. Nothing to Execute"); Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute");
break; break;
} }
default: default:
{ {
this.LogVerbose("Unknown join type. Use digital/serial/analog"); Debug.LogMessage(LogEventLevel.Verbose, "Unknown join type. Use digital/serial/analog");
break; break;
} }
} }
} }
catch (Exception e) catch (Exception e)
{ {
this.LogError("ExecuteJoinAction error: {message}", e.Message); Debug.LogMessage(LogEventLevel.Debug, this, "Error: {0}", e);
this.LogDebug(e, "Stack Trace: ");
} }
} }
/// <summary> /// <summary>
/// Handle incoming sig changes /// Handles incoming sig changes
/// </summary> /// </summary>
/// <param name="currentDevice">BasicTriList device that triggered the event</param> /// <param name="currentDevice"></param>
/// <param name="args">Event arguments containing the signal information</param> /// <param name="args"></param>
protected void Eisc_SigChange(object currentDevice, SigEventArgs args) protected void Eisc_SigChange(object currentDevice, SigEventArgs args)
{ {
try try
{ {
this.LogVerbose("EiscApiAdvanced change: {type} {number}={value}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); Debug.LogMessage(LogEventLevel.Verbose, this, "EiscApiAdvanced change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
var userObject = args.Sig.UserObject; var uo = args.Sig.UserObject;
if (userObject == null) return; if (uo == null) return;
Debug.LogMessage(LogEventLevel.Debug, this, "Executing Action: {0}", uo.ToString());
if (userObject is Action<bool>) if (uo is Action<bool>)
{ (uo as Action<bool>)(args.Sig.BoolValue);
this.LogDebug("Executing Boolean Action"); else if (uo is Action<ushort>)
(userObject as Action<bool>)(args.Sig.BoolValue); (uo as Action<ushort>)(args.Sig.UShortValue);
} else if (uo is Action<string>)
else if (userObject is Action<ushort>) (uo as Action<string>)(args.Sig.StringValue);
{
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);
}
} }
catch (Exception e) catch (Exception e)
{ {
this.LogError("Eisc_SigChange handler error: {message}", e.Message); Debug.LogMessage(LogEventLevel.Verbose, this, "Error in Eisc_SigChange handler: {0}", e);
this.LogDebug(e, "Stack Trace: ");
} }
} }
@@ -361,22 +356,22 @@ namespace PepperDash.Essentials.Core.Bridges
/// </summary> /// </summary>
public class EiscApiPropertiesConfig public class EiscApiPropertiesConfig
{ {
[JsonProperty("control")]
/// <summary> /// <summary>
/// Gets or sets the Control /// Gets or sets the Control
/// </summary> /// </summary>
[JsonProperty("control")]
public EssentialsControlPropertiesConfig Control { get; set; } public EssentialsControlPropertiesConfig Control { get; set; }
[JsonProperty("devices")]
/// <summary> /// <summary>
/// Gets or sets the Devices /// Gets or sets the Devices
/// </summary> /// </summary>
[JsonProperty("devices")]
public List<ApiDevicePropertiesConfig> Devices { get; set; } public List<ApiDevicePropertiesConfig> Devices { get; set; }
[JsonProperty("rooms")]
/// <summary> /// <summary>
/// Gets or sets the Rooms /// Gets or sets the Rooms
/// </summary> /// </summary>
[JsonProperty("rooms")]
public List<ApiRoomPropertiesConfig> Rooms { get; set; } public List<ApiRoomPropertiesConfig> Rooms { get; set; }
@@ -385,22 +380,22 @@ namespace PepperDash.Essentials.Core.Bridges
/// </summary> /// </summary>
public class ApiDevicePropertiesConfig public class ApiDevicePropertiesConfig
{ {
[JsonProperty("deviceKey")]
/// <summary> /// <summary>
/// Gets or sets the DeviceKey /// Gets or sets the DeviceKey
/// </summary> /// </summary>
[JsonProperty("deviceKey")]
public string DeviceKey { get; set; } public string DeviceKey { get; set; }
[JsonProperty("joinStart")]
/// <summary> /// <summary>
/// Gets or sets the JoinStart /// Gets or sets the JoinStart
/// </summary> /// </summary>
[JsonProperty("joinStart")]
public uint JoinStart { get; set; } public uint JoinStart { get; set; }
[JsonProperty("joinMapKey")]
/// <summary> /// <summary>
/// Gets or sets the JoinMapKey /// Gets or sets the JoinMapKey
/// </summary> /// </summary>
[JsonProperty("joinMapKey")]
public string JoinMapKey { get; set; } public string JoinMapKey { get; set; }
} }
@@ -409,55 +404,44 @@ namespace PepperDash.Essentials.Core.Bridges
/// </summary> /// </summary>
public class ApiRoomPropertiesConfig public class ApiRoomPropertiesConfig
{ {
[JsonProperty("roomKey")]
/// <summary> /// <summary>
/// Gets or sets the RoomKey /// Gets or sets the RoomKey
/// </summary> /// </summary>
[JsonProperty("roomKey")]
public string RoomKey { get; set; } public string RoomKey { get; set; }
[JsonProperty("joinStart")]
/// <summary> /// <summary>
/// Gets or sets the JoinStart /// Gets or sets the JoinStart
/// </summary> /// </summary>
[JsonProperty("joinStart")]
public uint JoinStart { get; set; } public uint JoinStart { get; set; }
[JsonProperty("joinMapKey")]
/// <summary> /// <summary>
/// Gets or sets the JoinMapKey /// Gets or sets the JoinMapKey
/// </summary> /// </summary>
[JsonProperty("joinMapKey")]
public string JoinMapKey { get; set; } public string JoinMapKey { get; set; }
} }
} }
/// <summary> /// <summary>
/// Factory class for EiscApiAdvanced devices /// Represents a EiscApiAdvancedFactory
/// </summary> /// </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> public class EiscApiAdvancedFactory : EssentialsDeviceFactory<EiscApiAdvanced>
{ {
/// <summary>
/// Constructor
/// </summary>
public EiscApiAdvancedFactory() 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 /> /// <inheritdoc />
public override EssentialsDevice BuildDevice(DeviceConfig dc) 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); var controlProperties = CommFactory.GetControlPropertiesConfig(dc);
@@ -465,13 +449,6 @@ namespace PepperDash.Essentials.Core.Bridges
switch (dc.Type.ToLower()) switch (dc.Type.ToLower())
{ {
case "eiscapiadvudp":
case "eiscapiadvancedudp":
{
eisc = new EthernetIntersystemCommunications(controlProperties.IpIdInt,
controlProperties.TcpSshProperties.Address, Global.ControlSystem);
break;
}
case "eiscapiadv": case "eiscapiadv":
case "eiscapiadvanced": case "eiscapiadvanced":
{ {
@@ -494,7 +471,7 @@ namespace PepperDash.Essentials.Core.Bridges
{ {
if (string.IsNullOrEmpty(controlProperties.RoomId)) 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; eisc = null;
break; break;
} }

View File

@@ -1,4 +1,4 @@
using PepperDash.Core; using PepperDash.Essentials.Core.Devices;
using Serilog.Events; using Serilog.Events;
//using PepperDash.Essentials.Devices.Common.Cameras; //using PepperDash.Essentials.Devices.Common.Cameras;

View File

@@ -7,13 +7,6 @@ namespace PepperDash.Essentials.Core.Bridges
/// </summary> /// </summary>
public interface IBridgeAdvanced public interface IBridgeAdvanced
{ {
/// <summary>
/// Links the bridge to the API using the provided trilist, join start, join map key, and bridge.
/// </summary>
/// <param name="trilist">The trilist to link to.</param>
/// <param name="joinStart">The starting join number.</param>
/// <param name="joinMapKey">The key for the join map.</param>
/// <param name="bridge">The EISC API bridge.</param>
void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge);
} }
} }

View File

@@ -1,6 +1,7 @@
using PepperDash.Essentials.Core.JoinMaps;
using System; using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a AirMediaControllerJoinMap /// Represents a AirMediaControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a AppleTvJoinMap /// Represents a AppleTvJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a C2nRthsControllerJoinMap /// Represents a C2nRthsControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a CameraControllerJoinMap /// Represents a CameraControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a CenOdtOccupancySensorBaseJoinMap /// Represents a CenOdtOccupancySensorBaseJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a DisplayControllerJoinMap /// Represents a DisplayControllerJoinMap

View File

@@ -1,6 +1,8 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges { namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{
/// <summary> /// <summary>
/// Represents a DmBladeChassisControllerJoinMap /// Represents a DmBladeChassisControllerJoinMap
/// </summary> /// </summary>

View File

@@ -1,6 +1,7 @@
using PepperDash.Essentials.Core.JoinMaps;
using System; using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a DmChassisControllerJoinMap /// Represents a DmChassisControllerJoinMap

View File

@@ -1,6 +1,7 @@
using PepperDash.Essentials.Core.JoinMaps;
using System; using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a DmRmcControllerJoinMap /// Represents a DmRmcControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a DmTxControllerJoinMap /// Represents a DmTxControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a DmpsAudioOutputControllerJoinMap /// Represents a DmpsAudioOutputControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a DmpsMicrophoneControllerJoinMap /// Represents a DmpsMicrophoneControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a DmpsRoutingControllerJoinMap /// Represents a DmpsRoutingControllerJoinMap

View File

@@ -1,4 +1,4 @@
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.JoinMaps;
namespace PepperDash.Essentials.Core.Bridges.JoinMaps namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {

View File

@@ -1,7 +1,8 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a GenericLightingJoinMap /// Represents a GenericLightingJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a GenericRelayControllerJoinMap /// Represents a GenericRelayControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a GlsOccupancySensorBaseJoinMap /// Represents a GlsOccupancySensorBaseJoinMap

View File

@@ -1,5 +1,5 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using PepperDash.Essentials.Core; using System;
namespace PepperDash.Essentials.Core.Bridges.JoinMaps namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a HdMdNxM4kEControllerJoinMap /// Represents a HdMdNxM4kEControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a HdMdxxxCEControllerJoinMap /// Represents a HdMdxxxCEControllerJoinMap

View File

@@ -1,7 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using PepperDash.Essentials.Core; using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a HdPsXxxControllerJoinMap /// Represents a HdPsXxxControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a Hrxxx0WirelessRemoteControllerJoinMap /// Represents a Hrxxx0WirelessRemoteControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a IAnalogInputJoinMap /// Represents a IAnalogInputJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a IBasicCommunicationJoinMap /// Represents a IBasicCommunicationJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a IDigitalInputJoinMap /// Represents a IDigitalInputJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a IDigitalOutputJoinMap /// Represents a IDigitalOutputJoinMap

View File

@@ -1,10 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System.Collections.Generic; using System;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Join map for IRBlurayBase devices /// Join map for IRBlurayBase devices

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a PduJoinMapBase /// Represents a PduJoinMapBase

View File

@@ -1,7 +1,8 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a SetTopBoxControllerJoinMap /// Represents a SetTopBoxControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a StatusSignControllerJoinMap /// Represents a StatusSignControllerJoinMap

View File

@@ -1,6 +1,7 @@
using System; using PepperDash.Essentials.Core.JoinMaps;
using System;
namespace PepperDash.Essentials.Core.Bridges namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>
/// Represents a SystemMonitorJoinMap /// Represents a SystemMonitorJoinMap

View File

@@ -1,5 +1,5 @@
using PepperDash.Essentials.Core.JoinMaps;
using System; using System;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Core.Bridges.JoinMaps namespace PepperDash.Essentials.Core.Bridges.JoinMaps
{ {
/// <summary> /// <summary>

View File

@@ -0,0 +1,72 @@
using System;
using Crestron.SimplSharpPro;
namespace PepperDash.Essentials.Core.Bridges
{
/// <summary>
/// Helper class for various Sig events
/// </summary>
public class SigHelper
{
/// <summary>
/// Runs action when Sig is pressed
/// </summary>
/// <param name="sig"></param>
public static void Pressed(Sig sig, Action act) { if (sig.BoolValue) act(); }
/// <summary>
/// Runs action when Sig is released
/// </summary>
public static void Released(Sig sig, Action act) { if (!sig.BoolValue) act(); }
/// <summary>
/// SetBoolOutAction method
/// </summary>
public static void SetBoolOutAction(BoolOutputSig sig, Action<bool> a)
{
if (sig != null)
sig.UserObject = a;
}
/// <summary>
/// Safely clears action of non-null sig.
/// </summary>
public static void ClearBoolOutAction(BoolOutputSig sig)
{
if (sig != null)
sig.UserObject = null;
}
/// <summary>
/// Does a timed ramp, where the time is scaled proportional to the
/// remaining range to cover
/// </summary>
/// <param name="sig">Ushort sig to scale</param>
/// <param name="newLevel">Level to go to</param>
/// <param name="time">In ms (not hundredths like Crestron Sig ramp function)</param>
/// <summary>
/// RampTimeScaled method
/// </summary>
public static void RampTimeScaled(Sig sig, ushort newLevel, uint time)
{
var level = sig.UShortValue;
var diff = Math.Abs(level - newLevel);
var scaledTime = (uint)(diff * time / 65535);
Ramp(sig, newLevel, scaledTime);
}
/// <summary>
/// Ramps signal
/// </summary>
/// <param name="sig"></param>
/// <param name="level"></param>
/// <param name="time">In ms (not hundredths like Crestron Sig ramp function)</param>
/// <summary>
/// Ramp method
/// </summary>
public static void Ramp(Sig sig, ushort level, uint time)
{
sig.CreateRamp(level, time / 10);
}
}
}

View File

@@ -1,228 +0,0 @@
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;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a ComPortController
/// </summary>
public class ComPortController : Device, IBasicCommunicationWithStreamDebugging
{
/// <summary>
/// Gets or sets the StreamDebugging
/// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; }
/// <summary>
/// Event fired when bytes are received
/// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Event fired when text is received
/// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary>
/// Gets or sets the IsConnected
/// </summary>
public bool IsConnected { get { return true; } }
ComPort Port;
ComPort.ComPortSpec Spec;
/// <summary>
/// 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)
{
StreamDebugging = new CommunicationStreamDebugging(key);
Spec = spec;
AddPostActivationAction(() =>
{
Port = postActivationFunc(config);
RegisterAndConfigureComPort();
});
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="key">Device key</param>
/// <param name="port">COM port instance</param>
/// <param name="spec">COM port specification</param>
public ComPortController(string key, ComPort port, ComPort.ComPortSpec spec)
: base(key)
{
if (port == null)
{
Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Invalid com port, continuing but comms will not function");
return;
}
Port = port;
Spec = spec;
//IsConnected = new BoolFeedback(CommonBoolCue.IsConnected, () => true);
RegisterAndConfigureComPort();
}
private void RegisterAndConfigureComPort()
{
if (Port == null)
{
this.LogInformation($"Configured {Port.Parent.GetType().Name}-comport-{Port.ID} for {Key} does not exist.");
return;
}
if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102)
{
var result = Port.Register();
if (result != eDeviceRegistrationUnRegistrationResponse.Success)
{
this.LogError($"Cannot register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
return;
}
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})");
return;
}
this.LogInformation($"Successfully set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
Port.SerialDataReceived += Port_SerialDataReceived;
}
/// <summary>
/// Destructor
/// </summary>
~ComPortController()
{
Port.SerialDataReceived -= Port_SerialDataReceived;
}
void Port_SerialDataReceived(ComPort ReceivingComPort, ComPortSerialDataEventArgs args)
{
OnDataReceived(args.SerialData);
}
void OnDataReceived(string s)
{
var eventSubscribed = false;
var bytesHandler = BytesReceived;
if (bytesHandler != null)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(s);
this.PrintReceivedBytes(bytes);
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
eventSubscribed = true;
}
var textHandler = TextReceived;
if (textHandler != null)
{
this.PrintReceivedText(s);
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
eventSubscribed = true;
}
if (!eventSubscribed) Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered");
}
/// <summary>
/// Deactivate method
/// </summary>
/// <inheritdoc />
public override bool Deactivate()
{
return Port.UnRegister() == eDeviceRegistrationUnRegistrationResponse.Success;
}
#region IBasicCommunication Members
/// <summary>
/// SendText method
/// </summary>
public void SendText(string text)
{
if (Port == null)
return;
this.PrintSentText(text);
Port.Send(text);
}
/// <summary>
/// SendBytes method
/// </summary>
public void SendBytes(byte[] bytes)
{
if (Port == null)
return;
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
this.PrintSentBytes(bytes);
Port.Send(text);
}
/// <summary>
/// Connect method
/// </summary>
public void Connect()
{
}
/// <summary>
/// Disconnect method
/// </summary>
public void Disconnect()
{
}
#endregion
/// <summary>
///
/// </summary>
/// <param name="s"></param>
/// <summary>
/// SimulateReceive method
/// </summary>
public void SimulateReceive(string s)
{
// split out hex chars and build string
var split = Regex.Split(s, @"(\\[Xx][0-9a-fA-F][0-9a-fA-F])");
StringBuilder b = new StringBuilder();
foreach (var t in split)
{
if (t.StartsWith(@"\") && t.Length == 4)
b.Append((char)(Convert.ToByte(t.Substring(2, 2), 16)));
else
b.Append(t);
}
OnDataReceived(b.ToString());
}
}
}

View File

@@ -1,138 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using Crestron.SimplSharp.CrestronSockets;
using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Devices;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Implements IBasicCommunication and sends all communication through an EISC
/// </summary>
[Description("Generic communication wrapper class for any IBasicCommunication type")]
public class CommBridge : EssentialsBridgeableDevice, IBasicCommunication
{
private EiscApiAdvanced eisc;
private IBasicCommunicationJoinMap joinMap;
/// <summary>
/// Event triggered when text is received through the communication bridge.
/// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary>
/// Event triggered when bytes are received through the communication bridge.
/// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Indicates whether the communication bridge is currently connected.
/// </summary>
public bool IsConnected { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="CommBridge"/> class.
/// </summary>
/// <param name="key">The unique key for the communication bridge.</param>
/// <param name="name">The display name for the communication bridge.</param>
public CommBridge(string key, string name)
: base(key, name)
{
}
/// <summary>
/// Sends a byte array through the communication bridge.
/// </summary>
/// <param name="bytes">The byte array to send.</param>
public void SendBytes(byte[] bytes)
{
if (eisc == null)
{
this.LogWarning("EISC is null, cannot send bytes.");
return;
}
eisc.Eisc.SetString(joinMap.SendText.JoinNumber, Encoding.ASCII.GetString(bytes, 0, bytes.Length));
}
/// <summary>
/// Sends a text string through the communication bridge.
/// </summary>
/// <param name="text">The text string to send.</param>
public void SendText(string text)
{
if (eisc == null)
{
this.LogWarning("EISC is null, cannot send text.");
return;
}
eisc.Eisc.SetString(joinMap.SendText.JoinNumber, text);
}
/// <summary>
/// Initiates a connection through the communication bridge.
/// </summary>
public void Connect()
{
if (eisc == null)
{
this.LogWarning("EISC is null, cannot connect.");
return;
}
eisc.Eisc.SetBool(joinMap.Connect.JoinNumber, true);
}
/// <summary>
/// Terminates the connection through the communication bridge.
/// </summary>
public void Disconnect()
{
if (eisc == null)
{
this.LogWarning("EISC is null, cannot disconnect.");
return;
}
eisc.Eisc.SetBool(joinMap.Connect.JoinNumber, false);
}
/// <inheritdoc />
public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
{
joinMap = new IBasicCommunicationJoinMap(joinStart);
var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey);
if (!string.IsNullOrEmpty(joinMapSerialized))
joinMap = JsonConvert.DeserializeObject<IBasicCommunicationJoinMap>(joinMapSerialized);
if (bridge != null)
{
bridge.AddJoinMap(Key, joinMap);
}
else
{
this.LogWarning("Please update config to use 'eiscapiadvanced' to get all join map features for this device.");
}
this.LogDebug("Linking to Trilist '{0}'", trilist.ID.ToString("X"));
eisc = bridge;
trilist.SetBoolSigAction(joinMap.Connected.JoinNumber, (b) => IsConnected = b);
trilist.SetStringSigAction(joinMap.TextReceived.JoinNumber, (s) =>
{
TextReceived?.Invoke(this, new GenericCommMethodReceiveTextArgs(s));
BytesReceived?.Invoke(this, new GenericCommMethodReceiveBytesArgs(Encoding.ASCII.GetBytes(s)));
});
}
}
}

View File

@@ -1,297 +0,0 @@
using System;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DM;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
using Serilog.Events;
namespace PepperDash.Essentials.Core
{
/// <summary>
///
/// </summary>
public class CommFactory
{
public static EssentialsControlPropertiesConfig GetControlPropertiesConfig(DeviceConfig deviceConfig)
{
try
{
return JsonConvert.DeserializeObject<EssentialsControlPropertiesConfig>
(deviceConfig.Properties["control"].ToString());
//Debug.LogMessage(LogEventLevel.Verbose, "Control TEST: {0}", JsonConvert.SerializeObject(controlConfig));
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, "ERROR: [{0}] Control properties deserialize failed:\r{1}", deviceConfig.Key, e);
return null;
}
}
/// <summary>
/// Returns a comm method of either com port, TCP, SSH, and puts this into the DeviceManager
/// </summary>
/// <param name="deviceConfig">The Device config object</param>
/// <summary>
/// CreateCommForDevice method
/// </summary>
public static IBasicCommunication CreateCommForDevice(DeviceConfig deviceConfig)
{
EssentialsControlPropertiesConfig controlConfig = GetControlPropertiesConfig(deviceConfig);
if (controlConfig == null)
return null;
IBasicCommunication comm = null;
try
{
var c = controlConfig.TcpSshProperties;
switch (controlConfig.Method)
{
case eControlMethod.Com:
comm = new ComPortController(deviceConfig.Key + "-com", GetComPort, controlConfig.ComParams.Value, controlConfig);
break;
case eControlMethod.ComBridge:
comm = new CommBridge(deviceConfig.Key + "-simpl", deviceConfig.Name + " Simpl");
break;
case eControlMethod.Cec:
comm = new CecPortController(deviceConfig.Key + "-cec", GetCecPort, controlConfig);
break;
case eControlMethod.IR:
break;
case eControlMethod.Ssh:
{
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;
break;
}
case eControlMethod.Tcpip:
{
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;
break;
}
case eControlMethod.Udp:
{
var udp = new GenericUdpServer(deviceConfig.Key + "-udp", c.Address, c.Port, c.BufferSize);
comm = udp;
break;
}
case eControlMethod.Telnet:
break;
case eControlMethod.SecureTcpIp:
{
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;
break;
}
default:
break;
}
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, "Cannot create communication from JSON:\r{0}\r\rException:\r{1}",
deviceConfig.Properties.ToString(), e);
}
// put it in the device manager if it's the right flavor
if (comm is Device comDev)
DeviceManager.AddDevice(comDev);
return comm;
}
/// <summary>
/// GetComPort method
/// </summary>
public static ComPort GetComPort(EssentialsControlPropertiesConfig config)
{
var comPar = config.ComParams;
var dev = GetIComPortsDeviceFromManagedDevice(config.ControlPortDevKey);
if (dev != null && config.ControlPortNumber <= dev.NumberOfComPorts)
return dev.ComPorts[config.ControlPortNumber.Value];
Debug.LogMessage(LogEventLevel.Information, "GetComPort: Device '{0}' does not have com port {1}", config.ControlPortDevKey, config.ControlPortNumber);
return null;
}
/// <summary>
/// Gets an ICec port from a RoutingInput or RoutingOutput on a device
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
/// <summary>
/// GetCecPort method
/// </summary>
public static ICec GetCecPort(ControlPropertiesConfig config)
{
try
{
var dev = DeviceManager.GetDeviceForKey(config.ControlPortDevKey);
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: device '{0}' {1}", config.ControlPortDevKey, dev == null
? "is not valid, failed to get cec port"
: "found in device manager, attempting to get cec port");
if (dev == null)
return null;
if (String.IsNullOrEmpty(config.ControlPortName))
{
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: '{0}' - Configuration missing 'ControlPortName'", config.ControlPortDevKey);
return null;
}
var inputsOutputs = dev as IRoutingInputsOutputs;
if (inputsOutputs == null)
{
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not support IRoutingInputsOutputs, failed to get CEC port called '{1}'",
config.ControlPortDevKey, config.ControlPortName);
return null;
}
var inputPort = inputsOutputs.InputPorts[config.ControlPortName];
if (inputPort != null && inputPort.Port is ICec)
return inputPort.Port as ICec;
var outputPort = inputsOutputs.OutputPorts[config.ControlPortName];
if (outputPort != null && outputPort.Port is ICec)
return outputPort.Port as ICec;
}
catch (Exception ex)
{
Debug.LogMessage(LogEventLevel.Debug, "GetCecPort Exception Message: {0}", ex.Message);
Debug.LogMessage(LogEventLevel.Verbose, "GetCecPort Exception StackTrace: {0}", ex.StackTrace);
if (ex.InnerException != null)
Debug.LogMessage(LogEventLevel.Information, "GetCecPort Exception InnerException: {0}", ex.InnerException);
}
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not have a CEC port called '{1}'",
config.ControlPortDevKey, config.ControlPortName);
return null;
}
/// <summary>
/// Helper to grab the IComPorts device for this PortDeviceKey. Key "controlSystem" will
/// return the ControlSystem object from the Global class.
/// </summary>
/// <returns>IComPorts device or null if the device is not found or does not implement IComPorts</returns>
/// <summary>
/// GetIComPortsDeviceFromManagedDevice method
/// </summary>
public static IComPorts GetIComPortsDeviceFromManagedDevice(string ComPortDevKey)
{
if ((ComPortDevKey.Equals("controlSystem", System.StringComparison.OrdinalIgnoreCase)
|| ComPortDevKey.Equals("processor", System.StringComparison.OrdinalIgnoreCase))
&& Global.ControlSystem is IComPorts)
return Global.ControlSystem;
else
{
var dev = DeviceManager.GetDeviceForKey(ComPortDevKey) as IComPorts;
if (dev == null)
Debug.LogMessage(LogEventLevel.Information, "ComPortConfig: Cannot find com port device '{0}'", ComPortDevKey);
return dev;
}
}
}
/// <summary>
/// Represents a EssentialsControlPropertiesConfig
/// </summary>
public class EssentialsControlPropertiesConfig :
ControlPropertiesConfig
{
[JsonProperty("comParams", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(ComSpecJsonConverter))]
public ComPort.ComPortSpec? ComParams { get; set; }
[JsonProperty("cresnetId", NullValueHandling = NullValueHandling.Ignore)]
public string CresnetId { get; set; }
/// <summary>
/// Attempts to provide uint conversion of string CresnetId
/// </summary>
[JsonIgnore]
public uint CresnetIdInt
{
get
{
try
{
return Convert.ToUInt32(CresnetId, 16);
}
catch (Exception)
{
throw new FormatException(string.Format("ERROR:Unable to convert Cresnet ID: {0} to hex. Error:\n{1}", CresnetId));
}
}
}
[JsonProperty("infinetId", NullValueHandling = NullValueHandling.Ignore)]
/// <summary>
/// Gets or sets the InfinetId
/// </summary>
public string InfinetId { get; set; }
/// <summary>
/// Attepmts to provide uiont conversion of string InifinetId
/// </summary>
[JsonIgnore]
public uint InfinetIdInt
{
get
{
try
{
return Convert.ToUInt32(InfinetId, 16);
}
catch (Exception)
{
throw new FormatException(string.Format("ERROR:Unable to conver Infinet ID: {0} to hex. Error:\n{1}", InfinetId));
}
}
}
}
/// <summary>
/// Represents a IrControlSpec
/// </summary>
public class IrControlSpec
{
/// <summary>
/// Gets or sets the PortDeviceKey
/// </summary>
public string PortDeviceKey { get; set; }
/// <summary>
/// Gets or sets the PortNumber
/// </summary>
public uint PortNumber { get; set; }
/// <summary>
/// Gets or sets the File
/// </summary>
public string File { get; set; }
}
}

View File

@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
///
/// </summary>
public interface IComPortsDevice
{
IComPorts Device { get; }
}
}

View File

@@ -1,26 +1,20 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DM; using Crestron.SimplSharpPro.DM;
using PepperDash.Core;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core.Communications
{ {
/// <summary> /// <summary>
/// Represents a CecPortController /// Represents a CecPortController
/// </summary> /// </summary>
public class CecPortController : Device, IBasicCommunicationWithStreamDebugging public class CecPortController : Device, IBasicCommunicationWithStreamDebugging
{ {
/// <summary> /// <summary>
/// Gets or sets the StreamDebugging /// Gets or sets the StreamDebugging
/// </summary> /// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; } public CommunicationStreamDebugging StreamDebugging { get; private set; }
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived; public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived; public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
@@ -33,16 +27,16 @@ namespace PepperDash.Essentials.Core
ICec Port; ICec Port;
public CecPortController(string key, Func<EssentialsControlPropertiesConfig, ICec> postActivationFunc, 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(() => AddPostActivationAction(() =>
{ {
Port = postActivationFunc(config); Port = postActivationFunc(config);
Port.StreamCec.CecChange += StreamCec_CecChange; Port.StreamCec.CecChange += StreamCec_CecChange;
}); });
} }
public CecPortController(string key, ICec port) public CecPortController(string key, ICec port)
@@ -58,25 +52,27 @@ namespace PepperDash.Essentials.Core
if (args.EventId == CecEventIds.CecMessageReceivedEventId) if (args.EventId == CecEventIds.CecMessageReceivedEventId)
OnDataReceived(cecDevice.Received.StringValue); OnDataReceived(cecDevice.Received.StringValue);
else if (args.EventId == CecEventIds.ErrorFeedbackEventId) else if (args.EventId == CecEventIds.ErrorFeedbackEventId)
if (cecDevice.ErrorFeedback.BoolValue) if(cecDevice.ErrorFeedback.BoolValue)
Debug.LogMessage(LogEventLevel.Verbose, this, "CEC NAK Error"); Debug.LogMessage(LogEventLevel.Verbose, this, "CEC NAK Error");
} }
void OnDataReceived(string s) void OnDataReceived(string s)
{ {
var bytesHandler = BytesReceived; var bytesHandler = BytesReceived;
if (bytesHandler != null) if (bytesHandler != null)
{ {
var bytes = Encoding.GetEncoding(28591).GetBytes(s); 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)); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
} }
var textHandler = TextReceived; var textHandler = TextReceived;
if (textHandler != null) if (textHandler != null)
{ {
this.PrintReceivedText(s); if (StreamDebugging.RxStreamDebuggingIsEnabled)
textHandler(this, new GenericCommMethodReceiveTextArgs(s)); Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
} textHandler(this, new GenericCommMethodReceiveTextArgs(s));
}
} }
#region IBasicCommunication Members #region IBasicCommunication Members
@@ -88,7 +84,8 @@ namespace PepperDash.Essentials.Core
{ {
if (Port == null) if (Port == null)
return; 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; Port.StreamCec.Send.StringValue = text;
} }
@@ -100,8 +97,8 @@ namespace PepperDash.Essentials.Core
if (Port == null) if (Port == null)
return; return;
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); 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)); Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
Port.StreamCec.Send.StringValue = text; Port.StreamCec.Send.StringValue = text;
} }
@@ -136,7 +133,7 @@ namespace PepperDash.Essentials.Core
foreach (var t in split) foreach (var t in split)
{ {
if (t.StartsWith(@"\") && t.Length == 4) if (t.StartsWith(@"\") && t.Length == 4)
b.Append((char)(Convert.ToByte(t.Substring(2, 2), 16))); b.Append((char)Convert.ToByte(t.Substring(2, 2), 16));
else else
b.Append(t); b.Append(t);
} }

View File

@@ -0,0 +1,200 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharpPro;
using Serilog.Events;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Represents a ComPortController
/// </summary>
public class ComPortController : Device, IBasicCommunicationWithStreamDebugging
{
/// <summary>
/// Gets or sets the StreamDebugging
/// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; }
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary>
/// Gets or sets the IsConnected
/// </summary>
public bool IsConnected { get { return true; } }
ComPort Port;
ComPort.ComPortSpec Spec;
public ComPortController(string key, Func<EssentialsControlPropertiesConfig, ComPort> postActivationFunc,
ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key)
{
StreamDebugging = new CommunicationStreamDebugging(key);
Spec = spec;
AddPostActivationAction(() =>
{
Port = postActivationFunc(config);
RegisterAndConfigureComPort();
});
}
public ComPortController(string key, ComPort port, ComPort.ComPortSpec spec)
: base(key)
{
if (port == null)
{
Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Invalid com port, continuing but comms will not function");
return;
}
Port = port;
Spec = spec;
//IsConnected = new BoolFeedback(CommonBoolCue.IsConnected, () => true);
RegisterAndConfigureComPort();
}
private void RegisterAndConfigureComPort()
{
if (Port == null)
{
Debug.LogMessage(LogEventLevel.Information, this, "Configured com Port for this device does not exist.");
return;
}
if (Port.Parent is CrestronControlSystem)
{
var result = Port.Register();
if (result != eDeviceRegistrationUnRegistrationResponse.Success)
{
Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Com port: {0}", result);
return; // false
}
}
var specResult = Port.SetComPortSpec(Spec);
if (specResult != 0)
{
Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Cannot set comspec");
return;
}
Port.SerialDataReceived += Port_SerialDataReceived;
}
~ComPortController()
{
Port.SerialDataReceived -= Port_SerialDataReceived;
}
void Port_SerialDataReceived(ComPort ReceivingComPort, ComPortSerialDataEventArgs args)
{
OnDataReceived(args.SerialData);
}
void OnDataReceived(string s)
{
var eventSubscribed = false;
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));
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
eventSubscribed = true;
}
var textHandler = TextReceived;
if (textHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
eventSubscribed = true;
}
if(!eventSubscribed) Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered");
}
/// <summary>
/// Deactivate method
/// </summary>
/// <inheritdoc />
public override bool Deactivate()
{
return Port.UnRegister() == eDeviceRegistrationUnRegistrationResponse.Success;
}
#region IBasicCommunication Members
/// <summary>
/// SendText method
/// </summary>
public void SendText(string text)
{
if (Port == null)
return;
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text);
Port.Send(text);
}
/// <summary>
/// SendBytes method
/// </summary>
public void SendBytes(byte[] bytes)
{
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));
Port.Send(text);
}
/// <summary>
/// Connect method
/// </summary>
public void Connect()
{
}
/// <summary>
/// Disconnect method
/// </summary>
public void Disconnect()
{
}
#endregion
/// <summary>
///
/// </summary>
/// <param name="s"></param>
/// <summary>
/// SimulateReceive method
/// </summary>
public void SimulateReceive(string s)
{
// split out hex chars and build string
var split = Regex.Split(s, @"(\\[Xx][0-9a-fA-F][0-9a-fA-F])");
var b = new StringBuilder();
foreach (var t in split)
{
if (t.StartsWith(@"\") && t.Length == 4)
b.Append((char)Convert.ToByte(t.Substring(2, 2), 16));
else
b.Append(t);
}
OnDataReceived(b.ToString());
}
}
}

View File

@@ -1,19 +1,12 @@
 
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro; using Crestron.SimplSharpPro;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core.Communications
{ {
/// <summary> /// <summary>
/// This converter creates a proper ComPort.ComPortSpec struct from more-friendly JSON values. It uses /// This converter creates a proper ComPort.ComPortSpec struct from more-friendly JSON values. It uses

View File

@@ -0,0 +1,49 @@
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
///
/// </summary>
public class ComTextHelper
{
/// <summary>
/// Gets escaped text for a byte array
/// </summary>
/// <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

@@ -0,0 +1,289 @@
using System;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DM;
using Newtonsoft.Json;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Devices;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
///
/// </summary>
public class CommFactory
{
public static EssentialsControlPropertiesConfig GetControlPropertiesConfig(DeviceConfig deviceConfig)
{
try
{
return JsonConvert.DeserializeObject<EssentialsControlPropertiesConfig>
(deviceConfig.Properties["control"].ToString());
//Debug.LogMessage(LogEventLevel.Verbose, "Control TEST: {0}", JsonConvert.SerializeObject(controlConfig));
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, "ERROR: [{0}] Control properties deserialize failed:\r{1}", deviceConfig.Key, e);
return null;
}
}
/// <summary>
/// Returns a comm method of either com port, TCP, SSH, and puts this into the DeviceManager
/// </summary>
/// <param name="deviceConfig">The Device config object</param>
/// <summary>
/// CreateCommForDevice method
/// </summary>
public static IBasicCommunication CreateCommForDevice(DeviceConfig deviceConfig)
{
EssentialsControlPropertiesConfig controlConfig = GetControlPropertiesConfig(deviceConfig);
if (controlConfig == null)
return null;
IBasicCommunication comm = null;
try
{
var c = controlConfig.TcpSshProperties;
switch (controlConfig.Method)
{
case eControlMethod.Com:
comm = new ComPortController(deviceConfig.Key + "-com", GetComPort, controlConfig.ComParams.Value, controlConfig);
break;
case eControlMethod.Cec:
comm = new CecPortController(deviceConfig.Key + "-cec", GetCecPort, controlConfig);
break;
case eControlMethod.IR:
break;
case eControlMethod.Ssh:
{
var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password);
ssh.AutoReconnect = c.AutoReconnect;
if(ssh.AutoReconnect)
ssh.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
comm = ssh;
break;
}
case eControlMethod.Tcpip:
{
var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize);
tcp.AutoReconnect = c.AutoReconnect;
if (tcp.AutoReconnect)
tcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
comm = tcp;
break;
}
case eControlMethod.Udp:
{
var udp = new GenericUdpServer(deviceConfig.Key + "-udp", c.Address, c.Port, c.BufferSize);
comm = udp;
break;
}
case eControlMethod.Telnet:
break;
case eControlMethod.SecureTcpIp:
{
var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize);
secureTcp.AutoReconnect = c.AutoReconnect;
if (secureTcp.AutoReconnect)
secureTcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
comm = secureTcp;
break;
}
default:
break;
}
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, "Cannot create communication from JSON:\r{0}\r\rException:\r{1}",
deviceConfig.Properties.ToString(), e);
}
// put it in the device manager if it's the right flavor
var comDev = comm as Device;
if (comDev != null)
DeviceManager.AddDevice(comDev);
return comm;
}
/// <summary>
/// GetComPort method
/// </summary>
public static ComPort GetComPort(EssentialsControlPropertiesConfig config)
{
var comPar = config.ComParams;
var dev = GetIComPortsDeviceFromManagedDevice(config.ControlPortDevKey);
if (dev != null && config.ControlPortNumber <= dev.NumberOfComPorts)
return dev.ComPorts[config.ControlPortNumber.Value];
Debug.LogMessage(LogEventLevel.Information, "GetComPort: Device '{0}' does not have com port {1}", config.ControlPortDevKey, config.ControlPortNumber);
return null;
}
/// <summary>
/// Gets an ICec port from a RoutingInput or RoutingOutput on a device
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
/// <summary>
/// GetCecPort method
/// </summary>
public static ICec GetCecPort(ControlPropertiesConfig config)
{
try
{
var dev = DeviceManager.GetDeviceForKey(config.ControlPortDevKey);
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: device '{0}' {1}", config.ControlPortDevKey, dev == null
? "is not valid, failed to get cec port"
: "found in device manager, attempting to get cec port");
if (dev == null)
return null;
if (string.IsNullOrEmpty(config.ControlPortName))
{
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: '{0}' - Configuration missing 'ControlPortName'", config.ControlPortDevKey);
return null;
}
var inputsOutputs = dev as IRoutingInputsOutputs;
if (inputsOutputs == null)
{
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not support IRoutingInputsOutputs, failed to get CEC port called '{1}'",
config.ControlPortDevKey, config.ControlPortName);
return null;
}
var inputPort = inputsOutputs.InputPorts[config.ControlPortName];
if (inputPort != null && inputPort.Port is ICec)
return inputPort.Port as ICec;
var outputPort = inputsOutputs.OutputPorts[config.ControlPortName];
if (outputPort != null && outputPort.Port is ICec)
return outputPort.Port as ICec;
}
catch (Exception ex)
{
Debug.LogMessage(LogEventLevel.Debug, "GetCecPort Exception Message: {0}", ex.Message);
Debug.LogMessage(LogEventLevel.Verbose, "GetCecPort Exception StackTrace: {0}", ex.StackTrace);
if (ex.InnerException != null)
Debug.LogMessage(LogEventLevel.Information, "GetCecPort Exception InnerException: {0}", ex.InnerException);
}
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not have a CEC port called '{1}'",
config.ControlPortDevKey, config.ControlPortName);
return null;
}
/// <summary>
/// Helper to grab the IComPorts device for this PortDeviceKey. Key "controlSystem" will
/// return the ControlSystem object from the Global class.
/// </summary>
/// <returns>IComPorts device or null if the device is not found or does not implement IComPorts</returns>
/// <summary>
/// GetIComPortsDeviceFromManagedDevice method
/// </summary>
public static IComPorts GetIComPortsDeviceFromManagedDevice(string ComPortDevKey)
{
if ((ComPortDevKey.Equals("controlSystem", StringComparison.OrdinalIgnoreCase)
|| ComPortDevKey.Equals("processor", StringComparison.OrdinalIgnoreCase))
&& Global.ControlSystem is IComPorts)
return Global.ControlSystem;
else
{
var dev = DeviceManager.GetDeviceForKey(ComPortDevKey) as IComPorts;
if (dev == null)
Debug.LogMessage(LogEventLevel.Information, "ComPortConfig: Cannot find com port device '{0}'", ComPortDevKey);
return dev;
}
}
}
/// <summary>
/// Represents a EssentialsControlPropertiesConfig
/// </summary>
public class EssentialsControlPropertiesConfig :
ControlPropertiesConfig
{
[JsonProperty("comParams", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(ComSpecJsonConverter))]
public ComPort.ComPortSpec? ComParams { get; set; }
[JsonProperty("cresnetId", NullValueHandling = NullValueHandling.Ignore)]
public string CresnetId { get; set; }
/// <summary>
/// Attempts to provide uint conversion of string CresnetId
/// </summary>
[JsonIgnore]
public uint CresnetIdInt
{
get
{
try
{
return Convert.ToUInt32(CresnetId, 16);
}
catch (Exception)
{
throw new FormatException(string.Format("ERROR:Unable to convert Cresnet ID: {0} to hex. Error:\n{1}", CresnetId));
}
}
}
[JsonProperty("infinetId", NullValueHandling = NullValueHandling.Ignore)]
/// <summary>
/// Gets or sets the InfinetId
/// </summary>
public string InfinetId { get; set; }
/// <summary>
/// Attepmts to provide uiont conversion of string InifinetId
/// </summary>
[JsonIgnore]
public uint InfinetIdInt
{
get
{
try
{
return Convert.ToUInt32(InfinetId, 16);
}
catch (Exception)
{
throw new FormatException(string.Format("ERROR:Unable to conver Infinet ID: {0} to hex. Error:\n{1}", InfinetId));
}
}
}
}
/// <summary>
/// Represents a IrControlSpec
/// </summary>
public class IrControlSpec
{
/// <summary>
/// Gets or sets the PortDeviceKey
/// </summary>
public string PortDeviceKey { get; set; }
/// <summary>
/// Gets or sets the PortNumber
/// </summary>
public uint PortNumber { get; set; }
/// <summary>
/// Gets or sets the File
/// </summary>
public string File { get; set; }
}
}

View File

@@ -0,0 +1,91 @@
using System;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
///
/// </summary>
public enum eGenericCommMethodStatusChangeType
{
/// <summary>
/// Connected
/// </summary>
Connected,
/// <summary>
/// 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>
///
/// </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;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericCommMethodReceiveBytesArgs() { }
}
/// <summary>
///
/// </summary>
public class GenericCommMethodReceiveTextArgs : EventArgs
{
/// <summary>
///
/// </summary>
public string Text { get; private set; }
/// <summary>
///
/// </summary>
public string Delimiter { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="text"></param>
public GenericCommMethodReceiveTextArgs(string text)
{
Text = text;
}
/// <summary>
///
/// </summary>
/// <param name="text"></param>
/// <param name="delimiter"></param>
public GenericCommMethodReceiveTextArgs(string text, string delimiter)
:this(text)
{
Delimiter = delimiter;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericCommMethodReceiveTextArgs() { }
}
}

View File

@@ -0,0 +1,173 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Defines the string event handler for line events on the gather
/// </summary>
/// <param name="text"></param>
public delegate void LineReceivedHandler(string text);
/// <summary>
/// Attaches to IBasicCommunication as a text gather
/// </summary>
public class CommunicationGather
{
/// <summary>
/// Event that fires when a line is received from the IBasicCommunication source.
/// The event merely contains the text, not an EventArgs type class.
/// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> LineReceived;
/// <summary>
/// The communication port that this gathers on
/// </summary>
public ICommunicationReceiver Port { get; private set; }
/// <summary>
/// Default false. If true, the delimiter will be included in the line output
/// events
/// </summary>
public bool IncludeDelimiter { get; set; }
/// <summary>
/// For receive buffer
/// </summary>
StringBuilder ReceiveBuffer = new StringBuilder();
/// <summary>
/// Delimiter, like it says!
/// </summary>
char Delimiter;
string[] StringDelimiters;
/// <summary>
/// Constructor for using a char delimiter
/// </summary>
/// <param name="port"></param>
/// <param name="delimiter"></param>
public CommunicationGather(ICommunicationReceiver port, char delimiter)
{
Port = port;
Delimiter = delimiter;
port.TextReceived += new EventHandler<GenericCommMethodReceiveTextArgs>(Port_TextReceived);
}
/// <summary>
/// Constructor for using a single string delimiter
/// </summary>
/// <param name="port"></param>
/// <param name="delimiter"></param>
public CommunicationGather(ICommunicationReceiver port, string delimiter)
:this(port, new string[] { delimiter} )
{
}
/// <summary>
/// Constructor for using an array of string delimiters
/// </summary>
/// <param name="port"></param>
/// <param name="delimiters"></param>
public CommunicationGather(ICommunicationReceiver port, string[] delimiters)
{
Port = port;
StringDelimiters = delimiters;
port.TextReceived += Port_TextReceivedStringDelimiter;
}
/// <summary>
/// Stop method
/// </summary>
public void Stop()
{
Port.TextReceived -= Port_TextReceived;
Port.TextReceived -= Port_TextReceivedStringDelimiter;
}
/// <summary>
/// Handler for raw data coming from port
/// </summary>
void Port_TextReceived(object sender, GenericCommMethodReceiveTextArgs args)
{
var handler = LineReceived;
if (handler != null)
{
ReceiveBuffer.Append(args.Text);
var str = ReceiveBuffer.ToString();
var lines = str.Split(Delimiter);
if (lines.Length > 0)
{
for (int i = 0; i < lines.Length - 1; i++)
{
string strToSend = null;
if (IncludeDelimiter)
strToSend = lines[i] + Delimiter;
else
strToSend = lines[i];
handler(this, new GenericCommMethodReceiveTextArgs(strToSend));
}
ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]);
}
}
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
void Port_TextReceivedStringDelimiter(object sender, GenericCommMethodReceiveTextArgs args)
{
var handler = LineReceived;
if (handler != null)
{
// Receive buffer should either be empty or not contain the delimiter
// If the line does not have a delimiter, append the
ReceiveBuffer.Append(args.Text);
var str = ReceiveBuffer.ToString();
// Case: Receiving DEVICE get version\x0d\0x0a+OK "value":"1234"\x0d\x0a
// RX: DEV
// Split: (1) "DEV"
// RX: I
// Split: (1) "DEVI"
// RX: CE get version
// Split: (1) "DEVICE get version"
// RX: \x0d\x0a+OK "value":"1234"\x0d\x0a
// Split: (2) DEVICE get version, +OK "value":"1234"
// Iterate the delimiters and fire an event for any matching delimiter
foreach (var delimiter in StringDelimiters)
{
var lines = Regex.Split(str, delimiter);
if (lines.Length == 1)
continue;
for (int i = 0; i < lines.Length - 1; i++)
{
string strToSend = null;
if (IncludeDelimiter)
strToSend = lines[i] + delimiter;
else
strToSend = lines[i];
handler(this, new GenericCommMethodReceiveTextArgs(strToSend, delimiter));
}
ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]);
}
}
}
/// <summary>
/// Deconstructor. Disconnects from port TextReceived events.
/// </summary>
~CommunicationGather()
{
Stop();
}
}
}

View File

@@ -0,0 +1,182 @@
using System;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Controls the ability to disable/enable debugging of TX/RX data sent to/from a device with a built in timer to disable
/// </summary>
public class CommunicationStreamDebugging
{
/// <summary>
/// Device Key that this instance configures
/// </summary>
public string ParentDeviceKey { get; private set; }
/// <summary>
/// Timer to disable automatically if not manually disabled
/// </summary>
private CTimer DebugExpiryPeriod;
/// <summary>
/// Gets or sets the DebugSetting
/// </summary>
public eStreamDebuggingSetting DebugSetting { get; private set; }
private uint _DebugTimeoutInMs;
private const uint _DefaultDebugTimeoutMin = 30;
/// <summary>
/// Timeout in Minutes
/// </summary>
public uint DebugTimeoutMinutes
{
get
{
return _DebugTimeoutInMs/60000;
}
}
/// <summary>
/// Gets or sets the RxStreamDebuggingIsEnabled
/// </summary>
public bool RxStreamDebuggingIsEnabled{ get; private set; }
/// <summary>
/// Indicates that transmit stream debugging is enabled
/// </summary>
public bool TxStreamDebuggingIsEnabled { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="parentDeviceKey"></param>
public CommunicationStreamDebugging(string parentDeviceKey)
{
ParentDeviceKey = parentDeviceKey;
}
/// <summary>
/// Sets the debugging setting and if not setting to off, assumes the default of 30 mintues
/// </summary>
/// <param name="setting"></param>
/// <summary>
/// SetDebuggingWithDefaultTimeout method
/// </summary>
public void SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting setting)
{
if (setting == eStreamDebuggingSetting.Off)
{
DisableDebugging();
return;
}
SetDebuggingWithSpecificTimeout(setting, _DefaultDebugTimeoutMin);
}
/// <summary>
/// Sets the debugging setting for the specified number of minutes
/// </summary>
/// <param name="setting"></param>
/// <param name="minutes"></param>
/// <summary>
/// SetDebuggingWithSpecificTimeout method
/// </summary>
public void SetDebuggingWithSpecificTimeout(eStreamDebuggingSetting setting, uint minutes)
{
if (setting == eStreamDebuggingSetting.Off)
{
DisableDebugging();
return;
}
_DebugTimeoutInMs = minutes * 60000;
StopDebugTimer();
DebugExpiryPeriod = new CTimer((o) => DisableDebugging(), _DebugTimeoutInMs);
if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx)
RxStreamDebuggingIsEnabled = true;
if ((setting & eStreamDebuggingSetting.Tx) == eStreamDebuggingSetting.Tx)
TxStreamDebuggingIsEnabled = true;
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
}
/// <summary>
/// Disabled debugging
/// </summary>
private void DisableDebugging()
{
StopDebugTimer();
Debug.SetDeviceDebugSettings(ParentDeviceKey, eStreamDebuggingSetting.Off);
}
private void StopDebugTimer()
{
RxStreamDebuggingIsEnabled = false;
TxStreamDebuggingIsEnabled = false;
if (DebugExpiryPeriod == null)
{
return;
}
DebugExpiryPeriod.Stop();
DebugExpiryPeriod.Dispose();
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

@@ -1,15 +1,12 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Devices;
using PepperDash.Essentials.Core.Monitoring;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core.Communications
{ {
/// <summary> /// <summary>
/// Represents a ConsoleCommMockDevice /// Represents a ConsoleCommMockDevice

View File

@@ -0,0 +1,92 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Represents a ControlPropertiesConfig
/// </summary>
public class ControlPropertiesConfig
{
/// <summary>
/// The method of control
/// </summary>
[JsonProperty("method")]
[JsonConverter(typeof(StringEnumConverter))]
public eControlMethod Method { get; set; }
/// <summary>
/// The key of the device that contains the control port
/// </summary>
[JsonProperty("controlPortDevKey", NullValueHandling = NullValueHandling.Ignore)]
public string ControlPortDevKey { get; set; }
/// <summary>
/// The number of the control port on the device specified by ControlPortDevKey
/// </summary>
[JsonProperty("controlPortNumber", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
public uint? ControlPortNumber { get; set; }
/// <summary>
/// The name of the control port on the device specified by ControlPortDevKey
/// </summary>
[JsonProperty("controlPortName", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
public string ControlPortName { get; set; }
/// <summary>
/// Properties for ethernet based communications
/// </summary>
[JsonProperty("tcpSshProperties", NullValueHandling = NullValueHandling.Ignore)]
public TcpSshPropertiesConfig TcpSshProperties { get; set; }
/// <summary>
/// The filename and path for the IR file
/// </summary>
[JsonProperty("irFile", NullValueHandling = NullValueHandling.Ignore)]
public string IrFile { get; set; }
/// <summary>
/// The IpId of a Crestron device
/// </summary>
[JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)]
public string IpId { get; set; }
/// <summary>
/// Readonly uint representation of the IpId
/// </summary>
[JsonIgnore]
public uint IpIdInt { get { return Convert.ToUInt32(IpId, 16); } }
/// <summary>
/// Char indicating end of line
/// </summary>
[JsonProperty("endOfLineChar", NullValueHandling = NullValueHandling.Ignore)]
public char EndOfLineChar { get; set; }
/// <summary>
/// Defaults to Environment.NewLine;
/// </summary>
[JsonProperty("endOfLineString", NullValueHandling = NullValueHandling.Ignore)]
public string EndOfLineString { get; set; }
/// <summary>
/// Indicates
/// </summary>
[JsonProperty("deviceReadyResponsePattern", NullValueHandling = NullValueHandling.Ignore)]
public string DeviceReadyResponsePattern { get; set; }
/// <summary>
/// Used when communcating to programs running in VC-4
/// </summary>
[JsonProperty("roomId", NullValueHandling = NullValueHandling.Ignore)]
public string RoomId { get; set; }
/// <summary>
/// Constructor
/// </summary>
public ControlPropertiesConfig()
{
}
}
}

View File

@@ -0,0 +1,247 @@
/*PepperDash Technology Corp.
Copyright: 2017
------------------------------------
***Notice of Ownership and Copyright***
The material in which this notice appears is the property of PepperDash Technology Corporation,
which claims copyright under the laws of the United States of America in the entire body of material
and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part,
of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited.
PepperDash Technology Corporation reserves all rights under applicable laws.
------------------------------------ */
using System;
using Crestron.SimplSharp.CrestronSockets;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Delegate for notifying of socket status changes
/// </summary>
/// <param name="client"></param>
public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client);
/// <summary>
/// EventArgs class for socket status changes
/// </summary>
public class GenericSocketStatusChageEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the Client
/// </summary>
public ISocketStatus Client { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="client"></param>
public GenericSocketStatusChageEventArgs(ISocketStatus client)
{
Client = client;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericSocketStatusChageEventArgs() { }
}
/// <summary>
/// Delegate for notifying of TCP Server state changes
/// </summary>
/// <param name="state"></param>
public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state);
/// <summary>
/// EventArgs class for TCP Server state changes
/// </summary>
public class GenericTcpServerStateChangedEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the State
/// </summary>
public ServerState State { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="state"></param>
public GenericTcpServerStateChangedEventArgs(ServerState state)
{
State = state;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerStateChangedEventArgs() { }
}
/// <summary>
/// Delegate for TCP Server socket status changes
/// </summary>
/// <param name="socket"></param>
/// <param name="clientIndex"></param>
/// <param name="clientStatus"></param>
public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus);
/// <summary>
/// EventArgs for TCP server socket status changes
/// </summary>
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
{
/// <summary>
///
/// </summary>
public object Socket { get; private set; }
/// <summary>
///
/// </summary>
public uint ReceivedFromClientIndex { get; private set; }
/// <summary>
///
/// </summary>
public SocketStatus ClientStatus { get; set; }
/// <summary>
///
/// </summary>
/// <param name="socket"></param>
/// <param name="clientStatus"></param>
public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
{
Socket = socket;
ClientStatus = clientStatus;
}
/// <summary>
///
/// </summary>
/// <param name="socket"></param>
/// <param name="clientIndex"></param>
/// <param name="clientStatus"></param>
public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus)
{
Socket = socket;
ReceivedFromClientIndex = clientIndex;
ClientStatus = clientStatus;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerSocketStatusChangeEventArgs() { }
}
/// <summary>
/// EventArgs for TCP server com method receive text
/// </summary>
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
{
/// <summary>
///
/// </summary>
public uint ReceivedFromClientIndex { get; private set; }
/// <summary>
///
/// </summary>
public ushort ReceivedFromClientIndexShort
{
get
{
return (ushort)ReceivedFromClientIndex;
}
}
/// <summary>
/// Gets or sets the Text
/// </summary>
public string Text { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="text"></param>
public GenericTcpServerCommMethodReceiveTextArgs(string text)
{
Text = text;
}
/// <summary>
///
/// </summary>
/// <param name="text"></param>
/// <param name="clientIndex"></param>
public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex)
{
Text = text;
ReceivedFromClientIndex = clientIndex;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerCommMethodReceiveTextArgs() { }
}
/// <summary>
/// EventArgs for TCP server client ready for communication
/// </summary>
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
{
/// <summary>
///
/// </summary>
public bool IsReady;
/// <summary>
///
/// </summary>
/// <param name="isReady"></param>
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
{
IsReady = isReady;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerClientReadyForcommunicationsEventArgs() { }
}
/// <summary>
/// EventArgs for UDP connected
/// </summary>
public class GenericUdpConnectedEventArgs : EventArgs
{
/// <summary>
///
/// </summary>
public ushort UConnected;
/// <summary>
///
/// </summary>
public bool Connected;
/// <summary>
/// Constructor
/// </summary>
public GenericUdpConnectedEventArgs() { }
/// <summary>
///
/// </summary>
/// <param name="uconnected"></param>
public GenericUdpConnectedEventArgs(ushort uconnected)
{
UConnected = uconnected;
}
/// <summary>
///
/// </summary>
/// <param name="connected"></param>
public GenericUdpConnectedEventArgs(bool connected)
{
Connected = connected;
}
}
}

View File

@@ -1,19 +1,19 @@
 using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Crestron.SimplSharp.CrestronSockets; using Crestron.SimplSharp.CrestronSockets;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json; using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Devices; using PepperDash.Essentials.Core.Devices;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using Serilog.Events; using Serilog.Events;
using PepperDash.Essentials.Core.Bridges.JoinMaps;
using PepperDash.Essentials.Core.JoinMaps;
using PepperDash.Essentials.Core.Config.Essentials;
using PepperDash.Essentials.Core.Touchpanels;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core.Communications
{ {
/// <summary> /// <summary>
/// Serves as a generic wrapper class for all styles of IBasicCommuncation ports /// Serves as a generic wrapper class for all styles of IBasicCommuncation ports
@@ -120,7 +120,7 @@ namespace PepperDash.Essentials.Core
if (sComm == null) return; if (sComm == null) return;
sComm.ConnectionChange += (s, a) => sComm.ConnectionChange += (s, a) =>
{ {
trilist.SetUshort(joinMap.Status.JoinNumber, (ushort)(a.Client.ClientStatus)); trilist.SetUshort(joinMap.Status.JoinNumber, (ushort)a.Client.ClientStatus);
trilist.SetBool(joinMap.Connected.JoinNumber, a.Client.ClientStatus == trilist.SetBool(joinMap.Connected.JoinNumber, a.Client.ClientStatus ==
SocketStatus.SOCKET_STATUS_CONNECTED); SocketStatus.SOCKET_STATUS_CONNECTED);
}; };

View File

@@ -1,8 +1,7 @@
using Crestron.SimplSharp.Net.Http; using Crestron.SimplSharp.Net.Http;
using PepperDash.Core;
using System; using System;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core.Communications
{ {
[Obsolete("Please use the builtin HttpClient class instead: https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines")] [Obsolete("Please use the builtin HttpClient class instead: https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines")]
/// <summary> /// <summary>
@@ -33,17 +32,17 @@ namespace PepperDash.Essentials.Core
/// </summary> /// </summary>
public void SendText(string path) public void SendText(string path)
{ {
HttpClientRequest request = new HttpClientRequest(); var request = new HttpClientRequest();
string url = string.Format("http://{0}/{1}", Client.HostName, path); var url = string.Format("http://{0}/{1}", Client.HostName, path);
request.Url = new UrlParser(url); request.Url = new UrlParser(url);
HttpClient.DISPATCHASYNC_ERROR error = Client.DispatchAsyncEx(request, Response, request); var error = Client.DispatchAsyncEx(request, Response, request);
} }
public void SendText(string format, params object[] items) public void SendText(string format, params object[] items)
{ {
HttpClientRequest request = new HttpClientRequest(); var request = new HttpClientRequest();
string url = string.Format("http://{0}/{1}", Client.HostName, string.Format(format, items)); var url = string.Format("http://{0}/{1}", Client.HostName, string.Format(format, items));
request.Url = new UrlParser(url); request.Url = new UrlParser(url);
HttpClient.DISPATCHASYNC_ERROR error = Client.DispatchAsyncEx(request, Response, request); var error = Client.DispatchAsyncEx(request, Response, request);
} }
/// <summary> /// <summary>
@@ -51,8 +50,8 @@ namespace PepperDash.Essentials.Core
/// </summary> /// </summary>
public void SendTextNoResponse(string format, params object[] items) public void SendTextNoResponse(string format, params object[] items)
{ {
HttpClientRequest request = new HttpClientRequest(); var request = new HttpClientRequest();
string url = string.Format("http://{0}/{1}", Client.HostName, string.Format(format, items)); var url = string.Format("http://{0}/{1}", Client.HostName, string.Format(format, items));
request.Url = new UrlParser(url); request.Url = new UrlParser(url);
Client.Dispatch(request); Client.Dispatch(request);
} }

View File

@@ -0,0 +1,21 @@
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Describes a device that can automatically attempt to reconnect
/// </summary>
public interface IAutoReconnect
{
/// <summary>
/// Enable automatic recconnect
/// </summary>
[JsonProperty("autoReconnect")]
bool AutoReconnect { get; set; }
/// <summary>
/// Interval in ms to attempt automatic recconnections
/// </summary>
[JsonProperty("autoReconnectIntervalMs")]
int AutoReconnectIntervalMs { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Defines the contract for IBasicCommunication
/// </summary>
public interface IBasicCommunication : ICommunicationReceiver
{
/// <summary>
/// Send text to the device
/// </summary>
/// <param name="text"></param>
void SendText(string text);
/// <summary>
/// Send bytes to the device
/// </summary>
/// <param name="bytes"></param>
void SendBytes(byte[] bytes);
}
}

View File

@@ -0,0 +1,10 @@
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Represents a device that implements IBasicCommunication and IStreamDebugging
/// </summary>
public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, IStreamDebugging
{
}
}

View File

@@ -0,0 +1,13 @@
using Crestron.SimplSharpPro;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
///
/// </summary>
public interface IComPortsDevice
{
IComPorts Device { get; }
}
}

View File

@@ -0,0 +1,34 @@
using System;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// An incoming communication stream
/// </summary>
public interface ICommunicationReceiver : IKeyed
{
/// <summary>
/// Notifies of bytes received
/// </summary>
event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Notifies of text received
/// </summary>
event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary>
/// Indicates connection status
/// </summary>
[JsonProperty("isConnected")]
bool IsConnected { get; }
/// <summary>
/// Connect to the device
/// </summary>
void Connect();
/// <summary>
/// Disconnect from the device
/// </summary>
void Disconnect();
}
}

View File

@@ -1,18 +1,15 @@
 
using System; using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharpPro; using Crestron.SimplSharpPro;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Devices;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core.Communications
{ {
/// <summary> /// <summary>
/// ///

View File

@@ -0,0 +1,25 @@
using System;
using Crestron.SimplSharp.CrestronSockets;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
/// GenericTcpIpClient
/// </summary>
public interface ISocketStatus : IBasicCommunication
{
/// <summary>
/// Notifies of socket status changes
/// </summary>
event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
/// <summary>
/// The current socket status of the client
/// </summary>
[JsonProperty("clientStatus")]
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
SocketStatus ClientStatus { get; }
}
}

View File

@@ -0,0 +1,10 @@
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Describes a device that implements ISocketStatus and IStreamDebugging
/// </summary>
public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugging
{
}
}

View File

@@ -0,0 +1,16 @@
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Represents a device with stream debugging capablities
/// </summary>
public interface IStreamDebugging
{
/// <summary>
/// Object to enable stream debugging
/// </summary>
[JsonProperty("streamDebugging")]
CommunicationStreamDebugging StreamDebugging { get; }
}
}

View File

@@ -0,0 +1,59 @@
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Represents a TcpClientConfigObject
/// </summary>
public class TcpClientConfigObject
{
/// <summary>
/// TcpSsh Properties
/// </summary>
[JsonProperty("control")]
public ControlPropertiesConfig Control { get; set; }
/// <summary>
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
/// </summary>
[JsonProperty("secure")]
public bool Secure { get; set; }
/// <summary>
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
/// </summary>
[JsonProperty("sharedKeyRequired")]
public bool SharedKeyRequired { get; set; }
/// <summary>
/// The shared key that must match on the server and client
/// </summary>
[JsonProperty("sharedKey")]
public string SharedKey { get; set; }
/// <summary>
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
/// heartbeats do not raise received events.
/// </summary>
[JsonProperty("heartbeatRequired")]
public bool HeartbeatRequired { get; set; }
/// <summary>
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
/// </summary>
[JsonProperty("heartbeatRequiredIntervalInSeconds")]
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
/// <summary>
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
/// </summary>
[JsonProperty("heartbeatStringToMatch")]
public string HeartbeatStringToMatch { get; set; }
/// <summary>
/// Receive Queue size must be greater than 20 or defaults to 20
/// </summary>
[JsonProperty("receiveQueueSize")]
public int ReceiveQueueSize { get; set; }
}
}

View File

@@ -0,0 +1,54 @@
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities
/// </summary>
public class TcpServerConfigObject
{
/// <summary>
/// Uique key
/// </summary>
public string Key { get; set; }
/// <summary>
/// Max Clients that the server will allow to connect.
/// </summary>
public ushort MaxClients { get; set; }
/// <summary>
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
/// </summary>
public bool Secure { get; set; }
/// <summary>
/// Port for the server to listen on
/// </summary>
public int Port { get; set; }
/// <summary>
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
/// </summary>
public bool SharedKeyRequired { get; set; }
/// <summary>
/// The shared key that must match on the server and client
/// </summary>
public string SharedKey { get; set; }
/// <summary>
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
/// heartbeats do not raise received events.
/// </summary>
public bool HeartbeatRequired { get; set; }
/// <summary>
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
/// </summary>
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
/// <summary>
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
/// </summary>
public string HeartbeatStringToMatch { get; set; }
/// <summary>
/// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000
/// </summary>
public int BufferSize { get; set; }
/// <summary>
/// Receive Queue size must be greater than 20 or defaults to 20
/// </summary>
public int ReceiveQueueSize { get; set; }
}
}

View File

@@ -0,0 +1,59 @@
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Represents a TcpSshPropertiesConfig
/// </summary>
public class TcpSshPropertiesConfig
{
/// <summary>
/// Address to connect to
/// </summary>
[JsonProperty(Required = Required.Always)]
public string Address { get; set; }
/// <summary>
/// Port to connect to
/// </summary>
[JsonProperty(Required = Required.Always)]
public int Port { get; set; }
/// <summary>
/// Username credential
/// </summary>
public string Username { get; set; }
/// <summary>
/// Gets or sets the Password
/// </summary>
public string Password { get; set; }
/// <summary>
/// Defaults to 32768
/// </summary>
public int BufferSize { get; set; }
/// <summary>
/// Gets or sets the AutoReconnect
/// </summary>
public bool AutoReconnect { get; set; }
/// <summary>
/// Gets or sets the AutoReconnectIntervalMs
/// </summary>
public int AutoReconnectIntervalMs { get; set; }
/// <summary>
/// Default constructor
/// </summary>
public TcpSshPropertiesConfig()
{
BufferSize = 32768;
AutoReconnect = true;
AutoReconnectIntervalMs = 5000;
Username = "";
Password = "";
}
}
}

View File

@@ -0,0 +1,73 @@
namespace PepperDash.Essentials.Core.Communications
{
/// <summary>
/// Crestron Control Methods for a comm object
/// </summary>
public enum eControlMethod
{
/// <summary>
///
/// </summary>
None = 0,
/// <summary>
/// RS232/422/485
/// </summary>
Com,
/// <summary>
/// Crestron IpId (most Crestron ethernet devices)
/// </summary>
IpId,
/// <summary>
/// Crestron IpIdTcp (HD-MD series, etc.)
/// </summary>
IpidTcp,
/// <summary>
/// Crestron IR control
/// </summary>
IR,
/// <summary>
/// SSH client
/// </summary>
Ssh,
/// <summary>
/// TCP/IP client
/// </summary>
Tcpip,
/// <summary>
/// Telnet
/// </summary>
Telnet,
/// <summary>
/// Crestnet device
/// </summary>
Cresnet,
/// <summary>
/// CEC Control, via a DM HDMI port
/// </summary>
Cec,
/// <summary>
/// UDP Server
/// </summary>
Udp,
/// <summary>
/// HTTP client
/// </summary>
Http,
/// <summary>
/// HTTPS client
/// </summary>
Https,
/// <summary>
/// Websocket client
/// </summary>
Ws,
/// <summary>
/// Secure Websocket client
/// </summary>
Wss,
/// <summary>
/// Secure TCP/IP
/// </summary>
SecureTcpIp
}
}

View File

@@ -1,10 +1,6 @@
using Crestron.SimplSharpPro; using Newtonsoft.Json;
using Newtonsoft.Json; using PepperDash.Essentials.Core.Devices;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Core.Config namespace PepperDash.Essentials.Core.Config
{ {

View File

@@ -1,29 +0,0 @@
using System;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core.Config
{
/// <summary>
/// Represents the base properties for a streaming device.
/// </summary>
public class BaseStreamingDeviceProperties
{
/// <summary>
/// The multicast video address for the streaming device.
/// </summary>
[JsonProperty("multicastVideoAddress", NullValueHandling = NullValueHandling.Ignore)]
public string MulticastVideoAddress { get; set; }
/// <summary>
/// The multicast audio address for the streaming device.
/// </summary>
[JsonProperty("multicastAudioAddress", NullValueHandling = NullValueHandling.Ignore)]
public string MulticastAudioAddress { get; set; }
/// <summary>
/// The URL for the streaming device's media stream.
/// </summary>
[JsonProperty("streamUrl", NullValueHandling = NullValueHandling.Ignore)]
public string StreamUrl { get; set; }
}
}

View File

@@ -6,6 +6,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Essentials.Core.Devices; using PepperDash.Essentials.Core.Devices;
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.Core.Config namespace PepperDash.Essentials.Core.Config
{ {

View File

@@ -1,15 +1,4 @@
namespace PepperDash.Essentials.Core.Config
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Essentials.Core;
using PepperDash.Core;
using Newtonsoft.Json.Linq;
namespace PepperDash.Essentials.Core.Config
{ {
/// <summary> /// <summary>
/// Represents a ConfigPropertiesHelpers /// Represents a ConfigPropertiesHelpers

View File

@@ -1,15 +1,9 @@
using System; using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharpPro;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Core.Config namespace PepperDash.Essentials.Core.Config
{ {
@@ -18,41 +12,41 @@ namespace PepperDash.Essentials.Core.Config
/// </summary> /// </summary>
public class DeviceConfig public class DeviceConfig
{ {
[JsonProperty("key")]
/// <summary> /// <summary>
/// Gets or sets the Key /// Gets or sets the Key
/// </summary> /// </summary>
[JsonProperty("key")]
public string Key { get; set; } public string Key { get; set; }
[JsonProperty("uid")]
/// <summary> /// <summary>
/// Gets or sets the Uid /// Gets or sets the Uid
/// </summary> /// </summary>
[JsonProperty("uid")]
public int Uid { get; set; } public int Uid { get; set; }
[JsonProperty("name")]
/// <summary> /// <summary>
/// Gets or sets the Name /// Gets or sets the Name
/// </summary> /// </summary>
[JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("group")]
/// <summary> /// <summary>
/// Gets or sets the Group /// Gets or sets the Group
/// </summary> /// </summary>
[JsonProperty("group")]
public string Group { get; set; } public string Group { get; set; }
[JsonProperty("type")]
/// <summary> /// <summary>
/// Gets or sets the Type /// Gets or sets the Type
/// </summary> /// </summary>
[JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("properties")]
[JsonConverter(typeof(DevicePropertiesConverter))]
/// <summary> /// <summary>
/// Gets or sets the Properties /// Gets or sets the Properties
/// </summary> /// </summary>
[JsonProperty("properties")]
[JsonConverter(typeof(DevicePropertiesConverter))]
public JToken Properties { get; set; } public JToken Properties { get; set; }
public DeviceConfig(DeviceConfig dc) public DeviceConfig(DeviceConfig dc)
@@ -68,7 +62,7 @@ namespace PepperDash.Essentials.Core.Config
//Properties = JToken.FromObject(dc.Properties); //Properties = JToken.FromObject(dc.Properties);
} }
public DeviceConfig() { } public DeviceConfig() {}
} }
/// <summary> /// <summary>

View File

@@ -5,13 +5,10 @@ using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Config;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Core.Config namespace PepperDash.Essentials.Core.Config.Essentials
{ {
/// <summary> /// <summary>
/// Loads the ConfigObject from the file /// Loads the ConfigObject from the file
@@ -37,7 +34,7 @@ namespace PepperDash.Essentials.Core.Config
// Check for local config file first // Check for local config file first
var filePath = Global.FilePathPrefix + ConfigWriter.LocalConfigFolder + Global.DirectorySeparator + Global.ConfigFileName; var filePath = Global.FilePathPrefix + ConfigWriter.LocalConfigFolder + Global.DirectorySeparator + Global.ConfigFileName;
bool localConfigFound = false; var localConfigFound = false;
Debug.LogMessage(LogEventLevel.Information, "Attempting to load Local config file: '{0}'", filePath); Debug.LogMessage(LogEventLevel.Information, "Attempting to load Local config file: '{0}'", filePath);
@@ -113,7 +110,7 @@ namespace PepperDash.Essentials.Core.Config
} }
// Read the file // Read the file
using (StreamReader fs = new StreamReader(filePath)) using (var fs = new StreamReader(filePath))
{ {
Debug.LogMessage(LogEventLevel.Information, "Loading config file: '{0}'", filePath); Debug.LogMessage(LogEventLevel.Information, "Loading config file: '{0}'", filePath);
@@ -124,35 +121,22 @@ namespace PepperDash.Essentials.Core.Config
Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Local Config"); Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Local Config");
return true; return true;
} }
else else
{ {
var parsedConfig = JObject.Parse(fs.ReadToEnd()); var doubleObj = JObject.Parse(fs.ReadToEnd());
ConfigObject = PortalConfigReader.MergeConfigs(doubleObj).ToObject<EssentialsConfig>();
// Check if it's a v2 config (check for "version" node)
// this means it's already merged by the Portal API
// from the v2 config tool
var isV2Config = parsedConfig["versions"] != null;
if (isV2Config)
{
Debug.LogMessage(LogEventLevel.Information, "Config file is a v2 format, no merge necessary.");
ConfigObject = parsedConfig.ToObject<EssentialsConfig>();
Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded v2 Config");
return true;
}
// Extract SystemUrl and TemplateUrl into final config output // Extract SystemUrl and TemplateUrl into final config output
ConfigObject = PortalConfigReader.MergeConfigs(parsedConfig).ToObject<EssentialsConfig>();
if (parsedConfig["system_url"] != null) if (doubleObj["system_url"] != null)
{ {
ConfigObject.SystemUrl = parsedConfig["system_url"].Value<string>(); ConfigObject.SystemUrl = doubleObj["system_url"].Value<string>();
} }
if (parsedConfig["template_url"] != null) if (doubleObj["template_url"] != null)
{ {
ConfigObject.TemplateUrl = parsedConfig["template_url"].Value<string>(); ConfigObject.TemplateUrl = doubleObj["template_url"].Value<string>();
} }
} }
@@ -226,7 +210,7 @@ namespace PepperDash.Essentials.Core.Config
{ {
debugStringWidth = 51; debugStringWidth = 51;
} }
var qualifier = (filePathLength % 2 != 0) var qualifier = filePathLength % 2 != 0
? " Using Local Config File " ? " Using Local Config File "
: " Using Local Config File "; : " Using Local Config File ";
var bookend1 = (debugStringWidth - qualifier.Length) / 2; var bookend1 = (debugStringWidth - qualifier.Length) / 2;

View File

@@ -1,20 +1,13 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Crestron.SimplSharp.Net.Http; using Crestron.SimplSharp.Net.Http;
using Crestron.SimplSharpPro.Diagnostics;
using PepperDash.Core;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Core.Config namespace PepperDash.Essentials.Core.Config.Essentials
{ {
public static class ConfigUpdater public static class ConfigUpdater
{ {
@@ -146,7 +139,7 @@ namespace PepperDash.Essentials.Core.Config
{ {
Debug.LogMessage(LogEventLevel.Information, "{0} Existing files found in archive folder. Deleting.", archivedConfigFiles.Length); Debug.LogMessage(LogEventLevel.Information, "{0} Existing files found in archive folder. Deleting.", archivedConfigFiles.Length);
for (int i = 0; i < archivedConfigFiles.Length; i++ ) for (var i = 0; i < archivedConfigFiles.Length; i++ )
{ {
var file = archivedConfigFiles[i]; var file = archivedConfigFiles[i];
Debug.LogMessage(LogEventLevel.Information, "Deleting archived file: '{0}'", file.FullName); Debug.LogMessage(LogEventLevel.Information, "Deleting archived file: '{0}'", file.FullName);
@@ -196,7 +189,7 @@ namespace PepperDash.Essentials.Core.Config
OnStatusUpdate(eUpdateStatus.RestartingProgram); OnStatusUpdate(eUpdateStatus.RestartingProgram);
string response = string.Empty; var response = string.Empty;
CrestronConsole.SendControlSystemCommand(string.Format("progreset -p:{0}", InitialParametersClass.ApplicationNumber), ref response); CrestronConsole.SendControlSystemCommand(string.Format("progreset -p:{0}", InitialParametersClass.ApplicationNumber), ref response);

View File

@@ -1,17 +1,14 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Core;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Core.Config namespace PepperDash.Essentials.Core.Config.Essentials
{ {
/// <summary> /// <summary>
/// Responsible for updating config at runtime, and writing the updates out to a local file /// Responsible for updating config at runtime, and writing the updates out to a local file
@@ -36,7 +33,7 @@ namespace PepperDash.Essentials.Core.Config
/// </summary> /// </summary>
public static bool UpdateDeviceProperties(string deviceKey, JToken properties) public static bool UpdateDeviceProperties(string deviceKey, JToken properties)
{ {
bool success = false; var success = false;
// Get the current device config // Get the current device config
var deviceConfig = ConfigReader.ConfigObject.Devices.FirstOrDefault(d => d.Key.Equals(deviceKey)); var deviceConfig = ConfigReader.ConfigObject.Devices.FirstOrDefault(d => d.Key.Equals(deviceKey));
@@ -61,7 +58,7 @@ namespace PepperDash.Essentials.Core.Config
/// </summary> /// </summary>
public static bool UpdateDeviceConfig(DeviceConfig config) public static bool UpdateDeviceConfig(DeviceConfig config)
{ {
bool success = false; var success = false;
var deviceConfigIndex = ConfigReader.ConfigObject.Devices.FindIndex(d => d.Key.Equals(config.Key)); var deviceConfigIndex = ConfigReader.ConfigObject.Devices.FindIndex(d => d.Key.Equals(config.Key));
@@ -84,7 +81,7 @@ namespace PepperDash.Essentials.Core.Config
/// </summary> /// </summary>
public static bool UpdateRoomConfig(DeviceConfig config) public static bool UpdateRoomConfig(DeviceConfig config)
{ {
bool success = false; var success = false;
var roomConfigIndex = ConfigReader.ConfigObject.Rooms.FindIndex(d => d.Key.Equals(config.Key)); var roomConfigIndex = ConfigReader.ConfigObject.Rooms.FindIndex(d => d.Key.Equals(config.Key));
@@ -149,7 +146,7 @@ namespace PepperDash.Essentials.Core.Config
{ {
if (fileLock.TryEnter()) if (fileLock.TryEnter())
{ {
using (StreamWriter sw = new StreamWriter(filePath)) using (var sw = new StreamWriter(filePath))
{ {
sw.Write(configData); sw.Write(configData);
sw.Flush(); sw.Flush();

View File

@@ -1,179 +1,89 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json; using Newtonsoft.Json;
using PepperDash.Core; namespace PepperDash.Essentials.Core.Config.Essentials
namespace PepperDash.Essentials.Core.Config
{ {
/// <summary> /// <summary>
/// Loads the ConfigObject from the file /// Loads the ConfigObject from the file
/// </summary> /// </summary>
public class EssentialsConfig : BasicConfig public class EssentialsConfig : BasicConfig
{ {
/// <summary> [JsonProperty("system_url")]
/// Gets or sets the SystemUrl
/// </summary>
[JsonProperty("system_url")]
public string SystemUrl { get; set; } public string SystemUrl { get; set; }
/// <summary> [JsonProperty("template_url")]
/// Gets or sets the TemplateUrl
/// </summary>
[JsonProperty("template_url")]
public string TemplateUrl { get; set; } public string TemplateUrl { get; set; }
/// <summary>
/// Gets the SystemUuid extracted from the SystemUrl
/// </summary>
[JsonProperty("systemUuid")] [JsonProperty("systemUuid")]
public string SystemUuid public string SystemUuid
{ {
get get
{ {
string uuid; if (string.IsNullOrEmpty(SystemUrl))
return "missing url";
if (string.IsNullOrEmpty(SystemUrl)) if (SystemUrl.Contains("#"))
{ {
uuid = "missing url"; var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
} var uuid = result.Groups[1].Value;
else if (SystemUrl.Contains("#")) return uuid;
{ } else
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
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*"); var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*");
uuid = result.Groups[1].Value; var uuid = result.Groups[1].Value;
return uuid;
} }
return uuid;
} }
} }
/// <summary>
/// Gets the TemplateUuid extracted from the TemplateUrl
/// </summary>
[JsonProperty("templateUuid")] [JsonProperty("templateUuid")]
public string TemplateUuid public string TemplateUuid
{ {
get get
{ {
string uuid; if (string.IsNullOrEmpty(TemplateUrl))
return "missing template url";
if (string.IsNullOrEmpty(TemplateUrl)) if (TemplateUrl.Contains("#"))
{ {
uuid = "missing template url"; var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
} var uuid = result.Groups[1].Value;
else if (TemplateUrl.Contains("#")) return uuid;
{ } else
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
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*"); var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*");
uuid = result.Groups[2].Value; var uuid = result.Groups[2].Value;
return uuid;
} }
return uuid;
} }
} }
[JsonProperty("rooms")]
/// <summary> /// <summary>
/// Gets or sets the Rooms /// Gets or sets the Rooms
/// </summary> /// </summary>
[JsonProperty("rooms")]
public List<DeviceConfig> Rooms { get; set; } public List<DeviceConfig> Rooms { get; set; }
/// <summary>
/// Gets or sets the Versions
/// </summary>
public VersionData Versions { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="EssentialsConfig"/> class.
/// </summary>
public EssentialsConfig() public EssentialsConfig()
: base() : base()
{ {
Rooms = new List<DeviceConfig>(); Rooms = new List<DeviceConfig>();
} }
} }
/// <summary> /// <summary>
/// Represents version data for Essentials and its packages /// Represents a SystemTemplateConfigs
/// </summary> /// </summary>
public class VersionData public class SystemTemplateConfigs
{ {
/// <summary> /// <summary>
/// Gets or sets the Essentials version /// Gets or sets the System
/// </summary> /// </summary>
[JsonProperty("essentials")]
public NugetVersion Essentials { get; set; }
/// <summary>
/// Gets or sets the list of Packages
/// </summary>
[JsonProperty("packages")]
public List<NugetVersion> Packages { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="VersionData"/> class.
/// </summary>
public VersionData()
{
Packages = new List<NugetVersion>();
}
}
/// <summary>
/// Represents a NugetVersion
/// </summary>
public class NugetVersion
{
/// <summary>
/// Gets or sets the Version
/// </summary>
[JsonProperty("version")]
public string Version { get; set; }
/// <summary>
/// Gets or sets the PackageId
/// </summary>
[JsonProperty("packageId")]
public string PackageId { get; set; }
}
/// <summary>
/// Represents a SystemTemplateConfigs
/// </summary>
public class SystemTemplateConfigs
{
/// <summary>
/// Gets or sets the System
/// </summary>
public EssentialsConfig System { get; set; } public EssentialsConfig System { get; set; }
/// <summary>
/// Gets or sets the Template
/// </summary>
public EssentialsConfig Template { get; set; } public EssentialsConfig Template { get; set; }
} }
} }

View File

@@ -1,10 +1,4 @@
using System; namespace PepperDash.Essentials.Core.Config
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Core
{ {
/// <summary> /// <summary>
/// Defines the contract for ILoadConfig /// Defines the contract for ILoadConfig

View File

@@ -2,6 +2,7 @@
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Newtonsoft.Json; using Newtonsoft.Json;
using PepperDash.Essentials.Core.Global;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -112,7 +113,7 @@ namespace PepperDash.Essentials.Core.Config
/// The information gathered by the processor at runtime about it's NICs and their IP addresses. /// The information gathered by the processor at runtime about it's NICs and their IP addresses.
/// </summary> /// </summary>
[JsonProperty("ipInfo")] [JsonProperty("ipInfo")]
public Dictionary<short, EthernetAdapterInfo> IpInfo public Dictionary<short, Core.Global.EthernetAdapterInfo> IpInfo
{ {
get get
{ {

View File

@@ -1,10 +1,4 @@
using System; namespace PepperDash.Essentials.Core.Config
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Core.Config
{ {
/// <summary> /// <summary>
/// Represents a SourceDevicePropertiesConfigBase /// Represents a SourceDevicePropertiesConfigBase

View File

@@ -1,13 +1,13 @@
using System; using System.Linq;
using System.Linq;
using Crestron.SimplSharpPro; using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using PepperDash.Core;
using PepperDash.Core.JsonStandardObjects;
using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Devices;
using PepperDash.Essentials.Core.Feedbacks;
using PepperDash.Essentials.Core.Monitoring;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core.Crestron
{ {
public abstract class CrestronGenericBaseDevice : EssentialsDevice, IOnline, IHasFeedback, ICommunicationMonitor, IUsageTracking public abstract class CrestronGenericBaseDevice : EssentialsDevice, IOnline, IHasFeedback, ICommunicationMonitor, IUsageTracking
{ {
@@ -16,7 +16,7 @@ namespace PepperDash.Essentials.Core
/// <summary> /// <summary>
/// Gets or sets the Feedbacks /// Gets or sets the Feedbacks
/// </summary> /// </summary>
public FeedbackCollection<Feedback> Feedbacks { get; private set; } public FeedbackCollection<Feedbacks.Feedback> Feedbacks { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the IsOnline /// Gets or sets the IsOnline
@@ -39,7 +39,7 @@ namespace PepperDash.Essentials.Core
protected CrestronGenericBaseDevice(string key, string name, GenericBase hardware) protected CrestronGenericBaseDevice(string key, string name, GenericBase hardware)
: base(key, name) : base(key, name)
{ {
Feedbacks = new FeedbackCollection<Feedback>(); Feedbacks = new FeedbackCollection<Feedbacks.Feedback>();
Hardware = hardware; Hardware = hardware;
IsOnline = new BoolFeedback("IsOnlineFeedback", () => Hardware.IsOnline); IsOnline = new BoolFeedback("IsOnlineFeedback", () => Hardware.IsOnline);
@@ -53,7 +53,7 @@ namespace PepperDash.Essentials.Core
protected CrestronGenericBaseDevice(string key, string name) protected CrestronGenericBaseDevice(string key, string name)
: base(key, name) : base(key, name)
{ {
Feedbacks = new FeedbackCollection<Feedback>(); Feedbacks = new FeedbackCollection<Feedbacks.Feedback>();
} }
@@ -142,7 +142,7 @@ namespace PepperDash.Essentials.Core
/// <summary> /// <summary>
/// AddToFeedbackList method /// AddToFeedbackList method
/// </summary> /// </summary>
public void AddToFeedbackList(params Feedback[] newFbs) public void AddToFeedbackList(params Feedbacks.Feedback[] newFbs)
{ {
foreach (var f in newFbs) foreach (var f in newFbs)
{ {
@@ -186,8 +186,10 @@ namespace PepperDash.Essentials.Core
/// </summary> /// </summary>
public UsageTracking UsageTracker { get; set; } public UsageTracking UsageTracker { get; set; }
FeedbackCollection<Feedbacks.Feedback> IHasFeedback.Feedbacks => throw new System.NotImplementedException();
#endregion #endregion
} }
public abstract class CrestronGenericBridgeableBaseDevice : CrestronGenericBaseDevice, IBridgeAdvanced public abstract class CrestronGenericBridgeableBaseDevice : CrestronGenericBaseDevice, IBridgeAdvanced
{ {

View File

@@ -1,128 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// A bridge class to cover the basic features of GenericBase hardware
/// </summary>
public class CrestronGenericBaseDevice : Device, IOnline, IHasFeedback, ICommunicationMonitor, IUsageTracking
{
public virtual GenericBase Hardware { get; protected set; }
public BoolFeedback IsOnline { get; private set; }
public BoolFeedback IsRegistered { get; private set; }
public StringFeedback IpConnectionsText { get; private set; }
public CrestronGenericBaseDevice(string key, string name, GenericBase hardware)
: base(key, name)
{
Hardware = hardware;
IsOnline = new BoolFeedback(CommonBoolCue.IsOnlineFeedback, () => Hardware.IsOnline);
IsRegistered = new BoolFeedback(new Cue("IsRegistered", 0, eCueType.Bool), () => Hardware.Registered);
IpConnectionsText = new StringFeedback(CommonStringCue.IpConnectionsText, () =>
string.Join(",", Hardware.ConnectedIpList.Select(cip => cip.DeviceIpAddress).ToArray()));
CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, hardware, 120000, 300000);
}
/// <summary>
/// Make sure that overriding classes call this!
/// Registers the Crestron device, connects up to the base events, starts communication monitor
/// </summary>
public override bool CustomActivate()
{
Debug.LogMessage(LogEventLevel.Information, this, "Activating");
var response = Hardware.RegisterWithLogging(Key);
if (response != eDeviceRegistrationUnRegistrationResponse.Success)
{
<<<<<<< HEAD
Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Crestron device: {0}", response);
return false;
}
=======
Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Crestron device: {0}", response);
return false;
}
>>>>>>> origin/feature/ecs-342-neil
Hardware.OnlineStatusChange += new OnlineStatusChangeEventHandler(Hardware_OnlineStatusChange);
CommunicationMonitor.Start();
return true;
}
/// <summary>
/// This disconnects events and unregisters the base hardware device.
/// </summary>
/// <returns></returns>
public override bool Deactivate()
{
CommunicationMonitor.Stop();
Hardware.OnlineStatusChange -= Hardware_OnlineStatusChange;
return Hardware.UnRegister() == eDeviceRegistrationUnRegistrationResponse.Success;
}
/// <summary>
/// Returns a list containing the Outputs that we want to expose.
/// </summary>
public virtual List<Feedback> Feedbacks
{
get
{
return new List<Feedback>
{
IsOnline,
IsRegistered,
IpConnectionsText
};
}
}
void Hardware_OnlineStatusChange(GenericBase currentDevice, OnlineOfflineEventArgs args)
{
IsOnline.FireUpdate();
}
#region IStatusMonitor Members
public StatusMonitorBase CommunicationMonitor { get; private set; }
#endregion
#region IUsageTracking Members
public UsageTracking UsageTracker { get; set; }
#endregion
}
//***********************************************************************************
public class CrestronGenericBaseDeviceEventIds
{
public const uint IsOnline = 1;
public const uint IpConnectionsText =2;
}
/// <summary>
/// Adds logging to Register() failure
/// </summary>
public static class GenericBaseExtensions
{
public static eDeviceRegistrationUnRegistrationResponse RegisterWithLogging(this GenericBase device, string key)
{
var result = device.Register();
if (result != eDeviceRegistrationUnRegistrationResponse.Success)
{
Debug.LogMessage(LogEventLevel.Information, "Cannot register device '{0}': {1}", key, result);
}
return result;
}
}
}

Some files were not shown because too many files have changed in this diff Show More