mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-04-02 02:55:20 +00:00
Compare commits
72 Commits
ssh-echo-o
...
tieline-vi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c9a643b6a | ||
|
|
fb8216beed | ||
|
|
d05ebecd7d | ||
|
|
d0fe225bbc | ||
|
|
3fb30d5561 | ||
|
|
57cd77f019 | ||
|
|
7f2bb078c8 | ||
|
|
316bb849b4 | ||
|
|
a983e2c87f | ||
|
|
babb9a77df | ||
|
|
7629921113 | ||
|
|
3878c85a7a | ||
|
|
7ad8218af0 | ||
|
|
0c4aec14d1 | ||
|
|
7d3f871460 | ||
|
|
78c9381108 | ||
|
|
a7ff2e8903 | ||
|
|
ae0b2fe086 | ||
|
|
bd11c827da | ||
|
|
7ea1efbabf | ||
|
|
9d6aaa2a0e | ||
|
|
53e7a30224 | ||
|
|
39c1f60a4d | ||
|
|
2cc37a4e40 | ||
|
|
076e5dfa9d | ||
|
|
092896bb25 | ||
|
|
7c8f0586e6 | ||
|
|
c5b0872a4c | ||
|
|
a7b88ec38d | ||
|
|
210b398a13 | ||
|
|
bce1e3610e | ||
|
|
6a33e7c99d | ||
|
|
2048e3f65d | ||
|
|
81df2738de | ||
|
|
08fbec416f | ||
|
|
7594b22096 | ||
|
|
d1babf6b9b | ||
|
|
2187c9fb0d | ||
|
|
a5e6059160 | ||
|
|
9ef4aedcce | ||
|
|
f7c7160bf0 | ||
|
|
dbf5740841 | ||
|
|
c07e099a79 | ||
|
|
06cb508f3a | ||
|
|
e93b5b34cc | ||
|
|
4f5d4ef87a | ||
|
|
636da8cc8c | ||
|
|
5de1e2d7bb | ||
|
|
03bbb84894 | ||
|
|
d17394cdd7 | ||
|
|
8467afde38 | ||
|
|
5c016fb4b8 | ||
|
|
2fbc32947c | ||
|
|
c06b57a5f9 | ||
|
|
6d64fffc50 | ||
|
|
0c4ebdaf1d | ||
|
|
2c49fb9321 | ||
|
|
c55de61da9 | ||
|
|
42444ede0a | ||
|
|
3d50f5f5ac | ||
|
|
11d62aebe1 | ||
|
|
edc10a9c2a | ||
|
|
9be5823956 | ||
|
|
35371dde22 | ||
|
|
d3ceb4d7e7 | ||
|
|
a782b57100 | ||
|
|
1360de599f | ||
|
|
fd95f5fed1 | ||
|
|
9426dff5df | ||
|
|
0d083e63c6 | ||
|
|
6ed7c96ec7 | ||
|
|
314570d6c3 |
13
.config/dotnet-tools.json
Normal file
13
.config/dotnet-tools.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"isRoot": true,
|
||||||
|
"tools": {
|
||||||
|
"csharpier": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"commands": [
|
||||||
|
"csharpier"
|
||||||
|
],
|
||||||
|
"rollForward": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/PepperDash.Core/ComTextHelper.cs
Normal file
43
src/PepperDash.Core/ComTextHelper.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class for formatting communication text and byte data for debugging purposes.
|
||||||
|
/// </summary>
|
||||||
|
public class ComTextHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets escaped text for a byte array
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes"></param>
|
||||||
|
/// <returns>string with all bytes escaped</returns>
|
||||||
|
public static string GetEscapedText(byte[] bytes)
|
||||||
|
{
|
||||||
|
return string.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets escaped text for a string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns>string with all bytes escaped</returns>
|
||||||
|
public static string GetEscapedText(string text)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
|
return string.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets debug text for a string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns>string with all non-printable characters escaped</returns>
|
||||||
|
public static string GetDebugText(string text)
|
||||||
|
{
|
||||||
|
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
@@ -37,14 +36,14 @@ namespace PepperDash.Core
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _DebugTimeoutInMs/60000;
|
return _DebugTimeoutInMs / 60000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the RxStreamDebuggingIsEnabled
|
/// Gets or sets the RxStreamDebuggingIsEnabled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool RxStreamDebuggingIsEnabled{ get; private set; }
|
public bool RxStreamDebuggingIsEnabled { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that transmit stream debugging is enabled
|
/// Indicates that transmit stream debugging is enabled
|
||||||
@@ -108,7 +107,7 @@ namespace PepperDash.Core
|
|||||||
TxStreamDebuggingIsEnabled = true;
|
TxStreamDebuggingIsEnabled = true;
|
||||||
|
|
||||||
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
|
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -136,51 +135,4 @@ namespace PepperDash.Core
|
|||||||
DebugExpiryPeriod = null;
|
DebugExpiryPeriod = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The available settings for stream debugging
|
|
||||||
/// </summary>
|
|
||||||
[Flags]
|
|
||||||
/// <summary>
|
|
||||||
/// Enumeration of eStreamDebuggingSetting values
|
|
||||||
/// </summary>
|
|
||||||
public enum eStreamDebuggingSetting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Debug off
|
|
||||||
/// </summary>
|
|
||||||
Off = 0,
|
|
||||||
/// <summary>
|
|
||||||
/// Debug received data
|
|
||||||
/// </summary>
|
|
||||||
Rx = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// Debug transmitted data
|
|
||||||
/// </summary>
|
|
||||||
Tx = 2,
|
|
||||||
/// <summary>
|
|
||||||
/// Debug both received and transmitted data
|
|
||||||
/// </summary>
|
|
||||||
Both = Rx | Tx
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The available settings for stream debugging response types
|
|
||||||
/// </summary>
|
|
||||||
[Flags]
|
|
||||||
public enum eStreamDebuggingDataTypeSettings
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Debug data in byte format
|
|
||||||
/// </summary>
|
|
||||||
Bytes = 0,
|
|
||||||
/// <summary>
|
|
||||||
/// Debug data in text format
|
|
||||||
/// </summary>
|
|
||||||
Text = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// Debug data in both byte and text formats
|
|
||||||
/// </summary>
|
|
||||||
Both = Bytes | Text,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ namespace PepperDash.Core
|
|||||||
if (shellStream.DataAvailable)
|
if (shellStream.DataAvailable)
|
||||||
{
|
{
|
||||||
// empty the buffer if there is data
|
// empty the buffer if there is data
|
||||||
string str = shellStream.Read();
|
shellStream.Read();
|
||||||
}
|
}
|
||||||
shellStream.DataReceived += Stream_DataReceived;
|
shellStream.DataReceived += Stream_DataReceived;
|
||||||
this.LogInformation("Connected");
|
this.LogInformation("Connected");
|
||||||
@@ -288,7 +288,6 @@ namespace PepperDash.Core
|
|||||||
catch (SshConnectionException e)
|
catch (SshConnectionException e)
|
||||||
{
|
{
|
||||||
var ie = e.InnerException; // The details are inside!!
|
var ie = e.InnerException; // The details are inside!!
|
||||||
var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
|
||||||
|
|
||||||
if (ie is SocketException)
|
if (ie is SocketException)
|
||||||
{
|
{
|
||||||
@@ -335,7 +334,6 @@ namespace PepperDash.Core
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
|
||||||
this.LogError("Unhandled exception on connect: {error}", e.Message);
|
this.LogError("Unhandled exception on connect: {error}", e.Message);
|
||||||
this.LogVerbose(e, "Exception details: ");
|
this.LogVerbose(e, "Exception details: ");
|
||||||
disconnectLogged = true;
|
disconnectLogged = true;
|
||||||
@@ -439,18 +437,14 @@ namespace PepperDash.Core
|
|||||||
if (bytesHandler != null)
|
if (bytesHandler != null)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.UTF8.GetBytes(response);
|
var bytes = Encoding.UTF8.GetBytes(response);
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
this.PrintReceivedBytes(bytes);
|
||||||
{
|
|
||||||
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
|
||||||
}
|
|
||||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
var textHandler = TextReceived;
|
var textHandler = TextReceived;
|
||||||
if (textHandler != null)
|
if (textHandler != null)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
this.PrintReceivedText(response);
|
||||||
this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response));
|
|
||||||
|
|
||||||
textHandler(this, new GenericCommMethodReceiveTextArgs(response));
|
textHandler(this, new GenericCommMethodReceiveTextArgs(response));
|
||||||
}
|
}
|
||||||
@@ -507,11 +501,7 @@ namespace PepperDash.Core
|
|||||||
{
|
{
|
||||||
if (client != null && shellStream != null && IsConnected)
|
if (client != null && shellStream != null && IsConnected)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
this.PrintSentText(text);
|
||||||
this.LogInformation(
|
|
||||||
"Sending {length} characters of text: '{text}'",
|
|
||||||
text.Length,
|
|
||||||
ComTextHelper.GetDebugText(text));
|
|
||||||
|
|
||||||
shellStream.Write(text);
|
shellStream.Write(text);
|
||||||
shellStream.Flush();
|
shellStream.Flush();
|
||||||
@@ -526,7 +516,7 @@ namespace PepperDash.Core
|
|||||||
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
|
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
|
||||||
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||||
StopReconnectTimer();
|
StartReconnectTimer();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -544,8 +534,7 @@ namespace PepperDash.Core
|
|||||||
{
|
{
|
||||||
if (client != null && shellStream != null && IsConnected)
|
if (client != null && shellStream != null && IsConnected)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
this.PrintSentBytes(bytes);
|
||||||
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
|
||||||
|
|
||||||
shellStream.Write(bytes, 0, bytes.Length);
|
shellStream.Write(bytes, 0, bytes.Length);
|
||||||
shellStream.Flush();
|
shellStream.Flush();
|
||||||
@@ -560,7 +549,7 @@ namespace PepperDash.Core
|
|||||||
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
|
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||||
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||||
StopReconnectTimer();
|
StartReconnectTimer();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -426,10 +426,7 @@ namespace PepperDash.Core
|
|||||||
var bytesHandler = BytesReceived;
|
var bytesHandler = BytesReceived;
|
||||||
if (bytesHandler != null)
|
if (bytesHandler != null)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
this.PrintReceivedBytes(bytes);
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
|
||||||
}
|
|
||||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||||
}
|
}
|
||||||
var textHandler = TextReceived;
|
var textHandler = TextReceived;
|
||||||
@@ -437,10 +434,7 @@ namespace PepperDash.Core
|
|||||||
{
|
{
|
||||||
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
this.PrintReceivedText(str);
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||||
}
|
}
|
||||||
@@ -456,8 +450,7 @@ namespace PepperDash.Core
|
|||||||
{
|
{
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
// Check debug level before processing byte array
|
// Check debug level before processing byte array
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
this.PrintSentText(text);
|
||||||
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
|
||||||
if (_client != null)
|
if (_client != null)
|
||||||
_client.SendData(bytes, bytes.Length);
|
_client.SendData(bytes, bytes.Length);
|
||||||
}
|
}
|
||||||
@@ -484,8 +477,7 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendBytes(byte[] bytes)
|
public void SendBytes(byte[] bytes)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
this.PrintSentBytes(bytes);
|
||||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
|
||||||
if (_client != null)
|
if (_client != null)
|
||||||
_client.SendData(bytes, bytes.Length);
|
_client.SendData(bytes, bytes.Length);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ namespace PepperDash.Core
|
|||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -135,7 +135,7 @@ namespace PepperDash.Core
|
|||||||
public GenericUdpServer(string key, string address, int port, int bufferSize)
|
public GenericUdpServer(string key, string address, int port, int bufferSize)
|
||||||
: base(key)
|
: base(key)
|
||||||
{
|
{
|
||||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||||
Hostname = address;
|
Hostname = address;
|
||||||
Port = port;
|
Port = port;
|
||||||
BufferSize = bufferSize;
|
BufferSize = bufferSize;
|
||||||
@@ -180,7 +180,7 @@ namespace PepperDash.Core
|
|||||||
/// <param name="programEventType"></param>
|
/// <param name="programEventType"></param>
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
{
|
{
|
||||||
if (programEventType != eProgramStatusEventType.Stopping)
|
if (programEventType != eProgramStatusEventType.Stopping)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Debug.Console(1, this, "Program stopping. Disabling Server");
|
Debug.Console(1, this, "Program stopping. Disabling Server");
|
||||||
@@ -243,7 +243,7 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Disconnect()
|
public void Disconnect()
|
||||||
{
|
{
|
||||||
if(Server != null)
|
if (Server != null)
|
||||||
Server.DisableUDPServer();
|
Server.DisableUDPServer();
|
||||||
|
|
||||||
IsConnected = false;
|
IsConnected = false;
|
||||||
@@ -265,7 +265,7 @@ namespace PepperDash.Core
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (numBytes <= 0)
|
if (numBytes <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var sourceIp = Server.IPAddressLastMessageReceivedFrom;
|
var sourceIp = Server.IPAddressLastMessageReceivedFrom;
|
||||||
@@ -281,17 +281,13 @@ namespace PepperDash.Core
|
|||||||
var bytesHandler = BytesReceived;
|
var bytesHandler = BytesReceived;
|
||||||
if (bytesHandler != null)
|
if (bytesHandler != null)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
this.PrintReceivedBytes(bytes);
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
|
||||||
}
|
|
||||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||||
}
|
}
|
||||||
var textHandler = TextReceived;
|
var textHandler = TextReceived;
|
||||||
if (textHandler != null)
|
if (textHandler != null)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
this.PrintReceivedText(str);
|
||||||
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
|
||||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,8 +314,7 @@ namespace PepperDash.Core
|
|||||||
|
|
||||||
if (IsConnected && Server != null)
|
if (IsConnected && Server != null)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
this.PrintSentText(text);
|
||||||
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
|
||||||
|
|
||||||
Server.SendData(bytes, bytes.Length);
|
Server.SendData(bytes, bytes.Length);
|
||||||
}
|
}
|
||||||
@@ -334,8 +329,7 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendBytes(byte[] bytes)
|
public void SendBytes(byte[] bytes)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
this.PrintSentBytes(bytes);
|
||||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
|
||||||
|
|
||||||
if (IsConnected && Server != null)
|
if (IsConnected && Server != null)
|
||||||
Server.SendData(bytes, bytes.Length);
|
Server.SendData(bytes, bytes.Length);
|
||||||
@@ -343,11 +337,11 @@ namespace PepperDash.Core
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a GenericUdpReceiveTextExtraArgs
|
/// Represents a GenericUdpReceiveTextExtraArgs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericUdpReceiveTextExtraArgs : EventArgs
|
public class GenericUdpReceiveTextExtraArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -359,7 +353,7 @@ namespace PepperDash.Core
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Port { get; private set; }
|
public int Port { get; private set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -373,18 +367,18 @@ namespace PepperDash.Core
|
|||||||
/// <param name="port"></param>
|
/// <param name="port"></param>
|
||||||
/// <param name="bytes"></param>
|
/// <param name="bytes"></param>
|
||||||
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
|
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
|
||||||
{
|
{
|
||||||
Text = text;
|
Text = text;
|
||||||
IpAddress = ipAddress;
|
IpAddress = ipAddress;
|
||||||
Port = port;
|
Port = port;
|
||||||
Bytes = bytes;
|
Bytes = bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stupid S+ Constructor
|
/// Stupid S+ Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GenericUdpReceiveTextExtraArgs() { }
|
public GenericUdpReceiveTextExtraArgs() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
|
|||||||
69
src/PepperDash.Core/Comm/StreamDebuggingExtensions.cs
Normal file
69
src/PepperDash.Core/Comm/StreamDebuggingExtensions.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for stream debugging
|
||||||
|
/// </summary>
|
||||||
|
public static class StreamDebuggingExtensions
|
||||||
|
{
|
||||||
|
private static readonly string app = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? $"App {InitialParametersClass.ApplicationNumber}" : $"{InitialParametersClass.RoomId}";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Print the sent bytes to the console
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="comms">comms device</param>
|
||||||
|
/// <param name="bytes">bytes to print</param>
|
||||||
|
public static void PrintSentBytes(this IStreamDebugging comms, byte[] bytes)
|
||||||
|
{
|
||||||
|
if (!comms.StreamDebugging.TxStreamDebuggingIsEnabled) return;
|
||||||
|
|
||||||
|
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||||
|
|
||||||
|
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Sending {bytes.Length} bytes: '{ComTextHelper.GetEscapedText(bytes)}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Print the received bytes to the console
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="comms">comms device</param>
|
||||||
|
/// <param name="bytes">bytes to print</param>
|
||||||
|
public static void PrintReceivedBytes(this IStreamDebugging comms, byte[] bytes)
|
||||||
|
{
|
||||||
|
if (!comms.StreamDebugging.RxStreamDebuggingIsEnabled) return;
|
||||||
|
|
||||||
|
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||||
|
|
||||||
|
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Received {bytes.Length} bytes: '{ComTextHelper.GetEscapedText(bytes)}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Print the sent text to the console
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="comms">comms device</param>
|
||||||
|
/// <param name="text">text to print</param>
|
||||||
|
public static void PrintSentText(this IStreamDebugging comms, string text)
|
||||||
|
{
|
||||||
|
if (!comms.StreamDebugging.TxStreamDebuggingIsEnabled) return;
|
||||||
|
|
||||||
|
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||||
|
|
||||||
|
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Sending Text: '{ComTextHelper.GetDebugText(text)}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Print the received text to the console
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="comms">comms device</param>
|
||||||
|
/// <param name="text">text to print</param>
|
||||||
|
public static void PrintReceivedText(this IStreamDebugging comms, string text)
|
||||||
|
{
|
||||||
|
if (!comms.StreamDebugging.RxStreamDebuggingIsEnabled) return;
|
||||||
|
|
||||||
|
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||||
|
|
||||||
|
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Received Text: '{ComTextHelper.GetDebugText(text)}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs
Normal file
24
src/PepperDash.Core/Comm/eStreamDebuggingDataTypeSettings.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The available settings for stream debugging data format types
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum eStreamDebuggingDataTypeSettings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Debug data in byte format
|
||||||
|
/// </summary>
|
||||||
|
Bytes = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Debug data in text format
|
||||||
|
/// </summary>
|
||||||
|
Text = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// Debug data in both byte and text formats
|
||||||
|
/// </summary>
|
||||||
|
Both = Bytes | Text
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/PepperDash.Core/Comm/eStreamDebuggingSetting.cs
Normal file
28
src/PepperDash.Core/Comm/eStreamDebuggingSetting.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The available settings for stream debugging
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum eStreamDebuggingSetting
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Debug off
|
||||||
|
/// </summary>
|
||||||
|
Off = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Debug received data
|
||||||
|
/// </summary>
|
||||||
|
Rx = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// Debug transmitted data
|
||||||
|
/// </summary>
|
||||||
|
Tx = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// Debug both received and transmitted data
|
||||||
|
/// </summary>
|
||||||
|
Both = Rx | Tx
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core
|
||||||
@@ -42,7 +39,7 @@ namespace PepperDash.Core
|
|||||||
/// Defines the contract for IBasicCommunication
|
/// Defines the contract for IBasicCommunication
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IBasicCommunication : ICommunicationReceiver
|
public interface IBasicCommunication : ICommunicationReceiver
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send text to the device
|
/// Send text to the device
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -54,7 +51,7 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bytes"></param>
|
/// <param name="bytes"></param>
|
||||||
void SendBytes(byte[] bytes);
|
void SendBytes(byte[] bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a device that implements IBasicCommunication and IStreamDebugging
|
/// Represents a device that implements IBasicCommunication and IStreamDebugging
|
||||||
@@ -67,7 +64,7 @@ namespace PepperDash.Core
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a device with stream debugging capablities
|
/// Represents a device with stream debugging capablities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IStreamDebugging
|
public interface IStreamDebugging : IKeyed
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Object to enable stream debugging
|
/// Object to enable stream debugging
|
||||||
@@ -76,12 +73,12 @@ namespace PepperDash.Core
|
|||||||
CommunicationStreamDebugging StreamDebugging { get; }
|
CommunicationStreamDebugging StreamDebugging { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
|
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
|
||||||
/// GenericTcpIpClient
|
/// GenericTcpIpClient
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ISocketStatus : IBasicCommunication
|
public interface ISocketStatus : IBasicCommunication
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of socket status changes
|
/// Notifies of socket status changes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -93,7 +90,7 @@ namespace PepperDash.Core
|
|||||||
[JsonProperty("clientStatus")]
|
[JsonProperty("clientStatus")]
|
||||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||||
SocketStatus ClientStatus { get; }
|
SocketStatus ClientStatus { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Describes a device that implements ISocketStatus and IStreamDebugging
|
/// Describes a device that implements ISocketStatus and IStreamDebugging
|
||||||
@@ -107,24 +104,24 @@ namespace PepperDash.Core
|
|||||||
/// Describes a device that can automatically attempt to reconnect
|
/// Describes a device that can automatically attempt to reconnect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IAutoReconnect
|
public interface IAutoReconnect
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enable automatic recconnect
|
/// Enable automatic recconnect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("autoReconnect")]
|
[JsonProperty("autoReconnect")]
|
||||||
bool AutoReconnect { get; set; }
|
bool AutoReconnect { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interval in ms to attempt automatic recconnections
|
/// Interval in ms to attempt automatic recconnections
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("autoReconnectIntervalMs")]
|
[JsonProperty("autoReconnectIntervalMs")]
|
||||||
int AutoReconnectIntervalMs { get; set; }
|
int AutoReconnectIntervalMs { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum eGenericCommMethodStatusChangeType
|
public enum eGenericCommMethodStatusChangeType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connected
|
/// Connected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -133,45 +130,45 @@ namespace PepperDash.Core
|
|||||||
/// Disconnected
|
/// Disconnected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Disconnected
|
Disconnected
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This delegate defines handler for IBasicCommunication status changes
|
/// This delegate defines handler for IBasicCommunication status changes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="comm">Device firing the status change</param>
|
/// <param name="comm">Device firing the status change</param>
|
||||||
/// <param name="status"></param>
|
/// <param name="status"></param>
|
||||||
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
|
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericCommMethodReceiveBytesArgs : EventArgs
|
public class GenericCommMethodReceiveBytesArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Bytes
|
/// Gets or sets the Bytes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte[] Bytes { get; private set; }
|
public byte[] Bytes { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bytes"></param>
|
/// <param name="bytes"></param>
|
||||||
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
|
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
|
||||||
{
|
{
|
||||||
Bytes = bytes;
|
Bytes = bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// S+ Constructor
|
/// S+ Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GenericCommMethodReceiveBytesArgs() { }
|
public GenericCommMethodReceiveBytesArgs() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericCommMethodReceiveTextArgs : EventArgs
|
public class GenericCommMethodReceiveTextArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -185,9 +182,9 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
public GenericCommMethodReceiveTextArgs(string text)
|
public GenericCommMethodReceiveTextArgs(string text)
|
||||||
{
|
{
|
||||||
Text = text;
|
Text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
@@ -195,59 +192,14 @@ namespace PepperDash.Core
|
|||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
/// <param name="delimiter"></param>
|
/// <param name="delimiter"></param>
|
||||||
public GenericCommMethodReceiveTextArgs(string text, string delimiter)
|
public GenericCommMethodReceiveTextArgs(string text, string delimiter)
|
||||||
:this(text)
|
: this(text)
|
||||||
{
|
{
|
||||||
Delimiter = delimiter;
|
Delimiter = delimiter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ Constructor
|
|
||||||
/// </summary>
|
|
||||||
public GenericCommMethodReceiveTextArgs() { }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class ComTextHelper
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets escaped text for a byte array
|
/// S+ Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bytes"></param>
|
public GenericCommMethodReceiveTextArgs() { }
|
||||||
/// <returns></returns>
|
}
|
||||||
public static string GetEscapedText(byte[] bytes)
|
|
||||||
{
|
|
||||||
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets escaped text for a string
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <summary>
|
|
||||||
/// GetEscapedText method
|
|
||||||
/// </summary>
|
|
||||||
public static string GetEscapedText(string text)
|
|
||||||
{
|
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
|
||||||
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets debug text for a string
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <summary>
|
|
||||||
/// GetDebugText method
|
|
||||||
/// </summary>
|
|
||||||
public static string GetDebugText(string text)
|
|
||||||
{
|
|
||||||
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -168,7 +168,7 @@ namespace PepperDash.Core
|
|||||||
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
|
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
|
||||||
rollingInterval: RollingInterval.Day,
|
rollingInterval: RollingInterval.Day,
|
||||||
restrictedToMinimumLevel: LogEventLevel.Debug,
|
restrictedToMinimumLevel: LogEventLevel.Debug,
|
||||||
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
|
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 7 : 14,
|
||||||
levelSwitch: _fileLogLevelSwitch
|
levelSwitch: _fileLogLevelSwitch
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1081,9 +1081,6 @@ namespace PepperDash.Core
|
|||||||
/// Logs to Console when at-level, and all messages to error log
|
/// Logs to Console when at-level, and all messages to error log
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
|
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
|
||||||
/// <summary>
|
|
||||||
/// Console method
|
|
||||||
/// </summary>
|
|
||||||
public static void Console(uint level, ErrorLogLevel errorLogLevel,
|
public static void Console(uint level, ErrorLogLevel errorLogLevel,
|
||||||
string format, params object[] items)
|
string format, params object[] items)
|
||||||
{
|
{
|
||||||
@@ -1096,9 +1093,6 @@ namespace PepperDash.Core
|
|||||||
/// it will only be written to the log.
|
/// it will only be written to the log.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
|
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
|
||||||
/// <summary>
|
|
||||||
/// ConsoleWithLog method
|
|
||||||
/// </summary>
|
|
||||||
public static void ConsoleWithLog(uint level, string format, params object[] items)
|
public static void ConsoleWithLog(uint level, string format, params object[] items)
|
||||||
{
|
{
|
||||||
LogMessage(level, format, items);
|
LogMessage(level, format, items);
|
||||||
|
|||||||
@@ -2,25 +2,29 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharpPro;
|
using Crestron.SimplSharpPro;
|
||||||
using Crestron.SimplSharpPro.DeviceSupport;
|
using Crestron.SimplSharpPro.DeviceSupport;
|
||||||
using Crestron.SimplSharpPro.EthernetCommunication;
|
using Crestron.SimplSharpPro.EthernetCommunication;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
using PepperDash.Core.Logging;
|
||||||
using PepperDash.Essentials.Core.Config;
|
using PepperDash.Essentials.Core.Config;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
//using PepperDash.Essentials.Devices.Common.Cameras;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Bridges
|
namespace PepperDash.Essentials.Core.Bridges
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for bridge API variants
|
/// Base class for bridge API variants
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Will be removed in v3.0.0")]
|
||||||
public abstract class BridgeApi : EssentialsDevice
|
public abstract class BridgeApi : EssentialsDevice
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">Device key</param>
|
||||||
protected BridgeApi(string key) :
|
protected BridgeApi(string key) :
|
||||||
base(key)
|
base(key)
|
||||||
{
|
{
|
||||||
@@ -29,23 +33,36 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a EiscApiAdvanced
|
/// Class to link devices and rooms to an EISC Instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EiscApiAdvanced : BridgeApi, ICommunicationMonitor
|
public class EiscApiAdvanced : BridgeApi, ICommunicationMonitor
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the PropertiesConfig
|
||||||
|
/// </summary>
|
||||||
public EiscApiPropertiesConfig PropertiesConfig { get; private set; }
|
public EiscApiPropertiesConfig PropertiesConfig { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the JoinMaps dictionary
|
||||||
|
/// </summary>
|
||||||
public Dictionary<string, JoinMapBaseAdvanced> JoinMaps { get; private set; }
|
public Dictionary<string, JoinMapBaseAdvanced> JoinMaps { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the EISC instance
|
||||||
|
/// </summary>
|
||||||
public BasicTriList Eisc { get; private set; }
|
public BasicTriList Eisc { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dc">Device configuration</param>
|
||||||
|
/// <param name="eisc">EISC instance</param>
|
||||||
public EiscApiAdvanced(DeviceConfig dc, BasicTriList eisc) :
|
public EiscApiAdvanced(DeviceConfig dc, BasicTriList eisc) :
|
||||||
base(dc.Key)
|
base(dc.Key)
|
||||||
{
|
{
|
||||||
JoinMaps = new Dictionary<string, JoinMapBaseAdvanced>();
|
JoinMaps = new Dictionary<string, JoinMapBaseAdvanced>();
|
||||||
|
|
||||||
PropertiesConfig = dc.Properties.ToObject<EiscApiPropertiesConfig>();
|
PropertiesConfig = dc.Properties.ToObject<EiscApiPropertiesConfig>();
|
||||||
//PropertiesConfig = JsonConvert.DeserializeObject<EiscApiPropertiesConfig>(dc.Properties.ToString());
|
|
||||||
|
|
||||||
Eisc = eisc;
|
Eisc = eisc;
|
||||||
|
|
||||||
@@ -60,8 +77,7 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CustomActivate method
|
/// CustomActivate method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool CustomActivate()
|
public override bool CustomActivate()
|
||||||
{
|
{
|
||||||
CommunicationMonitor.Start();
|
CommunicationMonitor.Start();
|
||||||
@@ -83,7 +99,7 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
|
|
||||||
if (PropertiesConfig.Devices == null)
|
if (PropertiesConfig.Devices == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "No devices linked to this bridge");
|
this.LogDebug("No devices linked to this bridge");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,9 +120,7 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, this,
|
this.LogWarning("{deviceKey} is not compatible with this bridge type. Please update the device.", device.Key);
|
||||||
"{0} is not compatible with this bridge type. Please use 'eiscapi' instead, or updae the device.",
|
|
||||||
device.Key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,34 +135,31 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
|
|
||||||
if (registerResult != eDeviceRegistrationUnRegistrationResponse.Success)
|
if (registerResult != eDeviceRegistrationUnRegistrationResponse.Success)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Registration result: {0}", registerResult);
|
this.LogVerbose("Registration result: {registerResult}", registerResult);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "EISC registration successful");
|
this.LogDebug("EISC registration successful");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// LinkRooms method
|
/// Link rooms to this EISC. Rooms MUST implement IBridgeAdvanced
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void LinkRooms()
|
public void LinkRooms()
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "Linking Rooms...");
|
this.LogDebug("Linking Rooms...");
|
||||||
|
|
||||||
if (PropertiesConfig.Rooms == null)
|
if (PropertiesConfig.Rooms == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "No rooms linked to this bridge.");
|
this.LogDebug("No rooms linked to this bridge.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var room in PropertiesConfig.Rooms)
|
foreach (var room in PropertiesConfig.Rooms)
|
||||||
{
|
{
|
||||||
var rm = DeviceManager.GetDeviceForKey(room.RoomKey) as IBridgeAdvanced;
|
if (!(DeviceManager.GetDeviceForKey(room.RoomKey) is IBridgeAdvanced rm))
|
||||||
|
|
||||||
if (rm == null)
|
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this,
|
this.LogDebug("Room {roomKey} does not implement IBridgeAdvanced. Skipping...", room.RoomKey);
|
||||||
"Room {0} does not implement IBridgeAdvanced. Skipping...", room.RoomKey);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,11 +170,8 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a join map
|
/// Adds a join map
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="deviceKey"></param>
|
/// <param name="deviceKey">The key of the device to add the join map for</param>
|
||||||
/// <param name="joinMap"></param>
|
/// <param name="joinMap">The join map to add</param>
|
||||||
/// <summary>
|
|
||||||
/// AddJoinMap method
|
|
||||||
/// </summary>
|
|
||||||
public void AddJoinMap(string deviceKey, JoinMapBaseAdvanced joinMap)
|
public void AddJoinMap(string deviceKey, JoinMapBaseAdvanced joinMap)
|
||||||
{
|
{
|
||||||
if (!JoinMaps.ContainsKey(deviceKey))
|
if (!JoinMaps.ContainsKey(deviceKey))
|
||||||
@@ -172,14 +180,13 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Unable to add join map with key '{0}'. Key already exists in JoinMaps dictionary", deviceKey);
|
this.LogWarning("Unable to add join map with key '{deviceKey}'. Key already exists in JoinMaps dictionary", deviceKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// PrintJoinMaps method
|
/// PrintJoinMaps method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <inheritdoc />
|
|
||||||
public virtual void PrintJoinMaps()
|
public virtual void PrintJoinMaps()
|
||||||
{
|
{
|
||||||
CrestronConsole.ConsoleCommandResponse("Join Maps for EISC IPID: {0}\r\n", Eisc.ID.ToString("X"));
|
CrestronConsole.ConsoleCommandResponse("Join Maps for EISC IPID: {0}\r\n", Eisc.ID.ToString("X"));
|
||||||
@@ -190,17 +197,17 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
joinMap.Value.PrintJoinMapInfo();
|
joinMap.Value.PrintJoinMapInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MarkdownForBridge method
|
/// MarkdownForBridge method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <inheritdoc />
|
|
||||||
public virtual void MarkdownForBridge(string bridgeKey)
|
public virtual void MarkdownForBridge(string bridgeKey)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Writing Joinmaps to files for EISC IPID: {0}", Eisc.ID.ToString("X"));
|
this.LogInformation("Writing Joinmaps to files for EISC IPID: {eiscId}", Eisc.ID.ToString("X"));
|
||||||
|
|
||||||
foreach (var joinMap in JoinMaps)
|
foreach (var joinMap in JoinMaps)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Generating markdown for device '{0}':", joinMap.Key);
|
this.LogInformation("Generating markdown for device '{deviceKey}':", joinMap.Key);
|
||||||
joinMap.Value.MarkdownJoinMapInfo(joinMap.Key, bridgeKey);
|
joinMap.Value.MarkdownJoinMapInfo(joinMap.Key, bridgeKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,53 +215,45 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prints the join map for a device by key
|
/// Prints the join map for a device by key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="deviceKey"></param>
|
/// <param name="deviceKey">The key of the device to print the join map for</param>
|
||||||
/// <summary>
|
|
||||||
/// PrintJoinMapForDevice method
|
|
||||||
/// </summary>
|
|
||||||
public void PrintJoinMapForDevice(string deviceKey)
|
public void PrintJoinMapForDevice(string deviceKey)
|
||||||
{
|
{
|
||||||
var joinMap = JoinMaps[deviceKey];
|
var joinMap = JoinMaps[deviceKey];
|
||||||
|
|
||||||
if (joinMap == null)
|
if (joinMap == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Unable to find joinMap for device with key: '{0}'", deviceKey);
|
this.LogInformation("Unable to find joinMap for device with key: '{deviceKey}'", deviceKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key);
|
this.LogInformation("Join map for device '{deviceKey}' on EISC '{eiscKey}':", deviceKey, Key);
|
||||||
joinMap.PrintJoinMapInfo();
|
joinMap.PrintJoinMapInfo();
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prints the join map for a device by key
|
/// Prints the join map for a device by key in Markdown format
|
||||||
/// </summary>
|
|
||||||
/// <param name="deviceKey"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// MarkdownJoinMapForDevice method
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="deviceKey">The key of the device to print the join map for</param>
|
||||||
|
/// <param name="bridgeKey">The key of the bridge to use for the Markdown output</param>
|
||||||
public void MarkdownJoinMapForDevice(string deviceKey, string bridgeKey)
|
public void MarkdownJoinMapForDevice(string deviceKey, string bridgeKey)
|
||||||
{
|
{
|
||||||
var joinMap = JoinMaps[deviceKey];
|
var joinMap = JoinMaps[deviceKey];
|
||||||
|
|
||||||
if (joinMap == null)
|
if (joinMap == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Unable to find joinMap for device with key: '{0}'", deviceKey);
|
this.LogInformation("Unable to find joinMap for device with key: '{deviceKey}'", deviceKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key);
|
this.LogInformation("Join map for device '{deviceKey}' on EISC '{eiscKey}':", deviceKey, Key);
|
||||||
joinMap.MarkdownJoinMapInfo(deviceKey, bridgeKey);
|
joinMap.MarkdownJoinMapInfo(deviceKey, bridgeKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used for debugging to trigger an action based on a join number and type
|
/// Used for debugging to trigger an action based on a join number and type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="join"></param>
|
/// <param name="join">The join number to execute the action for</param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type">The type of join (digital, analog, serial)</param>
|
||||||
/// <param name="state"></param>
|
/// <param name="state">The state to pass to the action</param>
|
||||||
/// <summary>
|
|
||||||
/// ExecuteJoinAction method
|
|
||||||
/// </summary>
|
|
||||||
public void ExecuteJoinAction(uint join, string type, object state)
|
public void ExecuteJoinAction(uint join, string type, object state)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -263,78 +262,87 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
{
|
{
|
||||||
case "digital":
|
case "digital":
|
||||||
{
|
{
|
||||||
var uo = Eisc.BooleanOutput[join].UserObject as Action<bool>;
|
if (Eisc.BooleanOutput[join].UserObject is Action<bool> userObject)
|
||||||
if (uo != null)
|
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
|
this.LogVerbose("Executing Boolean Action");
|
||||||
uo(Convert.ToBoolean(state));
|
userObject(Convert.ToBoolean(state));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute");
|
this.LogVerbose("User Object is null. Nothing to Execute");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "analog":
|
case "analog":
|
||||||
{
|
{
|
||||||
var uo = Eisc.BooleanOutput[join].UserObject as Action<ushort>;
|
if (Eisc.UShortOutput[join].UserObject is Action<ushort> userObject)
|
||||||
if (uo != null)
|
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
|
this.LogVerbose("Executing Analog Action");
|
||||||
uo(Convert.ToUInt16(state));
|
userObject(Convert.ToUInt16(state));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute"); break;
|
this.LogVerbose("User Object is null. Nothing to Execute");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case "serial":
|
case "serial":
|
||||||
{
|
{
|
||||||
var uo = Eisc.BooleanOutput[join].UserObject as Action<string>;
|
if (Eisc.StringOutput[join].UserObject is Action<string> userObject)
|
||||||
if (uo != null)
|
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
|
this.LogVerbose("Executing Serial Action");
|
||||||
uo(Convert.ToString(state));
|
userObject(Convert.ToString(state));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute");
|
this.LogVerbose("User Object is null. Nothing to Execute");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "Unknown join type. Use digital/serial/analog");
|
this.LogVerbose("Unknown join type. Use digital/serial/analog");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "Error: {0}", e);
|
this.LogError("ExecuteJoinAction error: {message}", e.Message);
|
||||||
|
this.LogDebug(e, "Stack Trace: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles incoming sig changes
|
/// Handle incoming sig changes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="currentDevice"></param>
|
/// <param name="currentDevice">BasicTriList device that triggered the event</param>
|
||||||
/// <param name="args"></param>
|
/// <param name="args">Event arguments containing the signal information</param>
|
||||||
protected void Eisc_SigChange(object currentDevice, SigEventArgs args)
|
protected void Eisc_SigChange(object currentDevice, SigEventArgs args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "EiscApiAdvanced change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
|
this.LogVerbose("EiscApiAdvanced change: {type} {number}={value}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
|
||||||
var uo = args.Sig.UserObject;
|
var userObject = args.Sig.UserObject;
|
||||||
|
|
||||||
if (uo == null) return;
|
if (userObject == null) return;
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "Executing Action: {0}", uo.ToString());
|
|
||||||
if (uo is Action<bool>)
|
if (userObject is Action<bool>)
|
||||||
(uo as Action<bool>)(args.Sig.BoolValue);
|
{
|
||||||
else if (uo is Action<ushort>)
|
this.LogDebug("Executing Boolean Action");
|
||||||
(uo as Action<ushort>)(args.Sig.UShortValue);
|
(userObject as Action<bool>)(args.Sig.BoolValue);
|
||||||
else if (uo is Action<string>)
|
}
|
||||||
(uo as Action<string>)(args.Sig.StringValue);
|
else if (userObject is Action<ushort>)
|
||||||
|
{
|
||||||
|
this.LogDebug("Executing Analog Action");
|
||||||
|
(userObject as Action<ushort>)(args.Sig.UShortValue);
|
||||||
|
}
|
||||||
|
else if (userObject is Action<string>)
|
||||||
|
{
|
||||||
|
this.LogDebug("Executing Serial Action");
|
||||||
|
(userObject as Action<string>)(args.Sig.StringValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Error in Eisc_SigChange handler: {0}", e);
|
this.LogError("Eisc_SigChange handler error: {message}", e.Message);
|
||||||
|
this.LogDebug(e, "Stack Trace: ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,22 +431,33 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a EiscApiAdvancedFactory
|
/// Factory class for EiscApiAdvanced devices
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Supported types:
|
||||||
|
/// eiscapiadv - Create a standard EISC client over TCP/IP
|
||||||
|
/// eiscapiadvanced - Create a standard EISC client over TCP/IP
|
||||||
|
/// eiscapiadvancedserver - Create an EISC server
|
||||||
|
/// eiscapiadvancedclient - Create an EISC client
|
||||||
|
/// vceiscapiadv - Create a VC-4 EISC client
|
||||||
|
/// vceiscapiadvanced - Create a VC-4 EISC client
|
||||||
|
/// eiscapiadvudp - Create a standard EISC client over UDP
|
||||||
|
/// eiscapiadvancedudp - Create a standard EISC client over UDP
|
||||||
|
/// </remarks>
|
||||||
public class EiscApiAdvancedFactory : EssentialsDeviceFactory<EiscApiAdvanced>
|
public class EiscApiAdvancedFactory : EssentialsDeviceFactory<EiscApiAdvanced>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
public EiscApiAdvancedFactory()
|
public EiscApiAdvancedFactory()
|
||||||
{
|
{
|
||||||
TypeNames = new List<string> { "eiscapiadv", "eiscapiadvanced", "eiscapiadvancedserver", "eiscapiadvancedclient", "vceiscapiadv", "vceiscapiadvanced" };
|
TypeNames = new List<string> { "eiscapiadv", "eiscapiadvanced", "eiscapiadvancedserver", "eiscapiadvancedclient", "vceiscapiadv", "vceiscapiadvanced", "eiscapiadvudp", "eiscapiadvancedudp" };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// BuildDevice method
|
|
||||||
/// </summary>
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new EiscApiAdvanced Device");
|
Debug.LogDebug("Attempting to create new EiscApiAdvanced Device");
|
||||||
|
|
||||||
var controlProperties = CommFactory.GetControlPropertiesConfig(dc);
|
var controlProperties = CommFactory.GetControlPropertiesConfig(dc);
|
||||||
|
|
||||||
@@ -446,6 +465,13 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
|
|
||||||
switch (dc.Type.ToLower())
|
switch (dc.Type.ToLower())
|
||||||
{
|
{
|
||||||
|
case "eiscapiadvudp":
|
||||||
|
case "eiscapiadvancedudp":
|
||||||
|
{
|
||||||
|
eisc = new EthernetIntersystemCommunications(controlProperties.IpIdInt,
|
||||||
|
controlProperties.TcpSshProperties.Address, Global.ControlSystem);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "eiscapiadv":
|
case "eiscapiadv":
|
||||||
case "eiscapiadvanced":
|
case "eiscapiadvanced":
|
||||||
{
|
{
|
||||||
@@ -468,7 +494,7 @@ namespace PepperDash.Essentials.Core.Bridges
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(controlProperties.RoomId))
|
if (string.IsNullOrEmpty(controlProperties.RoomId))
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Unable to build VC-4 EISC Client for device {0}. Room ID is missing or empty", dc.Key);
|
Debug.LogInformation("Unable to build VC-4 EISC Client for device {deviceKey}. Room ID is missing or empty", dc.Key);
|
||||||
eisc = null;
|
eisc = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ using Serilog.Events;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a CecPortController
|
/// Represents a CecPortController
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CecPortController : Device, IBasicCommunicationWithStreamDebugging
|
public class CecPortController : Device, IBasicCommunicationWithStreamDebugging
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the StreamDebugging
|
/// Gets or sets the StreamDebugging
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||||
|
|
||||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||||
@@ -33,16 +33,16 @@ namespace PepperDash.Essentials.Core
|
|||||||
ICec Port;
|
ICec Port;
|
||||||
|
|
||||||
public CecPortController(string key, Func<EssentialsControlPropertiesConfig, ICec> postActivationFunc,
|
public CecPortController(string key, Func<EssentialsControlPropertiesConfig, ICec> postActivationFunc,
|
||||||
EssentialsControlPropertiesConfig config):base(key)
|
EssentialsControlPropertiesConfig config) : base(key)
|
||||||
{
|
{
|
||||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||||
|
|
||||||
AddPostActivationAction(() =>
|
AddPostActivationAction(() =>
|
||||||
{
|
{
|
||||||
Port = postActivationFunc(config);
|
Port = postActivationFunc(config);
|
||||||
|
|
||||||
Port.StreamCec.CecChange += StreamCec_CecChange;
|
Port.StreamCec.CecChange += StreamCec_CecChange;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public CecPortController(string key, ICec port)
|
public CecPortController(string key, ICec port)
|
||||||
@@ -58,27 +58,25 @@ namespace PepperDash.Essentials.Core
|
|||||||
if (args.EventId == CecEventIds.CecMessageReceivedEventId)
|
if (args.EventId == CecEventIds.CecMessageReceivedEventId)
|
||||||
OnDataReceived(cecDevice.Received.StringValue);
|
OnDataReceived(cecDevice.Received.StringValue);
|
||||||
else if (args.EventId == CecEventIds.ErrorFeedbackEventId)
|
else if (args.EventId == CecEventIds.ErrorFeedbackEventId)
|
||||||
if(cecDevice.ErrorFeedback.BoolValue)
|
if (cecDevice.ErrorFeedback.BoolValue)
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "CEC NAK Error");
|
Debug.LogMessage(LogEventLevel.Verbose, this, "CEC NAK Error");
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnDataReceived(string s)
|
void OnDataReceived(string s)
|
||||||
{
|
{
|
||||||
var bytesHandler = BytesReceived;
|
var bytesHandler = BytesReceived;
|
||||||
if (bytesHandler != null)
|
if (bytesHandler != null)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(s);
|
var bytes = Encoding.GetEncoding(28591).GetBytes(s);
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
this.PrintReceivedBytes(bytes);
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes));
|
|
||||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||||
}
|
}
|
||||||
var textHandler = TextReceived;
|
var textHandler = TextReceived;
|
||||||
if (textHandler != null)
|
if (textHandler != null)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
this.PrintReceivedText(s);
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
|
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
|
||||||
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IBasicCommunication Members
|
#region IBasicCommunication Members
|
||||||
@@ -90,8 +88,7 @@ namespace PepperDash.Essentials.Core
|
|||||||
{
|
{
|
||||||
if (Port == null)
|
if (Port == null)
|
||||||
return;
|
return;
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
this.PrintSentText(text);
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text);
|
|
||||||
Port.StreamCec.Send.StringValue = text;
|
Port.StreamCec.Send.StringValue = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +100,8 @@ namespace PepperDash.Essentials.Core
|
|||||||
if (Port == null)
|
if (Port == null)
|
||||||
return;
|
return;
|
||||||
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
this.PrintSentBytes(bytes);
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||||
Port.StreamCec.Send.StringValue = text;
|
Port.StreamCec.Send.StringValue = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharpPro;
|
using Crestron.SimplSharpPro;
|
||||||
|
using Crestron.SimplSharpPro.GeneralIO;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
@@ -23,10 +25,10 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// Event fired when bytes are received
|
/// Event fired when bytes are received
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when text is received
|
/// Event fired when text is received
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -38,12 +40,12 @@ namespace PepperDash.Essentials.Core
|
|||||||
ComPort.ComPortSpec Spec;
|
ComPort.ComPortSpec Spec;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="postActivationFunc"></param>
|
/// <param name="postActivationFunc"></param>
|
||||||
/// <param name="spec"></param>
|
/// <param name="spec"></param>
|
||||||
/// <param name="config"></param>
|
/// <param name="config"></param>
|
||||||
public ComPortController(string key, Func<EssentialsControlPropertiesConfig, ComPort> postActivationFunc,
|
public ComPortController(string key, Func<EssentialsControlPropertiesConfig, ComPort> postActivationFunc,
|
||||||
ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key)
|
ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key)
|
||||||
{
|
{
|
||||||
@@ -85,61 +87,29 @@ namespace PepperDash.Essentials.Core
|
|||||||
{
|
{
|
||||||
if (Port == null)
|
if (Port == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Configured com Port for this device does not exist.");
|
this.LogInformation($"Configured {Port.Parent.GetType().Name}-comport-{Port.ID} for {Key} does not exist.");
|
||||||
return;
|
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)
|
if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102)
|
||||||
{
|
{
|
||||||
//this.LogInformation($"INFO: Attempting to register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID}");
|
var result = Port.Register();
|
||||||
var result = genericDevice.Register();
|
|
||||||
if (result != eDeviceRegistrationUnRegistrationResponse.Success)
|
if (result != eDeviceRegistrationUnRegistrationResponse.Success)
|
||||||
{
|
{
|
||||||
this.LogError($"ERROR: Cannot register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
|
this.LogError($"Cannot register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
|
||||||
return; // false
|
return;
|
||||||
}
|
}
|
||||||
|
this.LogInformation($"Successfully registered {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
|
||||||
}
|
}
|
||||||
|
|
||||||
var specResult = Port.SetComPortSpec(Spec);
|
var specResult = Port.SetComPortSpec(Spec);
|
||||||
if (specResult != 0)
|
if (specResult != 0)
|
||||||
{
|
{
|
||||||
this.LogError($"ERROR: Cannot set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
|
this.LogError($"Cannot set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//this.LogInformation($"INFO: Successfully set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
|
this.LogInformation($"Successfully set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
|
||||||
|
|
||||||
|
|
||||||
// TODO [ ] - Remove debug logging once verified working
|
|
||||||
// if (Port.Parent is CenIoCom102)
|
|
||||||
// {
|
|
||||||
// Port.PropertyChanged += (s, e) =>
|
|
||||||
// {
|
|
||||||
// this.LogInformation($@"RegisterAndConfigureComPort: PropertyChanged Fired >>
|
|
||||||
// comPort-'{Port.ID}',
|
|
||||||
// Property Changed-'{e.Property}',
|
|
||||||
// Value Changed-'{e.Value}',
|
|
||||||
// deviceName-'{Port.DeviceName}',
|
|
||||||
// parentDevice-'{Port.ParentDevice}',
|
|
||||||
// parent-`{Port.Parent}`,
|
|
||||||
// online-`{Port.IsOnline}`,
|
|
||||||
// present-`{Port.Present}`,
|
|
||||||
// supportedBaudRates-'{Port.SupportedBaudRates}'");
|
|
||||||
// };
|
|
||||||
// Port.ExtendedInformationChanged += (s, e) =>
|
|
||||||
// {
|
|
||||||
|
|
||||||
// this.LogInformation($@"RegisterAndConfigureComPort: ExtendedInformationChanged Fired >>
|
|
||||||
// comPort-'{Port.ID}',
|
|
||||||
// {e.Protocol},
|
|
||||||
// {e.BaudRate},
|
|
||||||
// {e.Parity},
|
|
||||||
// {e.DataBits},
|
|
||||||
// {e.StopBits},
|
|
||||||
// HW Handshake-'{e.HardwareHandshakeSetting}',
|
|
||||||
// SW Handshake-'{e.SoftwareHandshakeSetting}'");
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
Port.SerialDataReceived += Port_SerialDataReceived;
|
Port.SerialDataReceived += Port_SerialDataReceived;
|
||||||
}
|
}
|
||||||
@@ -165,16 +135,14 @@ namespace PepperDash.Essentials.Core
|
|||||||
if (bytesHandler != null)
|
if (bytesHandler != null)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(s);
|
var bytes = Encoding.GetEncoding(28591).GetBytes(s);
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
this.PrintReceivedBytes(bytes);
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes));
|
|
||||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||||
eventSubscribed = true;
|
eventSubscribed = true;
|
||||||
}
|
}
|
||||||
var textHandler = TextReceived;
|
var textHandler = TextReceived;
|
||||||
if (textHandler != null)
|
if (textHandler != null)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
this.PrintReceivedText(s);
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
|
|
||||||
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
|
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
|
||||||
eventSubscribed = true;
|
eventSubscribed = true;
|
||||||
}
|
}
|
||||||
@@ -201,8 +169,7 @@ namespace PepperDash.Essentials.Core
|
|||||||
if (Port == null)
|
if (Port == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
this.PrintSentText(text);
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text);
|
|
||||||
Port.Send(text);
|
Port.Send(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,8 +181,7 @@ namespace PepperDash.Essentials.Core
|
|||||||
if (Port == null)
|
if (Port == null)
|
||||||
return;
|
return;
|
||||||
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
this.PrintSentBytes(bytes);
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
|
||||||
|
|
||||||
Port.Send(text);
|
Port.Send(text);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ using PepperDash.Core;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Config
|
namespace PepperDash.Essentials.Core.Config
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads the ConfigObject from the file
|
/// Loads the ConfigObject from the file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EssentialsConfig : BasicConfig
|
public class EssentialsConfig : BasicConfig
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the SystemUrl
|
/// Gets or sets the SystemUrl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -32,24 +32,33 @@ namespace PepperDash.Essentials.Core.Config
|
|||||||
/// Gets the SystemUuid extracted from the SystemUrl
|
/// Gets the SystemUuid extracted from the SystemUrl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("systemUuid")]
|
[JsonProperty("systemUuid")]
|
||||||
public string SystemUuid
|
public string SystemUuid
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(SystemUrl))
|
string uuid;
|
||||||
return "missing url";
|
|
||||||
|
|
||||||
if (SystemUrl.Contains("#"))
|
if (string.IsNullOrEmpty(SystemUrl))
|
||||||
{
|
{
|
||||||
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
|
uuid = "missing url";
|
||||||
string uuid = result.Groups[1].Value;
|
|
||||||
return uuid;
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*");
|
|
||||||
string uuid = result.Groups[1].Value;
|
|
||||||
return uuid;
|
|
||||||
}
|
}
|
||||||
|
else if (SystemUrl.Contains("#"))
|
||||||
|
{
|
||||||
|
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
|
||||||
|
uuid = result.Groups[1].Value;
|
||||||
|
}
|
||||||
|
else if (SystemUrl.Contains("detail"))
|
||||||
|
{
|
||||||
|
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/detail\/(.*)\/.*");
|
||||||
|
uuid = result.Groups[1].Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*");
|
||||||
|
uuid = result.Groups[1].Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,24 +66,33 @@ namespace PepperDash.Essentials.Core.Config
|
|||||||
/// Gets the TemplateUuid extracted from the TemplateUrl
|
/// Gets the TemplateUuid extracted from the TemplateUrl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("templateUuid")]
|
[JsonProperty("templateUuid")]
|
||||||
public string TemplateUuid
|
public string TemplateUuid
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(TemplateUrl))
|
string uuid;
|
||||||
return "missing template url";
|
|
||||||
|
|
||||||
if (TemplateUrl.Contains("#"))
|
if (string.IsNullOrEmpty(TemplateUrl))
|
||||||
{
|
{
|
||||||
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
|
uuid = "missing template url";
|
||||||
string uuid = result.Groups[1].Value;
|
|
||||||
return uuid;
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*");
|
|
||||||
string uuid = result.Groups[2].Value;
|
|
||||||
return uuid;
|
|
||||||
}
|
}
|
||||||
|
else if (TemplateUrl.Contains("#"))
|
||||||
|
{
|
||||||
|
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
|
||||||
|
uuid = result.Groups[1].Value;
|
||||||
|
}
|
||||||
|
else if (TemplateUrl.Contains("detail"))
|
||||||
|
{
|
||||||
|
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/detail\/(.*)\/system-template-versions\/detail\/(.*)\/.*");
|
||||||
|
uuid = result.Groups[2].Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*");
|
||||||
|
uuid = result.Groups[2].Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +115,7 @@ namespace PepperDash.Essentials.Core.Config
|
|||||||
{
|
{
|
||||||
Rooms = new List<DeviceConfig>();
|
Rooms = new List<DeviceConfig>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents version data for Essentials and its packages
|
/// Represents version data for Essentials and its packages
|
||||||
@@ -147,7 +165,7 @@ namespace PepperDash.Essentials.Core.Config
|
|||||||
/// Represents a SystemTemplateConfigs
|
/// Represents a SystemTemplateConfigs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SystemTemplateConfigs
|
public class SystemTemplateConfigs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the System
|
/// Gets or sets the System
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -157,5 +175,5 @@ namespace PepperDash.Essentials.Core.Config
|
|||||||
/// Gets or sets the Template
|
/// Gets or sets the Template
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public EssentialsConfig Template { get; set; }
|
public EssentialsConfig Template { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines minimum functionality for an audio zone
|
||||||
|
/// </summary>
|
||||||
|
public interface IAudioZone : IBasicVolumeWithFeedback
|
||||||
|
{
|
||||||
|
void SelectInput(ushort input);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Identifies a device that contains audio zones
|
||||||
|
/// </summary>
|
||||||
|
public interface IAudioZones : IRouting
|
||||||
|
{
|
||||||
|
Dictionary<uint, IAudioZone> Zone { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using PepperDash.Core;
|
||||||
|
|
||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines minimal volume and mute control methods
|
||||||
|
/// </summary>
|
||||||
|
public interface IBasicVolumeControls : IKeyName
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Increases the volume
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pressRelease">Indicates whether the volume change is a press and hold action</param>
|
||||||
|
void VolumeUp(bool pressRelease);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decreases the volume
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pressRelease">Indicates whether the volume change is a press and hold action</param>
|
||||||
|
void VolumeDown(bool pressRelease);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toggles the mute state
|
||||||
|
/// </summary>
|
||||||
|
void MuteToggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the contract for IBasicVolumeWithFeedback
|
||||||
|
/// </summary>
|
||||||
|
public interface IBasicVolumeWithFeedback : IBasicVolumeControls
|
||||||
|
{
|
||||||
|
BoolFeedback MuteFeedback { get; }
|
||||||
|
void MuteOn();
|
||||||
|
void MuteOff();
|
||||||
|
void SetVolume(ushort level);
|
||||||
|
IntFeedback VolumeLevelFeedback { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the contract for IBasicVolumeWithFeedbackAdvanced
|
||||||
|
/// </summary>
|
||||||
|
public interface IBasicVolumeWithFeedbackAdvanced : IBasicVolumeWithFeedback
|
||||||
|
{
|
||||||
|
int RawVolumeLevel { get; }
|
||||||
|
|
||||||
|
eVolumeLevelUnits Units { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the contract for IFullAudioSettings
|
||||||
|
/// </summary>
|
||||||
|
public interface IFullAudioSettings : IBasicVolumeWithFeedback
|
||||||
|
{
|
||||||
|
void SetBalance(ushort level);
|
||||||
|
void BalanceLeft(bool pressRelease);
|
||||||
|
void BalanceRight(bool pressRelease);
|
||||||
|
|
||||||
|
void SetBass(ushort level);
|
||||||
|
void BassUp(bool pressRelease);
|
||||||
|
void BassDown(bool pressRelease);
|
||||||
|
|
||||||
|
void SetTreble(ushort level);
|
||||||
|
void TrebleUp(bool pressRelease);
|
||||||
|
void TrebleDown(bool pressRelease);
|
||||||
|
|
||||||
|
bool hasMaxVolume { get; }
|
||||||
|
void SetMaxVolume(ushort level);
|
||||||
|
void MaxVolumeUp(bool pressRelease);
|
||||||
|
void MaxVolumeDown(bool pressRelease);
|
||||||
|
|
||||||
|
bool hasDefaultVolume { get; }
|
||||||
|
void SetDefaultVolume(ushort level);
|
||||||
|
void DefaultVolumeUp(bool pressRelease);
|
||||||
|
void DefaultVolumeDown(bool pressRelease);
|
||||||
|
|
||||||
|
void LoudnessToggle();
|
||||||
|
void MonoToggle();
|
||||||
|
|
||||||
|
BoolFeedback LoudnessFeedback { get; }
|
||||||
|
BoolFeedback MonoFeedback { get; }
|
||||||
|
IntFeedback BalanceFeedback { get; }
|
||||||
|
IntFeedback BassFeedback { get; }
|
||||||
|
IntFeedback TrebleFeedback { get; }
|
||||||
|
IntFeedback MaxVolumeFeedback { get; }
|
||||||
|
IntFeedback DefaultVolumeFeedback { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the contract for IHasCurrentVolumeControls
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasCurrentVolumeControls
|
||||||
|
{
|
||||||
|
IBasicVolumeControls CurrentVolumeControls { get; }
|
||||||
|
event EventHandler<VolumeDeviceChangeEventArgs> CurrentVolumeDeviceChange;
|
||||||
|
|
||||||
|
void SetDefaultLevels();
|
||||||
|
|
||||||
|
bool ZeroVolumeWhenSwtichingVolumeDevices { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines basic mute control methods
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasMuteControl
|
||||||
|
{
|
||||||
|
void MuteToggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines mute control methods and properties with feedback
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasMuteControlWithFeedback : IHasMuteControl
|
||||||
|
{
|
||||||
|
BoolFeedback MuteFeedback { get; }
|
||||||
|
void MuteOn();
|
||||||
|
void MuteOff();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the contract for IHasVolumeControl
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasVolumeControl
|
||||||
|
{
|
||||||
|
void VolumeUp(bool pressRelease);
|
||||||
|
void VolumeDown(bool pressRelease);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines volume control methods and properties with feedback
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasVolumeControlWithFeedback : IHasVolumeControl
|
||||||
|
{
|
||||||
|
void SetVolume(ushort level);
|
||||||
|
IntFeedback VolumeLevelFeedback { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the contract for IHasVolumeDevice
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasVolumeDevice
|
||||||
|
{
|
||||||
|
IBasicVolumeControls VolumeDevice { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace PepperDash.Essentials.Core
|
||||||
|
{
|
||||||
|
public enum eVolumeLevelUnits
|
||||||
|
{
|
||||||
|
Decibels,
|
||||||
|
Percent,
|
||||||
|
Relative,
|
||||||
|
Absolute
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -436,14 +436,14 @@ namespace PepperDash.Essentials.Core
|
|||||||
CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Input Ports:{2}", s, inputPorts.Count, CrestronEnvironment.NewLine);
|
CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Input Ports:{2}", s, inputPorts.Count, CrestronEnvironment.NewLine);
|
||||||
foreach (var routingInputPort in inputPorts)
|
foreach (var routingInputPort in inputPorts)
|
||||||
{
|
{
|
||||||
CrestronConsole.ConsoleCommandResponse("{0}{1}", routingInputPort.Key, CrestronEnvironment.NewLine);
|
CrestronConsole.ConsoleCommandResponse("key: {0} signalType: {1}{2}", routingInputPort.Key, routingInputPort.Type, CrestronEnvironment.NewLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (outputPorts == null) return;
|
if (outputPorts == null) return;
|
||||||
CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Output Ports:{2}", s, outputPorts.Count, CrestronEnvironment.NewLine);
|
CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Output Ports:{2}", s, outputPorts.Count, CrestronEnvironment.NewLine);
|
||||||
foreach (var routingOutputPort in outputPorts)
|
foreach (var routingOutputPort in outputPorts)
|
||||||
{
|
{
|
||||||
CrestronConsole.ConsoleCommandResponse("{0}{1}", routingOutputPort.Key, CrestronEnvironment.NewLine);
|
CrestronConsole.ConsoleCommandResponse("key: {0} signalType: {1}{2}", routingOutputPort.Key, routingOutputPort.Type, CrestronEnvironment.NewLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines minimal volume and mute control methods
|
|
||||||
/// </summary>
|
|
||||||
public interface IBasicVolumeControls
|
|
||||||
{
|
|
||||||
void VolumeUp(bool pressRelease);
|
|
||||||
void VolumeDown(bool pressRelease);
|
|
||||||
void MuteToggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the contract for IHasVolumeControl
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasVolumeControl
|
|
||||||
{
|
|
||||||
void VolumeUp(bool pressRelease);
|
|
||||||
void VolumeDown(bool pressRelease);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines volume control methods and properties with feedback
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasVolumeControlWithFeedback : IHasVolumeControl
|
|
||||||
{
|
|
||||||
void SetVolume(ushort level);
|
|
||||||
IntFeedback VolumeLevelFeedback { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines basic mute control methods
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasMuteControl
|
|
||||||
{
|
|
||||||
void MuteToggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines mute control methods and properties with feedback
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasMuteControlWithFeedback : IHasMuteControl
|
|
||||||
{
|
|
||||||
BoolFeedback MuteFeedback { get; }
|
|
||||||
void MuteOn();
|
|
||||||
void MuteOff();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the contract for IBasicVolumeWithFeedback
|
|
||||||
/// </summary>
|
|
||||||
public interface IBasicVolumeWithFeedback : IBasicVolumeControls
|
|
||||||
{
|
|
||||||
BoolFeedback MuteFeedback { get; }
|
|
||||||
void MuteOn();
|
|
||||||
void MuteOff();
|
|
||||||
void SetVolume(ushort level);
|
|
||||||
IntFeedback VolumeLevelFeedback { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the contract for IBasicVolumeWithFeedbackAdvanced
|
|
||||||
/// </summary>
|
|
||||||
public interface IBasicVolumeWithFeedbackAdvanced : IBasicVolumeWithFeedback
|
|
||||||
{
|
|
||||||
int RawVolumeLevel { get; }
|
|
||||||
|
|
||||||
eVolumeLevelUnits Units { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum eVolumeLevelUnits
|
|
||||||
{
|
|
||||||
Decibels,
|
|
||||||
Percent,
|
|
||||||
Relative,
|
|
||||||
Absolute
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the contract for IHasCurrentVolumeControls
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasCurrentVolumeControls
|
|
||||||
{
|
|
||||||
IBasicVolumeControls CurrentVolumeControls { get; }
|
|
||||||
event EventHandler<VolumeDeviceChangeEventArgs> CurrentVolumeDeviceChange;
|
|
||||||
|
|
||||||
void SetDefaultLevels();
|
|
||||||
|
|
||||||
bool ZeroVolumeWhenSwtichingVolumeDevices { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the contract for IFullAudioSettings
|
|
||||||
/// </summary>
|
|
||||||
public interface IFullAudioSettings : IBasicVolumeWithFeedback
|
|
||||||
{
|
|
||||||
void SetBalance(ushort level);
|
|
||||||
void BalanceLeft(bool pressRelease);
|
|
||||||
void BalanceRight(bool pressRelease);
|
|
||||||
|
|
||||||
void SetBass(ushort level);
|
|
||||||
void BassUp(bool pressRelease);
|
|
||||||
void BassDown(bool pressRelease);
|
|
||||||
|
|
||||||
void SetTreble(ushort level);
|
|
||||||
void TrebleUp(bool pressRelease);
|
|
||||||
void TrebleDown(bool pressRelease);
|
|
||||||
|
|
||||||
bool hasMaxVolume { get; }
|
|
||||||
void SetMaxVolume(ushort level);
|
|
||||||
void MaxVolumeUp(bool pressRelease);
|
|
||||||
void MaxVolumeDown(bool pressRelease);
|
|
||||||
|
|
||||||
bool hasDefaultVolume { get; }
|
|
||||||
void SetDefaultVolume(ushort level);
|
|
||||||
void DefaultVolumeUp(bool pressRelease);
|
|
||||||
void DefaultVolumeDown(bool pressRelease);
|
|
||||||
|
|
||||||
void LoudnessToggle();
|
|
||||||
void MonoToggle();
|
|
||||||
|
|
||||||
BoolFeedback LoudnessFeedback { get; }
|
|
||||||
BoolFeedback MonoFeedback { get; }
|
|
||||||
IntFeedback BalanceFeedback { get; }
|
|
||||||
IntFeedback BassFeedback { get; }
|
|
||||||
IntFeedback TrebleFeedback { get; }
|
|
||||||
IntFeedback MaxVolumeFeedback { get; }
|
|
||||||
IntFeedback DefaultVolumeFeedback { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the contract for IHasVolumeDevice
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasVolumeDevice
|
|
||||||
{
|
|
||||||
IBasicVolumeControls VolumeDevice { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Identifies a device that contains audio zones
|
|
||||||
/// </summary>
|
|
||||||
public interface IAudioZones : IRouting
|
|
||||||
{
|
|
||||||
Dictionary<uint, IAudioZone> Zone { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines minimum functionality for an audio zone
|
|
||||||
/// </summary>
|
|
||||||
public interface IAudioZone : IBasicVolumeWithFeedback
|
|
||||||
{
|
|
||||||
void SelectInput(ushort input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -77,9 +77,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// A name that will override the source's name on the UI
|
/// A name that will override the source's name on the UI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Name
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using PepperDash.Essentials.Core;
|
|
||||||
using PepperDash.Essentials.Core.Config;
|
|
||||||
using PepperDash.Essentials.Core.Devices;
|
using PepperDash.Essentials.Core.Devices;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
@@ -25,17 +19,25 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluates the room info and custom properties from Fusion and updates the system properties aa needed
|
/// Evaluates the room info and custom properties from Fusion and updates the system properties aa needed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="roomInfo"></param>
|
/// <param name="room">The room associated with this Fusion instance</param>
|
||||||
public void EvaluateRoomInfo(string roomKey, RoomInformation roomInfo)
|
/// <param name="roomInfo">The room information from Fusion</param>
|
||||||
|
/// <param name="useFusionRoomName"></param>
|
||||||
|
public void EvaluateRoomInfo(IEssentialsRoom room, RoomInformation roomInfo, bool useFusionRoomName)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var reconfigurableDevices = DeviceManager.AllDevices.Where(d => d is ReconfigurableDevice);
|
var reconfigurableDevices = DeviceManager.AllDevices.OfType<ReconfigurableDevice>();
|
||||||
|
|
||||||
foreach (var device in reconfigurableDevices)
|
foreach (var device in reconfigurableDevices)
|
||||||
{
|
{
|
||||||
// Get the current device config so new values can be overwritten over existing
|
// Get the current device config so new values can be overwritten over existing
|
||||||
var deviceConfig = (device as ReconfigurableDevice).Config;
|
var deviceConfig = device.Config;
|
||||||
|
|
||||||
|
if (device is IEssentialsRoom)
|
||||||
|
{
|
||||||
|
// Skipping room name as this will affect ALL room instances in the configuration and cause unintended consequences when multiple rooms are present and multiple Fusion instances are used
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (device is RoomOnToDefaultSourceWhenOccupied)
|
if (device is RoomOnToDefaultSourceWhenOccupied)
|
||||||
{
|
{
|
||||||
@@ -85,36 +87,49 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
|
|
||||||
deviceConfig.Properties = JToken.FromObject(devProps);
|
deviceConfig.Properties = JToken.FromObject(devProps);
|
||||||
}
|
}
|
||||||
else if (device is IEssentialsRoom)
|
|
||||||
{
|
|
||||||
// Set the room name
|
|
||||||
if (!string.IsNullOrEmpty(roomInfo.Name))
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "Current Room Name: {0}. New Room Name: {1}", deviceConfig.Name, roomInfo.Name);
|
|
||||||
// Set the name in config
|
|
||||||
deviceConfig.Name = roomInfo.Name;
|
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "Room Name Successfully Changed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the help message
|
|
||||||
var helpMessage = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomHelpMessage"));
|
|
||||||
if (helpMessage != null)
|
|
||||||
{
|
|
||||||
//Debug.LogMessage(LogEventLevel.Debug, "Current Help Message: {0}. New Help Message: {1}", deviceConfig.Properties["help"]["message"].Value<string>(ToString()), helpMessage.CustomFieldValue);
|
|
||||||
deviceConfig.Properties["helpMessage"] = (string)helpMessage.CustomFieldValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the config on the device
|
// Set the config on the device
|
||||||
(device as ReconfigurableDevice).SetConfig(deviceConfig);
|
device.SetConfig(deviceConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(room is ReconfigurableDevice reconfigurable))
|
||||||
|
{
|
||||||
|
Debug.LogWarning("FusionCustomPropertiesBridge: Room is not a ReconfigurableDevice. Cannot map custom properties.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var roomConfig = reconfigurable.Config;
|
||||||
|
|
||||||
|
var updateConfig = false;
|
||||||
|
|
||||||
|
// Set the room name
|
||||||
|
if (!string.IsNullOrEmpty(roomInfo.Name) && useFusionRoomName)
|
||||||
|
{
|
||||||
|
Debug.LogDebug("Current Room Name: {currentName}. New Room Name: {fusionName}", roomConfig.Name, roomInfo.Name);
|
||||||
|
// Set the name in config
|
||||||
|
roomConfig.Name = roomInfo.Name;
|
||||||
|
updateConfig = true;
|
||||||
|
|
||||||
|
Debug.LogDebug("Room Name Successfully Changed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the help message
|
||||||
|
var helpMessage = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomHelpMessage"));
|
||||||
|
if (helpMessage != null)
|
||||||
|
{
|
||||||
|
roomConfig.Properties["helpMessage"] = helpMessage.CustomFieldValue;
|
||||||
|
updateConfig = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateConfig)
|
||||||
|
{
|
||||||
|
reconfigurable.SetConfig(roomConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "FusionCustomPropetiesBridge: Error mapping properties: {0}", e);
|
Debug.LogError("FusionCustomPropetiesBridge: Exception mapping properties for {roomKey}: {message}", room.Key, e.Message);
|
||||||
|
Debug.LogDebug(e, "Stack Trace: ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
using Crestron.SimplSharp;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Timers;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using Crestron.SimplSharp.CrestronXml;
|
using Crestron.SimplSharp.CrestronXml;
|
||||||
using Crestron.SimplSharp.CrestronXml.Serialization;
|
using Crestron.SimplSharp.CrestronXml.Serialization;
|
||||||
@@ -10,17 +15,13 @@ using PepperDash.Core.Logging;
|
|||||||
using PepperDash.Essentials.Core.Config;
|
using PepperDash.Essentials.Core.Config;
|
||||||
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Fusion
|
namespace PepperDash.Essentials.Core.Fusion
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a EssentialsHuddleSpaceFusionSystemControllerBase
|
/// Represents a EssentialsHuddleSpaceFusionSystemControllerBase
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IEssentialsRoomFusionController : EssentialsDevice, IOccupancyStatusProvider, IFusionHelpRequest
|
public class IEssentialsRoomFusionController : EssentialsDevice, IOccupancyStatusProvider, IFusionHelpRequest, IHasFeedback
|
||||||
{
|
{
|
||||||
private IEssentialsRoomFusionControllerPropertiesConfig _config;
|
private IEssentialsRoomFusionControllerPropertiesConfig _config;
|
||||||
|
|
||||||
@@ -87,15 +88,17 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public StringFeedback HelpRequestStatusFeedback { get; private set; }
|
public StringFeedback HelpRequestStatusFeedback { get; private set; }
|
||||||
|
|
||||||
|
private Timer _helpRequestTimeoutTimer;
|
||||||
|
|
||||||
#region System Info Sigs
|
/// <summary>
|
||||||
|
/// Gets the DefaultHelpRequestTimeoutMs
|
||||||
|
/// </summary>
|
||||||
|
public int HelpRequestTimeoutMs => _config.HelpRequestTimeoutMs;
|
||||||
|
|
||||||
//StringSigData SystemName;
|
/// <summary>
|
||||||
//StringSigData Model;
|
/// Gets whether to use a timer for help requests
|
||||||
//StringSigData SerialNumber;
|
/// </summary>
|
||||||
//StringSigData Uptime;
|
public bool UseHelpRequestTimer => _config.UseTimeoutForHelpRequests;
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Processor Info Sigs
|
#region Processor Info Sigs
|
||||||
|
|
||||||
@@ -240,6 +243,19 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
|
|
||||||
this.LogDebug("Occupancy setup complete");
|
this.LogDebug("Occupancy setup complete");
|
||||||
|
|
||||||
|
HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.OutputSig.StringValue);
|
||||||
|
|
||||||
|
HelpRequestSentFeedback = new BoolFeedback("HelpRequestSent", () => _helpRequestSent);
|
||||||
|
HelpRequestStatusFeedback = new StringFeedback("HelpRequestStatus", () => _helpRequestStatus.ToString());
|
||||||
|
|
||||||
|
Feedbacks.Add(HelpRequestResponseFeedback);
|
||||||
|
Feedbacks.Add(HelpRequestSentFeedback);
|
||||||
|
Feedbacks.Add(HelpRequestStatusFeedback);
|
||||||
|
if (RoomOccupancyRemoteStringFeedback != null)
|
||||||
|
Feedbacks.Add(RoomOccupancyRemoteStringFeedback);
|
||||||
|
if (RoomIsOccupiedFeedback != null)
|
||||||
|
Feedbacks.Add(RoomIsOccupiedFeedback);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -303,10 +319,6 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
|
|
||||||
FusionRVI.GenerateFileForAllFusionDevices();
|
FusionRVI.GenerateFileForAllFusionDevices();
|
||||||
|
|
||||||
HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.OutputSig.StringValue);
|
|
||||||
|
|
||||||
HelpRequestSentFeedback = new BoolFeedback("HelpRequestSent", () => _helpRequestSent);
|
|
||||||
HelpRequestStatusFeedback = new StringFeedback("HelpRequestStatus", () => _helpRequestStatus.ToString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -339,6 +351,11 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public FeedbackCollection<Feedback> Feedbacks { get; private set; } = new FeedbackCollection<Feedback>();
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ScheduleChange event
|
/// ScheduleChange event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1074,7 +1091,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
}
|
}
|
||||||
RoomInfoChange?.Invoke(this, new EventArgs());
|
RoomInfoChange?.Invoke(this, new EventArgs());
|
||||||
|
|
||||||
CustomPropertiesBridge.EvaluateRoomInfo(Room.Key, roomInformation);
|
CustomPropertiesBridge.EvaluateRoomInfo(Room, roomInformation, _config.UseFusionRoomName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -1772,7 +1789,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
{
|
{
|
||||||
if (args.EventId == FusionEventIds.HelpMessageReceivedEventId)
|
if (args.EventId == FusionEventIds.HelpMessageReceivedEventId)
|
||||||
{
|
{
|
||||||
this.LogInformation( "Help message received from Fusion for room '{0}'",
|
this.LogInformation("Help message received from Fusion for room '{0}'",
|
||||||
Room.Name);
|
Room.Name);
|
||||||
|
|
||||||
this.LogDebug("Help message content: {0}", FusionRoom.Help.OutputSig.StringValue);
|
this.LogDebug("Help message content: {0}", FusionRoom.Help.OutputSig.StringValue);
|
||||||
@@ -1791,7 +1808,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
break;
|
break;
|
||||||
case "Please call the helpdesk.":
|
case "Please call the helpdesk.":
|
||||||
// this.LogInformation("Please call the helpdesk.");
|
// this.LogInformation("Please call the helpdesk.");
|
||||||
// _helpRequestStatus = eFusionHelpResponse.CallHelpDesk;
|
_helpRequestStatus = eFusionHelpResponse.CallHelpDesk;
|
||||||
break;
|
break;
|
||||||
case "Please wait, I will reschedule your meeting to a different room.":
|
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.",
|
// this.LogInformation("Please wait, I will reschedule your meeting to a different room.",
|
||||||
@@ -1818,13 +1835,21 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
_helpRequestStatus = eFusionHelpResponse.None;
|
_helpRequestStatus = eFusionHelpResponse.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_helpRequestStatus == eFusionHelpResponse.None)
|
if (_helpRequestStatus == eFusionHelpResponse.None)
|
||||||
{
|
{
|
||||||
_helpRequestSent = false;
|
_helpRequestSent = false;
|
||||||
HelpRequestSentFeedback.FireUpdate();
|
HelpRequestSentFeedback.FireUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
HelpRequestStatusFeedback.FireUpdate();
|
HelpRequestStatusFeedback.FireUpdate();
|
||||||
|
|
||||||
|
if (_helpRequestTimeoutTimer != null)
|
||||||
|
{
|
||||||
|
_helpRequestTimeoutTimer.Stop();
|
||||||
|
_helpRequestTimeoutTimer.Elapsed -= OnTimedEvent;
|
||||||
|
_helpRequestTimeoutTimer.Dispose();
|
||||||
|
_helpRequestTimeoutTimer = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1895,10 +1920,34 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
_helpRequestSent = true;
|
_helpRequestSent = true;
|
||||||
HelpRequestSentFeedback.FireUpdate();
|
HelpRequestSentFeedback.FireUpdate();
|
||||||
|
|
||||||
|
if (UseHelpRequestTimer)
|
||||||
|
{
|
||||||
|
if (_helpRequestTimeoutTimer == null)
|
||||||
|
{
|
||||||
|
_helpRequestTimeoutTimer = new Timer(HelpRequestTimeoutMs);
|
||||||
|
_helpRequestTimeoutTimer.AutoReset = false;
|
||||||
|
_helpRequestTimeoutTimer.Enabled = true;
|
||||||
|
|
||||||
|
_helpRequestTimeoutTimer.Elapsed += OnTimedEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
_helpRequestTimeoutTimer.Interval = HelpRequestTimeoutMs;
|
||||||
|
_helpRequestTimeoutTimer.Start();
|
||||||
|
|
||||||
|
this.LogDebug("Help request timeout timer started for room '{0}' with timeout of {1} ms.",
|
||||||
|
Room.Name, HelpRequestTimeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
_helpRequestStatus = eFusionHelpResponse.HelpRequested;
|
_helpRequestStatus = eFusionHelpResponse.HelpRequested;
|
||||||
HelpRequestStatusFeedback.FireUpdate();
|
HelpRequestStatusFeedback.FireUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnTimedEvent(object source, ElapsedEventArgs e)
|
||||||
|
{
|
||||||
|
this.LogInformation("Help request timeout reached for room '{0}'. Cancelling help request.", Room.Name);
|
||||||
|
CancelHelpRequest();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void CancelHelpRequest()
|
public void CancelHelpRequest()
|
||||||
{
|
{
|
||||||
@@ -1909,7 +1958,16 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
HelpRequestSentFeedback.FireUpdate();
|
HelpRequestSentFeedback.FireUpdate();
|
||||||
_helpRequestStatus = eFusionHelpResponse.None;
|
_helpRequestStatus = eFusionHelpResponse.None;
|
||||||
HelpRequestStatusFeedback.FireUpdate();
|
HelpRequestStatusFeedback.FireUpdate();
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Help request cancelled in Fusion for room '{0}'", Room.Name);
|
Debug.LogMessage(LogEventLevel.Information, this, "Help request cancelled for room '{0}'", Room.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_helpRequestTimeoutTimer != null)
|
||||||
|
{
|
||||||
|
_helpRequestTimeoutTimer.Stop();
|
||||||
|
_helpRequestTimeoutTimer.Elapsed -= OnTimedEvent;
|
||||||
|
_helpRequestTimeoutTimer.Dispose();
|
||||||
|
_helpRequestTimeoutTimer = null;
|
||||||
|
this.LogDebug("Help request timeout timer stopped for room '{0}'.", Room.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class IEssentialsRoomFusionControllerPropertiesConfig
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogWarning( "Failed to parse IpId '{0}' as UInt16", IpId);
|
Debug.LogWarning("Failed to parse IpId '{0}' as UInt16", IpId);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,6 +45,13 @@ public class IEssentialsRoomFusionControllerPropertiesConfig
|
|||||||
[JsonProperty("roomKey")]
|
[JsonProperty("roomKey")]
|
||||||
public string RoomKey { get; set; }
|
public string RoomKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether to use the Fusion room name for this room
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Defaults to true to preserve current behavior. Set to false to skip updating the room name from Fusion</remarks>
|
||||||
|
[JsonProperty("useFusionRoomName")]
|
||||||
|
public bool UseFusionRoomName { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether to use HTML format for help requests
|
/// Gets or sets whether to use HTML format for help requests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -56,4 +63,16 @@ public class IEssentialsRoomFusionControllerPropertiesConfig
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("use24HourTimeFormat")]
|
[JsonProperty("use24HourTimeFormat")]
|
||||||
public bool Use24HourTimeFormat { get; set; } = false;
|
public bool Use24HourTimeFormat { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether to use a timeout for help requests
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("useTimeoutForHelpRequests")]
|
||||||
|
public bool UseTimeoutForHelpRequests { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the timeout duration for help requests in milliseconds
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("helpRequestTimeoutMs")]
|
||||||
|
public int HelpRequestTimeoutMs { get; set; } = 30000;
|
||||||
}
|
}
|
||||||
@@ -105,12 +105,21 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class EssentialsRoomPropertiesConfig
|
public class EssentialsRoomPropertiesConfig
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Addresses
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("addresses")]
|
[JsonProperty("addresses")]
|
||||||
public EssentialsRoomAddressPropertiesConfig Addresses { get; set; }
|
public EssentialsRoomAddressPropertiesConfig Addresses { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Description
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("description")]
|
[JsonProperty("description")]
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Emergency
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("emergency")]
|
[JsonProperty("emergency")]
|
||||||
public EssentialsRoomEmergencyConfig Emergency { get; set; }
|
public EssentialsRoomEmergencyConfig Emergency { get; set; }
|
||||||
|
|
||||||
@@ -226,11 +235,11 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
/// Indicates if this room represents a combination of other rooms
|
/// Indicates if this room represents a combination of other rooms
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("isRoomCombinationScenario")]
|
[JsonProperty("isRoomCombinationScenario")]
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the IsRoomCombinationScenario
|
|
||||||
/// </summary>
|
|
||||||
public bool IsRoomCombinationScenario { get; set; }
|
public bool IsRoomCombinationScenario { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
public EssentialsRoomPropertiesConfig()
|
public EssentialsRoomPropertiesConfig()
|
||||||
{
|
{
|
||||||
LogoLight = new EssentialsLogoPropertiesConfig();
|
LogoLight = new EssentialsLogoPropertiesConfig();
|
||||||
@@ -243,10 +252,10 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class EssentialsRoomUiBehaviorConfig
|
public class EssentialsRoomUiBehaviorConfig
|
||||||
{
|
{
|
||||||
[JsonProperty("disableActivityButtonsWhileWarmingCooling")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the DisableActivityButtonsWhileWarmingCooling
|
/// Gets or sets the DisableActivityButtonsWhileWarmingCooling
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("disableActivityButtonsWhileWarmingCooling")]
|
||||||
public bool DisableActivityButtonsWhileWarmingCooling { get; set; }
|
public bool DisableActivityButtonsWhileWarmingCooling { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,74 +264,86 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class EssentialsAvRoomPropertiesConfig : EssentialsRoomPropertiesConfig
|
public class EssentialsAvRoomPropertiesConfig : EssentialsRoomPropertiesConfig
|
||||||
{
|
{
|
||||||
[JsonProperty("defaultAudioKey")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the DefaultAudioKey
|
/// Gets or sets the DefaultAudioKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("defaultAudioKey")]
|
||||||
public string DefaultAudioKey { get; set; }
|
public string DefaultAudioKey { get; set; }
|
||||||
[JsonProperty("sourceListKey")]
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the DefaultOnDspPresetKey
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("defaultOnDspPresetKey")]
|
||||||
|
public string DefaultOnDspPresetKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the DefaultOffDspPresetKey
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("defaultOffDspPresetKey")]
|
||||||
|
public string DefaultOffDspPresetKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the SourceListKey
|
/// Gets or sets the SourceListKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("sourceListKey")]
|
||||||
public string SourceListKey { get; set; }
|
public string SourceListKey { get; set; }
|
||||||
[JsonProperty("destinationListKey")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the DestinationListKey
|
/// Gets or sets the DestinationListKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("destinationListKey")]
|
||||||
public string DestinationListKey { get; set; }
|
public string DestinationListKey { get; set; }
|
||||||
[JsonProperty("audioControlPointListKey")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the AudioControlPointListKey
|
/// Gets or sets the AudioControlPointListKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("audioControlPointListKey")]
|
||||||
public string AudioControlPointListKey { get; set; }
|
public string AudioControlPointListKey { get; set; }
|
||||||
[JsonProperty("cameraListKey")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the CameraListKey
|
/// Gets or sets the CameraListKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("cameraListKey")]
|
||||||
public string CameraListKey { get; set; }
|
public string CameraListKey { get; set; }
|
||||||
|
|
||||||
|
|
||||||
[JsonProperty("defaultSourceItem")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the DefaultSourceItem
|
/// Gets or sets the DefaultSourceItem
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("defaultSourceItem")]
|
||||||
public string DefaultSourceItem { get; set; }
|
public string DefaultSourceItem { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if the room supports advanced sharing
|
/// Indicates if the room supports advanced sharing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("supportsAdvancedSharing")]
|
[JsonProperty("supportsAdvancedSharing")]
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the SupportsAdvancedSharing
|
|
||||||
/// </summary>
|
|
||||||
public bool SupportsAdvancedSharing { get; set; }
|
public bool SupportsAdvancedSharing { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if non-tech users can change the share mode
|
/// Indicates if non-tech users can change the share mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("userCanChangeShareMode")]
|
[JsonProperty("userCanChangeShareMode")]
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the UserCanChangeShareMode
|
|
||||||
/// </summary>
|
|
||||||
public bool UserCanChangeShareMode { get; set; }
|
public bool UserCanChangeShareMode { get; set; }
|
||||||
|
|
||||||
|
|
||||||
[JsonProperty("matrixRoutingKey", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MatrixRoutingKey
|
/// Gets or sets the MatrixRoutingKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("matrixRoutingKey", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string MatrixRoutingKey { get; set; }
|
public string MatrixRoutingKey { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a EssentialsConferenceRoomPropertiesConfig
|
||||||
|
/// </summary>
|
||||||
public class EssentialsConferenceRoomPropertiesConfig : EssentialsAvRoomPropertiesConfig
|
public class EssentialsConferenceRoomPropertiesConfig : EssentialsAvRoomPropertiesConfig
|
||||||
{
|
{
|
||||||
[JsonProperty("videoCodecKey")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the VideoCodecKey
|
/// Gets or sets the VideoCodecKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("videoCodecKey")]
|
||||||
public string VideoCodecKey { get; set; }
|
public string VideoCodecKey { get; set; }
|
||||||
[JsonProperty("audioCodecKey")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the AudioCodecKey
|
/// Gets or sets the AudioCodecKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("audioCodecKey")]
|
||||||
public string AudioCodecKey { get; set; }
|
public string AudioCodecKey { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -337,12 +358,15 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
|
|
||||||
[JsonProperty("deviceKeys")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the DeviceKeys
|
/// Gets or sets the DeviceKeys
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("deviceKeys")]
|
||||||
public List<string> DeviceKeys { get; set; }
|
public List<string> DeviceKeys { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
public EssentialsEnvironmentPropertiesConfig()
|
public EssentialsEnvironmentPropertiesConfig()
|
||||||
{
|
{
|
||||||
DeviceKeys = new List<string>();
|
DeviceKeys = new List<string>();
|
||||||
@@ -355,6 +379,9 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class EssentialsRoomFusionConfig
|
public class EssentialsRoomFusionConfig
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the the IpId as a UInt16
|
||||||
|
/// </summary>
|
||||||
public uint IpIdInt
|
public uint IpIdInt
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -371,16 +398,16 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("ipId")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the IpId
|
/// Gets or sets the IpId
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("ipId")]
|
||||||
public string IpId { get; set; }
|
public string IpId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("joinMapKey")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the JoinMapKey
|
/// Gets or sets the JoinMapKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("joinMapKey")]
|
||||||
public string JoinMapKey { get; set; }
|
public string JoinMapKey { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -390,16 +417,16 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class EssentialsRoomMicrophonePrivacyConfig
|
public class EssentialsRoomMicrophonePrivacyConfig
|
||||||
{
|
{
|
||||||
[JsonProperty("deviceKey")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the DeviceKey
|
/// Gets or sets the DeviceKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("deviceKey")]
|
||||||
public string DeviceKey { get; set; }
|
public string DeviceKey { get; set; }
|
||||||
|
|
||||||
[JsonProperty("behaviour")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Behaviour
|
/// Gets or sets the Behaviour
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("behaviour")]
|
||||||
public string Behaviour { get; set; }
|
public string Behaviour { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,12 +435,15 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class EssentialsHelpPropertiesConfig
|
public class EssentialsHelpPropertiesConfig
|
||||||
{
|
{
|
||||||
[JsonProperty("message")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Message
|
/// Gets or sets the Message
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("message")]
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ShowCallButton
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("showCallButton")]
|
[JsonProperty("showCallButton")]
|
||||||
public bool ShowCallButton { get; set; }
|
public bool ShowCallButton { get; set; }
|
||||||
|
|
||||||
@@ -421,11 +451,11 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
/// Defaults to "Call Help Desk"
|
/// Defaults to "Call Help Desk"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("callButtonText")]
|
[JsonProperty("callButtonText")]
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the CallButtonText
|
|
||||||
/// </summary>
|
|
||||||
public string CallButtonText { get; set; }
|
public string CallButtonText { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
public EssentialsHelpPropertiesConfig()
|
public EssentialsHelpPropertiesConfig()
|
||||||
{
|
{
|
||||||
CallButtonText = "Call Help Desk";
|
CallButtonText = "Call Help Desk";
|
||||||
@@ -437,22 +467,28 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class EssentialsOneButtonMeetingPropertiesConfig
|
public class EssentialsOneButtonMeetingPropertiesConfig
|
||||||
{
|
{
|
||||||
[JsonProperty("enable")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Enable
|
/// Gets or sets the Enable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("enable")]
|
||||||
public bool Enable { get; set; }
|
public bool Enable { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a EssentialsRoomAddressPropertiesConfig
|
||||||
|
/// </summary>
|
||||||
public class EssentialsRoomAddressPropertiesConfig
|
public class EssentialsRoomAddressPropertiesConfig
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the PhoneNumber
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("phoneNumber")]
|
[JsonProperty("phoneNumber")]
|
||||||
public string PhoneNumber { get; set; }
|
public string PhoneNumber { get; set; }
|
||||||
|
|
||||||
[JsonProperty("sipAddress")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the SipAddress
|
/// Gets or sets the SipAddress
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("sipAddress")]
|
||||||
public string SipAddress { get; set; }
|
public string SipAddress { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,14 +498,18 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class EssentialsLogoPropertiesConfig
|
public class EssentialsLogoPropertiesConfig
|
||||||
{
|
{
|
||||||
[JsonProperty("type")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Type
|
/// Gets or sets the Type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("type")]
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Url
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("url")]
|
[JsonProperty("url")]
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GetLogoUrlLight method
|
/// GetLogoUrlLight method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -502,22 +542,28 @@ namespace PepperDash.Essentials.Room.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class EssentialsRoomOccSensorConfig
|
public class EssentialsRoomOccSensorConfig
|
||||||
{
|
{
|
||||||
[JsonProperty("deviceKey")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the DeviceKey
|
/// Gets or sets the DeviceKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("deviceKey")]
|
||||||
public string DeviceKey { get; set; }
|
public string DeviceKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the TimeoutMinutes
|
||||||
|
/// </summary>
|
||||||
[JsonProperty("timeoutMinutes")]
|
[JsonProperty("timeoutMinutes")]
|
||||||
public int TimeoutMinutes { get; set; }
|
public int TimeoutMinutes { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a EssentialsRoomTechConfig
|
||||||
|
/// </summary>
|
||||||
public class EssentialsRoomTechConfig
|
public class EssentialsRoomTechConfig
|
||||||
{
|
{
|
||||||
[JsonProperty("password")]
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Password
|
/// Gets or sets the Password
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonProperty("password")]
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using Crestron.SimplSharpPro.Keypads;
|
using System;
|
||||||
using PepperDash.Essentials.Core.Queues;
|
|
||||||
using PepperDash.Essentials.Core.Routing;
|
|
||||||
using Serilog.Events;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Crestron.SimplSharpPro.Keypads;
|
||||||
|
using PepperDash.Essentials.Core.Queues;
|
||||||
|
using PepperDash.Essentials.Core.Routing;
|
||||||
|
using Serilog.Events;
|
||||||
using Debug = PepperDash.Core.Debug;
|
using Debug = PepperDash.Core.Debug;
|
||||||
|
|
||||||
|
|
||||||
@@ -18,6 +18,20 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A collection of RouteDescriptors for each signal type.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Dictionary<eRoutingSignalType, RouteDescriptorCollection> RouteDescriptors = new Dictionary<eRoutingSignalType, RouteDescriptorCollection>()
|
||||||
|
{
|
||||||
|
{ eRoutingSignalType.Audio, new RouteDescriptorCollection() },
|
||||||
|
{ eRoutingSignalType.Video, new RouteDescriptorCollection() },
|
||||||
|
{ eRoutingSignalType.SecondaryAudio, new RouteDescriptorCollection() },
|
||||||
|
{ eRoutingSignalType.AudioVideo, new RouteDescriptorCollection() },
|
||||||
|
{ eRoutingSignalType.UsbInput, new RouteDescriptorCollection() },
|
||||||
|
{ eRoutingSignalType.UsbOutput, new RouteDescriptorCollection() }
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stores pending route requests, keyed by the destination device key.
|
/// Stores pending route requests, keyed by the destination device key.
|
||||||
/// Used primarily to handle routing requests while a device is cooling down.
|
/// Used primarily to handle routing requests while a device is cooling down.
|
||||||
@@ -29,6 +43,105 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly GenericQueue routeRequestQueue = new GenericQueue("routingQueue");
|
private static readonly GenericQueue routeRequestQueue = new GenericQueue("routingQueue");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indexed lookup of TieLines by destination device key for faster queries.
|
||||||
|
/// </summary>
|
||||||
|
private static Dictionary<string, List<TieLine>> _tieLinesByDestination;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indexed lookup of TieLines by source device key for faster queries.
|
||||||
|
/// </summary>
|
||||||
|
private static Dictionary<string, List<TieLine>> _tieLinesBySource;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cache of failed route attempts to avoid re-checking impossible paths.
|
||||||
|
/// Format: "sourceKey|destKey|signalType"
|
||||||
|
/// </summary>
|
||||||
|
private static readonly HashSet<string> _impossibleRoutes = new HashSet<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indexes all TieLines by source and destination device keys for faster lookups.
|
||||||
|
/// Should be called once at system startup after all TieLines are created.
|
||||||
|
/// </summary>
|
||||||
|
public static void IndexTieLines()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Debug.LogMessage(LogEventLevel.Information, "Indexing TieLines for faster route discovery");
|
||||||
|
|
||||||
|
_tieLinesByDestination = TieLineCollection.Default
|
||||||
|
.GroupBy(t => t.DestinationPort.ParentDevice.Key)
|
||||||
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
|
|
||||||
|
_tieLinesBySource = TieLineCollection.Default
|
||||||
|
.GroupBy(t => t.SourcePort.ParentDevice.Key)
|
||||||
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
|
|
||||||
|
Debug.LogMessage(LogEventLevel.Information, "TieLine indexing complete. {0} destination keys, {1} source keys",
|
||||||
|
null, _tieLinesByDestination.Count, _tieLinesBySource.Count);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError("Exception indexing TieLines: {exception}", ex.Message);
|
||||||
|
Debug.LogDebug(ex, "Stack Trace: ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets TieLines connected to a destination device.
|
||||||
|
/// Uses indexed lookup if available, otherwise falls back to LINQ query.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destinationKey">The destination device key</param>
|
||||||
|
/// <returns>List of TieLines connected to the destination</returns>
|
||||||
|
private static IEnumerable<TieLine> GetTieLinesForDestination(string destinationKey)
|
||||||
|
{
|
||||||
|
if (_tieLinesByDestination != null && _tieLinesByDestination.TryGetValue(destinationKey, out List<TieLine> tieLines))
|
||||||
|
{
|
||||||
|
return tieLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to LINQ if index not available
|
||||||
|
return TieLineCollection.Default.Where(t => t.DestinationPort.ParentDevice.Key == destinationKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets TieLines connected to a source device.
|
||||||
|
/// Uses indexed lookup if available, otherwise falls back to LINQ query.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceKey">The source device key</param>
|
||||||
|
/// <returns>List of TieLines connected to the source</returns>
|
||||||
|
private static IEnumerable<TieLine> GetTieLinesForSource(string sourceKey)
|
||||||
|
{
|
||||||
|
if (_tieLinesBySource != null && _tieLinesBySource.TryGetValue(sourceKey, out List<TieLine> tieLines))
|
||||||
|
{
|
||||||
|
return tieLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to LINQ if index not available
|
||||||
|
return TieLineCollection.Default.Where(t => t.SourcePort.ParentDevice.Key == sourceKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a cache key for route impossibility tracking.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceKey">Source device key</param>
|
||||||
|
/// <param name="destKey">Destination device key</param>
|
||||||
|
/// <param name="type">Signal type</param>
|
||||||
|
/// <returns>Cache key string</returns>
|
||||||
|
private static string GetRouteKey(string sourceKey, string destKey, eRoutingSignalType type)
|
||||||
|
{
|
||||||
|
return string.Format("{0}|{1}|{2}", sourceKey, destKey, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the impossible routes cache. Should be called if TieLines are added/removed at runtime.
|
||||||
|
/// </summary>
|
||||||
|
public static void ClearImpossibleRoutesCache()
|
||||||
|
{
|
||||||
|
_impossibleRoutes.Clear();
|
||||||
|
Debug.LogMessage(LogEventLevel.Information, "Impossible routes cache cleared");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
|
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
|
||||||
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor
|
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor
|
||||||
@@ -115,7 +228,7 @@ namespace PepperDash.Essentials.Core
|
|||||||
public static (RouteDescriptor, RouteDescriptor) GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort, RoutingOutputPort sourcePort)
|
public static (RouteDescriptor, RouteDescriptor) GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort, RoutingOutputPort sourcePort)
|
||||||
{
|
{
|
||||||
// if it's a single signal type, find the route
|
// if it's a single signal type, find the route
|
||||||
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo) &&
|
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo) &&
|
||||||
!(signalType.HasFlag(eRoutingSignalType.Video) && signalType.HasFlag(eRoutingSignalType.SecondaryAudio)))
|
!(signalType.HasFlag(eRoutingSignalType.Video) && signalType.HasFlag(eRoutingSignalType.SecondaryAudio)))
|
||||||
{
|
{
|
||||||
var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, signalType);
|
var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, signalType);
|
||||||
@@ -134,14 +247,15 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
// otherwise, audioVideo needs to be handled as two steps.
|
// otherwise, audioVideo needs to be handled as two steps.
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key);
|
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {destinationKey} to {sourceKey} of type {type}", destination, source.Key, signalType);
|
||||||
|
|
||||||
RouteDescriptor audioRouteDescriptor;
|
RouteDescriptor audioRouteDescriptor;
|
||||||
|
|
||||||
if (signalType.HasFlag(eRoutingSignalType.SecondaryAudio))
|
if (signalType.HasFlag(eRoutingSignalType.SecondaryAudio))
|
||||||
{
|
{
|
||||||
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.SecondaryAudio);
|
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.SecondaryAudio);
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Audio);
|
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Audio);
|
||||||
}
|
}
|
||||||
@@ -172,8 +286,9 @@ namespace PepperDash.Essentials.Core
|
|||||||
if (!audioSuccess && !videoSuccess)
|
if (!audioSuccess && !videoSuccess)
|
||||||
return (null, null);
|
return (null, null);
|
||||||
|
|
||||||
|
// Return null for descriptors that have no routes
|
||||||
return (audioRouteDescriptor, videoRouteDescriptor);
|
return (audioSuccess && audioRouteDescriptor.Routes.Count > 0 ? audioRouteDescriptor : null,
|
||||||
|
videoSuccess && videoRouteDescriptor.Routes.Count > 0 ? videoRouteDescriptor : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -199,13 +314,13 @@ namespace PepperDash.Essentials.Core
|
|||||||
Source = source,
|
Source = source,
|
||||||
SourcePort = sourcePort,
|
SourcePort = sourcePort,
|
||||||
SignalType = signalType
|
SignalType = signalType
|
||||||
};
|
};
|
||||||
|
|
||||||
var coolingDevice = destination as IWarmingCooling;
|
var coolingDevice = destination as IWarmingCooling;
|
||||||
|
|
||||||
//We already have a route request for this device, and it's a cooling device and is cooling
|
//We already have a route request for this device, and it's a cooling device and is cooling
|
||||||
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
|
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
|
||||||
{
|
{
|
||||||
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
|
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
|
||||||
|
|
||||||
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
|
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
|
||||||
@@ -219,7 +334,7 @@ namespace PepperDash.Essentials.Core
|
|||||||
|
|
||||||
//New Request
|
//New Request
|
||||||
if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
|
if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
|
||||||
{
|
{
|
||||||
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
|
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
|
||||||
|
|
||||||
RouteRequests.Add(destination.Key, routeRequest);
|
RouteRequests.Add(destination.Key, routeRequest);
|
||||||
@@ -239,9 +354,93 @@ namespace PepperDash.Essentials.Core
|
|||||||
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
|
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty, false));
|
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, destinationPort?.Key ?? string.Empty, false));
|
||||||
|
|
||||||
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
|
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps destination input ports to source output ports for all routing devices.
|
||||||
|
/// </summary>
|
||||||
|
public static void MapDestinationsToSources()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Index TieLines before mapping if not already done
|
||||||
|
if (_tieLinesByDestination == null || _tieLinesBySource == null)
|
||||||
|
{
|
||||||
|
IndexTieLines();
|
||||||
|
}
|
||||||
|
|
||||||
|
var sinks = DeviceManager.AllDevices.OfType<IRoutingInputs>().Where(d => !(d is IRoutingInputsOutputs));
|
||||||
|
var sources = DeviceManager.AllDevices.OfType<IRoutingOutputs>().Where(d => !(d is IRoutingInputsOutputs));
|
||||||
|
|
||||||
|
foreach (var sink in sinks)
|
||||||
|
{
|
||||||
|
foreach (var source in sources)
|
||||||
|
{
|
||||||
|
foreach (var inputPort in sink.InputPorts)
|
||||||
|
{
|
||||||
|
foreach (var outputPort in source.OutputPorts)
|
||||||
|
{
|
||||||
|
var (audioOrSingleRoute, videoRoute) = sink.GetRouteToSource(source, inputPort.Type, inputPort, outputPort);
|
||||||
|
|
||||||
|
if (audioOrSingleRoute == null && videoRoute == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioOrSingleRoute != null)
|
||||||
|
{
|
||||||
|
// Only add routes that have actual switching steps
|
||||||
|
if (audioOrSingleRoute.Routes == null || audioOrSingleRoute.Routes.Count == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to the appropriate collection(s) based on signal type
|
||||||
|
// Note: A single route descriptor with combined flags (e.g., AudioVideo) will be added once per matching signal type
|
||||||
|
if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.Audio))
|
||||||
|
{
|
||||||
|
RouteDescriptors[eRoutingSignalType.Audio].AddRouteDescriptor(audioOrSingleRoute);
|
||||||
|
}
|
||||||
|
if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.Video))
|
||||||
|
{
|
||||||
|
RouteDescriptors[eRoutingSignalType.Video].AddRouteDescriptor(audioOrSingleRoute);
|
||||||
|
}
|
||||||
|
if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.SecondaryAudio))
|
||||||
|
{
|
||||||
|
RouteDescriptors[eRoutingSignalType.SecondaryAudio].AddRouteDescriptor(audioOrSingleRoute);
|
||||||
|
}
|
||||||
|
if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.UsbInput))
|
||||||
|
{
|
||||||
|
RouteDescriptors[eRoutingSignalType.UsbInput].AddRouteDescriptor(audioOrSingleRoute);
|
||||||
|
}
|
||||||
|
if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.UsbOutput))
|
||||||
|
{
|
||||||
|
RouteDescriptors[eRoutingSignalType.UsbOutput].AddRouteDescriptor(audioOrSingleRoute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (videoRoute != null)
|
||||||
|
{
|
||||||
|
// Only add routes that have actual switching steps
|
||||||
|
if (videoRoute.Routes == null || videoRoute.Routes.Count == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RouteDescriptors[eRoutingSignalType.Video].AddRouteDescriptor(videoRoute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError("Exception mapping routes: {exception}", ex.Message);
|
||||||
|
Debug.LogDebug(ex, "Stack Trace: ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -256,7 +455,51 @@ namespace PepperDash.Essentials.Core
|
|||||||
if (request.Source == null)
|
if (request.Source == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var (audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort);
|
RouteDescriptor audioOrSingleRoute = null;
|
||||||
|
RouteDescriptor videoRoute = null;
|
||||||
|
|
||||||
|
// Try to use pre-loaded route descriptors first
|
||||||
|
if (request.SignalType.HasFlag(eRoutingSignalType.AudioVideo))
|
||||||
|
{
|
||||||
|
// For AudioVideo routes, check both Audio and Video collections
|
||||||
|
if (RouteDescriptors.TryGetValue(eRoutingSignalType.Audio, out RouteDescriptorCollection audioCollection))
|
||||||
|
{
|
||||||
|
audioOrSingleRoute = audioCollection.Descriptors.FirstOrDefault(d =>
|
||||||
|
d.Source.Key == request.Source.Key &&
|
||||||
|
d.Destination.Key == request.Destination.Key &&
|
||||||
|
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RouteDescriptors.TryGetValue(eRoutingSignalType.Video, out RouteDescriptorCollection videoCollection))
|
||||||
|
{
|
||||||
|
videoRoute = videoCollection.Descriptors.FirstOrDefault(d =>
|
||||||
|
d.Source.Key == request.Source.Key &&
|
||||||
|
d.Destination.Key == request.Destination.Key &&
|
||||||
|
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For single signal type routes
|
||||||
|
var signalTypeToCheck = request.SignalType.HasFlag(eRoutingSignalType.SecondaryAudio)
|
||||||
|
? eRoutingSignalType.SecondaryAudio
|
||||||
|
: request.SignalType;
|
||||||
|
|
||||||
|
if (RouteDescriptors.TryGetValue(signalTypeToCheck, out RouteDescriptorCollection collection))
|
||||||
|
{
|
||||||
|
audioOrSingleRoute = collection.Descriptors.FirstOrDefault(d =>
|
||||||
|
d.Source.Key == request.Source.Key &&
|
||||||
|
d.Destination.Key == request.Destination.Key &&
|
||||||
|
(request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-loaded route found, build it dynamically
|
||||||
|
if (audioOrSingleRoute == null && videoRoute == null)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(LogEventLevel.Debug, "No pre-loaded route found, building dynamically", request.Destination);
|
||||||
|
(audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort);
|
||||||
|
}
|
||||||
|
|
||||||
if (audioOrSingleRoute == null && videoRoute == null)
|
if (audioOrSingleRoute == null && videoRoute == null)
|
||||||
return;
|
return;
|
||||||
@@ -272,7 +515,8 @@ namespace PepperDash.Essentials.Core
|
|||||||
|
|
||||||
audioOrSingleRoute.ExecuteRoutes();
|
audioOrSingleRoute.ExecuteRoutes();
|
||||||
videoRoute?.ExecuteRoutes();
|
videoRoute?.ExecuteRoutes();
|
||||||
} catch(Exception ex)
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request);
|
Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request);
|
||||||
}
|
}
|
||||||
@@ -305,9 +549,10 @@ namespace PepperDash.Essentials.Core
|
|||||||
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
|
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
|
||||||
current.ReleaseRoutes(clearRoute);
|
current.ReleaseRoutes(clearRoute);
|
||||||
}
|
}
|
||||||
} catch (Exception ex)
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'",null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'", null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,11 +563,13 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="destination"></param>
|
/// <param name="destination"></param>
|
||||||
/// <param name="source"></param>
|
/// <param name="source"></param>
|
||||||
/// <param name="destinationPort">The RoutingOutputPort whose link is being checked for a route</param>
|
/// <param name="outputPortToUse">The RoutingOutputPort whose link is being checked for a route</param>
|
||||||
/// <param name="alreadyCheckedDevices">Prevents Devices from being twice-checked</param>
|
/// <param name="alreadyCheckedDevices">Prevents Devices from being twice-checked</param>
|
||||||
/// <param name="signalType">This recursive function should not be called with AudioVideo</param>
|
/// <param name="signalType">This recursive function should not be called with AudioVideo</param>
|
||||||
/// <param name="cycle">Just an informational counter</param>
|
/// <param name="cycle">Just an informational counter</param>
|
||||||
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
|
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
|
||||||
|
/// <param name="destinationPort">The RoutingOutputPort whose link is being checked for a route</param>
|
||||||
|
/// <param name="sourcePort">The source output port (optional)</param>
|
||||||
/// <returns>true if source is hit</returns>
|
/// <returns>true if source is hit</returns>
|
||||||
private static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
|
private static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
|
||||||
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices,
|
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices,
|
||||||
@@ -330,42 +577,54 @@ namespace PepperDash.Essentials.Core
|
|||||||
{
|
{
|
||||||
cycle++;
|
cycle++;
|
||||||
|
|
||||||
|
// Check if this route has already been determined to be impossible
|
||||||
|
var routeKey = GetRouteKey(source.Key, destination.Key, signalType);
|
||||||
|
if (_impossibleRoutes.Contains(routeKey))
|
||||||
|
{
|
||||||
|
Debug.LogMessage(LogEventLevel.Verbose, "Route {0} is cached as impossible, skipping", null, routeKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {cycle} {sourceKey}:{sourcePortKey}--> {destinationKey}:{destinationPortKey} {type}", null, cycle, source.Key, sourcePort?.Key ?? "auto", destination.Key, destinationPort?.Key ?? "auto", signalType.ToString());
|
Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {cycle} {sourceKey}:{sourcePortKey}--> {destinationKey}:{destinationPortKey} {type}", null, cycle, source.Key, sourcePort?.Key ?? "auto", destination.Key, destinationPort?.Key ?? "auto", signalType.ToString());
|
||||||
|
|
||||||
RoutingInputPort goodInputPort = null;
|
RoutingInputPort goodInputPort = null;
|
||||||
|
|
||||||
|
// Use indexed lookup instead of LINQ query
|
||||||
|
var allDestinationTieLines = GetTieLinesForDestination(destination.Key);
|
||||||
|
|
||||||
IEnumerable<TieLine> destinationTieLines;
|
IEnumerable<TieLine> destinationTieLines;
|
||||||
TieLine directTie = null;
|
TieLine directTie = null;
|
||||||
|
|
||||||
if (destinationPort == null)
|
if (destinationPort == null)
|
||||||
{
|
{
|
||||||
destinationTieLines = TieLineCollection.Default.Where(t =>
|
destinationTieLines = allDestinationTieLines.Where(t =>
|
||||||
t.DestinationPort.ParentDevice.Key == destination.Key && (t.Type.HasFlag(signalType) || signalType == eRoutingSignalType.AudioVideo));
|
t.Type.HasFlag(signalType) || signalType == eRoutingSignalType.AudioVideo);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
destinationTieLines = TieLineCollection.Default.Where(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.DestinationPort.Key == destinationPort.Key && (t.Type.HasFlag(signalType)));
|
destinationTieLines = allDestinationTieLines.Where(t =>
|
||||||
|
t.DestinationPort.Key == destinationPort.Key && t.Type.HasFlag(signalType));
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the TieLine without a port
|
// find the TieLine without a port
|
||||||
if (destinationPort == null && sourcePort == null)
|
if (destinationPort == null && sourcePort == null)
|
||||||
{
|
{
|
||||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.SourcePort.ParentDevice.Key == source.Key);
|
directTie = destinationTieLines.FirstOrDefault(t => t.SourcePort.ParentDevice.Key == source.Key);
|
||||||
}
|
}
|
||||||
// find a tieLine to a specific destination port without a specific source port
|
// find a tieLine to a specific destination port without a specific source port
|
||||||
else if (destinationPort != null && sourcePort == null)
|
else if (destinationPort != null && sourcePort == null)
|
||||||
{
|
{
|
||||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key);
|
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key);
|
||||||
}
|
}
|
||||||
// find a tieline to a specific source port without a specific destination port
|
// find a tieline to a specific source port without a specific destination port
|
||||||
else if (destinationPort == null & sourcePort != null)
|
else if (destinationPort == null & sourcePort != null)
|
||||||
{
|
{
|
||||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key);
|
directTie = destinationTieLines.FirstOrDefault(t => t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key);
|
||||||
}
|
}
|
||||||
// find a tieline to a specific source port and destination port
|
// find a tieline to a specific source port and destination port
|
||||||
else if (destinationPort != null && sourcePort != null)
|
else if (destinationPort != null && sourcePort != null)
|
||||||
{
|
{
|
||||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key);
|
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (directTie != null) // Found a tie directly to the source
|
if (directTie != null) // Found a tie directly to the source
|
||||||
@@ -420,6 +679,10 @@ namespace PepperDash.Essentials.Core
|
|||||||
if (goodInputPort == null)
|
if (goodInputPort == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key);
|
Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key);
|
||||||
|
|
||||||
|
// Cache this as an impossible route
|
||||||
|
_impossibleRoutes.Add(routeKey);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,15 +95,15 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// Releases the usage tracking for the route and optionally clears the route on the switching devices.
|
/// Releases the usage tracking for the route and optionally clears the route on the switching devices.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clearRoute">If true, attempts to clear the route on the switching devices (e.g., set input to null/0).</param>
|
/// <param name="clearRoute">If true, attempts to clear the route on the switching devices (e.g., set input to null/0).</param>
|
||||||
|
|
||||||
|
|
||||||
public void ReleaseRoutes(bool clearRoute = false)
|
public void ReleaseRoutes(bool clearRoute = false)
|
||||||
{
|
{
|
||||||
foreach (var route in Routes.Where(r => r.SwitchingDevice is IRouting))
|
foreach (var route in Routes.Where(r => r.SwitchingDevice is IRouting))
|
||||||
{
|
{
|
||||||
if (route.SwitchingDevice is IRouting switchingDevice)
|
if (route.SwitchingDevice is IRouting switchingDevice)
|
||||||
{
|
{
|
||||||
if(clearRoute)
|
if (clearRoute)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -137,98 +137,11 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// Returns a string representation of the route descriptor, including source, destination, and individual route steps.
|
/// Returns a string representation of the route descriptor, including source, destination, and individual route steps.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A string describing the route.</returns>
|
/// <returns>A string describing the route.</returns>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var routesText = Routes.Select(r => r.ToString()).ToArray();
|
var routesText = Routes.Select(r => r.ToString()).ToArray();
|
||||||
return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText));
|
return $"Route table from {Source.Key} to {Destination.Key} for {SignalType}:\r\n {string.Join("\r\n ", routesText)}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*/// <summary>
|
|
||||||
/// Represents an collection of individual route steps between Source and Destination
|
|
||||||
/// </summary>
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a RouteDescriptor
|
|
||||||
/// </summary>
|
|
||||||
public class RouteDescriptor<TInputSelector, TOutputSelector>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Destination
|
|
||||||
/// </summary>
|
|
||||||
public IRoutingInputs<TInputSelector> Destination { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Source
|
|
||||||
/// </summary>
|
|
||||||
public IRoutingOutputs<TOutputSelector> Source { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the SignalType
|
|
||||||
/// </summary>
|
|
||||||
public eRoutingSignalType SignalType { get; private set; }
|
|
||||||
public List<RouteSwitchDescriptor<TInputSelector, TOutputSelector>> Routes { get; private set; }
|
|
||||||
|
|
||||||
|
|
||||||
public RouteDescriptor(IRoutingOutputs<TOutputSelector> source, IRoutingInputs<TInputSelector> destination, eRoutingSignalType signalType)
|
|
||||||
{
|
|
||||||
Destination = destination;
|
|
||||||
Source = source;
|
|
||||||
SignalType = signalType;
|
|
||||||
Routes = new List<RouteSwitchDescriptor<TInputSelector, TOutputSelector>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ExecuteRoutes method
|
|
||||||
/// </summary>
|
|
||||||
public void ExecuteRoutes()
|
|
||||||
{
|
|
||||||
foreach (var route in Routes)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", null, route.ToString());
|
|
||||||
|
|
||||||
if (route.SwitchingDevice is IRoutingSinkWithSwitching<TInputSelector> sink)
|
|
||||||
{
|
|
||||||
sink.ExecuteSwitch(route.InputPort.Selector);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.SwitchingDevice is IRouting switchingDevice)
|
|
||||||
{
|
|
||||||
switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType);
|
|
||||||
|
|
||||||
route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType);
|
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ReleaseRoutes method
|
|
||||||
/// </summary>
|
|
||||||
public void ReleaseRoutes()
|
|
||||||
{
|
|
||||||
foreach (var route in Routes)
|
|
||||||
{
|
|
||||||
if (route.SwitchingDevice is IRouting<TInputSelector, TOutputSelector>)
|
|
||||||
{
|
|
||||||
// Pull the route from the port. Whatever is watching the output's in use tracker is
|
|
||||||
// responsible for responding appropriately.
|
|
||||||
route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType);
|
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ToString method
|
|
||||||
/// </summary>
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
var routesText = Routes.Select(r => r.ToString()).ToArray();
|
|
||||||
return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText));
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using PepperDash.Core;
|
using System.Collections.Generic;
|
||||||
using Serilog.Events;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using PepperDash.Core;
|
||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
@@ -11,6 +11,9 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class RouteDescriptorCollection
|
public class RouteDescriptorCollection
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the default collection of RouteDescriptors.
|
||||||
|
/// </summary>
|
||||||
public static RouteDescriptorCollection DefaultCollection
|
public static RouteDescriptorCollection DefaultCollection
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -24,6 +27,11 @@ namespace PepperDash.Essentials.Core
|
|||||||
|
|
||||||
private readonly List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();
|
private readonly List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an enumerable collection of all RouteDescriptors in this collection.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<RouteDescriptor> Descriptors => RouteDescriptors.AsReadOnly();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the
|
/// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the
|
||||||
/// destination exists already, it will not be added - in order to preserve
|
/// destination exists already, it will not be added - in order to preserve
|
||||||
@@ -37,13 +45,29 @@ namespace PepperDash.Essentials.Core
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)
|
// Check if a route already exists with the same source, destination, input port, AND signal type
|
||||||
&& RouteDescriptors.Any(t => t.Destination == descriptor.Destination && t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key))
|
var existingRoute = RouteDescriptors.FirstOrDefault(t =>
|
||||||
|
t.Source == descriptor.Source &&
|
||||||
|
t.Destination == descriptor.Destination &&
|
||||||
|
t.SignalType == descriptor.SignalType &&
|
||||||
|
((t.InputPort == null && descriptor.InputPort == null) ||
|
||||||
|
(t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key)));
|
||||||
|
|
||||||
|
if (existingRoute != null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination,
|
Debug.LogMessage(LogEventLevel.Information, descriptor.Destination,
|
||||||
"Route to [{0}] already exists in global routes table", descriptor?.Source?.Key);
|
"Route from {0} to {1}:{2} ({3}) already exists in this collection",
|
||||||
|
descriptor?.Source?.Key,
|
||||||
|
descriptor?.Destination?.Key,
|
||||||
|
descriptor?.InputPort?.Key ?? "auto",
|
||||||
|
descriptor?.SignalType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Debug.LogMessage(LogEventLevel.Verbose, "Adding route descriptor: {0} -> {1}:{2} ({3})",
|
||||||
|
descriptor?.Source?.Key,
|
||||||
|
descriptor?.Destination?.Key,
|
||||||
|
descriptor?.InputPort?.Key ?? "auto",
|
||||||
|
descriptor?.SignalType);
|
||||||
RouteDescriptors.Add(descriptor);
|
RouteDescriptors.Add(descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +81,12 @@ namespace PepperDash.Essentials.Core
|
|||||||
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
|
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the route descriptor for a specific destination and input port
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destination">The destination device</param>
|
||||||
|
/// <param name="inputPortKey">The input port key</param>
|
||||||
|
/// <returns>The matching RouteDescriptor or null if not found</returns>
|
||||||
public RouteDescriptor GetRouteDescriptorForDestinationAndInputPort(IRoutingInputs destination, string inputPortKey)
|
public RouteDescriptor GetRouteDescriptorForDestinationAndInputPort(IRoutingInputs destination, string inputPortKey)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Getting route descriptor for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
Debug.LogMessage(LogEventLevel.Information, "Getting route descriptor for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||||
@@ -73,7 +103,7 @@ namespace PepperDash.Essentials.Core
|
|||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Removing route descriptor for '{destination}':'{inputPortKey}'", destination.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
Debug.LogMessage(LogEventLevel.Information, "Removing route descriptor for '{destination}':'{inputPortKey}'", destination.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||||
|
|
||||||
var descr = string.IsNullOrEmpty(inputPortKey)
|
var descr = string.IsNullOrEmpty(inputPortKey)
|
||||||
? GetRouteDescriptorForDestination(destination)
|
? GetRouteDescriptorForDestination(destination)
|
||||||
: GetRouteDescriptorForDestinationAndInputPort(destination, inputPortKey);
|
: GetRouteDescriptorForDestinationAndInputPort(destination, inputPortKey);
|
||||||
if (descr != null)
|
if (descr != null)
|
||||||
@@ -84,70 +114,4 @@ namespace PepperDash.Essentials.Core
|
|||||||
return descr;
|
return descr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*/// <summary>
|
|
||||||
/// A collection of RouteDescriptors - typically the static DefaultCollection is used
|
|
||||||
/// </summary>
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a RouteDescriptorCollection
|
|
||||||
/// </summary>
|
|
||||||
public class RouteDescriptorCollection<TInputSelector, TOutputSelector>
|
|
||||||
{
|
|
||||||
public static RouteDescriptorCollection<TInputSelector, TOutputSelector> DefaultCollection
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_DefaultCollection == null)
|
|
||||||
_DefaultCollection = new RouteDescriptorCollection<TInputSelector, TOutputSelector>();
|
|
||||||
return _DefaultCollection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private static RouteDescriptorCollection<TInputSelector, TOutputSelector> _DefaultCollection;
|
|
||||||
|
|
||||||
private readonly List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the
|
|
||||||
/// destination exists already, it will not be added - in order to preserve
|
|
||||||
/// proper route releasing.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="descriptor"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// AddRouteDescriptor method
|
|
||||||
/// </summary>
|
|
||||||
public void AddRouteDescriptor(RouteDescriptor descriptor)
|
|
||||||
{
|
|
||||||
if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination))
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination,
|
|
||||||
"Route to [{0}] already exists in global routes table", descriptor.Source.Key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RouteDescriptors.Add(descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the RouteDescriptor for a destination
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>null if no RouteDescriptor for a destination exists</returns>
|
|
||||||
/// <summary>
|
|
||||||
/// GetRouteDescriptorForDestination method
|
|
||||||
/// </summary>
|
|
||||||
public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs<TInputSelector> destination)
|
|
||||||
{
|
|
||||||
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the RouteDescriptor for a given destination AND removes it from collection.
|
|
||||||
/// Returns null if no route with the provided destination exists.
|
|
||||||
/// </summary>
|
|
||||||
public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs<TInputSelector> destination)
|
|
||||||
{
|
|
||||||
var descr = GetRouteDescriptorForDestination(destination);
|
|
||||||
if (descr != null)
|
|
||||||
RouteDescriptors.Remove(descr);
|
|
||||||
return descr;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
@@ -4,96 +4,51 @@
|
|||||||
/// Represents a RouteSwitchDescriptor
|
/// Represents a RouteSwitchDescriptor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RouteSwitchDescriptor
|
public class RouteSwitchDescriptor
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the SwitchingDevice
|
|
||||||
/// </summary>
|
|
||||||
public IRoutingInputs SwitchingDevice { get { return InputPort?.ParentDevice; } }
|
|
||||||
/// <summary>
|
|
||||||
/// The output port being switched from (relevant for matrix switchers). Null for sink devices.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingOutputPort OutputPort { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// The input port being switched to.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingInputPort InputPort { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RouteSwitchDescriptor"/> class for sink devices (no output port).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="inputPort">The input port being switched to.</param>
|
|
||||||
public RouteSwitchDescriptor(RoutingInputPort inputPort)
|
|
||||||
{
|
|
||||||
InputPort = inputPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RouteSwitchDescriptor"/> class for matrix switchers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outputPort">The output port being switched from.</param>
|
|
||||||
/// <param name="inputPort">The input port being switched to.</param>
|
|
||||||
public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort)
|
|
||||||
{
|
|
||||||
InputPort = inputPort;
|
|
||||||
OutputPort = outputPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a string representation of the route switch descriptor.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string describing the switch operation.</returns>
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
if (SwitchingDevice is IRouting)
|
|
||||||
return $"{(SwitchingDevice != null ? SwitchingDevice.Key : "No Device")} switches output {(OutputPort != null ? OutputPort.Key : "No output port")} to input {(InputPort != null ? InputPort.Key : "No input port")}";
|
|
||||||
else
|
|
||||||
return $"{(SwitchingDevice != null ? SwitchingDevice.Key : "No Device")} switches to input {(InputPort != null ? InputPort.Key : "No input port")}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*/// <summary>
|
|
||||||
/// Represents an individual link for a route
|
|
||||||
/// </summary>
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a RouteSwitchDescriptor
|
|
||||||
/// </summary>
|
|
||||||
public class RouteSwitchDescriptor<TInputSelector, TOutputSelector>
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the SwitchingDevice
|
/// Gets or sets the SwitchingDevice
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IRoutingInputs<TInputSelector> SwitchingDevice { get { return InputPort.ParentDevice; } }
|
public IRoutingInputs SwitchingDevice { get { return InputPort?.ParentDevice; } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the OutputPort
|
/// The output port being switched from (relevant for matrix switchers). Null for sink devices.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RoutingOutputPort<TOutputSelector> OutputPort { get; set; }
|
public RoutingOutputPort OutputPort { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the InputPort
|
/// The input port being switched to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RoutingInputPort<TInputSelector> InputPort { get; set; }
|
public RoutingInputPort InputPort { get; set; }
|
||||||
|
|
||||||
public RouteSwitchDescriptor(RoutingInputPort<TInputSelector> inputPort)
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RouteSwitchDescriptor"/> class for sink devices (no output port).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputPort">The input port being switched to.</param>
|
||||||
|
public RouteSwitchDescriptor(RoutingInputPort inputPort)
|
||||||
{
|
{
|
||||||
InputPort = inputPort;
|
InputPort = inputPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RouteSwitchDescriptor(RoutingOutputPort<TOutputSelector> outputPort, RoutingInputPort<TInputSelector> inputPort)
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RouteSwitchDescriptor"/> class for matrix switchers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputPort">The output port being switched from.</param>
|
||||||
|
/// <param name="inputPort">The input port being switched to.</param>
|
||||||
|
public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort)
|
||||||
{
|
{
|
||||||
InputPort = inputPort;
|
InputPort = inputPort;
|
||||||
OutputPort = outputPort;
|
OutputPort = outputPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ToString method
|
/// Returns a string representation of the route switch descriptor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <returns>A string describing the switch operation.</returns>
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (SwitchingDevice is IRouting)
|
if (SwitchingDevice is IRouting)
|
||||||
return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector);
|
return $"{(SwitchingDevice != null ? SwitchingDevice.Key : "No Device")} switches output {(OutputPort != null ? OutputPort.Key : "No output port")} to input {(InputPort != null ? InputPort.Key : "No input port")}";
|
||||||
else
|
else
|
||||||
return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector);
|
return $"{(SwitchingDevice != null ? SwitchingDevice.Key : "No Device")} switches to input {(InputPort != null ? InputPort.Key : "No input port")}";
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
using PepperDash.Core;
|
using System;
|
||||||
using PepperDash.Essentials.Core.Config;
|
using System.Collections.Generic;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using PepperDash.Core;
|
||||||
|
using PepperDash.Essentials.Core.Config;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Routing
|
namespace PepperDash.Essentials.Core.Routing
|
||||||
{
|
{
|
||||||
@@ -9,19 +11,124 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
/// Manages routing feedback by subscribing to route changes on midpoint and sink devices,
|
/// Manages routing feedback by subscribing to route changes on midpoint and sink devices,
|
||||||
/// tracing the route back to the original source, and updating the CurrentSourceInfo on sink devices.
|
/// tracing the route back to the original source, and updating the CurrentSourceInfo on sink devices.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RoutingFeedbackManager:EssentialsDevice
|
public class RoutingFeedbackManager : EssentialsDevice
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Maps midpoint device keys to the set of sink device keys that are downstream
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<string, HashSet<string>> midpointToSinksMap;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Debounce timers for each sink device to prevent rapid successive updates
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<string, CTimer> updateTimers = new Dictionary<string, CTimer>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Debounce delay in milliseconds
|
||||||
|
/// </summary>
|
||||||
|
private const long DEBOUNCE_MS = 500;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RoutingFeedbackManager"/> class.
|
/// Initializes a new instance of the <see cref="RoutingFeedbackManager"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">The unique key for this manager device.</param>
|
/// <param name="key">The unique key for this manager device.</param>
|
||||||
/// <param name="name">The name of this manager device.</param>
|
/// <param name="name">The name of this manager device.</param>
|
||||||
public RoutingFeedbackManager(string key, string name): base(key, name)
|
public RoutingFeedbackManager(string key, string name)
|
||||||
{
|
: base(key, name)
|
||||||
|
{
|
||||||
|
AddPreActivationAction(BuildMidpointSinkMap);
|
||||||
AddPreActivationAction(SubscribeForMidpointFeedback);
|
AddPreActivationAction(SubscribeForMidpointFeedback);
|
||||||
AddPreActivationAction(SubscribeForSinkFeedback);
|
AddPreActivationAction(SubscribeForSinkFeedback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a map of which sink devices are downstream of each midpoint device
|
||||||
|
/// for performance optimization in HandleMidpointUpdate
|
||||||
|
/// </summary>
|
||||||
|
private void BuildMidpointSinkMap()
|
||||||
|
{
|
||||||
|
midpointToSinksMap = new Dictionary<string, HashSet<string>>();
|
||||||
|
|
||||||
|
var sinks = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
||||||
|
var midpoints = DeviceManager.AllDevices.OfType<IRoutingWithFeedback>();
|
||||||
|
|
||||||
|
foreach (var sink in sinks)
|
||||||
|
{
|
||||||
|
if (sink.CurrentInputPort == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Find all upstream midpoints for this sink
|
||||||
|
var upstreamMidpoints = GetUpstreamMidpoints(sink);
|
||||||
|
|
||||||
|
foreach (var midpointKey in upstreamMidpoints)
|
||||||
|
{
|
||||||
|
if (!midpointToSinksMap.ContainsKey(midpointKey))
|
||||||
|
midpointToSinksMap[midpointKey] = new HashSet<string>();
|
||||||
|
|
||||||
|
midpointToSinksMap[midpointKey].Add(sink.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Information,
|
||||||
|
"Built midpoint-to-sink map with {count} midpoints",
|
||||||
|
this,
|
||||||
|
midpointToSinksMap.Count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all upstream midpoint device keys for a given sink
|
||||||
|
/// </summary>
|
||||||
|
private HashSet<string> GetUpstreamMidpoints(IRoutingSinkWithSwitchingWithInputPort sink)
|
||||||
|
{
|
||||||
|
var result = new HashSet<string>();
|
||||||
|
var visited = new HashSet<string>();
|
||||||
|
|
||||||
|
if (sink.CurrentInputPort == null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var tieLine = TieLineCollection.Default.FirstOrDefault(tl =>
|
||||||
|
tl.DestinationPort.Key == sink.CurrentInputPort.Key &&
|
||||||
|
tl.DestinationPort.ParentDevice.Key == sink.CurrentInputPort.ParentDevice.Key);
|
||||||
|
|
||||||
|
if (tieLine == null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
TraceUpstreamMidpoints(tieLine, result, visited);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recursively traces upstream to find all midpoint devices
|
||||||
|
/// </summary>
|
||||||
|
private void TraceUpstreamMidpoints(TieLine tieLine, HashSet<string> midpoints, HashSet<string> visited)
|
||||||
|
{
|
||||||
|
if (tieLine == null || visited.Contains(tieLine.SourcePort.ParentDevice.Key))
|
||||||
|
return;
|
||||||
|
|
||||||
|
visited.Add(tieLine.SourcePort.ParentDevice.Key);
|
||||||
|
|
||||||
|
if (tieLine.SourcePort.ParentDevice is IRoutingWithFeedback midpoint)
|
||||||
|
{
|
||||||
|
midpoints.Add(midpoint.Key);
|
||||||
|
|
||||||
|
// Find upstream TieLines connected to this midpoint's inputs
|
||||||
|
var midpointInputs = (midpoint as IRoutingInputs)?.InputPorts;
|
||||||
|
if (midpointInputs != null)
|
||||||
|
{
|
||||||
|
foreach (var inputPort in midpointInputs)
|
||||||
|
{
|
||||||
|
var upstreamTieLine = TieLineCollection.Default.FirstOrDefault(tl =>
|
||||||
|
tl.DestinationPort.Key == inputPort.Key &&
|
||||||
|
tl.DestinationPort.ParentDevice.Key == inputPort.ParentDevice.Key);
|
||||||
|
|
||||||
|
if (upstreamTieLine != null)
|
||||||
|
TraceUpstreamMidpoints(upstreamTieLine, midpoints, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Subscribes to the RouteChanged event on all devices implementing <see cref="IRoutingWithFeedback"/>.
|
/// Subscribes to the RouteChanged event on all devices implementing <see cref="IRoutingWithFeedback"/>.
|
||||||
@@ -41,34 +148,66 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void SubscribeForSinkFeedback()
|
private void SubscribeForSinkFeedback()
|
||||||
{
|
{
|
||||||
var sinkDevices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
var sinkDevices =
|
||||||
|
DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
||||||
|
|
||||||
foreach (var device in sinkDevices)
|
foreach (var device in sinkDevices)
|
||||||
{
|
{
|
||||||
device.InputChanged += HandleSinkUpdate;
|
device.InputChanged += HandleSinkUpdate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles the RouteChanged event from a midpoint device.
|
/// Handles the RouteChanged event from a midpoint device.
|
||||||
/// Triggers an update for all sink devices.
|
/// Only triggers updates for sink devices that are downstream of this midpoint.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="midpoint">The midpoint device that reported a route change.</param>
|
/// <param name="midpoint">The midpoint device that reported a route change.</param>
|
||||||
/// <param name="newRoute">The descriptor of the new route.</param>
|
/// <param name="newRoute">The descriptor of the new route.</param>
|
||||||
private void HandleMidpointUpdate(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute)
|
private void HandleMidpointUpdate(
|
||||||
|
IRoutingWithFeedback midpoint,
|
||||||
|
RouteSwitchDescriptor newRoute
|
||||||
|
)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var devices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
// Only update affected sinks (performance optimization)
|
||||||
|
if (midpointToSinksMap != null && midpointToSinksMap.TryGetValue(midpoint.Key, out var affectedSinkKeys))
|
||||||
foreach (var device in devices)
|
|
||||||
{
|
{
|
||||||
UpdateDestination(device, device.CurrentInputPort);
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Debug,
|
||||||
|
"Midpoint {midpoint} changed, updating {count} downstream sinks",
|
||||||
|
this,
|
||||||
|
midpoint.Key,
|
||||||
|
affectedSinkKeys.Count
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var sinkKey in affectedSinkKeys)
|
||||||
|
{
|
||||||
|
if (DeviceManager.GetDeviceForKey(sinkKey) is IRoutingSinkWithSwitchingWithInputPort sink)
|
||||||
|
{
|
||||||
|
UpdateDestination(sink, sink.CurrentInputPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Debug,
|
||||||
|
"Midpoint {midpoint} changed but has no downstream sinks in map",
|
||||||
|
this,
|
||||||
|
midpoint.Key
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(ex, "Error handling midpoint update from {midpointKey}:{Exception}", this, midpoint.Key, ex);
|
Debug.LogMessage(
|
||||||
|
ex,
|
||||||
|
"Error handling midpoint update from {midpointKey}:{Exception}",
|
||||||
|
this,
|
||||||
|
midpoint.Key,
|
||||||
|
ex
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +217,10 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The sink device that reported an input change.</param>
|
/// <param name="sender">The sink device that reported an input change.</param>
|
||||||
/// <param name="currentInputPort">The new input port selected on the sink device.</param>
|
/// <param name="currentInputPort">The new input port selected on the sink device.</param>
|
||||||
private void HandleSinkUpdate(IRoutingSinkWithSwitching sender, RoutingInputPort currentInputPort)
|
private void HandleSinkUpdate(
|
||||||
|
IRoutingSinkWithSwitching sender,
|
||||||
|
RoutingInputPort currentInputPort
|
||||||
|
)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -86,23 +228,93 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(ex, "Error handling Sink update from {senderKey}:{Exception}", this, sender.Key, ex);
|
Debug.LogMessage(
|
||||||
|
ex,
|
||||||
|
"Error handling Sink update from {senderKey}:{Exception}",
|
||||||
|
this,
|
||||||
|
sender.Key,
|
||||||
|
ex
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the CurrentSourceInfo and CurrentSourceInfoKey properties on a destination (sink) device
|
/// Updates the CurrentSourceInfo and CurrentSourceInfoKey properties on a destination (sink) device
|
||||||
/// based on its currently selected input port by tracing the route back through tie lines.
|
/// based on its currently selected input port by tracing the route back through tie lines.
|
||||||
|
/// Uses debouncing to prevent rapid successive updates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="destination">The destination sink device to update.</param>
|
/// <param name="destination">The destination sink device to update.</param>
|
||||||
/// <param name="inputPort">The currently selected input port on the destination device.</param>
|
/// <param name="inputPort">The currently selected input port on the destination device.</param>
|
||||||
private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort)
|
private void UpdateDestination(
|
||||||
{
|
IRoutingSinkWithSwitching destination,
|
||||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this,destination?.Key, inputPort?.Key);
|
RoutingInputPort inputPort
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (destination == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if(inputPort == null)
|
var key = destination.Key;
|
||||||
|
|
||||||
|
// Cancel existing timer for this sink
|
||||||
|
if (updateTimers.TryGetValue(key, out var existingTimer))
|
||||||
{
|
{
|
||||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "Destination {destination} has not reported an input port yet", this,destination.Key);
|
existingTimer.Stop();
|
||||||
|
existingTimer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start new debounced timer
|
||||||
|
updateTimers[key] = new CTimer(_ =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
UpdateDestinationImmediate(destination, inputPort);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(
|
||||||
|
ex,
|
||||||
|
"Error in debounced update for destination {destinationKey}: {message}",
|
||||||
|
this,
|
||||||
|
destination.Key,
|
||||||
|
ex.Message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (updateTimers.ContainsKey(key))
|
||||||
|
{
|
||||||
|
updateTimers[key]?.Dispose();
|
||||||
|
updateTimers.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null, DEBOUNCE_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Immediately updates the CurrentSourceInfo for a destination device.
|
||||||
|
/// Called after debounce delay.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateDestinationImmediate(
|
||||||
|
IRoutingSinkWithSwitching destination,
|
||||||
|
RoutingInputPort inputPort
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Debug,
|
||||||
|
"Updating destination {destination} with inputPort {inputPort}",
|
||||||
|
this,
|
||||||
|
destination?.Key,
|
||||||
|
inputPort?.Key
|
||||||
|
);
|
||||||
|
|
||||||
|
if (inputPort == null)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Debug,
|
||||||
|
"Destination {destination} has not reported an input port yet",
|
||||||
|
this,
|
||||||
|
destination.Key
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,11 +323,19 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
{
|
{
|
||||||
var tieLines = TieLineCollection.Default;
|
var tieLines = TieLineCollection.Default;
|
||||||
|
|
||||||
firstTieLine = tieLines.FirstOrDefault(tl => tl.DestinationPort.Key == inputPort.Key && tl.DestinationPort.ParentDevice.Key == inputPort.ParentDevice.Key);
|
firstTieLine = tieLines.FirstOrDefault(tl =>
|
||||||
|
tl.DestinationPort.Key == inputPort.Key
|
||||||
|
&& tl.DestinationPort.ParentDevice.Key == inputPort.ParentDevice.Key
|
||||||
|
);
|
||||||
|
|
||||||
if (firstTieLine == null)
|
if (firstTieLine == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No tieline found for inputPort {inputPort}. Clearing current source", this, inputPort);
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Debug,
|
||||||
|
"No tieline found for inputPort {inputPort}. Clearing current source",
|
||||||
|
this,
|
||||||
|
inputPort
|
||||||
|
);
|
||||||
|
|
||||||
var tempSourceListItem = new SourceListItem
|
var tempSourceListItem = new SourceListItem
|
||||||
{
|
{
|
||||||
@@ -123,12 +343,13 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
Name = inputPort.Key,
|
Name = inputPort.Key,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
destination.CurrentSourceInfo = tempSourceListItem;
|
||||||
destination.CurrentSourceInfo = tempSourceListItem; ;
|
;
|
||||||
destination.CurrentSourceInfoKey = "$transient";
|
destination.CurrentSourceInfoKey = "$transient";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (Exception ex)
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(ex, "Error getting first tieline: {Exception}", this, ex);
|
Debug.LogMessage(ex, "Error getting first tieline: {Exception}", this, ex);
|
||||||
return;
|
return;
|
||||||
@@ -143,7 +364,12 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
|
|
||||||
if (sourceTieLine == null)
|
if (sourceTieLine == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route found to source for inputPort {inputPort}. Clearing current source", this, inputPort);
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Debug,
|
||||||
|
"No route found to source for inputPort {inputPort}. Clearing current source",
|
||||||
|
this,
|
||||||
|
inputPort
|
||||||
|
);
|
||||||
|
|
||||||
var tempSourceListItem = new SourceListItem
|
var tempSourceListItem = new SourceListItem
|
||||||
{
|
{
|
||||||
@@ -155,32 +381,45 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
destination.CurrentSourceInfoKey = string.Empty;
|
destination.CurrentSourceInfoKey = string.Empty;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch(Exception ex)
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(ex, "Error getting sourceTieLine: {Exception}", this, ex);
|
Debug.LogMessage(ex, "Error getting sourceTieLine: {Exception}", this, ex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root TieLine {tieLine}", this, sourceTieLine);
|
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root TieLine {tieLine}", this, sourceTieLine);
|
||||||
|
|
||||||
// Does not handle combinable scenarios or other scenarios where a display might be part of multiple rooms yet.
|
// Does not handle combinable scenarios or other scenarios where a display might be part of multiple rooms yet.
|
||||||
var room = DeviceManager.AllDevices.OfType<IEssentialsRoom>().FirstOrDefault((r) => {
|
var room = DeviceManager
|
||||||
if(r is IHasMultipleDisplays roomMultipleDisplays)
|
.AllDevices.OfType<IEssentialsRoom>()
|
||||||
{
|
.FirstOrDefault(
|
||||||
return roomMultipleDisplays.Displays.Any(d => d.Value.Key == destination.Key);
|
(r) =>
|
||||||
}
|
{
|
||||||
|
if (r is IHasMultipleDisplays roomMultipleDisplays)
|
||||||
|
{
|
||||||
|
return roomMultipleDisplays.Displays.Any(d =>
|
||||||
|
d.Value.Key == destination.Key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if(r is IHasDefaultDisplay roomDefaultDisplay)
|
if (r is IHasDefaultDisplay roomDefaultDisplay)
|
||||||
{
|
{
|
||||||
return roomDefaultDisplay.DefaultDisplay.Key == destination.Key;
|
return roomDefaultDisplay.DefaultDisplay.Key == destination.Key;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
if(room == null)
|
|
||||||
|
if (room == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No room found for display {destination}", this, destination.Key);
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Debug,
|
||||||
|
"No room found for display {destination}",
|
||||||
|
this,
|
||||||
|
destination.Key
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,29 +429,45 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
|
|
||||||
if (sourceList == null)
|
if (sourceList == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No source list found for source list key {key}. Unable to find source for tieLine {sourceTieLine}", this, room.SourceListKey, sourceTieLine);
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Debug,
|
||||||
|
"No source list found for source list key {key}. Unable to find source for tieLine {sourceTieLine}",
|
||||||
|
this,
|
||||||
|
room.SourceListKey,
|
||||||
|
sourceTieLine
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found sourceList for room {room}", this, room.Key);
|
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found sourceList for room {room}", this, room.Key);
|
||||||
|
|
||||||
var sourceListItem = sourceList.FirstOrDefault(sli => {
|
var sourceListItem = sourceList.FirstOrDefault(sli =>
|
||||||
//// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose,
|
{
|
||||||
// "SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}",
|
//// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose,
|
||||||
// this,
|
// "SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}",
|
||||||
// sli.Key,
|
// this,
|
||||||
// sli.Value.SourceKey,
|
// sli.Key,
|
||||||
// sourceTieLine.SourcePort.ParentDevice.Key);
|
// sli.Value.SourceKey,
|
||||||
|
// sourceTieLine.SourcePort.ParentDevice.Key);
|
||||||
|
|
||||||
return sli.Value.SourceKey.Equals(sourceTieLine.SourcePort.ParentDevice.Key,StringComparison.InvariantCultureIgnoreCase);
|
return sli.Value.SourceKey.Equals(
|
||||||
});
|
sourceTieLine.SourcePort.ParentDevice.Key,
|
||||||
|
StringComparison.InvariantCultureIgnoreCase
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
var source = sourceListItem.Value;
|
var source = sourceListItem.Value;
|
||||||
var sourceKey = sourceListItem.Key;
|
var sourceKey = sourceListItem.Key;
|
||||||
|
|
||||||
if (source == null)
|
if (source == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No source found for device {key}. Creating transient source for {destination}", this, sourceTieLine.SourcePort.ParentDevice.Key, destination);
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Debug,
|
||||||
|
"No source found for device {key}. Creating transient source for {destination}",
|
||||||
|
this,
|
||||||
|
sourceTieLine.SourcePort.ParentDevice.Key,
|
||||||
|
destination
|
||||||
|
);
|
||||||
|
|
||||||
var tempSourceListItem = new SourceListItem
|
var tempSourceListItem = new SourceListItem
|
||||||
{
|
{
|
||||||
@@ -221,7 +476,7 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
};
|
};
|
||||||
|
|
||||||
destination.CurrentSourceInfoKey = "$transient";
|
destination.CurrentSourceInfoKey = "$transient";
|
||||||
destination.CurrentSourceInfo = tempSourceListItem;
|
destination.CurrentSourceInfo = tempSourceListItem;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,82 +484,101 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
|
|
||||||
destination.CurrentSourceInfoKey = sourceKey;
|
destination.CurrentSourceInfoKey = sourceKey;
|
||||||
destination.CurrentSourceInfo = source;
|
destination.CurrentSourceInfo = source;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recursively traces a route back from a given tie line to find the root source tie line.
|
/// Traces a route back from a given tie line to find the root source tie line.
|
||||||
/// It navigates through midpoint devices (<see cref="IRoutingWithFeedback"/>) by checking their current routes.
|
/// Leverages the existing Extensions.GetRouteToSource method with loop protection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tieLine">The starting tie line (typically connected to a sink or midpoint).</param>
|
/// <param name="tieLine">The starting tie line (typically connected to a sink or midpoint).</param>
|
||||||
/// <returns>The <see cref="TieLine"/> connected to the original source device, or null if the source cannot be determined.</returns>
|
/// <returns>The <see cref="TieLine"/> connected to the original source device, or null if the source cannot be determined.</returns>
|
||||||
private TieLine GetRootTieLine(TieLine tieLine)
|
private TieLine GetRootTieLine(TieLine tieLine)
|
||||||
{
|
{
|
||||||
TieLine nextTieLine = null;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "**Following tieLine {tieLine}**", this, tieLine);
|
if (!(tieLine.DestinationPort.ParentDevice is IRoutingInputs sink))
|
||||||
|
|
||||||
if (tieLine.SourcePort.ParentDevice is IRoutingWithFeedback midpoint)
|
|
||||||
{
|
{
|
||||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLine Source device {sourceDevice} is midpoint", this, midpoint);
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Debug,
|
||||||
if(midpoint.CurrentRoutes == null || midpoint.CurrentRoutes.Count == 0)
|
"TieLine destination {device} is not IRoutingInputs",
|
||||||
{
|
this,
|
||||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Midpoint {midpointKey} has no routes",this, midpoint.Key);
|
tieLine.DestinationPort.ParentDevice.Key
|
||||||
return null;
|
);
|
||||||
}
|
return null;
|
||||||
|
|
||||||
var currentRoute = midpoint.CurrentRoutes.FirstOrDefault(route => {
|
|
||||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Checking {route} against {tieLine}", this, route, tieLine);
|
|
||||||
|
|
||||||
return route.OutputPort != null && route.InputPort != null && route.OutputPort?.Key == tieLine.SourcePort.Key && route.OutputPort?.ParentDevice.Key == tieLine.SourcePort.ParentDevice.Key;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (currentRoute == null)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route through midpoint {midpoint} for outputPort {outputPort}", this, midpoint.Key, tieLine.SourcePort);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found currentRoute {currentRoute} through {midpoint}", this, currentRoute, midpoint);
|
|
||||||
|
|
||||||
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => {
|
|
||||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Checking {route} against {tieLine}", tl.DestinationPort.Key, currentRoute.InputPort.Key);
|
|
||||||
return tl.DestinationPort.Key == currentRoute.InputPort.Key && tl.DestinationPort.ParentDevice.Key == currentRoute.InputPort.ParentDevice.Key; });
|
|
||||||
|
|
||||||
if (nextTieLine != null)
|
|
||||||
{
|
|
||||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found next tieLine {tieLine}. Walking the chain", this, nextTieLine);
|
|
||||||
return GetRootTieLine(nextTieLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root tieLine {tieLine}", this,nextTieLine);
|
|
||||||
return nextTieLine;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLIne Source Device {sourceDeviceKey} is IRoutingSource: {isIRoutingSource}", this, tieLine.SourcePort.ParentDevice.Key, tieLine.SourcePort.ParentDevice is IRoutingSource);
|
// Get all potential sources (devices that only have outputs, not inputs+outputs)
|
||||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLine Source Device interfaces: {typeFullName}:{interfaces}", this, tieLine.SourcePort.ParentDevice.GetType().FullName, tieLine.SourcePort.ParentDevice.GetType().GetInterfaces().Select(i => i.Name));
|
var sources = DeviceManager.AllDevices
|
||||||
|
.OfType<IRoutingOutputs>()
|
||||||
|
.Where(s => !(s is IRoutingInputsOutputs));
|
||||||
|
|
||||||
if (tieLine.SourcePort.ParentDevice is IRoutingSource || tieLine.SourcePort.ParentDevice is IRoutingOutputs) //end of the chain
|
// Try each signal type that this TieLine supports
|
||||||
|
var signalTypes = new[]
|
||||||
{
|
{
|
||||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root: {tieLine}", this, tieLine);
|
eRoutingSignalType.Audio,
|
||||||
return tieLine;
|
eRoutingSignalType.Video,
|
||||||
|
eRoutingSignalType.AudioVideo,
|
||||||
|
eRoutingSignalType.SecondaryAudio,
|
||||||
|
eRoutingSignalType.UsbInput,
|
||||||
|
eRoutingSignalType.UsbOutput
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var signalType in signalTypes)
|
||||||
|
{
|
||||||
|
if (!tieLine.Type.HasFlag(signalType))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var source in sources)
|
||||||
|
{
|
||||||
|
// Use the optimized route discovery with loop protection
|
||||||
|
var (route, _) = sink.GetRouteToSource(
|
||||||
|
source,
|
||||||
|
signalType,
|
||||||
|
tieLine.DestinationPort,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (route != null && route.Routes != null && route.Routes.Count > 0)
|
||||||
|
{
|
||||||
|
// Found a valid route - return the source TieLine
|
||||||
|
var sourceTieLine = TieLineCollection.Default.FirstOrDefault(tl =>
|
||||||
|
tl.SourcePort.ParentDevice.Key == source.Key &&
|
||||||
|
tl.Type.HasFlag(signalType));
|
||||||
|
|
||||||
|
if (sourceTieLine != null)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Debug,
|
||||||
|
"Found route from {source} to {sink} with {count} hops",
|
||||||
|
this,
|
||||||
|
source.Key,
|
||||||
|
sink.Key,
|
||||||
|
route.Routes.Count
|
||||||
|
);
|
||||||
|
return sourceTieLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => tl.DestinationPort.Key == tieLine.SourcePort.Key && tl.DestinationPort.ParentDevice.Key == tieLine.SourcePort.ParentDevice.Key );
|
Debug.LogMessage(
|
||||||
|
Serilog.Events.LogEventLevel.Debug,
|
||||||
if (nextTieLine != null)
|
"No route found to any source from {sink}",
|
||||||
{
|
this,
|
||||||
return GetRootTieLine(nextTieLine);
|
sink.Key
|
||||||
}
|
);
|
||||||
} catch (Exception ex)
|
return null;
|
||||||
{
|
}
|
||||||
Debug.LogMessage(ex, "Error walking tieLines: {Exception}", this, ex);
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(
|
||||||
|
ex,
|
||||||
|
"Error getting root tieLine: {Exception}",
|
||||||
|
this,
|
||||||
|
ex
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,71 +10,71 @@ using Serilog.Events;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Web
|
namespace PepperDash.Essentials.Core.Web
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a EssentialsWebApi
|
/// Represents a EssentialsWebApi
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EssentialsWebApi : EssentialsDevice
|
public class EssentialsWebApi : EssentialsDevice
|
||||||
{
|
{
|
||||||
private readonly WebApiServer _server;
|
private readonly WebApiServer _server;
|
||||||
|
|
||||||
///<example>
|
///<example>
|
||||||
/// http(s)://{ipaddress}/cws/{basePath}
|
/// http(s)://{ipaddress}/cws/{basePath}
|
||||||
/// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath}
|
/// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath}
|
||||||
/// </example>
|
/// </example>
|
||||||
private readonly string _defaultBasePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
|
private readonly string _defaultBasePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
|
||||||
? string.Format("/app{0:00}/api", InitialParametersClass.ApplicationNumber)
|
? string.Format("/app{0:00}/api", InitialParametersClass.ApplicationNumber)
|
||||||
: "/api";
|
: "/api";
|
||||||
|
|
||||||
private const int DebugTrace = 0;
|
private const int DebugTrace = 0;
|
||||||
private const int DebugInfo = 1;
|
private const int DebugInfo = 1;
|
||||||
private const int DebugVerbose = 2;
|
private const int DebugVerbose = 2;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the BasePath
|
/// Gets or sets the BasePath
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string BasePath { get; private set; }
|
public string BasePath { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tracks if CWS is registered
|
/// Tracks if CWS is registered
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsRegistered
|
public bool IsRegistered
|
||||||
{
|
{
|
||||||
get { return _server.IsRegistered; }
|
get { return _server.IsRegistered; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="name"></param>
|
/// <param name="name"></param>
|
||||||
public EssentialsWebApi(string key, string name)
|
public EssentialsWebApi(string key, string name)
|
||||||
: this(key, name, null)
|
: this(key, name, null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="name"></param>
|
/// <param name="name"></param>
|
||||||
/// <param name="config"></param>
|
/// <param name="config"></param>
|
||||||
public EssentialsWebApi(string key, string name, EssentialsWebApiPropertiesConfig config)
|
public EssentialsWebApi(string key, string name, EssentialsWebApiPropertiesConfig config)
|
||||||
: base(key, name)
|
: base(key, name)
|
||||||
{
|
{
|
||||||
Key = key;
|
Key = key;
|
||||||
|
|
||||||
if (config == null)
|
if (config == null)
|
||||||
BasePath = _defaultBasePath;
|
BasePath = _defaultBasePath;
|
||||||
else
|
else
|
||||||
BasePath = string.IsNullOrEmpty(config.BasePath) ? _defaultBasePath : config.BasePath;
|
BasePath = string.IsNullOrEmpty(config.BasePath) ? _defaultBasePath : config.BasePath;
|
||||||
|
|
||||||
_server = new WebApiServer(Key, Name, BasePath);
|
_server = new WebApiServer(Key, Name, BasePath);
|
||||||
|
|
||||||
SetupRoutes();
|
SetupRoutes();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupRoutes()
|
private void SetupRoutes()
|
||||||
{
|
{
|
||||||
var routes = new List<HttpCwsRoute>
|
var routes = new List<HttpCwsRoute>
|
||||||
{
|
{
|
||||||
new HttpCwsRoute("versions")
|
new HttpCwsRoute("versions")
|
||||||
@@ -177,6 +177,11 @@ namespace PepperDash.Essentials.Core.Web
|
|||||||
Name = "Get Routing Ports for a device",
|
Name = "Get Routing Ports for a device",
|
||||||
RouteHandler = new GetRoutingPortsHandler()
|
RouteHandler = new GetRoutingPortsHandler()
|
||||||
},
|
},
|
||||||
|
new HttpCwsRoute("routingDevicesAndTieLines")
|
||||||
|
{
|
||||||
|
Name = "Get Routing Devices and TieLines",
|
||||||
|
RouteHandler = new GetRoutingDevicesAndTieLinesHandler()
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
AddRoute(routes);
|
AddRoute(routes);
|
||||||
@@ -211,78 +216,79 @@ namespace PepperDash.Essentials.Core.Web
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
AddRoute(new HttpCwsRoute("apiPaths") {
|
AddRoute(new HttpCwsRoute("apiPaths")
|
||||||
|
{
|
||||||
Name = "GetPaths",
|
Name = "GetPaths",
|
||||||
RouteHandler = new GetRoutesHandler(_server.GetRouteCollection(), BasePath)
|
RouteHandler = new GetRoutesHandler(_server.GetRouteCollection(), BasePath)
|
||||||
});
|
});
|
||||||
|
|
||||||
// If running on an appliance
|
// If running on an appliance
|
||||||
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance)
|
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
WEBSERVER [ON | OFF | TIMEOUT <VALUE IN SECONDS> | MAXSESSIONSPERUSER <Number of sessions>]
|
WEBSERVER [ON | OFF | TIMEOUT <VALUE IN SECONDS> | MAXSESSIONSPERUSER <Number of sessions>]
|
||||||
*/
|
*/
|
||||||
var response = string.Empty;
|
var response = string.Empty;
|
||||||
CrestronConsole.SendControlSystemCommand("webserver", ref response);
|
CrestronConsole.SendControlSystemCommand("webserver", ref response);
|
||||||
if (response.Contains("OFF")) return;
|
if (response.Contains("OFF")) return;
|
||||||
|
|
||||||
var is4Series = eCrestronSeries.Series4 == (Global.ProcessorSeries & eCrestronSeries.Series4);
|
var is4Series = eCrestronSeries.Series4 == (Global.ProcessorSeries & eCrestronSeries.Series4);
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "Starting Essentials Web API on {0} Appliance", is4Series ? "4-series" : "3-series");
|
Debug.LogMessage(LogEventLevel.Verbose, "Starting Essentials Web API on {0} Appliance", is4Series ? "4-series" : "3-series");
|
||||||
|
|
||||||
_server.Start();
|
_server.Start();
|
||||||
|
|
||||||
GetPaths();
|
GetPaths();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically start CWS when running on a server (Linux OS, Virtual Control)
|
// Automatically start CWS when running on a server (Linux OS, Virtual Control)
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "Starting Essentials Web API on Virtual Control Server");
|
Debug.LogMessage(LogEventLevel.Verbose, "Starting Essentials Web API on Virtual Control Server");
|
||||||
|
|
||||||
_server.Start();
|
_server.Start();
|
||||||
|
|
||||||
GetPaths();
|
GetPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Print the available pahts
|
/// Print the available pahts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <example>
|
/// <example>
|
||||||
/// http(s)://{ipaddress}/cws/{basePath}
|
/// http(s)://{ipaddress}/cws/{basePath}
|
||||||
/// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath}
|
/// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath}
|
||||||
/// </example>
|
/// </example>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GetPaths method
|
/// GetPaths method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void GetPaths()
|
public void GetPaths()
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50));
|
Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50));
|
||||||
|
|
||||||
var currentIp = CrestronEthernetHelper.GetEthernetParameter(
|
var currentIp = CrestronEthernetHelper.GetEthernetParameter(
|
||||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
|
||||||
|
|
||||||
var hostname = CrestronEthernetHelper.GetEthernetParameter(
|
var hostname = CrestronEthernetHelper.GetEthernetParameter(
|
||||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0);
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0);
|
||||||
|
|
||||||
var path = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server
|
var path = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server
|
||||||
? $"https://{hostname}/VirtualControl/Rooms/{InitialParametersClass.RoomId}/cws{BasePath}"
|
? $"https://{hostname}/VirtualControl/Rooms/{InitialParametersClass.RoomId}/cws{BasePath}"
|
||||||
: $"https://{currentIp}/cws{BasePath}";
|
: $"https://{currentIp}/cws{BasePath}";
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Server:{path:l}", path);
|
|
||||||
|
|
||||||
var routeCollection = _server.GetRouteCollection();
|
Debug.LogMessage(LogEventLevel.Information, this, "Server:{path:l}", path);
|
||||||
if (routeCollection == null)
|
|
||||||
{
|
var routeCollection = _server.GetRouteCollection();
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Server route collection is null");
|
if (routeCollection == null)
|
||||||
return;
|
{
|
||||||
}
|
Debug.LogMessage(LogEventLevel.Information, this, "Server route collection is null");
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Configured Routes:");
|
return;
|
||||||
foreach (var route in routeCollection)
|
}
|
||||||
{
|
Debug.LogMessage(LogEventLevel.Information, this, "Configured Routes:");
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "{routeName:l}: {routePath:l}/{routeUrl:l}", route.Name, path, route.Url);
|
foreach (var route in routeCollection)
|
||||||
}
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50));
|
Debug.LogMessage(LogEventLevel.Information, this, "{routeName:l}: {routePath:l}/{routeUrl:l}", route.Name, path, route.Url);
|
||||||
}
|
}
|
||||||
}
|
Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Crestron.SimplSharp.WebScripting;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using PepperDash.Core;
|
||||||
|
using PepperDash.Core.Web.RequestHandlers;
|
||||||
|
|
||||||
|
namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles HTTP requests to retrieve routing devices and tielines information
|
||||||
|
/// </summary>
|
||||||
|
public class GetRoutingDevicesAndTieLinesHandler : WebApiBaseRequestHandler
|
||||||
|
{
|
||||||
|
public GetRoutingDevicesAndTieLinesHandler() : base(true) { }
|
||||||
|
|
||||||
|
protected override void HandleGet(HttpCwsContext context)
|
||||||
|
{
|
||||||
|
var devices = new List<RoutingDeviceInfo>();
|
||||||
|
|
||||||
|
// Get all devices from DeviceManager
|
||||||
|
foreach (var device in DeviceManager.AllDevices)
|
||||||
|
{
|
||||||
|
var deviceInfo = new RoutingDeviceInfo
|
||||||
|
{
|
||||||
|
Key = device.Key,
|
||||||
|
Name = (device as IKeyName)?.Name ?? device.Key
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if device implements IRoutingInputs
|
||||||
|
if (device is IRoutingInputs inputDevice)
|
||||||
|
{
|
||||||
|
deviceInfo.HasInputs = true;
|
||||||
|
deviceInfo.InputPorts = inputDevice.InputPorts.Select(p => new PortInfo
|
||||||
|
{
|
||||||
|
Key = p.Key,
|
||||||
|
SignalType = p.Type.ToString(),
|
||||||
|
ConnectionType = p.ConnectionType.ToString(),
|
||||||
|
IsInternal = p.IsInternal
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if device implements IRoutingOutputs
|
||||||
|
if (device is IRoutingOutputs outputDevice)
|
||||||
|
{
|
||||||
|
deviceInfo.HasOutputs = true;
|
||||||
|
deviceInfo.OutputPorts = outputDevice.OutputPorts.Select(p => new PortInfo
|
||||||
|
{
|
||||||
|
Key = p.Key,
|
||||||
|
SignalType = p.Type.ToString(),
|
||||||
|
ConnectionType = p.ConnectionType.ToString(),
|
||||||
|
IsInternal = p.IsInternal
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if device implements IRoutingInputsOutputs
|
||||||
|
if (device is IRoutingInputsOutputs)
|
||||||
|
{
|
||||||
|
deviceInfo.HasInputsAndOutputs = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only include devices that have routing capabilities
|
||||||
|
if (deviceInfo.HasInputs || deviceInfo.HasOutputs)
|
||||||
|
{
|
||||||
|
devices.Add(deviceInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all tielines
|
||||||
|
var tielines = TieLineCollection.Default.Select(tl => new TieLineInfo
|
||||||
|
{
|
||||||
|
SourceDeviceKey = tl.SourcePort.ParentDevice.Key,
|
||||||
|
SourcePortKey = tl.SourcePort.Key,
|
||||||
|
DestinationDeviceKey = tl.DestinationPort.ParentDevice.Key,
|
||||||
|
DestinationPortKey = tl.DestinationPort.Key,
|
||||||
|
SignalType = tl.Type.ToString(),
|
||||||
|
IsInternal = tl.IsInternal
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
var response = new RoutingSystemInfo
|
||||||
|
{
|
||||||
|
Devices = devices,
|
||||||
|
TieLines = tielines
|
||||||
|
};
|
||||||
|
|
||||||
|
var jsonResponse = JsonConvert.SerializeObject(response, Formatting.Indented);
|
||||||
|
|
||||||
|
context.Response.StatusCode = 200;
|
||||||
|
context.Response.StatusDescription = "OK";
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
context.Response.ContentEncoding = Encoding.UTF8;
|
||||||
|
context.Response.Write(jsonResponse, false);
|
||||||
|
context.Response.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the complete routing system information including devices and tielines
|
||||||
|
/// </summary>
|
||||||
|
public class RoutingSystemInfo
|
||||||
|
{
|
||||||
|
[JsonProperty("devices")]
|
||||||
|
public List<RoutingDeviceInfo> Devices { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("tieLines")]
|
||||||
|
public List<TieLineInfo> TieLines { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a routing device with its ports information
|
||||||
|
/// </summary>
|
||||||
|
public class RoutingDeviceInfo
|
||||||
|
{
|
||||||
|
[JsonProperty("key")]
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("hasInputs")]
|
||||||
|
public bool HasInputs { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("hasOutputs")]
|
||||||
|
public bool HasOutputs { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("hasInputsAndOutputs")]
|
||||||
|
public bool HasInputsAndOutputs { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("inputPorts", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
|
public List<PortInfo> InputPorts { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("outputPorts", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
|
public List<PortInfo> OutputPorts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a routing port with its properties
|
||||||
|
/// </summary>
|
||||||
|
public class PortInfo
|
||||||
|
{
|
||||||
|
[JsonProperty("key")]
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("signalType")]
|
||||||
|
public string SignalType { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("connectionType")]
|
||||||
|
public string ConnectionType { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("isInternal")]
|
||||||
|
public bool IsInternal { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a tieline connection between two ports
|
||||||
|
/// </summary>
|
||||||
|
public class TieLineInfo
|
||||||
|
{
|
||||||
|
[JsonProperty("sourceDeviceKey")]
|
||||||
|
public string SourceDeviceKey { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("sourcePortKey")]
|
||||||
|
public string SourcePortKey { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("destinationDeviceKey")]
|
||||||
|
public string DestinationDeviceKey { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("destinationPortKey")]
|
||||||
|
public string DestinationPortKey { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("signalType")]
|
||||||
|
public string SignalType { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("isInternal")]
|
||||||
|
public bool IsInternal { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,9 +6,9 @@ using PepperDash.Core.Web.RequestHandlers;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a SetDeviceStreamDebugRequestHandler
|
/// Represents a SetDeviceStreamDebugRequestHandler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SetDeviceStreamDebugRequestHandler : WebApiBaseRequestHandler
|
public class SetDeviceStreamDebugRequestHandler : WebApiBaseRequestHandler
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -122,23 +122,23 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(DeviceManager.GetDeviceForKey(body.DeviceKey) is IStreamDebugging device))
|
if (!(DeviceManager.GetDeviceForKey(body.DeviceKey) is IStreamDebugging device))
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = 404;
|
context.Response.StatusCode = 404;
|
||||||
context.Response.StatusDescription = "Not Found";
|
context.Response.StatusDescription = "Not Found";
|
||||||
context.Response.End();
|
context.Response.End();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
eStreamDebuggingSetting debugSetting;
|
eStreamDebuggingSetting debugSetting;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
debugSetting = (eStreamDebuggingSetting) Enum.Parse(typeof (eStreamDebuggingSetting), body.Setting, true);
|
debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), body.Setting, true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(ex, "Exception handling set debug request");
|
Debug.LogMessage(ex, "Exception handling set debug request");
|
||||||
context.Response.StatusCode = 500;
|
context.Response.StatusCode = 500;
|
||||||
context.Response.StatusDescription = "Internal Server Error";
|
context.Response.StatusDescription = "Internal Server Error";
|
||||||
context.Response.End();
|
context.Response.End();
|
||||||
@@ -164,7 +164,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(ex, "Exception handling set debug request");
|
Debug.LogMessage(ex, "Exception handling set debug request");
|
||||||
context.Response.StatusCode = 500;
|
context.Response.StatusCode = 500;
|
||||||
context.Response.StatusDescription = "Internal Server Error";
|
context.Response.StatusDescription = "Internal Server Error";
|
||||||
context.Response.End();
|
context.Response.End();
|
||||||
@@ -198,21 +198,21 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
|
|||||||
public class SetDeviceStreamDebugConfig
|
public class SetDeviceStreamDebugConfig
|
||||||
{
|
{
|
||||||
[JsonProperty("deviceKey", NullValueHandling = NullValueHandling.Include)]
|
[JsonProperty("deviceKey", NullValueHandling = NullValueHandling.Include)]
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the DeviceKey
|
/// Gets or sets the DeviceKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string DeviceKey { get; set; }
|
public string DeviceKey { get; set; }
|
||||||
|
|
||||||
[JsonProperty("setting", NullValueHandling = NullValueHandling.Include)]
|
[JsonProperty("setting", NullValueHandling = NullValueHandling.Include)]
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Setting
|
/// Gets or sets the Setting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Setting { get; set; }
|
public string Setting { get; set; }
|
||||||
|
|
||||||
[JsonProperty("timeout")]
|
[JsonProperty("timeout")]
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Timeout
|
/// Gets or sets the Timeout
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Timeout { get; set; }
|
public int Timeout { get; set; }
|
||||||
|
|
||||||
public SetDeviceStreamDebugConfig()
|
public SetDeviceStreamDebugConfig()
|
||||||
|
|||||||
@@ -61,13 +61,18 @@ namespace PepperDash.Essentials.Devices.Common.DSP
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for DSP control points
|
/// Base class for DSP control points
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class DspControlPoint : IKeyed
|
public abstract class DspControlPoint : IKeyName
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Key
|
/// Gets or sets the Key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Key { get; }
|
public string Key { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Name
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the DspControlPoint class
|
/// Initializes a new instance of the DspControlPoint class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,15 +1,27 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Timers;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
using PepperDash.Core.Logging;
|
||||||
using PepperDash.Essentials.Core;
|
using PepperDash.Essentials.Core;
|
||||||
using PepperDash.Essentials.Core.Config;
|
using PepperDash.Essentials.Core.Config;
|
||||||
using PepperDash.Essentials.Core.CrestronIO;
|
using PepperDash.Essentials.Core.CrestronIO;
|
||||||
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
||||||
using Serilog.Events;
|
using PepperDash.Essentials.Devices.Common.Displays;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Devices.Common.Shades
|
namespace PepperDash.Essentials.Devices.Common.Shades
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enumeration for requested state
|
||||||
|
/// </summary>
|
||||||
|
enum RequestedState
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Raise,
|
||||||
|
Lower
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controls a single shade using three relays
|
/// Controls a single shade using three relays
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -20,11 +32,16 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
readonly ScreenLiftRelaysConfig LowerRelayConfig;
|
readonly ScreenLiftRelaysConfig LowerRelayConfig;
|
||||||
readonly ScreenLiftRelaysConfig LatchedRelayConfig;
|
readonly ScreenLiftRelaysConfig LatchedRelayConfig;
|
||||||
|
|
||||||
Displays.DisplayBase DisplayDevice;
|
DisplayBase DisplayDevice;
|
||||||
ISwitchedOutput RaiseRelay;
|
ISwitchedOutput RaiseRelay;
|
||||||
ISwitchedOutput LowerRelay;
|
ISwitchedOutput LowerRelay;
|
||||||
ISwitchedOutput LatchedRelay;
|
ISwitchedOutput LatchedRelay;
|
||||||
|
|
||||||
|
private bool _isMoving;
|
||||||
|
private RequestedState _requestedState;
|
||||||
|
private RequestedState _currentMovement;
|
||||||
|
private Timer _movementTimer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the InUpPosition
|
/// Gets or sets the InUpPosition
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -80,6 +97,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
|
|
||||||
IsInUpPosition = new BoolFeedback("isInUpPosition", () => _isInUpPosition);
|
IsInUpPosition = new BoolFeedback("isInUpPosition", () => _isInUpPosition);
|
||||||
|
|
||||||
|
// Initialize movement timer for reuse
|
||||||
|
_movementTimer = new Timer();
|
||||||
|
_movementTimer.Elapsed += OnMovementComplete;
|
||||||
|
_movementTimer.AutoReset = false;
|
||||||
|
|
||||||
switch (Mode)
|
switch (Mode)
|
||||||
{
|
{
|
||||||
case eScreenLiftControlMode.momentary:
|
case eScreenLiftControlMode.momentary:
|
||||||
@@ -129,25 +151,25 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
{
|
{
|
||||||
case eScreenLiftControlMode.momentary:
|
case eScreenLiftControlMode.momentary:
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting relays for {Mode}");
|
this.LogDebug("Getting relays for {mode}", Mode);
|
||||||
RaiseRelay = GetSwitchedOutputFromDevice(RaiseRelayConfig.DeviceKey);
|
RaiseRelay = GetSwitchedOutputFromDevice(RaiseRelayConfig.DeviceKey);
|
||||||
LowerRelay = GetSwitchedOutputFromDevice(LowerRelayConfig.DeviceKey);
|
LowerRelay = GetSwitchedOutputFromDevice(LowerRelayConfig.DeviceKey);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case eScreenLiftControlMode.latched:
|
case eScreenLiftControlMode.latched:
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting relays for {Mode}");
|
this.LogDebug("Getting relays for {mode}", Mode);
|
||||||
LatchedRelay = GetSwitchedOutputFromDevice(LatchedRelayConfig.DeviceKey);
|
LatchedRelay = GetSwitchedOutputFromDevice(LatchedRelayConfig.DeviceKey);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting display with key {DisplayDeviceKey}");
|
this.LogDebug("Getting display with key {displayKey}", DisplayDeviceKey);
|
||||||
DisplayDevice = GetDisplayBaseFromDevice(DisplayDeviceKey);
|
DisplayDevice = GetDisplayBaseFromDevice(DisplayDeviceKey);
|
||||||
|
|
||||||
if (DisplayDevice != null)
|
if (DisplayDevice != null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Subscribing to {DisplayDeviceKey} feedbacks");
|
this.LogDebug("Subscribing to {displayKey} feedbacks", DisplayDeviceKey);
|
||||||
|
|
||||||
DisplayDevice.IsWarmingUpFeedback.OutputChange += IsWarmingUpFeedback_OutputChange;
|
DisplayDevice.IsWarmingUpFeedback.OutputChange += IsWarmingUpFeedback_OutputChange;
|
||||||
DisplayDevice.IsCoolingDownFeedback.OutputChange += IsCoolingDownFeedback_OutputChange;
|
DisplayDevice.IsCoolingDownFeedback.OutputChange += IsCoolingDownFeedback_OutputChange;
|
||||||
@@ -163,22 +185,49 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
{
|
{
|
||||||
if (RaiseRelay == null && LatchedRelay == null) return;
|
if (RaiseRelay == null && LatchedRelay == null) return;
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Raising {Type}");
|
this.LogDebug("Raise called for {type}", Type);
|
||||||
|
|
||||||
|
// If device is moving, bank the command
|
||||||
|
if (_isMoving)
|
||||||
|
{
|
||||||
|
this.LogDebug("Device is moving, banking Raise command");
|
||||||
|
_requestedState = RequestedState.Raise;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.LogDebug("Raising {type}", Type);
|
||||||
|
|
||||||
switch (Mode)
|
switch (Mode)
|
||||||
{
|
{
|
||||||
case eScreenLiftControlMode.momentary:
|
case eScreenLiftControlMode.momentary:
|
||||||
{
|
{
|
||||||
PulseOutput(RaiseRelay, RaiseRelayConfig.PulseTimeInMs);
|
PulseOutput(RaiseRelay, RaiseRelayConfig.PulseTimeInMs);
|
||||||
|
|
||||||
|
// Set moving flag and start timer if movement time is configured
|
||||||
|
if (RaiseRelayConfig.MoveTimeInMs > 0)
|
||||||
|
{
|
||||||
|
_isMoving = true;
|
||||||
|
_currentMovement = RequestedState.Raise;
|
||||||
|
if (_movementTimer.Enabled)
|
||||||
|
{
|
||||||
|
_movementTimer.Stop();
|
||||||
|
}
|
||||||
|
_movementTimer.Interval = RaiseRelayConfig.MoveTimeInMs;
|
||||||
|
_movementTimer.Start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InUpPosition = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case eScreenLiftControlMode.latched:
|
case eScreenLiftControlMode.latched:
|
||||||
{
|
{
|
||||||
LatchedRelay.Off();
|
LatchedRelay.Off();
|
||||||
|
InUpPosition = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InUpPosition = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -188,59 +237,145 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
{
|
{
|
||||||
if (LowerRelay == null && LatchedRelay == null) return;
|
if (LowerRelay == null && LatchedRelay == null) return;
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Lowering {Type}");
|
this.LogDebug("Lower called for {type}", Type);
|
||||||
|
|
||||||
|
// If device is moving, bank the command
|
||||||
|
if (_isMoving)
|
||||||
|
{
|
||||||
|
this.LogDebug("Device is moving, banking Lower command");
|
||||||
|
_requestedState = RequestedState.Lower;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.LogDebug("Lowering {type}", Type);
|
||||||
|
|
||||||
switch (Mode)
|
switch (Mode)
|
||||||
{
|
{
|
||||||
case eScreenLiftControlMode.momentary:
|
case eScreenLiftControlMode.momentary:
|
||||||
{
|
{
|
||||||
PulseOutput(LowerRelay, LowerRelayConfig.PulseTimeInMs);
|
PulseOutput(LowerRelay, LowerRelayConfig.PulseTimeInMs);
|
||||||
|
|
||||||
|
// Set moving flag and start timer if movement time is configured
|
||||||
|
if (LowerRelayConfig.MoveTimeInMs > 0)
|
||||||
|
{
|
||||||
|
_isMoving = true;
|
||||||
|
_currentMovement = RequestedState.Lower;
|
||||||
|
if (_movementTimer.Enabled)
|
||||||
|
{
|
||||||
|
_movementTimer.Stop();
|
||||||
|
}
|
||||||
|
_movementTimer.Interval = LowerRelayConfig.MoveTimeInMs;
|
||||||
|
_movementTimer.Start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InUpPosition = false;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case eScreenLiftControlMode.latched:
|
case eScreenLiftControlMode.latched:
|
||||||
{
|
{
|
||||||
LatchedRelay.On();
|
LatchedRelay.On();
|
||||||
|
InUpPosition = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InUpPosition = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PulseOutput(ISwitchedOutput output, int pulseTime)
|
private void DisposeMovementTimer()
|
||||||
{
|
{
|
||||||
output.On();
|
if (_movementTimer != null)
|
||||||
CTimer pulseTimer = new CTimer(new CTimerCallbackFunction((o) => output.Off()), pulseTime);
|
{
|
||||||
|
_movementTimer.Stop();
|
||||||
|
_movementTimer.Elapsed -= OnMovementComplete;
|
||||||
|
_movementTimer.Dispose();
|
||||||
|
_movementTimer = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to get the port on teh specified device from config
|
/// Called when movement timer completes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="relayKey"></param>
|
private void OnMovementComplete(object sender, ElapsedEventArgs e)
|
||||||
/// <returns></returns>
|
|
||||||
ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey)
|
|
||||||
{
|
{
|
||||||
var portDevice = DeviceManager.GetDeviceForKey(relayKey);
|
this.LogDebug("Movement complete");
|
||||||
|
|
||||||
|
// Update position based on completed movement
|
||||||
|
if (_currentMovement == RequestedState.Raise)
|
||||||
|
{
|
||||||
|
InUpPosition = true;
|
||||||
|
}
|
||||||
|
else if (_currentMovement == RequestedState.Lower)
|
||||||
|
{
|
||||||
|
InUpPosition = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isMoving = false;
|
||||||
|
_currentMovement = RequestedState.None;
|
||||||
|
|
||||||
|
// Execute banked command if one exists
|
||||||
|
if (_requestedState != RequestedState.None)
|
||||||
|
{
|
||||||
|
this.LogDebug("Executing next command: {command}", _requestedState);
|
||||||
|
|
||||||
|
var commandToExecute = _requestedState;
|
||||||
|
_requestedState = RequestedState.None;
|
||||||
|
|
||||||
|
// Check if current state matches what the banked command would do and execute if different
|
||||||
|
switch (commandToExecute)
|
||||||
|
{
|
||||||
|
case RequestedState.Raise:
|
||||||
|
Raise();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RequestedState.Lower:
|
||||||
|
Lower();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PulseOutput(ISwitchedOutput output, int pulseTime)
|
||||||
|
{
|
||||||
|
output.On();
|
||||||
|
|
||||||
|
var timer = new Timer(pulseTime)
|
||||||
|
{
|
||||||
|
AutoReset = false
|
||||||
|
};
|
||||||
|
|
||||||
|
timer.Elapsed += (sender, e) =>
|
||||||
|
{
|
||||||
|
output.Off();
|
||||||
|
timer.Dispose();
|
||||||
|
};
|
||||||
|
timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey)
|
||||||
|
{
|
||||||
|
var portDevice = DeviceManager.GetDeviceForKey<ISwitchedOutput>(relayKey);
|
||||||
if (portDevice != null)
|
if (portDevice != null)
|
||||||
{
|
{
|
||||||
return portDevice as ISwitchedOutput;
|
return portDevice;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "Error: Unable to get relay device with key '{0}'", relayKey);
|
this.LogWarning("Error: Unable to get relay device with key '{relayKey}'", relayKey);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Displays.DisplayBase GetDisplayBaseFromDevice(string displayKey)
|
private DisplayBase GetDisplayBaseFromDevice(string displayKey)
|
||||||
{
|
{
|
||||||
var displayDevice = DeviceManager.GetDeviceForKey(displayKey);
|
var displayDevice = DeviceManager.GetDeviceForKey<DisplayBase>(displayKey);
|
||||||
if (displayDevice != null)
|
if (displayDevice != null)
|
||||||
{
|
{
|
||||||
return displayDevice as Displays.DisplayBase;
|
return displayDevice;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "Error: Unable to get display device with key '{0}'", displayKey);
|
this.LogWarning("Error: Unable to get display device with key '{displayKey}'", displayKey);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,7 +383,7 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a ScreenLiftControllerFactory
|
/// Factory for ScreenLiftController devices
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ScreenLiftControllerFactory : EssentialsDeviceFactory<RelayControlledShade>
|
public class ScreenLiftControllerFactory : EssentialsDeviceFactory<RelayControlledShade>
|
||||||
{
|
{
|
||||||
@@ -260,14 +395,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
TypeNames = new List<string>() { "screenliftcontroller" };
|
TypeNames = new List<string>() { "screenliftcontroller" };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// BuildDevice method
|
|
||||||
/// </summary>
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Comm Device");
|
Debug.LogDebug("Factory Attempting to create new ScreenLiftController Device");
|
||||||
var props = Newtonsoft.Json.JsonConvert.DeserializeObject<ScreenLiftControllerConfigProperties>(dc.Properties.ToString());
|
var props = dc.Properties.ToObject<ScreenLiftControllerConfigProperties>();
|
||||||
|
|
||||||
return new ScreenLiftController(dc.Key, dc.Name, props);
|
return new ScreenLiftController(dc.Key, dc.Name, props);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,5 +18,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("pulseTimeInMs")]
|
[JsonProperty("pulseTimeInMs")]
|
||||||
public int PulseTimeInMs { get; set; }
|
public int PulseTimeInMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the MoveTimeInMs - time in milliseconds for the movement to complete
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("moveTimeInMs")]
|
||||||
|
public int MoveTimeInMs { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
using PepperDash.Core.Logging;
|
||||||
using PepperDash.Essentials.Core;
|
using PepperDash.Essentials.Core;
|
||||||
using PepperDash.Essentials.Core.Config;
|
using PepperDash.Essentials.Core.Config;
|
||||||
|
using PepperDash.Essentials.Core.Routing;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Devices.Common.Generic
|
namespace PepperDash.Essentials.Devices.Common.Generic
|
||||||
@@ -9,8 +12,17 @@ namespace PepperDash.Essentials.Devices.Common.Generic
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a GenericSink
|
/// Represents a GenericSink
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericSink : EssentialsDevice, IRoutingSinkWithInputPort
|
public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputPort, ICurrentSources
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event EventHandler CurrentSourcesChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the GenericSink class
|
/// Initializes a new instance of the GenericSink class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -20,9 +32,52 @@ namespace PepperDash.Essentials.Devices.Common.Generic
|
|||||||
{
|
{
|
||||||
InputPorts = new RoutingPortCollection<RoutingInputPort>();
|
InputPorts = new RoutingPortCollection<RoutingInputPort>();
|
||||||
|
|
||||||
var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this);
|
var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo | eRoutingSignalType.SecondaryAudio, eRoutingPortConnectionType.Hdmi, null, this);
|
||||||
|
|
||||||
InputPorts.Add(inputPort);
|
InputPorts.Add(inputPort);
|
||||||
|
|
||||||
|
CurrentSources = new Dictionary<eRoutingSignalType, SourceListItem>
|
||||||
|
{
|
||||||
|
{ eRoutingSignalType.Audio, null },
|
||||||
|
{ eRoutingSignalType.Video, null },
|
||||||
|
};
|
||||||
|
|
||||||
|
CurrentSourceKeys = new Dictionary<eRoutingSignalType, string>
|
||||||
|
{
|
||||||
|
{ eRoutingSignalType.Audio, string.Empty },
|
||||||
|
{ eRoutingSignalType.Video, string.Empty },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
|
||||||
|
{
|
||||||
|
foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType)))
|
||||||
|
{
|
||||||
|
var flagValue = Convert.ToInt32(type);
|
||||||
|
// Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag).
|
||||||
|
// (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set.
|
||||||
|
if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0)
|
||||||
|
{
|
||||||
|
this.LogDebug("Skipping {type}", type);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.LogDebug("setting {type}", type);
|
||||||
|
|
||||||
|
if (signalType.HasFlag(type))
|
||||||
|
{
|
||||||
|
UpdateCurrentSources(type, sourceListKey, sourceListItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Raise the CurrentSourcesChanged event
|
||||||
|
CurrentSourcesChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCurrentSources(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
|
||||||
|
{
|
||||||
|
CurrentSources[signalType] = sourceListItem;
|
||||||
|
CurrentSourceKeys[signalType] = sourceListKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -66,6 +121,15 @@ namespace PepperDash.Essentials.Devices.Common.Generic
|
|||||||
/// Event fired when the current source changes
|
/// Event fired when the current source changes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event SourceInfoChangeHandler CurrentSourceChange;
|
public event SourceInfoChangeHandler CurrentSourceChange;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event InputChangedEventHandler InputChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void ExecuteSwitch(object inputSelector)
|
||||||
|
{
|
||||||
|
this.LogDebug("GenericSink Executing Switch to: {inputSelector}", inputSelector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
@@ -39,10 +40,14 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
|
|
||||||
sourceDevice.CurrentSourcesChanged += (sender, e) =>
|
sourceDevice.CurrentSourcesChanged += (sender, e) =>
|
||||||
{
|
{
|
||||||
|
// need to copy the dictionaries to avoid enumeration issues
|
||||||
|
var currentSourceKeys = sourceDevice.CurrentSourceKeys.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||||
|
var currentSources = sourceDevice.CurrentSources.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||||
|
|
||||||
PostStatusMessage(JToken.FromObject(new
|
PostStatusMessage(JToken.FromObject(new
|
||||||
{
|
{
|
||||||
currentSourceKeys = sourceDevice.CurrentSourceKeys,
|
currentSourceKeys,
|
||||||
currentSources = sourceDevice.CurrentSources
|
currentSources,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
/// <param name="messagePath">The message path.</param>
|
/// <param name="messagePath">The message path.</param>
|
||||||
/// <param name="device">The device.</param>
|
/// <param name="device">The device.</param>
|
||||||
public DeviceVolumeMessenger(string key, string messagePath, IBasicVolumeControls device)
|
public DeviceVolumeMessenger(string key, string messagePath, IBasicVolumeControls device)
|
||||||
: base(key, messagePath, device as IKeyName)
|
: base(key, messagePath, device)
|
||||||
{
|
{
|
||||||
this.device = device;
|
this.device = device;
|
||||||
}
|
}
|
||||||
@@ -130,34 +130,33 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
|
|
||||||
feedback.MuteFeedback.OutputChange += (sender, args) =>
|
feedback.MuteFeedback.OutputChange += (sender, args) =>
|
||||||
{
|
{
|
||||||
PostStatusMessage(JToken.FromObject(
|
var message = new VolumeStateMessage
|
||||||
new
|
{
|
||||||
{
|
Volume = new Volume
|
||||||
volume = new
|
{
|
||||||
{
|
Muted = args.BoolValue
|
||||||
muted = args.BoolValue
|
}
|
||||||
}
|
};
|
||||||
})
|
|
||||||
);
|
PostStatusMessage(JToken.FromObject(message));
|
||||||
};
|
};
|
||||||
|
|
||||||
feedback.VolumeLevelFeedback.OutputChange += (sender, args) =>
|
feedback.VolumeLevelFeedback.OutputChange += (sender, args) =>
|
||||||
{
|
{
|
||||||
var rawValue = "";
|
var message = new VolumeStateMessage
|
||||||
if (feedback is IBasicVolumeWithFeedbackAdvanced volumeAdvanced)
|
|
||||||
{
|
{
|
||||||
rawValue = volumeAdvanced.RawVolumeLevel.ToString();
|
Volume = new Volume
|
||||||
}
|
|
||||||
|
|
||||||
var message = new
|
|
||||||
{
|
|
||||||
volume = new
|
|
||||||
{
|
{
|
||||||
level = args.IntValue,
|
Level = args.IntValue,
|
||||||
rawValue
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (device is IBasicVolumeWithFeedbackAdvanced volumeAdvanced)
|
||||||
|
{
|
||||||
|
message.Volume.RawValue = volumeAdvanced.RawVolumeLevel.ToString();
|
||||||
|
message.Volume.Units = volumeAdvanced.Units;
|
||||||
|
}
|
||||||
|
|
||||||
PostStatusMessage(JToken.FromObject(message));
|
PostStatusMessage(JToken.FromObject(message));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,14 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
inputs = matrixDevice.InputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingInput(kvp.Value))
|
inputs = matrixDevice.InputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingInput(kvp.Value))
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inputSlot.IsOnline.OutputChange += (sender, args) =>
|
||||||
|
{
|
||||||
|
PostStatusMessage(JToken.FromObject(new
|
||||||
|
{
|
||||||
|
inputs = matrixDevice.InputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingInput(kvp.Value))
|
||||||
|
}));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,12 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Unsoliciited feedback from a device in a messenger will ONLY be sent to devices in this subscription list. When a client disconnects, it's ID will be removed from the collection.
|
/// Unsoliciited feedback from a device in a messenger will ONLY be sent to devices in this subscription list. When a client disconnects, it's ID will be removed from the collection.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected HashSet<string> SubscriberIds = new HashSet<string>();
|
private readonly HashSet<string> subscriberIds = new HashSet<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lock object for thread-safe access to SubscriberIds
|
||||||
|
/// </summary>
|
||||||
|
private readonly object _subscriberLock = new object();
|
||||||
|
|
||||||
private readonly List<string> _deviceInterfaces;
|
private readonly List<string> _deviceInterfaces;
|
||||||
|
|
||||||
@@ -189,18 +194,18 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
{
|
{
|
||||||
if (!enableMessengerSubscriptions)
|
if (!enableMessengerSubscriptions)
|
||||||
{
|
{
|
||||||
this.LogWarning("Messenger subscriptions not enabled");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SubscriberIds.Any(id => id == clientId))
|
lock (_subscriberLock)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Client {clientId} already subscribed", clientId);
|
if (!subscriberIds.Add(clientId))
|
||||||
return;
|
{
|
||||||
|
this.LogVerbose("Client {clientId} already subscribed", clientId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SubscriberIds.Add(clientId);
|
|
||||||
|
|
||||||
this.LogDebug("Client {clientId} subscribed", clientId);
|
this.LogDebug("Client {clientId} subscribed", clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,19 +217,26 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
{
|
{
|
||||||
if (!enableMessengerSubscriptions)
|
if (!enableMessengerSubscriptions)
|
||||||
{
|
{
|
||||||
this.LogWarning("Messenger subscriptions not enabled");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SubscriberIds.Any(i => i == clientId))
|
bool wasSubscribed;
|
||||||
|
lock (_subscriberLock)
|
||||||
|
{
|
||||||
|
wasSubscribed = subscriberIds.Contains(clientId);
|
||||||
|
if (wasSubscribed)
|
||||||
|
{
|
||||||
|
subscriberIds.Remove(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wasSubscribed)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Client with ID {clientId} is not subscribed", clientId);
|
this.LogVerbose("Client with ID {clientId} is not subscribed", clientId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SubscriberIds.RemoveWhere((i) => i == clientId);
|
this.LogDebug("Client with ID {clientId} unsubscribed", clientId);
|
||||||
|
|
||||||
this.LogInformation("Client with ID {clientId} unsubscribed", clientId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -258,7 +270,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError(ex, "Exception posting status message for {messagePath} to {clientId}", MessagePath, clientId ?? "all clients");
|
this.LogError("Exception posting status message for {messagePath} to {clientId}: {message}", MessagePath, clientId ?? "all clients", ex.Message);
|
||||||
|
this.LogDebug(ex, "Stack trace: ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +300,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError(ex, "Exception posting status message for {type} to {clientId}", type, clientId ?? "all clients");
|
this.LogError("Exception posting status message for {type} to {clientId}: {message}", type, clientId ?? "all clients", ex.Message);
|
||||||
|
this.LogDebug(ex, "Stack trace: ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +326,14 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
// If client is null or empty, this message is unsolicited feedback. Iterate through the subscriber list and send to all interested parties
|
// If client is null or empty, this message is unsolicited feedback. Iterate through the subscriber list and send to all interested parties
|
||||||
if (string.IsNullOrEmpty(clientId))
|
if (string.IsNullOrEmpty(clientId))
|
||||||
{
|
{
|
||||||
foreach (var client in SubscriberIds)
|
// Create a snapshot of subscribers to avoid collection modification during iteration
|
||||||
|
List<string> subscriberSnapshot;
|
||||||
|
lock (_subscriberLock)
|
||||||
|
{
|
||||||
|
subscriberSnapshot = new List<string>(subscriberIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var client in subscriberSnapshot)
|
||||||
{
|
{
|
||||||
AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = client, Content = content });
|
AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = client, Content = content });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1312,6 +1312,11 @@ namespace PepperDash.Essentials
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
|
if (!Config.EnableMessengerSubscriptions)
|
||||||
|
{
|
||||||
|
this.LogWarning("Messenger subscriptions disabled. add \"enableMessengerSubscriptions\": true to config for {key} to enable.", Key);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var messenger in _messengers)
|
foreach (var messenger in _messengers)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -1743,7 +1748,7 @@ namespace PepperDash.Essentials
|
|||||||
var clientNo = 1;
|
var clientNo = 1;
|
||||||
foreach (var clientContext in _directServer.UiClientContexts)
|
foreach (var clientContext in _directServer.UiClientContexts)
|
||||||
{
|
{
|
||||||
var clients = _directServer.UiClients.Values.Where(c => c.Token == clientContext.Value.Token.Token);
|
var clients = _directServer.UiClients.Values.Where(c => c.TokenKey == clientContext.Key);
|
||||||
|
|
||||||
CrestronConsole.ConsoleCommandResponse(
|
CrestronConsole.ConsoleCommandResponse(
|
||||||
$"\r\nClient {clientNo}:\r\n" +
|
$"\r\nClient {clientNo}:\r\n" +
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
this.LogInformation("Setting theme to {theme}", theme.Value);
|
this.LogInformation("Setting theme to {theme}", theme.Value);
|
||||||
_tpDevice.UpdateTheme(theme.Value);
|
_tpDevice.UpdateTheme(theme.Value);
|
||||||
|
|
||||||
PostStatusMessage(JToken.FromObject(new { theme = theme.Value }), id);
|
PostStatusMessage(JToken.FromObject(new { theme = theme.Value }), clientId: id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Threading;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
using WebSocketSharp;
|
using WebSocketSharp;
|
||||||
@@ -12,13 +13,12 @@ namespace PepperDash.Essentials
|
|||||||
private static int nextClientId = 0;
|
private static int nextClientId = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the next unique client ID
|
/// Get the next unique client ID (thread-safe)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Client ID</returns>
|
/// <returns>Client ID</returns>
|
||||||
public static int GetNextClientId()
|
public static int GetNextClientId()
|
||||||
{
|
{
|
||||||
nextClientId++;
|
return Interlocked.Increment(ref nextClientId);
|
||||||
return nextClientId;
|
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a WebSocketServer LogData object to Essentials logging calls.
|
/// Converts a WebSocketServer LogData object to Essentials logging calls.
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
[JsonProperty("userAppUrl")]
|
[JsonProperty("userAppUrl")]
|
||||||
public string UserAppUrl { get; set; }
|
public string UserAppUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the WebSocketUrl with clientId query parameter
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("webSocketUrl")]
|
||||||
|
public string WebSocketUrl { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the EnableDebug
|
/// Gets or sets the EnableDebug
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -59,12 +60,24 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, UiClientContext> UiClientContexts { get; private set; }
|
public Dictionary<string, UiClientContext> UiClientContexts { get; private set; }
|
||||||
|
|
||||||
private readonly Dictionary<string, UiClient> uiClients = new Dictionary<string, UiClient>();
|
private readonly ConcurrentDictionary<string, UiClient> uiClients = new ConcurrentDictionary<string, UiClient>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores pending client registrations using composite key: token-clientId
|
||||||
|
/// This ensures the correct client ID is matched even when connections establish out of order
|
||||||
|
/// </summary>
|
||||||
|
private readonly ConcurrentDictionary<string, string> pendingClientRegistrations = new ConcurrentDictionary<string, string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores queues of pending client IDs per token for legacy clients (FIFO)
|
||||||
|
/// This ensures thread-safety when multiple legacy clients use the same token
|
||||||
|
/// </summary>
|
||||||
|
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> legacyClientIdQueues = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the collection of UI clients
|
/// Gets the collection of UI clients
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReadOnlyDictionary<string, UiClient> UiClients => new ReadOnlyDictionary<string, UiClient>(uiClients);
|
public IReadOnlyDictionary<string, UiClient> UiClients => uiClients;
|
||||||
|
|
||||||
private readonly MobileControlSystemController _parent;
|
private readonly MobileControlSystemController _parent;
|
||||||
|
|
||||||
@@ -723,23 +736,95 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
|
|
||||||
private UiClient BuildUiClient(string roomKey, JoinToken token, string key)
|
private UiClient BuildUiClient(string roomKey, JoinToken token, string key)
|
||||||
{
|
{
|
||||||
var c = new UiClient($"uiclient-{key}-{roomKey}-{token.Id}", token.Id, token.Token, token.TouchpanelKey);
|
// Dequeue the next clientId for legacy client support (FIFO per token)
|
||||||
this.LogInformation("Constructing UiClient with key {key} and ID {id}", key, token.Id);
|
// New clients will override this ID in OnOpen with the validated query parameter value
|
||||||
|
var clientId = "pending";
|
||||||
|
if (legacyClientIdQueues.TryGetValue(key, out var queue) && queue.TryDequeue(out var dequeuedId))
|
||||||
|
{
|
||||||
|
clientId = dequeuedId;
|
||||||
|
this.LogVerbose("Dequeued legacy clientId {clientId} for token {token}", clientId, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = new UiClient($"uiclient-{key}-{roomKey}-{clientId}", clientId, token.Token, token.TouchpanelKey);
|
||||||
|
this.LogInformation("Constructing UiClient with key {key} and temporary ID (will be set from query param)", key);
|
||||||
c.Controller = _parent;
|
c.Controller = _parent;
|
||||||
c.RoomKey = roomKey;
|
c.RoomKey = roomKey;
|
||||||
|
c.TokenKey = key; // Store the URL token key for filtering
|
||||||
|
c.Server = this; // Give UiClient access to server for ID registration
|
||||||
|
|
||||||
if (uiClients.ContainsKey(token.Id))
|
// Don't add to uiClients yet - will be added in OnOpen after ID is set from query param
|
||||||
|
|
||||||
|
c.ConnectionClosed += (o, a) =>
|
||||||
{
|
{
|
||||||
this.LogWarning("removing client with duplicate id {id}", token.Id);
|
uiClients.TryRemove(a.ClientId, out _);
|
||||||
uiClients.Remove(token.Id);
|
// Clean up any pending registrations for this token
|
||||||
}
|
var keysToRemove = pendingClientRegistrations.Keys
|
||||||
uiClients.Add(token.Id, c);
|
.Where(k => k.StartsWith($"{key}-"))
|
||||||
// UiClients[key].SetClient(c);
|
.ToList();
|
||||||
c.ConnectionClosed += (o, a) => uiClients.Remove(a.ClientId);
|
foreach (var k in keysToRemove)
|
||||||
token.Id = null;
|
{
|
||||||
|
pendingClientRegistrations.TryRemove(k, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up legacy queue if empty
|
||||||
|
if (legacyClientIdQueues.TryGetValue(key, out var legacyQueue) && legacyQueue.IsEmpty)
|
||||||
|
{
|
||||||
|
legacyClientIdQueues.TryRemove(key, out _);
|
||||||
|
}
|
||||||
|
};
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a UiClient with its validated client ID after WebSocket connection
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client">The UiClient to register</param>
|
||||||
|
/// <param name="clientId">The validated client ID</param>
|
||||||
|
/// <param name="tokenKey">The token key for validation</param>
|
||||||
|
/// <returns>True if registration successful, false if validation failed</returns>
|
||||||
|
public bool RegisterUiClient(UiClient client, string clientId, string tokenKey)
|
||||||
|
{
|
||||||
|
var registrationKey = $"{tokenKey}-{clientId}";
|
||||||
|
|
||||||
|
// Verify this clientId was generated during a join request for this token
|
||||||
|
if (!pendingClientRegistrations.TryRemove(registrationKey, out _))
|
||||||
|
{
|
||||||
|
this.LogWarning("Client attempted to connect with unregistered or expired clientId {clientId} for token {token}", clientId, tokenKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registration is valid - add to active clients
|
||||||
|
uiClients.AddOrUpdate(clientId, client, (id, existingClient) =>
|
||||||
|
{
|
||||||
|
this.LogWarning("Replacing existing client with duplicate id {id}", id);
|
||||||
|
return client;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.LogInformation("Successfully registered UiClient with ID {clientId} for token {token}", clientId, tokenKey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a UiClient using legacy flow (for backwards compatibility with older clients)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client">The UiClient to register</param>
|
||||||
|
public void RegisterLegacyUiClient(UiClient client)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(client.Id))
|
||||||
|
{
|
||||||
|
this.LogError("Cannot register client with null or empty ID");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uiClients.AddOrUpdate(client.Id, client, (id, existingClient) =>
|
||||||
|
{
|
||||||
|
this.LogWarning("Replacing existing client with duplicate id {id} (legacy flow)", id);
|
||||||
|
return client;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.LogInformation("Successfully registered UiClient with ID {clientId} using legacy flow", client.Id);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prints out the session data for each path
|
/// Prints out the session data for each path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1046,10 +1131,22 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate a client ID for this join request
|
||||||
var clientId = $"{Utilities.GetNextClientId()}";
|
var clientId = $"{Utilities.GetNextClientId()}";
|
||||||
clientContext.Token.Id = clientId;
|
|
||||||
|
// Store in pending registrations for new clients that send clientId via query param
|
||||||
|
var registrationKey = $"{token}-{clientId}";
|
||||||
|
pendingClientRegistrations.TryAdd(registrationKey, clientId);
|
||||||
|
|
||||||
|
// Also enqueue for legacy clients (thread-safe FIFO per token)
|
||||||
|
var queue = legacyClientIdQueues.GetOrAdd(token, _ => new ConcurrentQueue<string>());
|
||||||
|
queue.Enqueue(clientId);
|
||||||
|
|
||||||
this.LogVerbose("Assigning ClientId: {clientId}", clientId);
|
this.LogVerbose("Assigning ClientId: {clientId} for token: {token}", clientId, token);
|
||||||
|
|
||||||
|
// Construct WebSocket URL with clientId query parameter
|
||||||
|
var wsProtocol = "ws";
|
||||||
|
var wsUrl = $"{wsProtocol}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}";
|
||||||
|
|
||||||
// Construct the response object
|
// Construct the response object
|
||||||
JoinResponse jRes = new JoinResponse
|
JoinResponse jRes = new JoinResponse
|
||||||
@@ -1064,6 +1161,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
UserAppUrl = string.Format("http://{0}:{1}/mc/app",
|
UserAppUrl = string.Format("http://{0}:{1}/mc/app",
|
||||||
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
|
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
|
||||||
Port),
|
Port),
|
||||||
|
WebSocketUrl = wsUrl,
|
||||||
EnableDebug = false,
|
EnableDebug = false,
|
||||||
DeviceInterfaceSupport = deviceInterfaces
|
DeviceInterfaceSupport = deviceInterfaces
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Token { get; private set; }
|
public string Token { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The URL token key used to connect (from UiClientContexts dictionary key)
|
||||||
|
/// </summary>
|
||||||
|
public string TokenKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Touchpanel Key associated with this client
|
/// Touchpanel Key associated with this client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -41,6 +46,11 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public MobileControlSystemController Controller { get; set; }
|
public MobileControlSystemController Controller { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the server instance for client registration
|
||||||
|
/// </summary>
|
||||||
|
public MobileControlWebsocketServer Server { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the room key that this client is associated with
|
/// Gets or sets the room key that this client is associated with
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -99,6 +109,50 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
Log.Output = (data, message) => Utilities.ConvertWebsocketLog(data, message, this);
|
Log.Output = (data, message) => Utilities.ConvertWebsocketLog(data, message, this);
|
||||||
Log.Level = LogLevel.Trace;
|
Log.Level = LogLevel.Trace;
|
||||||
|
|
||||||
|
// Get clientId from query parameter
|
||||||
|
var queryString = Context.QueryString;
|
||||||
|
var clientId = queryString["clientId"];
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(clientId))
|
||||||
|
{
|
||||||
|
// New behavior: Validate and register with the server using provided clientId
|
||||||
|
if (Server == null || !Server.RegisterUiClient(this, clientId, TokenKey))
|
||||||
|
{
|
||||||
|
this.LogError("Failed to register client with ID {clientId}. Invalid or expired registration.", clientId);
|
||||||
|
Context.WebSocket.Close(CloseStatusCode.PolicyViolation, "Invalid or expired clientId");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update this client's ID to the validated one
|
||||||
|
Id = clientId;
|
||||||
|
Key = $"uiclient-{TokenKey}-{RoomKey}-{clientId}";
|
||||||
|
|
||||||
|
this.LogInformation("Client {clientId} successfully connected and registered (new flow)", clientId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Legacy behavior: Use clientId from Token.Id (generated in HandleJoinRequest)
|
||||||
|
this.LogInformation("Client connected without clientId query parameter. Using legacy registration flow.");
|
||||||
|
|
||||||
|
// Id is already set from Token in constructor, use it
|
||||||
|
if (string.IsNullOrEmpty(Id))
|
||||||
|
{
|
||||||
|
this.LogError("Legacy client has no ID from token. Connection will be closed.");
|
||||||
|
Context.WebSocket.Close(CloseStatusCode.PolicyViolation, "No client ID available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Key = $"uiclient-{TokenKey}-{RoomKey}-{Id}";
|
||||||
|
|
||||||
|
// Register directly to active clients (legacy flow)
|
||||||
|
if (Server != null)
|
||||||
|
{
|
||||||
|
Server.RegisterLegacyUiClient(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.LogInformation("Client {clientId} registered using legacy flow", Id);
|
||||||
|
}
|
||||||
|
|
||||||
if (Controller == null)
|
if (Controller == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");
|
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -92,12 +93,16 @@ namespace PepperDash.Essentials
|
|||||||
|
|
||||||
CrestronConsole.AddNewConsoleCommand(s => Debug.LogMessage(LogEventLevel.Information, "CONSOLE MESSAGE: {0}", s), "appdebugmessage", "Writes message to log", ConsoleAccessLevelEnum.AccessOperator);
|
CrestronConsole.AddNewConsoleCommand(s => Debug.LogMessage(LogEventLevel.Information, "CONSOLE MESSAGE: {0}", s), "appdebugmessage", "Writes message to log", ConsoleAccessLevelEnum.AccessOperator);
|
||||||
|
|
||||||
CrestronConsole.AddNewConsoleCommand(s =>
|
CrestronConsole.AddNewConsoleCommand(ListTieLines,
|
||||||
{
|
"listtielines", "Prints out all tie lines. Usage: listtielines [signaltype]", ConsoleAccessLevelEnum.AccessOperator);
|
||||||
foreach (var tl in TieLineCollection.Default)
|
|
||||||
CrestronConsole.ConsoleCommandResponse(" {0}{1}", tl, CrestronEnvironment.NewLine);
|
CrestronConsole.AddNewConsoleCommand(VisualizeRoutes, "visualizeroutes",
|
||||||
},
|
"Visualizes routes by signal type",
|
||||||
"listtielines", "Prints out all tie lines", ConsoleAccessLevelEnum.AccessOperator);
|
ConsoleAccessLevelEnum.AccessOperator);
|
||||||
|
|
||||||
|
CrestronConsole.AddNewConsoleCommand(VisualizeCurrentRoutes, "visualizecurrentroutes",
|
||||||
|
"Visualizes current active routes from DefaultCollection",
|
||||||
|
ConsoleAccessLevelEnum.AccessOperator);
|
||||||
|
|
||||||
CrestronConsole.AddNewConsoleCommand(s =>
|
CrestronConsole.AddNewConsoleCommand(s =>
|
||||||
{
|
{
|
||||||
@@ -443,6 +448,282 @@ namespace PepperDash.Essentials
|
|||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, "All Tie Lines Loaded.");
|
Debug.LogMessage(LogEventLevel.Information, "All Tie Lines Loaded.");
|
||||||
|
|
||||||
|
Extensions.MapDestinationsToSources();
|
||||||
|
|
||||||
|
Debug.LogMessage(LogEventLevel.Information, "All Routes Mapped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Visualizes routes in a tree format for better understanding of signal paths
|
||||||
|
/// </summary>
|
||||||
|
private void ListTieLines(string args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (args.Contains("?"))
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("Usage: listtielines [signaltype]\r\n");
|
||||||
|
CrestronConsole.ConsoleCommandResponse("Signal types: Audio, Video, SecondaryAudio, AudioVideo, UsbInput, UsbOutput\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
eRoutingSignalType? signalTypeFilter = null;
|
||||||
|
if (!string.IsNullOrEmpty(args))
|
||||||
|
{
|
||||||
|
eRoutingSignalType parsedType;
|
||||||
|
if (Enum.TryParse(args.Trim(), true, out parsedType))
|
||||||
|
{
|
||||||
|
signalTypeFilter = parsedType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("Invalid signal type: {0}\r\n", args.Trim());
|
||||||
|
CrestronConsole.ConsoleCommandResponse("Valid types: Audio, Video, SecondaryAudio, AudioVideo, UsbInput, UsbOutput\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tielines = signalTypeFilter.HasValue
|
||||||
|
? TieLineCollection.Default.Where(tl => tl.Type.HasFlag(signalTypeFilter.Value))
|
||||||
|
: TieLineCollection.Default;
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
foreach (var tl in tielines)
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse(" {0}{1}", tl, CrestronEnvironment.NewLine);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
CrestronConsole.ConsoleCommandResponse("\r\nTotal: {0} tieline{1}{2}", count, count == 1 ? "" : "s", CrestronEnvironment.NewLine);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("Error listing tielines: {0}\r\n", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VisualizeRoutes(string args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (args.Contains("?"))
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("Usage: visualizeroutes [signaltype] [-s source] [-d destination]\r\n");
|
||||||
|
CrestronConsole.ConsoleCommandResponse(" signaltype: Audio, Video, AudioVideo, etc.\r\n");
|
||||||
|
CrestronConsole.ConsoleCommandResponse(" -s: Filter by source key (partial match)\r\n");
|
||||||
|
CrestronConsole.ConsoleCommandResponse(" -d: Filter by destination key (partial match)\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseRouteFilters(args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter);
|
||||||
|
|
||||||
|
CrestronConsole.ConsoleCommandResponse("\r\n+===========================================================================+\r\n");
|
||||||
|
CrestronConsole.ConsoleCommandResponse("| ROUTE VISUALIZATION |\r\n");
|
||||||
|
CrestronConsole.ConsoleCommandResponse("+===========================================================================+\r\n\r\n");
|
||||||
|
|
||||||
|
foreach (var descriptorCollection in Extensions.RouteDescriptors.Where(kv => kv.Value.Descriptors.Count() > 0))
|
||||||
|
{
|
||||||
|
// Filter by signal type if specified
|
||||||
|
if (signalTypeFilter.HasValue && descriptorCollection.Key != signalTypeFilter.Value)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CrestronConsole.ConsoleCommandResponse("\r\n+--- Signal Type: {0} ({1} routes) ---\r\n",
|
||||||
|
descriptorCollection.Key,
|
||||||
|
descriptorCollection.Value.Descriptors.Count());
|
||||||
|
|
||||||
|
foreach (var descriptor in descriptorCollection.Value.Descriptors)
|
||||||
|
{
|
||||||
|
// Filter by source/dest if specified
|
||||||
|
if (sourceFilter != null && !descriptor.Source.Key.ToLower().Contains(sourceFilter))
|
||||||
|
continue;
|
||||||
|
if (destFilter != null && !descriptor.Destination.Key.ToLower().Contains(destFilter))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
VisualizeRouteDescriptor(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CrestronConsole.ConsoleCommandResponse("\r\n");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("Error visualizing routes: {0}\r\n", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VisualizeCurrentRoutes(string args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (args.Contains("?"))
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("Usage: visualizecurrentroutes [signaltype] [-s source] [-d destination]\r\n");
|
||||||
|
CrestronConsole.ConsoleCommandResponse(" signaltype: Audio, Video, AudioVideo, etc.\r\n");
|
||||||
|
CrestronConsole.ConsoleCommandResponse(" -s: Filter by source key (partial match)\r\n");
|
||||||
|
CrestronConsole.ConsoleCommandResponse(" -d: Filter by destination key (partial match)\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseRouteFilters(args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter);
|
||||||
|
|
||||||
|
CrestronConsole.ConsoleCommandResponse("\r\n+===========================================================================+\r\n");
|
||||||
|
CrestronConsole.ConsoleCommandResponse("| CURRENT ROUTES VISUALIZATION |\r\n");
|
||||||
|
CrestronConsole.ConsoleCommandResponse("+===========================================================================+\r\n\r\n");
|
||||||
|
|
||||||
|
var hasRoutes = false;
|
||||||
|
|
||||||
|
// Get all descriptors from DefaultCollection
|
||||||
|
var allDescriptors = RouteDescriptorCollection.DefaultCollection.Descriptors;
|
||||||
|
|
||||||
|
// Group by signal type
|
||||||
|
var groupedByType = allDescriptors.GroupBy(d => d.SignalType);
|
||||||
|
|
||||||
|
foreach (var group in groupedByType)
|
||||||
|
{
|
||||||
|
var signalType = group.Key;
|
||||||
|
|
||||||
|
// Filter by signal type if specified
|
||||||
|
if (signalTypeFilter.HasValue && signalType != signalTypeFilter.Value)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var filteredDescriptors = group.Where(d =>
|
||||||
|
{
|
||||||
|
if (sourceFilter != null && !d.Source.Key.ToLower().Contains(sourceFilter))
|
||||||
|
return false;
|
||||||
|
if (destFilter != null && !d.Destination.Key.ToLower().Contains(destFilter))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
if (filteredDescriptors.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
hasRoutes = true;
|
||||||
|
CrestronConsole.ConsoleCommandResponse("\r\n+--- Signal Type: {0} ({1} routes) ---\r\n",
|
||||||
|
signalType,
|
||||||
|
filteredDescriptors.Count);
|
||||||
|
|
||||||
|
foreach (var descriptor in filteredDescriptors)
|
||||||
|
{
|
||||||
|
VisualizeRouteDescriptor(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasRoutes)
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("\r\nNo active routes found in current state.\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
CrestronConsole.ConsoleCommandResponse("\r\n");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("Error visualizing current state: {0}\r\n", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses route filter arguments from command line
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">Command line arguments</param>
|
||||||
|
/// <param name="signalTypeFilter">Parsed signal type filter (if any)</param>
|
||||||
|
/// <param name="sourceFilter">Parsed source filter (if any)</param>
|
||||||
|
/// <param name="destFilter">Parsed destination filter (if any)</param>
|
||||||
|
private void ParseRouteFilters(string args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter)
|
||||||
|
{
|
||||||
|
signalTypeFilter = null;
|
||||||
|
sourceFilter = null;
|
||||||
|
destFilter = null;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(args))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var parts = args.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
for (int i = 0; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
var part = parts[i];
|
||||||
|
|
||||||
|
// Check for flags
|
||||||
|
if (part == "-s" && i + 1 < parts.Length)
|
||||||
|
{
|
||||||
|
sourceFilter = parts[++i].ToLower();
|
||||||
|
}
|
||||||
|
else if (part == "-d" && i + 1 < parts.Length)
|
||||||
|
{
|
||||||
|
destFilter = parts[++i].ToLower();
|
||||||
|
}
|
||||||
|
// Try to parse as signal type if not a flag and no signal type set yet
|
||||||
|
else if (!part.StartsWith("-") && !signalTypeFilter.HasValue)
|
||||||
|
{
|
||||||
|
if (Enum.TryParse(part, true, out eRoutingSignalType parsedType))
|
||||||
|
{
|
||||||
|
signalTypeFilter = parsedType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Visualizes a single route descriptor in a tree format
|
||||||
|
/// </summary>
|
||||||
|
private void VisualizeRouteDescriptor(RouteDescriptor descriptor)
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("|\r\n");
|
||||||
|
CrestronConsole.ConsoleCommandResponse("|-- {0} --> {1}\r\n",
|
||||||
|
descriptor.Source.Key,
|
||||||
|
descriptor.Destination.Key);
|
||||||
|
|
||||||
|
if (descriptor.Routes == null || descriptor.Routes.Count == 0)
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("| +-- (No switching steps)\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < descriptor.Routes.Count; i++)
|
||||||
|
{
|
||||||
|
var route = descriptor.Routes[i];
|
||||||
|
var isLast = i == descriptor.Routes.Count - 1;
|
||||||
|
var prefix = isLast ? "+" : "|";
|
||||||
|
var continuation = isLast ? " " : "|";
|
||||||
|
|
||||||
|
if (route.SwitchingDevice != null)
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("| {0}-- [{1}] {2}\r\n",
|
||||||
|
prefix,
|
||||||
|
route.SwitchingDevice.Key,
|
||||||
|
GetSwitchDescription(route));
|
||||||
|
|
||||||
|
// Add visual connection line for non-last items
|
||||||
|
if (!isLast)
|
||||||
|
CrestronConsole.ConsoleCommandResponse("| {0} |\r\n", continuation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("| {0}-- {1}\r\n", prefix, route.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a readable description of the switching operation
|
||||||
|
/// </summary>
|
||||||
|
private string GetSwitchDescription(RouteSwitchDescriptor route)
|
||||||
|
{
|
||||||
|
if (route.OutputPort != null && route.InputPort != null)
|
||||||
|
{
|
||||||
|
return string.Format("{0} -> {1}", route.OutputPort.Key, route.InputPort.Key);
|
||||||
|
}
|
||||||
|
else if (route.InputPort != null)
|
||||||
|
{
|
||||||
|
return string.Format("-> {0}", route.InputPort.Key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "(passthrough)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -662,6 +943,7 @@ namespace PepperDash.Essentials
|
|||||||
|
|
||||||
if (jsonFiles.Length > 1)
|
if (jsonFiles.Length > 1)
|
||||||
{
|
{
|
||||||
|
Debug.LogError("Multiple configuration files found in application directory: {@jsonFiles}", jsonFiles.Select(f => f.FullName).ToArray());
|
||||||
throw new Exception("Multiple configuration files found. Cannot continue.");
|
throw new Exception("Multiple configuration files found. Cannot continue.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user