Compare commits

...

46 Commits

Author SHA1 Message Date
Neil Dorin
7c8f0586e6 Merge pull request #1369 from PepperDash/volume-messenger-issue 2025-12-16 17:15:46 -05:00
Andrew Welker
c5b0872a4c fix: DeviceVolumeMessenger only sends rawValue when device implements it 2025-12-16 15:41:33 -06:00
Andrew Welker
a7b88ec38d Merge pull request #1368 from PepperDash/generic-sink
generic sink
2025-12-16 11:09:40 -05:00
Andrew Welker
210b398a13 fix: implement PR Review suggestions 2025-12-16 10:04:01 -06:00
Nick Genovese
bce1e3610e fix: copy dictionaries
- fixed multiple enumeration exception
2025-12-10 16:58:41 -06:00
Andrew Welker
6a33e7c99d fix: initialize current sources dictionaries 2025-12-05 16:26:08 -06:00
Andrew Welker
2048e3f65d fix: GenericSink implements ICurrentSources 2025-12-05 16:26:02 -06:00
Neil Dorin
81df2738de Merge pull request #1363 from PepperDash/feature/add-on-off-dsp-preset-keys-to-room-config
feat: Add on/off dsp keys to EssentialsAvRoomPropertiesConfig
2025-11-26 18:00:18 -05:00
Neil Dorin
08fbec416f Update src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 15:53:45 -07:00
Neil Dorin
7594b22096 Update src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 15:53:39 -07:00
Neil Dorin
d1babf6b9b Update src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 15:53:31 -07:00
Neil Dorin
2187c9fb0d Update src/PepperDash.Essentials.Core/Devices/SourceListItem.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 15:53:22 -07:00
Neil Dorin
a5e6059160 Update src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 15:53:14 -07:00
Neil Dorin
9ef4aedcce Update src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 15:53:07 -07:00
Neil Dorin
f7c7160bf0 feat: Add on/off dsp keys to EssentialsAvRoomPropertiesConfig
clean up XML comments and improve property definitions in EssentialsRoomConfig
2025-11-26 15:48:14 -07:00
Neil Dorin
dbf5740841 Merge pull request #1362 from PepperDash/feature/add-IHasFeedback-to-IEssentialsRoomFusionController
fix: ensure proper disposal of help request timeout timer and improve…
2025-11-26 13:28:55 -05:00
Neil Dorin
c07e099a79 feat: add logging for help request timeout events in IEssentialsRoomFusionController 2025-11-26 11:10:21 -07:00
Neil Dorin
06cb508f3a Update src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 11:09:07 -07:00
Neil Dorin
e93b5b34cc Update src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 11:09:02 -07:00
Neil Dorin
4f5d4ef87a fix: ensure proper disposal of help request timeout timer and improve logging 2025-11-26 11:02:31 -07:00
Andrew Welker
636da8cc8c Merge pull request #1361 from PepperDash/feature/add-IHasFeedback-to-IEssentialsRoomFusionController
feat: add help request timeout functionality to IEssentialsRoomFusion…
2025-11-26 12:34:48 -05:00
Neil Dorin
5de1e2d7bb feat: add help request timeout functionality to IEssentialsRoomFusionController 2025-11-26 10:26:41 -07:00
Andrew Welker
03bbb84894 Merge pull request #1359 from PepperDash/feature/add-IHasFeedback-to-IEssentialsRoomFusionController
feat: implement IHasFeedback interface in IEssentialsRoomFusionContro…
2025-11-25 13:37:31 -05:00
Neil Dorin
d17394cdd7 feat: add logging to ExecuteSwitch method in GenericSink 2025-11-25 11:06:48 -07:00
Neil Dorin
8467afde38 Update src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-24 17:23:38 -07:00
Neil Dorin
5c016fb4b8 feat: implement IHasFeedback interface in IEssentialsRoomFusionController 2025-11-24 17:14:34 -07:00
Neil Dorin
2fbc32947c Merge pull request #1357 from PepperDash/url-parsing
fix: check for multiple URL patterns for both template & system URLS
2025-11-18 14:14:16 -05:00
Andrew Welker
c06b57a5f9 fix: check for multiple URL patterns for both template & system URLS 2025-11-18 12:30:54 -06:00
Neil Dorin
6d64fffc50 Merge pull request #1356 from PepperDash/mc-subscription-logging
Multiple Fixes
2025-11-14 15:44:58 -05:00
Andrew Welker
0c4ebdaf1d fix: change how subscription state is logged to reduce traffic to console 2025-11-13 09:56:29 -06:00
Andrew Welker
2c49fb9321 fix: parse current Portal URLS for system and template UUIDs correctly 2025-11-12 16:58:23 -06:00
Neil Dorin
c55de61da9 fix: enhance COM port registration logging and update GenericSink class for input switching 2025-11-05 14:43:42 -07:00
Andrew Welker
42444ede0a Merge pull request #1354 from PepperDash/comport-controller-updates
fix: improve logging for COM port registration and configuration
2025-11-04 10:55:44 -05:00
jkdevito
3d50f5f5ac fix: improve logging for COM port registration and configuration 2025-11-04 09:37:14 -06:00
Nick Genovese
11d62aebe1 Merge pull request #1353 from PepperDash/mc-subscription-concurrency
fix: make subscriber functionality thread-safe
2025-11-03 17:26:31 -04:00
Andrew Welker
edc10a9c2a fix: make subscriberIds private & check for add failure 2025-11-03 15:19:17 -06:00
Andrew Welker
9be5823956 Merge branch 'main' into mc-subscription-concurrency 2025-11-03 15:18:45 -06:00
Andrew Welker
35371dde22 fix: make subscriber functionality thread-safe 2025-11-03 15:13:48 -06:00
Jason DeVito
d3ceb4d7e7 Merge pull request #1352 from PepperDash/theme-saving
fix: use correct overload for PostStatusMessage
2025-11-03 15:08:28 -06:00
Andrew Welker
a782b57100 fix: use correct overload for PostStatusMessage 2025-11-03 14:00:51 -06:00
Neil Dorin
1360de599f Merge pull request #1351 from PepperDash/stream-debugging
fix: centralize debug printing into extension methods
2025-11-03 12:19:05 -05:00
Andrew Welker
fd95f5fed1 docs: update XML docs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-03 11:11:00 -06:00
Andrew Welker
9426dff5df fix: copilot suggestions from PR review 2025-11-03 11:02:39 -06:00
Andrew Welker
0d083e63c6 Merge commit '314570d6c3d78c7a92a362f3ec3a4a06bdbebd28' into stream-debugging 2025-11-03 10:56:53 -06:00
Andrew Welker
6ed7c96ec7 fix: centralize debug printing into extension methods
Stream debugging now uses CrestronConsole instead of debug methods, so that the debug statements will be printed regardless of console debug level. This also means that comm debug statements will NOT be in the Crestron Error log or in the files created by the logging system
2025-11-03 10:53:21 -06:00
Andrew Welker
314570d6c3 fix: change number of retained files to 7 instead of 30 for processors 2025-10-31 13:11:50 -05:00
26 changed files with 704 additions and 476 deletions

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

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
@@ -37,14 +36,14 @@ namespace PepperDash.Core
{
get
{
return _DebugTimeoutInMs/60000;
return _DebugTimeoutInMs / 60000;
}
}
/// <summary>
/// Gets or sets the RxStreamDebuggingIsEnabled
/// </summary>
public bool RxStreamDebuggingIsEnabled{ get; private set; }
public bool RxStreamDebuggingIsEnabled { get; private set; }
/// <summary>
/// Indicates that transmit stream debugging is enabled
@@ -108,7 +107,7 @@ namespace PepperDash.Core
TxStreamDebuggingIsEnabled = true;
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
}
/// <summary>
@@ -136,51 +135,4 @@ namespace PepperDash.Core
DebugExpiryPeriod = null;
}
}
/// <summary>
/// The available settings for stream debugging
/// </summary>
[Flags]
/// <summary>
/// Enumeration of eStreamDebuggingSetting values
/// </summary>
public enum eStreamDebuggingSetting
{
/// <summary>
/// Debug off
/// </summary>
Off = 0,
/// <summary>
/// Debug received data
/// </summary>
Rx = 1,
/// <summary>
/// Debug transmitted data
/// </summary>
Tx = 2,
/// <summary>
/// Debug both received and transmitted data
/// </summary>
Both = Rx | Tx
}
/// <summary>
/// The available settings for stream debugging response types
/// </summary>
[Flags]
public enum eStreamDebuggingDataTypeSettings
{
/// <summary>
/// Debug data in byte format
/// </summary>
Bytes = 0,
/// <summary>
/// Debug data in text format
/// </summary>
Text = 1,
/// <summary>
/// Debug data in both byte and text formats
/// </summary>
Both = Bytes | Text,
}
}

View File

@@ -278,7 +278,7 @@ namespace PepperDash.Core
if (shellStream.DataAvailable)
{
// empty the buffer if there is data
string str = shellStream.Read();
shellStream.Read();
}
shellStream.DataReceived += Stream_DataReceived;
this.LogInformation("Connected");
@@ -288,7 +288,6 @@ namespace PepperDash.Core
catch (SshConnectionException e)
{
var ie = e.InnerException; // The details are inside!!
var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
if (ie is SocketException)
{
@@ -335,7 +334,6 @@ namespace PepperDash.Core
}
catch (Exception e)
{
var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
this.LogError("Unhandled exception on connect: {error}", e.Message);
this.LogVerbose(e, "Exception details: ");
disconnectLogged = true;
@@ -439,18 +437,14 @@ namespace PepperDash.Core
if (bytesHandler != null)
{
var bytes = Encoding.UTF8.GetBytes(response);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
this.PrintReceivedBytes(bytes);
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
var textHandler = TextReceived;
if (textHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response));
this.PrintReceivedText(response);
textHandler(this, new GenericCommMethodReceiveTextArgs(response));
}
@@ -507,11 +501,7 @@ namespace PepperDash.Core
{
if (client != null && shellStream != null && IsConnected)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
this.LogInformation(
"Sending {length} characters of text: '{text}'",
text.Length,
ComTextHelper.GetDebugText(text));
this.PrintSentText(text);
shellStream.Write(text);
shellStream.Flush();
@@ -526,7 +516,7 @@ namespace PepperDash.Core
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
StopReconnectTimer();
StartReconnectTimer();
}
catch (Exception ex)
{
@@ -544,8 +534,7 @@ namespace PepperDash.Core
{
if (client != null && shellStream != null && IsConnected)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
this.PrintSentBytes(bytes);
shellStream.Write(bytes, 0, bytes.Length);
shellStream.Flush();
@@ -560,7 +549,7 @@ namespace PepperDash.Core
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
StopReconnectTimer();
StartReconnectTimer();
}
catch (Exception ex)
{

View File

@@ -426,10 +426,7 @@ namespace PepperDash.Core
var bytesHandler = BytesReceived;
if (bytesHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
this.PrintReceivedBytes(bytes);
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
var textHandler = TextReceived;
@@ -437,10 +434,7 @@ namespace PepperDash.Core
{
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
}
this.PrintReceivedText(str);
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
}
@@ -456,8 +450,7 @@ namespace PepperDash.Core
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
// Check debug level before processing byte array
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
this.PrintSentText(text);
if (_client != null)
_client.SendData(bytes, bytes.Length);
}
@@ -484,8 +477,7 @@ namespace PepperDash.Core
/// </summary>
public void SendBytes(byte[] bytes)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
this.PrintSentBytes(bytes);
if (_client != null)
_client.SendData(bytes, bytes.Length);
}

View File

@@ -124,7 +124,7 @@ namespace PepperDash.Core
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
}
/// <summary>
///
/// </summary>
@@ -135,7 +135,7 @@ namespace PepperDash.Core
public GenericUdpServer(string key, string address, int port, int bufferSize)
: base(key)
{
StreamDebugging = new CommunicationStreamDebugging(key);
StreamDebugging = new CommunicationStreamDebugging(key);
Hostname = address;
Port = port;
BufferSize = bufferSize;
@@ -180,7 +180,7 @@ namespace PepperDash.Core
/// <param name="programEventType"></param>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType != eProgramStatusEventType.Stopping)
if (programEventType != eProgramStatusEventType.Stopping)
return;
Debug.Console(1, this, "Program stopping. Disabling Server");
@@ -243,7 +243,7 @@ namespace PepperDash.Core
/// </summary>
public void Disconnect()
{
if(Server != null)
if (Server != null)
Server.DisableUDPServer();
IsConnected = false;
@@ -265,7 +265,7 @@ namespace PepperDash.Core
try
{
if (numBytes <= 0)
if (numBytes <= 0)
return;
var sourceIp = Server.IPAddressLastMessageReceivedFrom;
@@ -281,17 +281,13 @@ namespace PepperDash.Core
var bytesHandler = BytesReceived;
if (bytesHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
this.PrintReceivedBytes(bytes);
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
var textHandler = TextReceived;
if (textHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
this.PrintReceivedText(str);
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
}
}
@@ -318,8 +314,7 @@ namespace PepperDash.Core
if (IsConnected && Server != null)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
this.PrintSentText(text);
Server.SendData(bytes, bytes.Length);
}
@@ -334,8 +329,7 @@ namespace PepperDash.Core
/// </summary>
public void SendBytes(byte[] bytes)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
this.PrintSentBytes(bytes);
if (IsConnected && Server != null)
Server.SendData(bytes, bytes.Length);
@@ -343,11 +337,11 @@ namespace PepperDash.Core
}
/// <summary>
/// Represents a GenericUdpReceiveTextExtraArgs
/// </summary>
public class GenericUdpReceiveTextExtraArgs : EventArgs
{
/// <summary>
/// Represents a GenericUdpReceiveTextExtraArgs
/// </summary>
public class GenericUdpReceiveTextExtraArgs : EventArgs
{
/// <summary>
///
/// </summary>
@@ -359,7 +353,7 @@ namespace PepperDash.Core
/// <summary>
///
/// </summary>
public int Port { get; private set; }
public int Port { get; private set; }
/// <summary>
///
/// </summary>
@@ -373,18 +367,18 @@ namespace PepperDash.Core
/// <param name="port"></param>
/// <param name="bytes"></param>
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
{
Text = text;
IpAddress = ipAddress;
Port = port;
Bytes = bytes;
}
{
Text = text;
IpAddress = ipAddress;
Port = port;
Bytes = bytes;
}
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericUdpReceiveTextExtraArgs() { }
}
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericUdpReceiveTextExtraArgs() { }
}
/// <summary>
///

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

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

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

View File

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

View File

@@ -168,7 +168,7 @@ namespace PepperDash.Core
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
rollingInterval: RollingInterval.Day,
restrictedToMinimumLevel: LogEventLevel.Debug,
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 7 : 14,
levelSwitch: _fileLogLevelSwitch
);
@@ -1081,9 +1081,6 @@ namespace PepperDash.Core
/// Logs to Console when at-level, and all messages to error log
/// </summary>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
/// <summary>
/// Console method
/// </summary>
public static void Console(uint level, ErrorLogLevel errorLogLevel,
string format, params object[] items)
{
@@ -1096,9 +1093,6 @@ namespace PepperDash.Core
/// it will only be written to the log.
/// </summary>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
/// <summary>
/// ConsoleWithLog method
/// </summary>
public static void ConsoleWithLog(uint level, string format, params object[] items)
{
LogMessage(level, format, items);

View File

@@ -12,15 +12,15 @@ using Serilog.Events;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a CecPortController
/// </summary>
public class CecPortController : Device, IBasicCommunicationWithStreamDebugging
/// <summary>
/// Represents a CecPortController
/// </summary>
public class CecPortController : Device, IBasicCommunicationWithStreamDebugging
{
/// <summary>
/// Gets or sets the StreamDebugging
/// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; }
/// <summary>
/// Gets or sets the StreamDebugging
/// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; }
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
@@ -33,16 +33,16 @@ namespace PepperDash.Essentials.Core
ICec Port;
public CecPortController(string key, Func<EssentialsControlPropertiesConfig, ICec> postActivationFunc,
EssentialsControlPropertiesConfig config):base(key)
EssentialsControlPropertiesConfig config) : base(key)
{
StreamDebugging = new CommunicationStreamDebugging(key);
StreamDebugging = new CommunicationStreamDebugging(key);
AddPostActivationAction(() =>
{
Port = postActivationFunc(config);
Port.StreamCec.CecChange += StreamCec_CecChange;
});
});
}
public CecPortController(string key, ICec port)
@@ -58,27 +58,25 @@ namespace PepperDash.Essentials.Core
if (args.EventId == CecEventIds.CecMessageReceivedEventId)
OnDataReceived(cecDevice.Received.StringValue);
else if (args.EventId == CecEventIds.ErrorFeedbackEventId)
if(cecDevice.ErrorFeedback.BoolValue)
if (cecDevice.ErrorFeedback.BoolValue)
Debug.LogMessage(LogEventLevel.Verbose, this, "CEC NAK Error");
}
void OnDataReceived(string s)
{
var bytesHandler = BytesReceived;
var bytesHandler = BytesReceived;
if (bytesHandler != null)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(s);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes));
this.PrintReceivedBytes(bytes);
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
var textHandler = TextReceived;
if (textHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
}
if (textHandler != null)
{
this.PrintReceivedText(s);
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
}
}
#region IBasicCommunication Members
@@ -90,8 +88,7 @@ namespace PepperDash.Essentials.Core
{
if (Port == null)
return;
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text);
this.PrintSentText(text);
Port.StreamCec.Send.StringValue = text;
}
@@ -103,8 +100,8 @@ namespace PepperDash.Essentials.Core
if (Port == null)
return;
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
this.PrintSentBytes(bytes);
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
Port.StreamCec.Send.StringValue = text;
}

View File

@@ -1,7 +1,9 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.GeneralIO;
using PepperDash.Core;
using PepperDash.Core.Logging;
using Serilog.Events;
@@ -23,10 +25,10 @@ namespace PepperDash.Essentials.Core
/// Event fired when bytes are received
/// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Event fired when text is received
/// </summary>
/// Event fired when text is received
/// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary>
@@ -38,12 +40,12 @@ namespace PepperDash.Essentials.Core
ComPort.ComPortSpec Spec;
/// <summary>
/// Constructor
/// </summary>
/// <param name="key"></param>
/// <param name="postActivationFunc"></param>
/// <param name="spec"></param>
/// <param name="config"></param>
/// Constructor
/// </summary>
/// <param name="key"></param>
/// <param name="postActivationFunc"></param>
/// <param name="spec"></param>
/// <param name="config"></param>
public ComPortController(string key, Func<EssentialsControlPropertiesConfig, ComPort> postActivationFunc,
ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key)
{
@@ -85,61 +87,29 @@ namespace PepperDash.Essentials.Core
{
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;
}
// 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 = genericDevice.Register();
var result = Port.Register();
if (result != eDeviceRegistrationUnRegistrationResponse.Success)
{
this.LogError($"ERROR: Cannot register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
return; // false
this.LogError($"Cannot register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
return;
}
this.LogInformation($"Successfully registered {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
}
var specResult = Port.SetComPortSpec(Spec);
if (specResult != 0)
{
this.LogError($"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;
}
//this.LogInformation($"INFO: 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}'");
// };
// }
this.LogInformation($"Successfully set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
Port.SerialDataReceived += Port_SerialDataReceived;
}
@@ -165,16 +135,14 @@ namespace PepperDash.Essentials.Core
if (bytesHandler != null)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(s);
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes));
this.PrintReceivedBytes(bytes);
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
eventSubscribed = true;
}
var textHandler = TextReceived;
if (textHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
this.PrintReceivedText(s);
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
eventSubscribed = true;
}
@@ -201,8 +169,7 @@ namespace PepperDash.Essentials.Core
if (Port == null)
return;
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text);
this.PrintSentText(text);
Port.Send(text);
}
@@ -214,8 +181,7 @@ namespace PepperDash.Essentials.Core
if (Port == null)
return;
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
this.PrintSentBytes(bytes);
Port.Send(text);
}

View File

@@ -11,11 +11,11 @@ using PepperDash.Core;
namespace PepperDash.Essentials.Core.Config
{
/// <summary>
/// Loads the ConfigObject from the file
/// </summary>
public class EssentialsConfig : BasicConfig
{
/// <summary>
/// Loads the ConfigObject from the file
/// </summary>
public class EssentialsConfig : BasicConfig
{
/// <summary>
/// Gets or sets the SystemUrl
/// </summary>
@@ -32,24 +32,33 @@ namespace PepperDash.Essentials.Core.Config
/// Gets the SystemUuid extracted from the SystemUrl
/// </summary>
[JsonProperty("systemUuid")]
public string SystemUuid
public string SystemUuid
{
get
{
if (string.IsNullOrEmpty(SystemUrl))
return "missing url";
string uuid;
if (SystemUrl.Contains("#"))
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
string uuid = result.Groups[1].Value;
return uuid;
} else
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*");
string uuid = result.Groups[1].Value;
return uuid;
if (string.IsNullOrEmpty(SystemUrl))
{
uuid = "missing url";
}
else if (SystemUrl.Contains("#"))
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
uuid = result.Groups[1].Value;
}
else if (SystemUrl.Contains("detail"))
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/detail\/(.*)\/.*");
uuid = result.Groups[1].Value;
}
else
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*");
uuid = result.Groups[1].Value;
}
return uuid;
}
}
@@ -57,24 +66,33 @@ namespace PepperDash.Essentials.Core.Config
/// Gets the TemplateUuid extracted from the TemplateUrl
/// </summary>
[JsonProperty("templateUuid")]
public string TemplateUuid
public string TemplateUuid
{
get
{
if (string.IsNullOrEmpty(TemplateUrl))
return "missing template url";
string uuid;
if (TemplateUrl.Contains("#"))
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
string uuid = result.Groups[1].Value;
return uuid;
} else
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*");
string uuid = result.Groups[2].Value;
return uuid;
if (string.IsNullOrEmpty(TemplateUrl))
{
uuid = "missing template url";
}
else if (TemplateUrl.Contains("#"))
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
uuid = result.Groups[1].Value;
}
else if (TemplateUrl.Contains("detail"))
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/detail\/(.*)\/system-template-versions\/detail\/(.*)\/.*");
uuid = result.Groups[2].Value;
}
else
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*");
uuid = result.Groups[2].Value;
}
return uuid;
}
}
@@ -97,7 +115,7 @@ namespace PepperDash.Essentials.Core.Config
{
Rooms = new List<DeviceConfig>();
}
}
}
/// <summary>
/// Represents version data for Essentials and its packages
@@ -147,7 +165,7 @@ namespace PepperDash.Essentials.Core.Config
/// Represents a SystemTemplateConfigs
/// </summary>
public class SystemTemplateConfigs
{
{
/// <summary>
/// Gets or sets the System
/// </summary>
@@ -157,5 +175,5 @@ namespace PepperDash.Essentials.Core.Config
/// Gets or sets the Template
/// </summary>
public EssentialsConfig Template { get; set; }
}
}
}

View File

@@ -77,9 +77,6 @@ namespace PepperDash.Essentials.Core
/// A name that will override the source's name on the UI
/// </summary>
[JsonProperty("name")]
/// <summary>
/// Gets or sets the Name
/// </summary>
public string Name { get; set; }
/// <summary>

View File

@@ -14,13 +14,14 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
namespace PepperDash.Essentials.Core.Fusion
{
/// <summary>
/// Represents a EssentialsHuddleSpaceFusionSystemControllerBase
/// </summary>
public class IEssentialsRoomFusionController : EssentialsDevice, IOccupancyStatusProvider, IFusionHelpRequest
public class IEssentialsRoomFusionController : EssentialsDevice, IOccupancyStatusProvider, IFusionHelpRequest, IHasFeedback
{
private IEssentialsRoomFusionControllerPropertiesConfig _config;
@@ -87,15 +88,17 @@ namespace PepperDash.Essentials.Core.Fusion
/// <inheritdoc />
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;
//StringSigData Model;
//StringSigData SerialNumber;
//StringSigData Uptime;
#endregion
/// <summary>
/// Gets whether to use a timer for help requests
/// </summary>
public bool UseHelpRequestTimer => _config.UseTimeoutForHelpRequests;
#region Processor Info Sigs
@@ -240,6 +243,19 @@ namespace PepperDash.Essentials.Core.Fusion
this.LogDebug("Occupancy setup complete");
HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.OutputSig.StringValue);
HelpRequestSentFeedback = new BoolFeedback("HelpRequestSent", () => _helpRequestSent);
HelpRequestStatusFeedback = new StringFeedback("HelpRequestStatus", () => _helpRequestStatus.ToString());
Feedbacks.Add(HelpRequestResponseFeedback);
Feedbacks.Add(HelpRequestSentFeedback);
Feedbacks.Add(HelpRequestStatusFeedback);
if (RoomOccupancyRemoteStringFeedback != null)
Feedbacks.Add(RoomOccupancyRemoteStringFeedback);
if (RoomIsOccupiedFeedback != null)
Feedbacks.Add(RoomIsOccupiedFeedback);
}
catch (Exception e)
{
@@ -303,10 +319,6 @@ namespace PepperDash.Essentials.Core.Fusion
FusionRVI.GenerateFileForAllFusionDevices();
HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.OutputSig.StringValue);
HelpRequestSentFeedback = new BoolFeedback("HelpRequestSent", () => _helpRequestSent);
HelpRequestStatusFeedback = new StringFeedback("HelpRequestStatus", () => _helpRequestStatus.ToString());
}
/// <summary>
@@ -339,6 +351,11 @@ namespace PepperDash.Essentials.Core.Fusion
#endregion
/// <inheritdoc />
public FeedbackCollection<Feedback> Feedbacks { get; private set; } = new FeedbackCollection<Feedback>();
/// <summary>
/// ScheduleChange event
/// </summary>
@@ -1772,7 +1789,7 @@ namespace PepperDash.Essentials.Core.Fusion
{
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);
this.LogDebug("Help message content: {0}", FusionRoom.Help.OutputSig.StringValue);
@@ -1791,7 +1808,7 @@ namespace PepperDash.Essentials.Core.Fusion
break;
case "Please call the helpdesk.":
// this.LogInformation("Please call the helpdesk.");
// _helpRequestStatus = eFusionHelpResponse.CallHelpDesk;
_helpRequestStatus = eFusionHelpResponse.CallHelpDesk;
break;
case "Please wait, I will reschedule your meeting to a different room.":
// this.LogInformation("Please wait, I will reschedule your meeting to a different room.",
@@ -1818,13 +1835,21 @@ namespace PepperDash.Essentials.Core.Fusion
_helpRequestStatus = eFusionHelpResponse.None;
}
if(_helpRequestStatus == eFusionHelpResponse.None)
if (_helpRequestStatus == eFusionHelpResponse.None)
{
_helpRequestSent = false;
HelpRequestSentFeedback.FireUpdate();
}
HelpRequestStatusFeedback.FireUpdate();
if (_helpRequestTimeoutTimer != null)
{
_helpRequestTimeoutTimer.Stop();
_helpRequestTimeoutTimer.Elapsed -= OnTimedEvent;
_helpRequestTimeoutTimer.Dispose();
_helpRequestTimeoutTimer = null;
}
}
@@ -1895,10 +1920,34 @@ namespace PepperDash.Essentials.Core.Fusion
_helpRequestSent = true;
HelpRequestSentFeedback.FireUpdate();
if (UseHelpRequestTimer)
{
if (_helpRequestTimeoutTimer == null)
{
_helpRequestTimeoutTimer = new Timer(HelpRequestTimeoutMs);
_helpRequestTimeoutTimer.AutoReset = false;
_helpRequestTimeoutTimer.Enabled = true;
_helpRequestTimeoutTimer.Elapsed += OnTimedEvent;
}
_helpRequestTimeoutTimer.Interval = HelpRequestTimeoutMs;
_helpRequestTimeoutTimer.Start();
this.LogDebug("Help request timeout timer started for room '{0}' with timeout of {1} ms.",
Room.Name, HelpRequestTimeoutMs);
}
_helpRequestStatus = eFusionHelpResponse.HelpRequested;
HelpRequestStatusFeedback.FireUpdate();
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
this.LogInformation("Help request timeout reached for room '{0}'. Cancelling help request.", Room.Name);
CancelHelpRequest();
}
/// <inheritdoc />
public void CancelHelpRequest()
{
@@ -1909,7 +1958,16 @@ namespace PepperDash.Essentials.Core.Fusion
HelpRequestSentFeedback.FireUpdate();
_helpRequestStatus = eFusionHelpResponse.None;
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);
}
}

View File

@@ -56,4 +56,16 @@ public class IEssentialsRoomFusionControllerPropertiesConfig
/// </summary>
[JsonProperty("use24HourTimeFormat")]
public bool Use24HourTimeFormat { get; set; } = false;
/// <summary>
/// Gets or sets whether to use a timeout for help requests
/// </summary>
[JsonProperty("useTimeoutForHelpRequests")]
public bool UseTimeoutForHelpRequests { get; set; } = false;
/// <summary>
/// Gets or sets the timeout duration for help requests in milliseconds
/// </summary>
[JsonProperty("helpRequestTimeoutMs")]
public int HelpRequestTimeoutMs { get; set; } = 30000;
}

View File

@@ -105,12 +105,21 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsRoomPropertiesConfig
{
/// <summary>
/// Gets or sets the Addresses
/// </summary>
[JsonProperty("addresses")]
public EssentialsRoomAddressPropertiesConfig Addresses { get; set; }
/// <summary>
/// Gets or sets the Description
/// </summary>
[JsonProperty("description")]
public string Description { get; set; }
/// <summary>
/// Gets or sets the Emergency
/// </summary>
[JsonProperty("emergency")]
public EssentialsRoomEmergencyConfig Emergency { get; set; }
@@ -226,11 +235,11 @@ namespace PepperDash.Essentials.Room.Config
/// Indicates if this room represents a combination of other rooms
/// </summary>
[JsonProperty("isRoomCombinationScenario")]
/// <summary>
/// Gets or sets the IsRoomCombinationScenario
/// </summary>
public bool IsRoomCombinationScenario { get; set; }
/// <summary>
/// Constructor
/// </summary>
public EssentialsRoomPropertiesConfig()
{
LogoLight = new EssentialsLogoPropertiesConfig();
@@ -243,10 +252,10 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsRoomUiBehaviorConfig
{
[JsonProperty("disableActivityButtonsWhileWarmingCooling")]
/// <summary>
/// Gets or sets the DisableActivityButtonsWhileWarmingCooling
/// </summary>
[JsonProperty("disableActivityButtonsWhileWarmingCooling")]
public bool DisableActivityButtonsWhileWarmingCooling { get; set; }
}
@@ -255,74 +264,86 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsAvRoomPropertiesConfig : EssentialsRoomPropertiesConfig
{
[JsonProperty("defaultAudioKey")]
/// <summary>
/// Gets or sets the DefaultAudioKey
/// </summary>
[JsonProperty("defaultAudioKey")]
public string DefaultAudioKey { get; set; }
[JsonProperty("sourceListKey")]
/// <summary>
/// Gets or sets the DefaultOnDspPresetKey
/// </summary>
[JsonProperty("defaultOnDspPresetKey")]
public string DefaultOnDspPresetKey { get; set; }
/// <summary>
/// Gets or sets the DefaultOffDspPresetKey
/// </summary>
[JsonProperty("defaultOffDspPresetKey")]
public string DefaultOffDspPresetKey { get; set; }
/// <summary>
/// Gets or sets the SourceListKey
/// </summary>
/// </summary>
[JsonProperty("sourceListKey")]
public string SourceListKey { get; set; }
[JsonProperty("destinationListKey")]
/// <summary>
/// Gets or sets the DestinationListKey
/// </summary>
[JsonProperty("destinationListKey")]
public string DestinationListKey { get; set; }
[JsonProperty("audioControlPointListKey")]
/// <summary>
/// Gets or sets the AudioControlPointListKey
/// </summary>
[JsonProperty("audioControlPointListKey")]
public string AudioControlPointListKey { get; set; }
[JsonProperty("cameraListKey")]
/// <summary>
/// Gets or sets the CameraListKey
/// </summary>
[JsonProperty("cameraListKey")]
public string CameraListKey { get; set; }
[JsonProperty("defaultSourceItem")]
/// <summary>
/// Gets or sets the DefaultSourceItem
/// </summary>
[JsonProperty("defaultSourceItem")]
public string DefaultSourceItem { get; set; }
/// <summary>
/// Indicates if the room supports advanced sharing
/// </summary>
[JsonProperty("supportsAdvancedSharing")]
/// <summary>
/// Gets or sets the SupportsAdvancedSharing
/// </summary>
public bool SupportsAdvancedSharing { get; set; }
/// <summary>
/// Indicates if non-tech users can change the share mode
/// </summary>
[JsonProperty("userCanChangeShareMode")]
/// <summary>
/// Gets or sets the UserCanChangeShareMode
/// </summary>
public bool UserCanChangeShareMode { get; set; }
[JsonProperty("matrixRoutingKey", NullValueHandling = NullValueHandling.Ignore)]
/// <summary>
/// Gets or sets the MatrixRoutingKey
/// </summary>
[JsonProperty("matrixRoutingKey", NullValueHandling = NullValueHandling.Ignore)]
public string MatrixRoutingKey { get; set; }
}
/// <summary>
/// Represents a EssentialsConferenceRoomPropertiesConfig
/// </summary>
public class EssentialsConferenceRoomPropertiesConfig : EssentialsAvRoomPropertiesConfig
{
[JsonProperty("videoCodecKey")]
/// <summary>
/// Gets or sets the VideoCodecKey
/// </summary>
[JsonProperty("videoCodecKey")]
public string VideoCodecKey { get; set; }
[JsonProperty("audioCodecKey")]
/// <summary>
/// Gets or sets the AudioCodecKey
/// </summary>
[JsonProperty("audioCodecKey")]
public string AudioCodecKey { get; set; }
}
@@ -337,12 +358,15 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public bool Enabled { get; set; }
[JsonProperty("deviceKeys")]
/// <summary>
/// Gets or sets the DeviceKeys
/// </summary>
[JsonProperty("deviceKeys")]
public List<string> DeviceKeys { get; set; }
/// <summary>
/// Constructor
/// </summary>
public EssentialsEnvironmentPropertiesConfig()
{
DeviceKeys = new List<string>();
@@ -355,6 +379,9 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsRoomFusionConfig
{
/// <summary>
/// Gets the the IpId as a UInt16
/// </summary>
public uint IpIdInt
{
get
@@ -371,16 +398,16 @@ namespace PepperDash.Essentials.Room.Config
}
}
[JsonProperty("ipId")]
/// <summary>
/// Gets or sets the IpId
/// </summary>
[JsonProperty("ipId")]
public string IpId { get; set; }
[JsonProperty("joinMapKey")]
/// <summary>
/// Gets or sets the JoinMapKey
/// </summary>
[JsonProperty("joinMapKey")]
public string JoinMapKey { get; set; }
}
@@ -390,16 +417,16 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsRoomMicrophonePrivacyConfig
{
[JsonProperty("deviceKey")]
/// <summary>
/// Gets or sets the DeviceKey
/// </summary>
[JsonProperty("deviceKey")]
public string DeviceKey { get; set; }
[JsonProperty("behaviour")]
/// <summary>
/// Gets or sets the Behaviour
/// </summary>
[JsonProperty("behaviour")]
public string Behaviour { get; set; }
}
@@ -408,12 +435,15 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsHelpPropertiesConfig
{
[JsonProperty("message")]
/// <summary>
/// Gets or sets the Message
/// </summary>
[JsonProperty("message")]
public string Message { get; set; }
/// <summary>
/// Gets or sets the ShowCallButton
/// </summary>
[JsonProperty("showCallButton")]
public bool ShowCallButton { get; set; }
@@ -421,11 +451,11 @@ namespace PepperDash.Essentials.Room.Config
/// Defaults to "Call Help Desk"
/// </summary>
[JsonProperty("callButtonText")]
/// <summary>
/// Gets or sets the CallButtonText
/// </summary>
public string CallButtonText { get; set; }
/// <summary>
/// Constructor
/// </summary>
public EssentialsHelpPropertiesConfig()
{
CallButtonText = "Call Help Desk";
@@ -437,22 +467,28 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsOneButtonMeetingPropertiesConfig
{
[JsonProperty("enable")]
/// <summary>
/// Gets or sets the Enable
/// </summary>
[JsonProperty("enable")]
public bool Enable { get; set; }
}
/// <summary>
/// Represents a EssentialsRoomAddressPropertiesConfig
/// </summary>
public class EssentialsRoomAddressPropertiesConfig
{
/// <summary>
/// Gets or sets the PhoneNumber
/// </summary>
[JsonProperty("phoneNumber")]
public string PhoneNumber { get; set; }
[JsonProperty("sipAddress")]
/// <summary>
/// Gets or sets the SipAddress
/// </summary>
[JsonProperty("sipAddress")]
public string SipAddress { get; set; }
}
@@ -462,14 +498,18 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsLogoPropertiesConfig
{
[JsonProperty("type")]
/// <summary>
/// Gets or sets the Type
/// </summary>
[JsonProperty("type")]
public string Type { get; set; }
/// <summary>
/// Gets or sets the Url
/// </summary>
[JsonProperty("url")]
public string Url { get; set; }
/// <summary>
/// GetLogoUrlLight method
/// </summary>
@@ -502,22 +542,28 @@ namespace PepperDash.Essentials.Room.Config
/// </summary>
public class EssentialsRoomOccSensorConfig
{
[JsonProperty("deviceKey")]
/// <summary>
/// Gets or sets the DeviceKey
/// </summary>
[JsonProperty("deviceKey")]
public string DeviceKey { get; set; }
/// <summary>
/// Gets or sets the TimeoutMinutes
/// </summary>
[JsonProperty("timeoutMinutes")]
public int TimeoutMinutes { get; set; }
}
/// <summary>
/// Represents a EssentialsRoomTechConfig
/// </summary>
public class EssentialsRoomTechConfig
{
[JsonProperty("password")]
/// <summary>
/// Gets or sets the Password
/// </summary>
[JsonProperty("password")]
public string Password { get; set; }
}
}

View File

@@ -1,11 +1,11 @@
using Crestron.SimplSharpPro.Keypads;
using PepperDash.Essentials.Core.Queues;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Crestron.SimplSharpPro.Keypads;
using PepperDash.Essentials.Core.Queues;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
using Debug = PepperDash.Core.Debug;
@@ -115,7 +115,7 @@ namespace PepperDash.Essentials.Core
public static (RouteDescriptor, RouteDescriptor) GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort, RoutingOutputPort sourcePort)
{
// if it's a single signal type, find the route
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo) &&
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo) &&
!(signalType.HasFlag(eRoutingSignalType.Video) && signalType.HasFlag(eRoutingSignalType.SecondaryAudio)))
{
var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, signalType);
@@ -134,14 +134,15 @@ namespace PepperDash.Essentials.Core
}
// otherwise, audioVideo needs to be handled as two steps.
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key);
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {destinationKey} to {sourceKey} of type {type}", destination, source.Key, signalType);
RouteDescriptor audioRouteDescriptor;
if (signalType.HasFlag(eRoutingSignalType.SecondaryAudio))
{
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.SecondaryAudio);
} else
}
else
{
audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Audio);
}
@@ -199,13 +200,13 @@ namespace PepperDash.Essentials.Core
Source = source,
SourcePort = sourcePort,
SignalType = signalType
};
};
var coolingDevice = destination as IWarmingCooling;
//We already have a route request for this device, and it's a cooling device and is cooling
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
{
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
@@ -219,7 +220,7 @@ namespace PepperDash.Essentials.Core
//New Request
if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
{
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
RouteRequests.Add(destination.Key, routeRequest);
@@ -239,9 +240,9 @@ namespace PepperDash.Essentials.Core
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
}
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty, false));
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, destinationPort?.Key ?? string.Empty, false));
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
}
/// <summary>
@@ -272,7 +273,8 @@ namespace PepperDash.Essentials.Core
audioOrSingleRoute.ExecuteRoutes();
videoRoute?.ExecuteRoutes();
} catch(Exception ex)
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request);
}
@@ -305,9 +307,10 @@ namespace PepperDash.Essentials.Core
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
current.ReleaseRoutes(clearRoute);
}
} catch (Exception ex)
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'",null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'", null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
}
}

View File

@@ -98,7 +98,7 @@ namespace PepperDash.Essentials.Core.Routing
/// <param name="inputPort">The currently selected input port on the destination device.</param>
private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort)
{
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this,destination?.Key, inputPort?.Key);
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this, destination?.Key, inputPort?.Key);
if(inputPort == null)
{

View File

@@ -6,9 +6,9 @@ using PepperDash.Core.Web.RequestHandlers;
namespace PepperDash.Essentials.Core.Web.RequestHandlers
{
/// <summary>
/// Represents a SetDeviceStreamDebugRequestHandler
/// </summary>
/// <summary>
/// Represents a SetDeviceStreamDebugRequestHandler
/// </summary>
public class SetDeviceStreamDebugRequestHandler : WebApiBaseRequestHandler
{
/// <summary>
@@ -122,23 +122,23 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
return;
}
if (!(DeviceManager.GetDeviceForKey(body.DeviceKey) is IStreamDebugging device))
{
context.Response.StatusCode = 404;
context.Response.StatusDescription = "Not Found";
context.Response.End();
if (!(DeviceManager.GetDeviceForKey(body.DeviceKey) is IStreamDebugging device))
{
context.Response.StatusCode = 404;
context.Response.StatusDescription = "Not Found";
context.Response.End();
return;
}
return;
}
eStreamDebuggingSetting debugSetting;
eStreamDebuggingSetting debugSetting;
try
{
debugSetting = (eStreamDebuggingSetting) Enum.Parse(typeof (eStreamDebuggingSetting), body.Setting, true);
debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), body.Setting, true);
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Exception handling set debug request");
Debug.LogMessage(ex, "Exception handling set debug request");
context.Response.StatusCode = 500;
context.Response.StatusDescription = "Internal Server Error";
context.Response.End();
@@ -164,7 +164,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Exception handling set debug request");
Debug.LogMessage(ex, "Exception handling set debug request");
context.Response.StatusCode = 500;
context.Response.StatusDescription = "Internal Server Error";
context.Response.End();
@@ -198,21 +198,21 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
public class SetDeviceStreamDebugConfig
{
[JsonProperty("deviceKey", NullValueHandling = NullValueHandling.Include)]
/// <summary>
/// Gets or sets the DeviceKey
/// </summary>
/// <summary>
/// Gets or sets the DeviceKey
/// </summary>
public string DeviceKey { get; set; }
[JsonProperty("setting", NullValueHandling = NullValueHandling.Include)]
/// <summary>
/// Gets or sets the Setting
/// </summary>
/// <summary>
/// Gets or sets the Setting
/// </summary>
public string Setting { get; set; }
[JsonProperty("timeout")]
/// <summary>
/// Gets or sets the Timeout
/// </summary>
/// <summary>
/// Gets or sets the Timeout
/// </summary>
public int Timeout { get; set; }
public SetDeviceStreamDebugConfig()

View File

@@ -1,7 +1,10 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Generic
@@ -9,8 +12,17 @@ namespace PepperDash.Essentials.Devices.Common.Generic
/// <summary>
/// Represents a GenericSink
/// </summary>
public class GenericSink : EssentialsDevice, IRoutingSinkWithInputPort
public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputPort, ICurrentSources
{
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; private set; }
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
/// <inheritdoc />
public event EventHandler CurrentSourcesChanged;
/// <summary>
/// Initializes a new instance of the GenericSink class
/// </summary>
@@ -20,9 +32,52 @@ namespace PepperDash.Essentials.Devices.Common.Generic
{
InputPorts = new RoutingPortCollection<RoutingInputPort>();
var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this);
var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo | eRoutingSignalType.SecondaryAudio, eRoutingPortConnectionType.Hdmi, null, this);
InputPorts.Add(inputPort);
CurrentSources = new Dictionary<eRoutingSignalType, SourceListItem>
{
{ eRoutingSignalType.Audio, null },
{ eRoutingSignalType.Video, null },
};
CurrentSourceKeys = new Dictionary<eRoutingSignalType, string>
{
{ eRoutingSignalType.Audio, string.Empty },
{ eRoutingSignalType.Video, string.Empty },
};
}
/// <inheritdoc />
public void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
{
foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType)))
{
var flagValue = Convert.ToInt32(type);
// Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag).
// (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set.
if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0)
{
this.LogDebug("Skipping {type}", type);
continue;
}
this.LogDebug("setting {type}", type);
if (signalType.HasFlag(type))
{
UpdateCurrentSources(type, sourceListKey, sourceListItem);
}
}
// Raise the CurrentSourcesChanged event
CurrentSourcesChanged?.Invoke(this, EventArgs.Empty);
}
private void UpdateCurrentSources(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
{
CurrentSources[signalType] = sourceListItem;
CurrentSourceKeys[signalType] = sourceListKey;
}
/// <summary>
@@ -66,6 +121,15 @@ namespace PepperDash.Essentials.Devices.Common.Generic
/// Event fired when the current source changes
/// </summary>
public event SourceInfoChangeHandler CurrentSourceChange;
/// <inheritdoc />
public event InputChangedEventHandler InputChanged;
/// <inheritdoc />
public void ExecuteSwitch(object inputSelector)
{
this.LogDebug("GenericSink Executing Switch to: {inputSelector}", inputSelector);
}
}
/// <summary>

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
@@ -39,10 +40,14 @@ namespace PepperDash.Essentials.AppServer.Messengers
sourceDevice.CurrentSourcesChanged += (sender, e) =>
{
// need to copy the dictionaries to avoid enumeration issues
var currentSourceKeys = sourceDevice.CurrentSourceKeys.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
var currentSources = sourceDevice.CurrentSources.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
PostStatusMessage(JToken.FromObject(new
{
currentSourceKeys = sourceDevice.CurrentSourceKeys,
currentSources = sourceDevice.CurrentSources
currentSourceKeys,
currentSources,
}));
};
}

View File

@@ -130,34 +130,33 @@ namespace PepperDash.Essentials.AppServer.Messengers
feedback.MuteFeedback.OutputChange += (sender, args) =>
{
PostStatusMessage(JToken.FromObject(
new
{
volume = new
{
muted = args.BoolValue
}
})
);
var message = new VolumeStateMessage
{
Volume = new Volume
{
Muted = args.BoolValue
}
};
PostStatusMessage(JToken.FromObject(message));
};
feedback.VolumeLevelFeedback.OutputChange += (sender, args) =>
{
var rawValue = "";
if (feedback is IBasicVolumeWithFeedbackAdvanced volumeAdvanced)
var message = new VolumeStateMessage
{
rawValue = volumeAdvanced.RawVolumeLevel.ToString();
}
var message = new
{
volume = new
Volume = new Volume
{
level = args.IntValue,
rawValue
Level = args.IntValue,
}
};
if (device is IBasicVolumeWithFeedbackAdvanced volumeAdvanced)
{
message.Volume.RawValue = volumeAdvanced.RawVolumeLevel.ToString();
message.Volume.Units = volumeAdvanced.Units;
}
PostStatusMessage(JToken.FromObject(message));
};
}

View File

@@ -31,7 +31,12 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// <remarks>
/// Unsoliciited feedback from a device in a messenger will ONLY be sent to devices in this subscription list. When a client disconnects, it's ID will be removed from the collection.
/// </remarks>
protected HashSet<string> SubscriberIds = new HashSet<string>();
private readonly HashSet<string> subscriberIds = new HashSet<string>();
/// <summary>
/// Lock object for thread-safe access to SubscriberIds
/// </summary>
private readonly object _subscriberLock = new object();
private readonly List<string> _deviceInterfaces;
@@ -189,18 +194,18 @@ namespace PepperDash.Essentials.AppServer.Messengers
{
if (!enableMessengerSubscriptions)
{
this.LogWarning("Messenger subscriptions not enabled");
return;
}
if (SubscriberIds.Any(id => id == clientId))
lock (_subscriberLock)
{
this.LogVerbose("Client {clientId} already subscribed", clientId);
return;
if (!subscriberIds.Add(clientId))
{
this.LogVerbose("Client {clientId} already subscribed", clientId);
return;
}
}
SubscriberIds.Add(clientId);
this.LogDebug("Client {clientId} subscribed", clientId);
}
@@ -212,19 +217,26 @@ namespace PepperDash.Essentials.AppServer.Messengers
{
if (!enableMessengerSubscriptions)
{
this.LogWarning("Messenger subscriptions not enabled");
return;
}
if (!SubscriberIds.Any(i => i == clientId))
bool wasSubscribed;
lock (_subscriberLock)
{
wasSubscribed = subscriberIds.Contains(clientId);
if (wasSubscribed)
{
subscriberIds.Remove(clientId);
}
}
if (!wasSubscribed)
{
this.LogVerbose("Client with ID {clientId} is not subscribed", clientId);
return;
}
SubscriberIds.RemoveWhere((i) => i == clientId);
this.LogInformation("Client with ID {clientId} unsubscribed", clientId);
this.LogDebug("Client with ID {clientId} unsubscribed", clientId);
}
/// <summary>
@@ -258,7 +270,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
}
catch (Exception ex)
{
this.LogError(ex, "Exception posting status message for {messagePath} to {clientId}", MessagePath, clientId ?? "all clients");
this.LogError("Exception posting status message for {messagePath} to {clientId}: {message}", MessagePath, clientId ?? "all clients", ex.Message);
this.LogDebug(ex, "Stack trace: ");
}
}
@@ -287,7 +300,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
}
catch (Exception ex)
{
this.LogError(ex, "Exception posting status message for {type} to {clientId}", type, clientId ?? "all clients");
this.LogError("Exception posting status message for {type} to {clientId}: {message}", type, clientId ?? "all clients", ex.Message);
this.LogDebug(ex, "Stack trace: ");
}
}
@@ -312,7 +326,14 @@ namespace PepperDash.Essentials.AppServer.Messengers
// If client is null or empty, this message is unsolicited feedback. Iterate through the subscriber list and send to all interested parties
if (string.IsNullOrEmpty(clientId))
{
foreach (var client in SubscriberIds)
// Create a snapshot of subscribers to avoid collection modification during iteration
List<string> subscriberSnapshot;
lock (_subscriberLock)
{
subscriberSnapshot = new List<string>(subscriberIds);
}
foreach (var client in subscriberSnapshot)
{
AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = client, Content = content });
}

View File

@@ -1312,6 +1312,11 @@ namespace PepperDash.Essentials
/// <inheritdoc />
public override void Initialize()
{
if (!Config.EnableMessengerSubscriptions)
{
this.LogWarning("Messenger subscriptions disabled. add \"enableMessengerSubscriptions\": true to config for {key} to enable.", Key);
}
foreach (var messenger in _messengers)
{
try

View File

@@ -40,7 +40,7 @@ namespace PepperDash.Essentials.Touchpanel
this.LogInformation("Setting theme to {theme}", theme.Value);
_tpDevice.UpdateTheme(theme.Value);
PostStatusMessage(JToken.FromObject(new { theme = theme.Value }), id);
PostStatusMessage(JToken.FromObject(new { theme = theme.Value }), clientId: id);
});
}
}