mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-01-12 03:54:51 +00:00
Compare commits
91 Commits
v2.19.1
...
mc-connect
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c4aec14d1 | ||
|
|
7d3f871460 | ||
|
|
78c9381108 | ||
|
|
a7ff2e8903 | ||
|
|
ae0b2fe086 | ||
|
|
bd11c827da | ||
|
|
7ea1efbabf | ||
|
|
9d6aaa2a0e | ||
|
|
53e7a30224 | ||
|
|
39c1f60a4d | ||
|
|
2cc37a4e40 | ||
|
|
076e5dfa9d | ||
|
|
092896bb25 | ||
|
|
7c8f0586e6 | ||
|
|
c5b0872a4c | ||
|
|
a7b88ec38d | ||
|
|
210b398a13 | ||
|
|
bce1e3610e | ||
|
|
6a33e7c99d | ||
|
|
2048e3f65d | ||
|
|
81df2738de | ||
|
|
08fbec416f | ||
|
|
7594b22096 | ||
|
|
d1babf6b9b | ||
|
|
2187c9fb0d | ||
|
|
a5e6059160 | ||
|
|
9ef4aedcce | ||
|
|
f7c7160bf0 | ||
|
|
dbf5740841 | ||
|
|
c07e099a79 | ||
|
|
06cb508f3a | ||
|
|
e93b5b34cc | ||
|
|
4f5d4ef87a | ||
|
|
636da8cc8c | ||
|
|
5de1e2d7bb | ||
|
|
03bbb84894 | ||
|
|
d17394cdd7 | ||
|
|
8467afde38 | ||
|
|
5c016fb4b8 | ||
|
|
2fbc32947c | ||
|
|
c06b57a5f9 | ||
|
|
6d64fffc50 | ||
|
|
0c4ebdaf1d | ||
|
|
2c49fb9321 | ||
|
|
c55de61da9 | ||
|
|
42444ede0a | ||
|
|
3d50f5f5ac | ||
|
|
11d62aebe1 | ||
|
|
edc10a9c2a | ||
|
|
9be5823956 | ||
|
|
35371dde22 | ||
|
|
d3ceb4d7e7 | ||
|
|
a782b57100 | ||
|
|
1360de599f | ||
|
|
fd95f5fed1 | ||
|
|
9426dff5df | ||
|
|
0d083e63c6 | ||
|
|
6ed7c96ec7 | ||
|
|
314570d6c3 | ||
|
|
ff609dfecd | ||
|
|
666be5ae59 | ||
|
|
bfc9b7e7fa | ||
|
|
b02e952765 | ||
|
|
72861a5097 | ||
|
|
44ed067f4d | ||
|
|
faa2169baf | ||
|
|
fd40b0c6d1 | ||
|
|
afcddad1cc | ||
|
|
c4cf8f13e9 | ||
|
|
c852f87a27 | ||
|
|
071174fa7d | ||
|
|
da0f28a10c | ||
|
|
0538a304ed | ||
|
|
705c5db237 | ||
|
|
2e95f5337e | ||
|
|
ba576180a7 | ||
|
|
92c9db8237 | ||
|
|
c4d064965f | ||
|
|
f27965ac29 | ||
|
|
3ce282e2db | ||
|
|
32a332a6e2 | ||
|
|
151a90c9be | ||
|
|
89e893b3b7 | ||
|
|
d01eb03890 | ||
|
|
16c92afabb | ||
|
|
dff5d2d32e | ||
|
|
317bde3814 | ||
|
|
8bab3dc966 | ||
|
|
514ac850ca | ||
|
|
44432f7a41 | ||
|
|
99253b30c2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -396,3 +396,4 @@ _site/
|
||||
api/
|
||||
*.DS_Store
|
||||
/._PepperDash.Essentials.4Series.sln
|
||||
dotnet
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>2.18.2-local</Version>
|
||||
<Version>2.19.4-local</Version>
|
||||
<InformationalVersion>$(Version)</InformationalVersion>
|
||||
<Authors>PepperDash Technology</Authors>
|
||||
<Company>PepperDash Technology</Company>
|
||||
|
||||
43
src/PepperDash.Core/ComTextHelper.cs
Normal file
43
src/PepperDash.Core/ComTextHelper.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
@@ -37,14 +36,14 @@ namespace PepperDash.Core
|
||||
{
|
||||
get
|
||||
{
|
||||
return _DebugTimeoutInMs/60000;
|
||||
return _DebugTimeoutInMs / 60000;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the RxStreamDebuggingIsEnabled
|
||||
/// </summary>
|
||||
public bool RxStreamDebuggingIsEnabled{ get; private set; }
|
||||
public bool RxStreamDebuggingIsEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that transmit stream debugging is enabled
|
||||
@@ -108,7 +107,7 @@ namespace PepperDash.Core
|
||||
TxStreamDebuggingIsEnabled = true;
|
||||
|
||||
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -136,51 +135,4 @@ namespace PepperDash.Core
|
||||
DebugExpiryPeriod = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The available settings for stream debugging
|
||||
/// </summary>
|
||||
[Flags]
|
||||
/// <summary>
|
||||
/// Enumeration of eStreamDebuggingSetting values
|
||||
/// </summary>
|
||||
public enum eStreamDebuggingSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug off
|
||||
/// </summary>
|
||||
Off = 0,
|
||||
/// <summary>
|
||||
/// Debug received data
|
||||
/// </summary>
|
||||
Rx = 1,
|
||||
/// <summary>
|
||||
/// Debug transmitted data
|
||||
/// </summary>
|
||||
Tx = 2,
|
||||
/// <summary>
|
||||
/// Debug both received and transmitted data
|
||||
/// </summary>
|
||||
Both = Rx | Tx
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The available settings for stream debugging response types
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum eStreamDebuggingDataTypeSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug data in byte format
|
||||
/// </summary>
|
||||
Bytes = 0,
|
||||
/// <summary>
|
||||
/// Debug data in text format
|
||||
/// </summary>
|
||||
Text = 1,
|
||||
/// <summary>
|
||||
/// Debug data in both byte and text formats
|
||||
/// </summary>
|
||||
Both = Bytes | Text,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Crestron.SimplSharp;
|
||||
@@ -11,11 +12,12 @@ using Renci.SshNet.Common;
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// SSH Client
|
||||
/// </summary>
|
||||
public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
||||
{
|
||||
private const string SPlusKey = "Uninitialized SshClient";
|
||||
|
||||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
@@ -36,11 +38,6 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
|
||||
/// <summary>
|
||||
/////
|
||||
///// </summary>
|
||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Hostname
|
||||
/// </summary>
|
||||
@@ -67,7 +64,7 @@ namespace PepperDash.Core
|
||||
public bool IsConnected
|
||||
{
|
||||
// returns false if no client or not connected
|
||||
get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
get { return client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -83,16 +80,26 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus
|
||||
{
|
||||
get { return _ClientStatus; }
|
||||
get { lock (_stateLock) { return _ClientStatus; } }
|
||||
private set
|
||||
{
|
||||
if (_ClientStatus == value)
|
||||
return;
|
||||
_ClientStatus = value;
|
||||
OnConnectionChange();
|
||||
bool shouldFireEvent = false;
|
||||
lock (_stateLock)
|
||||
{
|
||||
if (_ClientStatus != value)
|
||||
{
|
||||
_ClientStatus = value;
|
||||
shouldFireEvent = true;
|
||||
}
|
||||
}
|
||||
// Fire event outside lock to avoid deadlock
|
||||
if (shouldFireEvent)
|
||||
OnConnectionChange();
|
||||
}
|
||||
}
|
||||
SocketStatus _ClientStatus;
|
||||
|
||||
private SocketStatus _ClientStatus;
|
||||
private bool _ConnectEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
||||
@@ -100,7 +107,7 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public ushort UStatus
|
||||
{
|
||||
get { return (ushort)_ClientStatus; }
|
||||
get { lock (_stateLock) { return (ushort)_ClientStatus; } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -111,7 +118,11 @@ namespace PepperDash.Core
|
||||
/// <summary>
|
||||
/// Will be set and unset by connect and disconnect only
|
||||
/// </summary>
|
||||
public bool ConnectEnabled { get; private set; }
|
||||
public bool ConnectEnabled
|
||||
{
|
||||
get { lock (_stateLock) { return _ConnectEnabled; } }
|
||||
private set { lock (_stateLock) { _ConnectEnabled = value; } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for AutoReconnect
|
||||
@@ -127,17 +138,25 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
|
||||
SshClient Client;
|
||||
private SshClient client;
|
||||
|
||||
ShellStream TheStream;
|
||||
private ShellStream shellStream;
|
||||
|
||||
CTimer ReconnectTimer;
|
||||
private readonly Timer reconnectTimer;
|
||||
|
||||
//Lock object to prevent simulatneous connect/disconnect operations
|
||||
//private CCriticalSection connectLock = new CCriticalSection();
|
||||
private SemaphoreSlim connectLock = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim connectLock = new SemaphoreSlim(1);
|
||||
|
||||
private bool DisconnectLogged = false;
|
||||
// Thread-safety lock for state changes
|
||||
private readonly object _stateLock = new object();
|
||||
|
||||
private bool disconnectLogged = false;
|
||||
|
||||
/// <summary>
|
||||
/// When true, turns off echo for the SSH session
|
||||
/// </summary>
|
||||
public bool DisableEcho { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Typical constructor.
|
||||
@@ -154,13 +173,13 @@ namespace PepperDash.Core
|
||||
Password = password;
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
|
||||
ReconnectTimer = new CTimer(o =>
|
||||
reconnectTimer = new Timer(o =>
|
||||
{
|
||||
if (ConnectEnabled)
|
||||
if (ConnectEnabled) // Now thread-safe property access
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
}, System.Threading.Timeout.Infinite);
|
||||
}, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -172,23 +191,23 @@ namespace PepperDash.Core
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
|
||||
ReconnectTimer = new CTimer(o =>
|
||||
reconnectTimer = new Timer(o =>
|
||||
{
|
||||
if (ConnectEnabled)
|
||||
if (ConnectEnabled) // Now thread-safe property access
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
}, System.Threading.Timeout.Infinite);
|
||||
}, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles closing this up when the program shuts down
|
||||
/// </summary>
|
||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
private void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
{
|
||||
if (programEventType == eProgramStatusEventType.Stopping)
|
||||
{
|
||||
if (Client != null)
|
||||
if (client != null)
|
||||
{
|
||||
this.LogDebug("Program stopping. Closing connection");
|
||||
Disconnect();
|
||||
@@ -223,10 +242,10 @@ namespace PepperDash.Core
|
||||
this.LogDebug("Attempting connect");
|
||||
|
||||
// Cancel reconnect if running.
|
||||
ReconnectTimer?.Stop();
|
||||
StopReconnectTimer();
|
||||
|
||||
// Cleanup the old client if it already exists
|
||||
if (Client != null)
|
||||
if (client != null)
|
||||
{
|
||||
this.LogDebug("Cleaning up disconnected client");
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||
@@ -239,29 +258,36 @@ namespace PepperDash.Core
|
||||
|
||||
this.LogDebug("Creating new SshClient");
|
||||
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
|
||||
Client = new SshClient(connectionInfo);
|
||||
Client.ErrorOccurred += Client_ErrorOccurred;
|
||||
client = new SshClient(connectionInfo);
|
||||
client.ErrorOccurred += Client_ErrorOccurred;
|
||||
|
||||
//Attempt to connect
|
||||
ClientStatus = SocketStatus.SOCKET_STATUS_WAITING;
|
||||
try
|
||||
{
|
||||
Client.Connect();
|
||||
TheStream = Client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534);
|
||||
if (TheStream.DataAvailable)
|
||||
client.Connect();
|
||||
|
||||
var modes = new Dictionary<TerminalModes, uint>();
|
||||
|
||||
if (DisableEcho)
|
||||
{
|
||||
modes.Add(TerminalModes.ECHO, 0);
|
||||
}
|
||||
|
||||
shellStream = client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534, modes);
|
||||
if (shellStream.DataAvailable)
|
||||
{
|
||||
// empty the buffer if there is data
|
||||
string str = TheStream.Read();
|
||||
shellStream.Read();
|
||||
}
|
||||
TheStream.DataReceived += Stream_DataReceived;
|
||||
shellStream.DataReceived += Stream_DataReceived;
|
||||
this.LogInformation("Connected");
|
||||
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
|
||||
DisconnectLogged = false;
|
||||
disconnectLogged = false;
|
||||
}
|
||||
catch (SshConnectionException e)
|
||||
{
|
||||
var ie = e.InnerException; // The details are inside!!
|
||||
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
||||
|
||||
if (ie is SocketException)
|
||||
{
|
||||
@@ -286,37 +312,36 @@ namespace PepperDash.Core
|
||||
this.LogVerbose(ie, "Exception details: ");
|
||||
}
|
||||
|
||||
DisconnectLogged = true;
|
||||
disconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
StartReconnectTimer();
|
||||
}
|
||||
}
|
||||
catch (SshOperationTimeoutException ex)
|
||||
{
|
||||
this.LogWarning("Connection attempt timed out: {message}", ex.Message);
|
||||
|
||||
DisconnectLogged = true;
|
||||
disconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
StartReconnectTimer();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
||||
this.LogError("Unhandled exception on connect: {error}", e.Message);
|
||||
this.LogVerbose(e, "Exception details: ");
|
||||
DisconnectLogged = true;
|
||||
disconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
StartReconnectTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,11 +359,7 @@ namespace PepperDash.Core
|
||||
{
|
||||
ConnectEnabled = false;
|
||||
// Stop trying reconnects, if we are
|
||||
if (ReconnectTimer != null)
|
||||
{
|
||||
ReconnectTimer.Stop();
|
||||
// ReconnectTimer = null;
|
||||
}
|
||||
StopReconnectTimer();
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||
}
|
||||
@@ -352,12 +373,12 @@ namespace PepperDash.Core
|
||||
|
||||
try
|
||||
{
|
||||
if (Client != null)
|
||||
if (client != null)
|
||||
{
|
||||
Client.ErrorOccurred -= Client_ErrorOccurred;
|
||||
Client.Disconnect();
|
||||
Client.Dispose();
|
||||
Client = null;
|
||||
client.ErrorOccurred -= Client_ErrorOccurred;
|
||||
client.Disconnect();
|
||||
client.Dispose();
|
||||
client = null;
|
||||
ClientStatus = status;
|
||||
this.LogDebug("Disconnected");
|
||||
}
|
||||
@@ -371,16 +392,16 @@ namespace PepperDash.Core
|
||||
/// <summary>
|
||||
/// Kills the stream
|
||||
/// </summary>
|
||||
void KillStream()
|
||||
private void KillStream()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (TheStream != null)
|
||||
if (shellStream != null)
|
||||
{
|
||||
TheStream.DataReceived -= Stream_DataReceived;
|
||||
TheStream.Close();
|
||||
TheStream.Dispose();
|
||||
TheStream = null;
|
||||
shellStream.DataReceived -= Stream_DataReceived;
|
||||
shellStream.Close();
|
||||
shellStream.Dispose();
|
||||
shellStream = null;
|
||||
this.LogDebug("Disconnected stream");
|
||||
}
|
||||
}
|
||||
@@ -393,7 +414,7 @@ namespace PepperDash.Core
|
||||
/// <summary>
|
||||
/// Handles the keyboard interactive authentication, should it be required.
|
||||
/// </summary>
|
||||
void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
|
||||
private void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
|
||||
{
|
||||
foreach (AuthenticationPrompt prompt in e.Prompts)
|
||||
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
|
||||
@@ -403,7 +424,7 @@ namespace PepperDash.Core
|
||||
/// <summary>
|
||||
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
|
||||
/// </summary>
|
||||
void Stream_DataReceived(object sender, ShellDataEventArgs e)
|
||||
private void Stream_DataReceived(object sender, ShellDataEventArgs e)
|
||||
{
|
||||
if (((ShellStream)sender).Length <= 0L)
|
||||
{
|
||||
@@ -416,18 +437,14 @@ namespace PepperDash.Core
|
||||
if (bytesHandler != null)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(response);
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
}
|
||||
this.PrintReceivedBytes(bytes);
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
}
|
||||
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response));
|
||||
this.PrintReceivedText(response);
|
||||
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(response));
|
||||
}
|
||||
@@ -439,7 +456,7 @@ namespace PepperDash.Core
|
||||
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
|
||||
/// event
|
||||
/// </summary>
|
||||
void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
|
||||
private void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
|
||||
{
|
||||
CrestronInvoke.BeginInvoke(o =>
|
||||
{
|
||||
@@ -459,7 +476,7 @@ namespace PepperDash.Core
|
||||
if (AutoReconnect && ConnectEnabled)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
StartReconnectTimer();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -467,7 +484,7 @@ namespace PepperDash.Core
|
||||
/// <summary>
|
||||
/// Helper for ConnectionChange event
|
||||
/// </summary>
|
||||
void OnConnectionChange()
|
||||
private void OnConnectionChange()
|
||||
{
|
||||
ConnectionChange?.Invoke(this, new GenericSocketStatusChageEventArgs(this));
|
||||
}
|
||||
@@ -482,16 +499,12 @@ namespace PepperDash.Core
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Client != null && TheStream != null && IsConnected)
|
||||
if (client != null && shellStream != null && IsConnected)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
this.LogInformation(
|
||||
"Sending {length} characters of text: '{text}'",
|
||||
text.Length,
|
||||
ComTextHelper.GetDebugText(text));
|
||||
this.PrintSentText(text);
|
||||
|
||||
TheStream.Write(text);
|
||||
TheStream.Flush();
|
||||
shellStream.Write(text);
|
||||
shellStream.Flush();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -503,7 +516,7 @@ namespace PepperDash.Core
|
||||
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
ReconnectTimer.Reset();
|
||||
StartReconnectTimer();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -519,13 +532,12 @@ namespace PepperDash.Core
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Client != null && TheStream != null && IsConnected)
|
||||
if (client != null && shellStream != null && IsConnected)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
this.PrintSentBytes(bytes);
|
||||
|
||||
TheStream.Write(bytes, 0, bytes.Length);
|
||||
TheStream.Flush();
|
||||
shellStream.Write(bytes, 0, bytes.Length);
|
||||
shellStream.Flush();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -537,7 +549,7 @@ namespace PepperDash.Core
|
||||
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
ReconnectTimer.Reset();
|
||||
StartReconnectTimer();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -546,6 +558,83 @@ namespace PepperDash.Core
|
||||
}
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//*****************************************************************************************************
|
||||
|
||||
@@ -19,44 +19,44 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as a Byte array
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as a Byte array
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as text
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as text
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
|
||||
|
||||
private string _hostname;
|
||||
private string _hostname;
|
||||
|
||||
/// <summary>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hostname;
|
||||
}
|
||||
get
|
||||
{
|
||||
return _hostname;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_hostname = value;
|
||||
if (_client != null)
|
||||
{
|
||||
_client.AddressClientConnectedTo = _hostname;
|
||||
}
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
_hostname = value;
|
||||
if (_client != null)
|
||||
{
|
||||
_client.AddressClientConnectedTo = _hostname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Port
|
||||
@@ -78,19 +78,19 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The actual client class
|
||||
/// </summary>
|
||||
private TCPClient _client;
|
||||
/// <summary>
|
||||
/// The actual client class
|
||||
/// </summary>
|
||||
private TCPClient _client;
|
||||
|
||||
/// <summary>
|
||||
/// Bool showing if socket is connected
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
/// <summary>
|
||||
/// Bool showing if socket is connected
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for IsConnected
|
||||
/// </summary>
|
||||
@@ -99,15 +99,15 @@ namespace PepperDash.Core
|
||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// _client socket status Read only
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus
|
||||
{
|
||||
get
|
||||
/// <summary>
|
||||
/// _client socket status Read only
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
|
||||
}
|
||||
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -119,26 +119,26 @@ namespace PepperDash.Core
|
||||
get { return (ushort)ClientStatus; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Status text shows the message associated with socket status
|
||||
/// </summary>
|
||||
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
||||
/// </summary>
|
||||
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
||||
|
||||
/// <summary>
|
||||
/// Ushort representation of client status
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Ushort representation of client status
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public ushort UClientStatus { get { return (ushort)ClientStatus; } }
|
||||
public ushort UClientStatus { get { return (ushort)ClientStatus; } }
|
||||
|
||||
/// <summary>
|
||||
/// Connection failure reason
|
||||
/// </summary>
|
||||
public string ConnectionFailure { get { return ClientStatus.ToString(); } }
|
||||
/// <summary>
|
||||
/// Connection failure reason
|
||||
/// </summary>
|
||||
public string ConnectionFailure { get { return ClientStatus.ToString(); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AutoReconnect
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the AutoReconnect
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for AutoReconnect
|
||||
@@ -149,29 +149,29 @@ namespace PepperDash.Core
|
||||
set { AutoReconnect = value == 1; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
/// <summary>
|
||||
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set only when the disconnect method is called
|
||||
/// </summary>
|
||||
bool DisconnectCalledByUser;
|
||||
/// <summary>
|
||||
/// Set only when the disconnect method is called
|
||||
/// </summary>
|
||||
bool DisconnectCalledByUser;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Connected
|
||||
{
|
||||
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Connected
|
||||
{
|
||||
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
|
||||
//Lock object to prevent simulatneous connect/disconnect operations
|
||||
private CCriticalSection connectLock = new CCriticalSection();
|
||||
|
||||
// private Timer for auto reconnect
|
||||
private CTimer RetryTimer;
|
||||
private CTimer RetryTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
@@ -181,8 +181,8 @@ namespace PepperDash.Core
|
||||
/// <param name="port"></param>
|
||||
/// <param name="bufferSize"></param>
|
||||
public GenericTcpIpClient(string key, string address, int port, int bufferSize)
|
||||
: base(key)
|
||||
{
|
||||
: base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
@@ -218,18 +218,18 @@ namespace PepperDash.Core
|
||||
/// Default constructor for S+
|
||||
/// </summary>
|
||||
public GenericTcpIpClient()
|
||||
: base(SplusKey)
|
||||
: base(SplusKey)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
BufferSize = 2000;
|
||||
|
||||
RetryTimer = new CTimer(o =>
|
||||
{
|
||||
Reconnect();
|
||||
}, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize method
|
||||
@@ -255,26 +255,26 @@ namespace PepperDash.Core
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <summary>
|
||||
/// Deactivate method
|
||||
/// </summary>
|
||||
public override bool Deactivate()
|
||||
{
|
||||
/// <summary>
|
||||
/// Deactivate method
|
||||
/// </summary>
|
||||
public override bool Deactivate()
|
||||
{
|
||||
RetryTimer.Stop();
|
||||
RetryTimer.Dispose();
|
||||
if (_client != null)
|
||||
{
|
||||
_client.SocketStatusChange -= this.Client_SocketStatusChange;
|
||||
_client.SocketStatusChange -= this.Client_SocketStatusChange;
|
||||
DisconnectClient();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect method
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
/// <summary>
|
||||
/// Connect method
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Hostname))
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key);
|
||||
@@ -310,7 +310,7 @@ namespace PepperDash.Core
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Reconnect()
|
||||
{
|
||||
@@ -337,11 +337,11 @@ namespace PepperDash.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect method
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
/// <summary>
|
||||
/// Disconnect method
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
@@ -355,7 +355,7 @@ namespace PepperDash.Core
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DisconnectClient method
|
||||
@@ -375,7 +375,7 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
/// <param name="c"></param>
|
||||
void ConnectToServerCallback(TCPClient c)
|
||||
{
|
||||
{
|
||||
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus);
|
||||
@@ -385,13 +385,13 @@ namespace PepperDash.Core
|
||||
{
|
||||
Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects, waits and attemtps to connect again
|
||||
/// </summary>
|
||||
void WaitAndTryReconnect()
|
||||
{
|
||||
{
|
||||
CrestronInvoke.BeginInvoke(o =>
|
||||
{
|
||||
try
|
||||
@@ -409,7 +409,7 @@ namespace PepperDash.Core
|
||||
connectLock.Leave();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recieves incoming data
|
||||
@@ -417,7 +417,7 @@ namespace PepperDash.Core
|
||||
/// <param name="client"></param>
|
||||
/// <param name="numBytes"></param>
|
||||
void Receive(TCPClient client, int numBytes)
|
||||
{
|
||||
{
|
||||
if (client != null)
|
||||
{
|
||||
if (numBytes > 0)
|
||||
@@ -426,10 +426,7 @@ namespace PepperDash.Core
|
||||
var bytesHandler = BytesReceived;
|
||||
if (bytesHandler != null)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
}
|
||||
this.PrintReceivedBytes(bytes);
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
}
|
||||
var textHandler = TextReceived;
|
||||
@@ -437,58 +434,53 @@ namespace PepperDash.Core
|
||||
{
|
||||
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||
}
|
||||
this.PrintReceivedText(str);
|
||||
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||
}
|
||||
}
|
||||
}
|
||||
client.ReceiveDataAsync(Receive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SendText method
|
||||
/// </summary>
|
||||
public void SendText(string text)
|
||||
{
|
||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||
// Check debug level before processing byte array
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||
/// <summary>
|
||||
/// SendText method
|
||||
/// </summary>
|
||||
public void SendText(string text)
|
||||
{
|
||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||
// Check debug level before processing byte array
|
||||
this.PrintSentText(text);
|
||||
if (_client != null)
|
||||
_client.SendData(bytes, bytes.Length);
|
||||
}
|
||||
_client.SendData(bytes, bytes.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SendEscapedText method
|
||||
/// </summary>
|
||||
public void SendEscapedText(string text)
|
||||
{
|
||||
var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s =>
|
||||
{
|
||||
var hex = s.Groups[1].Value;
|
||||
return ((char)Convert.ToByte(hex, 16)).ToString();
|
||||
});
|
||||
SendText(unescapedText);
|
||||
}
|
||||
/// <summary>
|
||||
/// SendEscapedText method
|
||||
/// </summary>
|
||||
public void SendEscapedText(string text)
|
||||
{
|
||||
var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s =>
|
||||
{
|
||||
var hex = s.Groups[1].Value;
|
||||
return ((char)Convert.ToByte(hex, 16)).ToString();
|
||||
});
|
||||
SendText(unescapedText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends Bytes to the server
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <summary>
|
||||
/// SendBytes method
|
||||
/// </summary>
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
/// <summary>
|
||||
/// SendBytes method
|
||||
/// </summary>
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
this.PrintSentBytes(bytes);
|
||||
if (_client != null)
|
||||
_client.SendData(bytes, bytes.Length);
|
||||
}
|
||||
_client.SendData(bytes, bytes.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Socket Status Change Handler
|
||||
@@ -496,7 +488,7 @@ namespace PepperDash.Core
|
||||
/// <param name="client"></param>
|
||||
/// <param name="clientSocketStatus"></param>
|
||||
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
|
||||
{
|
||||
{
|
||||
if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||
@@ -505,68 +497,73 @@ namespace PepperDash.Core
|
||||
else
|
||||
{
|
||||
Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||
_client.ReceiveDataAsync(Receive);
|
||||
_client.ReceiveDataAsync(Receive);
|
||||
}
|
||||
|
||||
var handler = ConnectionChange;
|
||||
if (handler != null)
|
||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
||||
}
|
||||
}
|
||||
var handler = ConnectionChange;
|
||||
if (handler != null)
|
||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a TcpSshPropertiesConfig
|
||||
/// </summary>
|
||||
public class TcpSshPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a TcpSshPropertiesConfig
|
||||
/// </summary>
|
||||
public class TcpSshPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Address to connect to
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string Address { get; set; }
|
||||
|
||||
public string Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Port to connect to
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public int Port { get; set; }
|
||||
|
||||
[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; }
|
||||
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>
|
||||
/// 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 AutoReconnect
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AutoReconnectIntervalMs
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the AutoReconnectIntervalMs
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, turns off echo for the SSH session
|
||||
/// </summary>
|
||||
[JsonProperty("disableSshEcho")]
|
||||
public bool DisableSshEcho { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public TcpSshPropertiesConfig()
|
||||
{
|
||||
BufferSize = 32768;
|
||||
AutoReconnect = true;
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
{
|
||||
BufferSize = 32768;
|
||||
AutoReconnect = true;
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
Username = "";
|
||||
Password = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DisableSshEcho = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace PepperDash.Core
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -135,7 +135,7 @@ namespace PepperDash.Core
|
||||
public GenericUdpServer(string key, string address, int port, int bufferSize)
|
||||
: base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
Hostname = address;
|
||||
Port = port;
|
||||
BufferSize = bufferSize;
|
||||
@@ -180,7 +180,7 @@ namespace PepperDash.Core
|
||||
/// <param name="programEventType"></param>
|
||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
{
|
||||
if (programEventType != eProgramStatusEventType.Stopping)
|
||||
if (programEventType != eProgramStatusEventType.Stopping)
|
||||
return;
|
||||
|
||||
Debug.Console(1, this, "Program stopping. Disabling Server");
|
||||
@@ -243,7 +243,7 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
if(Server != null)
|
||||
if (Server != null)
|
||||
Server.DisableUDPServer();
|
||||
|
||||
IsConnected = false;
|
||||
@@ -265,7 +265,7 @@ namespace PepperDash.Core
|
||||
|
||||
try
|
||||
{
|
||||
if (numBytes <= 0)
|
||||
if (numBytes <= 0)
|
||||
return;
|
||||
|
||||
var sourceIp = Server.IPAddressLastMessageReceivedFrom;
|
||||
@@ -281,17 +281,13 @@ namespace PepperDash.Core
|
||||
var bytesHandler = BytesReceived;
|
||||
if (bytesHandler != null)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
}
|
||||
this.PrintReceivedBytes(bytes);
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
}
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||
this.PrintReceivedText(str);
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||
}
|
||||
}
|
||||
@@ -318,8 +314,7 @@ namespace PepperDash.Core
|
||||
|
||||
if (IsConnected && Server != null)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||
this.PrintSentText(text);
|
||||
|
||||
Server.SendData(bytes, bytes.Length);
|
||||
}
|
||||
@@ -334,8 +329,7 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
this.PrintSentBytes(bytes);
|
||||
|
||||
if (IsConnected && Server != null)
|
||||
Server.SendData(bytes, bytes.Length);
|
||||
@@ -343,11 +337,11 @@ namespace PepperDash.Core
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a GenericUdpReceiveTextExtraArgs
|
||||
/// </summary>
|
||||
public class GenericUdpReceiveTextExtraArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a GenericUdpReceiveTextExtraArgs
|
||||
/// </summary>
|
||||
public class GenericUdpReceiveTextExtraArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -359,7 +353,7 @@ namespace PepperDash.Core
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int Port { get; private set; }
|
||||
public int Port { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -373,18 +367,18 @@ namespace PepperDash.Core
|
||||
/// <param name="port"></param>
|
||||
/// <param name="bytes"></param>
|
||||
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
|
||||
{
|
||||
Text = text;
|
||||
IpAddress = ipAddress;
|
||||
Port = port;
|
||||
Bytes = bytes;
|
||||
}
|
||||
{
|
||||
Text = text;
|
||||
IpAddress = ipAddress;
|
||||
Port = port;
|
||||
Bytes = bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stupid S+ Constructor
|
||||
/// </summary>
|
||||
public GenericUdpReceiveTextExtraArgs() { }
|
||||
}
|
||||
/// <summary>
|
||||
/// Stupid S+ Constructor
|
||||
/// </summary>
|
||||
public GenericUdpReceiveTextExtraArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
|
||||
69
src/PepperDash.Core/Comm/StreamDebuggingExtensions.cs
Normal file
69
src/PepperDash.Core/Comm/StreamDebuggingExtensions.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
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)}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,10 @@ namespace PepperDash.Core
|
||||
/// <summary>
|
||||
/// Used when comms needs to be handled in SIMPL and bridged opposite the normal direction
|
||||
/// </summary>
|
||||
ComBridge
|
||||
ComBridge,
|
||||
/// <summary>
|
||||
/// InfinetEX control
|
||||
/// </summary>
|
||||
InfinetEx
|
||||
}
|
||||
}
|
||||
24
src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs
Normal file
24
src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
28
src/PepperDash.Core/Comm/eStreamDebuggingSetting.cs
Normal file
28
src/PepperDash.Core/Comm/eStreamDebuggingSetting.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Core
|
||||
@@ -42,7 +39,7 @@ namespace PepperDash.Core
|
||||
/// Defines the contract for IBasicCommunication
|
||||
/// </summary>
|
||||
public interface IBasicCommunication : ICommunicationReceiver
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Send text to the device
|
||||
/// </summary>
|
||||
@@ -54,7 +51,7 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
void SendBytes(byte[] bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a device that implements IBasicCommunication and IStreamDebugging
|
||||
@@ -67,7 +64,7 @@ namespace PepperDash.Core
|
||||
/// <summary>
|
||||
/// Represents a device with stream debugging capablities
|
||||
/// </summary>
|
||||
public interface IStreamDebugging
|
||||
public interface IStreamDebugging : IKeyed
|
||||
{
|
||||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
@@ -76,12 +73,12 @@ namespace PepperDash.Core
|
||||
CommunicationStreamDebugging StreamDebugging { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
|
||||
/// GenericTcpIpClient
|
||||
/// </summary>
|
||||
public interface ISocketStatus : IBasicCommunication
|
||||
{
|
||||
/// <summary>
|
||||
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
|
||||
/// GenericTcpIpClient
|
||||
/// </summary>
|
||||
public interface ISocketStatus : IBasicCommunication
|
||||
{
|
||||
/// <summary>
|
||||
/// Notifies of socket status changes
|
||||
/// </summary>
|
||||
@@ -93,7 +90,7 @@ namespace PepperDash.Core
|
||||
[JsonProperty("clientStatus")]
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
SocketStatus ClientStatus { get; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a device that implements ISocketStatus and IStreamDebugging
|
||||
@@ -107,24 +104,24 @@ namespace PepperDash.Core
|
||||
/// Describes a device that can automatically attempt to reconnect
|
||||
/// </summary>
|
||||
public interface IAutoReconnect
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Enable automatic recconnect
|
||||
/// </summary>
|
||||
[JsonProperty("autoReconnect")]
|
||||
bool AutoReconnect { get; set; }
|
||||
bool AutoReconnect { get; set; }
|
||||
/// <summary>
|
||||
/// Interval in ms to attempt automatic recconnections
|
||||
/// </summary>
|
||||
[JsonProperty("autoReconnectIntervalMs")]
|
||||
int AutoReconnectIntervalMs { get; set; }
|
||||
}
|
||||
int AutoReconnectIntervalMs { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum eGenericCommMethodStatusChangeType
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum eGenericCommMethodStatusChangeType
|
||||
{
|
||||
/// <summary>
|
||||
/// Connected
|
||||
/// </summary>
|
||||
@@ -133,45 +130,45 @@ namespace PepperDash.Core
|
||||
/// Disconnected
|
||||
/// </summary>
|
||||
Disconnected
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This delegate defines handler for IBasicCommunication status changes
|
||||
/// </summary>
|
||||
/// <param name="comm">Device firing the status change</param>
|
||||
/// <param name="status"></param>
|
||||
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
|
||||
/// <summary>
|
||||
/// This delegate defines handler for IBasicCommunication status changes
|
||||
/// </summary>
|
||||
/// <param name="comm">Device firing the status change</param>
|
||||
/// <param name="status"></param>
|
||||
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericCommMethodReceiveBytesArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Bytes
|
||||
/// </summary>
|
||||
public byte[] Bytes { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericCommMethodReceiveBytesArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Bytes
|
||||
/// </summary>
|
||||
public byte[] Bytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
|
||||
{
|
||||
Bytes = bytes;
|
||||
}
|
||||
{
|
||||
Bytes = bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericCommMethodReceiveBytesArgs() { }
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericCommMethodReceiveBytesArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericCommMethodReceiveTextArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericCommMethodReceiveTextArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -185,9 +182,9 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public GenericCommMethodReceiveTextArgs(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -195,59 +192,14 @@ namespace PepperDash.Core
|
||||
/// <param name="text"></param>
|
||||
/// <param name="delimiter"></param>
|
||||
public GenericCommMethodReceiveTextArgs(string text, string delimiter)
|
||||
:this(text)
|
||||
: this(text)
|
||||
{
|
||||
Delimiter = delimiter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericCommMethodReceiveTextArgs() { }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ComTextHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets escaped text for a byte array
|
||||
/// S+ Constructor
|
||||
/// </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));
|
||||
}
|
||||
}
|
||||
public GenericCommMethodReceiveTextArgs() { }
|
||||
}
|
||||
}
|
||||
@@ -168,7 +168,7 @@ namespace PepperDash.Core
|
||||
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
|
||||
rollingInterval: RollingInterval.Day,
|
||||
restrictedToMinimumLevel: LogEventLevel.Debug,
|
||||
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
|
||||
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 7 : 14,
|
||||
levelSwitch: _fileLogLevelSwitch
|
||||
);
|
||||
|
||||
@@ -1081,9 +1081,6 @@ namespace PepperDash.Core
|
||||
/// Logs to Console when at-level, and all messages to error log
|
||||
/// </summary>
|
||||
[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,
|
||||
string format, params object[] items)
|
||||
{
|
||||
@@ -1096,9 +1093,6 @@ namespace PepperDash.Core
|
||||
/// it will only be written to the log.
|
||||
/// </summary>
|
||||
[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)
|
||||
{
|
||||
LogMessage(level, format, items);
|
||||
|
||||
@@ -2,25 +2,29 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.DeviceSupport;
|
||||
using Crestron.SimplSharpPro.EthernetCommunication;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using Serilog.Events;
|
||||
|
||||
//using PepperDash.Essentials.Devices.Common.Cameras;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for bridge API variants
|
||||
/// </summary>
|
||||
[Obsolete("Will be removed in v3.0.0")]
|
||||
public abstract class BridgeApi : EssentialsDevice
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key">Device key</param>
|
||||
protected BridgeApi(string key) :
|
||||
base(key)
|
||||
{
|
||||
@@ -29,23 +33,36 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a EiscApiAdvanced
|
||||
/// Class to link devices and rooms to an EISC Instance
|
||||
/// </summary>
|
||||
public class EiscApiAdvanced : BridgeApi, ICommunicationMonitor
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the PropertiesConfig
|
||||
/// </summary>
|
||||
public EiscApiPropertiesConfig PropertiesConfig { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the JoinMaps dictionary
|
||||
/// </summary>
|
||||
public Dictionary<string, JoinMapBaseAdvanced> JoinMaps { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the EISC instance
|
||||
/// </summary>
|
||||
public BasicTriList Eisc { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="dc">Device configuration</param>
|
||||
/// <param name="eisc">EISC instance</param>
|
||||
public EiscApiAdvanced(DeviceConfig dc, BasicTriList eisc) :
|
||||
base(dc.Key)
|
||||
{
|
||||
JoinMaps = new Dictionary<string, JoinMapBaseAdvanced>();
|
||||
|
||||
PropertiesConfig = dc.Properties.ToObject<EiscApiPropertiesConfig>();
|
||||
//PropertiesConfig = JsonConvert.DeserializeObject<EiscApiPropertiesConfig>(dc.Properties.ToString());
|
||||
|
||||
Eisc = eisc;
|
||||
|
||||
@@ -60,8 +77,7 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
|
||||
/// <summary>
|
||||
/// CustomActivate method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
/// </summary>
|
||||
public override bool CustomActivate()
|
||||
{
|
||||
CommunicationMonitor.Start();
|
||||
@@ -83,7 +99,7 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
|
||||
if (PropertiesConfig.Devices == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "No devices linked to this bridge");
|
||||
this.LogDebug("No devices linked to this bridge");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,9 +120,7 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, this,
|
||||
"{0} is not compatible with this bridge type. Please use 'eiscapi' instead, or updae the device.",
|
||||
device.Key);
|
||||
this.LogWarning("{deviceKey} is not compatible with this bridge type. Please update the device.", device.Key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,34 +135,31 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
|
||||
if (registerResult != eDeviceRegistrationUnRegistrationResponse.Success)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Registration result: {0}", registerResult);
|
||||
this.LogVerbose("Registration result: {registerResult}", registerResult);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "EISC registration successful");
|
||||
this.LogDebug("EISC registration successful");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// LinkRooms method
|
||||
/// Link rooms to this EISC. Rooms MUST implement IBridgeAdvanced
|
||||
/// </summary>
|
||||
public void LinkRooms()
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Linking Rooms...");
|
||||
this.LogDebug("Linking Rooms...");
|
||||
|
||||
if (PropertiesConfig.Rooms == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "No rooms linked to this bridge.");
|
||||
this.LogDebug("No rooms linked to this bridge.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var room in PropertiesConfig.Rooms)
|
||||
{
|
||||
var rm = DeviceManager.GetDeviceForKey(room.RoomKey) as IBridgeAdvanced;
|
||||
|
||||
if (rm == null)
|
||||
if (!(DeviceManager.GetDeviceForKey(room.RoomKey) is IBridgeAdvanced rm))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this,
|
||||
"Room {0} does not implement IBridgeAdvanced. Skipping...", room.RoomKey);
|
||||
this.LogDebug("Room {roomKey} does not implement IBridgeAdvanced. Skipping...", room.RoomKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -159,11 +170,8 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
/// <summary>
|
||||
/// Adds a join map
|
||||
/// </summary>
|
||||
/// <param name="deviceKey"></param>
|
||||
/// <param name="joinMap"></param>
|
||||
/// <summary>
|
||||
/// AddJoinMap method
|
||||
/// </summary>
|
||||
/// <param name="deviceKey">The key of the device to add the join map for</param>
|
||||
/// <param name="joinMap">The join map to add</param>
|
||||
public void AddJoinMap(string deviceKey, JoinMapBaseAdvanced joinMap)
|
||||
{
|
||||
if (!JoinMaps.ContainsKey(deviceKey))
|
||||
@@ -172,14 +180,13 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Unable to add join map with key '{0}'. Key already exists in JoinMaps dictionary", deviceKey);
|
||||
this.LogWarning("Unable to add join map with key '{deviceKey}'. Key already exists in JoinMaps dictionary", deviceKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PrintJoinMaps method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
/// </summary>
|
||||
public virtual void PrintJoinMaps()
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Join Maps for EISC IPID: {0}\r\n", Eisc.ID.ToString("X"));
|
||||
@@ -190,17 +197,17 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
joinMap.Value.PrintJoinMapInfo();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MarkdownForBridge method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
/// </summary>
|
||||
public virtual void MarkdownForBridge(string bridgeKey)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Writing Joinmaps to files for EISC IPID: {0}", Eisc.ID.ToString("X"));
|
||||
this.LogInformation("Writing Joinmaps to files for EISC IPID: {eiscId}", Eisc.ID.ToString("X"));
|
||||
|
||||
foreach (var joinMap in JoinMaps)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Generating markdown for device '{0}':", joinMap.Key);
|
||||
this.LogInformation("Generating markdown for device '{deviceKey}':", joinMap.Key);
|
||||
joinMap.Value.MarkdownJoinMapInfo(joinMap.Key, bridgeKey);
|
||||
}
|
||||
}
|
||||
@@ -208,53 +215,45 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
/// <summary>
|
||||
/// Prints the join map for a device by key
|
||||
/// </summary>
|
||||
/// <param name="deviceKey"></param>
|
||||
/// <summary>
|
||||
/// PrintJoinMapForDevice method
|
||||
/// </summary>
|
||||
/// <param name="deviceKey">The key of the device to print the join map for</param>
|
||||
public void PrintJoinMapForDevice(string deviceKey)
|
||||
{
|
||||
var joinMap = JoinMaps[deviceKey];
|
||||
|
||||
if (joinMap == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Unable to find joinMap for device with key: '{0}'", deviceKey);
|
||||
this.LogInformation("Unable to find joinMap for device with key: '{deviceKey}'", deviceKey);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key);
|
||||
this.LogInformation("Join map for device '{deviceKey}' on EISC '{eiscKey}':", deviceKey, Key);
|
||||
joinMap.PrintJoinMapInfo();
|
||||
}
|
||||
/// <summary>
|
||||
/// Prints the join map for a device by key
|
||||
/// </summary>
|
||||
/// <param name="deviceKey"></param>
|
||||
/// <summary>
|
||||
/// MarkdownJoinMapForDevice method
|
||||
/// Prints the join map for a device by key in Markdown format
|
||||
/// </summary>
|
||||
/// <param name="deviceKey">The key of the device to print the join map for</param>
|
||||
/// <param name="bridgeKey">The key of the bridge to use for the Markdown output</param>
|
||||
public void MarkdownJoinMapForDevice(string deviceKey, string bridgeKey)
|
||||
{
|
||||
var joinMap = JoinMaps[deviceKey];
|
||||
|
||||
if (joinMap == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Unable to find joinMap for device with key: '{0}'", deviceKey);
|
||||
this.LogInformation("Unable to find joinMap for device with key: '{deviceKey}'", deviceKey);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key);
|
||||
this.LogInformation("Join map for device '{deviceKey}' on EISC '{eiscKey}':", deviceKey, Key);
|
||||
joinMap.MarkdownJoinMapInfo(deviceKey, bridgeKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for debugging to trigger an action based on a join number and type
|
||||
/// </summary>
|
||||
/// <param name="join"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="state"></param>
|
||||
/// <summary>
|
||||
/// ExecuteJoinAction method
|
||||
/// </summary>
|
||||
/// <param name="join">The join number to execute the action for</param>
|
||||
/// <param name="type">The type of join (digital, analog, serial)</param>
|
||||
/// <param name="state">The state to pass to the action</param>
|
||||
public void ExecuteJoinAction(uint join, string type, object state)
|
||||
{
|
||||
try
|
||||
@@ -263,78 +262,87 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
case "digital":
|
||||
{
|
||||
var uo = Eisc.BooleanOutput[join].UserObject as Action<bool>;
|
||||
if (uo != null)
|
||||
if (Eisc.BooleanOutput[join].UserObject is Action<bool> userObject)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
|
||||
uo(Convert.ToBoolean(state));
|
||||
this.LogVerbose("Executing Boolean Action");
|
||||
userObject(Convert.ToBoolean(state));
|
||||
}
|
||||
else
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute");
|
||||
this.LogVerbose("User Object is null. Nothing to Execute");
|
||||
break;
|
||||
}
|
||||
case "analog":
|
||||
{
|
||||
var uo = Eisc.BooleanOutput[join].UserObject as Action<ushort>;
|
||||
if (uo != null)
|
||||
if (Eisc.UShortOutput[join].UserObject is Action<ushort> userObject)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
|
||||
uo(Convert.ToUInt16(state));
|
||||
this.LogVerbose("Executing Analog Action");
|
||||
userObject(Convert.ToUInt16(state));
|
||||
}
|
||||
else
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute"); break;
|
||||
this.LogVerbose("User Object is null. Nothing to Execute");
|
||||
break;
|
||||
}
|
||||
case "serial":
|
||||
{
|
||||
var uo = Eisc.BooleanOutput[join].UserObject as Action<string>;
|
||||
if (uo != null)
|
||||
if (Eisc.StringOutput[join].UserObject is Action<string> userObject)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
|
||||
uo(Convert.ToString(state));
|
||||
this.LogVerbose("Executing Serial Action");
|
||||
userObject(Convert.ToString(state));
|
||||
}
|
||||
else
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute");
|
||||
this.LogVerbose("User Object is null. Nothing to Execute");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Unknown join type. Use digital/serial/analog");
|
||||
this.LogVerbose("Unknown join type. Use digital/serial/analog");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Error: {0}", e);
|
||||
this.LogError("ExecuteJoinAction error: {message}", e.Message);
|
||||
this.LogDebug(e, "Stack Trace: ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles incoming sig changes
|
||||
/// Handle incoming sig changes
|
||||
/// </summary>
|
||||
/// <param name="currentDevice"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <param name="currentDevice">BasicTriList device that triggered the event</param>
|
||||
/// <param name="args">Event arguments containing the signal information</param>
|
||||
protected void Eisc_SigChange(object currentDevice, SigEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "EiscApiAdvanced change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
|
||||
var uo = args.Sig.UserObject;
|
||||
this.LogVerbose("EiscApiAdvanced change: {type} {number}={value}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
|
||||
var userObject = args.Sig.UserObject;
|
||||
|
||||
if (uo == null) return;
|
||||
if (userObject == null) return;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Executing Action: {0}", uo.ToString());
|
||||
if (uo is Action<bool>)
|
||||
(uo as Action<bool>)(args.Sig.BoolValue);
|
||||
else if (uo is Action<ushort>)
|
||||
(uo as Action<ushort>)(args.Sig.UShortValue);
|
||||
else if (uo is Action<string>)
|
||||
(uo as Action<string>)(args.Sig.StringValue);
|
||||
|
||||
if (userObject is Action<bool>)
|
||||
{
|
||||
this.LogDebug("Executing Boolean Action");
|
||||
(userObject as Action<bool>)(args.Sig.BoolValue);
|
||||
}
|
||||
else if (userObject is Action<ushort>)
|
||||
{
|
||||
this.LogDebug("Executing Analog Action");
|
||||
(userObject as Action<ushort>)(args.Sig.UShortValue);
|
||||
}
|
||||
else if (userObject is Action<string>)
|
||||
{
|
||||
this.LogDebug("Executing Serial Action");
|
||||
(userObject as Action<string>)(args.Sig.StringValue);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Error in Eisc_SigChange handler: {0}", e);
|
||||
this.LogError("Eisc_SigChange handler error: {message}", e.Message);
|
||||
this.LogDebug(e, "Stack Trace: ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,22 +431,33 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a EiscApiAdvancedFactory
|
||||
/// Factory class for EiscApiAdvanced devices
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Supported types:
|
||||
/// eiscapiadv - Create a standard EISC client over TCP/IP
|
||||
/// eiscapiadvanced - Create a standard EISC client over TCP/IP
|
||||
/// eiscapiadvancedserver - Create an EISC server
|
||||
/// eiscapiadvancedclient - Create an EISC client
|
||||
/// vceiscapiadv - Create a VC-4 EISC client
|
||||
/// vceiscapiadvanced - Create a VC-4 EISC client
|
||||
/// eiscapiadvudp - Create a standard EISC client over UDP
|
||||
/// eiscapiadvancedudp - Create a standard EISC client over UDP
|
||||
/// </remarks>
|
||||
public class EiscApiAdvancedFactory : EssentialsDeviceFactory<EiscApiAdvanced>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public EiscApiAdvancedFactory()
|
||||
{
|
||||
TypeNames = new List<string> { "eiscapiadv", "eiscapiadvanced", "eiscapiadvancedserver", "eiscapiadvancedclient", "vceiscapiadv", "vceiscapiadvanced" };
|
||||
TypeNames = new List<string> { "eiscapiadv", "eiscapiadvanced", "eiscapiadvancedserver", "eiscapiadvancedclient", "vceiscapiadv", "vceiscapiadvanced", "eiscapiadvudp", "eiscapiadvancedudp" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BuildDevice method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new EiscApiAdvanced Device");
|
||||
Debug.LogDebug("Attempting to create new EiscApiAdvanced Device");
|
||||
|
||||
var controlProperties = CommFactory.GetControlPropertiesConfig(dc);
|
||||
|
||||
@@ -446,6 +465,13 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
|
||||
switch (dc.Type.ToLower())
|
||||
{
|
||||
case "eiscapiadvudp":
|
||||
case "eiscapiadvancedudp":
|
||||
{
|
||||
eisc = new EthernetIntersystemCommunications(controlProperties.IpIdInt,
|
||||
controlProperties.TcpSshProperties.Address, Global.ControlSystem);
|
||||
break;
|
||||
}
|
||||
case "eiscapiadv":
|
||||
case "eiscapiadvanced":
|
||||
{
|
||||
@@ -468,7 +494,7 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
if (string.IsNullOrEmpty(controlProperties.RoomId))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to build VC-4 EISC Client for device {0}. Room ID is missing or empty", dc.Key);
|
||||
Debug.LogInformation("Unable to build VC-4 EISC Client for device {deviceKey}. Room ID is missing or empty", dc.Key);
|
||||
eisc = null;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -12,15 +12,15 @@ using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a CecPortController
|
||||
/// </summary>
|
||||
public class CecPortController : Device, IBasicCommunicationWithStreamDebugging
|
||||
/// <summary>
|
||||
/// Represents a CecPortController
|
||||
/// </summary>
|
||||
public class CecPortController : Device, IBasicCommunicationWithStreamDebugging
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the StreamDebugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the StreamDebugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
@@ -33,16 +33,16 @@ namespace PepperDash.Essentials.Core
|
||||
ICec Port;
|
||||
|
||||
public CecPortController(string key, Func<EssentialsControlPropertiesConfig, ICec> postActivationFunc,
|
||||
EssentialsControlPropertiesConfig config):base(key)
|
||||
EssentialsControlPropertiesConfig config) : base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
|
||||
AddPostActivationAction(() =>
|
||||
{
|
||||
Port = postActivationFunc(config);
|
||||
|
||||
Port.StreamCec.CecChange += StreamCec_CecChange;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public CecPortController(string key, ICec port)
|
||||
@@ -58,27 +58,25 @@ namespace PepperDash.Essentials.Core
|
||||
if (args.EventId == CecEventIds.CecMessageReceivedEventId)
|
||||
OnDataReceived(cecDevice.Received.StringValue);
|
||||
else if (args.EventId == CecEventIds.ErrorFeedbackEventId)
|
||||
if(cecDevice.ErrorFeedback.BoolValue)
|
||||
if (cecDevice.ErrorFeedback.BoolValue)
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "CEC NAK Error");
|
||||
}
|
||||
|
||||
void OnDataReceived(string s)
|
||||
{
|
||||
var bytesHandler = BytesReceived;
|
||||
var bytesHandler = BytesReceived;
|
||||
if (bytesHandler != null)
|
||||
{
|
||||
var bytes = Encoding.GetEncoding(28591).GetBytes(s);
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes));
|
||||
this.PrintReceivedBytes(bytes);
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
}
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
|
||||
}
|
||||
if (textHandler != null)
|
||||
{
|
||||
this.PrintReceivedText(s);
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
|
||||
}
|
||||
}
|
||||
|
||||
#region IBasicCommunication Members
|
||||
@@ -90,8 +88,7 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
if (Port == null)
|
||||
return;
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text);
|
||||
this.PrintSentText(text);
|
||||
Port.StreamCec.Send.StringValue = text;
|
||||
}
|
||||
|
||||
@@ -103,8 +100,8 @@ namespace PepperDash.Essentials.Core
|
||||
if (Port == null)
|
||||
return;
|
||||
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
this.PrintSentBytes(bytes);
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
Port.StreamCec.Send.StringValue = text;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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>
|
||||
/// <summary>
|
||||
/// Represents a ComPortController
|
||||
/// </summary>
|
||||
public class ComPortController : Device, IBasicCommunicationWithStreamDebugging
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the StreamDebugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the StreamDebugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
|
||||
/// <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>
|
||||
/// <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);
|
||||
/// <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;
|
||||
Spec = spec;
|
||||
|
||||
AddPostActivationAction(() =>
|
||||
{
|
||||
Port = postActivationFunc(config);
|
||||
AddPostActivationAction(() =>
|
||||
{
|
||||
Port = postActivationFunc(config);
|
||||
|
||||
RegisterAndConfigureComPort();
|
||||
});
|
||||
}
|
||||
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");
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Invalid com port, continuing but comms will not function");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -64,71 +83,77 @@ namespace PepperDash.Essentials.Core
|
||||
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
|
||||
}
|
||||
}
|
||||
private void RegisterAndConfigureComPort()
|
||||
{
|
||||
if (Port == null)
|
||||
{
|
||||
this.LogInformation($"Configured {Port.Parent.GetType().Name}-comport-{Port.ID} for {Key} does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
var specResult = Port.SetComPortSpec(Spec);
|
||||
if (specResult != 0)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Cannot set comspec");
|
||||
return;
|
||||
}
|
||||
Port.SerialDataReceived += Port_SerialDataReceived;
|
||||
}
|
||||
|
||||
~ComPortController()
|
||||
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);
|
||||
OnDataReceived(args.SerialData);
|
||||
}
|
||||
|
||||
void OnDataReceived(string s)
|
||||
{
|
||||
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));
|
||||
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)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
|
||||
}
|
||||
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");
|
||||
}
|
||||
if (!eventSubscribed) Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivate method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Deactivate method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override bool Deactivate()
|
||||
{
|
||||
return Port.UnRegister() == eDeviceRegistrationUnRegistrationResponse.Success;
|
||||
@@ -136,70 +161,68 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
#region IBasicCommunication Members
|
||||
|
||||
/// <summary>
|
||||
/// SendText method
|
||||
/// </summary>
|
||||
/// <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);
|
||||
this.PrintSentText(text);
|
||||
Port.Send(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SendBytes method
|
||||
/// </summary>
|
||||
/// <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));
|
||||
this.PrintSentBytes(bytes);
|
||||
|
||||
Port.Send(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect method
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Connect method
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect method
|
||||
/// </summary>
|
||||
/// <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);
|
||||
}
|
||||
/// <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());
|
||||
}
|
||||
OnDataReceived(b.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,11 @@ namespace PepperDash.Essentials.Core
|
||||
break;
|
||||
case eControlMethod.Ssh:
|
||||
{
|
||||
var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password);
|
||||
ssh.AutoReconnect = c.AutoReconnect;
|
||||
var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password)
|
||||
{
|
||||
AutoReconnect = c.AutoReconnect,
|
||||
DisableEcho = c.DisableSshEcho
|
||||
};
|
||||
if (ssh.AutoReconnect)
|
||||
ssh.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
|
||||
comm = ssh;
|
||||
@@ -73,8 +76,10 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
case eControlMethod.Tcpip:
|
||||
{
|
||||
var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize);
|
||||
tcp.AutoReconnect = c.AutoReconnect;
|
||||
var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize)
|
||||
{
|
||||
AutoReconnect = c.AutoReconnect
|
||||
};
|
||||
if (tcp.AutoReconnect)
|
||||
tcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
|
||||
comm = tcp;
|
||||
@@ -90,8 +95,10 @@ namespace PepperDash.Essentials.Core
|
||||
break;
|
||||
case eControlMethod.SecureTcpIp:
|
||||
{
|
||||
var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize);
|
||||
secureTcp.AutoReconnect = c.AutoReconnect;
|
||||
var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize)
|
||||
{
|
||||
AutoReconnect = c.AutoReconnect
|
||||
};
|
||||
if (secureTcp.AutoReconnect)
|
||||
secureTcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
|
||||
comm = secureTcp;
|
||||
|
||||
@@ -11,11 +11,11 @@ using PepperDash.Core;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads the ConfigObject from the file
|
||||
/// </summary>
|
||||
public class EssentialsConfig : BasicConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads the ConfigObject from the file
|
||||
/// </summary>
|
||||
public class EssentialsConfig : BasicConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the SystemUrl
|
||||
/// </summary>
|
||||
@@ -32,24 +32,33 @@ namespace PepperDash.Essentials.Core.Config
|
||||
/// Gets the SystemUuid extracted from the SystemUrl
|
||||
/// </summary>
|
||||
[JsonProperty("systemUuid")]
|
||||
public string SystemUuid
|
||||
public string SystemUuid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(SystemUrl))
|
||||
return "missing url";
|
||||
string uuid;
|
||||
|
||||
if (SystemUrl.Contains("#"))
|
||||
{
|
||||
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
|
||||
string uuid = result.Groups[1].Value;
|
||||
return uuid;
|
||||
} else
|
||||
{
|
||||
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*");
|
||||
string uuid = result.Groups[1].Value;
|
||||
return uuid;
|
||||
if (string.IsNullOrEmpty(SystemUrl))
|
||||
{
|
||||
uuid = "missing url";
|
||||
}
|
||||
else if (SystemUrl.Contains("#"))
|
||||
{
|
||||
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
|
||||
uuid = result.Groups[1].Value;
|
||||
}
|
||||
else if (SystemUrl.Contains("detail"))
|
||||
{
|
||||
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/detail\/(.*)\/.*");
|
||||
uuid = result.Groups[1].Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*");
|
||||
uuid = result.Groups[1].Value;
|
||||
}
|
||||
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,24 +66,33 @@ namespace PepperDash.Essentials.Core.Config
|
||||
/// Gets the TemplateUuid extracted from the TemplateUrl
|
||||
/// </summary>
|
||||
[JsonProperty("templateUuid")]
|
||||
public string TemplateUuid
|
||||
public string TemplateUuid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(TemplateUrl))
|
||||
return "missing template url";
|
||||
string uuid;
|
||||
|
||||
if (TemplateUrl.Contains("#"))
|
||||
{
|
||||
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
|
||||
string uuid = result.Groups[1].Value;
|
||||
return uuid;
|
||||
} else
|
||||
{
|
||||
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*");
|
||||
string uuid = result.Groups[2].Value;
|
||||
return uuid;
|
||||
if (string.IsNullOrEmpty(TemplateUrl))
|
||||
{
|
||||
uuid = "missing template url";
|
||||
}
|
||||
else if (TemplateUrl.Contains("#"))
|
||||
{
|
||||
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
|
||||
uuid = result.Groups[1].Value;
|
||||
}
|
||||
else if (TemplateUrl.Contains("detail"))
|
||||
{
|
||||
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/detail\/(.*)\/system-template-versions\/detail\/(.*)\/.*");
|
||||
uuid = result.Groups[2].Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*");
|
||||
uuid = result.Groups[2].Value;
|
||||
}
|
||||
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +115,7 @@ namespace PepperDash.Essentials.Core.Config
|
||||
{
|
||||
Rooms = new List<DeviceConfig>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents version data for Essentials and its packages
|
||||
@@ -147,7 +165,7 @@ namespace PepperDash.Essentials.Core.Config
|
||||
/// Represents a SystemTemplateConfigs
|
||||
/// </summary>
|
||||
public class SystemTemplateConfigs
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the System
|
||||
/// </summary>
|
||||
@@ -157,5 +175,5 @@ namespace PepperDash.Essentials.Core.Config
|
||||
/// Gets or sets the Template
|
||||
/// </summary>
|
||||
public EssentialsConfig Template { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,9 +60,9 @@ namespace PepperDash.Essentials.Core
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s).Replace(Environment.NewLine, "\r\n")), "devprops", "", ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s).Replace(Environment.NewLine, "\r\n")), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s).Replace(Environment.NewLine, "\r\n")), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive",
|
||||
"Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
|
||||
@@ -77,9 +77,6 @@ namespace PepperDash.Essentials.Core
|
||||
/// A name that will override the source's name on the UI
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
/// <summary>
|
||||
/// Gets or sets the Name
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,121 +16,201 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
{
|
||||
|
||||
// Processor Attributes
|
||||
/// <summary>
|
||||
/// Processor IP 1
|
||||
/// </summary>
|
||||
[JoinName("ProcessorIp1")]
|
||||
public JoinDataComplete ProcessorIp1 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1, AttributeName = "Info - Processor - IP 1" },
|
||||
new JoinMetadata { Description = "Info - Processor - IP 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor IP 2
|
||||
/// </summary>
|
||||
[JoinName("ProcessorIp2")]
|
||||
public JoinDataComplete ProcessorIp2 = new JoinDataComplete(new JoinData { JoinNumber = 51, JoinSpan = 1, AttributeName = "Info - Processor - IP 2" },
|
||||
new JoinMetadata { Description = "Info - Processor - IP 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor Gateway
|
||||
/// </summary>
|
||||
[JoinName("ProcessorGateway")]
|
||||
public JoinDataComplete ProcessorGateway = new JoinDataComplete(new JoinData { JoinNumber = 52, JoinSpan = 1, AttributeName = "Info - Processor - Gateway" },
|
||||
new JoinMetadata { Description = "Info - Processor - Gateway", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor Hostname
|
||||
/// </summary>
|
||||
[JoinName("ProcessorHostname")]
|
||||
public JoinDataComplete ProcessorHostname = new JoinDataComplete(new JoinData { JoinNumber = 53, JoinSpan = 1, AttributeName = "Info - Processor - Hostname" },
|
||||
new JoinMetadata { Description = "Info - Processor - Hostname", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor Domain
|
||||
/// </summary>
|
||||
[JoinName("ProcessorDomain")]
|
||||
public JoinDataComplete ProcessorDomain = new JoinDataComplete(new JoinData { JoinNumber = 54, JoinSpan = 1, AttributeName = "Info - Processor - Domain" },
|
||||
new JoinMetadata { Description = "Info - Processor - Domain", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor DNS 1
|
||||
/// </summary>
|
||||
[JoinName("ProcessorDns1")]
|
||||
public JoinDataComplete ProcessorDns1 = new JoinDataComplete(new JoinData { JoinNumber = 55, JoinSpan = 1, AttributeName = "Info - Processor - DNS 1" },
|
||||
new JoinMetadata { Description = "Info - Processor - DNS 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor DNS 2
|
||||
/// </summary>
|
||||
[JoinName("ProcessorDns2")]
|
||||
public JoinDataComplete ProcessorDns2 = new JoinDataComplete(new JoinData { JoinNumber = 56, JoinSpan = 1, AttributeName = "Info - Processor - DNS 2" },
|
||||
new JoinMetadata { Description = "Info - Processor - DNS 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor MAC 1
|
||||
/// </summary>
|
||||
[JoinName("ProcessorMac1")]
|
||||
public JoinDataComplete ProcessorMac1 = new JoinDataComplete(new JoinData { JoinNumber = 57, JoinSpan = 1, AttributeName = "Info - Processor - MAC 1" },
|
||||
new JoinMetadata { Description = "Info - Processor - MAC 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor MAC 2
|
||||
/// </summary>
|
||||
[JoinName("ProcessorMac2")]
|
||||
public JoinDataComplete ProcessorMac2 = new JoinDataComplete(new JoinData { JoinNumber = 58, JoinSpan = 1, AttributeName = "Info - Processor - MAC 2" },
|
||||
new JoinMetadata { Description = "Info - Processor - MAC 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor Net Mask 1
|
||||
/// </summary>
|
||||
[JoinName("ProcessorNetMask1")]
|
||||
public JoinDataComplete ProcessorNetMask1 = new JoinDataComplete(new JoinData { JoinNumber = 59, JoinSpan = 1, AttributeName = "Info - Processor - Net Mask 1" },
|
||||
new JoinMetadata { Description = "Info - Processor - Net Mask 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor Net Mask 2
|
||||
/// </summary>
|
||||
[JoinName("ProcessorNetMask2")]
|
||||
public JoinDataComplete ProcessorNetMask2 = new JoinDataComplete(new JoinData { JoinNumber = 60, JoinSpan = 1, AttributeName = "Info - Processor - Net Mask 2" },
|
||||
new JoinMetadata { Description = "Info - Processor - Net Mask 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor Firmware
|
||||
/// </summary>
|
||||
[JoinName("ProcessorFirmware")]
|
||||
public JoinDataComplete ProcessorFirmware = new JoinDataComplete(new JoinData { JoinNumber = 61, JoinSpan = 1, AttributeName = "Info - Processor - Firmware" },
|
||||
new JoinMetadata { Description = "Info - Processor - Firmware", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Program Name Start
|
||||
/// </summary>
|
||||
[JoinName("ProgramNameStart")]
|
||||
public JoinDataComplete ProgramNameStart = new JoinDataComplete(new JoinData { JoinNumber = 62, JoinSpan = 10, AttributeName = "Info - Processor - Program" },
|
||||
new JoinMetadata { Description = "Info - Processor - Program", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor Reboot
|
||||
/// </summary>
|
||||
[JoinName("ProcessorReboot")]
|
||||
public JoinDataComplete ProcessorReboot = new JoinDataComplete(new JoinData { JoinNumber = 74, JoinSpan = 1, AttributeName = "Processor - Reboot" },
|
||||
new JoinMetadata { Description = "Processor - Reboot", JoinCapabilities = eJoinCapabilities.FromFusion, JoinType = eJoinType.Digital });
|
||||
|
||||
// Volume Controls
|
||||
/// <summary>
|
||||
/// Volume Fader 1
|
||||
/// </summary>
|
||||
[JoinName("VolumeFader1")]
|
||||
public JoinDataComplete VolumeFader1 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1, AttributeName = "Volume - Fader01" },
|
||||
new JoinMetadata { Description = "Volume - Fader01", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Analog });
|
||||
|
||||
// Codec Info
|
||||
/// <summary>
|
||||
/// VC Codec In Call
|
||||
/// </summary>
|
||||
[JoinName("VcCodecInCall")]
|
||||
public JoinDataComplete VcCodecInCall = new JoinDataComplete(new JoinData { JoinNumber = 69, JoinSpan = 1, AttributeName = "Conf - VC 1 In Call" },
|
||||
new JoinMetadata { Description = "Conf - VC 1 In Call", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital });
|
||||
|
||||
/// <summary>
|
||||
/// VC Codec Online
|
||||
/// </summary>
|
||||
[JoinName("VcCodecOnline")]
|
||||
public JoinDataComplete VcCodecOnline = new JoinDataComplete(new JoinData { JoinNumber = 122, JoinSpan = 1, AttributeName = "Online - VC 1" },
|
||||
new JoinMetadata { Description = "Online - VC 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital });
|
||||
|
||||
/// <summary>
|
||||
/// VC Codec IP Address
|
||||
/// </summary>
|
||||
[JoinName("VcCodecIpAddress")]
|
||||
public JoinDataComplete VcCodecIpAddress = new JoinDataComplete(new JoinData { JoinNumber = 121, JoinSpan = 1, AttributeName = "IP Address - VC" },
|
||||
new JoinMetadata { Description = "IP Address - VC", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// VC Codec IP Port
|
||||
/// </summary>
|
||||
[JoinName("VcCodecIpPort")]
|
||||
public JoinDataComplete VcCodecIpPort = new JoinDataComplete(new JoinData { JoinNumber = 150, JoinSpan = 1, AttributeName = "IP Port - VC" },
|
||||
new JoinMetadata { Description = "IP Port - VC", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
// Source Attributes
|
||||
/// <summary>
|
||||
/// Display 1 Current Source Name
|
||||
/// </summary>
|
||||
[JoinName("Display1CurrentSourceName")]
|
||||
public JoinDataComplete Display1CurrentSourceName = new JoinDataComplete(new JoinData { JoinNumber = 84, JoinSpan = 1, AttributeName = "Display 1 - Current Source" },
|
||||
new JoinMetadata { Description = "Display 1 - Current Source", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
|
||||
|
||||
|
||||
// Device Online Status
|
||||
/// <summary>
|
||||
/// Touchpanel Online Start
|
||||
/// </summary>
|
||||
[JoinName("TouchpanelOnlineStart")]
|
||||
public JoinDataComplete TouchpanelOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 150, JoinSpan = 10, AttributeName = "Online - Touch Panel" },
|
||||
new JoinMetadata { Description = "Online - Touch Panel", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital });
|
||||
|
||||
/// <summary>
|
||||
/// Xpanel Online Start
|
||||
/// </summary>
|
||||
[JoinName("XpanelOnlineStart")]
|
||||
public JoinDataComplete XpanelOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 160, JoinSpan = 5, AttributeName = "Online - XPanel" },
|
||||
new JoinMetadata { Description = "Online - XPanel", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital });
|
||||
|
||||
/// <summary>
|
||||
/// Display Online Start
|
||||
/// </summary>
|
||||
[JoinName("DisplayOnlineStart")]
|
||||
public JoinDataComplete DisplayOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 170, JoinSpan = 10, AttributeName = "Online - Display" },
|
||||
new JoinMetadata { Description = "Online - Display", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital });
|
||||
|
||||
/// <summary>
|
||||
/// Display 1 Laptop Source Start
|
||||
/// </summary>
|
||||
[JoinName("Display1LaptopSourceStart")]
|
||||
public JoinDataComplete Display1LaptopSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 166, JoinSpan = 5, AttributeName = "Display 1 - Source Laptop" },
|
||||
public JoinDataComplete Display1LaptopSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 165, JoinSpan = 5, AttributeName = "Display 1 - Source Laptop" },
|
||||
new JoinMetadata { Description = "Display 1 - Source Laptop", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital });
|
||||
|
||||
/// <summary>
|
||||
/// Display 1 Disc Player Source Start
|
||||
/// </summary>
|
||||
[JoinName("Display1DiscPlayerSourceStart")]
|
||||
public JoinDataComplete Display1DiscPlayerSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 181, JoinSpan = 5, AttributeName = "Display 1 - Source Disc Player" },
|
||||
public JoinDataComplete Display1DiscPlayerSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 180, JoinSpan = 5, AttributeName = "Display 1 - Source Disc Player" },
|
||||
new JoinMetadata { Description = "Display 1 - Source Disc Player", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital });
|
||||
|
||||
/// <summary>
|
||||
/// Display 1 Set Top Box Source Start
|
||||
/// </summary>
|
||||
[JoinName("Display1SetTopBoxSourceStart")]
|
||||
public JoinDataComplete Display1SetTopBoxSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 188, JoinSpan = 5, AttributeName = "Display 1 - Source TV" },
|
||||
public JoinDataComplete Display1SetTopBoxSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 185, JoinSpan = 5, AttributeName = "Display 1 - Source TV" },
|
||||
new JoinMetadata { Description = "Display 1 - Source TV", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital });
|
||||
|
||||
// Display 1
|
||||
/// <summary>
|
||||
/// Display 1 Start
|
||||
/// </summary>
|
||||
[JoinName("Display1Start")]
|
||||
public JoinDataComplete Display1Start = new JoinDataComplete(new JoinData { JoinNumber = 158, JoinSpan = 1 },
|
||||
public JoinDataComplete Display1Start = new JoinDataComplete(new JoinData { JoinNumber = 190, JoinSpan = 1 },
|
||||
new JoinMetadata { Description = "Display 1 Start", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital });
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to use when instantiating this Join Map without inheriting from it
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
|
||||
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharp.CrestronXml;
|
||||
using Crestron.SimplSharp.CrestronXml.Serialization;
|
||||
@@ -8,6 +6,7 @@ using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.Fusion;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
||||
using Serilog.Events;
|
||||
@@ -15,32 +14,49 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Fusion
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a EssentialsHuddleSpaceFusionSystemControllerBase
|
||||
/// </summary>
|
||||
public class EssentialsHuddleSpaceFusionSystemControllerBase : Device, IOccupancyStatusProvider
|
||||
public class IEssentialsRoomFusionController : EssentialsDevice, IOccupancyStatusProvider, IFusionHelpRequest, IHasFeedback
|
||||
{
|
||||
private readonly EssentialsHuddleSpaceRoomFusionRoomJoinMap JoinMap;
|
||||
private IEssentialsRoomFusionControllerPropertiesConfig _config;
|
||||
|
||||
private EssentialsHuddleSpaceRoomFusionRoomJoinMap JoinMap;
|
||||
|
||||
private const string RemoteOccupancyXml = "<Occupancy><Type>Local</Type><State>{0}</State></Occupancy>";
|
||||
private readonly bool _guidFileExists;
|
||||
private bool _guidFileExists;
|
||||
|
||||
private readonly Dictionary<Device, BoolInputSig> _sourceToFeedbackSigs =
|
||||
new Dictionary<Device, BoolInputSig>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CurrentRoomSourceNameSig
|
||||
/// </summary>
|
||||
protected StringSigData CurrentRoomSourceNameSig;
|
||||
|
||||
private readonly FusionCustomPropertiesBridge CustomPropertiesBridge = new FusionCustomPropertiesBridge();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FusionOccSensor
|
||||
/// </summary>
|
||||
protected FusionOccupancySensorAsset FusionOccSensor;
|
||||
private readonly FusionRemoteOccupancySensor FusionRemoteOccSensor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FusionRoom
|
||||
/// </summary>
|
||||
protected FusionRoom FusionRoom;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FusionStaticAssets
|
||||
/// </summary>
|
||||
protected Dictionary<int, FusionAsset> FusionStaticAssets;
|
||||
private readonly long PushNotificationTimeout = 5000;
|
||||
private readonly IEssentialsRoom Room;
|
||||
private IEssentialsRoom Room;
|
||||
private readonly long SchedulePollInterval = 300000;
|
||||
|
||||
private Event _currentMeeting;
|
||||
@@ -48,8 +64,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
private CTimer _dailyTimeRequestTimer;
|
||||
private StatusMonitorCollection _errorMessageRollUp;
|
||||
|
||||
private FusionRoomGuids _guiDs;
|
||||
private uint _ipId;
|
||||
private FusionRoomGuids _guids;
|
||||
|
||||
private bool _isRegisteredForSchedulePushNotifications;
|
||||
private Event _nextMeeting;
|
||||
@@ -60,14 +75,30 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
|
||||
private string _roomOccupancyRemoteString;
|
||||
|
||||
#region System Info Sigs
|
||||
private bool _helpRequestSent;
|
||||
|
||||
//StringSigData SystemName;
|
||||
//StringSigData Model;
|
||||
//StringSigData SerialNumber;
|
||||
//StringSigData Uptime;
|
||||
private eFusionHelpResponse _helpRequestStatus;
|
||||
|
||||
#endregion
|
||||
/// <inheritdoc />
|
||||
public StringFeedback HelpRequestResponseFeedback { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public BoolFeedback HelpRequestSentFeedback { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public StringFeedback HelpRequestStatusFeedback { get; private set; }
|
||||
|
||||
private Timer _helpRequestTimeoutTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DefaultHelpRequestTimeoutMs
|
||||
/// </summary>
|
||||
public int HelpRequestTimeoutMs => _config.HelpRequestTimeoutMs;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether to use a timer for help requests
|
||||
/// </summary>
|
||||
public bool UseHelpRequestTimer => _config.UseTimeoutForHelpRequests;
|
||||
|
||||
#region Processor Info Sigs
|
||||
|
||||
@@ -93,71 +124,110 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
|
||||
#endregion
|
||||
|
||||
public EssentialsHuddleSpaceFusionSystemControllerBase(IEssentialsRoom room, uint ipId, string joinMapKey)
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public IEssentialsRoomFusionController(string key, string name, IEssentialsRoomFusionControllerPropertiesConfig config)
|
||||
: base(key, name)
|
||||
{
|
||||
_config = config;
|
||||
|
||||
AddPostActivationAction(() =>
|
||||
{
|
||||
var room = DeviceManager.GetDeviceForKey<IEssentialsRoom>(_config.RoomKey);
|
||||
|
||||
if (room == null)
|
||||
{
|
||||
this.LogError("Error Creating Fusion Room Controller. No room found with key '{0}'", _config.RoomKey);
|
||||
return;
|
||||
}
|
||||
|
||||
this.LogInformation("Creating Fusion Room Controller for room '{0}' at IPID: {1:X2}", room.Key, _config.IpIdInt);
|
||||
|
||||
ConstructorHelper(room, _config.IpIdInt, _config.JoinMapKey);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="room"></param>
|
||||
/// <param name="ipId"></param>
|
||||
/// <param name="joinMapKey"></param>
|
||||
public IEssentialsRoomFusionController(IEssentialsRoom room, string ipId, string joinMapKey)
|
||||
: base(room.Key + "-fusion")
|
||||
{
|
||||
_config = new IEssentialsRoomFusionControllerPropertiesConfig()
|
||||
{
|
||||
IpId = ipId,
|
||||
RoomKey = room.Key,
|
||||
JoinMapKey = joinMapKey
|
||||
};
|
||||
|
||||
ConstructorHelper(room, _config.IpIdInt, joinMapKey);
|
||||
}
|
||||
|
||||
private void ConstructorHelper(IEssentialsRoom room, uint ipId, string joinMapKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogDebug("ConstructorHelper called for Fusion Room Controller for room '{0}' with IPID {1:X2}", room.Key, ipId);
|
||||
|
||||
this.LogDebug("JoinMap Key: {0}", joinMapKey);
|
||||
|
||||
JoinMap = new EssentialsHuddleSpaceRoomFusionRoomJoinMap(1);
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand((o) => JoinMap.PrintJoinMapInfo(), string.Format("ptjnmp-{0}", Key), "Prints Attribute Join Map", ConsoleAccessLevelEnum.AccessOperator);
|
||||
this.LogDebug("JoinMap created");
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand((o) =>
|
||||
{
|
||||
if (o is string deviceKey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(deviceKey) || deviceKey == "?")
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Please provide a device key for a Fusion Room instance");
|
||||
return;
|
||||
}
|
||||
else if (deviceKey != this.Key)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Invalid parameter. Please provide a device key for a Fusion Room instance");
|
||||
return;
|
||||
}
|
||||
|
||||
JoinMap.PrintJoinMapInfo();
|
||||
}, "printfusionjoinmap", "Prints Attribute Join Map", ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
if (!string.IsNullOrEmpty(joinMapKey))
|
||||
{
|
||||
// this.LogDebug("Attempting to get custom join map for key: {0}", joinMapKey);
|
||||
var customJoins = JoinMapHelper.TryGetJoinMapAdvancedForDevice(joinMapKey);
|
||||
if (customJoins != null)
|
||||
{
|
||||
JoinMap.SetCustomJoinData(customJoins);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Room = room;
|
||||
|
||||
_ipId = ipId;
|
||||
this.LogDebug("Room found: {0}", Room.Key);
|
||||
|
||||
FusionStaticAssets = new Dictionary<int, FusionAsset>();
|
||||
|
||||
_guiDs = new FusionRoomGuids();
|
||||
this.LogDebug("FusionStaticAssets dictionary created");
|
||||
|
||||
var mac =
|
||||
CrestronEthernetHelper.GetEthernetParameter(
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, 0);
|
||||
|
||||
var slot = Global.ControlSystem.ProgramNumber;
|
||||
|
||||
var guidFilePath = Global.FilePathPrefix +
|
||||
string.Format(@"{0}-FusionGuids-{1:X2}.json", InitialParametersClass.ProgramIDTag, _ipId);
|
||||
|
||||
var oldGuidFilePath = Global.FilePathPrefix +
|
||||
string.Format(@"{0}-FusionGuids.json", InitialParametersClass.ProgramIDTag);
|
||||
|
||||
if (File.Exists(oldGuidFilePath))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Migrating from old Fusion GUID file to new Fusion GUID File");
|
||||
|
||||
File.Copy(oldGuidFilePath, guidFilePath);
|
||||
|
||||
File.Delete(oldGuidFilePath);
|
||||
}
|
||||
|
||||
_guidFileExists = File.Exists(guidFilePath);
|
||||
|
||||
// Check if file exists
|
||||
if (!_guidFileExists)
|
||||
{
|
||||
// Does not exist. Create GUIDs
|
||||
_guiDs = new FusionRoomGuids(Room.Name, ipId, _guiDs.GenerateNewRoomGuid(slot, mac),
|
||||
FusionStaticAssets);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Exists. Read GUIDs
|
||||
ReadGuidFile(guidFilePath);
|
||||
}
|
||||
_guids = new FusionRoomGuids();
|
||||
|
||||
this.LogDebug("FusionRoomGuids created");
|
||||
|
||||
if (Room is IRoomOccupancy occupancyRoom)
|
||||
{
|
||||
Debug.LogDebug(this, "Room '{0}' supports IRoomOccupancy", Room.Key);
|
||||
if (occupancyRoom.RoomOccupancy != null)
|
||||
{
|
||||
if (occupancyRoom.OccupancyStatusProviderIsRemote)
|
||||
@@ -171,8 +241,21 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
}
|
||||
|
||||
this.LogDebug("Occupancy setup complete");
|
||||
|
||||
HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.OutputSig.StringValue);
|
||||
|
||||
HelpRequestSentFeedback = new BoolFeedback("HelpRequestSent", () => _helpRequestSent);
|
||||
HelpRequestStatusFeedback = new StringFeedback("HelpRequestStatus", () => _helpRequestStatus.ToString());
|
||||
|
||||
Feedbacks.Add(HelpRequestResponseFeedback);
|
||||
Feedbacks.Add(HelpRequestSentFeedback);
|
||||
Feedbacks.Add(HelpRequestStatusFeedback);
|
||||
if (RoomOccupancyRemoteStringFeedback != null)
|
||||
Feedbacks.Add(RoomOccupancyRemoteStringFeedback);
|
||||
if (RoomIsOccupiedFeedback != null)
|
||||
Feedbacks.Add(RoomIsOccupiedFeedback);
|
||||
|
||||
AddPostActivationAction(() => PostActivate(guidFilePath));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -180,9 +263,54 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
}
|
||||
|
||||
private void PostActivate(string guidFilePath)
|
||||
private string GetGuidFilePath(uint ipId)
|
||||
{
|
||||
CreateSymbolAndBasicSigs(_ipId);
|
||||
var mac =
|
||||
CrestronEthernetHelper.GetEthernetParameter(
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, 0);
|
||||
|
||||
var slot = Global.ControlSystem.ProgramNumber;
|
||||
|
||||
var guidFilePath = Global.FilePathPrefix +
|
||||
string.Format(@"{0}-FusionGuids-{1:X2}.json", InitialParametersClass.ProgramIDTag, _config.IpIdInt);
|
||||
|
||||
var oldGuidFilePath = Global.FilePathPrefix +
|
||||
string.Format(@"{0}-FusionGuids.json", InitialParametersClass.ProgramIDTag);
|
||||
|
||||
if (File.Exists(oldGuidFilePath))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Migrating from old Fusion GUID file to new Fusion GUID File");
|
||||
|
||||
File.Copy(oldGuidFilePath, guidFilePath);
|
||||
|
||||
File.Delete(oldGuidFilePath);
|
||||
}
|
||||
|
||||
_guidFileExists = File.Exists(guidFilePath);
|
||||
|
||||
// Check if file exists
|
||||
if (!_guidFileExists)
|
||||
{
|
||||
// Does not exist. Create GUIDs
|
||||
_guids = new FusionRoomGuids(Room.Name, ipId, _guids.GenerateNewRoomGuid(slot, mac),
|
||||
FusionStaticAssets);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Exists. Read GUIDs
|
||||
ReadGuidFile(guidFilePath);
|
||||
}
|
||||
|
||||
return guidFilePath;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
GenerateGuidFile(GetGuidFilePath(_config.IpIdInt));
|
||||
|
||||
CreateSymbolAndBasicSigs(_config.IpIdInt);
|
||||
SetUpSources();
|
||||
SetUpCommunitcationMonitors();
|
||||
SetUpDisplay();
|
||||
@@ -191,12 +319,14 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
|
||||
FusionRVI.GenerateFileForAllFusionDevices();
|
||||
|
||||
GenerateGuidFile(guidFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RoomGuid
|
||||
/// </summary>
|
||||
protected string RoomGuid
|
||||
{
|
||||
get { return _guiDs.RoomGuid; }
|
||||
get { return _guids.RoomGuid; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -204,6 +334,9 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
/// </summary>
|
||||
public StringFeedback RoomOccupancyRemoteStringFeedback { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RoomIsOccupiedFeedbackFunc
|
||||
/// </summary>
|
||||
protected Func<bool> RoomIsOccupiedFeedbackFunc
|
||||
{
|
||||
get { return () => FusionRemoteOccSensor.RoomOccupied.OutputSig.BoolValue; }
|
||||
@@ -218,10 +351,21 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public FeedbackCollection<Feedback> Feedbacks { get; private set; } = new FeedbackCollection<Feedback>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ScheduleChange event
|
||||
/// </summary>
|
||||
public event EventHandler<ScheduleChangeEventArgs> ScheduleChange;
|
||||
//public event EventHandler<MeetingChangeEventArgs> MeetingEndWarning;
|
||||
//public event EventHandler<MeetingChangeEventArgs> NextMeetingBeginWarning;
|
||||
|
||||
/// <summary>
|
||||
/// RoomInfoChange event
|
||||
/// </summary>
|
||||
public event EventHandler<EventArgs> RoomInfoChange;
|
||||
|
||||
//ScheduleResponseEvent NextMeeting;
|
||||
@@ -258,11 +402,11 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Writing GUIDs to file");
|
||||
|
||||
_guiDs = FusionOccSensor == null
|
||||
? new FusionRoomGuids(Room.Name, _ipId, RoomGuid, FusionStaticAssets)
|
||||
: new FusionRoomGuids(Room.Name, _ipId, RoomGuid, FusionStaticAssets, FusionOccSensor);
|
||||
_guids = FusionOccSensor == null
|
||||
? new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets)
|
||||
: new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets, FusionOccSensor);
|
||||
|
||||
var json = JsonConvert.SerializeObject(_guiDs, Newtonsoft.Json.Formatting.Indented);
|
||||
var json = JsonConvert.SerializeObject(_guids, Newtonsoft.Json.Formatting.Indented);
|
||||
|
||||
using (var sw = new StreamWriter(filePath))
|
||||
{
|
||||
@@ -312,17 +456,17 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
{
|
||||
var json = File.ReadToEnd(filePath, Encoding.ASCII);
|
||||
|
||||
_guiDs = JsonConvert.DeserializeObject<FusionRoomGuids>(json);
|
||||
_guids = JsonConvert.DeserializeObject<FusionRoomGuids>(json);
|
||||
|
||||
_ipId = _guiDs.IpId;
|
||||
// _config.IpId = _guids.IpId;
|
||||
|
||||
FusionStaticAssets = _guiDs.StaticAssets;
|
||||
FusionStaticAssets = _guids.StaticAssets;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Fusion Guids successfully read from file: {0}",
|
||||
filePath);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "\r\n********************\r\n\tRoom Name: {0}\r\n\tIPID: {1:X}\r\n\tRoomGuid: {2}\r\n*******************", Room.Name, _ipId, RoomGuid);
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "\r\n********************\r\n\tRoom Name: {0}\r\n\tIPID: {1:X}\r\n\tRoomGuid: {2}\r\n*******************", Room.Name, _config.IpIdInt, RoomGuid);
|
||||
|
||||
foreach (var item in FusionStaticAssets)
|
||||
{
|
||||
@@ -343,6 +487,10 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CreateSymbolAndBasicSigs method
|
||||
/// </summary>
|
||||
/// <param name="ipId"></param>
|
||||
protected virtual void CreateSymbolAndBasicSigs(uint ipId)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Creating Fusion Room symbol with GUID: {0} and IP-ID {1:X2}", RoomGuid, ipId);
|
||||
@@ -405,6 +553,10 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CrestronEnvironment_EthernetEventHandler method
|
||||
/// </summary>
|
||||
/// <param name="ethernetEventArgs"></param>
|
||||
protected void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
|
||||
{
|
||||
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp)
|
||||
@@ -413,6 +565,9 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetSystemInfo method
|
||||
/// </summary>
|
||||
protected void GetSystemInfo()
|
||||
{
|
||||
//SystemName.InputSig.StringValue = Room.Name;
|
||||
@@ -426,6 +581,9 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
() => CrestronConsole.SendControlSystemCommand("reboot", ref response));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SetUpEthernetValues method
|
||||
/// </summary>
|
||||
protected void SetUpEthernetValues()
|
||||
{
|
||||
_ip1 = FusionRoom.CreateOffsetStringSig(JoinMap.ProcessorIp1.JoinNumber, JoinMap.ProcessorIp1.AttributeName, eSigIoMask.InputSigOnly);
|
||||
@@ -441,6 +599,9 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
_netMask2 = FusionRoom.CreateOffsetStringSig(JoinMap.ProcessorNetMask2.JoinNumber, JoinMap.ProcessorNetMask2.AttributeName, eSigIoMask.InputSigOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetProcessorEthernetValues method
|
||||
/// </summary>
|
||||
protected void GetProcessorEthernetValues()
|
||||
{
|
||||
_ip1.InputSig.StringValue =
|
||||
@@ -475,7 +636,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
// Interface 1
|
||||
|
||||
if (InitialParametersClass.NumberOfEthernetInterfaces > 1)
|
||||
// Only get these values if the processor has more than 1 NIC
|
||||
// Only get these values if the processor has more than 1 NIC
|
||||
{
|
||||
_ip2.InputSig.StringValue =
|
||||
CrestronEthernetHelper.GetEthernetParameter(
|
||||
@@ -489,6 +650,9 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetProcessorInfo method
|
||||
/// </summary>
|
||||
protected void GetProcessorInfo()
|
||||
{
|
||||
_firmware = FusionRoom.CreateOffsetStringSig(JoinMap.ProcessorFirmware.JoinNumber, JoinMap.ProcessorFirmware.AttributeName, eSigIoMask.InputSigOnly);
|
||||
@@ -499,7 +663,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
{
|
||||
var join = JoinMap.ProgramNameStart.JoinNumber + i;
|
||||
var progNum = i + 1;
|
||||
_program[i] = FusionRoom.CreateOffsetStringSig((uint) join,
|
||||
_program[i] = FusionRoom.CreateOffsetStringSig((uint)join,
|
||||
string.Format("{0} {1}", JoinMap.ProgramNameStart.AttributeName, progNum), eSigIoMask.InputSigOnly);
|
||||
}
|
||||
}
|
||||
@@ -507,6 +671,9 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
_firmware.InputSig.StringValue = InitialParametersClass.FirmwareVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetCustomProperties method
|
||||
/// </summary>
|
||||
protected void GetCustomProperties()
|
||||
{
|
||||
if (FusionRoom.IsOnline)
|
||||
@@ -524,11 +691,16 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
// TODO: Get IP and Project Name from TP
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FusionRoom_OnlineStatusChange method
|
||||
/// </summary>
|
||||
/// <param name="currentDevice"></param>
|
||||
/// <param name="args"></param>
|
||||
protected void FusionRoom_OnlineStatusChange(GenericBase currentDevice, OnlineOfflineEventArgs args)
|
||||
{
|
||||
if (args.DeviceOnLine)
|
||||
{
|
||||
CrestronInvoke.BeginInvoke( (o) =>
|
||||
CrestronInvoke.BeginInvoke((o) =>
|
||||
{
|
||||
CrestronEnvironment.Sleep(200);
|
||||
|
||||
@@ -676,7 +848,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
var extendTime = _currentMeeting.dtEnd - DateTime.Now;
|
||||
var extendMinutesRaw = extendTime.TotalMinutes;
|
||||
|
||||
extendMinutes += (int) Math.Round(extendMinutesRaw);
|
||||
extendMinutes += (int)Math.Round(extendMinutesRaw);
|
||||
}
|
||||
|
||||
|
||||
@@ -784,11 +956,11 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
var parameters = actionResponse["Parameters"];
|
||||
|
||||
foreach (var isRegistered in from XmlElement parameter in parameters
|
||||
where parameter.HasAttributes
|
||||
select parameter.Attributes
|
||||
where parameter.HasAttributes
|
||||
select parameter.Attributes
|
||||
into attributes
|
||||
where attributes["ID"].Value == "Registered"
|
||||
select Int32.Parse(attributes["Value"].Value))
|
||||
where attributes["ID"].Value == "Registered"
|
||||
select Int32.Parse(attributes["Value"].Value))
|
||||
{
|
||||
switch (isRegistered)
|
||||
{
|
||||
@@ -845,9 +1017,9 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "DateTime from Fusion Server: {0}", currentTime);
|
||||
|
||||
// Parse time and date from response and insert values
|
||||
CrestronEnvironment.SetTimeAndDate((ushort) currentTime.Hour, (ushort) currentTime.Minute,
|
||||
(ushort) currentTime.Second, (ushort) currentTime.Month, (ushort) currentTime.Day,
|
||||
(ushort) currentTime.Year);
|
||||
CrestronEnvironment.SetTimeAndDate((ushort)currentTime.Hour, (ushort)currentTime.Minute,
|
||||
(ushort)currentTime.Second, (ushort)currentTime.Month, (ushort)currentTime.Day,
|
||||
(ushort)currentTime.Year);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Processor time set to {0}", CrestronEnvironment.GetLocalTime());
|
||||
}
|
||||
@@ -1065,6 +1237,9 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SetUpSources method
|
||||
/// </summary>
|
||||
protected virtual void SetUpSources()
|
||||
{
|
||||
// Sources
|
||||
@@ -1074,10 +1249,10 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
// NEW PROCESS:
|
||||
// Make these lists and insert the fusion attributes by iterating these
|
||||
var setTopBoxes = dict.Where(d => d.Value.SourceDevice is ISetTopBoxControls);
|
||||
uint i = 1;
|
||||
uint i = 0;
|
||||
foreach (var kvp in setTopBoxes)
|
||||
{
|
||||
TryAddRouteActionSigs(JoinMap.Display1SetTopBoxSourceStart.AttributeName + " " + i, JoinMap.Display1SetTopBoxSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice);
|
||||
TryAddRouteActionSigs(JoinMap.Display1SetTopBoxSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1SetTopBoxSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice);
|
||||
i++;
|
||||
if (i > JoinMap.Display1SetTopBoxSourceStart.JoinSpan) // We only have five spots
|
||||
{
|
||||
@@ -1086,10 +1261,10 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
|
||||
var discPlayers = dict.Where(d => d.Value.SourceDevice is IDiscPlayerControls);
|
||||
i = 1;
|
||||
i = 0;
|
||||
foreach (var kvp in discPlayers)
|
||||
{
|
||||
TryAddRouteActionSigs(JoinMap.Display1DiscPlayerSourceStart.AttributeName + " " + i, JoinMap.Display1DiscPlayerSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice);
|
||||
TryAddRouteActionSigs(JoinMap.Display1DiscPlayerSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1DiscPlayerSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice);
|
||||
i++;
|
||||
if (i > JoinMap.Display1DiscPlayerSourceStart.JoinSpan) // We only have five spots
|
||||
{
|
||||
@@ -1098,10 +1273,10 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
|
||||
var laptops = dict.Where(d => d.Value.SourceDevice is IRoutingSource);
|
||||
i = 1;
|
||||
i = 0;
|
||||
foreach (var kvp in laptops)
|
||||
{
|
||||
TryAddRouteActionSigs(JoinMap.Display1LaptopSourceStart.AttributeName + " " + i, JoinMap.Display1LaptopSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice);
|
||||
TryAddRouteActionSigs(JoinMap.Display1LaptopSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1LaptopSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice);
|
||||
i++;
|
||||
if (i > JoinMap.Display1LaptopSourceStart.JoinSpan) // We only have ten spots???
|
||||
{
|
||||
@@ -1111,7 +1286,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
|
||||
foreach (var usageDevice in dict.Select(kvp => kvp.Value.SourceDevice).OfType<IUsageTracking>())
|
||||
{
|
||||
usageDevice.UsageTracker = new UsageTracking(usageDevice as Device) {UsageIsTracked = true};
|
||||
usageDevice.UsageTracker = new UsageTracking(usageDevice as Device) { UsageIsTracked = true };
|
||||
usageDevice.UsageTracker.DeviceUsageEnded += UsageTracker_DeviceUsageEnded;
|
||||
}
|
||||
}
|
||||
@@ -1157,17 +1332,31 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Device usage string: {0}", deviceUsage);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add route action sigs for a source
|
||||
/// </summary>
|
||||
/// <param name="attrName"></param>
|
||||
/// <param name="attrNum"></param>
|
||||
/// <param name="routeKey"></param>
|
||||
/// <param name="pSrc"></param>
|
||||
protected void TryAddRouteActionSigs(string attrName, uint attrNum, string routeKey, Device pSrc)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Creating attribute '{0}' with join {1} for source {2}",
|
||||
this.LogVerbose("Creating attribute '{0}' with join {1} for source {2}",
|
||||
attrName, attrNum, pSrc.Key);
|
||||
try
|
||||
{
|
||||
var sigD = FusionRoom.CreateOffsetBoolSig(attrNum, attrName, eSigIoMask.InputOutputSig);
|
||||
// Need feedback when this source is selected
|
||||
// Event handler, added below, will compare source changes with this sig dict
|
||||
_sourceToFeedbackSigs.Add(pSrc, sigD.InputSig);
|
||||
if (!_sourceToFeedbackSigs.ContainsKey(pSrc))
|
||||
{
|
||||
_sourceToFeedbackSigs.Add(pSrc, sigD.InputSig);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogWarning("Source '{0}' already has a feedback sig mapped. Overwriting.", pSrc.Key);
|
||||
_sourceToFeedbackSigs[pSrc] = sigD.InputSig;
|
||||
}
|
||||
|
||||
// And respond to selection in Fusion
|
||||
sigD.OutputSig.SetSigFalseAction(() =>
|
||||
@@ -1180,14 +1369,12 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Error creating Fusion signal {0} {1} for device '{2}'. THIS NEEDS REWORKING",
|
||||
this.LogVerbose("Error creating Fusion signal {0} {1} for device '{2}'. THIS NEEDS REWORKING",
|
||||
attrNum, attrName, pSrc.Key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
||||
private void SetUpCommunitcationMonitors()
|
||||
{
|
||||
uint displayNum = 0;
|
||||
@@ -1274,6 +1461,8 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
|
||||
if (attrName != null)
|
||||
{
|
||||
this.LogDebug("Linking communication monitor for device '{0}' to Fusion attribute '{1}' at join {2}",
|
||||
dev.Key, attrName, attrNum);
|
||||
// Link comm status to sig and update
|
||||
var sigD = FusionRoom.CreateOffsetBoolSig(attrNum, attrName, eSigIoMask.InputSigOnly);
|
||||
var smd = dev as ICommunicationMonitor;
|
||||
@@ -1285,6 +1474,9 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SetUpDisplay method
|
||||
/// </summary>
|
||||
protected virtual void SetUpDisplay()
|
||||
{
|
||||
try
|
||||
@@ -1297,7 +1489,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
|
||||
foreach (var display in displays.Cast<IDisplay>())
|
||||
{
|
||||
display.UsageTracker = new UsageTracking(display as Device) {UsageIsTracked = true};
|
||||
display.UsageTracker = new UsageTracking(display as Device) { UsageIsTracked = true };
|
||||
display.UsageTracker.DeviceUsageEnded += UsageTracker_DeviceUsageEnded;
|
||||
}
|
||||
|
||||
@@ -1410,7 +1602,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
|
||||
|
||||
// Power on
|
||||
var defaultDisplayPowerOn = FusionRoom.CreateOffsetBoolSig((uint) joinOffset, displayName + "Power On",
|
||||
var defaultDisplayPowerOn = FusionRoom.CreateOffsetBoolSig((uint)joinOffset, displayName + "Power On",
|
||||
eSigIoMask.InputOutputSig);
|
||||
defaultDisplayPowerOn.OutputSig.UserObject = new Action<bool>(b =>
|
||||
{
|
||||
@@ -1421,7 +1613,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
});
|
||||
|
||||
// Power Off
|
||||
var defaultDisplayPowerOff = FusionRoom.CreateOffsetBoolSig((uint) joinOffset + 1, displayName + "Power Off",
|
||||
var defaultDisplayPowerOff = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 1, displayName + "Power Off",
|
||||
eSigIoMask.InputOutputSig);
|
||||
defaultDisplayPowerOn.OutputSig.UserObject = new Action<bool>(b =>
|
||||
{
|
||||
@@ -1439,7 +1631,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
|
||||
// Current Source
|
||||
var defaultDisplaySourceNone = FusionRoom.CreateOffsetBoolSig((uint) joinOffset + 8,
|
||||
var defaultDisplaySourceNone = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 8,
|
||||
displayName + "Source None", eSigIoMask.InputOutputSig);
|
||||
defaultDisplaySourceNone.OutputSig.UserObject = new Action<bool>(b =>
|
||||
{
|
||||
@@ -1507,7 +1699,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
//if (Room.OccupancyObj != null)
|
||||
//{
|
||||
|
||||
var tempOccAsset = _guiDs.OccupancyAsset;
|
||||
var tempOccAsset = _guids.OccupancyAsset;
|
||||
|
||||
if (tempOccAsset == null)
|
||||
{
|
||||
@@ -1532,7 +1724,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
occRoom.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += RoomIsOccupiedFeedback_OutputChange;
|
||||
}
|
||||
RoomOccupancyRemoteStringFeedback = new StringFeedback(() => _roomOccupancyRemoteString);
|
||||
|
||||
|
||||
RoomOccupancyRemoteStringFeedback.LinkInputSig(occSensorAsset.RoomOccupancyInfo.InputSig);
|
||||
|
||||
//}
|
||||
@@ -1588,12 +1780,82 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for Fusion state changes
|
||||
/// </summary>
|
||||
/// <param name="device"></param>
|
||||
/// <param name="args"></param>
|
||||
protected void FusionRoom_FusionStateChange(FusionBase device, FusionStateEventArgs args)
|
||||
{
|
||||
if (args.EventId == FusionEventIds.HelpMessageReceivedEventId)
|
||||
{
|
||||
this.LogInformation("Help message received from Fusion for room '{0}'",
|
||||
Room.Name);
|
||||
|
||||
this.LogDebug("Help message content: {0}", FusionRoom.Help.OutputSig.StringValue);
|
||||
// Fire help request event
|
||||
HelpRequestResponseFeedback.FireUpdate();
|
||||
|
||||
if (!string.IsNullOrEmpty(FusionRoom.Help.OutputSig.StringValue))
|
||||
{
|
||||
switch (FusionRoom.Help.OutputSig.StringValue)
|
||||
{
|
||||
case "Please wait, a technician is on his / her way.":
|
||||
// this.LogInformation("Please wait, a technician is on his / her way.",
|
||||
// Room.Name);
|
||||
|
||||
_helpRequestStatus = eFusionHelpResponse.HelpOnTheWay;
|
||||
break;
|
||||
case "Please call the helpdesk.":
|
||||
// this.LogInformation("Please call the helpdesk.");
|
||||
_helpRequestStatus = eFusionHelpResponse.CallHelpDesk;
|
||||
break;
|
||||
case "Please wait, I will reschedule your meeting to a different room.":
|
||||
// this.LogInformation("Please wait, I will reschedule your meeting to a different room.",
|
||||
// Room.Name);
|
||||
|
||||
_helpRequestStatus = eFusionHelpResponse.ReschedulingMeeting;
|
||||
break;
|
||||
case "I will be taking control of your system. Please be patient while I adjust the settings.":
|
||||
// this.LogInformation("I will be taking control of your system. Please be patient while I adjust the settings.",
|
||||
// Room.Name);
|
||||
|
||||
_helpRequestStatus = eFusionHelpResponse.TakingControl;
|
||||
break;
|
||||
default:
|
||||
// this.LogInformation("Unknown help request code received from Fusion for room '{0}'",
|
||||
// Room.Name);
|
||||
|
||||
_helpRequestStatus = eFusionHelpResponse.None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_helpRequestStatus = eFusionHelpResponse.None;
|
||||
}
|
||||
|
||||
if (_helpRequestStatus == eFusionHelpResponse.None)
|
||||
{
|
||||
_helpRequestSent = false;
|
||||
HelpRequestSentFeedback.FireUpdate();
|
||||
}
|
||||
|
||||
HelpRequestStatusFeedback.FireUpdate();
|
||||
|
||||
if (_helpRequestTimeoutTimer != null)
|
||||
{
|
||||
_helpRequestTimeoutTimer.Stop();
|
||||
_helpRequestTimeoutTimer.Elapsed -= OnTimedEvent;
|
||||
_helpRequestTimeoutTimer.Dispose();
|
||||
_helpRequestTimeoutTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The sig/UO method: Need separate handlers for fixed and user sigs, all flavors,
|
||||
// even though they all contain sigs.
|
||||
|
||||
|
||||
BoolOutputSig outSig;
|
||||
if (args.UserConfiguredSigDetail is BooleanSigDataFixedName sigData)
|
||||
{
|
||||
@@ -1632,9 +1894,102 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
(outSig.UserObject as Action<string>).Invoke(outSig.StringValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendHelpRequest()
|
||||
{
|
||||
|
||||
var now = DateTime.Now;
|
||||
|
||||
var breakString = _config.UseHtmlFormatForHelpRequests ? "<BR>" : "\r\n";
|
||||
|
||||
var date = now.ToString("MMMM dd, yyyy");
|
||||
var time = now.ToString("hh:mm tt");
|
||||
if (_config.Use24HourTimeFormat)
|
||||
{
|
||||
time = now.ToString("HH:mm");
|
||||
}
|
||||
|
||||
var requestString = $"HR00: {breakString} Assistance has been requested from room {Room.Name}{breakString}on {date} at {time}";
|
||||
|
||||
FusionRoom.Help.InputSig.StringValue = requestString;
|
||||
|
||||
this.LogInformation("Help request sent to Fusion from room '{0}'", Room.Name);
|
||||
this.LogDebug("Help request content: {0}", FusionRoom.Help.InputSig.StringValue);
|
||||
|
||||
_helpRequestSent = true;
|
||||
HelpRequestSentFeedback.FireUpdate();
|
||||
|
||||
if (UseHelpRequestTimer)
|
||||
{
|
||||
if (_helpRequestTimeoutTimer == null)
|
||||
{
|
||||
_helpRequestTimeoutTimer = new Timer(HelpRequestTimeoutMs);
|
||||
_helpRequestTimeoutTimer.AutoReset = false;
|
||||
_helpRequestTimeoutTimer.Enabled = true;
|
||||
|
||||
_helpRequestTimeoutTimer.Elapsed += OnTimedEvent;
|
||||
}
|
||||
|
||||
_helpRequestTimeoutTimer.Interval = HelpRequestTimeoutMs;
|
||||
_helpRequestTimeoutTimer.Start();
|
||||
|
||||
this.LogDebug("Help request timeout timer started for room '{0}' with timeout of {1} ms.",
|
||||
Room.Name, HelpRequestTimeoutMs);
|
||||
}
|
||||
|
||||
_helpRequestStatus = eFusionHelpResponse.HelpRequested;
|
||||
HelpRequestStatusFeedback.FireUpdate();
|
||||
}
|
||||
|
||||
private void OnTimedEvent(object source, ElapsedEventArgs e)
|
||||
{
|
||||
this.LogInformation("Help request timeout reached for room '{0}'. Cancelling help request.", Room.Name);
|
||||
CancelHelpRequest();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CancelHelpRequest()
|
||||
{
|
||||
if (_helpRequestSent)
|
||||
{
|
||||
FusionRoom.Help.InputSig.StringValue = "";
|
||||
_helpRequestSent = false;
|
||||
HelpRequestSentFeedback.FireUpdate();
|
||||
_helpRequestStatus = eFusionHelpResponse.None;
|
||||
HelpRequestStatusFeedback.FireUpdate();
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Help request cancelled for room '{0}'", Room.Name);
|
||||
}
|
||||
|
||||
if (_helpRequestTimeoutTimer != null)
|
||||
{
|
||||
_helpRequestTimeoutTimer.Stop();
|
||||
_helpRequestTimeoutTimer.Elapsed -= OnTimedEvent;
|
||||
_helpRequestTimeoutTimer.Dispose();
|
||||
_helpRequestTimeoutTimer = null;
|
||||
this.LogDebug("Help request timeout timer stopped for room '{0}'.", Room.Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ToggleHelpRequest()
|
||||
{
|
||||
if (_helpRequestSent)
|
||||
{
|
||||
CancelHelpRequest();
|
||||
}
|
||||
else
|
||||
{
|
||||
SendHelpRequest();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Extensions to enhance Fusion room, asset and signal creation.
|
||||
/// </summary>
|
||||
public static class FusionRoomExtensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -1648,6 +2003,8 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
/// </summary>
|
||||
public static BooleanSigData CreateOffsetBoolSig(this FusionRoom fr, uint number, string name, eSigIoMask mask)
|
||||
{
|
||||
Debug.LogDebug("Creating Offset Bool Sig: {0} at Join {1}", name, number);
|
||||
|
||||
if (number < 50)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("number", "Cannot be less than 50");
|
||||
@@ -1668,6 +2025,8 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
/// </summary>
|
||||
public static UShortSigData CreateOffsetUshortSig(this FusionRoom fr, uint number, string name, eSigIoMask mask)
|
||||
{
|
||||
Debug.LogDebug("Creating Offset UShort Sig: {0} at Join {1}", name, number);
|
||||
|
||||
if (number < 50)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("number", "Cannot be less than 50");
|
||||
@@ -1688,6 +2047,8 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
/// </summary>
|
||||
public static StringSigData CreateOffsetStringSig(this FusionRoom fr, uint number, string name, eSigIoMask mask)
|
||||
{
|
||||
Debug.LogDebug("Creating Offset String Sig: {0} at Join {1}", name, number);
|
||||
|
||||
if (number < 50)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("number", "Cannot be less than 50");
|
||||
@@ -1803,6 +2164,9 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
/// </summary>
|
||||
public class RoomInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public RoomInformation()
|
||||
{
|
||||
FusionCustomProperties = new List<FusionCustomProperty>();
|
||||
@@ -1855,10 +2219,17 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
/// </summary>
|
||||
public class FusionCustomProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public FusionCustomProperty()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with id
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
public FusionCustomProperty(string id)
|
||||
{
|
||||
ID = id;
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Core.Fusion;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating IEssentialsRoomFusionController devices
|
||||
/// </summary>
|
||||
public class IEssentialsRoomFusionControllerFactory : EssentialsDeviceFactory<IEssentialsRoomFusionController>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public IEssentialsRoomFusionControllerFactory()
|
||||
{
|
||||
TypeNames = new List<string>() { "fusionRoom" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the device
|
||||
/// </summary>
|
||||
/// <param name="dc"></param>
|
||||
/// <returns></returns>
|
||||
public override EssentialsDevice BuildDevice(PepperDash.Essentials.Core.Config.DeviceConfig dc)
|
||||
{
|
||||
Debug.LogDebug("Factory Attempting to create new IEssentialsRoomFusionController Device");
|
||||
|
||||
|
||||
var properties = dc.Properties.ToObject<IEssentialsRoomFusionControllerPropertiesConfig>();
|
||||
|
||||
return new IEssentialsRoomFusionController(dc.Key, dc.Name, properties);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Config properties for an IEssentialsRoomFusionController device
|
||||
/// </summary>
|
||||
public class IEssentialsRoomFusionControllerPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the IP ID of the Fusion Room Controller
|
||||
/// </summary>
|
||||
[JsonProperty("ipId")]
|
||||
public string IpId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IP ID as a UInt16
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint IpIdInt
|
||||
{
|
||||
get
|
||||
{
|
||||
// Try to parse the IpId string to UInt16 as hex
|
||||
if (ushort.TryParse(IpId, System.Globalization.NumberStyles.HexNumber, null, out ushort result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning( "Failed to parse IpId '{0}' as UInt16", IpId);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the join map key
|
||||
/// </summary>
|
||||
[JsonProperty("joinMapKey")]
|
||||
public string JoinMapKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the room key associated with this Fusion Room Controller
|
||||
/// </summary>
|
||||
[JsonProperty("roomKey")]
|
||||
public string RoomKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use HTML format for help requests
|
||||
/// </summary>
|
||||
[JsonProperty("useHtmlFormatForHelpRequests")]
|
||||
public bool UseHtmlFormatForHelpRequests { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use 24-hour time format
|
||||
/// </summary>
|
||||
[JsonProperty("use24HourTimeFormat")]
|
||||
public bool Use24HourTimeFormat { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use a timeout for help requests
|
||||
/// </summary>
|
||||
[JsonProperty("useTimeoutForHelpRequests")]
|
||||
public bool UseTimeoutForHelpRequests { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout duration for help requests in milliseconds
|
||||
/// </summary>
|
||||
[JsonProperty("helpRequestTimeoutMs")]
|
||||
public int HelpRequestTimeoutMs { get; set; } = 30000;
|
||||
}
|
||||
44
src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs
Normal file
44
src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Fusion
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents Fusion Help Request functionality
|
||||
/// </summary>
|
||||
public interface IFusionHelpRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Feedback containing the response to a help request
|
||||
/// </summary>
|
||||
StringFeedback HelpRequestResponseFeedback { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether a help request has been sent
|
||||
/// </summary>
|
||||
BoolFeedback HelpRequestSentFeedback { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Feedback containing the current status of the help request
|
||||
/// </summary>
|
||||
StringFeedback HelpRequestStatusFeedback { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sends a help request
|
||||
/// </summary>
|
||||
void SendHelpRequest();
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current help request status
|
||||
/// </summary>
|
||||
void CancelHelpRequest();
|
||||
|
||||
/// <summary>
|
||||
/// Toggles between sending and cancelling a help request
|
||||
/// </summary>
|
||||
void ToggleHelpRequest();
|
||||
}
|
||||
}
|
||||
37
src/PepperDash.Essentials.Core/Fusion/eFusionHelpResponse.cs
Normal file
37
src/PepperDash.Essentials.Core/Fusion/eFusionHelpResponse.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core.Fusion
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of possible Fusion Help Responses based on the standard responses from Fusion
|
||||
/// </summary>
|
||||
public enum eFusionHelpResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// No help response
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// Help has been requested
|
||||
/// </summary>
|
||||
HelpRequested,
|
||||
/// <summary>
|
||||
/// Help is on the way
|
||||
/// </summary>
|
||||
HelpOnTheWay,
|
||||
/// <summary>
|
||||
/// Please call the helpdesk.
|
||||
/// </summary>
|
||||
CallHelpDesk,
|
||||
/// <summary>
|
||||
/// Rescheduling meeting.
|
||||
/// </summary>
|
||||
ReschedulingMeeting,
|
||||
|
||||
/// <summary>
|
||||
/// Technician taking control.
|
||||
/// </summary>
|
||||
TakingControl,
|
||||
}
|
||||
|
||||
}
|
||||
@@ -49,7 +49,7 @@ namespace PepperDash.Essentials.Core
|
||||
/// </summary>
|
||||
public IRoomOccupancy Room { get; private set; }
|
||||
|
||||
private Fusion.EssentialsHuddleSpaceFusionSystemControllerBase FusionRoom;
|
||||
private Fusion.IEssentialsRoomFusionController FusionRoom;
|
||||
|
||||
public RoomOnToDefaultSourceWhenOccupied(DeviceConfig config) :
|
||||
base (config)
|
||||
@@ -74,7 +74,7 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
var fusionRoomKey = PropertiesConfig.RoomKey + "-fusion";
|
||||
|
||||
FusionRoom = DeviceManager.GetDeviceForKey(fusionRoomKey) as Core.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase;
|
||||
FusionRoom = DeviceManager.GetDeviceForKey(fusionRoomKey) as Core.Fusion.IEssentialsRoomFusionController;
|
||||
|
||||
if (FusionRoom == null)
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Unable to get Fusion Room from Device Manager with key: {0}", fusionRoomKey);
|
||||
|
||||
@@ -105,12 +105,21 @@ namespace PepperDash.Essentials.Room.Config
|
||||
/// </summary>
|
||||
public class EssentialsRoomPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Addresses
|
||||
/// </summary>
|
||||
[JsonProperty("addresses")]
|
||||
public EssentialsRoomAddressPropertiesConfig Addresses { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Description
|
||||
/// </summary>
|
||||
[JsonProperty("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Emergency
|
||||
/// </summary>
|
||||
[JsonProperty("emergency")]
|
||||
public EssentialsRoomEmergencyConfig Emergency { get; set; }
|
||||
|
||||
@@ -226,11 +235,11 @@ namespace PepperDash.Essentials.Room.Config
|
||||
/// Indicates if this room represents a combination of other rooms
|
||||
/// </summary>
|
||||
[JsonProperty("isRoomCombinationScenario")]
|
||||
/// <summary>
|
||||
/// Gets or sets the IsRoomCombinationScenario
|
||||
/// </summary>
|
||||
public bool IsRoomCombinationScenario { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public EssentialsRoomPropertiesConfig()
|
||||
{
|
||||
LogoLight = new EssentialsLogoPropertiesConfig();
|
||||
@@ -243,10 +252,10 @@ namespace PepperDash.Essentials.Room.Config
|
||||
/// </summary>
|
||||
public class EssentialsRoomUiBehaviorConfig
|
||||
{
|
||||
[JsonProperty("disableActivityButtonsWhileWarmingCooling")]
|
||||
/// <summary>
|
||||
/// Gets or sets the DisableActivityButtonsWhileWarmingCooling
|
||||
/// </summary>
|
||||
[JsonProperty("disableActivityButtonsWhileWarmingCooling")]
|
||||
public bool DisableActivityButtonsWhileWarmingCooling { get; set; }
|
||||
}
|
||||
|
||||
@@ -255,74 +264,86 @@ namespace PepperDash.Essentials.Room.Config
|
||||
/// </summary>
|
||||
public class EssentialsAvRoomPropertiesConfig : EssentialsRoomPropertiesConfig
|
||||
{
|
||||
[JsonProperty("defaultAudioKey")]
|
||||
/// <summary>
|
||||
/// Gets or sets the DefaultAudioKey
|
||||
/// </summary>
|
||||
[JsonProperty("defaultAudioKey")]
|
||||
public string DefaultAudioKey { get; set; }
|
||||
[JsonProperty("sourceListKey")]
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DefaultOnDspPresetKey
|
||||
/// </summary>
|
||||
[JsonProperty("defaultOnDspPresetKey")]
|
||||
public string DefaultOnDspPresetKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DefaultOffDspPresetKey
|
||||
/// </summary>
|
||||
[JsonProperty("defaultOffDspPresetKey")]
|
||||
public string DefaultOffDspPresetKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SourceListKey
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
[JsonProperty("sourceListKey")]
|
||||
public string SourceListKey { get; set; }
|
||||
[JsonProperty("destinationListKey")]
|
||||
/// <summary>
|
||||
/// Gets or sets the DestinationListKey
|
||||
/// </summary>
|
||||
[JsonProperty("destinationListKey")]
|
||||
public string DestinationListKey { get; set; }
|
||||
[JsonProperty("audioControlPointListKey")]
|
||||
/// <summary>
|
||||
/// Gets or sets the AudioControlPointListKey
|
||||
/// </summary>
|
||||
[JsonProperty("audioControlPointListKey")]
|
||||
public string AudioControlPointListKey { get; set; }
|
||||
[JsonProperty("cameraListKey")]
|
||||
/// <summary>
|
||||
/// Gets or sets the CameraListKey
|
||||
/// </summary>
|
||||
[JsonProperty("cameraListKey")]
|
||||
public string CameraListKey { get; set; }
|
||||
|
||||
|
||||
[JsonProperty("defaultSourceItem")]
|
||||
/// <summary>
|
||||
/// Gets or sets the DefaultSourceItem
|
||||
/// </summary>
|
||||
[JsonProperty("defaultSourceItem")]
|
||||
public string DefaultSourceItem { get; set; }
|
||||
/// <summary>
|
||||
/// Indicates if the room supports advanced sharing
|
||||
/// </summary>
|
||||
[JsonProperty("supportsAdvancedSharing")]
|
||||
/// <summary>
|
||||
/// Gets or sets the SupportsAdvancedSharing
|
||||
/// </summary>
|
||||
public bool SupportsAdvancedSharing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if non-tech users can change the share mode
|
||||
/// </summary>
|
||||
[JsonProperty("userCanChangeShareMode")]
|
||||
/// <summary>
|
||||
/// Gets or sets the UserCanChangeShareMode
|
||||
/// </summary>
|
||||
public bool UserCanChangeShareMode { get; set; }
|
||||
|
||||
|
||||
[JsonProperty("matrixRoutingKey", NullValueHandling = NullValueHandling.Ignore)]
|
||||
/// <summary>
|
||||
/// Gets or sets the MatrixRoutingKey
|
||||
/// </summary>
|
||||
[JsonProperty("matrixRoutingKey", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string MatrixRoutingKey { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a EssentialsConferenceRoomPropertiesConfig
|
||||
/// </summary>
|
||||
public class EssentialsConferenceRoomPropertiesConfig : EssentialsAvRoomPropertiesConfig
|
||||
{
|
||||
[JsonProperty("videoCodecKey")]
|
||||
/// <summary>
|
||||
/// Gets or sets the VideoCodecKey
|
||||
/// </summary>
|
||||
[JsonProperty("videoCodecKey")]
|
||||
public string VideoCodecKey { get; set; }
|
||||
[JsonProperty("audioCodecKey")]
|
||||
/// <summary>
|
||||
/// Gets or sets the AudioCodecKey
|
||||
/// </summary>
|
||||
[JsonProperty("audioCodecKey")]
|
||||
public string AudioCodecKey { get; set; }
|
||||
|
||||
}
|
||||
@@ -337,12 +358,15 @@ namespace PepperDash.Essentials.Room.Config
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
[JsonProperty("deviceKeys")]
|
||||
/// <summary>
|
||||
/// Gets or sets the DeviceKeys
|
||||
/// </summary>
|
||||
[JsonProperty("deviceKeys")]
|
||||
public List<string> DeviceKeys { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public EssentialsEnvironmentPropertiesConfig()
|
||||
{
|
||||
DeviceKeys = new List<string>();
|
||||
@@ -355,6 +379,9 @@ namespace PepperDash.Essentials.Room.Config
|
||||
/// </summary>
|
||||
public class EssentialsRoomFusionConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the the IpId as a UInt16
|
||||
/// </summary>
|
||||
public uint IpIdInt
|
||||
{
|
||||
get
|
||||
@@ -371,16 +398,16 @@ namespace PepperDash.Essentials.Room.Config
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty("ipId")]
|
||||
/// <summary>
|
||||
/// Gets or sets the IpId
|
||||
/// </summary>
|
||||
[JsonProperty("ipId")]
|
||||
public string IpId { get; set; }
|
||||
|
||||
[JsonProperty("joinMapKey")]
|
||||
/// <summary>
|
||||
/// Gets or sets the JoinMapKey
|
||||
/// </summary>
|
||||
[JsonProperty("joinMapKey")]
|
||||
public string JoinMapKey { get; set; }
|
||||
|
||||
}
|
||||
@@ -390,16 +417,16 @@ namespace PepperDash.Essentials.Room.Config
|
||||
/// </summary>
|
||||
public class EssentialsRoomMicrophonePrivacyConfig
|
||||
{
|
||||
[JsonProperty("deviceKey")]
|
||||
/// <summary>
|
||||
/// Gets or sets the DeviceKey
|
||||
/// </summary>
|
||||
[JsonProperty("deviceKey")]
|
||||
public string DeviceKey { get; set; }
|
||||
|
||||
[JsonProperty("behaviour")]
|
||||
/// <summary>
|
||||
/// Gets or sets the Behaviour
|
||||
/// </summary>
|
||||
[JsonProperty("behaviour")]
|
||||
public string Behaviour { get; set; }
|
||||
}
|
||||
|
||||
@@ -408,12 +435,15 @@ namespace PepperDash.Essentials.Room.Config
|
||||
/// </summary>
|
||||
public class EssentialsHelpPropertiesConfig
|
||||
{
|
||||
[JsonProperty("message")]
|
||||
/// <summary>
|
||||
/// Gets or sets the Message
|
||||
/// </summary>
|
||||
[JsonProperty("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ShowCallButton
|
||||
/// </summary>
|
||||
[JsonProperty("showCallButton")]
|
||||
public bool ShowCallButton { get; set; }
|
||||
|
||||
@@ -421,11 +451,11 @@ namespace PepperDash.Essentials.Room.Config
|
||||
/// Defaults to "Call Help Desk"
|
||||
/// </summary>
|
||||
[JsonProperty("callButtonText")]
|
||||
/// <summary>
|
||||
/// Gets or sets the CallButtonText
|
||||
/// </summary>
|
||||
public string CallButtonText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public EssentialsHelpPropertiesConfig()
|
||||
{
|
||||
CallButtonText = "Call Help Desk";
|
||||
@@ -437,22 +467,28 @@ namespace PepperDash.Essentials.Room.Config
|
||||
/// </summary>
|
||||
public class EssentialsOneButtonMeetingPropertiesConfig
|
||||
{
|
||||
[JsonProperty("enable")]
|
||||
/// <summary>
|
||||
/// Gets or sets the Enable
|
||||
/// </summary>
|
||||
[JsonProperty("enable")]
|
||||
public bool Enable { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a EssentialsRoomAddressPropertiesConfig
|
||||
/// </summary>
|
||||
public class EssentialsRoomAddressPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the PhoneNumber
|
||||
/// </summary>
|
||||
[JsonProperty("phoneNumber")]
|
||||
public string PhoneNumber { get; set; }
|
||||
|
||||
[JsonProperty("sipAddress")]
|
||||
/// <summary>
|
||||
/// Gets or sets the SipAddress
|
||||
/// </summary>
|
||||
[JsonProperty("sipAddress")]
|
||||
public string SipAddress { get; set; }
|
||||
}
|
||||
|
||||
@@ -462,14 +498,18 @@ namespace PepperDash.Essentials.Room.Config
|
||||
/// </summary>
|
||||
public class EssentialsLogoPropertiesConfig
|
||||
{
|
||||
[JsonProperty("type")]
|
||||
/// <summary>
|
||||
/// Gets or sets the Type
|
||||
/// </summary>
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Url
|
||||
/// </summary>
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// GetLogoUrlLight method
|
||||
/// </summary>
|
||||
@@ -502,22 +542,28 @@ namespace PepperDash.Essentials.Room.Config
|
||||
/// </summary>
|
||||
public class EssentialsRoomOccSensorConfig
|
||||
{
|
||||
[JsonProperty("deviceKey")]
|
||||
/// <summary>
|
||||
/// Gets or sets the DeviceKey
|
||||
/// </summary>
|
||||
[JsonProperty("deviceKey")]
|
||||
public string DeviceKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TimeoutMinutes
|
||||
/// </summary>
|
||||
[JsonProperty("timeoutMinutes")]
|
||||
public int TimeoutMinutes { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a EssentialsRoomTechConfig
|
||||
/// </summary>
|
||||
public class EssentialsRoomTechConfig
|
||||
{
|
||||
[JsonProperty("password")]
|
||||
/// <summary>
|
||||
/// Gets or sets the Password
|
||||
/// </summary>
|
||||
[JsonProperty("password")]
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -408,7 +408,7 @@ namespace PepperDash.Essentials.Core
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Timeout Minutes from Config is: {0}", timeoutMinutes);
|
||||
|
||||
// If status provider is fusion, set flag to remote
|
||||
if (statusProvider is Core.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase)
|
||||
if (statusProvider is Core.Fusion.IEssentialsRoomFusionController)
|
||||
OccupancyStatusProviderIsRemote = true;
|
||||
|
||||
if(timeoutMinutes > 0)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using Crestron.SimplSharpPro.Keypads;
|
||||
using PepperDash.Essentials.Core.Queues;
|
||||
using PepperDash.Essentials.Core.Routing;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Crestron.SimplSharpPro.Keypads;
|
||||
using PepperDash.Essentials.Core.Queues;
|
||||
using PepperDash.Essentials.Core.Routing;
|
||||
using Serilog.Events;
|
||||
using Debug = PepperDash.Core.Debug;
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace PepperDash.Essentials.Core
|
||||
public static (RouteDescriptor, RouteDescriptor) GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort, RoutingOutputPort sourcePort)
|
||||
{
|
||||
// if it's a single signal type, find the route
|
||||
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo) &&
|
||||
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo) &&
|
||||
!(signalType.HasFlag(eRoutingSignalType.Video) && signalType.HasFlag(eRoutingSignalType.SecondaryAudio)))
|
||||
{
|
||||
var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, signalType);
|
||||
@@ -134,14 +134,15 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
// otherwise, audioVideo needs to be handled as two steps.
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key);
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {destinationKey} to {sourceKey} of type {type}", destination, source.Key, signalType);
|
||||
|
||||
RouteDescriptor audioRouteDescriptor;
|
||||
|
||||
if (signalType.HasFlag(eRoutingSignalType.SecondaryAudio))
|
||||
{
|
||||
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.SecondaryAudio);
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Audio);
|
||||
}
|
||||
@@ -199,13 +200,13 @@ namespace PepperDash.Essentials.Core
|
||||
Source = source,
|
||||
SourcePort = sourcePort,
|
||||
SignalType = signalType
|
||||
};
|
||||
};
|
||||
|
||||
var coolingDevice = destination as IWarmingCooling;
|
||||
|
||||
//We already have a route request for this device, and it's a cooling device and is cooling
|
||||
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
|
||||
{
|
||||
{
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
|
||||
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
|
||||
@@ -219,7 +220,7 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
//New Request
|
||||
if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
|
||||
{
|
||||
{
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
|
||||
|
||||
RouteRequests.Add(destination.Key, routeRequest);
|
||||
@@ -239,9 +240,9 @@ namespace PepperDash.Essentials.Core
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
|
||||
}
|
||||
|
||||
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty, false));
|
||||
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, destinationPort?.Key ?? string.Empty, false));
|
||||
|
||||
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
|
||||
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -272,7 +273,8 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
audioOrSingleRoute.ExecuteRoutes();
|
||||
videoRoute?.ExecuteRoutes();
|
||||
} catch(Exception ex)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request);
|
||||
}
|
||||
@@ -305,9 +307,10 @@ namespace PepperDash.Essentials.Core
|
||||
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
|
||||
current.ReleaseRoutes(clearRoute);
|
||||
}
|
||||
} catch (Exception ex)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'",null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||
Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'", null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
/// <param name="inputPort">The currently selected input port on the destination device.</param>
|
||||
private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort)
|
||||
{
|
||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this,destination?.Key, inputPort?.Key);
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this, destination?.Key, inputPort?.Key);
|
||||
|
||||
if(inputPort == null)
|
||||
{
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using PepperDash.Essentials.Core;
|
||||
using Crestron.SimplSharpPro.DeviceSupport;
|
||||
using PepperDash.Core;
|
||||
using Crestron.SimplSharpPro.UI;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.DeviceSupport;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Essentials.Core.UI
|
||||
{
|
||||
public abstract class TouchpanelBase: EssentialsDevice, IHasBasicTriListWithSmartObject
|
||||
/// <summary>
|
||||
/// Base class for Touchpanel devices
|
||||
/// </summary>
|
||||
public abstract class TouchpanelBase : EssentialsDevice, IHasBasicTriListWithSmartObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the configuration for the Crestron touchpanel.
|
||||
/// </summary>
|
||||
protected CrestronTouchpanelPropertiesConfig _config;
|
||||
/// <summary>
|
||||
/// Gets or sets the Panel
|
||||
@@ -27,12 +29,11 @@ namespace PepperDash.Essentials.Core.UI
|
||||
/// is provided.
|
||||
/// </summary>
|
||||
/// <param name="key">Essentials Device Key</param>
|
||||
/// <param name="name">Essentials Device Name</param>
|
||||
/// <param name="type">Touchpanel Type to build</param>
|
||||
/// <param name="config">Touchpanel Configuration</param>
|
||||
/// <param name="id">IP-ID to use for touch panel</param>
|
||||
/// <param name="name">Essentials Device Name</param>
|
||||
/// <param name="panel">Crestron Touchpanel Device</param>
|
||||
/// <param name="config">Touchpanel Configuration</param>
|
||||
protected TouchpanelBase(string key, string name, BasicTriListWithSmartObject panel, CrestronTouchpanelPropertiesConfig config)
|
||||
:base(key, name)
|
||||
: base(key, name)
|
||||
{
|
||||
|
||||
if (panel == null)
|
||||
@@ -55,23 +56,21 @@ namespace PepperDash.Essentials.Core.UI
|
||||
tsw.ButtonStateChange += Tsw_ButtonStateChange;
|
||||
}
|
||||
|
||||
_config = config;
|
||||
|
||||
AddPreActivationAction(() => {
|
||||
if (Panel.Register() != eDeviceRegistrationUnRegistrationResponse.Success)
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Registration failed. Continuing, but panel may not function: {0}", Panel.RegistrationFailureReason);
|
||||
_config = config;
|
||||
|
||||
AddPreActivationAction(() =>
|
||||
{
|
||||
// Give up cleanly if SGD is not present.
|
||||
var sgdName = Global.FilePathPrefix + "sgd" + Global.DirectorySeparator + _config.SgdFile;
|
||||
if (!File.Exists(sgdName))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Smart object file '{0}' not present in User folder. Looking for embedded file", sgdName);
|
||||
this.LogInformation("Smart object file '{0}' not present in User folder. Looking for embedded file", sgdName);
|
||||
|
||||
sgdName = Global.ApplicationDirectoryPathPrefix + Global.DirectorySeparator + "SGD" + Global.DirectorySeparator + _config.SgdFile;
|
||||
|
||||
if (!File.Exists(sgdName))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Unable to find SGD file '{0}' in User sgd or application SGD folder. Exiting touchpanel load.", sgdName);
|
||||
this.LogWarning("Unable to find SGD file '{0}' in User sgd or application SGD folder. Exiting touchpanel load.", sgdName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -82,12 +81,11 @@ namespace PepperDash.Essentials.Core.UI
|
||||
AddPostActivationAction(() =>
|
||||
{
|
||||
// Check for IEssentialsRoomCombiner in DeviceManager and if found, subscribe to its event
|
||||
var roomCombiner = DeviceManager.AllDevices.FirstOrDefault((d) => d is IEssentialsRoomCombiner) as IEssentialsRoomCombiner;
|
||||
|
||||
if (roomCombiner != null)
|
||||
if (DeviceManager.AllDevices.FirstOrDefault((d) => d is IEssentialsRoomCombiner) is IEssentialsRoomCombiner roomCombiner)
|
||||
{
|
||||
// Subscribe to the even
|
||||
roomCombiner.RoomCombinationScenarioChanged += new EventHandler<EventArgs>(roomCombiner_RoomCombinationScenarioChanged);
|
||||
roomCombiner.RoomCombinationScenarioChanged += new EventHandler<EventArgs>(RoomCombiner_RoomCombinationScenarioChanged);
|
||||
|
||||
// Connect to the initial roomKey
|
||||
if (roomCombiner.CurrentScenario != null)
|
||||
@@ -106,6 +104,11 @@ namespace PepperDash.Essentials.Core.UI
|
||||
// No room combiner, use the default key
|
||||
SetupPanelDrivers(_config.DefaultRoomKey);
|
||||
}
|
||||
|
||||
var panelRegistrationResponse = Panel.Register();
|
||||
|
||||
if (panelRegistrationResponse != eDeviceRegistrationUnRegistrationResponse.Success)
|
||||
this.LogInformation("WARNING: Registration failed. Continuing, but panel may not function: {0}", Panel.RegistrationFailureReason);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,7 +118,6 @@ namespace PepperDash.Essentials.Core.UI
|
||||
/// <param name="roomKey">Room Key for this panel</param>
|
||||
protected abstract void SetupPanelDrivers(string roomKey);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for System Extender Events
|
||||
/// </summary>
|
||||
@@ -129,7 +131,7 @@ namespace PepperDash.Essentials.Core.UI
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void roomCombiner_RoomCombinationScenarioChanged(object sender, EventArgs e)
|
||||
protected virtual void RoomCombiner_RoomCombinationScenarioChanged(object sender, EventArgs e)
|
||||
{
|
||||
var roomCombiner = sender as IEssentialsRoomCombiner;
|
||||
|
||||
@@ -156,23 +158,23 @@ namespace PepperDash.Essentials.Core.UI
|
||||
SetupPanelDrivers(newRoomKey);
|
||||
}
|
||||
|
||||
private void Panel_SigChange(object currentDevice, Crestron.SimplSharpPro.SigEventArgs args)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Sig change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
|
||||
var uo = args.Sig.UserObject;
|
||||
if (uo is Action<bool>)
|
||||
(uo as Action<bool>)(args.Sig.BoolValue);
|
||||
else if (uo is Action<ushort>)
|
||||
(uo as Action<ushort>)(args.Sig.UShortValue);
|
||||
else if (uo is Action<string>)
|
||||
(uo as Action<string>)(args.Sig.StringValue);
|
||||
}
|
||||
|
||||
private void Tsw_ButtonStateChange(GenericBase device, ButtonEventArgs args)
|
||||
{
|
||||
var uo = args.Button.UserObject;
|
||||
if(uo is Action<bool>)
|
||||
(uo as Action<bool>)(args.Button.State == eButtonState.Pressed);
|
||||
}
|
||||
private void Panel_SigChange(object currentDevice, SigEventArgs args)
|
||||
{
|
||||
this.LogVerbose("Sig change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
|
||||
var uo = args.Sig.UserObject;
|
||||
if (uo is Action<bool>)
|
||||
(uo as Action<bool>)(args.Sig.BoolValue);
|
||||
else if (uo is Action<ushort>)
|
||||
(uo as Action<ushort>)(args.Sig.UShortValue);
|
||||
else if (uo is Action<string>)
|
||||
(uo as Action<string>)(args.Sig.StringValue);
|
||||
}
|
||||
|
||||
private void Tsw_ButtonStateChange(GenericBase device, ButtonEventArgs args)
|
||||
{
|
||||
var uo = args.Button.UserObject;
|
||||
if (uo is Action<bool>)
|
||||
(uo as Action<bool>)(args.Button.State == eButtonState.Pressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ using PepperDash.Core.Web.RequestHandlers;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a SetDeviceStreamDebugRequestHandler
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Represents a SetDeviceStreamDebugRequestHandler
|
||||
/// </summary>
|
||||
public class SetDeviceStreamDebugRequestHandler : WebApiBaseRequestHandler
|
||||
{
|
||||
/// <summary>
|
||||
@@ -122,23 +122,23 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(DeviceManager.GetDeviceForKey(body.DeviceKey) is IStreamDebugging device))
|
||||
{
|
||||
context.Response.StatusCode = 404;
|
||||
context.Response.StatusDescription = "Not Found";
|
||||
context.Response.End();
|
||||
if (!(DeviceManager.GetDeviceForKey(body.DeviceKey) is IStreamDebugging device))
|
||||
{
|
||||
context.Response.StatusCode = 404;
|
||||
context.Response.StatusDescription = "Not Found";
|
||||
context.Response.End();
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
eStreamDebuggingSetting debugSetting;
|
||||
eStreamDebuggingSetting debugSetting;
|
||||
try
|
||||
{
|
||||
debugSetting = (eStreamDebuggingSetting) Enum.Parse(typeof (eStreamDebuggingSetting), body.Setting, true);
|
||||
debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), body.Setting, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Exception handling set debug request");
|
||||
Debug.LogMessage(ex, "Exception handling set debug request");
|
||||
context.Response.StatusCode = 500;
|
||||
context.Response.StatusDescription = "Internal Server Error";
|
||||
context.Response.End();
|
||||
@@ -164,7 +164,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Exception handling set debug request");
|
||||
Debug.LogMessage(ex, "Exception handling set debug request");
|
||||
context.Response.StatusCode = 500;
|
||||
context.Response.StatusDescription = "Internal Server Error";
|
||||
context.Response.End();
|
||||
@@ -198,21 +198,21 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||
public class SetDeviceStreamDebugConfig
|
||||
{
|
||||
[JsonProperty("deviceKey", NullValueHandling = NullValueHandling.Include)]
|
||||
/// <summary>
|
||||
/// Gets or sets the DeviceKey
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Gets or sets the DeviceKey
|
||||
/// </summary>
|
||||
public string DeviceKey { get; set; }
|
||||
|
||||
[JsonProperty("setting", NullValueHandling = NullValueHandling.Include)]
|
||||
/// <summary>
|
||||
/// Gets or sets the Setting
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Gets or sets the Setting
|
||||
/// </summary>
|
||||
public string Setting { get; set; }
|
||||
|
||||
[JsonProperty("timeout")]
|
||||
/// <summary>
|
||||
/// Gets or sets the Timeout
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Gets or sets the Timeout
|
||||
/// </summary>
|
||||
public int Timeout { get; set; }
|
||||
|
||||
public SetDeviceStreamDebugConfig()
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Timers;
|
||||
using Crestron.SimplSharp;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using PepperDash.Essentials.Core.CrestronIO;
|
||||
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
||||
using Serilog.Events;
|
||||
using PepperDash.Essentials.Devices.Common.Displays;
|
||||
|
||||
namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration for requested state
|
||||
/// </summary>
|
||||
enum RequestedState
|
||||
{
|
||||
None,
|
||||
Raise,
|
||||
Lower
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls a single shade using three relays
|
||||
/// </summary>
|
||||
@@ -20,11 +32,16 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
readonly ScreenLiftRelaysConfig LowerRelayConfig;
|
||||
readonly ScreenLiftRelaysConfig LatchedRelayConfig;
|
||||
|
||||
Displays.DisplayBase DisplayDevice;
|
||||
DisplayBase DisplayDevice;
|
||||
ISwitchedOutput RaiseRelay;
|
||||
ISwitchedOutput LowerRelay;
|
||||
ISwitchedOutput LatchedRelay;
|
||||
|
||||
private bool _isMoving;
|
||||
private RequestedState _requestedState;
|
||||
private RequestedState _currentMovement;
|
||||
private Timer _movementTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the InUpPosition
|
||||
/// </summary>
|
||||
@@ -80,6 +97,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
|
||||
IsInUpPosition = new BoolFeedback("isInUpPosition", () => _isInUpPosition);
|
||||
|
||||
// Initialize movement timer for reuse
|
||||
_movementTimer = new Timer();
|
||||
_movementTimer.Elapsed += OnMovementComplete;
|
||||
_movementTimer.AutoReset = false;
|
||||
|
||||
switch (Mode)
|
||||
{
|
||||
case eScreenLiftControlMode.momentary:
|
||||
@@ -129,25 +151,25 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
{
|
||||
case eScreenLiftControlMode.momentary:
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting relays for {Mode}");
|
||||
this.LogDebug("Getting relays for {mode}", Mode);
|
||||
RaiseRelay = GetSwitchedOutputFromDevice(RaiseRelayConfig.DeviceKey);
|
||||
LowerRelay = GetSwitchedOutputFromDevice(LowerRelayConfig.DeviceKey);
|
||||
break;
|
||||
}
|
||||
case eScreenLiftControlMode.latched:
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting relays for {Mode}");
|
||||
this.LogDebug("Getting relays for {mode}", Mode);
|
||||
LatchedRelay = GetSwitchedOutputFromDevice(LatchedRelayConfig.DeviceKey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting display with key {DisplayDeviceKey}");
|
||||
this.LogDebug("Getting display with key {displayKey}", DisplayDeviceKey);
|
||||
DisplayDevice = GetDisplayBaseFromDevice(DisplayDeviceKey);
|
||||
|
||||
if (DisplayDevice != null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Subscribing to {DisplayDeviceKey} feedbacks");
|
||||
this.LogDebug("Subscribing to {displayKey} feedbacks", DisplayDeviceKey);
|
||||
|
||||
DisplayDevice.IsWarmingUpFeedback.OutputChange += IsWarmingUpFeedback_OutputChange;
|
||||
DisplayDevice.IsCoolingDownFeedback.OutputChange += IsCoolingDownFeedback_OutputChange;
|
||||
@@ -163,22 +185,49 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
{
|
||||
if (RaiseRelay == null && LatchedRelay == null) return;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Raising {Type}");
|
||||
this.LogDebug("Raise called for {type}", Type);
|
||||
|
||||
// If device is moving, bank the command
|
||||
if (_isMoving)
|
||||
{
|
||||
this.LogDebug("Device is moving, banking Raise command");
|
||||
_requestedState = RequestedState.Raise;
|
||||
return;
|
||||
}
|
||||
|
||||
this.LogDebug("Raising {type}", Type);
|
||||
|
||||
switch (Mode)
|
||||
{
|
||||
case eScreenLiftControlMode.momentary:
|
||||
{
|
||||
PulseOutput(RaiseRelay, RaiseRelayConfig.PulseTimeInMs);
|
||||
|
||||
// Set moving flag and start timer if movement time is configured
|
||||
if (RaiseRelayConfig.MoveTimeInMs > 0)
|
||||
{
|
||||
_isMoving = true;
|
||||
_currentMovement = RequestedState.Raise;
|
||||
if (_movementTimer.Enabled)
|
||||
{
|
||||
_movementTimer.Stop();
|
||||
}
|
||||
_movementTimer.Interval = RaiseRelayConfig.MoveTimeInMs;
|
||||
_movementTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
InUpPosition = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case eScreenLiftControlMode.latched:
|
||||
{
|
||||
LatchedRelay.Off();
|
||||
InUpPosition = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
InUpPosition = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -188,59 +237,145 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
{
|
||||
if (LowerRelay == null && LatchedRelay == null) return;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Lowering {Type}");
|
||||
this.LogDebug("Lower called for {type}", Type);
|
||||
|
||||
// If device is moving, bank the command
|
||||
if (_isMoving)
|
||||
{
|
||||
this.LogDebug("Device is moving, banking Lower command");
|
||||
_requestedState = RequestedState.Lower;
|
||||
return;
|
||||
}
|
||||
|
||||
this.LogDebug("Lowering {type}", Type);
|
||||
|
||||
switch (Mode)
|
||||
{
|
||||
case eScreenLiftControlMode.momentary:
|
||||
{
|
||||
PulseOutput(LowerRelay, LowerRelayConfig.PulseTimeInMs);
|
||||
|
||||
// Set moving flag and start timer if movement time is configured
|
||||
if (LowerRelayConfig.MoveTimeInMs > 0)
|
||||
{
|
||||
_isMoving = true;
|
||||
_currentMovement = RequestedState.Lower;
|
||||
if (_movementTimer.Enabled)
|
||||
{
|
||||
_movementTimer.Stop();
|
||||
}
|
||||
_movementTimer.Interval = LowerRelayConfig.MoveTimeInMs;
|
||||
_movementTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
InUpPosition = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case eScreenLiftControlMode.latched:
|
||||
{
|
||||
LatchedRelay.On();
|
||||
InUpPosition = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
InUpPosition = false;
|
||||
}
|
||||
|
||||
void PulseOutput(ISwitchedOutput output, int pulseTime)
|
||||
private void DisposeMovementTimer()
|
||||
{
|
||||
output.On();
|
||||
CTimer pulseTimer = new CTimer(new CTimerCallbackFunction((o) => output.Off()), pulseTime);
|
||||
if (_movementTimer != null)
|
||||
{
|
||||
_movementTimer.Stop();
|
||||
_movementTimer.Elapsed -= OnMovementComplete;
|
||||
_movementTimer.Dispose();
|
||||
_movementTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the port on teh specified device from config
|
||||
/// Called when movement timer completes
|
||||
/// </summary>
|
||||
/// <param name="relayKey"></param>
|
||||
/// <returns></returns>
|
||||
ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey)
|
||||
private void OnMovementComplete(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
var portDevice = DeviceManager.GetDeviceForKey(relayKey);
|
||||
this.LogDebug("Movement complete");
|
||||
|
||||
// Update position based on completed movement
|
||||
if (_currentMovement == RequestedState.Raise)
|
||||
{
|
||||
InUpPosition = true;
|
||||
}
|
||||
else if (_currentMovement == RequestedState.Lower)
|
||||
{
|
||||
InUpPosition = false;
|
||||
}
|
||||
|
||||
_isMoving = false;
|
||||
_currentMovement = RequestedState.None;
|
||||
|
||||
// Execute banked command if one exists
|
||||
if (_requestedState != RequestedState.None)
|
||||
{
|
||||
this.LogDebug("Executing next command: {command}", _requestedState);
|
||||
|
||||
var commandToExecute = _requestedState;
|
||||
_requestedState = RequestedState.None;
|
||||
|
||||
// Check if current state matches what the banked command would do and execute if different
|
||||
switch (commandToExecute)
|
||||
{
|
||||
case RequestedState.Raise:
|
||||
Raise();
|
||||
break;
|
||||
|
||||
case RequestedState.Lower:
|
||||
Lower();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PulseOutput(ISwitchedOutput output, int pulseTime)
|
||||
{
|
||||
output.On();
|
||||
|
||||
var timer = new Timer(pulseTime)
|
||||
{
|
||||
AutoReset = false
|
||||
};
|
||||
|
||||
timer.Elapsed += (sender, e) =>
|
||||
{
|
||||
output.Off();
|
||||
timer.Dispose();
|
||||
};
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey)
|
||||
{
|
||||
var portDevice = DeviceManager.GetDeviceForKey<ISwitchedOutput>(relayKey);
|
||||
if (portDevice != null)
|
||||
{
|
||||
return portDevice as ISwitchedOutput;
|
||||
return portDevice;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Error: Unable to get relay device with key '{0}'", relayKey);
|
||||
this.LogWarning("Error: Unable to get relay device with key '{relayKey}'", relayKey);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Displays.DisplayBase GetDisplayBaseFromDevice(string displayKey)
|
||||
private DisplayBase GetDisplayBaseFromDevice(string displayKey)
|
||||
{
|
||||
var displayDevice = DeviceManager.GetDeviceForKey(displayKey);
|
||||
var displayDevice = DeviceManager.GetDeviceForKey<DisplayBase>(displayKey);
|
||||
if (displayDevice != null)
|
||||
{
|
||||
return displayDevice as Displays.DisplayBase;
|
||||
return displayDevice;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Error: Unable to get display device with key '{0}'", displayKey);
|
||||
this.LogWarning("Error: Unable to get display device with key '{displayKey}'", displayKey);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -248,7 +383,7 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a ScreenLiftControllerFactory
|
||||
/// Factory for ScreenLiftController devices
|
||||
/// </summary>
|
||||
public class ScreenLiftControllerFactory : EssentialsDeviceFactory<RelayControlledShade>
|
||||
{
|
||||
@@ -260,14 +395,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
TypeNames = new List<string>() { "screenliftcontroller" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BuildDevice method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Comm Device");
|
||||
var props = Newtonsoft.Json.JsonConvert.DeserializeObject<ScreenLiftControllerConfigProperties>(dc.Properties.ToString());
|
||||
Debug.LogDebug("Factory Attempting to create new ScreenLiftController Device");
|
||||
var props = dc.Properties.ToObject<ScreenLiftControllerConfigProperties>();
|
||||
|
||||
return new ScreenLiftController(dc.Key, dc.Name, props);
|
||||
}
|
||||
|
||||
@@ -18,5 +18,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
/// </summary>
|
||||
[JsonProperty("pulseTimeInMs")]
|
||||
public int PulseTimeInMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the MoveTimeInMs - time in milliseconds for the movement to complete
|
||||
/// </summary>
|
||||
[JsonProperty("moveTimeInMs")]
|
||||
public int MoveTimeInMs { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using PepperDash.Essentials.Core.Routing;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Essentials.Devices.Common.Generic
|
||||
@@ -9,8 +12,17 @@ namespace PepperDash.Essentials.Devices.Common.Generic
|
||||
/// <summary>
|
||||
/// Represents a GenericSink
|
||||
/// </summary>
|
||||
public class GenericSink : EssentialsDevice, IRoutingSinkWithInputPort
|
||||
public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputPort, ICurrentSources
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler CurrentSourcesChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the GenericSink class
|
||||
/// </summary>
|
||||
@@ -20,9 +32,52 @@ namespace PepperDash.Essentials.Devices.Common.Generic
|
||||
{
|
||||
InputPorts = new RoutingPortCollection<RoutingInputPort>();
|
||||
|
||||
var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this);
|
||||
var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo | eRoutingSignalType.SecondaryAudio, eRoutingPortConnectionType.Hdmi, null, this);
|
||||
|
||||
InputPorts.Add(inputPort);
|
||||
|
||||
CurrentSources = new Dictionary<eRoutingSignalType, SourceListItem>
|
||||
{
|
||||
{ eRoutingSignalType.Audio, null },
|
||||
{ eRoutingSignalType.Video, null },
|
||||
};
|
||||
|
||||
CurrentSourceKeys = new Dictionary<eRoutingSignalType, string>
|
||||
{
|
||||
{ eRoutingSignalType.Audio, string.Empty },
|
||||
{ eRoutingSignalType.Video, string.Empty },
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
|
||||
{
|
||||
foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType)))
|
||||
{
|
||||
var flagValue = Convert.ToInt32(type);
|
||||
// Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag).
|
||||
// (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set.
|
||||
if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0)
|
||||
{
|
||||
this.LogDebug("Skipping {type}", type);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.LogDebug("setting {type}", type);
|
||||
|
||||
if (signalType.HasFlag(type))
|
||||
{
|
||||
UpdateCurrentSources(type, sourceListKey, sourceListItem);
|
||||
}
|
||||
}
|
||||
// Raise the CurrentSourcesChanged event
|
||||
CurrentSourcesChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void UpdateCurrentSources(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
|
||||
{
|
||||
CurrentSources[signalType] = sourceListItem;
|
||||
CurrentSourceKeys[signalType] = sourceListKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -66,6 +121,15 @@ namespace PepperDash.Essentials.Devices.Common.Generic
|
||||
/// Event fired when the current source changes
|
||||
/// </summary>
|
||||
public event SourceInfoChangeHandler CurrentSourceChange;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event InputChangedEventHandler InputChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ExecuteSwitch(object inputSelector)
|
||||
{
|
||||
this.LogDebug("GenericSink Executing Switch to: {inputSelector}", inputSelector);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -39,10 +40,14 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
|
||||
sourceDevice.CurrentSourcesChanged += (sender, e) =>
|
||||
{
|
||||
// need to copy the dictionaries to avoid enumeration issues
|
||||
var currentSourceKeys = sourceDevice.CurrentSourceKeys.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
var currentSources = sourceDevice.CurrentSources.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
|
||||
PostStatusMessage(JToken.FromObject(new
|
||||
{
|
||||
currentSourceKeys = sourceDevice.CurrentSourceKeys,
|
||||
currentSources = sourceDevice.CurrentSources
|
||||
currentSourceKeys,
|
||||
currentSources,
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -130,34 +130,33 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
|
||||
feedback.MuteFeedback.OutputChange += (sender, args) =>
|
||||
{
|
||||
PostStatusMessage(JToken.FromObject(
|
||||
new
|
||||
{
|
||||
volume = new
|
||||
{
|
||||
muted = args.BoolValue
|
||||
}
|
||||
})
|
||||
);
|
||||
var message = new VolumeStateMessage
|
||||
{
|
||||
Volume = new Volume
|
||||
{
|
||||
Muted = args.BoolValue
|
||||
}
|
||||
};
|
||||
|
||||
PostStatusMessage(JToken.FromObject(message));
|
||||
};
|
||||
|
||||
feedback.VolumeLevelFeedback.OutputChange += (sender, args) =>
|
||||
{
|
||||
var rawValue = "";
|
||||
if (feedback is IBasicVolumeWithFeedbackAdvanced volumeAdvanced)
|
||||
var message = new VolumeStateMessage
|
||||
{
|
||||
rawValue = volumeAdvanced.RawVolumeLevel.ToString();
|
||||
}
|
||||
|
||||
var message = new
|
||||
{
|
||||
volume = new
|
||||
Volume = new Volume
|
||||
{
|
||||
level = args.IntValue,
|
||||
rawValue
|
||||
Level = args.IntValue,
|
||||
}
|
||||
};
|
||||
|
||||
if (device is IBasicVolumeWithFeedbackAdvanced volumeAdvanced)
|
||||
{
|
||||
message.Volume.RawValue = volumeAdvanced.RawVolumeLevel.ToString();
|
||||
message.Volume.Units = volumeAdvanced.Units;
|
||||
}
|
||||
|
||||
PostStatusMessage(JToken.FromObject(message));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,7 +31,12 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
/// <remarks>
|
||||
/// Unsoliciited feedback from a device in a messenger will ONLY be sent to devices in this subscription list. When a client disconnects, it's ID will be removed from the collection.
|
||||
/// </remarks>
|
||||
protected HashSet<string> SubscriberIds = new HashSet<string>();
|
||||
private readonly HashSet<string> subscriberIds = new HashSet<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Lock object for thread-safe access to SubscriberIds
|
||||
/// </summary>
|
||||
private readonly object _subscriberLock = new object();
|
||||
|
||||
private readonly List<string> _deviceInterfaces;
|
||||
|
||||
@@ -189,18 +194,18 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
{
|
||||
if (!enableMessengerSubscriptions)
|
||||
{
|
||||
this.LogWarning("Messenger subscriptions not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SubscriberIds.Any(id => id == clientId))
|
||||
lock (_subscriberLock)
|
||||
{
|
||||
this.LogVerbose("Client {clientId} already subscribed", clientId);
|
||||
return;
|
||||
if (!subscriberIds.Add(clientId))
|
||||
{
|
||||
this.LogVerbose("Client {clientId} already subscribed", clientId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SubscriberIds.Add(clientId);
|
||||
|
||||
this.LogDebug("Client {clientId} subscribed", clientId);
|
||||
}
|
||||
|
||||
@@ -212,19 +217,26 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
{
|
||||
if (!enableMessengerSubscriptions)
|
||||
{
|
||||
this.LogWarning("Messenger subscriptions not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SubscriberIds.Any(i => i == clientId))
|
||||
bool wasSubscribed;
|
||||
lock (_subscriberLock)
|
||||
{
|
||||
wasSubscribed = subscriberIds.Contains(clientId);
|
||||
if (wasSubscribed)
|
||||
{
|
||||
subscriberIds.Remove(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasSubscribed)
|
||||
{
|
||||
this.LogVerbose("Client with ID {clientId} is not subscribed", clientId);
|
||||
return;
|
||||
}
|
||||
|
||||
SubscriberIds.RemoveWhere((i) => i == clientId);
|
||||
|
||||
this.LogInformation("Client with ID {clientId} unsubscribed", clientId);
|
||||
this.LogDebug("Client with ID {clientId} unsubscribed", clientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -258,7 +270,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(ex, "Exception posting status message for {messagePath} to {clientId}", MessagePath, clientId ?? "all clients");
|
||||
this.LogError("Exception posting status message for {messagePath} to {clientId}: {message}", MessagePath, clientId ?? "all clients", ex.Message);
|
||||
this.LogDebug(ex, "Stack trace: ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +300,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(ex, "Exception posting status message for {type} to {clientId}", type, clientId ?? "all clients");
|
||||
this.LogError("Exception posting status message for {type} to {clientId}: {message}", type, clientId ?? "all clients", ex.Message);
|
||||
this.LogDebug(ex, "Stack trace: ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +326,14 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
// If client is null or empty, this message is unsolicited feedback. Iterate through the subscriber list and send to all interested parties
|
||||
if (string.IsNullOrEmpty(clientId))
|
||||
{
|
||||
foreach (var client in SubscriberIds)
|
||||
// Create a snapshot of subscribers to avoid collection modification during iteration
|
||||
List<string> subscriberSnapshot;
|
||||
lock (_subscriberLock)
|
||||
{
|
||||
subscriberSnapshot = new List<string>(subscriberIds);
|
||||
}
|
||||
|
||||
foreach (var client in subscriberSnapshot)
|
||||
{
|
||||
AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = client, Content = content });
|
||||
}
|
||||
|
||||
@@ -1312,6 +1312,11 @@ namespace PepperDash.Essentials
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
if (!Config.EnableMessengerSubscriptions)
|
||||
{
|
||||
this.LogWarning("Messenger subscriptions disabled. add \"enableMessengerSubscriptions\": true to config for {key} to enable.", Key);
|
||||
}
|
||||
|
||||
foreach (var messenger in _messengers)
|
||||
{
|
||||
try
|
||||
@@ -1743,7 +1748,7 @@ namespace PepperDash.Essentials
|
||||
var clientNo = 1;
|
||||
foreach (var clientContext in _directServer.UiClientContexts)
|
||||
{
|
||||
var clients = _directServer.UiClients.Values.Where(c => c.Token == clientContext.Value.Token.Token);
|
||||
var clients = _directServer.UiClients.Values.Where(c => c.TokenKey == clientContext.Key);
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse(
|
||||
$"\r\nClient {clientNo}:\r\n" +
|
||||
@@ -2174,6 +2179,7 @@ namespace PepperDash.Essentials
|
||||
{
|
||||
var clientId = content["clientId"].Value<string>();
|
||||
var roomKey = content["roomKey"].Value<string>();
|
||||
var touchpanelKey = content.SelectToken("touchpanelKey");
|
||||
|
||||
if (_roomCombiner == null)
|
||||
{
|
||||
@@ -2187,6 +2193,8 @@ namespace PepperDash.Essentials
|
||||
SendMessageObject(message);
|
||||
|
||||
SendDeviceInterfaces(clientId);
|
||||
|
||||
SendTouchpanelKey(clientId, touchpanelKey);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2202,6 +2210,8 @@ namespace PepperDash.Essentials
|
||||
SendMessageObject(message);
|
||||
|
||||
SendDeviceInterfaces(clientId);
|
||||
|
||||
SendTouchpanelKey(clientId, touchpanelKey);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2221,6 +2231,8 @@ namespace PepperDash.Essentials
|
||||
SendMessageObject(message);
|
||||
|
||||
SendDeviceInterfaces(clientId);
|
||||
|
||||
SendTouchpanelKey(clientId, touchpanelKey);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2236,6 +2248,24 @@ namespace PepperDash.Essentials
|
||||
SendMessageObject(newMessage);
|
||||
|
||||
SendDeviceInterfaces(clientId);
|
||||
|
||||
SendTouchpanelKey(clientId, touchpanelKey);
|
||||
}
|
||||
|
||||
private void SendTouchpanelKey(string clientId, JToken touchpanelKeyToken)
|
||||
{
|
||||
if (touchpanelKeyToken == null)
|
||||
{
|
||||
this.LogWarning("Touchpanel key not found for client {clientId}", clientId);
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessageObject(new MobileControlMessage
|
||||
{
|
||||
Type = "/system/touchpanelKey",
|
||||
ClientId = clientId,
|
||||
Content = touchpanelKeyToken.Value<string>()
|
||||
});
|
||||
}
|
||||
|
||||
private void SendDeviceInterfaces(string clientId)
|
||||
@@ -2374,7 +2404,7 @@ namespace PepperDash.Essentials
|
||||
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace PepperDash.Essentials.Touchpanel
|
||||
return;
|
||||
}
|
||||
|
||||
AddAction($"/fullStatus", (id, context) => SendFullStatus());
|
||||
AddAction($"/fullStatus", (id, context) => SendFullStatus(id));
|
||||
|
||||
AddAction($"/openApp", (id, context) => _appControl.OpenApp());
|
||||
|
||||
|
||||
@@ -252,6 +252,7 @@ namespace PepperDash.Essentials.Touchpanel
|
||||
if (!x70Panel.ExtenderApplicationControlReservedSigs.HideOpenedApplicationFeedback.BoolValue)
|
||||
{
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.ShowButtonToolbar();
|
||||
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.Button2On();
|
||||
}
|
||||
else
|
||||
@@ -294,17 +295,16 @@ namespace PepperDash.Essentials.Touchpanel
|
||||
handler(this, new DeviceInfoEventArgs(DeviceInfo));
|
||||
};
|
||||
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.DeviceExtenderSigChange += (o, a) =>
|
||||
{
|
||||
this.LogVerbose("X70 Button Toolbar Device Extender args: {event}:{sig}:{name}:{type}:{boolValue}:{ushortValue}:{stringValue}", a.Event, a.Sig, a.Sig.Name, a.Sig.Type, a.Sig.BoolValue, a.Sig.UShortValue, a.Sig.StringValue);
|
||||
};
|
||||
|
||||
x70Panel.ExtenderApplicationControlReservedSigs.Use();
|
||||
x70Panel.ExtenderZoomRoomAppReservedSigs.Use();
|
||||
x70Panel.ExtenderEthernetReservedSigs.Use();
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.Use();
|
||||
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.Button1Off();
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.Button3Off();
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.Button4Off();
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.Button5Off();
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.Button6Off();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -414,34 +414,79 @@ namespace PepperDash.Essentials.Touchpanel
|
||||
McServerUrlFeedback.LinkInputSig(Panel.StringInput[3]);
|
||||
UserCodeFeedback.LinkInputSig(Panel.StringInput[4]);
|
||||
|
||||
Panel.IpInformationChange += (sender, args) =>
|
||||
Panel.IpInformationChange -= Panel_IpInformationChange;
|
||||
Panel.IpInformationChange += Panel_IpInformationChange;
|
||||
|
||||
Panel.OnlineStatusChange -= Panel_OnlineChange;
|
||||
Panel.OnlineStatusChange += Panel_OnlineChange;
|
||||
}
|
||||
|
||||
private void Panel_OnlineChange(GenericBase sender, OnlineOfflineEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (args.Connected)
|
||||
if (!args.DeviceOnLine)
|
||||
{
|
||||
this.LogVerbose("Connection from IP: {ip}", args.DeviceIpAddress);
|
||||
this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue);
|
||||
|
||||
var appUrl = GetUrlWithCorrectIp(_appUrl);
|
||||
Panel.StringInput[1].StringValue = appUrl;
|
||||
|
||||
SetAppUrl(appUrl);
|
||||
this.LogInformation("panel is offline");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogVerbose("Disconnection from IP: {ip}", args.DeviceIpAddress);
|
||||
}
|
||||
};
|
||||
|
||||
Panel.OnlineStatusChange += (sender, args) =>
|
||||
{
|
||||
this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue);
|
||||
this.LogDebug("panel is online");
|
||||
|
||||
UpdateFeedbacks();
|
||||
Panel.StringInput[1].StringValue = _appUrl;
|
||||
Panel.StringInput[2].StringValue = QrCodeUrlFeedback.StringValue;
|
||||
Panel.StringInput[3].StringValue = McServerUrlFeedback.StringValue;
|
||||
Panel.StringInput[4].StringValue = UserCodeFeedback.StringValue;
|
||||
};
|
||||
|
||||
if (Panel is TswXX70Base x70Panel)
|
||||
{
|
||||
this.LogDebug("setting buttons off");
|
||||
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.Button1Off();
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.Button3Off();
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.Button4Off();
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.Button5Off();
|
||||
x70Panel.ExtenderButtonToolbarReservedSigs.Button6Off();
|
||||
}
|
||||
|
||||
SendUrlToPanel();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError("Exception in panel online: {message}", ex.Message);
|
||||
this.LogDebug(ex, "Stack Trace: ");
|
||||
}
|
||||
}
|
||||
|
||||
private void SendUrlToPanel()
|
||||
{
|
||||
var appUrl = GetUrlWithCorrectIp(_appUrl);
|
||||
|
||||
this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue);
|
||||
|
||||
if (Panel.StringInput[1].StringValue == appUrl)
|
||||
{
|
||||
this.LogInformation("App URL already set to {appUrl}, no update needed", AppUrlFeedback.StringValue);
|
||||
return;
|
||||
}
|
||||
|
||||
Panel.StringInput[1].StringValue = appUrl;
|
||||
|
||||
SetAppUrl(appUrl);
|
||||
}
|
||||
|
||||
private void Panel_IpInformationChange(GenericBase sender, ConnectedIpEventArgs args)
|
||||
{
|
||||
if (args.Connected)
|
||||
{
|
||||
this.LogVerbose("Connection from IP: {ip}", args.DeviceIpAddress);
|
||||
SendUrlToPanel();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogVerbose("Disconnection from IP: {ip}", args.DeviceIpAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using PepperDash.Essentials.AppServer;
|
||||
using PepperDash.Essentials.AppServer.Messengers;
|
||||
|
||||
@@ -29,17 +30,17 @@ namespace PepperDash.Essentials.Touchpanel
|
||||
{
|
||||
AddAction("/fullStatus", (id, content) =>
|
||||
{
|
||||
PostStatusMessage(new ThemeUpdateMessage { Theme = _tpDevice.Theme });
|
||||
PostStatusMessage(new ThemeUpdateMessage { Theme = _tpDevice.Theme }, id);
|
||||
});
|
||||
|
||||
AddAction("/saveTheme", (id, content) =>
|
||||
{
|
||||
var theme = content.ToObject<MobileControlSimpleContent<string>>();
|
||||
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Setting theme to {theme}", this, theme.Value);
|
||||
this.LogInformation("Setting theme to {theme}", theme.Value);
|
||||
_tpDevice.UpdateTheme(theme.Value);
|
||||
|
||||
PostStatusMessage(JToken.FromObject(new { theme = theme.Value }));
|
||||
PostStatusMessage(JToken.FromObject(new { theme = theme.Value }), clientId: id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using WebSocketSharp;
|
||||
@@ -12,13 +13,12 @@ namespace PepperDash.Essentials
|
||||
private static int nextClientId = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Get the next unique client ID
|
||||
/// Get the next unique client ID (thread-safe)
|
||||
/// </summary>
|
||||
/// <returns>Client ID</returns>
|
||||
public static int GetNextClientId()
|
||||
{
|
||||
nextClientId++;
|
||||
return nextClientId;
|
||||
return Interlocked.Increment(ref nextClientId);
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts a WebSocketServer LogData object to Essentials logging calls.
|
||||
|
||||
@@ -64,6 +64,12 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
[JsonProperty("userAppUrl")]
|
||||
public string UserAppUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the WebSocketUrl with clientId query parameter
|
||||
/// </summary>
|
||||
[JsonProperty("webSocketUrl")]
|
||||
public string WebSocketUrl { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the EnableDebug
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
@@ -59,12 +60,24 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// </summary>
|
||||
public Dictionary<string, UiClientContext> UiClientContexts { get; private set; }
|
||||
|
||||
private readonly Dictionary<string, UiClient> uiClients = new Dictionary<string, UiClient>();
|
||||
private readonly ConcurrentDictionary<string, UiClient> uiClients = new ConcurrentDictionary<string, UiClient>();
|
||||
|
||||
/// <summary>
|
||||
/// Stores pending client registrations using composite key: token-clientId
|
||||
/// This ensures the correct client ID is matched even when connections establish out of order
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, string> pendingClientRegistrations = new ConcurrentDictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Stores queues of pending client IDs per token for legacy clients (FIFO)
|
||||
/// This ensures thread-safety when multiple legacy clients use the same token
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> legacyClientIdQueues = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of UI clients
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, UiClient> UiClients => new ReadOnlyDictionary<string, UiClient>(uiClients);
|
||||
public IReadOnlyDictionary<string, UiClient> UiClients => uiClients;
|
||||
|
||||
private readonly MobileControlSystemController _parent;
|
||||
|
||||
@@ -385,9 +398,9 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}";
|
||||
|
||||
this.LogVerbose("Sending URL {appUrl}", appUrl);
|
||||
this.LogVerbose("Sending URL {appUrl} to touchpanel {touchpanelKey}", appUrl, touchpanel.Touchpanel.Key);
|
||||
|
||||
touchpanel.Messenger.UpdateAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}");
|
||||
touchpanel.Touchpanel.SetAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,23 +736,95 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
private UiClient BuildUiClient(string roomKey, JoinToken token, string key)
|
||||
{
|
||||
var c = new UiClient($"uiclient-{key}-{roomKey}-{token.Id}", token.Id, token.Token);
|
||||
this.LogInformation("Constructing UiClient with key {key} and ID {id}", key, token.Id);
|
||||
// Dequeue the next clientId for legacy client support (FIFO per token)
|
||||
// New clients will override this ID in OnOpen with the validated query parameter value
|
||||
var clientId = "pending";
|
||||
if (legacyClientIdQueues.TryGetValue(key, out var queue) && queue.TryDequeue(out var dequeuedId))
|
||||
{
|
||||
clientId = dequeuedId;
|
||||
this.LogVerbose("Dequeued legacy clientId {clientId} for token {token}", clientId, key);
|
||||
}
|
||||
|
||||
var c = new UiClient($"uiclient-{key}-{roomKey}-{clientId}", clientId, token.Token, token.TouchpanelKey);
|
||||
this.LogInformation("Constructing UiClient with key {key} and temporary ID (will be set from query param)", key);
|
||||
c.Controller = _parent;
|
||||
c.RoomKey = roomKey;
|
||||
c.TokenKey = key; // Store the URL token key for filtering
|
||||
c.Server = this; // Give UiClient access to server for ID registration
|
||||
|
||||
if (uiClients.ContainsKey(token.Id))
|
||||
// Don't add to uiClients yet - will be added in OnOpen after ID is set from query param
|
||||
|
||||
c.ConnectionClosed += (o, a) =>
|
||||
{
|
||||
this.LogWarning("removing client with duplicate id {id}", token.Id);
|
||||
uiClients.Remove(token.Id);
|
||||
}
|
||||
uiClients.Add(token.Id, c);
|
||||
// UiClients[key].SetClient(c);
|
||||
c.ConnectionClosed += (o, a) => uiClients.Remove(a.ClientId);
|
||||
token.Id = null;
|
||||
uiClients.TryRemove(a.ClientId, out _);
|
||||
// Clean up any pending registrations for this token
|
||||
var keysToRemove = pendingClientRegistrations.Keys
|
||||
.Where(k => k.StartsWith($"{key}-"))
|
||||
.ToList();
|
||||
foreach (var k in keysToRemove)
|
||||
{
|
||||
pendingClientRegistrations.TryRemove(k, out _);
|
||||
}
|
||||
|
||||
// Clean up legacy queue if empty
|
||||
if (legacyClientIdQueues.TryGetValue(key, out var legacyQueue) && legacyQueue.IsEmpty)
|
||||
{
|
||||
legacyClientIdQueues.TryRemove(key, out _);
|
||||
}
|
||||
};
|
||||
return c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a UiClient with its validated client ID after WebSocket connection
|
||||
/// </summary>
|
||||
/// <param name="client">The UiClient to register</param>
|
||||
/// <param name="clientId">The validated client ID</param>
|
||||
/// <param name="tokenKey">The token key for validation</param>
|
||||
/// <returns>True if registration successful, false if validation failed</returns>
|
||||
public bool RegisterUiClient(UiClient client, string clientId, string tokenKey)
|
||||
{
|
||||
var registrationKey = $"{tokenKey}-{clientId}";
|
||||
|
||||
// Verify this clientId was generated during a join request for this token
|
||||
if (!pendingClientRegistrations.TryRemove(registrationKey, out _))
|
||||
{
|
||||
this.LogWarning("Client attempted to connect with unregistered or expired clientId {clientId} for token {token}", clientId, tokenKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Registration is valid - add to active clients
|
||||
uiClients.AddOrUpdate(clientId, client, (id, existingClient) =>
|
||||
{
|
||||
this.LogWarning("Replacing existing client with duplicate id {id}", id);
|
||||
return client;
|
||||
});
|
||||
|
||||
this.LogInformation("Successfully registered UiClient with ID {clientId} for token {token}", clientId, tokenKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a UiClient using legacy flow (for backwards compatibility with older clients)
|
||||
/// </summary>
|
||||
/// <param name="client">The UiClient to register</param>
|
||||
public void RegisterLegacyUiClient(UiClient client)
|
||||
{
|
||||
if (string.IsNullOrEmpty(client.Id))
|
||||
{
|
||||
this.LogError("Cannot register client with null or empty ID");
|
||||
return;
|
||||
}
|
||||
|
||||
uiClients.AddOrUpdate(client.Id, client, (id, existingClient) =>
|
||||
{
|
||||
this.LogWarning("Replacing existing client with duplicate id {id} (legacy flow)", id);
|
||||
return client;
|
||||
});
|
||||
|
||||
this.LogInformation("Successfully registered UiClient with ID {clientId} using legacy flow", client.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints out the session data for each path
|
||||
/// </summary>
|
||||
@@ -1046,10 +1131,22 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
});
|
||||
}
|
||||
|
||||
// Generate a client ID for this join request
|
||||
var clientId = $"{Utilities.GetNextClientId()}";
|
||||
clientContext.Token.Id = clientId;
|
||||
|
||||
// Store in pending registrations for new clients that send clientId via query param
|
||||
var registrationKey = $"{token}-{clientId}";
|
||||
pendingClientRegistrations.TryAdd(registrationKey, clientId);
|
||||
|
||||
// Also enqueue for legacy clients (thread-safe FIFO per token)
|
||||
var queue = legacyClientIdQueues.GetOrAdd(token, _ => new ConcurrentQueue<string>());
|
||||
queue.Enqueue(clientId);
|
||||
|
||||
this.LogVerbose("Assigning ClientId: {clientId}", clientId);
|
||||
this.LogVerbose("Assigning ClientId: {clientId} for token: {token}", clientId, token);
|
||||
|
||||
// Construct WebSocket URL with clientId query parameter
|
||||
var wsProtocol = "ws";
|
||||
var wsUrl = $"{wsProtocol}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}";
|
||||
|
||||
// Construct the response object
|
||||
JoinResponse jRes = new JoinResponse
|
||||
@@ -1064,6 +1161,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
UserAppUrl = string.Format("http://{0}:{1}/mc/app",
|
||||
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
|
||||
Port),
|
||||
WebSocketUrl = wsUrl,
|
||||
EnableDebug = false,
|
||||
DeviceInterfaceSupport = deviceInterfaces
|
||||
};
|
||||
|
||||
@@ -31,11 +31,26 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// </summary>
|
||||
public string Token { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL token key used to connect (from UiClientContexts dictionary key)
|
||||
/// </summary>
|
||||
public string TokenKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Touchpanel Key associated with this client
|
||||
/// </summary>
|
||||
public string TouchpanelKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mobile control system controller that handles this client's messages
|
||||
/// </summary>
|
||||
public MobileControlSystemController Controller { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the server instance for client registration
|
||||
/// </summary>
|
||||
public MobileControlWebsocketServer Server { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the room key that this client is associated with
|
||||
/// </summary>
|
||||
@@ -75,11 +90,13 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// <param name="key">The unique key to identify this client</param>
|
||||
/// <param name="id">The client ID used by the client for this connection</param>
|
||||
/// <param name="token">The token associated with this client</param>
|
||||
public UiClient(string key, string id, string token)
|
||||
/// <param name="touchpanelKey">The touchpanel key associated with this client</param>
|
||||
public UiClient(string key, string id, string token, string touchpanelKey = "")
|
||||
{
|
||||
Key = key;
|
||||
Id = id;
|
||||
Token = token;
|
||||
TouchpanelKey = touchpanelKey;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -92,6 +109,50 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
Log.Output = (data, message) => Utilities.ConvertWebsocketLog(data, message, this);
|
||||
Log.Level = LogLevel.Trace;
|
||||
|
||||
// Get clientId from query parameter
|
||||
var queryString = Context.QueryString;
|
||||
var clientId = queryString["clientId"];
|
||||
|
||||
if (!string.IsNullOrEmpty(clientId))
|
||||
{
|
||||
// New behavior: Validate and register with the server using provided clientId
|
||||
if (Server == null || !Server.RegisterUiClient(this, clientId, TokenKey))
|
||||
{
|
||||
this.LogError("Failed to register client with ID {clientId}. Invalid or expired registration.", clientId);
|
||||
Context.WebSocket.Close(CloseStatusCode.PolicyViolation, "Invalid or expired clientId");
|
||||
return;
|
||||
}
|
||||
|
||||
// Update this client's ID to the validated one
|
||||
Id = clientId;
|
||||
Key = $"uiclient-{TokenKey}-{RoomKey}-{clientId}";
|
||||
|
||||
this.LogInformation("Client {clientId} successfully connected and registered (new flow)", clientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Legacy behavior: Use clientId from Token.Id (generated in HandleJoinRequest)
|
||||
this.LogInformation("Client connected without clientId query parameter. Using legacy registration flow.");
|
||||
|
||||
// Id is already set from Token in constructor, use it
|
||||
if (string.IsNullOrEmpty(Id))
|
||||
{
|
||||
this.LogError("Legacy client has no ID from token. Connection will be closed.");
|
||||
Context.WebSocket.Close(CloseStatusCode.PolicyViolation, "No client ID available");
|
||||
return;
|
||||
}
|
||||
|
||||
Key = $"uiclient-{TokenKey}-{RoomKey}-{Id}";
|
||||
|
||||
// Register directly to active clients (legacy flow)
|
||||
if (Server != null)
|
||||
{
|
||||
Server.RegisterLegacyUiClient(this);
|
||||
}
|
||||
|
||||
this.LogInformation("Client {clientId} registered using legacy flow", Id);
|
||||
}
|
||||
|
||||
if (Controller == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");
|
||||
@@ -105,6 +166,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
clientId = Id,
|
||||
roomKey = RoomKey,
|
||||
touchpanelKey = TouchpanelKey ?? string.Empty,
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace PepperDash.Essentials
|
||||
CrestronConsole.ConsoleCommandResponse
|
||||
("Current running configuration. This is the merged system and template configuration" + CrestronEnvironment.NewLine);
|
||||
CrestronConsole.ConsoleCommandResponse(Newtonsoft.Json.JsonConvert.SerializeObject
|
||||
(ConfigReader.ConfigObject, Newtonsoft.Json.Formatting.Indented));
|
||||
(ConfigReader.ConfigObject, Newtonsoft.Json.Formatting.Indented).Replace(Environment.NewLine, "\r\n"));
|
||||
}, "showconfig", "Shows the current running merged config", ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand(s =>
|
||||
@@ -662,6 +662,7 @@ namespace PepperDash.Essentials
|
||||
|
||||
if (jsonFiles.Length > 1)
|
||||
{
|
||||
Debug.LogError("Multiple configuration files found in application directory: {@jsonFiles}", jsonFiles.Select(f => f.FullName).ToArray());
|
||||
throw new Exception("Multiple configuration files found. Cannot continue.");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user