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

@@ -0,0 +1,33 @@
using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a ConfigSnippetAttribute
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class ConfigSnippetAttribute : Attribute
{
private string _ConfigSnippet;
/// <summary>
/// Represents a configuration snippet for the device.
/// </summary>
/// <param name="configSnippet"></param>
public ConfigSnippetAttribute(string configSnippet)
{
//Debug.LogMessage(LogEventLevel.Verbose, "Setting Config Snippet {0}", configSnippet);
_ConfigSnippet = configSnippet;
}
/// <summary>
/// Gets the configuration snippet for the device.
/// This snippet can be used in the DeviceConfig to instantiate the device.
/// </summary>
public string ConfigSnippet
{
get { return _ConfigSnippet; }
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a description attribute for a device.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class DescriptionAttribute : Attribute
{
private string _Description;
/// <summary>
/// Represents a description attribute for a device.
/// </summary>
/// <param name="description"></param>
public DescriptionAttribute(string description)
{
//Debug.LogMessage(LogEventLevel.Verbose, "Setting Description: {0}", description);
_Description = description;
}
/// <summary>
/// Gets the description for the device.
/// </summary>
public string Description
{
get { return _Description; }
}
}
}

View File

@@ -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
{
/// <summary>
/// Manages the devices in the system
/// </summary>
public static class DeviceManager
{
/// <summary>
/// Raised when all devices have been activated
/// </summary>
public static event EventHandler<EventArgs> AllDevicesActivated;
/// <summary>
/// Raised when all devices have been registered
/// </summary>
public static event EventHandler<EventArgs> AllDevicesRegistered;
/// <summary>
/// Raised when all devices have been initialized
/// </summary>
public static event EventHandler<EventArgs> AllDevicesInitialized;
private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection();
private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true);
//public static List<Device> Devices { get { return _Devices; } }
//static List<Device> _Devices = new List<Device>();
private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true);
private static readonly Dictionary<string, IKeyed> Devices = new Dictionary<string, IKeyed>(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;
}
/// <summary>
/// GetDeviceForKey method
/// </summary>
/// <typeparam name="T"></typeparam>
public static T GetDeviceForKey<T>(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];
}
/// <summary>
/// Console handler that simulates com port data receive
/// </summary>

View File

@@ -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
{
/// <summary>
/// Event raised when the device is initialized.
/// </summary>
public event EventHandler Initialized;
private bool _isInitialized;
/// <summary>
/// Gets a value indicating whether the device is initialized.
/// </summary>
public bool IsInitialized
{
get { return _isInitialized; }
@@ -36,12 +37,21 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Initializes a new instance of the EssentialsDevice class.
/// </summary>
/// <param name="key">The unique identifier for the device.</param>
protected EssentialsDevice(string key)
: base(key)
{
SubscribeToActivateComplete();
}
/// <summary>
/// Initializes a new instance of the EssentialsDevice class.
/// </summary>
/// <param name="key">The unique identifier for the device.</param>
/// <param name="name">The name of the device.</param>
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)]
/// <summary>
/// Represents a ConfigSnippetAttribute
/// </summary>
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; }
}
}
/// <summary>
/// Devices the basic needs for a Device Factory
/// </summary>
public abstract class EssentialsDeviceFactory<T> : IDeviceFactory where T:EssentialsDevice
{
#region IDeviceFactory Members
/// <summary>
/// A list of strings that can be used in the type property of a DeviceConfig object to build an instance of this device
/// </summary>
public List<string> TypeNames { get; protected set; }
/// <summary>
/// LoadTypeFactories method
/// </summary>
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);
}
}
/// <summary>
/// The method that will build the device
/// </summary>
/// <param name="dc">The device config</param>
/// <returns>An instance of the device</returns>
public abstract EssentialsDevice BuildDevice(DeviceConfig dc);
#endregion
}
public abstract class ProcessorExtensionDeviceFactory<T> : IProcessorExtensionDeviceFactory where T: EssentialsDevice
{
#region IProcessorExtensionDeviceFactory Members
/// <summary>
/// Gets or sets the TypeNames
/// </summary>
public List<string> TypeNames { get; protected set; }
/// <summary>
/// LoadFactories method
/// </summary>
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);
}
}
/// <summary>
/// The method that will build the device
/// </summary>
/// <param name="dc">The device config</param>
/// <returns>An instance of the device</returns>
public abstract EssentialsDevice BuildDevice(DeviceConfig dc);
#endregion
}
/// <summary>
/// Devices the basic needs for a Device Factory
/// </summary>
public abstract class EssentialsPluginDeviceFactory<T> : EssentialsDeviceFactory<T>, IPluginDeviceFactory where T : EssentialsDevice
{
/// <summary>
/// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33")
/// </summary>
public string MinimumEssentialsFrameworkVersion { get; protected set; }
}
public abstract class EssentialsPluginDevelopmentDeviceFactory<T> : EssentialsDeviceFactory<T>, IPluginDevelopmentDeviceFactory where T : EssentialsDevice
{
/// <summary>
/// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33")
/// </summary>
public string MinimumEssentialsFrameworkVersion { get; protected set; }
/// <summary>
/// Gets or sets the DevelopmentEssentialsFrameworkVersions
/// </summary>
public List<string> DevelopmentEssentialsFrameworkVersions { get; protected set; }
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Provides the basic needs for a Device Factory
/// </summary>
public abstract class EssentialsDeviceFactory<T> : IDeviceFactory where T : EssentialsDevice
{
/// <inheritdoc />
public Type FactoryType => typeof(T);
/// <summary>
/// A list of strings that can be used in the type property of a DeviceConfig object to build an instance of this device
/// </summary>
public List<string> TypeNames { get; protected set; }
/// <summary>
/// The method that will build the device
/// </summary>
/// <param name="dc">The device config</param>
/// <returns>An instance of the device</returns>
public abstract EssentialsDevice BuildDevice(DeviceConfig dc);
}
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace PepperDash.Essentials.Core
{
public abstract class EssentialsPluginDevelopmentDeviceFactory<T> : EssentialsDeviceFactory<T>, IPluginDevelopmentDeviceFactory where T : EssentialsDevice
{
/// <summary>
/// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33")
/// </summary>
public string MinimumEssentialsFrameworkVersion { get; protected set; }
/// <summary>
/// Gets or sets the DevelopmentEssentialsFrameworkVersions
/// </summary>
public List<string> DevelopmentEssentialsFrameworkVersions { get; protected set; }
}
}

View File

@@ -0,0 +1,14 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Devices the basic needs for a Device Factory
/// </summary>
public abstract class EssentialsPluginDeviceFactory<T> : EssentialsDeviceFactory<T>, IPluginDeviceFactory where T : EssentialsDevice
{
/// <summary>
/// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33")
/// </summary>
public string MinimumEssentialsFrameworkVersion { get; protected set; }
}
}

View File

@@ -0,0 +1,41 @@
using System.Collections.Generic;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core
{
public abstract class ProcessorExtensionDeviceFactory<T> : IProcessorExtensionDeviceFactory where T : EssentialsDevice
{
#region IProcessorExtensionDeviceFactory Members
/// <summary>
/// Gets or sets the TypeNames
/// </summary>
public List<string> TypeNames { get; protected set; }
/// <summary>
/// LoadFactories method
/// </summary>
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);
}
}
/// <summary>
/// The method that will build the device
/// </summary>
/// <param name="dc">The device config</param>
/// <returns>An instance of the device</returns>
public abstract EssentialsDevice BuildDevice(DeviceConfig dc);
#endregion
}
}