feat: Add unit tests and fakes for Crestron environment and data store

- Introduced `FakeCrestronEnvironment` and `FakeEthernetHelper` for testing purposes.
- Implemented `InMemoryCrestronDataStore` to facilitate unit tests for data storage.
- Created `DebugServiceTests` to validate the behavior of debug-related services.
- Added project files for unit tests targeting .NET 9.0 with necessary dependencies.
- Developed production adapters (`CrestronConsoleAdapter`, `CrestronDataStoreAdapter`, `CrestronEnvironmentAdapter`, `CrestronEthernetAdapter`) to interface with the Crestron SDK.
- Updated `Debug` class to utilize injected service abstractions instead of direct SDK calls.
- Enhanced `ControlSystem` initialization to register service adapters before usage.
This commit is contained in:
Neil Dorin 2026-04-08 12:41:15 -06:00
parent bfb9838743
commit 24df4e7a03
21 changed files with 1020 additions and 93 deletions

View file

@ -0,0 +1,35 @@
using System;
using Crestron.SimplSharp;
using PdCore = PepperDash.Core.Abstractions;
namespace PepperDash.Core.Adapters;
/// <summary>
/// Production adapter — delegates ICrestronConsole calls to the real Crestron SDK.
/// </summary>
public sealed class CrestronConsoleAdapter : PdCore.ICrestronConsole
{
public void PrintLine(string message) => CrestronConsole.PrintLine(message);
public void Print(string message) => CrestronConsole.Print(message);
public void ConsoleCommandResponse(string message) =>
CrestronConsole.ConsoleCommandResponse(message);
public void AddNewConsoleCommand(
Action<string> callback,
string command,
string helpText,
PdCore.ConsoleAccessLevel accessLevel)
{
var crestronLevel = accessLevel switch
{
PdCore.ConsoleAccessLevel.AccessAdministrator => ConsoleAccessLevelEnum.AccessAdministrator,
PdCore.ConsoleAccessLevel.AccessProgrammer => ConsoleAccessLevelEnum.AccessProgrammer,
_ => ConsoleAccessLevelEnum.AccessOperator,
};
// Wrap Action<string> in a lambda — Crestron's delegate is not a standard Action<string>.
CrestronConsole.AddNewConsoleCommand(s => callback(s), command, helpText, crestronLevel);
}
}

View file

@ -0,0 +1,42 @@
using Crestron.SimplSharp.CrestronDataStore;
using PepperDash.Core.Abstractions;
namespace PepperDash.Core.Adapters;
/// <summary>
/// Production adapter — delegates ICrestronDataStore calls to the real Crestron SDK.
/// </summary>
public sealed class CrestronDataStoreAdapter : ICrestronDataStore
{
public void InitStore() => CrestronDataStoreStatic.InitCrestronDataStore();
public bool TryGetLocalInt(string key, out int value)
{
var err = CrestronDataStoreStatic.GetLocalIntValue(key, out value);
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
}
public bool SetLocalInt(string key, int value)
{
var err = CrestronDataStoreStatic.SetLocalIntValue(key, value);
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
}
public bool SetLocalUint(string key, uint value)
{
var err = CrestronDataStoreStatic.SetLocalUintValue(key, value);
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
}
public bool TryGetLocalBool(string key, out bool value)
{
var err = CrestronDataStoreStatic.GetLocalBoolValue(key, out value);
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
}
public bool SetLocalBool(string key, bool value)
{
var err = CrestronDataStoreStatic.SetLocalBoolValue(key, value);
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
}
}

View file

@ -0,0 +1,68 @@
using System;
using Crestron.SimplSharp;
using PdCore = PepperDash.Core.Abstractions;
namespace PepperDash.Core.Adapters;
/// <summary>
/// Production adapter — delegates ICrestronEnvironment calls to the real Crestron SDK.
/// </summary>
public sealed class CrestronEnvironmentAdapter : PdCore.ICrestronEnvironment
{
// Subscribe once in constructor and re-raise as our event types.
private event EventHandler<PdCore.ProgramStatusEventArgs>? _programStatusChanged;
private event EventHandler<PdCore.PepperDashEthernetEventArgs>? _ethernetEventReceived;
public CrestronEnvironmentAdapter()
{
CrestronEnvironment.ProgramStatusEventHandler += type =>
_programStatusChanged?.Invoke(this, new PdCore.ProgramStatusEventArgs(MapProgramStatus(type)));
CrestronEnvironment.EthernetEventHandler += args =>
_ethernetEventReceived?.Invoke(this, new PdCore.PepperDashEthernetEventArgs(
args.EthernetEventType == eEthernetEventType.LinkDown
? PdCore.EthernetEventType.LinkDown
: PdCore.EthernetEventType.LinkUp,
(short)args.EthernetAdapter));
}
public PdCore.DevicePlatform DevicePlatform =>
CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
? PdCore.DevicePlatform.Appliance
: PdCore.DevicePlatform.Server;
public PdCore.RuntimeEnvironment RuntimeEnvironment =>
CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro
? PdCore.RuntimeEnvironment.SimplSharpPro
: PdCore.RuntimeEnvironment.Other;
public string NewLine => CrestronEnvironment.NewLine;
public uint ApplicationNumber => InitialParametersClass.ApplicationNumber;
public uint RoomId => uint.TryParse(InitialParametersClass.RoomId, out var r) ? r : 0;
public event EventHandler<PdCore.ProgramStatusEventArgs> ProgramStatusChanged
{
add => _programStatusChanged += value;
remove => _programStatusChanged -= value;
}
public event EventHandler<PdCore.PepperDashEthernetEventArgs> EthernetEventReceived
{
add => _ethernetEventReceived += value;
remove => _ethernetEventReceived -= value;
}
public string GetApplicationRootDirectory() =>
Crestron.SimplSharp.CrestronIO.Directory.GetApplicationRootDirectory();
private static PdCore.ProgramStatusEventType MapProgramStatus(eProgramStatusEventType type) =>
type switch
{
eProgramStatusEventType.Stopping => PdCore.ProgramStatusEventType.Stopping,
eProgramStatusEventType.Paused => PdCore.ProgramStatusEventType.Paused,
eProgramStatusEventType.Resumed => PdCore.ProgramStatusEventType.Resumed,
_ => PdCore.ProgramStatusEventType.Starting,
};
}

View file

@ -0,0 +1,39 @@
using System;
using Crestron.SimplSharp;
using PepperDash.Core.Abstractions;
namespace PepperDash.Core.Adapters;
/// <summary>
/// Production adapter — delegates IEthernetHelper calls to the real Crestron SDK.
/// </summary>
public sealed class CrestronEthernetAdapter : IEthernetHelper
{
public string GetEthernetParameter(EthernetParameterType parameter, short ethernetAdapterId)
{
var crestronParam = parameter switch
{
EthernetParameterType.GetCurrentIpAddress =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS,
EthernetParameterType.GetHostname =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME,
EthernetParameterType.GetDomainName =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME,
EthernetParameterType.GetLinkStatus =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_LINK_STATUS,
EthernetParameterType.GetCurrentDhcpState =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE,
EthernetParameterType.GetCurrentIpMask =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK,
EthernetParameterType.GetCurrentRouter =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER,
EthernetParameterType.GetMacAddress =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS,
EthernetParameterType.GetDnsServer =>
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER,
_ => throw new ArgumentOutOfRangeException(nameof(parameter), parameter, null),
};
return CrestronEthernetHelper.GetEthernetParameter(crestronParam, ethernetAdapterId);
}
}

View file

@ -11,6 +11,7 @@ using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharp.CrestronLogger;
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
using PdCore = PepperDash.Core.Abstractions;
using PepperDash.Core.Logging;
using Serilog;
using Serilog.Context;
@ -45,6 +46,13 @@ public static class Debug
private static ILogger _logger;
// Injected service abstractions. Populated by DebugServiceRegistration.Register()
// before the Debug static constructor runs. Null when running on hardware without
// pre-registration (the static ctor falls back to the Crestron SDK directly).
private static PdCore.ICrestronEnvironment _environment;
private static PdCore.ICrestronConsole _console;
private static PdCore.ICrestronDataStore _dataStore;
private static readonly LoggingLevelSwitch consoleLoggingLevelSwitch;
private static readonly LoggingLevelSwitch websocketLoggingLevelSwitch;
@ -107,7 +115,7 @@ public static class Debug
/// <summary>
/// Indicates whether the code is running on an appliance or not. Used to determine file paths and other appliance vs server differences
/// </summary>
public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance;
public static bool IsRunningOnAppliance; // set in static constructor
/// <summary>
/// Version for the currently loaded PepperDashCore dll
@ -145,102 +153,114 @@ public static class Debug
{
try
{
CrestronDataStoreStatic.InitCrestronDataStore();
// Pick up services pre-registered by the composition root (or test setup).
// If null, fall back to direct Crestron SDK calls for production hardware.
_environment = PdCore.DebugServiceRegistration.Environment;
_console = PdCore.DebugServiceRegistration.Console;
_dataStore = PdCore.DebugServiceRegistration.DataStore;
IsRunningOnAppliance = _environment?.DevicePlatform == PdCore.DevicePlatform.Appliance;
_dataStore?.InitStore();
consoleDebugTimer = new Timer(defaultConsoleDebugTimeoutMin * 60000) { AutoReset = false };
consoleDebugTimer.Elapsed += (s, e) =>
{
SetDebugLevel(LogEventLevel.Information);
CrestronConsole.ConsoleCommandResponse($"Console debug level reset to {LogEventLevel.Information} after timeout of {defaultConsoleDebugTimeoutMin} minutes");
_console?.ConsoleCommandResponse($"Console debug level reset to {LogEventLevel.Information} after timeout of {defaultConsoleDebugTimeoutMin} minutes");
};
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);
fileLoggingLevelSwitch = 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";
var appRoot = _environment?.GetApplicationRootDirectory()
?? System.IO.Path.GetTempPath();
var sep = System.IO.Path.DirectorySeparatorChar;
var appNum = _environment?.ApplicationNumber ?? 0;
var roomId = _environment?.RoomId ?? 0;
CrestronConsole.PrintLine($"Saving log files to {logFilePath}");
var logFilePath = IsRunningOnAppliance
? $"{appRoot}{sep}user{sep}debug{sep}app{appNum}{sep}global-log.log"
: $"{appRoot}{sep}user{sep}debug{sep}room{roomId}{sep}global-log.log";
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}";
_console?.PrintLine($"Saving log files to {logFilePath}");
// Build the base Serilog pipeline — sinks that require the Crestron SDK are
// added conditionally so the logger remains usable in test environments.
_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,
retainedFileCountLimit: IsRunningOnAppliance ? 30 : 60,
levelSwitch: fileLoggingLevelSwitch
);
// 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)
// Add Crestron-specific enricher and error-log sink only when running on hardware.
if (_environment != null)
{
msg = $"[Room {InitialParametersClass.RoomId}] Using PepperDash_Core v{PepperDashCoreVersion}";
var errorLogTemplate = IsRunningOnAppliance
? "{@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
.Enrich.With(new CrestronEnricher())
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: errorLogLevelSwitch);
}
CrestronConsole.PrintLine(msg);
_loggerConfiguration = _defaultLoggerConfiguration;
_logger = _loggerConfiguration.CreateLogger();
GetVersion();
string msg = IsRunningOnAppliance
? $"[App {appNum}] Using PepperDash_Core v{PepperDashCoreVersion}"
: $"[Room {roomId}] Using PepperDash_Core v{PepperDashCoreVersion}";
_console?.PrintLine(msg);
LogMessage(LogEventLevel.Information, msg);
IncludedExcludedKeys = new Dictionary<string, object>();
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
if (_environment?.RuntimeEnvironment == PdCore.RuntimeEnvironment.SimplSharpPro)
{
// Add command to console
CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot",
"donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator);
_console?.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot",
"donotloadonnextboot:P [true/false]: Should the application load on next boot",
PdCore.ConsoleAccessLevel.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
_console?.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);
}
PdCore.ConsoleAccessLevel.AccessOperator);
// CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
_console?.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
"appdebuglog:P [all] Use \"all\" for full log.",
PdCore.ConsoleAccessLevel.AccessOperator);
_console?.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
"appdebugclear:P Clears the current custom log",
PdCore.ConsoleAccessLevel.AccessOperator);
_console?.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
"appdebugfilter [params]",
PdCore.ConsoleAccessLevel.AccessOperator);
}
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));
_console?.PrintLine($"Program {appNum} will not load config after next boot. Use console command go:{appNum} to load the config manually");
consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
{
@ -250,18 +270,20 @@ public static class Debug
catch (Exception ex)
{
// _logger may not have been initialized yet — do not call LogError here.
CrestronConsole.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}");
// _console may also be null; fall back to CrestronConsole as last resort.
try { _console?.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}"); }
catch { CrestronConsole.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}"); }
}
}
private static bool GetDoNotLoadOnNextBoot()
{
var err = CrestronDataStoreStatic.GetLocalBoolValue(DoNotLoadOnNextBootKey, out var doNotLoad);
if (_dataStore == null) return false;
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
if (!_dataStore.TryGetLocalBool(DoNotLoadOnNextBootKey, out var doNotLoad))
{
LogError("Error retrieving DoNotLoadOnNextBoot value: {err}", err);
doNotLoad = false;
LogError("Error retrieving DoNotLoadOnNextBoot value");
return false;
}
return doNotLoad;
@ -294,17 +316,17 @@ public static class Debug
{
try
{
var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel);
if (_dataStore == null) return LogEventLevel.Information;
if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
if (!_dataStore.TryGetLocalInt(levelStoreKey, out int logLevel))
{
CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n");
_console?.Print($"Unable to retrieve stored log level for {levelStoreKey}. Setting level to {LogEventLevel.Information}\r\n");
return LogEventLevel.Information;
}
if (logLevel < 0 || logLevel > 5)
{
CrestronConsole.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}");
_console?.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}");
return LogEventLevel.Information;
}
@ -312,7 +334,7 @@ public static class Debug
}
catch (Exception ex)
{
CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}");
_console?.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}");
return LogEventLevel.Information;
}
}
@ -369,7 +391,7 @@ public static class Debug
{
if (levelString.Trim() == "?")
{
CrestronConsole.ConsoleCommandResponse(
_console?.ConsoleCommandResponse(
"Used to set the minimum level of debug messages to be printed to the console:\r\n" +
"[LogLevel] [TimeoutInMinutes]\r\n" +
@ -386,7 +408,7 @@ public static class Debug
if (string.IsNullOrEmpty(levelString.Trim()))
{
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", consoleLoggingLevelSwitch.MinimumLevel);
_console?.ConsoleCommandResponse($"AppDebug level = {consoleLoggingLevelSwitch.MinimumLevel}");
return;
}
@ -406,7 +428,7 @@ public static class Debug
{
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");
_console?.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level. If using a number, value must be between 0-5");
return;
}
SetDebugLevel((uint)levelInt);
@ -420,11 +442,11 @@ public static class Debug
return;
}
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
_console?.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
}
catch
{
CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5]");
_console?.ConsoleCommandResponse("Usage: appdebug:P [0-5]");
}
}
@ -439,7 +461,7 @@ public static class Debug
{
logLevel = LogEventLevel.Information;
CrestronConsole.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}");
_console?.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}");
SetDebugLevel(logLevel, timeout);
}
@ -460,17 +482,14 @@ public static class Debug
consoleLoggingLevelSwitch.MinimumLevel = level;
CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}\r\n",
InitialParametersClass.ApplicationNumber, consoleLoggingLevelSwitch.MinimumLevel);
var appNum = _environment?.ApplicationNumber ?? 0;
_console?.ConsoleCommandResponse($"[Application {appNum}], Debug level set to {consoleLoggingLevelSwitch.MinimumLevel}\r\n");
_console?.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
var err = CrestronDataStoreStatic.SetLocalIntValue(LevelStoreKey, (int)level);
CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}");
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
CrestronConsole.PrintLine($"Error saving console debug level setting: {err}");
if (_dataStore != null && !_dataStore.SetLocalInt(LevelStoreKey, (int)level))
_console?.PrintLine($"Error saving console debug level setting");
else
_console?.ConsoleCommandResponse($"Store result: {(int)level}");
}
/// <summary>
@ -481,10 +500,8 @@ public static class Debug
{
websocketLoggingLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {error}", err);
if (_dataStore != null && !_dataStore.SetLocalUint(WebSocketLevelStoreKey, (uint)level))
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting");
LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", websocketLoggingLevelSwitch.MinimumLevel);
}
@ -498,10 +515,8 @@ public static class Debug
{
errorLogLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting: {error}", err);
if (_dataStore != null && !_dataStore.SetLocalUint(ErrorLogLevelStoreKey, (uint)level))
LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting");
LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", errorLogLevelSwitch.MinimumLevel);
}
@ -513,10 +528,8 @@ public static class Debug
{
fileLoggingLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(FileLevelStoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err);
if (_dataStore != null && !_dataStore.SetLocalUint(FileLevelStoreKey, (uint)level))
LogMessage(LogEventLevel.Information, "Error saving File debug level setting");
LogMessage(LogEventLevel.Information, "File debug level set to {0}", fileLoggingLevelSwitch.MinimumLevel);
}
@ -531,7 +544,7 @@ public static class Debug
{
if (string.IsNullOrEmpty(stateString.Trim()))
{
CrestronConsole.ConsoleCommandResponse("DoNotLoadOnNextBoot = {0}", DoNotLoadConfigOnNextBoot);
_console?.ConsoleCommandResponse($"DoNotLoadOnNextBoot = {DoNotLoadConfigOnNextBoot}");
return;
}
@ -539,7 +552,7 @@ public static class Debug
}
catch
{
CrestronConsole.ConsoleCommandResponse("Usage: donotloadonnextboot:P [true/false]");
_console?.ConsoleCommandResponse("Usage: donotloadonnextboot:P [true/false]");
}
}
@ -655,10 +668,8 @@ public static class Debug
{
DoNotLoadConfigOnNextBoot = state;
var err = CrestronDataStoreStatic.SetLocalBoolValue(DoNotLoadOnNextBootKey, state);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogError("Error saving console debug level setting: {err}", err);
if (_dataStore != null && !_dataStore.SetLocalBool(DoNotLoadOnNextBootKey, state))
LogError("Error saving DoNotLoadConfigOnNextBoot setting");
LogInformation("Do Not Load Config on Next Boot set to {state}", DoNotLoadConfigOnNextBoot);
}
@ -668,9 +679,10 @@ public static class Debug
/// </summary>
public static void ShowDebugLog(string s)
{
if (_environment == null) return; // CrestronLogger not available in test environments
var loglist = CrestronLogger.PrintTheLog(s.ToLower() == "all");
foreach (var l in loglist)
CrestronConsole.ConsoleCommandResponse(l + CrestronEnvironment.NewLine);
_console?.ConsoleCommandResponse(l + CrestronEnvironment.NewLine);
}
/// <summary>

View file

@ -50,6 +50,9 @@
<PackageReference Include="SSH.NET" Version="2025.0.0" />
<PackageReference Include="WebSocketSharp-netstandard" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PepperDash.Core.Abstractions\PepperDash.Core.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Comm\._GenericSshClient.cs" />
<Compile Remove="Comm\._GenericTcpIpClient.cs" />