From 8db559f1975115607d08d59825db91224f0226d9 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Fri, 25 Jul 2025 08:28:55 -0500 Subject: [PATCH] feat: factory updates & refactoring This commit introduces significant updates to the device factory system, enhancing the way devices are created and managed within the PepperDash Essentials framework. The changes include: - New attributes for device configuration and description. - Refactoring of the device manager and essentials device classes to support new factory methods. - modified factory classes for essentials devices, plugin development devices, and processor extension devices. - The device factory interface has been updated to include a factory method for creating devices. - Added a wrapper for the device factory to streamline device creation. - Updated plugin loader to accommodate the new device factory structure. Fixes #1065 Fixed #1277 --- .../Devices/ConfigSnippetAttribute.cs | 33 +++ .../Devices/DescriptionAttribute.cs | 32 +++ .../Devices/DeviceManager.cs | 70 ++--- .../Devices/EssentialsDevice.cs | 160 ++--------- .../Devices/EssentialsDeviceFactory.cs | 27 ++ ...ssentialsPluginDevelopmentDeviceFactory.cs | 18 ++ .../Devices/EssentialsPluginDeviceFactory.cs | 14 + .../ProcessorExtensionDeviceFactory.cs | 41 +++ .../Factory/DeviceFactory.cs | 251 +++++++++++------- .../Factory/DeviceFactoryWrapper.cs | 43 +++ .../Factory/IDeviceFactory.cs | 23 +- .../Plugins/IPluginDeviceFactory.cs | 10 +- .../Plugins/PluginLoader.cs | 111 ++++++-- .../DeviceFactory.cs | 23 +- .../MobileControlFactory.cs | 27 +- src/PepperDash.Essentials/ControlSystem.cs | 132 ++++----- .../Factory/DeviceFactory.cs | 26 +- 17 files changed, 662 insertions(+), 379 deletions(-) create mode 100644 src/PepperDash.Essentials.Core/Devices/ConfigSnippetAttribute.cs create mode 100644 src/PepperDash.Essentials.Core/Devices/DescriptionAttribute.cs create mode 100644 src/PepperDash.Essentials.Core/Devices/EssentialsDeviceFactory.cs create mode 100644 src/PepperDash.Essentials.Core/Devices/EssentialsPluginDevelopmentDeviceFactory.cs create mode 100644 src/PepperDash.Essentials.Core/Devices/EssentialsPluginDeviceFactory.cs create mode 100644 src/PepperDash.Essentials.Core/Devices/ProcessorExtensionDeviceFactory.cs create mode 100644 src/PepperDash.Essentials.Core/Factory/DeviceFactoryWrapper.cs diff --git a/src/PepperDash.Essentials.Core/Devices/ConfigSnippetAttribute.cs b/src/PepperDash.Essentials.Core/Devices/ConfigSnippetAttribute.cs new file mode 100644 index 00000000..76d3640c --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/ConfigSnippetAttribute.cs @@ -0,0 +1,33 @@ +using System; + +namespace PepperDash.Essentials.Core +{ + /// + /// Represents a ConfigSnippetAttribute + /// + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] + public class ConfigSnippetAttribute : Attribute + { + private string _ConfigSnippet; + + /// + /// Represents a configuration snippet for the device. + /// + /// + public ConfigSnippetAttribute(string configSnippet) + { + //Debug.LogMessage(LogEventLevel.Verbose, "Setting Config Snippet {0}", configSnippet); + _ConfigSnippet = configSnippet; + } + + /// + /// Gets the configuration snippet for the device. + /// This snippet can be used in the DeviceConfig to instantiate the device. + /// + public string ConfigSnippet + { + get { return _ConfigSnippet; } + } + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/DescriptionAttribute.cs b/src/PepperDash.Essentials.Core/Devices/DescriptionAttribute.cs new file mode 100644 index 00000000..6d4073d9 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/DescriptionAttribute.cs @@ -0,0 +1,32 @@ +using System; + +namespace PepperDash.Essentials.Core +{ + /// + /// Represents a description attribute for a device. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] + public class DescriptionAttribute : Attribute + { + private string _Description; + + /// + /// Represents a description attribute for a device. + /// + /// + public DescriptionAttribute(string description) + { + //Debug.LogMessage(LogEventLevel.Verbose, "Setting Description: {0}", description); + _Description = description; + } + + /// + /// Gets the description for the device. + /// + public string Description + { + get { return _Description; } + } + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs b/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs index fd116aed..7572835e 100644 --- a/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs +++ b/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs @@ -1,26 +1,38 @@ -using Crestron.SimplSharp; -using Crestron.SimplSharpPro; -using PepperDash.Core; -using Serilog.Events; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using PepperDash.Core; +using Serilog.Events; namespace PepperDash.Essentials.Core { + /// + /// Manages the devices in the system + /// public static class DeviceManager { + /// + /// Raised when all devices have been activated + /// public static event EventHandler AllDevicesActivated; + + /// + /// Raised when all devices have been registered + /// public static event EventHandler AllDevicesRegistered; + + /// + /// Raised when all devices have been initialized + /// public static event EventHandler AllDevicesInitialized; private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection(); - private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true); - //public static List Devices { get { return _Devices; } } - //static List _Devices = new List(); + private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true); private static readonly Dictionary Devices = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -74,7 +86,7 @@ namespace PepperDash.Essentials.Core foreach (var d in Devices.Values) { try - { + { if (d is Device) (d as Device).PreActivate(); } @@ -188,27 +200,6 @@ namespace PepperDash.Essentials.Core } } - //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); @@ -397,6 +388,25 @@ namespace PepperDash.Essentials.Core return null; } + /// + /// GetDeviceForKey method + /// + /// + public static T GetDeviceForKey(string key) + { + //return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); + if (key == null || !Devices.ContainsKey(key)) + return default; + + if (!(Devices[key] is T)) + { + Debug.LogMessage(LogEventLevel.Error, "Device with key '{0}' is not of type '{1}'", key, typeof(T).Name); + return default; + } + + return (T)Devices[key]; + } + /// /// Console handler that simulates com port data receive /// diff --git a/src/PepperDash.Essentials.Core/Devices/EssentialsDevice.cs b/src/PepperDash.Essentials.Core/Devices/EssentialsDevice.cs index 2fcbf1a4..59fba681 100644 --- a/src/PepperDash.Essentials.Core/Devices/EssentialsDevice.cs +++ b/src/PepperDash.Essentials.Core/Devices/EssentialsDevice.cs @@ -1,12 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using System.Reflection; - +using System.Threading.Tasks; using PepperDash.Core; -using PepperDash.Essentials.Core.Config; using Serilog.Events; namespace PepperDash.Essentials.Core @@ -17,9 +11,16 @@ namespace PepperDash.Essentials.Core [Description("The base Essentials Device Class")] public abstract class EssentialsDevice : Device { + /// + /// Event raised when the device is initialized. + /// public event EventHandler Initialized; private bool _isInitialized; + + /// + /// Gets a value indicating whether the device is initialized. + /// public bool IsInitialized { get { return _isInitialized; } @@ -36,12 +37,21 @@ namespace PepperDash.Essentials.Core } } + /// + /// Initializes a new instance of the EssentialsDevice class. + /// + /// The unique identifier for the device. protected EssentialsDevice(string key) : base(key) { SubscribeToActivateComplete(); } + /// + /// Initializes a new instance of the EssentialsDevice class. + /// + /// The unique identifier for the device. + /// The name of the device. protected EssentialsDevice(string key, string name) : base(key, name) { @@ -55,7 +65,7 @@ namespace PepperDash.Essentials.Core private void DeviceManagerOnAllDevicesActivated(object sender, EventArgs eventArgs) { - CrestronInvoke.BeginInvoke((o) => + Task.Run(() => { try { @@ -90,138 +100,4 @@ namespace PepperDash.Essentials.Core } } - - [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] - public class DescriptionAttribute : Attribute - { - private string _Description; - - public DescriptionAttribute(string description) - { - //Debug.LogMessage(LogEventLevel.Verbose, "Setting Description: {0}", description); - _Description = description; - } - - public string Description - { - get { return _Description; } - } - } - - [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] - /// - /// Represents a ConfigSnippetAttribute - /// - public class ConfigSnippetAttribute : Attribute - { - private string _ConfigSnippet; - - public ConfigSnippetAttribute(string configSnippet) - { - //Debug.LogMessage(LogEventLevel.Verbose, "Setting Config Snippet {0}", configSnippet); - _ConfigSnippet = configSnippet; - } - - public string ConfigSnippet - { - get { return _ConfigSnippet; } - } - } - - /// - /// Devices the basic needs for a Device Factory - /// - public abstract class EssentialsDeviceFactory : IDeviceFactory where T:EssentialsDevice - { - #region IDeviceFactory Members - - /// - /// A list of strings that can be used in the type property of a DeviceConfig object to build an instance of this device - /// - public List TypeNames { get; protected set; } - - /// - /// LoadTypeFactories method - /// - public void LoadTypeFactories() - { - foreach (var typeName in TypeNames) - { - //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); - var descriptionAttribute = typeof(T).GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; - string description = descriptionAttribute[0].Description; - var snippetAttribute = typeof(T).GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; - DeviceFactory.AddFactoryForType(typeName.ToLower(), description, typeof(T), BuildDevice); - } - } - - /// - /// The method that will build the device - /// - /// The device config - /// An instance of the device - public abstract EssentialsDevice BuildDevice(DeviceConfig dc); - - #endregion - } - - public abstract class ProcessorExtensionDeviceFactory : IProcessorExtensionDeviceFactory where T: EssentialsDevice - { - #region IProcessorExtensionDeviceFactory Members - - /// - /// Gets or sets the TypeNames - /// - public List TypeNames { get; protected set; } - - /// - /// LoadFactories method - /// - public void LoadFactories() - { - foreach (var typeName in TypeNames) - { - //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); - var descriptionAttribute = typeof(T).GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; - string description = descriptionAttribute[0].Description; - var snippetAttribute = typeof(T).GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; - ProcessorExtensionDeviceFactory.AddFactoryForType(typeName.ToLower(), description, typeof(T), BuildDevice); - } - } - - /// - /// The method that will build the device - /// - /// The device config - /// An instance of the device - public abstract EssentialsDevice BuildDevice(DeviceConfig dc); - - #endregion - - } - - /// - /// Devices the basic needs for a Device Factory - /// - public abstract class EssentialsPluginDeviceFactory : EssentialsDeviceFactory, IPluginDeviceFactory where T : EssentialsDevice - { - /// - /// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33") - /// - public string MinimumEssentialsFrameworkVersion { get; protected set; } - } - - public abstract class EssentialsPluginDevelopmentDeviceFactory : EssentialsDeviceFactory, IPluginDevelopmentDeviceFactory where T : EssentialsDevice - { - /// - /// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33") - /// - public string MinimumEssentialsFrameworkVersion { get; protected set; } - - /// - /// Gets or sets the DevelopmentEssentialsFrameworkVersions - /// - public List DevelopmentEssentialsFrameworkVersions { get; protected set; } - } - } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/EssentialsDeviceFactory.cs b/src/PepperDash.Essentials.Core/Devices/EssentialsDeviceFactory.cs new file mode 100644 index 00000000..3ac326bf --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/EssentialsDeviceFactory.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core +{ + /// + /// Provides the basic needs for a Device Factory + /// + public abstract class EssentialsDeviceFactory : IDeviceFactory where T : EssentialsDevice + { + /// + public Type FactoryType => typeof(T); + + /// + /// A list of strings that can be used in the type property of a DeviceConfig object to build an instance of this device + /// + public List TypeNames { get; protected set; } + + /// + /// The method that will build the device + /// + /// The device config + /// An instance of the device + public abstract EssentialsDevice BuildDevice(DeviceConfig dc); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/EssentialsPluginDevelopmentDeviceFactory.cs b/src/PepperDash.Essentials.Core/Devices/EssentialsPluginDevelopmentDeviceFactory.cs new file mode 100644 index 00000000..62864551 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/EssentialsPluginDevelopmentDeviceFactory.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace PepperDash.Essentials.Core +{ + public abstract class EssentialsPluginDevelopmentDeviceFactory : EssentialsDeviceFactory, IPluginDevelopmentDeviceFactory where T : EssentialsDevice + { + /// + /// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33") + /// + public string MinimumEssentialsFrameworkVersion { get; protected set; } + + /// + /// Gets or sets the DevelopmentEssentialsFrameworkVersions + /// + public List DevelopmentEssentialsFrameworkVersions { get; protected set; } + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/EssentialsPluginDeviceFactory.cs b/src/PepperDash.Essentials.Core/Devices/EssentialsPluginDeviceFactory.cs new file mode 100644 index 00000000..7a891905 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/EssentialsPluginDeviceFactory.cs @@ -0,0 +1,14 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Devices the basic needs for a Device Factory + /// + public abstract class EssentialsPluginDeviceFactory : EssentialsDeviceFactory, IPluginDeviceFactory where T : EssentialsDevice + { + /// + /// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33") + /// + public string MinimumEssentialsFrameworkVersion { get; protected set; } + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/ProcessorExtensionDeviceFactory.cs b/src/PepperDash.Essentials.Core/Devices/ProcessorExtensionDeviceFactory.cs new file mode 100644 index 00000000..469d18ec --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/ProcessorExtensionDeviceFactory.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core +{ + public abstract class ProcessorExtensionDeviceFactory : IProcessorExtensionDeviceFactory where T : EssentialsDevice + { + #region IProcessorExtensionDeviceFactory Members + + /// + /// Gets or sets the TypeNames + /// + public List TypeNames { get; protected set; } + + /// + /// LoadFactories method + /// + public void LoadFactories() + { + foreach (var typeName in TypeNames) + { + //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); + var descriptionAttribute = typeof(T).GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; + string description = descriptionAttribute[0].Description; + var snippetAttribute = typeof(T).GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; + ProcessorExtensionDeviceFactory.AddFactoryForType(typeName.ToLower(), description, typeof(T), BuildDevice); + } + } + + /// + /// The method that will build the device + /// + /// The device config + /// An instance of the device + public abstract EssentialsDevice BuildDevice(DeviceConfig dc); + + #endregion + + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs b/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs index 5c8d4dec..08aa67cf 100644 --- a/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs @@ -1,123 +1,162 @@  -using Crestron.SimplSharp; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Reflection; +using Crestron.SimplSharp; using Newtonsoft.Json.Linq; using PepperDash.Core; using PepperDash.Essentials.Core.Config; using Serilog.Events; -using System; -using System.Collections.Generic; -using System.Linq; namespace PepperDash.Essentials.Core { /// - /// Wrapper class for device factory information - /// - public class DeviceFactoryWrapper - { - /// - /// Gets or sets the device type - /// - public Type Type { get; set; } - - /// - /// Gets or sets the Description - /// - public string Description { get; set; } - - /// - /// Gets or sets the factory method for creating devices - /// - public Func FactoryMethod { get; set; } - - /// - /// Initializes a new instance of the DeviceFactoryWrapper class - /// - public DeviceFactoryWrapper() - { - Type = null; - Description = "Not Available"; - } - } - - /// - /// Represents a DeviceFactory + /// 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 DeviceFactory class and loads all device type factories + /// 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 assy = Assembly.GetExecutingAssembly(); - PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy); + 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 assembly in programAssemblies) { + try + { + Assembly.LoadFrom(assembly); + } + catch (Exception e) + { + Debug.LogError("Unable to load assembly: {assemblyName} - {message}", assembly, e.Message); + } + } + + 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); - factory.LoadTypeFactories(); + LoadDeviceFactories(factory); } catch (Exception e) { - Debug.LogMessage(LogEventLevel.Information, "Unable to load type: '{1}' DeviceFactory: {0}", e, type.Name); + Debug.LogError("Unable to load type: '{message}' DeviceFactory: {type}", e.Message, 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 = + /// + /// 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[]; + 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 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}); - } + /// + /// 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 }); + } + /// + /// 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) { - //Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName); - - if(FactoryMethods.ContainsKey(typeName)) + if (FactoryMethods.ContainsKey(typeName)) { - Debug.LogMessage(LogEventLevel.Information, "Unable to add type: '{0}'. Already exists in DeviceFactory", typeName); + Debug.LogInformation("Unable to add type: '{typeName}'. Already exists in DeviceFactory", typeName); return; } var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method }; - DeviceFactory.FactoryMethods.Add(typeName, wrapper); + + 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") + if (prop.Name.Equals("secret", StringComparison.CurrentCultureIgnoreCase)) { 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; + + if (!(prop.Value is JObject recurseProp)) return; + CheckForSecrets(recurseProp.Properties()); } } @@ -127,66 +166,70 @@ namespace PepperDash.Essentials.Core var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider); if (secretProvider == null) return null; var secret = secretProvider.GetSecret(data.Key); - if (secret != null) return (string) secret.Value; + if (secret != null) return (string)secret.Value; Debug.LogMessage(LogEventLevel.Debug, "Unable to retrieve secret {0}{1} - Make sure you've added it to the secrets provider", data.Provider, data.Key); - return String.Empty; + return string.Empty; } /// - /// The factory method for Core "things". Also iterates the Factory methods that have - /// been loaded from plugins - /// - /// - /// - /// - /// GetDevice method + /// 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 { - 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) + if (properties is JObject jObject) { 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); + 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.LogMessage(ex, "Exception occurred while creating device {0}: {1}", null, dc.Key, ex.Message); + Debug.LogError(ex, "Exception occurred while creating device {0}: {1}", null, dc.Key, ex.Message); return null; } } /// - /// Prints the type names and associated metadata from the FactoryMethods collection. - /// - /// - /// - /// GetDeviceFactoryTypes method + /// 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) @@ -212,16 +255,18 @@ namespace PepperDash.Essentials.Core } } - /// - /// 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/DeviceFactoryWrapper.cs b/src/PepperDash.Essentials.Core/Factory/DeviceFactoryWrapper.cs new file mode 100644 index 00000000..dc1f72d9 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Factory/DeviceFactoryWrapper.cs @@ -0,0 +1,43 @@ + + +using System; +using PepperDash.Core; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core +{ + /// + /// Wraps a device factory, providing metadata and a factory method for creating devices. + /// + public class DeviceFactoryWrapper + { + /// + /// Gets or sets the type associated with the current instance. + /// + public Type Type { get; set; } + + /// + /// 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"; + } + } +} diff --git a/src/PepperDash.Essentials.Core/Factory/IDeviceFactory.cs b/src/PepperDash.Essentials.Core/Factory/IDeviceFactory.cs index 31ff95d2..b267edf6 100644 --- a/src/PepperDash.Essentials.Core/Factory/IDeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Factory/IDeviceFactory.cs @@ -1,4 +1,8 @@ -namespace PepperDash.Essentials.Core +using System; +using System.Collections.Generic; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core { /// /// Defines the contract for IDeviceFactory @@ -6,8 +10,21 @@ public interface IDeviceFactory { /// - /// Loads all the types to the DeviceFactory + /// Gets the type of the factory associated with the current instance. /// - void LoadTypeFactories(); + Type FactoryType { get; } + + /// + /// Gets a list of type names associated with the current plugin. + /// + List TypeNames { get; } + + /// + /// Builds and returns an instance based on the provided configuration. + /// + /// The configuration settings used to initialize the device. This parameter cannot be null. + /// An instance configured according to the specified . + EssentialsDevice BuildDevice(DeviceConfig deviceConfig); } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Plugins/IPluginDeviceFactory.cs b/src/PepperDash.Essentials.Core/Plugins/IPluginDeviceFactory.cs index ec823007..caad4b57 100644 --- a/src/PepperDash.Essentials.Core/Plugins/IPluginDeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Plugins/IPluginDeviceFactory.cs @@ -1,5 +1,5 @@ +using System; using System.Collections.Generic; -using PepperDash.Core; namespace PepperDash.Essentials.Core @@ -16,8 +16,16 @@ namespace PepperDash.Essentials.Core } + /// + /// Defines a class that is capable of loading custom plugin device types for development purposes + /// + [Obsolete("This interface is obsolete and will be removed in a future version." + + " Use IPluginDeviceFactory instead and check Global.IsRunningDevelopmentVersion to determine if the Essentials framework is in development mode.")] public interface IPluginDevelopmentDeviceFactory : IPluginDeviceFactory { + /// + /// Gets a list of all the development versions of the Essentials framework that are supported by this factory. + /// List DevelopmentEssentialsFrameworkVersions { get; } } } diff --git a/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs b/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs index a74ba16a..8978c752 100644 --- a/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs +++ b/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs @@ -1,15 +1,13 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using Crestron.SimplSharp; -// using Crestron.SimplSharp.CrestronIO; using System.Reflection; - +using Crestron.SimplSharp; +using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Essentials.Core; using Serilog.Events; -using Newtonsoft.Json; -using System.IO; namespace PepperDash.Essentials { @@ -28,10 +26,19 @@ namespace PepperDash.Essentials /// static List LoadedPluginFolderAssemblies; + /// + /// The assembly for the Essentials Framework + /// public static LoadedAssembly EssentialsAssembly { get; private set; } + /// + /// The assembly for the PepperDash Core + /// public static LoadedAssembly PepperDashCoreAssembly { get; private set; } + /// + /// The list of assemblies loaded from the Essentials plugins folder + /// public static List EssentialsPluginAssemblies { get; private set; } /// @@ -131,7 +138,7 @@ namespace PepperDash.Essentials /// /// Loads an assembly via Reflection and adds it to the list of loaded assemblies /// - /// + /// static LoadedAssembly LoadAssembly(string filePath) { try @@ -153,7 +160,8 @@ namespace PepperDash.Essentials } return null; - } catch(Exception ex) + } + catch (Exception ex) { Debug.LogMessage(ex, "Error loading assembly from {path}", null, filePath); return null; @@ -212,6 +220,20 @@ namespace PepperDash.Essentials } } + /// + /// Associates the specified assembly with the given name in the loaded assemblies collection. + /// + /// If an assembly with the specified name already exists in the loaded assemblies collection, + /// this method updates its associated assembly. If no matching name is found, the method does nothing. + /// The name used to identify the assembly. This value is case-sensitive and must not be null or empty. + /// The assembly to associate with the specified name. This value must not be null. + public static void AddLoadedAssembly(string name, Assembly assembly) + { + var loadedAssembly = LoadedAssemblies.FirstOrDefault(la => la.Name.Equals(name)); + + loadedAssembly?.SetAssembly(assembly); + } + /// /// Used by console command to report the currently loaded assemblies and versions /// @@ -402,7 +424,7 @@ namespace PepperDash.Essentials try { var assy = loadedAssembly.Assembly; - Type[] types = {}; + Type[] types = { }; try { types = assy.GetTypes(); @@ -419,8 +441,8 @@ namespace PepperDash.Essentials foreach (var type in types) { try - { - if (typeof (IPluginDeviceFactory).IsAssignableFrom(type) && !type.IsAbstract) + { + if (typeof(IPluginDeviceFactory).IsAssignableFrom(type) && !type.IsAbstract) { var plugin = (IPluginDeviceFactory)Activator.CreateInstance(type); @@ -454,37 +476,61 @@ namespace PepperDash.Essentials } /// - /// Loads a + /// Loads a custom plugin and performs a dependency check to ensure compatibility with the required Essentials + /// framework version. /// - /// - /// - static void LoadCustomPlugin(IPluginDeviceFactory plugin, LoadedAssembly loadedAssembly) + /// This method verifies that the plugin meets the minimum required Essentials framework version + /// before loading it. If the plugin fails the dependency check, it is skipped, and a log message is generated. If + /// the plugin passes the check, it is loaded, and its type factories are initialized. + /// The plugin to be loaded, implementing the interface. If the plugin also + /// implements , additional checks for development versions are + /// performed. + /// The assembly associated with the plugin being loaded. This is used for logging and tracking purposes. + static void LoadCustomPlugin(IPluginDeviceFactory pluginDeviceFactory, LoadedAssembly loadedAssembly) { - var developmentPlugin = plugin as IPluginDevelopmentDeviceFactory; - - var passed = developmentPlugin != null ? Global.IsRunningDevelopmentVersion - (developmentPlugin.DevelopmentEssentialsFrameworkVersions, developmentPlugin.MinimumEssentialsFrameworkVersion) - : Global.IsRunningMinimumVersionOrHigher(plugin.MinimumEssentialsFrameworkVersion); + var passed = pluginDeviceFactory is IPluginDevelopmentDeviceFactory developmentPlugin ? Global.IsRunningDevelopmentVersion + (developmentPlugin.DevelopmentEssentialsFrameworkVersions, developmentPlugin.MinimumEssentialsFrameworkVersion) + : Global.IsRunningMinimumVersionOrHigher(pluginDeviceFactory.MinimumEssentialsFrameworkVersion); if (!passed) { Debug.LogMessage(LogEventLevel.Information, "\r\n********************\r\n\tPlugin indicates minimum Essentials version {0}. Dependency check failed. Skipping Plugin {1}\r\n********************", - plugin.MinimumEssentialsFrameworkVersion, loadedAssembly.Name); + pluginDeviceFactory.MinimumEssentialsFrameworkVersion, loadedAssembly.Name); return; } else { - Debug.LogMessage(LogEventLevel.Information, "Passed plugin passed dependency check (required version {0})", plugin.MinimumEssentialsFrameworkVersion); + Debug.LogMessage(LogEventLevel.Information, "Passed plugin passed dependency check (required version {0})", pluginDeviceFactory.MinimumEssentialsFrameworkVersion); } - Debug.LogMessage(LogEventLevel.Information, "Loading plugin: {0}", loadedAssembly.Name); - plugin.LoadTypeFactories(); + Debug.LogMessage(LogEventLevel.Information, "Loading plugin factory: {0}", loadedAssembly.Name); - if(!EssentialsPluginAssemblies.Contains(loadedAssembly)) + LoadDeviceFactories(pluginDeviceFactory); + + if (!EssentialsPluginAssemblies.Contains(loadedAssembly)) EssentialsPluginAssemblies.Add(loadedAssembly); } + /// + /// 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(IPluginDeviceFactory 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[]; + DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + } + } + /// /// Loads a a custom plugin via the legacy method /// @@ -567,13 +613,30 @@ namespace PepperDash.Essentials /// public class LoadedAssembly { + /// + /// Gets the name of the assembly + /// [JsonProperty("name")] public string Name { get; private set; } + + /// + /// Gets the version of the assembly + /// [JsonProperty("version")] public string Version { get; private set; } + + /// + /// Gets the assembly + /// [JsonIgnore] public Assembly Assembly { get; private set; } + /// + /// Initializes a new instance of the LoadedAssembly class + /// + /// The name of the assembly + /// The version of the assembly + /// The assembly public LoadedAssembly(string name, string version, Assembly assembly) { Name = name; diff --git a/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs b/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs index 7656b277..a0fa0c9f 100644 --- a/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs +++ b/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs @@ -19,7 +19,7 @@ namespace PepperDash.Essentials.Devices.Common { 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) @@ -29,7 +29,7 @@ namespace PepperDash.Essentials.Devices.Common try { var factory = (IDeviceFactory)Activator.CreateInstance(type); - factory.LoadTypeFactories(); + LoadDeviceFactories(factory); } catch (Exception e) { @@ -38,5 +38,24 @@ namespace PepperDash.Essentials.Devices.Common } } } + + /// + /// 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 7d4b0340..ce40aa64 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlFactory.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlFactory.cs @@ -1,8 +1,8 @@ -using PepperDash.Core; -using PepperDash.Essentials.Core; -using System; +using System; using System.Linq; using System.Reflection; +using PepperDash.Core; +using PepperDash.Essentials.Core; namespace PepperDash.Essentials { @@ -30,7 +30,7 @@ namespace PepperDash.Essentials { var factory = (IDeviceFactory)Activator.CreateInstance(type); - factory.LoadTypeFactories(); + LoadDeviceFactories(factory); } catch (Exception ex) { @@ -38,5 +38,24 @@ namespace PepperDash.Essentials } } } + + /// + /// 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 56615017..3da56d8b 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -1,7 +1,9 @@  +using System; +using System.Linq; +using System.Reflection; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; -using System.Reflection; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.CrestronThread; using Crestron.SimplSharpPro.Diagnostics; @@ -9,12 +11,9 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.DeviceTypeInterfaces; -using PepperDash.Essentials.Core.Web; -using System; -using System.Linq; -using Serilog.Events; using PepperDash.Essentials.Core.Routing; +using PepperDash.Essentials.Core.Web; +using Serilog.Events; namespace PepperDash.Essentials { @@ -46,22 +45,22 @@ namespace PepperDash.Essentials // AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; } - private System.Reflection.Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) + private Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) { - var assemblyName = new System.Reflection.AssemblyName(args.Name).Name; + var assemblyName = new AssemblyName(args.Name).Name; if (assemblyName == "PepperDash_Core") { - return System.Reflection.Assembly.LoadFrom("PepperDashCore.dll"); + return Assembly.LoadFrom("PepperDashCore.dll"); } if (assemblyName == "PepperDash_Essentials_Core") { - return System.Reflection.Assembly.LoadFrom("PepperDash.Essentials.Core.dll"); + return Assembly.LoadFrom("PepperDash.Essentials.Core.dll"); } if (assemblyName == "Essentials Devices Common") { - return System.Reflection.Assembly.LoadFrom("PepperDash.Essentials.Devices.Common.dll"); + return Assembly.LoadFrom("PepperDash.Essentials.Devices.Common.dll"); } return null; @@ -79,7 +78,7 @@ namespace PepperDash.Essentials if (preventInitializationComplete) { Debug.LogMessage(LogEventLevel.Debug, "******************* InitializeSystem() Entering **********************"); - + _startTimer = new CTimer(StartSystem, preventInitializationComplete, StartupTime); _initializeEvent = new CEvent(true, false); DeviceManager.AllDevicesRegistered += (o, a) => @@ -88,7 +87,7 @@ namespace PepperDash.Essentials }; _initializeEvent.Wait(30000); Debug.LogMessage(LogEventLevel.Debug, "******************* InitializeSystem() Exiting **********************"); - + SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; } else @@ -99,7 +98,7 @@ namespace PepperDash.Essentials private void StartSystem(object preventInitialization) { - Debug.SetErrorLogMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose); + Debug.SetErrorLogMinimumDebugLevel(LogEventLevel.Verbose); DeterminePlatform(); @@ -201,8 +200,8 @@ namespace PepperDash.Essentials { userFolder = "User"; nvramFolder = "Nvram"; - } - + } + Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on {processorSeries:l} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series"); //Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{0} on {1} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series"); @@ -210,8 +209,8 @@ namespace PepperDash.Essentials 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); + + 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; } @@ -220,7 +219,7 @@ namespace PepperDash.Essentials + 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; } @@ -228,7 +227,7 @@ namespace PepperDash.Essentials 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; } @@ -236,7 +235,7 @@ namespace PepperDash.Essentials else // Handles Linux OS (Virtual Control) { //Debug.SetDebugLevel(2); - Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on Virtual Control Server", Global.AssemblyVersion); + Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on Virtual Control Server", Global.AssemblyVersion); // Set path to User/ filePathPrefix = directoryPrefix + dirSeparator + "User" + dirSeparator; @@ -246,7 +245,7 @@ namespace PepperDash.Essentials } catch (Exception e) { - Debug.LogMessage(e, "Unable to determine platform due to exception"); + Debug.LogMessage(e, "Unable to determine platform due to exception"); } } @@ -262,11 +261,11 @@ namespace PepperDash.Essentials PluginLoader.AddProgramAssemblies(); _ = new Core.DeviceFactory(); - _ = new Devices.Common.DeviceFactory(); - _ = new DeviceFactory(); + // _ = new Devices.Common.DeviceFactory(); + // _ = new DeviceFactory(); - _ = new ProcessorExtensionDeviceFactory(); - _ = new MobileControlFactory(); + // _ = new ProcessorExtensionDeviceFactory(); + // _ = new MobileControlFactory(); Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration"); @@ -313,7 +312,7 @@ namespace PepperDash.Essentials } - + /// /// Verifies filesystem is set up. IR, SGD, and programX folders @@ -333,46 +332,46 @@ namespace PepperDash.Essentials Directory.Create(irDir); var sgdDir = Global.FilePathPrefix + "sgd"; - if (!Directory.Exists(sgdDir)) - Directory.Create(sgdDir); + if (!Directory.Exists(sgdDir)) + Directory.Create(sgdDir); var pluginDir = Global.FilePathPrefix + "plugins"; if (!Directory.Exists(pluginDir)) Directory.Create(pluginDir); var joinmapDir = Global.FilePathPrefix + "joinmaps"; - if(!Directory.Exists(joinmapDir)) + if (!Directory.Exists(joinmapDir)) Directory.Create(joinmapDir); - return configExists; - } + return configExists; + } - /// - /// TearDown method - /// - public void TearDown() - { - Debug.LogMessage(LogEventLevel.Information, "Tearing down existing system"); - DeviceManager.DeactivateAll(); + /// + /// TearDown method + /// + public void TearDown() + { + Debug.LogMessage(LogEventLevel.Information, "Tearing down existing system"); + DeviceManager.DeactivateAll(); - TieLineCollection.Default.Clear(); + TieLineCollection.Default.Clear(); - foreach (var key in DeviceManager.GetDevices()) - DeviceManager.RemoveDevice(key); + foreach (var key in DeviceManager.GetDevices()) + DeviceManager.RemoveDevice(key); - Debug.LogMessage(LogEventLevel.Information, "Tear down COMPLETE"); - } + Debug.LogMessage(LogEventLevel.Information, "Tear down COMPLETE"); + } - /// - /// - /// - void Load() - { - LoadDevices(); - LoadRooms(); - LoadLogoServer(); + /// + /// + /// + void Load() + { + LoadDevices(); + LoadRooms(); + LoadLogoServer(); - DeviceManager.ActivateAll(); + DeviceManager.ActivateAll(); LoadTieLines(); @@ -381,8 +380,8 @@ namespace PepperDash.Essentials if (mobileControl == null) return; mobileControl.LinkSystemMonitorToAppServer();*/ - - } + + } /// /// LoadDevices method @@ -414,8 +413,8 @@ namespace PepperDash.Essentials { 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, @@ -430,14 +429,14 @@ namespace PepperDash.Essentials if (newDev == null) newDev = Core.DeviceFactory.GetDevice(devConf); - if (newDev != null) - DeviceManager.AddDevice(newDev); - else + 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(e, "ERROR: Creating device {deviceKey:l}. Skipping device.", args: new[] { devConf.Key }); } } Debug.LogMessage(LogEventLevel.Information, "All Devices Loaded."); @@ -475,27 +474,28 @@ namespace PepperDash.Essentials /// LoadRooms method /// 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) + foreach (var roomConfig in ConfigReader.ConfigObject.Rooms) { try { var room = Core.DeviceFactory.GetDevice(roomConfig); - if(room == null) + 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) + } + catch (Exception ex) { Debug.LogMessage(ex, "Exception loading room {roomKey}:{roomType}", null, roomConfig.Key, roomConfig.Type); continue; @@ -564,7 +564,7 @@ namespace PepperDash.Essentials } catch { - Debug.LogMessage(LogEventLevel.Information, "Unable to find logo information in any room config"); + 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 7a2df7d1..092a67a2 100644 --- a/src/PepperDash.Essentials/Factory/DeviceFactory.cs +++ b/src/PepperDash.Essentials/Factory/DeviceFactory.cs @@ -3,11 +3,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharpPro; -using System.Reflection; - using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PepperDash.Core; @@ -39,14 +38,33 @@ namespace PepperDash.Essentials try { var factory = (IDeviceFactory)Activator.CreateInstance(type); - factory.LoadTypeFactories(); + LoadDeviceFactories(factory); } catch (Exception e) { - Debug.LogMessage(Serilog.Events.LogEventLevel.Error, "Unable to load type: '{exception}' DeviceFactory: {factoryName}", e, type.Name); + 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); + } + } } }