diff --git a/Crestron-Library-Usage-Analysis.md b/Crestron-Library-Usage-Analysis.md new file mode 100644 index 00000000..e295d0db --- /dev/null +++ b/Crestron-Library-Usage-Analysis.md @@ -0,0 +1,282 @@ +# Crestron Library Usage Analysis - PepperDash Essentials + +This document provides a comprehensive analysis of Crestron classes and interfaces used throughout the PepperDash Essentials framework, organized by namespace and library component. + +## Executive Summary + +The PepperDash Essentials framework extensively leverages Crestron SDK components across 100+ files, providing abstractions for: +- Control system hardware (processors, touchpanels, IO devices) +- Communication interfaces (Serial, TCP/IP, SSH, CEC, IR) +- Device management and routing +- User interface components and smart objects +- System monitoring and diagnostics + +## 1. Core Crestron Libraries + +### 1.1 Crestron.SimplSharp + +**Primary Usage**: Foundational framework components, collections, and basic types. + +**Key Files**: +- Multiple files across all projects use `Crestron.SimplSharp` namespaces +- Provides basic C# runtime support for Crestron processors + +### 1.2 Crestron.SimplSharpPro + +**Primary Usage**: Main hardware abstraction layer for Crestron devices. + +**Key Classes Used**: + +#### CrestronControlSystem +- **File**: `/src/PepperDash.Essentials/ControlSystem.cs` +- **Usage**: Base class for the main control system implementation +- **Implementation**: `public class ControlSystem : CrestronControlSystem, ILoadConfig` + +#### Device (Base Class) +- **Files**: 50+ files inherit from or use this class +- **Key Implementations**: + - `/src/PepperDash.Core/Device.cs` - Core device abstraction + - `/src/PepperDash.Essentials.Core/Devices/EssentialsDevice.cs` - Extended device base + - `/src/PepperDash.Essentials.Core/Room/Room.cs` - Room device implementation + - `/src/PepperDash.Essentials.Core/Devices/CrestronProcessor.cs` - Processor device wrapper + +#### BasicTriList +- **Files**: 30+ files use this class extensively +- **Primary Usage**: Touchpanel communication and SIMPL bridging +- **Key Files**: + - `/src/PepperDash.Essentials.Core/Touchpanels/TriListExtensions.cs` - Extension methods for signal handling + - `/src/PepperDash.Essentials.Core/Devices/EssentialsBridgeableDevice.cs` - Bridge interface + - `/src/PepperDash.Essentials.Core/Touchpanels/ModalDialog.cs` - UI dialog implementation + +#### BasicTriListWithSmartObject +- **Files**: Multiple touchpanel and UI files +- **Usage**: Enhanced touchpanel support with smart object integration +- **Key Files**: + - `/src/PepperDash.Essentials.Core/Touchpanels/Interfaces.cs` - Interface definitions + - `/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferenceList/SubpageReferenceList.cs` + +## 2. Communication Hardware + +### 2.1 Serial Communication (ComPort) + +**Primary Class**: `ComPort` +**Key Files**: +- `/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs` +- `/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs` + +**Usage Pattern**: +```csharp +public class ComPortController : Device, IBasicCommunicationWithStreamDebugging +public static ComPort GetComPort(EssentialsControlPropertiesConfig config) +``` + +**Interface Support**: `IComPorts` - Used for devices that provide multiple COM ports + +### 2.2 IR Communication (IROutputPort) + +**Primary Class**: `IROutputPort` +**Key Files**: +- `/src/PepperDash.Essentials.Core/Devices/IrOutputPortController.cs` +- `/src/PepperDash.Essentials.Core/Devices/GenericIRController.cs` +- `/src/PepperDash.Essentials.Core/Comm and IR/IRPortHelper.cs` + +**Usage Pattern**: +```csharp +public class IrOutputPortController : Device +IROutputPort IrPort; +public IrOutputPortController(string key, IROutputPort port, string irDriverFilepath) +``` + +### 2.3 CEC Communication (ICec) + +**Primary Interface**: `ICec` +**Key Files**: +- `/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs` +- `/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs` + +**Usage Pattern**: +```csharp +public class CecPortController : Device, IBasicCommunicationWithStreamDebugging +public static ICec GetCecPort(ControlPropertiesConfig config) +``` + +## 3. Input/Output Hardware + +### 3.1 Digital Input + +**Primary Interface**: `IDigitalInput` +**Key Files**: +- `/src/PepperDash.Essentials.Core/CrestronIO/GenericDigitalInputDevice.cs` +- `/src/PepperDash.Essentials.Core/Microphone Privacy/MicrophonePrivacyController.cs` + +**Usage Pattern**: +```csharp +public List Inputs { get; private set; } +void AddInput(IDigitalInput input) +``` + +### 3.2 Versiport Support + +**Key Files**: +- `/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportInputDevice.cs` +- `/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportAnalogInputDevice.cs` +- `/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportOutputDevice.cs` + +**Usage**: Provides flexible I/O port configuration for various signal types + +## 4. Touchpanel Hardware + +### 4.1 MPC3 Touchpanel + +**Primary Class**: `MPC3Basic` +**Key File**: `/src/PepperDash.Essentials.Core/Touchpanels/Mpc3Touchpanel.cs` + +**Usage Pattern**: +```csharp +public class Mpc3TouchpanelController : Device +readonly MPC3Basic _touchpanel; +_touchpanel = processor.ControllerTouchScreenSlotDevice as MPC3Basic; +``` + +### 4.2 TSW Series Support + +**Evidence**: References found in messenger files and mobile control components +**Usage**: Integrated through mobile control messaging system for TSW touchpanel features + +## 5. Timer and Threading + +### 5.1 CTimer + +**Primary Class**: `CTimer` +**Key File**: `/src/PepperDash.Core/PasswordManagement/PasswordManager.cs` + +**Usage Pattern**: +```csharp +Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Started")); +Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Reset")); +``` + +## 6. Networking and Communication + +### 6.1 Ethernet Communication + +**Libraries Used**: +- `Crestron.SimplSharpPro.EthernetCommunication` +- `Crestron.SimplSharp.Net.Utilities.EthernetHelper` + +**Key Files**: +- `/src/PepperDash.Core/Comm/GenericTcpIpClient.cs` +- `/src/PepperDash.Core/Comm/GenericTcpIpServer.cs` +- `/src/PepperDash.Core/Comm/GenericSecureTcpIpClient.cs` +- `/src/PepperDash.Core/Comm/GenericSshClient.cs` +- `/src/PepperDash.Core/Comm/GenericUdpServer.cs` + +**Usage Pattern**: +```csharp +public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect +public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect +``` + +## 7. Device Management Libraries + +### 7.1 DeviceSupport + +**Library**: `Crestron.SimplSharpPro.DeviceSupport` +**Usage**: Core device support infrastructure used throughout the framework + +### 7.2 DM (DigitalMedia) + +**Library**: `Crestron.SimplSharpPro.DM` +**Usage**: Digital media routing and switching support +**Evidence**: Found in routing configuration and DM output card references + +## 8. User Interface Libraries + +### 8.1 UI Components + +**Library**: `Crestron.SimplSharpPro.UI` +**Usage**: User interface elements and touchpanel controls + +### 8.2 Smart Objects + +**Key Files**: +- `/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectDynamicList.cs` +- `/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferenceList/SubpageReferenceList.cs` + +**Usage**: Advanced UI components with dynamic content + +## 9. System Monitoring and Diagnostics + +### 9.1 Diagnostics + +**Library**: `Crestron.SimplSharpPro.Diagnostics` +**Usage**: System health monitoring and performance tracking + +### 9.2 System Information + +**Key Files**: +- `/src/PepperDash.Essentials.Core/Monitoring/SystemMonitorController.cs` + +**Usage**: Provides system status, Ethernet information, and program details + +## 10. Integration Patterns + +### 10.1 SIMPL Bridging + +**Pattern**: Extensive use of `BasicTriList` for SIMPL integration +**Files**: Bridge classes throughout the framework implement `LinkToApi` methods: +```csharp +public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); +``` + +### 10.2 Device Factory Pattern + +**Implementation**: Factory classes create hardware-specific implementations +**Example**: `CommFactory.cs` provides communication device creation + +### 10.3 Extension Methods + +**Pattern**: Extensive use of extension methods for Crestron classes +**Example**: `TriListExtensions.cs` adds 30+ extension methods to `BasicTriList` + +## 11. Signal Processing + +### 11.1 Signal Types + +**Bool Signals**: Digital control and feedback +**UShort Signals**: Analog values and numeric data +**String Signals**: Text and configuration data + +**Implementation**: Comprehensive signal handling in `TriListExtensions.cs` + +## 12. Error Handling and Logging + +**Pattern**: Consistent use of Crestron's Debug logging throughout +**Examples**: +```csharp +Debug.LogMessage(LogEventLevel.Information, "Device {0} is not a valid device", dc.PortDeviceKey); +Debug.LogMessage(LogEventLevel.Debug, "Error Waking Panel. Maybe testing with Xpanel?"); +``` + +## 13. Threading and Synchronization + +**Components**: +- CTimer for time-based operations +- Thread-safe collections and patterns +- Event-driven programming models + +## Conclusion + +The PepperDash Essentials framework demonstrates sophisticated integration with the Crestron ecosystem, leveraging: + +- **Core Infrastructure**: CrestronControlSystem, Device base classes +- **Communication**: COM, IR, CEC, TCP/IP, SSH protocols +- **Hardware Abstraction**: Touchpanels, I/O devices, processors +- **User Interface**: Smart objects, signal processing, SIMPL bridging +- **System Services**: Monitoring, diagnostics, device management + +This analysis shows that Essentials serves as a comprehensive middleware layer, abstracting Crestron hardware complexities while providing modern software development patterns and practices. + +--- +*Generated: [Current Date]* +*Framework Version: PepperDash Essentials (Based on codebase analysis)* diff --git a/src/PepperDash.Core/Comm/GenericSshClient.cs b/src/PepperDash.Core/Comm/GenericSshClient.cs index 3ba9059e..13192ed5 100644 --- a/src/PepperDash.Core/Comm/GenericSshClient.cs +++ b/src/PepperDash.Core/Comm/GenericSshClient.cs @@ -146,31 +146,31 @@ namespace PepperDash.Core base(key) { StreamDebugging = new CommunicationStreamDebugging(key); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - Key = key; - Hostname = hostname; - Port = port; - Username = username; - Password = password; - AutoReconnectIntervalMs = 5000; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + Key = key; + Hostname = hostname; + Port = port; + Username = username; + Password = password; + AutoReconnectIntervalMs = 5000; ReconnectTimer = new CTimer(o => - { + { if (ConnectEnabled) { Connect(); } - }, System.Threading.Timeout.Infinite); - } + }, System.Threading.Timeout.Infinite); + } - /// - /// S+ Constructor - Must set all properties before calling Connect - /// - public GenericSshClient() - : base(SPlusKey) - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; + /// + /// S+ Constructor - Must set all properties before calling Connect + /// + public GenericSshClient() + : base(SPlusKey) + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; ReconnectTimer = new CTimer(o => { @@ -179,18 +179,18 @@ namespace PepperDash.Core Connect(); } }, System.Threading.Timeout.Infinite); - } + } - /// - /// Handles closing this up when the program shuts down - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping) - { - if (Client != null) - { - this.LogDebug("Program stopping. Closing connection"); + /// + /// Handles closing this up when the program shuts down + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + if (Client != null) + { + this.LogDebug("Program stopping. Closing connection"); Disconnect(); } } @@ -223,10 +223,7 @@ namespace PepperDash.Core this.LogDebug("Attempting connect"); // Cancel reconnect if running. - if (ReconnectTimer != null) - { - ReconnectTimer.Stop(); - } + ReconnectTimer?.Stop(); // Cleanup the old client if it already exists if (Client != null) @@ -268,20 +265,26 @@ namespace PepperDash.Core if (ie is SocketException) { - this.LogException(ie, "CONNECTION failure: Cannot reach host"); + this.LogError("CONNECTION failure: Cannot reach host"); + this.LogVerbose(ie, "Exception details: "); } if (ie is System.Net.Sockets.SocketException socketException) { - this.LogException(ie, "Connection failure: Cannot reach {host} on {port}", + this.LogError("Connection failure: Cannot reach {host} on {port}", Hostname, Port); + this.LogVerbose(socketException, "SocketException details: "); } if (ie is SshAuthenticationException) { - this.LogException(ie, "Authentication failure for username {userName}", Username); + this.LogError("Authentication failure for username {userName}", Username); + this.LogVerbose(ie, "AuthenticationException details: "); } else - this.LogException(ie, "Error on connect"); + { + this.LogError("Error on connect: {error}", ie.Message); + this.LogVerbose(ie, "Exception details: "); + } DisconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); @@ -291,7 +294,7 @@ namespace PepperDash.Core ReconnectTimer.Reset(AutoReconnectIntervalMs); } } - catch(SshOperationTimeoutException ex) + catch (SshOperationTimeoutException ex) { this.LogWarning("Connection attempt timed out: {message}", ex.Message); @@ -306,7 +309,8 @@ namespace PepperDash.Core catch (Exception e) { var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; - this.LogException(e, "Unhandled exception on connect"); + this.LogError("Unhandled exception on connect: {error}", e.Message); + this.LogVerbose(e, "Exception details: "); DisconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); if (AutoReconnect) @@ -323,18 +327,18 @@ namespace PepperDash.Core } } - /// - /// Disconnect method - /// - public void Disconnect() - { - ConnectEnabled = false; - // Stop trying reconnects, if we are - if (ReconnectTimer != null) - { - ReconnectTimer.Stop(); - // ReconnectTimer = null; - } + /// + /// Disconnect method + /// + public void Disconnect() + { + ConnectEnabled = false; + // Stop trying reconnects, if we are + if (ReconnectTimer != null) + { + ReconnectTimer.Stop(); + // ReconnectTimer = null; + } KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); } @@ -360,7 +364,7 @@ namespace PepperDash.Core } catch (Exception ex) { - this.LogException(ex,"Exception in Kill Client"); + this.LogException(ex, "Exception in Kill Client"); } } @@ -368,7 +372,7 @@ namespace PepperDash.Core /// Kills the stream /// void KillStream() - { + { try { if (TheStream != null) @@ -384,59 +388,59 @@ namespace PepperDash.Core { this.LogException(ex, "Exception in Kill Stream:{0}"); } - } + } - /// - /// Handles the keyboard interactive authentication, should it be required. - /// - void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) - { - foreach (AuthenticationPrompt prompt in e.Prompts) - if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) - prompt.Response = Password; - } - - /// - /// Handler for data receive on ShellStream. Passes data across to queue for line parsing. - /// - void Stream_DataReceived(object sender, ShellDataEventArgs e) - { + /// + /// Handles the keyboard interactive authentication, should it be required. + /// + void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) + { + foreach (AuthenticationPrompt prompt in e.Prompts) + if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) + prompt.Response = Password; + } + + /// + /// Handler for data receive on ShellStream. Passes data across to queue for line parsing. + /// + void Stream_DataReceived(object sender, ShellDataEventArgs e) + { if (((ShellStream)sender).Length <= 0L) { return; } - var response = ((ShellStream)sender).Read(); - - var bytesHandler = BytesReceived; - - if (bytesHandler != null) - { + var response = ((ShellStream)sender).Read(); + + var bytesHandler = BytesReceived; + + if (bytesHandler != null) + { var bytes = Encoding.UTF8.GetBytes(response); - if (StreamDebugging.RxStreamDebuggingIsEnabled) - { - this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); - } + if (StreamDebugging.RxStreamDebuggingIsEnabled) + { + this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); + } bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - } - - var textHandler = TextReceived; - if (textHandler != null) - { + } + + var textHandler = TextReceived; + if (textHandler != null) + { if (StreamDebugging.RxStreamDebuggingIsEnabled) this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response)); textHandler(this, new GenericCommMethodReceiveTextArgs(response)); } - - } + + } - /// - /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange - /// event - /// - void Client_ErrorOccurred(object sender, ExceptionEventArgs e) - { + /// + /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange + /// event + /// + void Client_ErrorOccurred(object sender, ExceptionEventArgs e) + { CrestronInvoke.BeginInvoke(o => { if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException) @@ -465,61 +469,54 @@ namespace PepperDash.Core /// void OnConnectionChange() { - if (ConnectionChange != null) - ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); + ConnectionChange?.Invoke(this, new GenericSocketStatusChageEventArgs(this)); } #region IBasicCommunication Members - /// - /// Sends text to the server - /// - /// - /// - /// SendText method - /// - public void SendText(string text) - { - try - { - if (Client != null && TheStream != null && IsConnected) - { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - this.LogInformation( - "Sending {length} characters of text: '{text}'", - text.Length, - ComTextHelper.GetDebugText(text)); - - TheStream.Write(text); - TheStream.Flush(); - } - else - { - this.LogDebug("Client is null or disconnected. Cannot Send Text"); - } - } - catch (ObjectDisposedException) + /// + /// Sends text to the server + /// + /// The text to send + public void SendText(string text) + { + try { - this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); + if (Client != null && TheStream != null && IsConnected) + { + if (StreamDebugging.TxStreamDebuggingIsEnabled) + this.LogInformation( + "Sending {length} characters of text: '{text}'", + text.Length, + ComTextHelper.GetDebugText(text)); + + TheStream.Write(text); + TheStream.Flush(); + } + else + { + this.LogDebug("Client is null or disconnected. Cannot Send Text"); + } + } + catch (ObjectDisposedException) + { + this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); ReconnectTimer.Reset(); - } - catch (Exception ex) - { + } + catch (Exception ex) + { this.LogException(ex, "Exception sending text: '{message}'", text); - } - } + } + } /// /// Sends Bytes to the server /// - /// - /// - /// SendBytes method - /// - public void SendBytes(byte[] bytes) - { + /// The bytes to send + public void SendBytes(byte[] bytes) + { try { if (Client != null && TheStream != null && IsConnected) @@ -545,38 +542,38 @@ namespace PepperDash.Core catch (Exception ex) { this.LogException(ex, "Exception sending {message}", ComTextHelper.GetEscapedText(bytes)); - } - } - #endregion + } + } + #endregion -} + } -//***************************************************************************************************** -//***************************************************************************************************** -/// -/// Represents a SshConnectionChangeEventArgs -/// -public class SshConnectionChangeEventArgs : EventArgs - { + //***************************************************************************************************** + //***************************************************************************************************** + /// + /// Represents a SshConnectionChangeEventArgs + /// + public class SshConnectionChangeEventArgs : EventArgs + { /// /// Connection State /// public bool IsConnected { get; private set; } - /// - /// Gets or sets the UIsConnected - /// - public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } } + /// + /// Gets or sets the UIsConnected + /// + public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } } - /// - /// Gets or sets the Client - /// - public GenericSshClient Client { get; private set; } + /// + /// Gets or sets the Client + /// + public GenericSshClient Client { get; private set; } - /// - /// Gets or sets the Status - /// - public ushort Status { get { return Client.UStatus; } } + /// + /// Gets or sets the Status + /// + public ushort Status { get { return Client.UStatus; } } /// /// S+ Constructor diff --git a/src/PepperDash.Essentials.Core/Devices/ConfigSnippetAttribute.cs b/src/PepperDash.Essentials.Core/Devices/ConfigSnippetAttribute.cs new file mode 100644 index 00000000..a1fdef35 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/ConfigSnippetAttribute.cs @@ -0,0 +1,27 @@ +using System; + +namespace PepperDash.Essentials.Core +{ + /// + /// Represents a ConfigSnippetAttribute + /// + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] + public class ConfigSnippetAttribute : Attribute + { + /// + /// Represents a configuration snippet for the device. + /// + /// + public ConfigSnippetAttribute(string 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; } + } + +} \ 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..abe51665 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/DescriptionAttribute.cs @@ -0,0 +1,26 @@ +using System; + +namespace PepperDash.Essentials.Core +{ + /// + /// Represents a description attribute for a device. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] + public class DescriptionAttribute : Attribute + { + /// + /// Represents a description attribute for a device. + /// + /// + public DescriptionAttribute(string description) + { + Description = description; + } + + /// + /// Gets the description for the device. + /// + public string Description { get; } + } + +} \ 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/FIND HOMES Interfaces.cs b/src/PepperDash.Essentials.Core/Devices/FIND HOMES Interfaces.cs deleted file mode 100644 index 44b36134..00000000 --- a/src/PepperDash.Essentials.Core/Devices/FIND HOMES Interfaces.cs +++ /dev/null @@ -1,35 +0,0 @@ -using PepperDash.Core; - - -namespace PepperDash.Essentials.Core -{ - /// - /// Defines the contract for IOnline - /// - public interface IOnline - { - BoolFeedback IsOnline { get; } - } - - /// - /// Defines the contract for IAttachVideoStatus - /// - public interface IAttachVideoStatus : IKeyed - { - // Extension methods will depend on this - } - - /// - /// For display classes that can provide usage data - /// - public interface IDisplayUsage - { - IntFeedback LampHours { get; } - } - - public interface IMakeModel : IKeyed - { - string DeviceMake { get; } - string DeviceModel { get; } - } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/IAttachVideoStatus.cs b/src/PepperDash.Essentials.Core/Devices/IAttachVideoStatus.cs new file mode 100644 index 00000000..4b9b36ca --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/IAttachVideoStatus.cs @@ -0,0 +1,13 @@ +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for IAttachVideoStatus + /// + public interface IAttachVideoStatus : IKeyed + { + // Extension methods will depend on this + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/IDisplayUsage.cs b/src/PepperDash.Essentials.Core/Devices/IDisplayUsage.cs new file mode 100644 index 00000000..0d74d819 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/IDisplayUsage.cs @@ -0,0 +1,10 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// For display classes that can provide usage data + /// + public interface IDisplayUsage + { + IntFeedback LampHours { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/IMakeModel.cs b/src/PepperDash.Essentials.Core/Devices/IMakeModel.cs new file mode 100644 index 00000000..494c9af3 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/IMakeModel.cs @@ -0,0 +1,22 @@ +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + + /// + /// Defines the contract for device make and model information + /// + public interface IMakeModel : IKeyed + { + /// + /// Gets the make of the device + /// + string DeviceMake { get; } + + /// + /// Gets the model of the device + /// + string DeviceModel { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/IOnline.cs b/src/PepperDash.Essentials.Core/Devices/IOnline.cs new file mode 100644 index 00000000..8c86a480 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/IOnline.cs @@ -0,0 +1,13 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for IOnline + /// + public interface IOnline + { + /// + /// Gets a value indicating whether the device is online. + /// + BoolFeedback IsOnline { get; } + } +} \ 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..8549fd38 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/ProcessorExtensionDeviceFactory.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core +{ + /// + /// Represents a factory for creating processor extension devices. + /// + /// The type of the processor extension device. + [Obsolete("will be removed in a future version")] + 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) + { + string description = typeof(T).GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] descriptionAttribute && descriptionAttribute.Length > 0 + ? descriptionAttribute[0].Description + : "No description available"; + + 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/Devices/SourceListItem.cs b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs index d4fac061..d171b3d9 100644 --- a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using PepperDash.Core; @@ -248,6 +249,7 @@ namespace PepperDash.Essentials.Core /// /// Defines the valid destination types for SourceListItems in a room /// + [Obsolete] public enum eSourceListItemDestinationTypes { /// diff --git a/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs b/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs index 5c8d4dec..b723f6c9 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 ?? "Unknown"); + + PluginLoader.AddLoadedAssembly(assembly.GetName()?.Name ?? "Unknown", 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) + { + string description = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] descriptionAttribute && descriptionAttribute.Length > 0 + ? descriptionAttribute[0].Description + : "No description available"; + + 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)) continue; + 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/Feedbacks/BoolFeedback.cs b/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedback.cs index bc4cfde5..eb3abbb4 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedback.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedback.cs @@ -42,6 +42,7 @@ namespace PepperDash.Essentials.Core /// it will NOT reflect an actual value from a device until has been called /// /// Delegate to invoke when this feedback needs to be updated + [Obsolete("use constructor with Key parameter. This constructor will be removed in a future version")] public BoolFeedback(Func valueFunc) : this(null, valueFunc) { diff --git a/src/PepperDash.Essentials.Core/Feedbacks/FeedbackBase.cs b/src/PepperDash.Essentials.Core/Feedbacks/FeedbackBase.cs index d4caa7e5..723d22a6 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/FeedbackBase.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/FeedbackBase.cs @@ -9,45 +9,54 @@ using PepperDash.Core; namespace PepperDash.Essentials.Core { - public abstract class Feedback : IKeyed - { - public event EventHandler OutputChange; + /// + /// Base class for all feedback types + /// + public abstract class Feedback : IKeyed + { + /// + /// Occurs when the output value changes + /// + public event EventHandler OutputChange; /// /// Gets or sets the Key /// public string Key { get; private set; } - /// - /// Gets or sets the BoolValue - /// - /// - public virtual bool BoolValue { get { return false; } } - /// - /// Gets or sets the IntValue - /// - public virtual int IntValue { get { return 0; } } - /// - /// Gets or sets the StringValue - /// - public virtual string StringValue { get { return ""; } } + /// + /// Gets or sets the BoolValue + /// + /// + public virtual bool BoolValue { get { return false; } } + /// + /// Gets or sets the IntValue + /// + public virtual int IntValue { get { return 0; } } + /// + /// Gets or sets the StringValue + /// + public virtual string StringValue { get { return ""; } } /// /// Gets or sets the SerialValue /// public virtual string SerialValue { get { return ""; } } - /// - /// Gets or sets the InTestMode - /// - public bool InTestMode { get; protected set; } + /// + /// Gets or sets the InTestMode + /// + public bool InTestMode { get; protected set; } - /// - /// Base Constructor - empty - /// - protected Feedback() - { - } + /// + /// Base Constructor - empty + /// + [Obsolete("use constructor with Key parameter. This constructor will be removed in a future version")] + protected Feedback() : this(null) { } + /// + /// Constructor with Key parameter + /// + /// The key for the feedback protected Feedback(string key) { if (key == null) @@ -58,27 +67,27 @@ namespace PepperDash.Essentials.Core - /// - /// ClearTestValue method - /// - public void ClearTestValue() - { - InTestMode = false; - FireUpdate(); - } + /// + /// ClearTestValue method + /// + public void ClearTestValue() + { + InTestMode = false; + FireUpdate(); + } - /// - /// Fires an update synchronously - /// - public abstract void FireUpdate(); + /// + /// Fires an update synchronously + /// + public abstract void FireUpdate(); - /// - /// Fires the update asynchronously within a CrestronInvoke - /// - public void InvokeFireUpdate() - { - CrestronInvoke.BeginInvoke(o => FireUpdate()); - } + /// + /// Fires the update asynchronously within a CrestronInvoke + /// + public void InvokeFireUpdate() + { + CrestronInvoke.BeginInvoke(o => FireUpdate()); + } /// /// Helper method that fires event. Use this intstead of calling OutputChange @@ -103,5 +112,5 @@ namespace PepperDash.Essentials.Core { if (OutputChange != null) OutputChange(this, new FeedbackEventArgs(value)); } - } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Feedbacks/IntFeedback.cs b/src/PepperDash.Essentials.Core/Feedbacks/IntFeedback.cs index 06e35850..ee23e310 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/IntFeedback.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/IntFeedback.cs @@ -43,6 +43,7 @@ namespace PepperDash.Essentials.Core /// it will NOT reflect an actual value from a device until has been called /// /// Delegate to invoke when this feedback needs to be updated + [Obsolete("use constructor with Key parameter. This constructor will be removed in a future version")] public IntFeedback(Func valueFunc) : this(null, valueFunc) { diff --git a/src/PepperDash.Essentials.Core/Feedbacks/SerialFeedback.cs b/src/PepperDash.Essentials.Core/Feedbacks/SerialFeedback.cs index 0daec8c1..b26e23e6 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/SerialFeedback.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/SerialFeedback.cs @@ -13,7 +13,7 @@ namespace PepperDash.Essentials.Core /// public class SerialFeedback : Feedback { - public override string SerialValue { get { return _SerialValue; } } + public override string SerialValue { get { return _SerialValue; } } string _SerialValue; //public override eCueType Type { get { return eCueType.Serial; } } @@ -25,6 +25,7 @@ namespace PepperDash.Essentials.Core List LinkedInputSigs = new List(); + [Obsolete("use constructor with Key parameter. This constructor will be removed in a future version")] public SerialFeedback() { } diff --git a/src/PepperDash.Essentials.Core/Feedbacks/StringFeedback.cs b/src/PepperDash.Essentials.Core/Feedbacks/StringFeedback.cs index fe6d6884..f7d594c4 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/StringFeedback.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/StringFeedback.cs @@ -38,6 +38,7 @@ namespace PepperDash.Essentials.Core /// it will NOT reflect an actual value from a device until has been called /// /// Delegate to invoke when this feedback needs to be updated + [Obsolete("use constructor with Key parameter. This constructor will be removed in a future version")] public StringFeedback(Func valueFunc) : this(null, valueFunc) { 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..a1dd9413 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,84 +476,62 @@ 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 a a custom plugin via the legacy method + /// Loads device factories from the specified plugin device factory and registers them for use. /// - /// - /// - static void LoadCustomLegacyPlugin(Type type, MethodInfo loadPlugin, LoadedAssembly loadedAssembly) + /// 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) { - Debug.LogMessage(LogEventLevel.Verbose, "LoadPlugin method found in {0}", type.Name); - - var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static); - - var minimumVersion = fields.FirstOrDefault(p => p.Name.Equals("MinimumEssentialsFrameworkVersion")); - if (minimumVersion != null) + foreach (var typeName in deviceFactory.TypeNames) { - Debug.LogMessage(LogEventLevel.Verbose, "MinimumEssentialsFrameworkVersion found"); + string description = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] descriptionAttribute && descriptionAttribute.Length > 0 + ? descriptionAttribute[0].Description + : "No description available"; - var minimumVersionString = minimumVersion.GetValue(null) as string; - - if (!string.IsNullOrEmpty(minimumVersionString)) - { - var passed = Global.IsRunningMinimumVersionOrHigher(minimumVersionString); - - if (!passed) - { - Debug.LogMessage(LogEventLevel.Information, "Plugin indicates minimum Essentials version {0}. Dependency check failed. Skipping Plugin", minimumVersionString); - return; - } - else - { - Debug.LogMessage(LogEventLevel.Information, "Passed plugin passed dependency check (required version {0})", minimumVersionString); - } - } - else - { - Debug.LogMessage(LogEventLevel.Information, "MinimumEssentialsFrameworkVersion found but not set. Loading plugin, but your mileage may vary."); - } + DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); } - else - { - Debug.LogMessage(LogEventLevel.Information, "MinimumEssentialsFrameworkVersion not found. Loading plugin, but your mileage may vary."); - } - - Debug.LogMessage(LogEventLevel.Information, "Loading legacy plugin: {0}", loadedAssembly.Name); - loadPlugin.Invoke(null, null); - } + /// /// LoadPlugins method /// @@ -567,13 +567,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.Core/Room/Interfaces.cs b/src/PepperDash.Essentials.Core/Room/Interfaces.cs index 00fa0b7c..bffb80a3 100644 --- a/src/PepperDash.Essentials.Core/Room/Interfaces.cs +++ b/src/PepperDash.Essentials.Core/Room/Interfaces.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; using PepperDash.Core; @@ -28,6 +25,7 @@ namespace PepperDash.Essentials.Core /// /// For rooms with multiple displays /// + [Obsolete("Will be removed in a future version")] public interface IHasMultipleDisplays { Dictionary Displays { get; } @@ -40,7 +38,7 @@ namespace PepperDash.Essentials.Core { void RunRouteAction(string routeKey, string sourceListKey); - void RunRouteAction(string routeKey, string sourceListKey, Action successCallback); + void RunRouteAction(string routeKey, string sourceListKey, Action successCallback); } /// @@ -50,7 +48,7 @@ namespace PepperDash.Essentials.Core { void RunDirectRoute(string sourceKey, string destinationKey, eRoutingSignalType type = eRoutingSignalType.AudioVideo); } - + /// /// Describes a room with matrix routing /// @@ -139,7 +137,7 @@ namespace PepperDash.Essentials.Core bool HasEnvironmentalControlDevices { get; } } - public interface IRoomOccupancy:IKeyed + public interface IRoomOccupancy : IKeyed { IOccupancyStatusProvider RoomOccupancy { get; } bool OccupancyStatusProviderIsRemote { get; } diff --git a/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs b/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs index 7656b277..178c53a7 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,25 @@ 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); + string description = (deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] descriptionAttribute && descriptionAttribute.Length > 0) + ? descriptionAttribute[0].Description + : "No description available"; + + 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..393b3385 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) + { + string description = (deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] descriptionAttribute && descriptionAttribute.Length > 0) + ? descriptionAttribute[0].Description + : "No description available"; // Default value if no DescriptionAttribute is found + + 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..e60b3fa4 100644 --- a/src/PepperDash.Essentials/Factory/DeviceFactory.cs +++ b/src/PepperDash.Essentials/Factory/DeviceFactory.cs @@ -1,18 +1,11 @@  using System; -using System.Collections.Generic; using System.Linq; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronIO; -using Crestron.SimplSharpPro; using System.Reflection; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using PepperDash.Core; using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; namespace PepperDash.Essentials { @@ -39,14 +32,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) + { + string description = (deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] descriptionAttribute && descriptionAttribute.Length > 0) + ? descriptionAttribute[0].Description + : "No description available"; + + Core.DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + } + } } }