feat: modify factory loading

Updating IDeviceFactory to resolve [FEATURE]-Refactor Plugin loading mechanism  #1065.
This change should be backwards-compatible with existing plugins that use the EssentialsPluginDeviceFactory<T> class,
as the interfaces are implemented by the various base classes.

In addition, the correct assembly name is now printed when a type is loaded.
This commit is contained in:
Andrew Welker
2025-07-04 16:07:45 -05:00
parent 1cbc8194ec
commit 04d6508c80
8 changed files with 1409 additions and 1261 deletions

View File

@@ -8,8 +8,8 @@ using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core;
{
public static class DeviceManager public static class DeviceManager
{ {
public static event EventHandler<EventArgs> AllDevicesActivated; public static event EventHandler<EventArgs> AllDevicesActivated;
@@ -17,7 +17,6 @@ namespace PepperDash.Essentials.Core
public static event EventHandler<EventArgs> AllDevicesInitialized; public static event EventHandler<EventArgs> AllDevicesInitialized;
private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection(); private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection();
private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true);
//public static List<Device> Devices { get { return _Devices; } } //public static List<Device> Devices { get { return _Devices; } }
//static List<Device> _Devices = new List<Device>(); //static List<Device> _Devices = new List<Device>();
@@ -27,13 +26,22 @@ namespace PepperDash.Essentials.Core
/// <summary> /// <summary>
/// Returns a copy of all the devices in a list /// Returns a copy of all the devices in a list
/// </summary> /// </summary>
public static List<IKeyed> AllDevices { get { return new List<IKeyed>(Devices.Values); } } public static List<IKeyed> AllDevices => [.. Devices.Values];
public static bool AddDeviceEnabled; public static bool AddDeviceEnabled;
/// <summary>
/// Initializes the control system by enabling device management and registering console commands.
/// </summary>
/// <remarks>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.</remarks>
/// <param name="cs">The <see cref="CrestronControlSystem"/> instance representing the control system to initialize.</param>
public static void Initialize(CrestronControlSystem cs) public static void Initialize(CrestronControlSystem cs)
{ {
AddDeviceEnabled = true; AddDeviceEnabled = true;
CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices", CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices",
ConsoleAccessLevelEnum.AccessOperator); ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks", CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks",
@@ -505,4 +513,3 @@ namespace PepperDash.Essentials.Core
} }
} }
} }
}

View File

@@ -9,15 +9,42 @@ using Serilog.Events;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.IO;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core;
{
/// <summary>
/// Represents a wrapper for a device factory, encapsulating the type of device, a description, and a factory method
/// for creating device instances.
/// </summary>
/// <remarks>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.</remarks>
public class DeviceFactoryWrapper public class DeviceFactoryWrapper
{ {
/// <summary>
/// Gets or sets the type associated with the current instance.
/// </summary>
public Type Type { get; set; } public Type Type { get; set; }
/// <summary>
/// Gets or sets the description associated with the object.
/// </summary>
public string Description { get; set; } public string Description { get; set; }
/// <summary>
/// Gets or sets the factory method used to create an <see cref="IKeyed"/> instance based on the provided <see
/// cref="DeviceConfig"/>.
/// </summary>
/// <remarks>The factory method allows customization of how <see cref="IKeyed"/> instances are created for
/// specific <see cref="DeviceConfig"/> inputs. Ensure the delegate is not null before invoking it.</remarks>
public Func<DeviceConfig, IKeyed> FactoryMethod { get; set; } public Func<DeviceConfig, IKeyed> FactoryMethod { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DeviceFactoryWrapper"/> class with default values.
/// </summary>
/// <remarks>The <see cref="Type"/> property is initialized to <see langword="null"/>, and the <see
/// cref="Description"/> property is set to "Not Available".</remarks>
public DeviceFactoryWrapper() public DeviceFactoryWrapper()
{ {
Type = null; Type = null;
@@ -25,29 +52,90 @@ namespace PepperDash.Essentials.Core
} }
} }
/// <summary>
/// Provides functionality for managing and registering device factories, including loading plugin-based factories and
/// retrieving devices based on their configuration.
/// </summary>
/// <remarks>The <see cref="DeviceFactory"/> 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.</remarks>
public class DeviceFactory public class DeviceFactory
{ {
/// <summary>
/// Initializes a new instance of the <see cref="DeviceFactory"/> class and loads all available device factories
/// from the current assembly.
/// </summary>
/// <remarks>This constructor scans the executing assembly for types that implement the <see
/// cref="IDeviceFactory"/> interface and are not abstract or interfaces. For each valid type, an instance is
/// created and passed to the <c>LoadDeviceFactories</c> method for further processing. If a type cannot be
/// instantiated, an informational log message is generated, and the process continues with the remaining
/// types.</remarks>
public DeviceFactory() public DeviceFactory()
{ {
var assy = Assembly.GetExecutingAssembly(); var programAssemblies = Directory.GetFiles(InitialParametersClass.ProgramDirectory.ToString(), "*.dll");
PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy);
var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); foreach(var assembly in programAssemblies)
if (types != null)
{ {
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) foreach (var type in types)
{ {
try try
{ {
var factory = (IDeviceFactory)Activator.CreateInstance(type); var factory = (IDeviceFactory)Activator.CreateInstance(type);
factory.LoadTypeFactories(); LoadDeviceFactories(factory);
} }
catch (Exception e) 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);
} }
} }
}
}
/// <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(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);
} }
} }
@@ -55,46 +143,57 @@ namespace PepperDash.Essentials.Core
/// A dictionary of factory methods, keyed by config types, added by plugins. /// A dictionary of factory methods, keyed by config types, added by plugins.
/// These methods are looked up and called by GetDevice in this class. /// These methods are looked up and called by GetDevice in this class.
/// </summary> /// </summary>
static Dictionary<string, DeviceFactoryWrapper> FactoryMethods = private static readonly Dictionary<string, DeviceFactoryWrapper> FactoryMethods =
new Dictionary<string, DeviceFactoryWrapper>(StringComparer.OrdinalIgnoreCase); new(StringComparer.OrdinalIgnoreCase);
/// <summary> /// <summary>
/// Adds a plugin factory method /// Registers a factory method for creating instances of a specific type.
/// </summary> /// </summary>
/// <param name="dc"></param> /// <remarks>This method associates a type name with a factory method, allowing instances of the type to
/// <returns></returns> /// be created dynamically. The factory method is stored internally and can be retrieved or invoked as
/// needed.</remarks>
/// <param name="typeName">The name of the type for which the factory method is being registered. This value cannot be null or empty.</param>
/// <param name="method">A delegate that defines the factory method. The delegate takes a <see cref="DeviceConfig"/> parameter and
/// returns an instance of <see cref="IKeyed"/>.</param>
public static void AddFactoryForType(string typeName, Func<DeviceConfig, IKeyed> method) public static void AddFactoryForType(string typeName, Func<DeviceConfig, IKeyed> method)
{ {
//Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName); FactoryMethods.Add(typeName, new DeviceFactoryWrapper() { FactoryMethod = method});
DeviceFactory.FactoryMethods.Add(typeName, new DeviceFactoryWrapper() { FactoryMethod = method});
} }
/// <summary>
/// Registers a factory method for creating instances of a specific device type.
/// </summary>
/// <remarks>If a factory method for the specified <paramref name="typeName"/> already exists, the method
/// will not overwrite it and will log an informational message instead.</remarks>
/// <param name="typeName">The unique name of the device type. This serves as the key for identifying the factory method.</param>
/// <param name="description">A brief description of the device type. This is used for informational purposes.</param>
/// <param name="Type">The <see cref="Type"/> of the device being registered. This represents the runtime type of the device.</param>
/// <param name="method">A factory method that takes a <see cref="DeviceConfig"/> as input and returns an instance of <see
/// cref="IKeyed"/>.</param>
public static void AddFactoryForType(string typeName, string description, Type Type, Func<DeviceConfig, IKeyed> method) public static void AddFactoryForType(string typeName, string description, Type Type, Func<DeviceConfig, IKeyed> 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; return;
} }
var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method }; var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method };
DeviceFactory.FactoryMethods.Add(typeName, wrapper);
FactoryMethods.Add(typeName, wrapper);
} }
private static void CheckForSecrets(IEnumerable<JProperty> obj) private static void CheckForSecrets(IEnumerable<JProperty> obj)
{ {
foreach (var prop in obj.Where(prop => prop.Value as JObject != null)) 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<SecretsPropertiesConfig>()); var secret = GetSecret(prop.Children().First().ToObject<SecretsPropertiesConfig>());
//var secret = GetSecret(JsonConvert.DeserializeObject<SecretsPropertiesConfig>(prop.Children().First().ToString()));
prop.Parent.Replace(secret); prop.Parent.Replace(secret);
} }
var recurseProp = prop.Value as JObject; if (prop.Value is not JObject recurseProp) return;
if (recurseProp == null) return;
CheckForSecrets(recurseProp.Properties()); CheckForSecrets(recurseProp.Properties());
} }
} }
@@ -108,56 +207,66 @@ namespace PepperDash.Essentials.Core
Debug.LogMessage(LogEventLevel.Debug, Debug.LogMessage(LogEventLevel.Debug,
"Unable to retrieve secret {0}{1} - Make sure you've added it to the secrets provider", "Unable to retrieve secret {0}{1} - Make sure you've added it to the secrets provider",
data.Provider, data.Key); data.Provider, data.Key);
return String.Empty; return string.Empty;
} }
/// <summary> /// <summary>
/// The factory method for Core "things". Also iterates the Factory methods that have /// Creates and returns a device instance based on the provided <see cref="DeviceConfig"/>.
/// been loaded from plugins
/// </summary> /// </summary>
/// <param name="dc"></param> /// <remarks>This method attempts to create a device using the type specified in the <paramref name="dc"/>
/// <returns></returns> /// 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 <see
/// langword="null"/>.</remarks>
/// <param name="dc">The configuration object containing the key, name, type, and properties required to create the device.</param>
/// <returns>An instance of a device that implements <see cref="IKeyed"/>, or <see langword="null"/> if the device type is
/// not recognized or an error occurs during creation.</returns>
public static IKeyed GetDevice(DeviceConfig dc) public static IKeyed GetDevice(DeviceConfig dc)
{ {
try try
{ {
Debug.LogMessage(LogEventLevel.Information, "Loading '{0}' from Essentials Core", dc.Type);
var localDc = new DeviceConfig(dc); var localDc = new DeviceConfig(dc);
var key = localDc.Key; var key = localDc.Key;
var name = localDc.Name; var name = localDc.Name;
var type = localDc.Type; var type = localDc.Type;
var properties = localDc.Properties; var properties = localDc.Properties;
//var propRecurse = properties;
var typeName = localDc.Type.ToLower(); var typeName = localDc.Type.ToLower();
if (properties is JObject jObject)
var jObject = properties as JObject;
if (jObject != null)
{ {
var jProp = jObject.Properties(); var jProp = jObject.Properties();
CheckForSecrets(jProp); CheckForSecrets(jProp);
} }
Debug.LogMessage(LogEventLevel.Verbose, "typeName = {0}", typeName); 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. // Check for types that have been added by plugin dlls.
return !FactoryMethods.ContainsKey(typeName) ? null : FactoryMethods[typeName].FactoryMethod(localDc); return wrapper.FactoryMethod(localDc);
} }
catch (Exception ex) 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; return null;
} }
} }
/// <summary> /// <summary>
/// Prints the type names and associated metadata from the FactoryMethods collection. /// Displays a list of device factory types that match the specified filter.
/// </summary> /// </summary>
/// <param name="filter"></param> /// <remarks>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."</remarks>
/// <param name="filter">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.</param>
public static void GetDeviceFactoryTypes(string filter) public static void GetDeviceFactoryTypes(string filter)
{ {
var types = !string.IsNullOrEmpty(filter) var types = !string.IsNullOrEmpty(filter)
@@ -184,10 +293,12 @@ namespace PepperDash.Essentials.Core
} }
/// <summary> /// <summary>
/// Returns the device factory dictionary /// Retrieves a dictionary of device factory wrappers, optionally filtered by a specified string.
/// </summary> /// </summary>
/// <param name="filter"></param> /// <param name="filter">A string used to filter the dictionary keys. Only entries with keys containing the specified filter will be
/// <returns></returns> /// included. If <see langword="null"/> or empty, all entries are returned.</param>
/// <returns>A dictionary where the keys are strings representing device identifiers and the values are <see
/// cref="DeviceFactoryWrapper"/> instances. The dictionary may be empty if no entries match the filter.</returns>
public static Dictionary<string, DeviceFactoryWrapper> GetDeviceFactoryDictionary(string filter) public static Dictionary<string, DeviceFactoryWrapper> GetDeviceFactoryDictionary(string filter)
{ {
return string.IsNullOrEmpty(filter) return string.IsNullOrEmpty(filter)
@@ -195,4 +306,3 @@ namespace PepperDash.Essentials.Core
: FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value); : FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value);
} }
} }
}

View File

@@ -14,7 +14,7 @@ namespace PepperDash.Essentials.Core;
{ {
public ProcessorExtensionDeviceFactory() { public ProcessorExtensionDeviceFactory() {
var assy = Assembly.GetExecutingAssembly(); var assy = Assembly.GetExecutingAssembly();
PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy); PluginLoader.AddLoadedAssembly(assy.GetName().Name, assy);
var extensions = assy.GetTypes().Where(ct => typeof(IProcessorExtensionDeviceFactory) var extensions = assy.GetTypes().Where(ct => typeof(IProcessorExtensionDeviceFactory)
.IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); .IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract);

View File

@@ -7,15 +7,15 @@ using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common namespace PepperDash.Essentials.Devices.Common;
{
public class DeviceFactory public class DeviceFactory
{ {
public DeviceFactory() public DeviceFactory()
{ {
var assy = Assembly.GetExecutingAssembly(); var assy = Assembly.GetExecutingAssembly();
PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy); PluginLoader.AddLoadedAssembly(assy.GetName().Name, assy);
var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract);
@@ -26,7 +26,7 @@ namespace PepperDash.Essentials.Devices.Common
try try
{ {
var factory = (IDeviceFactory)Activator.CreateInstance(type); var factory = (IDeviceFactory)Activator.CreateInstance(type);
factory.LoadTypeFactories(); LoadDeviceFactories(factory);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -35,5 +35,23 @@ namespace PepperDash.Essentials.Devices.Common
} }
} }
} }
/// <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(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);
}
} }
} }

View File

@@ -4,15 +4,15 @@ using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace PepperDash.Essentials namespace PepperDash.Essentials;
{
public class MobileControlFactory public class MobileControlFactory
{ {
public MobileControlFactory() public MobileControlFactory()
{ {
var assembly = Assembly.GetExecutingAssembly(); var assembly = Assembly.GetExecutingAssembly();
PluginLoader.SetEssentialsAssembly(assembly.GetName().Name, assembly); PluginLoader.AddLoadedAssembly(assembly.GetName().Name, assembly);
var types = assembly.GetTypes().Where(t => typeof(IDeviceFactory).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); var types = assembly.GetTypes().Where(t => typeof(IDeviceFactory).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
@@ -27,7 +27,7 @@ namespace PepperDash.Essentials
{ {
var factory = (IDeviceFactory)Activator.CreateInstance(type); var factory = (IDeviceFactory)Activator.CreateInstance(type);
factory.LoadTypeFactories(); LoadDeviceFactories(factory);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -35,5 +35,23 @@ namespace PepperDash.Essentials
} }
} }
} }
/// <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(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);
}
} }
} }

View File

@@ -14,57 +14,62 @@ using System;
using System.Linq; using System.Linq;
using Serilog.Events; using Serilog.Events;
using PepperDash.Essentials.Core.Routing; using PepperDash.Essentials.Core.Routing;
using System.Threading;
using Timeout = Crestron.SimplSharp.Timeout;
namespace PepperDash.Essentials namespace PepperDash.Essentials;
{
/// <summary>
/// Represents the main control system for the application, providing initialization, configuration loading, and device
/// management functionality.
/// </summary>
/// <remarks>This class extends <see cref="CrestronControlSystem"/> 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.</remarks>
public class ControlSystem : CrestronControlSystem, ILoadConfig public class ControlSystem : CrestronControlSystem, ILoadConfig
{ {
HttpLogoServer LogoServer; private HttpLogoServer LogoServer;
private CTimer _startTimer; private Timer _startTimer;
private CEvent _initializeEvent; private ManualResetEventSlim _initializeEvent;
private const long StartupTime = 500; private const long StartupTime = 500;
/// <summary>
/// Initializes a new instance of the <see cref="ControlSystem"/> class, setting up the system's global state and
/// dependencies.
/// </summary>
/// <remarks>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.</remarks>
public ControlSystem() public ControlSystem()
: base() : base()
{ {
try
{
Crestron.SimplSharpPro.CrestronThread.Thread.MaxNumberOfUserThreads = 400;
Thread.MaxNumberOfUserThreads = 400;
Global.ControlSystem = this; Global.ControlSystem = this;
DeviceManager.Initialize(this); DeviceManager.Initialize(this);
SecretsManager.Initialize(); SecretsManager.Initialize();
SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true;
Debug.SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? LogEventLevel.Warning : LogEventLevel.Verbose); Debug.SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? LogEventLevel.Warning : LogEventLevel.Verbose);
// AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;
} }
catch (Exception e)
private System.Reflection.Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
{ {
var assemblyName = new System.Reflection.AssemblyName(args.Name).Name; Debug.LogError(e, "FATAL INITIALIZE ERROR. System is in an inconsistent state");
if (assemblyName == "PepperDash_Core")
{
return System.Reflection.Assembly.LoadFrom("PepperDashCore.dll");
} }
if (assemblyName == "PepperDash_Essentials_Core")
{
return System.Reflection.Assembly.LoadFrom("PepperDash.Essentials.Core.dll");
}
if (assemblyName == "Essentials Devices Common")
{
return System.Reflection.Assembly.LoadFrom("PepperDash.Essentials.Devices.Common.dll");
}
return null;
} }
/// <summary> /// <summary>
/// Entry point for the program /// Initializes the control system and prepares it for operation.
/// </summary> /// </summary>
/// <remarks>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.</remarks>
public override void InitializeSystem() public override void InitializeSystem()
{ {
// If the control system is a DMPS type, we need to wait to exit this method until all devices have had time to activate // If the control system is a DMPS type, we need to wait to exit this method until all devices have had time to activate
@@ -72,29 +77,31 @@ namespace PepperDash.Essentials
bool preventInitializationComplete = Global.ControlSystemIsDmpsType; bool preventInitializationComplete = Global.ControlSystemIsDmpsType;
if (preventInitializationComplete) if (preventInitializationComplete)
{ {
Debug.LogMessage(LogEventLevel.Debug, "******************* InitializeSystem() Entering **********************"); Debug.LogMessage(LogEventLevel.Debug, "******************* Initializing System **********************");
_startTimer = new Timer(StartSystem, preventInitializationComplete, StartupTime, Timeout.Infinite);
_initializeEvent = new ManualResetEventSlim(false);
_startTimer = new CTimer(StartSystem, preventInitializationComplete, StartupTime);
_initializeEvent = new CEvent(true, false);
DeviceManager.AllDevicesRegistered += (o, a) => DeviceManager.AllDevicesRegistered += (o, a) =>
{ {
_initializeEvent.Set(); _initializeEvent.Set();
}; };
_initializeEvent.Wait(30000); _initializeEvent.Wait(30000);
Debug.LogMessage(LogEventLevel.Debug, "******************* InitializeSystem() Exiting **********************");
Debug.LogMessage(LogEventLevel.Debug, "******************* System Initialization Complete **********************");
SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true;
} }
else else
{ {
_startTimer = new CTimer(StartSystem, preventInitializationComplete, StartupTime); _startTimer = new Timer(StartSystem, preventInitializationComplete, StartupTime, Timeout.Infinite);
} }
} }
private void StartSystem(object preventInitialization) private void StartSystem(object preventInitialization)
{ {
Debug.SetErrorLogMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose);
DeterminePlatform(); DeterminePlatform();
// Print .NET runtime version // Print .NET runtime version
@@ -148,7 +155,6 @@ namespace PepperDash.Essentials
CrestronConsole.AddNewConsoleCommand(DeviceManager.GetRoutingPorts, CrestronConsole.AddNewConsoleCommand(DeviceManager.GetRoutingPorts,
"getroutingports", "Reports all routing ports, if any. Requires a device key", ConsoleAccessLevelEnum.AccessOperator); "getroutingports", "Reports all routing ports, if any. Requires a device key", ConsoleAccessLevelEnum.AccessOperator);
//DeviceManager.AddDevice(new EssentialsWebApi("essentialsWebApi", "Essentials Web API"));
if (!Debug.DoNotLoadConfigOnNextBoot) if (!Debug.DoNotLoadConfigOnNextBoot)
{ {
@@ -247,11 +253,6 @@ namespace PepperDash.Essentials
PluginLoader.AddProgramAssemblies(); PluginLoader.AddProgramAssemblies();
_ = new Core.DeviceFactory(); _ = new Core.DeviceFactory();
_ = new Devices.Common.DeviceFactory();
_ = new DeviceFactory();
_ = new ProcessorExtensionDeviceFactory();
_ = new MobileControlFactory();
Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration"); Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration");
@@ -400,8 +401,8 @@ namespace PepperDash.Essentials
{ {
var prompt = Global.ControlSystem.ControllerPrompt; var prompt = Global.ControlSystem.ControllerPrompt;
var typeMatch = String.Equals(devConf.Type, prompt, StringComparison.OrdinalIgnoreCase) || var typeMatch = string.Equals(devConf.Type, prompt, StringComparison.OrdinalIgnoreCase) ||
String.Equals(devConf.Type, prompt.Replace("-", ""), StringComparison.OrdinalIgnoreCase); string.Equals(devConf.Type, prompt.Replace("-", ""), StringComparison.OrdinalIgnoreCase);
if (!typeMatch) if (!typeMatch)
Debug.LogMessage(LogEventLevel.Information, Debug.LogMessage(LogEventLevel.Information,
@@ -555,4 +556,3 @@ namespace PepperDash.Essentials
} }
} }
} }
}

View File

@@ -14,8 +14,8 @@ using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials namespace PepperDash.Essentials;
{
/// <summary> /// <summary>
/// Responsible for loading all of the device types for this library /// Responsible for loading all of the device types for this library
/// </summary> /// </summary>
@@ -25,7 +25,7 @@ namespace PepperDash.Essentials
public DeviceFactory() public DeviceFactory()
{ {
var assy = Assembly.GetExecutingAssembly(); var assy = Assembly.GetExecutingAssembly();
PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy); PluginLoader.AddLoadedAssembly(assy.GetName().Name, assy);
var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract);
@@ -36,7 +36,7 @@ namespace PepperDash.Essentials
try try
{ {
var factory = (IDeviceFactory)Activator.CreateInstance(type); var factory = (IDeviceFactory)Activator.CreateInstance(type);
factory.LoadTypeFactories(); LoadDeviceFactories(factory);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -45,5 +45,23 @@ namespace PepperDash.Essentials
} }
} }
} }
/// <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(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);
}
} }
} }

View File

@@ -26,29 +26,6 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Include="Example Configuration\EssentialsHuddleSpaceRoom\configurationFile-HuddleSpace-2-Source.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Example Configuration\EssentialsHuddleVtc1Room\configurationFile-mockVideoCodec_din-ap3_-_dm4x1.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Example Configuration\SIMPLBridging\configurationFile-dmps3300c-avRouting.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Example Configuration\SIMPLBridging\SIMPLBridgeExample_configurationFile.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="SGD\PepperDash Essentials iPad.sgd">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="SGD\PepperDash Essentials TSW-560.sgd">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="SGD\PepperDash Essentials TSW-760.sgd">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.21.128" /> <PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.21.128" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />