Compare commits

..

36 Commits

Author SHA1 Message Date
Andrew Welker
6cb4f6396b fix: change number of retained files to 7 instead of 30 for processors 2025-10-31 13:09:12 -05:00
Andrew Welker
ff609dfecd fix: add config option to turn echo off for SSH
In addition, removed CTimer in favor of System.Threading.Timers Timer in the SSH class, and modified the class to be thread-safe.
2025-10-31 09:01:54 -05:00
Andrew Welker
666be5ae59 Merge pull request #1345 from PepperDash/mc-touchpanel-key
mc touchpanel key
2025-10-30 17:11:15 -04:00
Andrew Welker
bfc9b7e7fa fix: logging & ternary changes 2025-10-30 16:07:26 -05:00
Andrew Welker
b02e952765 Merge remote-tracking branch 'origin/com102-comspec' into mc-touchpanel-key 2025-10-30 15:58:36 -05:00
Andrew Welker
72861a5097 Merge remote-tracking branch 'origin/feature/fusion-help-request' into mc-touchpanel-key 2025-10-30 15:58:26 -05:00
Andrew Welker
44ed067f4d chore: update local build version 2025-10-30 15:57:37 -05:00
Andrew Welker
faa2169baf fix: send correct URL to panel 2025-10-30 15:57:28 -05:00
Andrew Welker
fd40b0c6d1 fix: send all status messages with ClientId 2025-10-30 15:57:15 -05:00
Andrew Welker
afcddad1cc fix: remove async on task, as it's unnecessary 2025-10-30 15:56:50 -05:00
Andrew Welker
c4cf8f13e9 fix: register panel in post phase rather than activation cycle 2025-10-30 15:56:28 -05:00
Neil Dorin
c852f87a27 refactor: Comment out logging statements in help request handling 2025-10-30 14:33:58 -06:00
Neil Dorin
071174fa7d feat: Implement help request status tracking in Fusion system 2025-10-30 14:13:02 -06:00
Neil Dorin
da0f28a10c feat: Enhance IEssentialsRoomFusionController with additional properties and logging 2025-10-29 16:47:18 -06:00
Andrew Welker
0538a304ed Merge branch 'main' into mc-touchpanel-key 2025-10-29 15:13:53 -05:00
Andrew Welker
705c5db237 Merge pull request #1347 from PepperDash/json-console
fix: print json response to console using \r\n instead of \n
2025-10-29 16:12:49 -04:00
Neil Dorin
2e95f5337e feat: Add IEssentialsRoomFusionController and related configurations
- Introduced IEssentialsRoomFusionControllerFactory for creating Fusion Room Controller devices.
- Added IEssentialsRoomFusionControllerPropertiesConfig to define configuration properties for the Fusion Room Controller.
- Updated IFusionHelpRequest interface to include methods for cancelling and toggling help requests.
- Refactored RoomOnToDefaultSourceWhenOccupied to use IEssentialsRoomFusionController instead of EssentialsHuddleSpaceFusionSystemControllerBase.
- Modified EssentialsRoomBase to check for IEssentialsRoomFusionController in occupancy status provider.
2025-10-28 16:49:29 -06:00
jkdevito
ba576180a7 refactor: remove unused using directives in ComPortController 2025-10-28 09:05:56 -05:00
jkdevito
92c9db8237 refactor: improve logging messages in ComPort registration and configuration 2025-10-28 09:04:15 -05:00
jkdevito
c4d064965f refactor: enhance ComPortController with additional event documentation and logging improvements 2025-10-28 08:40:29 -05:00
Neil Dorin
f27965ac29 feat: Add help request functionality to Fusion system controller
Introduce `IFusionHelpRequest` interface for managing help requests, including `HelpRequestResponseFeedback` property and `SendHelpRequest` method. Enhance `EssentialsHuddleSpaceFusionSystemControllerBase` with new properties and methods to support help request tracking and management. Improve code organization and documentation throughout the affected files.
2025-10-27 17:35:38 -06:00
jkdevito
3ce282e2db refactor: comment out debug logging for ComPort registration 2025-10-27 17:33:06 -05:00
jkdevito
32a332a6e2 refactor: update TODO comment for Port registration verification 2025-10-27 17:32:40 -05:00
jkdevito
151a90c9be refactor: streamline ComPort registration and configuration logic with enhanced logging 2025-10-27 17:13:43 -05:00
jkdevito
89e893b3b7 fix: improve formatting and logging in ComPortController 2025-10-27 16:46:09 -05:00
jkdevito
d01eb03890 feat: enhance ComPortController with detailed logging on configuration changes 2025-10-27 14:06:20 -05:00
Andrew Welker
16c92afabb fix: print json response to console using \r\n instead of \n 2025-10-27 09:21:51 -05:00
Andrew Welker
dff5d2d32e Merge pull request #1346 from PepperDash/feature/add-infinetEx-comm-method 2025-10-23 18:50:06 -04:00
Neil Dorin
317bde3814 fix: add InfinetEx to eControlMethods enum 2025-10-23 15:22:46 -06:00
Andrew Welker
8bab3dc966 fix: send touchpanelKey message with all room combiner checks 2025-10-23 11:21:17 -05:00
Andrew Welker
514ac850ca fix: send touchpanel key correctly 2025-10-23 10:01:05 -05:00
Andrew Welker
44432f7a41 fix: send touchpanel key to client when client joins direct server 2025-10-23 09:54:03 -05:00
Andrew Welker
99253b30c2 fix: send touchpanel key to client when client joins 2025-10-23 09:49:45 -05:00
Andrew Welker
bf248fe33e Merge pull request #1343 from PepperDash/device-interface-system
device interface system
2025-10-23 10:40:56 -04:00
Andrew Welker
7c72a0d905 fix: send device interface list on client join
In order to avoid updating the MC Edge API to send device interfaces, one of the responses to the clientJoined message will now be a message with the type `/system/deviceInterfaces`. This has a corresponding handler in the core library to put the interfaces in the correct location.
2025-10-21 18:19:09 -05:00
Andrew Welker
5d5e78629e chore: update local build version to 2.18.2-local 2025-10-21 18:17:25 -05:00
25 changed files with 1414 additions and 562 deletions

1
.gitignore vendored
View File

@@ -396,3 +396,4 @@ _site/
api/
*.DS_Store
/._PepperDash.Essentials.4Series.sln
dotnet

View File

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

View File

@@ -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,37 @@ namespace PepperDash.Core
this.LogDebug("Creating new SshClient");
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
Client = new SshClient(connectionInfo);
Client.ErrorOccurred += Client_ErrorOccurred;
client = new SshClient(connectionInfo);
client.ErrorOccurred += Client_ErrorOccurred;
//Attempt to connect
ClientStatus = SocketStatus.SOCKET_STATUS_WAITING;
try
{
Client.Connect();
TheStream = Client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534);
if (TheStream.DataAvailable)
client.Connect();
var modes = new Dictionary<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();
string str = shellStream.Read();
}
TheStream.DataReceived += Stream_DataReceived;
shellStream.DataReceived += Stream_DataReceived;
this.LogInformation("Connected");
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
DisconnectLogged = false;
disconnectLogged = false;
}
catch (SshConnectionException e)
{
var ie = e.InnerException; // The details are inside!!
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
if (ie is SocketException)
{
@@ -286,37 +313,37 @@ namespace PepperDash.Core
this.LogVerbose(ie, "Exception details: ");
}
DisconnectLogged = true;
disconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect)
{
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
ReconnectTimer.Reset(AutoReconnectIntervalMs);
StartReconnectTimer();
}
}
catch (SshOperationTimeoutException ex)
{
this.LogWarning("Connection attempt timed out: {message}", ex.Message);
DisconnectLogged = true;
disconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect)
{
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
ReconnectTimer.Reset(AutoReconnectIntervalMs);
StartReconnectTimer();
}
}
catch (Exception e)
{
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
this.LogError("Unhandled exception on connect: {error}", e.Message);
this.LogVerbose(e, "Exception details: ");
DisconnectLogged = true;
disconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect)
{
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
ReconnectTimer.Reset(AutoReconnectIntervalMs);
StartReconnectTimer();
}
}
}
@@ -334,11 +361,7 @@ namespace PepperDash.Core
{
ConnectEnabled = false;
// Stop trying reconnects, if we are
if (ReconnectTimer != null)
{
ReconnectTimer.Stop();
// ReconnectTimer = null;
}
StopReconnectTimer();
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
}
@@ -352,12 +375,12 @@ namespace PepperDash.Core
try
{
if (Client != null)
if (client != null)
{
Client.ErrorOccurred -= Client_ErrorOccurred;
Client.Disconnect();
Client.Dispose();
Client = null;
client.ErrorOccurred -= Client_ErrorOccurred;
client.Disconnect();
client.Dispose();
client = null;
ClientStatus = status;
this.LogDebug("Disconnected");
}
@@ -371,16 +394,16 @@ namespace PepperDash.Core
/// <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 +416,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 +426,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)
{
@@ -439,7 +462,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 +482,7 @@ namespace PepperDash.Core
if (AutoReconnect && ConnectEnabled)
{
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
ReconnectTimer.Reset(AutoReconnectIntervalMs);
StartReconnectTimer();
}
});
}
@@ -467,7 +490,7 @@ namespace PepperDash.Core
/// <summary>
/// Helper for ConnectionChange event
/// </summary>
void OnConnectionChange()
private void OnConnectionChange()
{
ConnectionChange?.Invoke(this, new GenericSocketStatusChageEventArgs(this));
}
@@ -482,7 +505,7 @@ namespace PepperDash.Core
{
try
{
if (Client != null && TheStream != null && IsConnected)
if (client != null && shellStream != null && IsConnected)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
this.LogInformation(
@@ -490,8 +513,8 @@ namespace PepperDash.Core
text.Length,
ComTextHelper.GetDebugText(text));
TheStream.Write(text);
TheStream.Flush();
shellStream.Write(text);
shellStream.Flush();
}
else
{
@@ -503,7 +526,7 @@ namespace PepperDash.Core
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
ReconnectTimer.Reset();
StopReconnectTimer();
}
catch (Exception ex)
{
@@ -519,13 +542,13 @@ namespace PepperDash.Core
{
try
{
if (Client != null && TheStream != null && IsConnected)
if (client != null && shellStream != null && IsConnected)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
TheStream.Write(bytes, 0, bytes.Length);
TheStream.Flush();
shellStream.Write(bytes, 0, bytes.Length);
shellStream.Flush();
}
else
{
@@ -537,7 +560,7 @@ namespace PepperDash.Core
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
ReconnectTimer.Reset();
StopReconnectTimer();
}
catch (Exception ex)
{
@@ -546,6 +569,83 @@ namespace PepperDash.Core
}
#endregion
/// <summary>
/// Safely starts the reconnect timer with exception handling
/// </summary>
private void StartReconnectTimer()
{
try
{
reconnectTimer?.Change(AutoReconnectIntervalMs, System.Threading.Timeout.Infinite);
}
catch (ObjectDisposedException)
{
// Timer was disposed, ignore
this.LogDebug("Attempted to start timer but it was already disposed");
}
}
/// <summary>
/// Safely stops the reconnect timer with exception handling
/// </summary>
private void StopReconnectTimer()
{
try
{
reconnectTimer?.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
}
catch (ObjectDisposedException)
{
// Timer was disposed, ignore
this.LogDebug("Attempted to stop timer but it was already disposed");
}
}
/// <summary>
/// Deactivate method - properly dispose of resources
/// </summary>
public override bool Deactivate()
{
try
{
this.LogDebug("Deactivating SSH client - disposing resources");
// Stop trying reconnects
ConnectEnabled = false;
StopReconnectTimer();
// Disconnect and cleanup client
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
// Dispose timer
try
{
reconnectTimer?.Dispose();
}
catch (ObjectDisposedException)
{
// Already disposed, ignore
}
// Dispose semaphore
try
{
connectLock?.Dispose();
}
catch (ObjectDisposedException)
{
// Already disposed, ignore
}
return base.Deactivate();
}
catch (Exception ex)
{
this.LogException(ex, "Error during SSH client deactivation");
return false;
}
}
}
//*****************************************************************************************************

View File

@@ -19,44 +19,44 @@ namespace PepperDash.Core
/// </summary>
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)
@@ -443,52 +443,52 @@ namespace PepperDash.Core
}
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
/// <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));
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)
{
/// <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));
if (_client != null)
_client.SendData(bytes, bytes.Length);
}
_client.SendData(bytes, bytes.Length);
}
/// <summary>
/// Socket Status Change Handler
@@ -496,7 +496,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 +505,73 @@ namespace PepperDash.Core
else
{
Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
_client.ReceiveDataAsync(Receive);
_client.ReceiveDataAsync(Receive);
}
var handler = ConnectionChange;
if (handler != null)
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
}
}
var handler = ConnectionChange;
if (handler != null)
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
}
}
/// <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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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);

View File

@@ -1,59 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
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 +81,111 @@ 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)
{
Debug.LogMessage(LogEventLevel.Information, this, "Configured com Port for this device does not exist.");
return;
}
// TODO [ ] - Remove commented out code once verified working
//if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102)
if (Port.Parent is GenericBase genericDevice && genericDevice.Registerable)
{
//this.LogInformation($"INFO: Attempting to register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID}");
var result = genericDevice.Register();
if (result != eDeviceRegistrationUnRegistrationResponse.Success)
{
this.LogError($"ERROR: Cannot register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
return; // false
}
}
var specResult = Port.SetComPortSpec(Spec);
if (specResult != 0)
{
this.LogError($"ERROR: Cannot set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
return;
}
//this.LogInformation($"INFO: Successfully set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
var specResult = Port.SetComPortSpec(Spec);
if (specResult != 0)
{
Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Cannot set comspec");
return;
}
Port.SerialDataReceived += Port_SerialDataReceived;
}
~ComPortController()
// TODO [ ] - Remove debug logging once verified working
// if (Port.Parent is CenIoCom102)
// {
// Port.PropertyChanged += (s, e) =>
// {
// this.LogInformation($@"RegisterAndConfigureComPort: PropertyChanged Fired >>
// comPort-'{Port.ID}',
// Property Changed-'{e.Property}',
// Value Changed-'{e.Value}',
// deviceName-'{Port.DeviceName}',
// parentDevice-'{Port.ParentDevice}',
// parent-`{Port.Parent}`,
// online-`{Port.IsOnline}`,
// present-`{Port.Present}`,
// supportedBaudRates-'{Port.SupportedBaudRates}'");
// };
// Port.ExtendedInformationChanged += (s, e) =>
// {
// this.LogInformation($@"RegisterAndConfigureComPort: ExtendedInformationChanged Fired >>
// comPort-'{Port.ID}',
// {e.Protocol},
// {e.BaudRate},
// {e.Parity},
// {e.DataBits},
// {e.StopBits},
// HW Handshake-'{e.HardwareHandshakeSetting}',
// SW Handshake-'{e.SoftwareHandshakeSetting}'");
// };
// }
Port.SerialDataReceived += Port_SerialDataReceived;
}
/// <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);
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));
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
eventSubscribed = true;
}
var textHandler = TextReceived;
if (textHandler != null)
{
}
var textHandler = TextReceived;
if (textHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
textHandler(this, new GenericCommMethodReceiveTextArgs(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 +193,70 @@ 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);
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, 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));
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(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());
}
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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>

View File

@@ -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;
@@ -21,26 +20,42 @@ namespace PepperDash.Essentials.Core.Fusion
/// <summary>
/// Represents a EssentialsHuddleSpaceFusionSystemControllerBase
/// </summary>
public class EssentialsHuddleSpaceFusionSystemControllerBase : Device, IOccupancyStatusProvider
public class IEssentialsRoomFusionController : EssentialsDevice, IOccupancyStatusProvider, IFusionHelpRequest
{
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 +63,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,6 +74,20 @@ namespace PepperDash.Essentials.Core.Fusion
private string _roomOccupancyRemoteString;
private bool _helpRequestSent;
private eFusionHelpResponse _helpRequestStatus;
/// <inheritdoc />
public StringFeedback HelpRequestResponseFeedback { get; private set; }
/// <inheritdoc />
public BoolFeedback HelpRequestSentFeedback { get; private set; }
/// <inheritdoc />
public StringFeedback HelpRequestStatusFeedback { get; private set; }
#region System Info Sigs
//StringSigData SystemName;
@@ -93,71 +121,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 +238,8 @@ namespace PepperDash.Essentials.Core.Fusion
}
}
this.LogDebug("Occupancy setup complete");
AddPostActivationAction(() => PostActivate(guidFilePath));
}
catch (Exception e)
{
@@ -180,9 +247,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 +303,18 @@ namespace PepperDash.Essentials.Core.Fusion
FusionRVI.GenerateFileForAllFusionDevices();
GenerateGuidFile(guidFilePath);
HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.OutputSig.StringValue);
HelpRequestSentFeedback = new BoolFeedback("HelpRequestSent", () => _helpRequestSent);
HelpRequestStatusFeedback = new StringFeedback("HelpRequestStatus", () => _helpRequestStatus.ToString());
}
/// <summary>
/// Gets the RoomGuid
/// </summary>
protected string RoomGuid
{
get { return _guiDs.RoomGuid; }
get { return _guids.RoomGuid; }
}
/// <summary>
@@ -204,6 +322,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 +339,16 @@ namespace PepperDash.Essentials.Core.Fusion
#endregion
/// <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 +385,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 +439,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 +470,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 +536,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 +548,9 @@ namespace PepperDash.Essentials.Core.Fusion
}
}
/// <summary>
/// GetSystemInfo method
/// </summary>
protected void GetSystemInfo()
{
//SystemName.InputSig.StringValue = Room.Name;
@@ -426,6 +564,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 +582,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 +619,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 +633,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 +646,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 +654,9 @@ namespace PepperDash.Essentials.Core.Fusion
_firmware.InputSig.StringValue = InitialParametersClass.FirmwareVersion;
}
/// <summary>
/// GetCustomProperties method
/// </summary>
protected void GetCustomProperties()
{
if (FusionRoom.IsOnline)
@@ -524,11 +674,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 +831,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 +939,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 +1000,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 +1220,9 @@ namespace PepperDash.Essentials.Core.Fusion
}
}
/// <summary>
/// SetUpSources method
/// </summary>
protected virtual void SetUpSources()
{
// Sources
@@ -1074,10 +1232,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 +1244,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 +1256,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 +1269,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 +1315,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 +1352,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 +1444,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 +1457,9 @@ namespace PepperDash.Essentials.Core.Fusion
}
}
/// <summary>
/// SetUpDisplay method
/// </summary>
protected virtual void SetUpDisplay()
{
try
@@ -1297,7 +1472,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 +1585,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 +1596,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 +1614,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 +1682,7 @@ namespace PepperDash.Essentials.Core.Fusion
//if (Room.OccupancyObj != null)
//{
var tempOccAsset = _guiDs.OccupancyAsset;
var tempOccAsset = _guids.OccupancyAsset;
if (tempOccAsset == null)
{
@@ -1532,7 +1707,7 @@ namespace PepperDash.Essentials.Core.Fusion
occRoom.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += RoomIsOccupiedFeedback_OutputChange;
}
RoomOccupancyRemoteStringFeedback = new StringFeedback(() => _roomOccupancyRemoteString);
RoomOccupancyRemoteStringFeedback.LinkInputSig(occSensorAsset.RoomOccupancyInfo.InputSig);
//}
@@ -1588,12 +1763,74 @@ 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();
}
// 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 +1869,69 @@ 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();
_helpRequestStatus = eFusionHelpResponse.HelpRequested;
HelpRequestStatusFeedback.FireUpdate();
}
/// <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 in Fusion 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 +1945,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 +1967,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 +1989,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 +2106,9 @@ namespace PepperDash.Essentials.Core.Fusion
/// </summary>
public class RoomInformation
{
/// <summary>
/// Constructor
/// </summary>
public RoomInformation()
{
FusionCustomProperties = new List<FusionCustomProperty>();
@@ -1855,10 +2161,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;

View File

@@ -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);
}
}

View File

@@ -0,0 +1,59 @@
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;
}

View 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();
}
}

View 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,
}
}

View File

@@ -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);

View File

@@ -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)

View File

@@ -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);
}
}
}

View File

@@ -2174,6 +2174,7 @@ namespace PepperDash.Essentials
{
var clientId = content["clientId"].Value<string>();
var roomKey = content["roomKey"].Value<string>();
var touchpanelKey = content.SelectToken("touchpanelKey");
if (_roomCombiner == null)
{
@@ -2185,6 +2186,10 @@ namespace PepperDash.Essentials
};
SendMessageObject(message);
SendDeviceInterfaces(clientId);
SendTouchpanelKey(clientId, touchpanelKey);
return;
}
@@ -2196,7 +2201,12 @@ namespace PepperDash.Essentials
ClientId = clientId,
Content = roomKey
};
SendMessageObject(message);
SendDeviceInterfaces(clientId);
SendTouchpanelKey(clientId, touchpanelKey);
return;
}
@@ -2214,6 +2224,10 @@ namespace PepperDash.Essentials
};
SendMessageObject(message);
SendDeviceInterfaces(clientId);
SendTouchpanelKey(clientId, touchpanelKey);
return;
}
@@ -2227,6 +2241,54 @@ 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)
{
this.LogDebug("Sending Device interfaces");
var devices = DeviceManager.GetDevices();
Dictionary<string, DeviceInterfaceInfo> deviceInterfaces = new Dictionary<string, DeviceInterfaceInfo>();
foreach (var device in devices)
{
var interfaces = device?.GetType().GetInterfaces().Select((i) => i.Name).ToList() ?? new List<string>();
deviceInterfaces.Add(device.Key, new DeviceInterfaceInfo
{
Key = device.Key,
Name = (device as IKeyName)?.Name ?? "",
Interfaces = interfaces
});
}
var message = new MobileControlMessage
{
Type = "/system/deviceInterfaces",
ClientId = clientId,
Content = JToken.FromObject(new { deviceInterfaces })
};
SendMessageObject(message);
}
private void HandleUserCode(JToken content, Action<string, string> action = null)
@@ -2337,7 +2399,7 @@ namespace PepperDash.Essentials
foreach (var handler in handlers)
{
Task.Run(async () =>
Task.Run(() =>
{
try
{

View File

@@ -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());

View File

@@ -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>

View File

@@ -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 }), id);
});
}
}

View File

@@ -385,9 +385,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,7 +723,7 @@ 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);
var c = new UiClient($"uiclient-{key}-{roomKey}-{token.Id}", token.Id, token.Token, token.TouchpanelKey);
this.LogInformation("Constructing UiClient with key {key} and ID {id}", key, token.Id);
c.Controller = _parent;
c.RoomKey = roomKey;

View File

@@ -31,6 +31,11 @@ namespace PepperDash.Essentials.WebSocketServer
/// </summary>
public string Token { get; private 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>
@@ -75,11 +80,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 />
@@ -105,6 +112,7 @@ namespace PepperDash.Essentials.WebSocketServer
{
clientId = Id,
roomKey = RoomKey,
touchpanelKey = TouchpanelKey ?? string.Empty,
})
};

View File

@@ -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 =>