From e1e32cea6ff51bf110e53f18a458bcb5fac8859c Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Wed, 6 Aug 2025 08:55:08 -0500 Subject: [PATCH] fix: use correct line endings for verbatim strings --- Crestron-Library-Usage-Analysis.md | 0 src/PepperDash.Core/Logging/Debug.cs | 457 +++++++++--------- .../Devices/DeviceManager.cs | 36 +- .../Factory/DeviceFactory.cs | 240 ++++----- .../MobileControlSystemController.cs | 90 ++-- 5 files changed, 407 insertions(+), 416 deletions(-) create mode 100644 Crestron-Library-Usage-Analysis.md diff --git a/Crestron-Library-Usage-Analysis.md b/Crestron-Library-Usage-Analysis.md new file mode 100644 index 00000000..e69de29b diff --git a/src/PepperDash.Core/Logging/Debug.cs b/src/PepperDash.Core/Logging/Debug.cs index 9a405b28..beb14d67 100644 --- a/src/PepperDash.Core/Logging/Debug.cs +++ b/src/PepperDash.Core/Logging/Debug.cs @@ -1,4 +1,8 @@ -using Crestron.SimplSharp; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text.RegularExpressions; +using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronDataStore; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronLogger; @@ -11,10 +15,6 @@ using Serilog.Events; using Serilog.Formatting.Compact; using Serilog.Formatting.Json; using Serilog.Templates; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text.RegularExpressions; namespace PepperDash.Core; @@ -97,19 +97,19 @@ public static class Debug /// /// Version for the currently loaded PepperDashCore dll /// - public static string PepperDashCoreVersion { get; private set; } + public static string PepperDashCoreVersion { get; private set; } private static CTimer _saveTimer; - /// - /// When true, the IncludedExcludedKeys dict will contain keys to include. - /// When false (default), IncludedExcludedKeys will contain keys to exclude. - /// - private static bool _excludeAllMode; + /// + /// When true, the IncludedExcludedKeys dict will contain keys to include. + /// When false (default), IncludedExcludedKeys will contain keys to exclude. + /// + private static bool _excludeAllMode; - //static bool ExcludeNoKeyMessages; + //static bool ExcludeNoKeyMessages; - private static readonly Dictionary IncludedExcludedKeys; + private static readonly Dictionary IncludedExcludedKeys; private static readonly LoggerConfiguration _defaultLoggerConfiguration; @@ -119,120 +119,120 @@ public static class Debug static Debug() { - try - { - CrestronDataStoreStatic.InitCrestronDataStore(); - - var defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey); - - var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey); - - var defaultErrorLogLevel = GetStoredLogEventLevel(ErrorLogLevelStoreKey); - - var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey); - - _consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel); - - _websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel); - - _errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel); - - _fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel); - - _websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true)); - - var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? - $@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" : - $@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}room{InitialParametersClass.RoomId}{Path.DirectorySeparatorChar}global-log.log"; - - CrestronConsole.PrintLine($"Saving log files to {logFilePath}"); - - var errorLogTemplate = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance - ? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}" - : "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"; - - _defaultLoggerConfiguration = new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.FromLogContext() - .Enrich.With(new CrestronEnricher()) - .WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLoggingLevelSwitch) - .WriteTo.Sink(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch) - .WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch) - .WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath, - rollingInterval: RollingInterval.Day, - restrictedToMinimumLevel: LogEventLevel.Debug, - retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60, - levelSwitch: _fileLevelSwitch - ); - - // Instantiate the root logger - _loggerConfiguration = _defaultLoggerConfiguration; - - _logger = _loggerConfiguration.CreateLogger(); - // Get the assembly version and print it to console and the log - GetVersion(); - - string msg = $"[App {InitialParametersClass.ApplicationNumber}] Using PepperDash_Core v{PepperDashCoreVersion}"; - - if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) - { - msg = $"[Room {InitialParametersClass.RoomId}] Using PepperDash_Core v{PepperDashCoreVersion}"; - } - - CrestronConsole.PrintLine(msg); - - LogMessage(LogEventLevel.Information, msg); - - IncludedExcludedKeys = new Dictionary(); - - if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro) - { - // Add command to console - CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot", - "donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug", - "appdebug:P [0-5]: Sets the application's console debug message level", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog", - "appdebuglog:P [all] Use \"all\" for full log.", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear", - "appdebugclear:P Clears the current custom log", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter", - "appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator); - } - - CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; - - DoNotLoadConfigOnNextBoot = GetDoNotLoadOnNextBoot(); - - if (DoNotLoadConfigOnNextBoot) - CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber)); - - _consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) => - { - LogMessage(LogEventLevel.Information, "Console debug level set to {minimumLevel}", _consoleLoggingLevelSwitch.MinimumLevel); - }; - } + try + { + CrestronDataStoreStatic.InitCrestronDataStore(); + + var defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey); + + var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey); + + var defaultErrorLogLevel = GetStoredLogEventLevel(ErrorLogLevelStoreKey); + + var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey); + + _consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel); + + _websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel); + + _errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel); + + _fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel); + + _websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true)); + + var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? + $@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" : + $@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}room{InitialParametersClass.RoomId}{Path.DirectorySeparatorChar}global-log.log"; + + CrestronConsole.PrintLine($"Saving log files to {logFilePath}"); + + var errorLogTemplate = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance + ? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}" + : "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"; + + _defaultLoggerConfiguration = new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.FromLogContext() + .Enrich.With(new CrestronEnricher()) + .WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLoggingLevelSwitch) + .WriteTo.Sink(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch) + .WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch) + .WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath, + rollingInterval: RollingInterval.Day, + restrictedToMinimumLevel: LogEventLevel.Debug, + retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60, + levelSwitch: _fileLevelSwitch + ); + + // Instantiate the root logger + _loggerConfiguration = _defaultLoggerConfiguration; + + _logger = _loggerConfiguration.CreateLogger(); + // Get the assembly version and print it to console and the log + GetVersion(); + + string msg = $"[App {InitialParametersClass.ApplicationNumber}] Using PepperDash_Core v{PepperDashCoreVersion}"; + + if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) + { + msg = $"[Room {InitialParametersClass.RoomId}] Using PepperDash_Core v{PepperDashCoreVersion}"; + } + + CrestronConsole.PrintLine(msg); + + LogMessage(LogEventLevel.Information, msg); + + IncludedExcludedKeys = new Dictionary(); + + if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro) + { + // Add command to console + CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot", + "donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug", + "appdebug:P [0-5]: Sets the application's console debug message level", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog", + "appdebuglog:P [all] Use \"all\" for full log.", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear", + "appdebugclear:P Clears the current custom log", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter", + "appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator); + } + + CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; + + DoNotLoadConfigOnNextBoot = GetDoNotLoadOnNextBoot(); + + if (DoNotLoadConfigOnNextBoot) + CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber)); + + _consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) => + { + LogMessage(LogEventLevel.Information, "Console debug level set to {minimumLevel}", _consoleLoggingLevelSwitch.MinimumLevel); + }; + } catch (Exception ex) { LogError(ex, "Exception in Debug static constructor: {message}", ex.Message); } } - private static bool GetDoNotLoadOnNextBoot() - { - var err = CrestronDataStoreStatic.GetLocalBoolValue(DoNotLoadOnNextBootKey, out var doNotLoad); - - if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) - { - LogError("Error retrieving DoNotLoadOnNextBoot value: {err}", err); - doNotLoad = false; - } - - return doNotLoad; + private static bool GetDoNotLoadOnNextBoot() + { + var err = CrestronDataStoreStatic.GetLocalBoolValue(DoNotLoadOnNextBootKey, out var doNotLoad); + + if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + { + LogError("Error retrieving DoNotLoadOnNextBoot value: {err}", err); + doNotLoad = false; + } + + return doNotLoad; } public static void UpdateLoggerConfiguration(LoggerConfiguration config) @@ -253,7 +253,7 @@ public static class Debug { try { - var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel); + var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel); if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) { @@ -261,14 +261,15 @@ public static class Debug return LogEventLevel.Information; } - if(logLevel < 0 || logLevel > 5) + if (logLevel < 0 || logLevel > 5) { CrestronConsole.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}"); return LogEventLevel.Information; } return (LogEventLevel)logLevel; - } catch (Exception ex) + } + catch (Exception ex) { CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}"); return LogEventLevel.Information; @@ -280,7 +281,7 @@ public static class Debug var assembly = Assembly.GetExecutingAssembly(); var ver = assembly - .GetCustomAttributes(typeof (AssemblyInformationalVersionAttribute), false); + .GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false); if (ver != null && ver.Length > 0) { @@ -328,13 +329,13 @@ public static class Debug if (levelString.Trim() == "?") { CrestronConsole.ConsoleCommandResponse( - $@"Used to set the minimum level of debug messages to be printed to the console: -{_logLevels[0]} = 0 -{_logLevels[1]} = 1 -{_logLevels[2]} = 2 -{_logLevels[3]} = 3 -{_logLevels[4]} = 4 -{_logLevels[5]} = 5"); + "Used to set the minimum level of debug messages to be printed to the console:\r\n" + + $"{_logLevels[0]} = 0\r\n" + + $"{_logLevels[1]} = 1\r\n" + + $"{_logLevels[2]} = 2\r\n" + + $"{_logLevels[3]} = 3\r\n" + + $"{_logLevels[4]} = 4\r\n" + + $"{_logLevels[5]} = 5"); return; } @@ -344,25 +345,25 @@ public static class Debug return; } - if(int.TryParse(levelString, out var levelInt)) + if (int.TryParse(levelString, out var levelInt)) { - if(levelInt < 0 || levelInt > 5) + if (levelInt < 0 || levelInt > 5) { CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level. If using a number, value must be between 0-5"); return; } - SetDebugLevel((uint) levelInt); + SetDebugLevel((uint)levelInt); return; } - if(Enum.TryParse(levelString, out var levelEnum)) + if (Enum.TryParse(levelString, out var levelEnum)) { SetDebugLevel(levelEnum); return; } CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level"); - } + } catch { CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5]"); @@ -375,7 +376,7 @@ public static class Debug /// Valid values 0-5 public static void SetDebugLevel(uint level) { - if(!_logLevels.TryGetValue(level, out var logLevel)) + if (!_logLevels.TryGetValue(level, out var logLevel)) { logLevel = LogEventLevel.Information; @@ -394,9 +395,9 @@ public static class Debug CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}\r\n", InitialParametersClass.ApplicationNumber, _consoleLoggingLevelSwitch.MinimumLevel); - CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int) level}"); + CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}"); - var err = CrestronDataStoreStatic.SetLocalIntValue(LevelStoreKey, (int) level); + var err = CrestronDataStoreStatic.SetLocalIntValue(LevelStoreKey, (int)level); CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}"); @@ -406,9 +407,9 @@ public static class Debug public static void SetWebSocketMinimumDebugLevel(LogEventLevel level) { - _websocketLoggingLevelSwitch.MinimumLevel = level; + _websocketLoggingLevelSwitch.MinimumLevel = level; - var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint) level); + var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint)level); if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err); @@ -466,80 +467,80 @@ public static class Debug /// Callback for console command /// /// - public static void SetDebugFilterFromConsole(string items) - { - var str = items.Trim(); - if (str == "?") - { - CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " + - "+all: at beginning puts filter into 'default include' mode\r" + - " All keys that follow will be excluded from output.\r" + - "-all: at beginning puts filter into 'default exclude all' mode.\r" + - " All keys that follow will be the only keys that are shown\r" + - "+nokey: Enables messages with no key (default)\r" + - "-nokey: Disables messages with no key.\r" + - "(nokey settings are independent of all other settings)"); - return; - } - var keys = Regex.Split(str, @"\s*"); - foreach (var keyToken in keys) - { - var lkey = keyToken.ToLower(); - if (lkey == "+all") - { - IncludedExcludedKeys.Clear(); - _excludeAllMode = false; - } - else if (lkey == "-all") - { - IncludedExcludedKeys.Clear(); - _excludeAllMode = true; - } - //else if (lkey == "+nokey") - //{ - // ExcludeNoKeyMessages = false; - //} - //else if (lkey == "-nokey") - //{ - // ExcludeNoKeyMessages = true; - //} - else - { - string key; - if (lkey.StartsWith("-")) - { - key = lkey.Substring(1); - // if in exclude all mode, we need to remove this from the inclusions - if (_excludeAllMode) - { - if (IncludedExcludedKeys.ContainsKey(key)) - IncludedExcludedKeys.Remove(key); - } - // otherwise include all mode, add to the exclusions - else - { - IncludedExcludedKeys[key] = new object(); - } - } - else if (lkey.StartsWith("+")) - { - key = lkey.Substring(1); - // if in exclude all mode, we need to add this as inclusion - if (_excludeAllMode) - { + public static void SetDebugFilterFromConsole(string items) + { + var str = items.Trim(); + if (str == "?") + { + CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " + + "+all: at beginning puts filter into 'default include' mode\r" + + " All keys that follow will be excluded from output.\r" + + "-all: at beginning puts filter into 'default exclude all' mode.\r" + + " All keys that follow will be the only keys that are shown\r" + + "+nokey: Enables messages with no key (default)\r" + + "-nokey: Disables messages with no key.\r" + + "(nokey settings are independent of all other settings)"); + return; + } + var keys = Regex.Split(str, @"\s*"); + foreach (var keyToken in keys) + { + var lkey = keyToken.ToLower(); + if (lkey == "+all") + { + IncludedExcludedKeys.Clear(); + _excludeAllMode = false; + } + else if (lkey == "-all") + { + IncludedExcludedKeys.Clear(); + _excludeAllMode = true; + } + //else if (lkey == "+nokey") + //{ + // ExcludeNoKeyMessages = false; + //} + //else if (lkey == "-nokey") + //{ + // ExcludeNoKeyMessages = true; + //} + else + { + string key; + if (lkey.StartsWith("-")) + { + key = lkey.Substring(1); + // if in exclude all mode, we need to remove this from the inclusions + if (_excludeAllMode) + { + if (IncludedExcludedKeys.ContainsKey(key)) + IncludedExcludedKeys.Remove(key); + } + // otherwise include all mode, add to the exclusions + else + { + IncludedExcludedKeys[key] = new object(); + } + } + else if (lkey.StartsWith("+")) + { + key = lkey.Substring(1); + // if in exclude all mode, we need to add this as inclusion + if (_excludeAllMode) + { - IncludedExcludedKeys[key] = new object(); - } - // otherwise include all mode, remove this from exclusions - else - { - if (IncludedExcludedKeys.ContainsKey(key)) - IncludedExcludedKeys.Remove(key); - } - } - } - } - } + IncludedExcludedKeys[key] = new object(); + } + // otherwise include all mode, remove this from exclusions + else + { + if (IncludedExcludedKeys.ContainsKey(key)) + IncludedExcludedKeys.Remove(key); + } + } + } + } + } @@ -564,7 +565,7 @@ public static class Debug public static object GetDeviceDebugSettingsForKey(string deviceKey) { return _contexts.GetDebugSettingsForKey(deviceKey); - } + } /// /// Sets the flag to prevent application starting on next boot @@ -572,8 +573,8 @@ public static class Debug /// public static void SetDoNotLoadConfigOnNextBoot(bool state) { - DoNotLoadConfigOnNextBoot = state; - + DoNotLoadConfigOnNextBoot = state; + var err = CrestronDataStoreStatic.SetLocalBoolValue(DoNotLoadOnNextBootKey, state); if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) @@ -624,7 +625,7 @@ public static class Debug public static void LogMessage(LogEventLevel level, string message, params object[] args) { - _logger.Write(level, message, args); + _logger.Write(level, message, args); } public static void LogMessage(LogEventLevel level, Exception ex, string message, params object[] args) @@ -648,7 +649,7 @@ public static class Debug #region Explicit methods for logging levels public static void LogVerbose(IKeyed keyed, string message, params object[] args) { - using(LogContext.PushProperty("Key", keyed?.Key)) + using (LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Verbose, message, args); } @@ -656,7 +657,7 @@ public static class Debug public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args) { - using(LogContext.PushProperty("Key", keyed?.Key)) + using (LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Verbose, ex, message, args); } @@ -810,7 +811,7 @@ public static class Debug if (!_logLevels.ContainsKey(level)) return; var logLevel = _logLevels[level]; - + LogMessage(logLevel, format, items); } @@ -847,7 +848,7 @@ public static class Debug } /// - /// Logs to Console when at-level, and all messages to error log, including device key + /// Logs to Console when at-level, and all messages to error log, including device key /// [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] public static void Console(uint level, IKeyed dev, string format, params object[] items) @@ -865,7 +866,7 @@ public static class Debug [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel, string format, params object[] items) - { + { LogMessage(level, dev, format, items); } @@ -1003,7 +1004,7 @@ public static class Debug return string.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); } - return string.Format("{0}{1}user{1}debugSettings{1}{2}.json",Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId); + return string.Format("{0}{1}user{1}debugSettings{1}{2}.json", Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId); } /// @@ -1014,15 +1015,15 @@ public static class Debug /// /// Error /// - Error, + Error, /// /// Warning /// - Warning, + Warning, /// /// Notice /// - Notice, + Notice, /// /// None /// diff --git a/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs b/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs index 528e9536..ad003c46 100644 --- a/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs +++ b/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs @@ -1,11 +1,11 @@ -using Crestron.SimplSharp; -using Crestron.SimplSharpPro; -using PepperDash.Core; -using Serilog.Events; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using PepperDash.Core; +using Serilog.Events; namespace PepperDash.Essentials.Core; @@ -26,17 +26,17 @@ public static class DeviceManager /// /// Returns a copy of all the devices in a list /// - public static List AllDevices => [.. Devices.Values]; - + public static List AllDevices => [.. Devices.Values]; + public static bool AddDeviceEnabled; - /// - /// Initializes the control system by enabling device management and registering console commands. - /// - /// This method sets up the control system for device management by enabling the addition of - /// devices and registering a series of console commands for interacting with devices. These commands allow - /// operators to list device statuses, feedbacks, and managed devices, as well as perform actions such as - /// simulating communication, debugging device streams, and accessing device properties and methods. + /// + /// Initializes the control system by enabling device management and registering console commands. + /// + /// This method sets up the control system for device management by enabling the addition of + /// devices and registering a series of console commands for interacting with devices. These commands allow + /// operators to list device statuses, feedbacks, and managed devices, as well as perform actions such as + /// simulating communication, debugging device streams, and accessing device properties and methods. /// The instance representing the control system to initialize. public static void Initialize(CrestronControlSystem cs) { @@ -76,7 +76,7 @@ public static class DeviceManager foreach (var d in Devices.Values) { try - { + { if (d is Device) (d as Device).PreActivate(); } @@ -440,9 +440,9 @@ public static class DeviceManager if (String.IsNullOrEmpty(s) || s.Contains("?")) { CrestronConsole.ConsoleCommandResponse( - @"SETDEVICESTREAMDEBUG [{deviceKey}] [OFF |TX | RX | BOTH] [timeOutInMinutes] - {deviceKey} [OFF | TX | RX | BOTH] - Device to set stream debugging on, and which setting to use - timeOutInMinutes - Set timeout for stream debugging. Default is 30 minutes"); + "SETDEVICESTREAMDEBUG [{deviceKey}] [OFF |TX | RX | BOTH] [timeOutInMinutes]\r\n" + + " {deviceKey} [OFF | TX | RX | BOTH] - Device to set stream debugging on, and which setting to use\r\n" + + " timeOutInMinutes - Set timeout for stream debugging. Default is 30 minutes"); return; } diff --git a/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs b/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs index e62e771b..dca09cb9 100644 --- a/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs @@ -1,47 +1,47 @@  -using Crestron.SimplSharp; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; using System.Reflection; +using Crestron.SimplSharp; using Newtonsoft.Json.Linq; using PepperDash.Core; using PepperDash.Essentials.Core.Config; using Serilog.Events; -using System; -using System.Collections.Generic; -using System.Linq; -using System.ComponentModel.DataAnnotations; -using System.IO; namespace PepperDash.Essentials.Core; -/// -/// Represents a wrapper for a device factory, encapsulating the type of device, a description, and a factory method -/// for creating device instances. +/// +/// Represents a wrapper for a device factory, encapsulating the type of device, a description, and a factory method +/// for creating device instances. /// /// This class is designed to provide a convenient way to store and manage metadata and factory methods /// for creating devices based on a given configuration. public class DeviceFactoryWrapper { - /// - /// Gets or sets the type associated with the current instance. + /// + /// Gets or sets the type associated with the current instance. /// public Type Type { get; set; } - /// - /// Gets or sets the description associated with the object. + /// + /// Gets or sets the description associated with the object. /// public string Description { get; set; } - /// - /// Gets or sets the factory method used to create an instance based on the provided . + /// + /// Gets or sets the factory method used to create an instance based on the provided . /// /// The factory method allows customization of how instances are created for /// specific inputs. Ensure the delegate is not null before invoking it. public Func FactoryMethod { get; set; } - /// - /// Initializes a new instance of the class with default values. + /// + /// Initializes a new instance of the class with default values. /// /// The property is initialized to , and the property is set to "Not Available". @@ -52,9 +52,9 @@ public class DeviceFactoryWrapper } } -/// -/// Provides functionality for managing and registering device factories, including loading plugin-based factories and -/// retrieving devices based on their configuration. +/// +/// Provides functionality for managing and registering device factories, including loading plugin-based factories and +/// retrieving devices based on their configuration. /// /// The class is responsible for discovering and registering device factories /// from plugins, as well as providing methods to retrieve devices based on their configuration. It maintains a @@ -62,9 +62,9 @@ public class DeviceFactoryWrapper /// class also handles metadata retrieval and secret management for device configurations. public class DeviceFactory { - /// - /// Initializes a new instance of the class and loads all available device factories - /// from the current assembly. + /// + /// Initializes a new instance of the class and loads all available device factories + /// from the current assembly. /// /// This constructor scans the executing assembly for types that implement the interface and are not abstract or interfaces. For each valid type, an instance is @@ -72,14 +72,14 @@ public class DeviceFactory /// instantiated, an informational log message is generated, and the process continues with the remaining /// types. public DeviceFactory() - { + { var programAssemblies = Directory.GetFiles(InitialParametersClass.ProgramDirectory.ToString(), "*.dll"); - foreach(var assembly in programAssemblies) + foreach (var assembly in programAssemblies) { try { - Assembly.LoadFrom(assembly); + Assembly.LoadFrom(assembly); } catch (Exception e) { @@ -87,39 +87,39 @@ public class DeviceFactory } } - var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); // Loop through all loaded assemblies that contain at least 1 type that implements IDeviceFactory - foreach (var assembly in loadedAssemblies) - { - Debug.LogDebug("loaded assembly: {assemblyName}", assembly.GetName().Name); - - PluginLoader.AddLoadedAssembly(assembly.GetName().Name, assembly); - - var types = assembly.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); - - if(types == null || !types.Any()) - { - Debug.LogDebug("No DeviceFactory types found in assembly: {assemblyName}", assembly.GetName().Name); - continue; - } - - foreach (var type in types) - { - try - { - var factory = (IDeviceFactory)Activator.CreateInstance(type); - LoadDeviceFactories(factory); - } - catch (Exception e) - { - Debug.LogError("Unable to load type: '{message}' DeviceFactory: {type}", e.Message, type.Name); - } - } - - } - } - + foreach (var assembly in loadedAssemblies) + { + Debug.LogDebug("loaded assembly: {assemblyName}", assembly.GetName().Name); + + PluginLoader.AddLoadedAssembly(assembly.GetName().Name, assembly); + + var types = assembly.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); + + if (types == null || !types.Any()) + { + Debug.LogDebug("No DeviceFactory types found in assembly: {assemblyName}", assembly.GetName().Name); + continue; + } + + foreach (var type in types) + { + try + { + var factory = (IDeviceFactory)Activator.CreateInstance(type); + LoadDeviceFactories(factory); + } + catch (Exception e) + { + Debug.LogError("Unable to load type: '{message}' DeviceFactory: {type}", e.Message, type.Name); + } + } + + } + } + /// /// Loads device factories from the specified plugin device factory and registers them for use. /// @@ -137,8 +137,8 @@ public class DeviceFactory var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); } - } - + } + /// /// A dictionary of factory methods, keyed by config types, added by plugins. /// These methods are looked up and called by GetDevice in this class. @@ -146,33 +146,33 @@ public class DeviceFactory private static readonly Dictionary FactoryMethods = new(StringComparer.OrdinalIgnoreCase); - /// - /// Registers a factory method for creating instances of a specific type. - /// - /// This method associates a type name with a factory method, allowing instances of the type to - /// be created dynamically. The factory method is stored internally and can be retrieved or invoked as - /// needed. - /// The name of the type for which the factory method is being registered. This value cannot be null or empty. + /// + /// Registers a factory method for creating instances of a specific type. + /// + /// This method associates a type name with a factory method, allowing instances of the type to + /// be created dynamically. The factory method is stored internally and can be retrieved or invoked as + /// needed. + /// The name of the type for which the factory method is being registered. This value cannot be null or empty. /// A delegate that defines the factory method. The delegate takes a parameter and /// returns an instance of . - public static void AddFactoryForType(string typeName, Func method) - { - FactoryMethods.Add(typeName, new DeviceFactoryWrapper() { FactoryMethod = method}); - } + public static void AddFactoryForType(string typeName, Func method) + { + FactoryMethods.Add(typeName, new DeviceFactoryWrapper() { FactoryMethod = method }); + } - /// - /// Registers a factory method for creating instances of a specific device type. - /// - /// If a factory method for the specified already exists, the method - /// will not overwrite it and will log an informational message instead. - /// The unique name of the device type. This serves as the key for identifying the factory method. - /// A brief description of the device type. This is used for informational purposes. - /// The of the device being registered. This represents the runtime type of the device. + /// + /// Registers a factory method for creating instances of a specific device type. + /// + /// If a factory method for the specified already exists, the method + /// will not overwrite it and will log an informational message instead. + /// The unique name of the device type. This serves as the key for identifying the factory method. + /// A brief description of the device type. This is used for informational purposes. + /// The of the device being registered. This represents the runtime type of the device. /// A factory method that takes a as input and returns an instance of . public static void AddFactoryForType(string typeName, string description, Type Type, Func method) { - if(FactoryMethods.ContainsKey(typeName)) + if (FactoryMethods.ContainsKey(typeName)) { Debug.LogInformation("Unable to add type: '{typeName}'. Already exists in DeviceFactory", typeName); return; @@ -190,7 +190,7 @@ public class DeviceFactory if (prop.Name.Equals("secret", StringComparison.CurrentCultureIgnoreCase)) { var secret = GetSecret(prop.Children().First().ToObject()); - + prop.Parent.Replace(secret); } if (prop.Value is not JObject recurseProp) return; @@ -203,7 +203,7 @@ public class DeviceFactory var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider); if (secretProvider == null) return null; var secret = secretProvider.GetSecret(data.Key); - if (secret != null) return (string) secret.Value; + if (secret != null) return (string)secret.Value; Debug.LogMessage(LogEventLevel.Debug, "Unable to retrieve secret {0}{1} - Make sure you've added it to the secrets provider", data.Provider, data.Key); @@ -211,20 +211,20 @@ public class DeviceFactory } - /// - /// Creates and returns a device instance based on the provided . - /// - /// This method attempts to create a device using the type specified in the - /// parameter. If the type corresponds to a registered factory method, the device is created and returned. If the - /// type is unrecognized or an exception occurs, the method logs the error and returns . - /// The configuration object containing the key, name, type, and properties required to create the device. + /// + /// Creates and returns a device instance based on the provided . + /// + /// This method attempts to create a device using the type specified in the + /// parameter. If the type corresponds to a registered factory method, the device is created and returned. If the + /// type is unrecognized or an exception occurs, the method logs the error and returns . + /// The configuration object containing the key, name, type, and properties required to create the device. /// An instance of a device that implements , or if the device type is /// not recognized or an error occurs during creation. public static IKeyed GetDevice(DeviceConfig dc) { try - { + { var localDc = new DeviceConfig(dc); var key = localDc.Key; @@ -239,17 +239,17 @@ public class DeviceFactory var jProp = jObject.Properties(); CheckForSecrets(jProp); - } - - if (!FactoryMethods.TryGetValue(typeName, out var wrapper)) - { - Debug.LogWarning("Device type '{typeName}' not found in DeviceFactory", typeName); - return null; } - Debug.LogInformation("Loading '{type}' from {assemblyName}", typeName, wrapper.Type.Assembly.FullName); - - // Check for types that have been added by plugin dlls. + if (!FactoryMethods.TryGetValue(typeName, out var wrapper)) + { + Debug.LogWarning("Device type '{typeName}' not found in DeviceFactory", typeName); + return null; + } + + Debug.LogInformation("Loading '{type}' from {assemblyName}", typeName, wrapper.Type.Assembly.FullName); + + // Check for types that have been added by plugin dlls. return wrapper.FactoryMethod(localDc); } catch (Exception ex) @@ -259,12 +259,12 @@ public class DeviceFactory } } - /// - /// Displays a list of device factory types that match the specified filter. - /// - /// The method outputs the filtered list of device factory types to the console, including their - /// key, type, and description. If a type is not specified by the plugin, it will be displayed as "Not Specified by - /// Plugin." + /// + /// Displays a list of device factory types that match the specified filter. + /// + /// The method outputs the filtered list of device factory types to the console, including their + /// key, type, and description. If a type is not specified by the plugin, it will be displayed as "Not Specified by + /// Plugin." /// A string used to filter the device factory types by their keys. If the filter is null or empty, all device /// factory types are displayed. public static void GetDeviceFactoryTypes(string filter) @@ -286,23 +286,23 @@ public class DeviceFactory } CrestronConsole.ConsoleCommandResponse( - @"Type: '{0}' - Type: '{1}' - Description: {2}{3}", type.Key, Type, description, CrestronEnvironment.NewLine); + "Type: '{0}'\r\n" + + " Type: '{1}'\r\n" + + " Description: {2}{3}", type.Key, Type, description, CrestronEnvironment.NewLine); } } - /// - /// Retrieves a dictionary of device factory wrappers, optionally filtered by a specified string. - /// - /// A string used to filter the dictionary keys. Only entries with keys containing the specified filter will be - /// included. If or empty, all entries are returned. + /// + /// Retrieves a dictionary of device factory wrappers, optionally filtered by a specified string. + /// + /// A string used to filter the dictionary keys. Only entries with keys containing the specified filter will be + /// included. If or empty, all entries are returned. /// A dictionary where the keys are strings representing device identifiers and the values are instances. The dictionary may be empty if no entries match the filter. - public static Dictionary GetDeviceFactoryDictionary(string filter) - { - return string.IsNullOrEmpty(filter) - ? FactoryMethods - : FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value); - } + public static Dictionary GetDeviceFactoryDictionary(string filter) + { + return string.IsNullOrEmpty(filter) + ? FactoryMethods + : FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value); + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs index d7d956e3..cb026a53 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs @@ -1,4 +1,10 @@ -using Crestron.SimplSharp; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.Net.Http; using Crestron.SimplSharp.WebScripting; @@ -30,12 +36,6 @@ using PepperDash.Essentials.RoomBridges; using PepperDash.Essentials.Services; using PepperDash.Essentials.WebApiHandlers; using PepperDash.Essentials.WebSocketServer; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using System.Threading.Tasks; using WebSocketSharp; namespace PepperDash.Essentials; @@ -570,7 +570,7 @@ public class MobileControlSystemController : EssentialsDevice, IMobileControl { this.LogVerbose( "Adding ISetTopBoxControlMessenger for {deviceKey}" - ); + ); var messenger = new ISetTopBoxControlsMessenger( $"{device.Key}-stb-{Key}", @@ -587,7 +587,7 @@ public class MobileControlSystemController : EssentialsDevice, IMobileControl { this.LogVerbose( "Adding IChannelMessenger for {deviceKey}", device.Key - ); + ); var messenger = new IChannelMessenger( $"{device.Key}-channel-{Key}", @@ -602,7 +602,7 @@ public class MobileControlSystemController : EssentialsDevice, IMobileControl if (device is IColor colorDevice) { - this.LogVerbose("Adding IColorMessenger for {deviceKey}", device.Key); + this.LogVerbose("Adding IColorMessenger for {deviceKey}", device.Key); var messenger = new IColorMessenger( $"{device.Key}-color-{Key}", @@ -617,7 +617,7 @@ public class MobileControlSystemController : EssentialsDevice, IMobileControl if (device is IDPad dPadDevice) { - this.LogVerbose("Adding IDPadMessenger for {deviceKey}", device.Key); + this.LogVerbose("Adding IDPadMessenger for {deviceKey}", device.Key); var messenger = new IDPadMessenger( $"{device.Key}-dPad-{Key}", @@ -632,7 +632,7 @@ public class MobileControlSystemController : EssentialsDevice, IMobileControl if (device is INumericKeypad nkDevice) { - this.LogVerbose("Adding INumericKeyapdMessenger for {deviceKey}", device.Key); + this.LogVerbose("Adding INumericKeyapdMessenger for {deviceKey}", device.Key); var messenger = new INumericKeypadMessenger( $"{device.Key}-numericKeypad-{Key}", @@ -647,7 +647,7 @@ public class MobileControlSystemController : EssentialsDevice, IMobileControl if (device is IHasPowerControl pcDevice) { - this.LogVerbose("Adding IHasPowerControlMessenger for {deviceKey}", device.Key); + this.LogVerbose("Adding IHasPowerControlMessenger for {deviceKey}", device.Key); var messenger = new IHasPowerMessenger( $"{device.Key}-powerControl-{Key}", @@ -681,7 +681,7 @@ public class MobileControlSystemController : EssentialsDevice, IMobileControl { this.LogVerbose( "Adding ITransportMessenger for {deviceKey}", device.Key - ); + ); var messenger = new ITransportMessenger( $"{device.Key}-transport-{Key}", @@ -1569,15 +1569,14 @@ public class MobileControlSystemController : EssentialsDevice, IMobileControl if (Config.EnableApiServer) { CrestronConsole.ConsoleCommandResponse( - @"Mobile Control Edge Server API Information: - - Server address: {0} - System Name: {1} - System URL: {2} - System UUID: {3} - System User code: {4} - Connected?: {5} - Seconds Since Last Ack: {6}", + "Mobile Control Edge Server API Information:\r\n\r\n" + + "\tServer address: {0}\r\n" + + "\tSystem Name: {1}\r\n" + + "\tSystem URL: {2}\r\n" + + "\tSystem UUID: {3}\r\n" + + "\tSystem User code: {4}\r\n" + + "\tConnected?: {5}\r\n" + + "\tSeconds Since Last Ack: {6}\r\n", url, name, ConfigReader.ConfigObject.SystemUrl, @@ -1590,10 +1589,8 @@ public class MobileControlSystemController : EssentialsDevice, IMobileControl else { CrestronConsole.ConsoleCommandResponse( - @" -Mobile Control Edge Server API Information: - Not Enabled in Config. -" + "\r\nMobile Control Edge Server API Information:\r\n" + + " Not Enabled in Config.\r\n" ); } @@ -1604,21 +1601,17 @@ Mobile Control Edge Server API Information: ) { CrestronConsole.ConsoleCommandResponse( - @" -Mobile Control Direct Server Information: - User App URL: {0} - Server port: {1} -", + "\r\nMobile Control Direct Server Information:\r\n" + + " User App URL: {0}\r\n" + + " Server port: {1}\r\n", string.Format("{0}[insert_client_token]", _directServer.UserAppUrlPrefix), _directServer.Port ); CrestronConsole.ConsoleCommandResponse( - @" - UI Client Info: - Tokens Defined: {0} - Clients Connected: {1} -", + "\r\n UI Client Info:\r\n" + + " Tokens Defined: {0}\r\n" + + " Clients Connected: {1}\r\n", _directServer.UiClients.Count, _directServer.ConnectedUiClientsCount ); @@ -1636,15 +1629,13 @@ Mobile Control Direct Server Information: } CrestronConsole.ConsoleCommandResponse( - @" -Client {0}: -Room Key: {1} -Touchpanel Key: {6} -Token: {2} -Client URL: {3} -Connected: {4} -Duration: {5} -", + "\r\nClient {0}:\r\n" + + "Room Key: {1}\r\n" + + "Touchpanel Key: {6}\r\n" + + "Token: {2}\r\n" + + "Client URL: {3}\r\n" + + "Connected: {4}\r\n" + + "Duration: {5}\r\n", clientNo, clientContext.Value.Token.RoomKey, clientContext.Key, @@ -1659,9 +1650,8 @@ Duration: {5} else { CrestronConsole.ConsoleCommandResponse( - @" -Mobile Control Direct Server Infromation: - Not Enabled in Config." + "\r\nMobile Control Direct Server Information:\r\n" + + " Not Enabled in Config.\r\n" ); } } @@ -2238,7 +2228,7 @@ Mobile Control Direct Server Infromation: { this.LogInformation("-- Warning: Incoming message has no registered handler {type}", message.Type); break; - } + } foreach (var handler in handlers) {