diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IInitializationExceptions.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IInitializationExceptions.cs new file mode 100644 index 00000000..55ce51ab --- /dev/null +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IInitializationExceptions.cs @@ -0,0 +1,16 @@ + + +using System.Collections.Generic; + +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; + +/// +/// Defines the contract for devices that can report initialization exceptions. +/// +public interface IInitializationExceptions +{ + /// + /// Gets a list of exceptions that occurred during the initialization of the program. + /// + List InitializationExceptions { get; } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs b/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs index f661a7ff..a1b81630 100644 --- a/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs +++ b/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs @@ -184,6 +184,11 @@ public class EssentialsWebApi : EssentialsDevice Name = "Get Routing Devices and TieLines", RouteHandler = new GetRoutingDevicesAndTieLinesHandler() }, + new HttpCwsRoute("initializationExceptions") + { + Name = "Get Initialization Exceptions", + RouteHandler = new GetInitializationExceptionsRequestHandler() + } }; AddRoute(routes); @@ -296,4 +301,5 @@ public class EssentialsWebApi : EssentialsDevice Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50)); } -} \ No newline at end of file +} + diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetInitializationExceptionRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetInitializationExceptionRequestHandler.cs new file mode 100644 index 00000000..c0d77e41 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetInitializationExceptionRequestHandler.cs @@ -0,0 +1,50 @@ + +using Crestron.SimplSharp.WebScripting; +using System.Collections.Generic; +using System.Text; +using PepperDash.Essentials.Core.DeviceTypeInterfaces; +using PepperDash.Core.Web.RequestHandlers; +using System.Linq; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers; + +/// +/// Request handler for retrieving initialization exceptions from the control system. +/// +public class GetInitializationExceptionsRequestHandler : WebApiBaseRequestHandler +{ + + /// + /// Initializes a new instance of the class. + /// + public GetInitializationExceptionsRequestHandler() : base(true) { } + + /// + protected override void HandleGet(HttpCwsContext context) + { + var exceptions = (Global.ControlSystem as IInitializationExceptions)?.InitializationExceptions ?? new List(); + + var response = Newtonsoft.Json.JsonConvert.SerializeObject(new + { + Exceptions = exceptions.Select(e => new + { + Message = e.Message, + StackTrace = e.StackTrace, + Type = e.GetType().FullName, + InnerException = e.InnerException != null ? new + { + Message = e.InnerException.Message, + StackTrace = e.InnerException.StackTrace, + Type = e.InnerException.GetType().FullName + } : null + }).ToList() + }); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.Write(response, false); + context.Response.End(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index 01859cb2..5a6b8777 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -18,6 +18,8 @@ using Timeout = Crestron.SimplSharp.Timeout; using Serilog.Events; using System.Threading.Tasks; using PepperDash.Essentials.Core.Web; +using System.Collections.Generic; +using PepperDash.Essentials.Core.DeviceTypeInterfaces; namespace PepperDash.Essentials; @@ -28,12 +30,19 @@ namespace PepperDash.Essentials; /// This class extends and serves as the entry point for the control /// system. It manages the initialization of devices, rooms, tie lines, and other system components. Additionally, it /// provides methods for platform determination, configuration loading, and system teardown. -public class ControlSystem : CrestronControlSystem, ILoadConfig +public class ControlSystem : CrestronControlSystem, ILoadConfig, IInitializationExceptions { private Timer startTimer; private ManualResetEventSlim initializeEvent; private const long StartupTime = 500; + /// + /// List of exceptions that occurred during initialization. + /// This can be used to report issues with loading devices or tie lines without crashing the entire system, + /// which allows for partial functionality in cases where some components are misconfigured or have issues. + /// + public List InitializationExceptions { get; private set; } = new List(); + /// /// Initializes a new instance of the class, setting up the system's global state and /// dependencies. @@ -457,6 +466,7 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig } catch (Exception e) { + InitializationExceptions.Add(e); Debug.LogMessage(e, "ERROR: Creating device {deviceKey:l}. Skipping device.", args: new[] { devConf.Key }); } } @@ -482,12 +492,22 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig return; } - foreach (var tieLineConfig in ConfigReader.ConfigObject.TieLines) + try { - var newTL = tieLineConfig.GetTieLine(); - if (newTL != null) - tlc.Add(newTL); + + foreach (var tieLineConfig in ConfigReader.ConfigObject.TieLines) + { + var newTL = tieLineConfig.GetTieLine(); + if (newTL != null) + tlc.Add(newTL); + } } + catch (Exception e) + { + InitializationExceptions.Add(e); + Debug.LogMessage(e, "ERROR: Creating tie line. Skipping tie line."); + } + Debug.LogMessage(LogEventLevel.Information, "All Tie Lines Loaded."); @@ -796,6 +816,7 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig } catch (Exception ex) { + InitializationExceptions.Add(ex); Debug.LogMessage(ex, "Exception loading room {roomKey}:{roomType}", null, roomConfig.Key, roomConfig.Type); continue; }