diff --git a/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs b/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs index 5cd921b8..528e9536 100644 --- a/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs +++ b/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs @@ -8,500 +8,507 @@ using System.Linq; using System.Text.RegularExpressions; -namespace PepperDash.Essentials.Core +namespace PepperDash.Essentials.Core; + +public static class DeviceManager { - public static class DeviceManager + public static event EventHandler AllDevicesActivated; + public static event EventHandler AllDevicesRegistered; + public static event EventHandler AllDevicesInitialized; + + private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection(); + + //public static List Devices { get { return _Devices; } } + //static List _Devices = new List(); + + private static readonly Dictionary Devices = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Returns a copy of all the devices in a list + /// + 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. + /// The instance representing the control system to initialize. + public static void Initialize(CrestronControlSystem cs) { - public static event EventHandler AllDevicesActivated; - public static event EventHandler AllDevicesRegistered; - public static event EventHandler AllDevicesInitialized; + AddDeviceEnabled = true; - private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection(); - private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true); + CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(ListDevices, "devlist", "Lists current managed devices", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive", + "Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator); - //public static List Devices { get { return _Devices; } } - //static List _Devices = new List(); + CrestronConsole.AddNewConsoleCommand(SetDeviceStreamDebugging, "setdevicestreamdebug", "set comm debug [deviceKey] [off/rx/tx/both] ([minutes])", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => DisableAllDeviceStreamDebugging(), "disableallstreamdebug", "disables stream debugging on all devices", ConsoleAccessLevelEnum.AccessOperator); + } - private static readonly Dictionary Devices = new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Returns a copy of all the devices in a list - /// - public static List AllDevices { get { return new List(Devices.Values); } } - - public static bool AddDeviceEnabled; - - public static void Initialize(CrestronControlSystem cs) + /// + /// Calls activate steps on all Device class items + /// + public static void ActivateAll() + { + try { - AddDeviceEnabled = true; - CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(ListDevices, "devlist", "Lists current managed devices", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive", - "Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator); + OnAllDevicesRegistered(); - CrestronConsole.AddNewConsoleCommand(SetDeviceStreamDebugging, "setdevicestreamdebug", "set comm debug [deviceKey] [off/rx/tx/both] ([minutes])", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => DisableAllDeviceStreamDebugging(), "disableallstreamdebug", "disables stream debugging on all devices", ConsoleAccessLevelEnum.AccessOperator); - } - - /// - /// Calls activate steps on all Device class items - /// - public static void ActivateAll() - { - try - { - OnAllDevicesRegistered(); - - DeviceCriticalSection.Enter(); - AddDeviceEnabled = false; - // PreActivate all devices - Debug.LogMessage(LogEventLevel.Information, "****PreActivation starting...****"); - foreach (var d in Devices.Values) - { - try - { - if (d is Device) - (d as Device).PreActivate(); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PreActivation failure: {0}", e.Message, d.Key); - Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); - } - } - Debug.LogMessage(LogEventLevel.Information, "****PreActivation complete****"); - Debug.LogMessage(LogEventLevel.Information, "****Activation starting...****"); - - // Activate all devices - foreach (var d in Devices.Values) - { - try - { - if (d is Device) - (d as Device).Activate(); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} Activation failure: {0}", e.Message, d.Key); - Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); - } - } - - Debug.LogMessage(LogEventLevel.Information, "****Activation complete****"); - Debug.LogMessage(LogEventLevel.Information, "****PostActivation starting...****"); - - // PostActivate all devices - foreach (var d in Devices.Values) - { - try - { - if (d is Device) - (d as Device).PostActivate(); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key); - Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); - } - } - - Debug.LogMessage(LogEventLevel.Information, "****PostActivation complete****"); - - OnAllDevicesActivated(); - } - finally - { - DeviceCriticalSection.Leave(); - } - } - - private static void DeviceManager_Initialized(object sender, EventArgs e) - { - var allInitialized = Devices.Values.OfType().All(d => d.IsInitialized); - - if (allInitialized) - { - Debug.LogMessage(LogEventLevel.Information, "****All Devices Initalized****"); - - OnAllDevicesInitialized(); - } - } - - private static void OnAllDevicesActivated() - { - var handler = AllDevicesActivated; - if (handler != null) - { - handler(null, new EventArgs()); - } - } - - private static void OnAllDevicesRegistered() - { - var handler = AllDevicesRegistered; - if (handler != null) - { - handler(null, new EventArgs()); - } - } - - private static void OnAllDevicesInitialized() - { - var handler = AllDevicesInitialized; - if (handler != null) - { - handler(null, new EventArgs()); - } - } - - /// - /// Calls activate on all Device class items - /// - public static void DeactivateAll() - { - try - { - DeviceCriticalSection.Enter(); - foreach (var d in Devices.Values.OfType()) - { - d.Deactivate(); - } - } - finally - { - DeviceCriticalSection.Leave(); - } - } - - //static void ListMethods(string devKey) - //{ - // var dev = GetDeviceForKey(devKey); - // if(dev != null) - // { - // var type = dev.GetType().GetType(); - // var methods = type.GetMethods(BindingFlags.Public|BindingFlags.Instance); - // var sb = new StringBuilder(); - // sb.AppendLine(string.Format("{2} methods on [{0}] ({1}):", dev.Key, type.Name, methods.Length)); - // foreach (var m in methods) - // { - // sb.Append(string.Format("{0}(", m.Name)); - // var pars = m.GetParameters(); - // foreach (var p in pars) - // sb.Append(string.Format("({1}){0} ", p.Name, p.ParameterType.Name)); - // sb.AppendLine(")"); - // } - // CrestronConsole.ConsoleCommandResponse(sb.ToString()); - // } - //} - - private static void ListDevices(string s) - { - Debug.LogMessage(LogEventLevel.Information, "{0} Devices registered with Device Manager:", Devices.Count); - var sorted = Devices.Values.ToList(); - sorted.Sort((a, b) => a.Key.CompareTo(b.Key)); - - foreach (var d in sorted) - { - var name = d is IKeyName ? (d as IKeyName).Name : "---"; - Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name); - } - } - - private static void ListDeviceFeedbacks(string devKey) - { - var dev = GetDeviceForKey(devKey); - if (dev == null) - { - Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey); - return; - } - if (!(dev is IHasFeedback statusDev)) - { - Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey); - return; - } - statusDev.DumpFeedbacksToConsole(true); - } - - //static void ListDeviceCommands(string devKey) - //{ - // var dev = GetDeviceForKey(devKey); - // if (dev == null) - // { - // Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey); - // return; - // } - // Debug.LogMessage(LogEventLevel.Information, "This needs to be reworked. Stay tuned.", devKey); - //} - - private static void ListDeviceCommStatuses(string input) - { - - foreach (var dev in Devices.Values.OfType()) - { - CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}\r\n"); - } - } - - - //static void DoDeviceCommand(string command) - //{ - // Debug.LogMessage(LogEventLevel.Information, "Not yet implemented. Stay tuned"); - //} - - public static void AddDevice(IKeyed newDev) - { - try - { - if (!DeviceCriticalSection.TryEnter()) - { - Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again"); - return; - } - // Check for device with same key - //var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase)); - ////// If it exists, remove or warn?? - //if (existingDevice != null) - - if (!AddDeviceEnabled) - { - Debug.LogMessage(LogEventLevel.Information, "All devices have been activated. Adding new devices is not allowed."); - return; - } - - if (Devices.ContainsKey(newDev.Key)) - { - Debug.LogMessage(LogEventLevel.Information, newDev, "WARNING: A device with this key already exists. Not added to manager"); - return; - } - Devices.Add(newDev.Key, newDev); - //if (!(_Devices.Contains(newDev))) - // _Devices.Add(newDev); - - if (newDev is EssentialsDevice essentialsDev) - essentialsDev.Initialized += DeviceManager_Initialized; - } - finally - { - DeviceCriticalSection.Leave(); - } - } - - public static void AddDevice(IEnumerable devicesToAdd) - { - try - { - if (!DeviceCriticalSection.TryEnter()) - { - Debug.LogMessage(LogEventLevel.Information, - "Currently unable to add devices to Device Manager. Please try again"); - return; - } - if (!AddDeviceEnabled) - { - Debug.LogMessage(LogEventLevel.Information, - "All devices have been activated. Adding new devices is not allowed."); - return; - } - - foreach (var dev in devicesToAdd) - { - try - { - Devices.Add(dev.Key, dev); - } - catch (ArgumentException ex) - { - Debug.LogMessage(LogEventLevel.Information, "Error adding device with key {0} to Device Manager: {1}\r\nStack Trace: {2}", - dev.Key, ex.Message, ex.StackTrace); - } - } - } - finally - { - DeviceCriticalSection.Leave(); - } - } - - public static void RemoveDevice(IKeyed newDev) - { - try - { - DeviceCriticalSection.Enter(); - if (newDev == null) - return; - if (Devices.ContainsKey(newDev.Key)) - Devices.Remove(newDev.Key); - //if (_Devices.Contains(newDev)) - // _Devices.Remove(newDev); - else - Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key); - } - finally - { - DeviceCriticalSection.Leave(); - } - } - - public static IEnumerable GetDeviceKeys() - { - //return _Devices.Select(d => d.Key).ToList(); - return Devices.Keys; - } - - public static IEnumerable GetDevices() - { - //return _Devices.Select(d => d.Key).ToList(); - return Devices.Values; - } - - public static IKeyed GetDeviceForKey(string key) - { - //return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); - if (key != null && Devices.ContainsKey(key)) - return Devices[key]; - - return null; - } - - /// - /// Console handler that simulates com port data receive - /// - /// - public static void SimulateComReceiveOnDevice(string s) - { - // devcomsim:1 xyzabc - var match = Regex.Match(s, @"(\S*)\s*(.*)"); - if (match.Groups.Count < 3) - { - CrestronConsole.ConsoleCommandResponse(" Format: devsimreceive:P "); - return; - } - //Debug.LogMessage(LogEventLevel.Verbose, "**** {0} - {1} ****", match.Groups[1].Value, match.Groups[2].Value); - - if (!(GetDeviceForKey(match.Groups[1].Value) is ComPortController com)) - { - CrestronConsole.ConsoleCommandResponse("'{0}' is not a comm port device", match.Groups[1].Value); - return; - } - com.SimulateReceive(match.Groups[2].Value); - } - - /// - /// Prints a list of routing inputs and outputs by device key. - /// - /// Device key from which to report data - public static void GetRoutingPorts(string s) - { - var device = GetDeviceForKey(s); - - if (device == null) return; - var inputPorts = ((device as IRoutingInputs) != null) ? (device as IRoutingInputs).InputPorts : null; - var outputPorts = ((device as IRoutingOutputs) != null) ? (device as IRoutingOutputs).OutputPorts : null; - if (inputPorts != null) - { - CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Input Ports:{2}", s, inputPorts.Count, CrestronEnvironment.NewLine); - foreach (var routingInputPort in inputPorts) - { - CrestronConsole.ConsoleCommandResponse("{0}{1}", routingInputPort.Key, CrestronEnvironment.NewLine); - } - } - if (outputPorts == null) return; - CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Output Ports:{2}", s, outputPorts.Count, CrestronEnvironment.NewLine); - foreach (var routingOutputPort in outputPorts) - { - CrestronConsole.ConsoleCommandResponse("{0}{1}", routingOutputPort.Key, CrestronEnvironment.NewLine); - } - } - - /// - /// Attempts to set the debug level of a device - /// - /// - public static void SetDeviceStreamDebugging(string s) - { - 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"); - return; - } - - var args = s.Split(' '); - - var deviceKey = args[0]; - var setting = args[1]; - - var timeout = String.Empty; - - if (args.Length >= 3) - { - timeout = args[2]; - } - - - if (!(GetDeviceForKey(deviceKey) is IStreamDebugging device)) - { - CrestronConsole.ConsoleCommandResponse("Unable to get device with key: {0}", deviceKey); - return; - } - - eStreamDebuggingSetting debugSetting; - - try - { - debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), setting, true); - } - catch - { - CrestronConsole.ConsoleCommandResponse("Unable to convert setting value. Please use off/rx/tx/both"); - return; - } - - if (!string.IsNullOrEmpty(timeout)) + DeviceCriticalSection.Enter(); + AddDeviceEnabled = false; + // PreActivate all devices + Debug.LogMessage(LogEventLevel.Information, "****PreActivation starting...****"); + foreach (var d in Devices.Values) { try - { - var min = Convert.ToUInt32(timeout); - - device.StreamDebugging.SetDebuggingWithSpecificTimeout(debugSetting, min); - CrestronConsole.ConsoleCommandResponse("Device: '{0}' debug level set to {1} for {2} minutes", deviceKey, debugSetting, min); - + { + if (d is Device) + (d as Device).PreActivate(); } catch (Exception e) { - CrestronConsole.ConsoleCommandResponse("Unable to convert minutes or settings value. Please use an integer value for minutes. Error: {0}", e); + Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PreActivation failure: {0}", e.Message, d.Key); + Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); } } - else + Debug.LogMessage(LogEventLevel.Information, "****PreActivation complete****"); + Debug.LogMessage(LogEventLevel.Information, "****Activation starting...****"); + + // Activate all devices + foreach (var d in Devices.Values) { - device.StreamDebugging.SetDebuggingWithDefaultTimeout(debugSetting); - CrestronConsole.ConsoleCommandResponse("Device: '{0}' debug level set to {1} for default time (30 minutes)", deviceKey, debugSetting); + try + { + if (d is Device) + (d as Device).Activate(); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} Activation failure: {0}", e.Message, d.Key); + Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); + } + } + + Debug.LogMessage(LogEventLevel.Information, "****Activation complete****"); + Debug.LogMessage(LogEventLevel.Information, "****PostActivation starting...****"); + + // PostActivate all devices + foreach (var d in Devices.Values) + { + try + { + if (d is Device) + (d as Device).PostActivate(); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key); + Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); + } + } + + Debug.LogMessage(LogEventLevel.Information, "****PostActivation complete****"); + + OnAllDevicesActivated(); + } + finally + { + DeviceCriticalSection.Leave(); + } + } + + private static void DeviceManager_Initialized(object sender, EventArgs e) + { + var allInitialized = Devices.Values.OfType().All(d => d.IsInitialized); + + if (allInitialized) + { + Debug.LogMessage(LogEventLevel.Information, "****All Devices Initalized****"); + + OnAllDevicesInitialized(); + } + } + + private static void OnAllDevicesActivated() + { + var handler = AllDevicesActivated; + if (handler != null) + { + handler(null, new EventArgs()); + } + } + + private static void OnAllDevicesRegistered() + { + var handler = AllDevicesRegistered; + if (handler != null) + { + handler(null, new EventArgs()); + } + } + + private static void OnAllDevicesInitialized() + { + var handler = AllDevicesInitialized; + if (handler != null) + { + handler(null, new EventArgs()); + } + } + + /// + /// Calls activate on all Device class items + /// + public static void DeactivateAll() + { + try + { + DeviceCriticalSection.Enter(); + foreach (var d in Devices.Values.OfType()) + { + d.Deactivate(); } } - - /// - /// Sets stream debugging settings to off for all devices - /// - public static void DisableAllDeviceStreamDebugging() + finally { - foreach (var device in AllDevices) + DeviceCriticalSection.Leave(); + } + } + + //static void ListMethods(string devKey) + //{ + // var dev = GetDeviceForKey(devKey); + // if(dev != null) + // { + // var type = dev.GetType().GetType(); + // var methods = type.GetMethods(BindingFlags.Public|BindingFlags.Instance); + // var sb = new StringBuilder(); + // sb.AppendLine(string.Format("{2} methods on [{0}] ({1}):", dev.Key, type.Name, methods.Length)); + // foreach (var m in methods) + // { + // sb.Append(string.Format("{0}(", m.Name)); + // var pars = m.GetParameters(); + // foreach (var p in pars) + // sb.Append(string.Format("({1}){0} ", p.Name, p.ParameterType.Name)); + // sb.AppendLine(")"); + // } + // CrestronConsole.ConsoleCommandResponse(sb.ToString()); + // } + //} + + private static void ListDevices(string s) + { + Debug.LogMessage(LogEventLevel.Information, "{0} Devices registered with Device Manager:", Devices.Count); + var sorted = Devices.Values.ToList(); + sorted.Sort((a, b) => a.Key.CompareTo(b.Key)); + + foreach (var d in sorted) + { + var name = d is IKeyName ? (d as IKeyName).Name : "---"; + Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name); + } + } + + private static void ListDeviceFeedbacks(string devKey) + { + var dev = GetDeviceForKey(devKey); + if (dev == null) + { + Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey); + return; + } + if (!(dev is IHasFeedback statusDev)) + { + Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey); + return; + } + statusDev.DumpFeedbacksToConsole(true); + } + + //static void ListDeviceCommands(string devKey) + //{ + // var dev = GetDeviceForKey(devKey); + // if (dev == null) + // { + // Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey); + // return; + // } + // Debug.LogMessage(LogEventLevel.Information, "This needs to be reworked. Stay tuned.", devKey); + //} + + private static void ListDeviceCommStatuses(string input) + { + + foreach (var dev in Devices.Values.OfType()) + { + CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}\r\n"); + } + } + + + //static void DoDeviceCommand(string command) + //{ + // Debug.LogMessage(LogEventLevel.Information, "Not yet implemented. Stay tuned"); + //} + + public static void AddDevice(IKeyed newDev) + { + try + { + if (!DeviceCriticalSection.TryEnter()) { - if (device is IStreamDebugging streamDevice) + Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again"); + return; + } + // Check for device with same key + //var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase)); + ////// If it exists, remove or warn?? + //if (existingDevice != null) + + if (!AddDeviceEnabled) + { + Debug.LogMessage(LogEventLevel.Information, "All devices have been activated. Adding new devices is not allowed."); + return; + } + + if (Devices.ContainsKey(newDev.Key)) + { + Debug.LogMessage(LogEventLevel.Information, newDev, "WARNING: A device with this key already exists. Not added to manager"); + return; + } + Devices.Add(newDev.Key, newDev); + //if (!(_Devices.Contains(newDev))) + // _Devices.Add(newDev); + + if (newDev is EssentialsDevice essentialsDev) + essentialsDev.Initialized += DeviceManager_Initialized; + } + finally + { + DeviceCriticalSection.Leave(); + } + } + + public static void AddDevice(IEnumerable devicesToAdd) + { + try + { + if (!DeviceCriticalSection.TryEnter()) + { + Debug.LogMessage(LogEventLevel.Information, + "Currently unable to add devices to Device Manager. Please try again"); + return; + } + if (!AddDeviceEnabled) + { + Debug.LogMessage(LogEventLevel.Information, + "All devices have been activated. Adding new devices is not allowed."); + return; + } + + foreach (var dev in devicesToAdd) + { + try { - streamDevice.StreamDebugging.SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting.Off); + Devices.Add(dev.Key, dev); } + catch (ArgumentException ex) + { + Debug.LogMessage(LogEventLevel.Information, "Error adding device with key {0} to Device Manager: {1}\r\nStack Trace: {2}", + dev.Key, ex.Message, ex.StackTrace); + } + } + } + finally + { + DeviceCriticalSection.Leave(); + } + } + + public static void RemoveDevice(IKeyed newDev) + { + try + { + DeviceCriticalSection.Enter(); + if (newDev == null) + return; + if (Devices.ContainsKey(newDev.Key)) + Devices.Remove(newDev.Key); + //if (_Devices.Contains(newDev)) + // _Devices.Remove(newDev); + else + Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key); + } + finally + { + DeviceCriticalSection.Leave(); + } + } + + public static IEnumerable GetDeviceKeys() + { + //return _Devices.Select(d => d.Key).ToList(); + return Devices.Keys; + } + + public static IEnumerable GetDevices() + { + //return _Devices.Select(d => d.Key).ToList(); + return Devices.Values; + } + + public static IKeyed GetDeviceForKey(string key) + { + //return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); + if (key != null && Devices.ContainsKey(key)) + return Devices[key]; + + return null; + } + + /// + /// Console handler that simulates com port data receive + /// + /// + public static void SimulateComReceiveOnDevice(string s) + { + // devcomsim:1 xyzabc + var match = Regex.Match(s, @"(\S*)\s*(.*)"); + if (match.Groups.Count < 3) + { + CrestronConsole.ConsoleCommandResponse(" Format: devsimreceive:P "); + return; + } + //Debug.LogMessage(LogEventLevel.Verbose, "**** {0} - {1} ****", match.Groups[1].Value, match.Groups[2].Value); + + if (!(GetDeviceForKey(match.Groups[1].Value) is ComPortController com)) + { + CrestronConsole.ConsoleCommandResponse("'{0}' is not a comm port device", match.Groups[1].Value); + return; + } + com.SimulateReceive(match.Groups[2].Value); + } + + /// + /// Prints a list of routing inputs and outputs by device key. + /// + /// Device key from which to report data + public static void GetRoutingPorts(string s) + { + var device = GetDeviceForKey(s); + + if (device == null) return; + var inputPorts = ((device as IRoutingInputs) != null) ? (device as IRoutingInputs).InputPorts : null; + var outputPorts = ((device as IRoutingOutputs) != null) ? (device as IRoutingOutputs).OutputPorts : null; + if (inputPorts != null) + { + CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Input Ports:{2}", s, inputPorts.Count, CrestronEnvironment.NewLine); + foreach (var routingInputPort in inputPorts) + { + CrestronConsole.ConsoleCommandResponse("{0}{1}", routingInputPort.Key, CrestronEnvironment.NewLine); + } + } + if (outputPorts == null) return; + CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Output Ports:{2}", s, outputPorts.Count, CrestronEnvironment.NewLine); + foreach (var routingOutputPort in outputPorts) + { + CrestronConsole.ConsoleCommandResponse("{0}{1}", routingOutputPort.Key, CrestronEnvironment.NewLine); + } + } + + /// + /// Attempts to set the debug level of a device + /// + /// + public static void SetDeviceStreamDebugging(string s) + { + 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"); + return; + } + + var args = s.Split(' '); + + var deviceKey = args[0]; + var setting = args[1]; + + var timeout = String.Empty; + + if (args.Length >= 3) + { + timeout = args[2]; + } + + + if (!(GetDeviceForKey(deviceKey) is IStreamDebugging device)) + { + CrestronConsole.ConsoleCommandResponse("Unable to get device with key: {0}", deviceKey); + return; + } + + eStreamDebuggingSetting debugSetting; + + try + { + debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), setting, true); + } + catch + { + CrestronConsole.ConsoleCommandResponse("Unable to convert setting value. Please use off/rx/tx/both"); + return; + } + + if (!string.IsNullOrEmpty(timeout)) + { + try + { + var min = Convert.ToUInt32(timeout); + + device.StreamDebugging.SetDebuggingWithSpecificTimeout(debugSetting, min); + CrestronConsole.ConsoleCommandResponse("Device: '{0}' debug level set to {1} for {2} minutes", deviceKey, debugSetting, min); + + } + catch (Exception e) + { + CrestronConsole.ConsoleCommandResponse("Unable to convert minutes or settings value. Please use an integer value for minutes. Error: {0}", e); + } + } + else + { + device.StreamDebugging.SetDebuggingWithDefaultTimeout(debugSetting); + CrestronConsole.ConsoleCommandResponse("Device: '{0}' debug level set to {1} for default time (30 minutes)", deviceKey, debugSetting); + } + } + + /// + /// Sets stream debugging settings to off for all devices + /// + public static void DisableAllDeviceStreamDebugging() + { + foreach (var device in AllDevices) + { + if (device is IStreamDebugging streamDevice) + { + streamDevice.StreamDebugging.SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting.Off); } } } diff --git a/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs b/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs index 07bcc56a..e62e771b 100644 --- a/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs @@ -9,190 +9,300 @@ using Serilog.Events; using System; using System.Collections.Generic; using System.Linq; +using System.ComponentModel.DataAnnotations; +using System.IO; -namespace PepperDash.Essentials.Core +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. +/// +/// 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 { - public class DeviceFactoryWrapper - { - public Type Type { get; set; } - public string Description { get; set; } - public Func FactoryMethod { get; set; } + /// + /// Gets or sets the type associated with the current instance. + /// + public Type Type { get; set; } - public DeviceFactoryWrapper() - { - Type = null; - Description = "Not Available"; - } + /// + /// 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 . + /// + /// 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. + /// + /// The property is initialized to , and the property is set to "Not Available". + public DeviceFactoryWrapper() + { + Type = null; + Description = "Not Available"; } +} - public class DeviceFactory - { - public DeviceFactory() - { - var assy = Assembly.GetExecutingAssembly(); - PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy); +/// +/// 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 +/// collection of factory methods that are keyed by device type names, allowing for extensibility through plugins. This +/// 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. + /// + /// 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 + /// created and passed to the LoadDeviceFactories method for further processing. If a type cannot be + /// 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"); - var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); - - if (types != null) - { - foreach (var type in types) - { - try - { - var factory = (IDeviceFactory)Activator.CreateInstance(type); - factory.LoadTypeFactories(); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, "Unable to load type: '{1}' DeviceFactory: {0}", e, type.Name); - } - } - } - } - - /// - /// A dictionary of factory methods, keyed by config types, added by plugins. - /// These methods are looked up and called by GetDevice in this class. - /// - static Dictionary FactoryMethods = - new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Adds a plugin factory method - /// - /// - /// - public static void AddFactoryForType(string typeName, Func method) - { - //Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName); - DeviceFactory.FactoryMethods.Add(typeName, new DeviceFactoryWrapper() { FactoryMethod = method}); - } - - public static void AddFactoryForType(string typeName, string description, Type Type, Func method) - { - //Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName); - - if(FactoryMethods.ContainsKey(typeName)) - { - Debug.LogMessage(LogEventLevel.Information, "Unable to add type: '{0}'. Already exists in DeviceFactory", typeName); - return; - } - - var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method }; - DeviceFactory.FactoryMethods.Add(typeName, wrapper); - } - - private static void CheckForSecrets(IEnumerable obj) - { - foreach (var prop in obj.Where(prop => prop.Value as JObject != null)) - { - if (prop.Name.ToLower() == "secret") - { - var secret = GetSecret(prop.Children().First().ToObject()); - //var secret = GetSecret(JsonConvert.DeserializeObject(prop.Children().First().ToString())); - prop.Parent.Replace(secret); - } - var recurseProp = prop.Value as JObject; - if (recurseProp == null) return; - CheckForSecrets(recurseProp.Properties()); - } - } - - private static string GetSecret(SecretsPropertiesConfig data) - { - var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider); - if (secretProvider == null) return null; - var secret = secretProvider.GetSecret(data.Key); - 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); - return String.Empty; - } - - - /// - /// The factory method for Core "things". Also iterates the Factory methods that have - /// been loaded from plugins - /// - /// - /// - public static IKeyed GetDevice(DeviceConfig dc) + foreach(var assembly in programAssemblies) { try { - Debug.LogMessage(LogEventLevel.Information, "Loading '{0}' from Essentials Core", dc.Type); - - var localDc = new DeviceConfig(dc); - - var key = localDc.Key; - var name = localDc.Name; - var type = localDc.Type; - var properties = localDc.Properties; - //var propRecurse = properties; - - var typeName = localDc.Type.ToLower(); - - - var jObject = properties as JObject; - if (jObject != null) - { - var jProp = jObject.Properties(); - - CheckForSecrets(jProp); - } - - Debug.LogMessage(LogEventLevel.Verbose, "typeName = {0}", typeName); - // Check for types that have been added by plugin dlls. - return !FactoryMethods.ContainsKey(typeName) ? null : FactoryMethods[typeName].FactoryMethod(localDc); + Assembly.LoadFrom(assembly); } - catch (Exception ex) + catch (Exception e) { - Debug.LogMessage(ex, "Exception occurred while creating device {0}: {1}", null, dc.Key, ex.Message); - return null; + Debug.LogError("Unable to load assembly: {assemblyName} - {message}", assembly, e.Message); } } - /// - /// Prints the type names and associated metadata from the FactoryMethods collection. - /// - /// - public static void GetDeviceFactoryTypes(string filter) + 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); + } + } + + } + } + + /// + /// Loads device factories from the specified plugin device factory and registers them for use. + /// + /// This method retrieves metadata from the provided , including + /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type + /// names are converted to lowercase for registration. + /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. + private static void LoadDeviceFactories(IDeviceFactory deviceFactory) + { + foreach (var typeName in deviceFactory.TypeNames) { - var types = !string.IsNullOrEmpty(filter) - ? FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value) - : FactoryMethods; + //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); + var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; + string description = descriptionAttribute[0].Description; + 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. + /// + private static readonly Dictionary FactoryMethods = + new(StringComparer.OrdinalIgnoreCase); - CrestronConsole.ConsoleCommandResponse("Device Types:"); + /// + /// 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}); + } - foreach (var type in types.OrderBy(t => t.Key)) + /// + /// 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)) + { + Debug.LogInformation("Unable to add type: '{typeName}'. Already exists in DeviceFactory", typeName); + return; + } + + var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method }; + + FactoryMethods.Add(typeName, wrapper); + } + + private static void CheckForSecrets(IEnumerable obj) + { + foreach (var prop in obj.Where(prop => prop.Value as JObject != null)) + { + if (prop.Name.Equals("secret", StringComparison.CurrentCultureIgnoreCase)) { - var description = type.Value.Description; - var Type = "Not Specified by Plugin"; + var secret = GetSecret(prop.Children().First().ToObject()); + + prop.Parent.Replace(secret); + } + if (prop.Value is not JObject recurseProp) return; + CheckForSecrets(recurseProp.Properties()); + } + } - if (type.Value.Type != null) - { - Type = type.Value.Type.FullName; - } + private static string GetSecret(SecretsPropertiesConfig data) + { + var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider); + if (secretProvider == null) return null; + var secret = secretProvider.GetSecret(data.Key); + 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); + return string.Empty; + } - CrestronConsole.ConsoleCommandResponse( - @"Type: '{0}' + + /// + /// 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; + var name = localDc.Name; + var type = localDc.Type; + var properties = localDc.Properties; + + var typeName = localDc.Type.ToLower(); + + if (properties is JObject jObject) + { + 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. + return wrapper.FactoryMethod(localDc); + } + catch (Exception ex) + { + Debug.LogError(ex, "Exception occurred while creating device {0}: {1}", null, dc.Key, ex.Message); + return null; + } + } + + /// + /// 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) + { + var types = !string.IsNullOrEmpty(filter) + ? FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value) + : FactoryMethods; + + CrestronConsole.ConsoleCommandResponse("Device Types:"); + + foreach (var type in types.OrderBy(t => t.Key)) + { + var description = type.Value.Description; + var Type = "Not Specified by Plugin"; + + if (type.Value.Type != null) + { + Type = type.Value.Type.FullName; + } + + CrestronConsole.ConsoleCommandResponse( + @"Type: '{0}' Type: '{1}' Description: {2}{3}", type.Key, Type, description, CrestronEnvironment.NewLine); - } } - - /// - /// Returns the device factory dictionary - /// - /// - /// - 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); - } } + + /// + /// 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); + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Factory/ProcessorExtensionDeviceFactory.cs b/src/PepperDash.Essentials.Core/Factory/ProcessorExtensionDeviceFactory.cs index 565a2a48..5a082209 100644 --- a/src/PepperDash.Essentials.Core/Factory/ProcessorExtensionDeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Factory/ProcessorExtensionDeviceFactory.cs @@ -10,143 +10,143 @@ using System.Linq; namespace PepperDash.Essentials.Core; - public class ProcessorExtensionDeviceFactory +public class ProcessorExtensionDeviceFactory +{ + public ProcessorExtensionDeviceFactory() { + var assy = Assembly.GetExecutingAssembly(); + PluginLoader.AddLoadedAssembly(assy.GetName().Name, assy); + + var extensions = assy.GetTypes().Where(ct => typeof(IProcessorExtensionDeviceFactory) + .IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); + + if (extensions != null ) + { + foreach ( var extension in extensions ) + { + try + { + var factory = (IProcessorExtensionDeviceFactory)Activator.CreateInstance(extension); + factory.LoadFactories(); + } + catch( Exception e ) + { + Debug.LogMessage(LogEventLevel.Information, "Unable to load extension device: '{1}' ProcessorExtensionDeviceFactory: {0}", e, extension.Name); + } + } + } + } + + /// + /// A dictionary of factory methods, keyed by config types, added by plugins. + /// These methods are looked up and called by GetDevice in this class. + /// + static Dictionary ProcessorExtensionFactoryMethods = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + + /// + /// Adds a plugin factory method + /// + /// + /// + public static void AddFactoryForType(string extensionName, Func method) { - public ProcessorExtensionDeviceFactory() { - var assy = Assembly.GetExecutingAssembly(); - PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy); + //Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName); + ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, new DeviceFactoryWrapper() { FactoryMethod = method }); + } - var extensions = assy.GetTypes().Where(ct => typeof(IProcessorExtensionDeviceFactory) - .IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); + public static void AddFactoryForType(string extensionName, string description, Type Type, Func method) + { + //Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName); - if (extensions != null ) + if (ProcessorExtensionFactoryMethods.ContainsKey(extensionName)) + { + Debug.LogMessage(LogEventLevel.Information, "Unable to add extension device: '{0}'. Already exists in ProcessorExtensionDeviceFactory", extensionName); + return; + } + + var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method }; + ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, wrapper); + } + + private static void CheckForSecrets(IEnumerable obj) + { + foreach (var prop in obj.Where(prop => prop.Value as Newtonsoft.Json.Linq.JObject != null)) + { + if (prop.Name.ToLower() == "secret") { - foreach ( var extension in extensions ) - { - try - { - var factory = (IProcessorExtensionDeviceFactory)Activator.CreateInstance(extension); - factory.LoadFactories(); - } - catch( Exception e ) - { - Debug.LogMessage(LogEventLevel.Information, "Unable to load extension device: '{1}' ProcessorExtensionDeviceFactory: {0}", e, extension.Name); - } - } + var secret = GetSecret(prop.Children().First().ToObject()); + //var secret = GetSecret(JsonConvert.DeserializeObject(prop.Children().First().ToString())); + prop.Parent.Replace(secret); } + var recurseProp = prop.Value as Newtonsoft.Json.Linq.JObject; + if (recurseProp == null) return; + CheckForSecrets(recurseProp.Properties()); } + } - /// - /// A dictionary of factory methods, keyed by config types, added by plugins. - /// These methods are looked up and called by GetDevice in this class. - /// - static Dictionary ProcessorExtensionFactoryMethods = - new Dictionary(StringComparer.OrdinalIgnoreCase); + private static string GetSecret(SecretsPropertiesConfig data) + { + var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider); + if (secretProvider == null) return null; + var secret = secretProvider.GetSecret(data.Key); + 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); + return String.Empty; + } - - /// - /// Adds a plugin factory method - /// - /// - /// - public static void AddFactoryForType(string extensionName, Func method) + /// + /// The factory method for processor extension devices. Also iterates the Factory methods that have + /// been loaded from plugins + /// + /// + /// + public static IKeyed GetExtensionDevice(DeviceConfig dc) + { + try { - //Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName); - ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, new DeviceFactoryWrapper() { FactoryMethod = method }); - } + Debug.LogMessage(LogEventLevel.Information, "Loading '{0}' from Essentials Core", dc.Type); - public static void AddFactoryForType(string extensionName, string description, Type Type, Func method) - { - //Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName); + var localDc = new DeviceConfig(dc); - if (ProcessorExtensionFactoryMethods.ContainsKey(extensionName)) + var key = localDc.Key; + var name = localDc.Name; + var type = localDc.Type; + var properties = localDc.Properties; + //var propRecurse = properties; + + var typeName = localDc.Type.ToLower(); + + var jObject = properties as Newtonsoft.Json.Linq.JObject; + if (jObject != null) { - Debug.LogMessage(LogEventLevel.Information, "Unable to add extension device: '{0}'. Already exists in ProcessorExtensionDeviceFactory", extensionName); - return; + var jProp = jObject.Properties(); + + CheckForSecrets(jProp); } - var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method }; - ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, wrapper); + Debug.LogMessage(LogEventLevel.Verbose, "typeName = {0}", typeName); + // Check for types that have been added by plugin dlls. + return !ProcessorExtensionFactoryMethods.ContainsKey(typeName) ? null : ProcessorExtensionFactoryMethods[typeName].FactoryMethod(localDc); } - - private static void CheckForSecrets(IEnumerable obj) + catch (Exception ex) { - foreach (var prop in obj.Where(prop => prop.Value as Newtonsoft.Json.Linq.JObject != null)) + Debug.LogMessage(LogEventLevel.Information, "Exception occurred while creating device {0}: {1}", dc.Key, ex.Message); + + Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.StackTrace); + + if (ex.InnerException == null) { - if (prop.Name.ToLower() == "secret") - { - var secret = GetSecret(prop.Children().First().ToObject()); - //var secret = GetSecret(JsonConvert.DeserializeObject(prop.Children().First().ToString())); - prop.Parent.Replace(secret); - } - var recurseProp = prop.Value as Newtonsoft.Json.Linq.JObject; - if (recurseProp == null) return; - CheckForSecrets(recurseProp.Properties()); - } - } - - private static string GetSecret(SecretsPropertiesConfig data) - { - var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider); - if (secretProvider == null) return null; - var secret = secretProvider.GetSecret(data.Key); - 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); - return String.Empty; - } - - /// - /// The factory method for processor extension devices. Also iterates the Factory methods that have - /// been loaded from plugins - /// - /// - /// - public static IKeyed GetExtensionDevice(DeviceConfig dc) - { - try - { - Debug.LogMessage(LogEventLevel.Information, "Loading '{0}' from Essentials Core", dc.Type); - - var localDc = new DeviceConfig(dc); - - var key = localDc.Key; - var name = localDc.Name; - var type = localDc.Type; - var properties = localDc.Properties; - //var propRecurse = properties; - - var typeName = localDc.Type.ToLower(); - - var jObject = properties as Newtonsoft.Json.Linq.JObject; - if (jObject != null) - { - var jProp = jObject.Properties(); - - CheckForSecrets(jProp); - } - - Debug.LogMessage(LogEventLevel.Verbose, "typeName = {0}", typeName); - // Check for types that have been added by plugin dlls. - return !ProcessorExtensionFactoryMethods.ContainsKey(typeName) ? null : ProcessorExtensionFactoryMethods[typeName].FactoryMethod(localDc); - } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Information, "Exception occurred while creating device {0}: {1}", dc.Key, ex.Message); - - Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.StackTrace); - - if (ex.InnerException == null) - { - return null; - } - - Debug.LogMessage(LogEventLevel.Information, "Inner exception while creating device {0}: {1}", dc.Key, - ex.InnerException.Message); - Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.InnerException.StackTrace); return null; } + + Debug.LogMessage(LogEventLevel.Information, "Inner exception while creating device {0}: {1}", dc.Key, + ex.InnerException.Message); + Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.InnerException.StackTrace); + return null; + } } } diff --git a/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs b/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs index 0f1f973f..778ba56f 100644 --- a/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs +++ b/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs @@ -7,33 +7,51 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common +namespace PepperDash.Essentials.Devices.Common; + +public class DeviceFactory { - public class DeviceFactory + + public DeviceFactory() { + var assy = Assembly.GetExecutingAssembly(); + PluginLoader.AddLoadedAssembly(assy.GetName().Name, assy); + + var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); - public DeviceFactory() + if (types != null) { - var assy = Assembly.GetExecutingAssembly(); - PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy); - - var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); - - if (types != null) + foreach (var type in types) { - foreach (var type in types) + try { - try - { - var factory = (IDeviceFactory)Activator.CreateInstance(type); - factory.LoadTypeFactories(); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, "Unable to load type: '{1}' DeviceFactory: {0}", e, type.Name); - } + var factory = (IDeviceFactory)Activator.CreateInstance(type); + LoadDeviceFactories(factory); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Information, "Unable to load type: '{1}' DeviceFactory: {0}", e, type.Name); } } } + } + + /// + /// Loads device factories from the specified plugin device factory and registers them for use. + /// + /// This method retrieves metadata from the provided , including + /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type + /// names are converted to lowercase for registration. + /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. + private static void LoadDeviceFactories(IDeviceFactory deviceFactory) + { + foreach (var typeName in deviceFactory.TypeNames) + { + //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); + var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; + string description = descriptionAttribute[0].Description; + var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; + Core.DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlFactory.cs b/src/PepperDash.Essentials.MobileControl/MobileControlFactory.cs index 58e4ed8a..bbd1d6de 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlFactory.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlFactory.cs @@ -4,36 +4,54 @@ using System; using System.Linq; using System.Reflection; -namespace PepperDash.Essentials +namespace PepperDash.Essentials; + +public class MobileControlFactory { - public class MobileControlFactory + public MobileControlFactory() { - public MobileControlFactory() + var assembly = Assembly.GetExecutingAssembly(); + + PluginLoader.AddLoadedAssembly(assembly.GetName().Name, assembly); + + var types = assembly.GetTypes().Where(t => typeof(IDeviceFactory).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); + + if (types == null) { - var assembly = Assembly.GetExecutingAssembly(); + return; + } - PluginLoader.SetEssentialsAssembly(assembly.GetName().Name, assembly); - - var types = assembly.GetTypes().Where(t => typeof(IDeviceFactory).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); - - if (types == null) + foreach (var type in types) + { + try { - return; + var factory = (IDeviceFactory)Activator.CreateInstance(type); + + LoadDeviceFactories(factory); } - - foreach (var type in types) + catch (Exception ex) { - try - { - var factory = (IDeviceFactory)Activator.CreateInstance(type); - - factory.LoadTypeFactories(); - } - catch (Exception ex) - { - Debug.LogMessage(ex, "Unable to load type '{type}' DeviceFactory: {factory}", null, type.Name); - } + Debug.LogMessage(ex, "Unable to load type '{type}' DeviceFactory: {factory}", null, type.Name); } } } + + /// + /// Loads device factories from the specified plugin device factory and registers them for use. + /// + /// This method retrieves metadata from the provided , including + /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type + /// names are converted to lowercase for registration. + /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. + private static void LoadDeviceFactories(IDeviceFactory deviceFactory) + { + foreach (var typeName in deviceFactory.TypeNames) + { + //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); + var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; + string description = descriptionAttribute[0].Description; + var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; + Core.DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + } + } } diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index 6a926d3e..b42b7aad 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -14,267 +14,268 @@ using System; using System.Linq; using Serilog.Events; using PepperDash.Essentials.Core.Routing; +using System.Threading; +using Timeout = Crestron.SimplSharp.Timeout; -namespace PepperDash.Essentials +namespace PepperDash.Essentials; + +/// +/// Represents the main control system for the application, providing initialization, configuration loading, and device +/// management functionality. +/// +/// 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 + private HttpLogoServer LogoServer; + + private Timer _startTimer; + private ManualResetEventSlim _initializeEvent; + private const long StartupTime = 500; + + /// + /// Initializes a new instance of the class, setting up the system's global state and + /// dependencies. + /// + /// This constructor configures the control system by initializing key components such as the + /// device manager and secrets manager, and sets global properties like the maximum number of user threads and the + /// program initialization state. It also adjusts the error log's minimum debug level based on the device + /// platform. + public ControlSystem() + : base() + + { + try + { + Crestron.SimplSharpPro.CrestronThread.Thread.MaxNumberOfUserThreads = 400; + + Global.ControlSystem = this; + DeviceManager.Initialize(this); + SecretsManager.Initialize(); + SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; + + Debug.SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? LogEventLevel.Warning : LogEventLevel.Verbose); + } + catch (Exception e) + { + Debug.LogError(e, "FATAL INITIALIZE ERROR. System is in an inconsistent state"); + } + } + + /// + /// Initializes the control system and prepares it for operation. + /// + /// This method ensures that all devices in the system are properly registered and initialized + /// before the system is fully operational. If the control system is of a DMPS type, the method waits for all + /// devices to activate, allowing HD-BaseT DM endpoints to register before completing initialization. For non-DMPS + /// systems, initialization proceeds without waiting. + public override void InitializeSystem() { - HttpLogoServer LogoServer; - - private CTimer _startTimer; - private CEvent _initializeEvent; - private const long StartupTime = 500; - - public ControlSystem() - : base() - + // If the control system is a DMPS type, we need to wait to exit this method until all devices have had time to activate + // to allow any HD-BaseT DM endpoints to register first. + bool preventInitializationComplete = Global.ControlSystemIsDmpsType; + if (preventInitializationComplete) { + Debug.LogMessage(LogEventLevel.Debug, "******************* Initializing System **********************"); - Thread.MaxNumberOfUserThreads = 400; - Global.ControlSystem = this; - DeviceManager.Initialize(this); - SecretsManager.Initialize(); - SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; + _startTimer = new Timer(StartSystem, preventInitializationComplete, StartupTime, Timeout.Infinite); - Debug.SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? LogEventLevel.Warning : LogEventLevel.Verbose); + _initializeEvent = new ManualResetEventSlim(false); + + DeviceManager.AllDevicesRegistered += (o, a) => + { + _initializeEvent.Set(); + }; + + _initializeEvent.Wait(30000); + + Debug.LogMessage(LogEventLevel.Debug, "******************* System Initialization Complete **********************"); - // AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; + SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; + } + else + { + _startTimer = new Timer(StartSystem, preventInitializationComplete, StartupTime, Timeout.Infinite); + } + } + + private void StartSystem(object preventInitialization) + { + DeterminePlatform(); + + // Print .NET runtime version + Debug.LogMessage(LogEventLevel.Information, "Running on .NET runtime version: {0}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription); + + if (Debug.DoNotLoadConfigOnNextBoot) + { + CrestronConsole.AddNewConsoleCommand(s => CrestronInvoke.BeginInvoke((o) => GoWithLoad()), "go", "Loads configuration file", + ConsoleAccessLevelEnum.AccessOperator); } - private System.Reflection.Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) + CrestronConsole.AddNewConsoleCommand(PluginLoader.ReportAssemblyVersions, "reportversions", "Reports the versions of the loaded assemblies", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(Core.DeviceFactory.GetDeviceFactoryTypes, "gettypes", "Gets the device types that can be built. Accepts a filter string.", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(BridgeHelper.PrintJoinMap, "getjoinmap", "map(s) for bridge or device on bridge [brKey [devKey]]", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(BridgeHelper.JoinmapMarkdown, "getjoinmapmarkdown" + , "generate markdown of map(s) for bridge or device on bridge [brKey [devKey]]", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(s => Debug.LogMessage(LogEventLevel.Information, "CONSOLE MESSAGE: {0}", s), "appdebugmessage", "Writes message to log", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(s => { - var assemblyName = new System.Reflection.AssemblyName(args.Name).Name; - if (assemblyName == "PepperDash_Core") - { - return System.Reflection.Assembly.LoadFrom("PepperDashCore.dll"); - } + foreach (var tl in TieLineCollection.Default) + CrestronConsole.ConsoleCommandResponse(" {0}{1}", tl, CrestronEnvironment.NewLine); + }, + "listtielines", "Prints out all tie lines", ConsoleAccessLevelEnum.AccessOperator); - if (assemblyName == "PepperDash_Essentials_Core") - { - return System.Reflection.Assembly.LoadFrom("PepperDash.Essentials.Core.dll"); - } + CrestronConsole.AddNewConsoleCommand(s => + { + CrestronConsole.ConsoleCommandResponse + ("Current running configuration. This is the merged system and template configuration" + CrestronEnvironment.NewLine); + CrestronConsole.ConsoleCommandResponse(Newtonsoft.Json.JsonConvert.SerializeObject + (ConfigReader.ConfigObject, Newtonsoft.Json.Formatting.Indented)); + }, "showconfig", "Shows the current running merged config", ConsoleAccessLevelEnum.AccessOperator); - if (assemblyName == "Essentials Devices Common") - { - return System.Reflection.Assembly.LoadFrom("PepperDash.Essentials.Devices.Common.dll"); - } + CrestronConsole.AddNewConsoleCommand(s => + CrestronConsole.ConsoleCommandResponse( + "This system can be found at the following URLs:{2}" + + "System URL: {0}{2}" + + "Template URL: {1}{2}", + ConfigReader.ConfigObject.SystemUrl, + ConfigReader.ConfigObject.TemplateUrl, + CrestronEnvironment.NewLine), + "portalinfo", + "Shows portal URLS from configuration", + ConsoleAccessLevelEnum.AccessOperator); - return null; + + CrestronConsole.AddNewConsoleCommand(DeviceManager.GetRoutingPorts, + "getroutingports", "Reports all routing ports, if any. Requires a device key", ConsoleAccessLevelEnum.AccessOperator); + + + if (!Debug.DoNotLoadConfigOnNextBoot) + { + GoWithLoad(); + return; } - /// - /// Entry point for the program - /// - public override void InitializeSystem() + if (!(bool)preventInitialization) { - // If the control system is a DMPS type, we need to wait to exit this method until all devices have had time to activate - // to allow any HD-BaseT DM endpoints to register first. - bool preventInitializationComplete = Global.ControlSystemIsDmpsType; - if (preventInitializationComplete) + SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; + } + } + + /// + /// Determines if the program is running on a processor (appliance) or server (VC-4). + /// + /// Sets Global.FilePathPrefix and Global.ApplicationDirectoryPathPrefix based on platform + /// + public void DeterminePlatform() + { + try + { + Debug.LogMessage(LogEventLevel.Information, "Determining Platform..."); + + string filePathPrefix; + + var dirSeparator = Global.DirectorySeparator; + + string directoryPrefix; + + directoryPrefix = Directory.GetApplicationRootDirectory(); + + Global.SetAssemblyVersion(PluginLoader.GetAssemblyVersion(Assembly.GetExecutingAssembly())); + + if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Server) // Handles 3-series running Windows CE OS { - Debug.LogMessage(LogEventLevel.Debug, "******************* InitializeSystem() Entering **********************"); - - _startTimer = new CTimer(StartSystem, preventInitializationComplete, StartupTime); - _initializeEvent = new CEvent(true, false); - DeviceManager.AllDevicesRegistered += (o, a) => + string userFolder = "user"; + string nvramFolder = "nvram"; + + Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on {processorSeries:l} Appliance", Global.AssemblyVersion, "4-series"); + //Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{0} on {1} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series"); + + // Check if User/ProgramX exists + if (Directory.Exists(Global.ApplicationDirectoryPathPrefix + dirSeparator + userFolder + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) { - _initializeEvent.Set(); - }; - _initializeEvent.Wait(30000); - Debug.LogMessage(LogEventLevel.Debug, "******************* InitializeSystem() Exiting **********************"); - - SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; + + Debug.LogMessage(LogEventLevel.Information, "{userFolder:l}/program{applicationNumber} directory found", userFolder, InitialParametersClass.ApplicationNumber); + filePathPrefix = directoryPrefix + dirSeparator + userFolder + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + } + // Check if Nvram/Programx exists + else if (Directory.Exists(directoryPrefix + dirSeparator + nvramFolder + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) + { + Debug.LogMessage(LogEventLevel.Information, "{nvramFolder:l}/program{applicationNumber} directory found", nvramFolder, InitialParametersClass.ApplicationNumber); + + filePathPrefix = directoryPrefix + dirSeparator + nvramFolder + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + } + // If neither exists, set path to User/ProgramX + else + { + Debug.LogMessage(LogEventLevel.Information, "{userFolder:l}/program{applicationNumber} directory found", userFolder, InitialParametersClass.ApplicationNumber); + + filePathPrefix = directoryPrefix + dirSeparator + userFolder + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + } + } + else // Handles Linux OS (Virtual Control) + { + //Debug.SetDebugLevel(2); + Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on Virtual Control Server", Global.AssemblyVersion); + + // Set path to User/ + filePathPrefix = directoryPrefix + dirSeparator + "User" + dirSeparator; + } + + Global.SetFilePathPrefix(filePathPrefix); + } + catch (Exception e) + { + Debug.LogMessage(e, "Unable to determine platform due to exception"); + } + } + + /// + /// Begins the process of loading resources including plugins and configuration data + /// + public void GoWithLoad() + { + try + { + Debug.SetDoNotLoadConfigOnNextBoot(false); + + PluginLoader.AddProgramAssemblies(); + + _ = new Core.DeviceFactory(); + + Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration"); + + var filesReady = SetupFilesystem(); + if (filesReady) + { + Debug.LogMessage(LogEventLevel.Information, "Checking for plugins"); + PluginLoader.LoadPlugins(); + + Debug.LogMessage(LogEventLevel.Information, "Folder structure verified. Loading config..."); + if (!ConfigReader.LoadConfig2()) + { + Debug.LogMessage(LogEventLevel.Information, "Essentials Load complete with errors"); + return; + } + + Load(); + Debug.LogMessage(LogEventLevel.Information, "Essentials load complete"); } else { - _startTimer = new CTimer(StartSystem, preventInitializationComplete, StartupTime); - } - } - - private void StartSystem(object preventInitialization) - { - Debug.SetErrorLogMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose); - - DeterminePlatform(); - - // Print .NET runtime version - Debug.LogMessage(LogEventLevel.Information, "Running on .NET runtime version: {0}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription); - - if (Debug.DoNotLoadConfigOnNextBoot) - { - CrestronConsole.AddNewConsoleCommand(s => CrestronInvoke.BeginInvoke((o) => GoWithLoad()), "go", "Loads configuration file", - ConsoleAccessLevelEnum.AccessOperator); - } - - CrestronConsole.AddNewConsoleCommand(PluginLoader.ReportAssemblyVersions, "reportversions", "Reports the versions of the loaded assemblies", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(Core.DeviceFactory.GetDeviceFactoryTypes, "gettypes", "Gets the device types that can be built. Accepts a filter string.", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(BridgeHelper.PrintJoinMap, "getjoinmap", "map(s) for bridge or device on bridge [brKey [devKey]]", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(BridgeHelper.JoinmapMarkdown, "getjoinmapmarkdown" - , "generate markdown of map(s) for bridge or device on bridge [brKey [devKey]]", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(s => Debug.LogMessage(LogEventLevel.Information, "CONSOLE MESSAGE: {0}", s), "appdebugmessage", "Writes message to log", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(s => - { - foreach (var tl in TieLineCollection.Default) - CrestronConsole.ConsoleCommandResponse(" {0}{1}", tl, CrestronEnvironment.NewLine); - }, - "listtielines", "Prints out all tie lines", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(s => - { - CrestronConsole.ConsoleCommandResponse - ("Current running configuration. This is the merged system and template configuration" + CrestronEnvironment.NewLine); - CrestronConsole.ConsoleCommandResponse(Newtonsoft.Json.JsonConvert.SerializeObject - (ConfigReader.ConfigObject, Newtonsoft.Json.Formatting.Indented)); - }, "showconfig", "Shows the current running merged config", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(s => - CrestronConsole.ConsoleCommandResponse( - "This system can be found at the following URLs:{2}" + - "System URL: {0}{2}" + - "Template URL: {1}{2}", - ConfigReader.ConfigObject.SystemUrl, - ConfigReader.ConfigObject.TemplateUrl, - CrestronEnvironment.NewLine), - "portalinfo", - "Shows portal URLS from configuration", - ConsoleAccessLevelEnum.AccessOperator); - - - CrestronConsole.AddNewConsoleCommand(DeviceManager.GetRoutingPorts, - "getroutingports", "Reports all routing ports, if any. Requires a device key", ConsoleAccessLevelEnum.AccessOperator); - - //DeviceManager.AddDevice(new EssentialsWebApi("essentialsWebApi", "Essentials Web API")); - - if (!Debug.DoNotLoadConfigOnNextBoot) - { - GoWithLoad(); - return; - } - - if (!(bool)preventInitialization) - { - SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; - } - } - - /// - /// Determines if the program is running on a processor (appliance) or server (VC-4). - /// - /// Sets Global.FilePathPrefix and Global.ApplicationDirectoryPathPrefix based on platform - /// - public void DeterminePlatform() - { - try - { - Debug.LogMessage(LogEventLevel.Information, "Determining Platform..."); - - string filePathPrefix; - - var dirSeparator = Global.DirectorySeparator; - - string directoryPrefix; - - directoryPrefix = Directory.GetApplicationRootDirectory(); - - Global.SetAssemblyVersion(PluginLoader.GetAssemblyVersion(Assembly.GetExecutingAssembly())); - - if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Server) // Handles 3-series running Windows CE OS - { - string userFolder = "user"; - string nvramFolder = "nvram"; - - Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on {processorSeries:l} Appliance", Global.AssemblyVersion, "4-series"); - //Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{0} on {1} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series"); - - // Check if User/ProgramX exists - if (Directory.Exists(Global.ApplicationDirectoryPathPrefix + dirSeparator + userFolder - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) - { - - Debug.LogMessage(LogEventLevel.Information, "{userFolder:l}/program{applicationNumber} directory found", userFolder, InitialParametersClass.ApplicationNumber); - filePathPrefix = directoryPrefix + dirSeparator + userFolder - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; - } - // Check if Nvram/Programx exists - else if (Directory.Exists(directoryPrefix + dirSeparator + nvramFolder - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) - { - Debug.LogMessage(LogEventLevel.Information, "{nvramFolder:l}/program{applicationNumber} directory found", nvramFolder, InitialParametersClass.ApplicationNumber); - - filePathPrefix = directoryPrefix + dirSeparator + nvramFolder - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; - } - // If neither exists, set path to User/ProgramX - else - { - Debug.LogMessage(LogEventLevel.Information, "{userFolder:l}/program{applicationNumber} directory found", userFolder, InitialParametersClass.ApplicationNumber); - - filePathPrefix = directoryPrefix + dirSeparator + userFolder - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; - } - } - else // Handles Linux OS (Virtual Control) - { - //Debug.SetDebugLevel(2); - Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on Virtual Control Server", Global.AssemblyVersion); - - // Set path to User/ - filePathPrefix = directoryPrefix + dirSeparator + "User" + dirSeparator; - } - - Global.SetFilePathPrefix(filePathPrefix); - } - catch (Exception e) - { - Debug.LogMessage(e, "Unable to determine platform due to exception"); - } - } - - /// - /// Begins the process of loading resources including plugins and configuration data - /// - public void GoWithLoad() - { - try - { - Debug.SetDoNotLoadConfigOnNextBoot(false); - - PluginLoader.AddProgramAssemblies(); - - _ = new Core.DeviceFactory(); - _ = new Devices.Common.DeviceFactory(); - _ = new DeviceFactory(); - - _ = new ProcessorExtensionDeviceFactory(); - _ = new MobileControlFactory(); - - Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration"); - - var filesReady = SetupFilesystem(); - if (filesReady) - { - Debug.LogMessage(LogEventLevel.Information, "Checking for plugins"); - PluginLoader.LoadPlugins(); - - Debug.LogMessage(LogEventLevel.Information, "Folder structure verified. Loading config..."); - if (!ConfigReader.LoadConfig2()) - { - Debug.LogMessage(LogEventLevel.Information, "Essentials Load complete with errors"); - return; - } - - Load(); - Debug.LogMessage(LogEventLevel.Information, "Essentials load complete"); - } - else - { - Debug.LogMessage(LogEventLevel.Information, - @"---------------------------------------------- + Debug.LogMessage(LogEventLevel.Information, + @"---------------------------------------------- ------------------------------------------------ ------------------------------------------------ Essentials file structure setup completed. @@ -283,51 +284,51 @@ namespace PepperDash.Essentials ------------------------------------------------ ------------------------------------------------ ------------------------------------------------"); - } - - } - catch (Exception e) - { - Debug.LogMessage(e, "FATAL INITIALIZE ERROR. System is in an inconsistent state"); - } - finally - { - // Notify the OS that the program intitialization has completed - SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; } } - - - - /// - /// Verifies filesystem is set up. IR, SGD, and programX folders - /// - bool SetupFilesystem() + catch (Exception e) { - Debug.LogMessage(LogEventLevel.Information, "Verifying and/or creating folder structure"); - var configDir = Global.FilePathPrefix; + Debug.LogMessage(e, "FATAL INITIALIZE ERROR. System is in an inconsistent state"); + } + finally + { + // Notify the OS that the program intitialization has completed + SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; + } - Debug.LogMessage(LogEventLevel.Information, "FilePathPrefix: {filePathPrefix:l}", configDir); - var configExists = Directory.Exists(configDir); - if (!configExists) - Directory.Create(configDir); + } - var irDir = Global.FilePathPrefix + "ir"; - if (!Directory.Exists(irDir)) - Directory.Create(irDir); + - var sgdDir = Global.FilePathPrefix + "sgd"; + /// + /// Verifies filesystem is set up. IR, SGD, and programX folders + /// + bool SetupFilesystem() + { + Debug.LogMessage(LogEventLevel.Information, "Verifying and/or creating folder structure"); + var configDir = Global.FilePathPrefix; + + Debug.LogMessage(LogEventLevel.Information, "FilePathPrefix: {filePathPrefix:l}", configDir); + var configExists = Directory.Exists(configDir); + if (!configExists) + Directory.Create(configDir); + + var irDir = Global.FilePathPrefix + "ir"; + if (!Directory.Exists(irDir)) + Directory.Create(irDir); + + var sgdDir = Global.FilePathPrefix + "sgd"; if (!Directory.Exists(sgdDir)) Directory.Create(sgdDir); - var pluginDir = Global.FilePathPrefix + "plugins"; - if (!Directory.Exists(pluginDir)) - Directory.Create(pluginDir); + var pluginDir = Global.FilePathPrefix + "plugins"; + if (!Directory.Exists(pluginDir)) + Directory.Create(pluginDir); - var joinmapDir = Global.FilePathPrefix + "joinmaps"; - if(!Directory.Exists(joinmapDir)) - Directory.Create(joinmapDir); + var joinmapDir = Global.FilePathPrefix + "joinmaps"; + if(!Directory.Exists(joinmapDir)) + Directory.Create(joinmapDir); return configExists; } @@ -360,199 +361,198 @@ namespace PepperDash.Essentials DeviceManager.ActivateAll(); - LoadTieLines(); + LoadTieLines(); - /*var mobileControl = GetMobileControlDevice(); + /*var mobileControl = GetMobileControlDevice(); if (mobileControl == null) return; - mobileControl.LinkSystemMonitorToAppServer();*/ + mobileControl.LinkSystemMonitorToAppServer();*/ } - /// - /// Reads all devices from config and adds them to DeviceManager - /// - public void LoadDevices() + /// + /// Reads all devices from config and adds them to DeviceManager + /// + public void LoadDevices() + { + + // Build the processor wrapper class + DeviceManager.AddDevice(new Core.Devices.CrestronProcessor("processor")); + + DeviceManager.AddDevice(new RoutingFeedbackManager($"routingFeedbackManager", "Routing Feedback Manager")); + + // Add global System Monitor device + if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) { + DeviceManager.AddDevice( + new Core.Monitoring.SystemMonitorController("systemMonitor")); + } - // Build the processor wrapper class - DeviceManager.AddDevice(new Core.Devices.CrestronProcessor("processor")); + foreach (var devConf in ConfigReader.ConfigObject.Devices) + { + IKeyed newDev = null; - DeviceManager.AddDevice(new RoutingFeedbackManager($"routingFeedbackManager", "Routing Feedback Manager")); - - // Add global System Monitor device - if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) + try { - DeviceManager.AddDevice( - new Core.Monitoring.SystemMonitorController("systemMonitor")); - } - - foreach (var devConf in ConfigReader.ConfigObject.Devices) - { - IKeyed newDev = null; - - try + Debug.LogMessage(LogEventLevel.Information, "Creating device '{deviceKey:l}', type '{deviceType:l}'", devConf.Key, devConf.Type); + // Skip this to prevent unnecessary warnings + if (devConf.Key == "processor") { - Debug.LogMessage(LogEventLevel.Information, "Creating device '{deviceKey:l}', type '{deviceType:l}'", devConf.Key, devConf.Type); - // Skip this to prevent unnecessary warnings - if (devConf.Key == "processor") - { - var prompt = Global.ControlSystem.ControllerPrompt; + var prompt = Global.ControlSystem.ControllerPrompt; - var typeMatch = String.Equals(devConf.Type, prompt, StringComparison.OrdinalIgnoreCase) || - String.Equals(devConf.Type, prompt.Replace("-", ""), StringComparison.OrdinalIgnoreCase); + var typeMatch = string.Equals(devConf.Type, prompt, StringComparison.OrdinalIgnoreCase) || + string.Equals(devConf.Type, prompt.Replace("-", ""), StringComparison.OrdinalIgnoreCase); - if (!typeMatch) - Debug.LogMessage(LogEventLevel.Information, - "WARNING: Config file defines processor type as '{deviceType:l}' but actual processor is '{processorType:l}'! Some ports may not be available", - devConf.Type.ToUpper(), Global.ControlSystem.ControllerPrompt.ToUpper()); + if (!typeMatch) + Debug.LogMessage(LogEventLevel.Information, + "WARNING: Config file defines processor type as '{deviceType:l}' but actual processor is '{processorType:l}'! Some ports may not be available", + devConf.Type.ToUpper(), Global.ControlSystem.ControllerPrompt.ToUpper()); - continue; - } + continue; + } - if (newDev == null) - newDev = Core.DeviceFactory.GetDevice(devConf); + if (newDev == null) + newDev = Core.DeviceFactory.GetDevice(devConf); if (newDev != null) DeviceManager.AddDevice(newDev); else - Debug.LogMessage(LogEventLevel.Information, "ERROR: Cannot load unknown device type '{deviceType:l}', key '{deviceKey:l}'.", devConf.Type, devConf.Key); - } - catch (Exception e) - { - Debug.LogMessage(e, "ERROR: Creating device {deviceKey:l}. Skipping device.",args: new[] { devConf.Key }); - } + Debug.LogMessage(LogEventLevel.Information, "ERROR: Cannot load unknown device type '{deviceType:l}', key '{deviceKey:l}'.", devConf.Type, devConf.Key); + } + catch (Exception e) + { + Debug.LogMessage(e, "ERROR: Creating device {deviceKey:l}. Skipping device.",args: new[] { devConf.Key }); } - Debug.LogMessage(LogEventLevel.Information, "All Devices Loaded."); - } + Debug.LogMessage(LogEventLevel.Information, "All Devices Loaded."); + + } - /// - /// Helper method to load tie lines. This should run after devices have loaded - /// - public void LoadTieLines() + /// + /// Helper method to load tie lines. This should run after devices have loaded + /// + public void LoadTieLines() + { + // In the future, we can't necessarily just clear here because devices + // might be making their own internal sources/tie lines + + var tlc = TieLineCollection.Default; + + if (ConfigReader.ConfigObject.TieLines == null) { - // In the future, we can't necessarily just clear here because devices - // might be making their own internal sources/tie lines - - var tlc = TieLineCollection.Default; - - if (ConfigReader.ConfigObject.TieLines == null) - { - return; - } - - foreach (var tieLineConfig in ConfigReader.ConfigObject.TieLines) - { - var newTL = tieLineConfig.GetTieLine(); - if (newTL != null) - tlc.Add(newTL); - } - - Debug.LogMessage(LogEventLevel.Information, "All Tie Lines Loaded."); - + return; } - /// - /// Reads all rooms from config and adds them to DeviceManager - /// - public void LoadRooms() - { - if (ConfigReader.ConfigObject.Rooms == null) + foreach (var tieLineConfig in ConfigReader.ConfigObject.TieLines) + { + var newTL = tieLineConfig.GetTieLine(); + if (newTL != null) + tlc.Add(newTL); + } + + Debug.LogMessage(LogEventLevel.Information, "All Tie Lines Loaded."); + + } + + /// + /// Reads all rooms from config and adds them to DeviceManager + /// + public void LoadRooms() + { + if (ConfigReader.ConfigObject.Rooms == null) + { + Debug.LogMessage(LogEventLevel.Information, "Notice: Configuration contains no rooms - Is this intentional? This may be a valid configuration."); + return; + } + + foreach (var roomConfig in ConfigReader.ConfigObject.Rooms) + { + try { - Debug.LogMessage(LogEventLevel.Information, "Notice: Configuration contains no rooms - Is this intentional? This may be a valid configuration."); - return; - } + var room = Core.DeviceFactory.GetDevice(roomConfig); - foreach (var roomConfig in ConfigReader.ConfigObject.Rooms) - { - try + if(room == null) { - var room = Core.DeviceFactory.GetDevice(roomConfig); - - if(room == null) - { - Debug.LogWarning("ERROR: Cannot load unknown room type '{roomType:l}', key '{roomKey:l}'.", roomConfig.Type, roomConfig.Key); - continue; - } - - DeviceManager.AddDevice(room); - } catch (Exception ex) - { - Debug.LogMessage(ex, "Exception loading room {roomKey}:{roomType}", null, roomConfig.Key, roomConfig.Type); + Debug.LogWarning("ERROR: Cannot load unknown room type '{roomType:l}', key '{roomKey:l}'.", roomConfig.Type, roomConfig.Key); continue; } - } - Debug.LogMessage(LogEventLevel.Information, "All Rooms Loaded."); - - - } - - /// - /// Fires up a logo server if not already running - /// - void LoadLogoServer() - { - if (ConfigReader.ConfigObject.Rooms == null) + DeviceManager.AddDevice(room); + } catch (Exception ex) { - Debug.LogMessage(LogEventLevel.Information, "No rooms configured. Bypassing Logo server startup."); - return; - } - - if ( - !ConfigReader.ConfigObject.Rooms.Any( - CheckRoomConfig)) - { - Debug.LogMessage(LogEventLevel.Information, "No rooms configured to use system Logo server. Bypassing Logo server startup"); - return; - } - - try - { - LogoServer = new HttpLogoServer(8080, Global.DirectorySeparator + "html" + Global.DirectorySeparator + "logo"); - } - catch (Exception) - { - Debug.LogMessage(LogEventLevel.Information, "NOTICE: Logo server cannot be started. Likely already running in another program"); + Debug.LogMessage(ex, "Exception loading room {roomKey}:{roomType}", null, roomConfig.Key, roomConfig.Type); + continue; } } - private bool CheckRoomConfig(DeviceConfig c) + Debug.LogMessage(LogEventLevel.Information, "All Rooms Loaded."); + + + } + + /// + /// Fires up a logo server if not already running + /// + void LoadLogoServer() + { + if (ConfigReader.ConfigObject.Rooms == null) { - string logoDark = null; - string logoLight = null; - string logo = null; + Debug.LogMessage(LogEventLevel.Information, "No rooms configured. Bypassing Logo server startup."); + return; + } - try + if ( + !ConfigReader.ConfigObject.Rooms.Any( + CheckRoomConfig)) + { + Debug.LogMessage(LogEventLevel.Information, "No rooms configured to use system Logo server. Bypassing Logo server startup"); + return; + } + + try + { + LogoServer = new HttpLogoServer(8080, Global.DirectorySeparator + "html" + Global.DirectorySeparator + "logo"); + } + catch (Exception) + { + Debug.LogMessage(LogEventLevel.Information, "NOTICE: Logo server cannot be started. Likely already running in another program"); + } + } + + private bool CheckRoomConfig(DeviceConfig c) + { + string logoDark = null; + string logoLight = null; + string logo = null; + + try + { + if (c.Properties["logoDark"] != null) { - if (c.Properties["logoDark"] != null) - { - logoDark = c.Properties["logoDark"].Value("type"); - } - - if (c.Properties["logoLight"] != null) - { - logoLight = c.Properties["logoLight"].Value("type"); - } - - if (c.Properties["logo"] != null) - { - logo = c.Properties["logo"].Value("type"); - } - - return ((logoDark != null && logoDark == "system") || - (logoLight != null && logoLight == "system") || (logo != null && logo == "system")); + logoDark = c.Properties["logoDark"].Value("type"); } - catch + + if (c.Properties["logoLight"] != null) { - Debug.LogMessage(LogEventLevel.Information, "Unable to find logo information in any room config"); - return false; + logoLight = c.Properties["logoLight"].Value("type"); } + + if (c.Properties["logo"] != null) + { + logo = c.Properties["logo"].Value("type"); + } + + return ((logoDark != null && logoDark == "system") || + (logoLight != null && logoLight == "system") || (logo != null && logo == "system")); + } + catch + { + Debug.LogMessage(LogEventLevel.Information, "Unable to find logo information in any room config"); + return false; } } } diff --git a/src/PepperDash.Essentials/Factory/DeviceFactory.cs b/src/PepperDash.Essentials/Factory/DeviceFactory.cs index 262130f1..425394f8 100644 --- a/src/PepperDash.Essentials/Factory/DeviceFactory.cs +++ b/src/PepperDash.Essentials/Factory/DeviceFactory.cs @@ -14,36 +14,54 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -namespace PepperDash.Essentials +namespace PepperDash.Essentials; + +/// +/// Responsible for loading all of the device types for this library +/// +public class DeviceFactory { - /// - /// Responsible for loading all of the device types for this library - /// - public class DeviceFactory + + public DeviceFactory() { + var assy = Assembly.GetExecutingAssembly(); + PluginLoader.AddLoadedAssembly(assy.GetName().Name, assy); - public DeviceFactory() + var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); + + if (types != null) { - var assy = Assembly.GetExecutingAssembly(); - PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy); - - var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); - - if (types != null) + foreach (var type in types) { - foreach (var type in types) + try { - try - { - var factory = (IDeviceFactory)Activator.CreateInstance(type); - factory.LoadTypeFactories(); - } - catch (Exception e) - { - Debug.LogMessage(Serilog.Events.LogEventLevel.Error, "Unable to load type: '{exception}' DeviceFactory: {factoryName}", e, type.Name); - } + var factory = (IDeviceFactory)Activator.CreateInstance(type); + LoadDeviceFactories(factory); + } + catch (Exception e) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Error, "Unable to load type: '{exception}' DeviceFactory: {factoryName}", e, type.Name); } } } } + + /// + /// Loads device factories from the specified plugin device factory and registers them for use. + /// + /// This method retrieves metadata from the provided , including + /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type + /// names are converted to lowercase for registration. + /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. + private static void LoadDeviceFactories(IDeviceFactory deviceFactory) + { + foreach (var typeName in deviceFactory.TypeNames) + { + //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); + var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; + string description = descriptionAttribute[0].Description; + var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; + Core.DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + } + } } diff --git a/src/PepperDash.Essentials/PepperDash.Essentials.csproj b/src/PepperDash.Essentials/PepperDash.Essentials.csproj index 64699f5c..5efd98c9 100644 --- a/src/PepperDash.Essentials/PepperDash.Essentials.csproj +++ b/src/PepperDash.Essentials/PepperDash.Essentials.csproj @@ -26,29 +26,6 @@ pdbonly - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - -