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
This commit is contained in:
Andrew Welker
2025-07-25 08:28:55 -05:00
parent 86f20da116
commit 8db559f197
17 changed files with 662 additions and 379 deletions

View File

@@ -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
}
/// <summary>
/// Defines a class that is capable of loading custom plugin device types for development purposes
/// </summary>
[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
{
/// <summary>
/// Gets a list of all the development versions of the Essentials framework that are supported by this factory.
/// </summary>
List<string> DevelopmentEssentialsFrameworkVersions { get; }
}
}

View File

@@ -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
/// </summary>
static List<LoadedAssembly> LoadedPluginFolderAssemblies;
/// <summary>
/// The assembly for the Essentials Framework
/// </summary>
public static LoadedAssembly EssentialsAssembly { get; private set; }
/// <summary>
/// The assembly for the PepperDash Core
/// </summary>
public static LoadedAssembly PepperDashCoreAssembly { get; private set; }
/// <summary>
/// The list of assemblies loaded from the Essentials plugins folder
/// </summary>
public static List<LoadedAssembly> EssentialsPluginAssemblies { get; private set; }
/// <summary>
@@ -131,7 +138,7 @@ namespace PepperDash.Essentials
/// <summary>
/// Loads an assembly via Reflection and adds it to the list of loaded assemblies
/// </summary>
/// <param name="fileName"></param>
/// <param name="filePath"></param>
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
}
}
/// <summary>
/// Associates the specified assembly with the given name in the loaded assemblies collection.
/// </summary>
/// <remarks>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.</remarks>
/// <param name="name">The name used to identify the assembly. This value is case-sensitive and must not be null or empty.</param>
/// <param name="assembly">The assembly to associate with the specified name. This value must not be null.</param>
public static void AddLoadedAssembly(string name, Assembly assembly)
{
var loadedAssembly = LoadedAssemblies.FirstOrDefault(la => la.Name.Equals(name));
loadedAssembly?.SetAssembly(assembly);
}
/// <summary>
/// Used by console command to report the currently loaded assemblies and versions
/// </summary>
@@ -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
}
/// <summary>
/// Loads a
/// Loads a custom plugin and performs a dependency check to ensure compatibility with the required Essentials
/// framework version.
/// </summary>
/// <param name="plugin"></param>
/// <param name="loadedAssembly"></param>
static void LoadCustomPlugin(IPluginDeviceFactory plugin, LoadedAssembly loadedAssembly)
/// <remarks>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.</remarks>
/// <param name="pluginDeviceFactory">The plugin to be loaded, implementing the <see cref="IPluginDeviceFactory"/> interface. If the plugin also
/// implements <see cref="IPluginDevelopmentDeviceFactory"/>, additional checks for development versions are
/// performed.</param>
/// <param name="loadedAssembly">The assembly associated with the plugin being loaded. This is used for logging and tracking purposes.</param>
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);
}
/// <summary>
/// Loads device factories from the specified plugin device factory and registers them for use.
/// </summary>
/// <remarks>This method retrieves metadata from the provided <paramref name="deviceFactory"/>, including
/// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type
/// names are converted to lowercase for registration.</remarks>
/// <param name="deviceFactory">The plugin device factory that provides the device types, descriptions, and factory methods to be registered.</param>
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);
}
}
/// <summary>
/// Loads a a custom plugin via the legacy method
/// </summary>
@@ -567,13 +613,30 @@ namespace PepperDash.Essentials
/// </summary>
public class LoadedAssembly
{
/// <summary>
/// Gets the name of the assembly
/// </summary>
[JsonProperty("name")]
public string Name { get; private set; }
/// <summary>
/// Gets the version of the assembly
/// </summary>
[JsonProperty("version")]
public string Version { get; private set; }
/// <summary>
/// Gets the assembly
/// </summary>
[JsonIgnore]
public Assembly Assembly { get; private set; }
/// <summary>
/// Initializes a new instance of the LoadedAssembly class
/// </summary>
/// <param name="name">The name of the assembly</param>
/// <param name="version">The version of the assembly</param>
/// <param name="assembly">The assembly</param>
public LoadedAssembly(string name, string version, Assembly assembly)
{
Name = name;