feat: modify factory loading

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

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

View File

@@ -8,500 +8,507 @@ using System.Linq;
using System.Text.RegularExpressions;
namespace PepperDash.Essentials.Core
namespace PepperDash.Essentials.Core;
public static class DeviceManager
{
public static class DeviceManager
public static event EventHandler<EventArgs> AllDevicesActivated;
public static event EventHandler<EventArgs> AllDevicesRegistered;
public static event EventHandler<EventArgs> AllDevicesInitialized;
private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection();
//public static List<Device> Devices { get { return _Devices; } }
//static List<Device> _Devices = new List<Device>();
private static readonly Dictionary<string, IKeyed> Devices = new Dictionary<string, IKeyed>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Returns a copy of all the devices in a list
/// </summary>
public static List<IKeyed> AllDevices => [.. Devices.Values];
public static bool AddDeviceEnabled;
/// <summary>
/// Initializes the control system by enabling device management and registering console commands.
/// </summary>
/// <remarks>This method sets up the control system for device management by enabling the addition of
/// devices and registering a series of console commands for interacting with devices. These commands allow
/// operators to list device statuses, feedbacks, and managed devices, as well as perform actions such as
/// simulating communication, debugging device streams, and accessing device properties and methods.</remarks>
/// <param name="cs">The <see cref="CrestronControlSystem"/> instance representing the control system to initialize.</param>
public static void Initialize(CrestronControlSystem cs)
{
public static event EventHandler<EventArgs> AllDevicesActivated;
public static event EventHandler<EventArgs> AllDevicesRegistered;
public static event EventHandler<EventArgs> AllDevicesInitialized;
AddDeviceEnabled = true;
private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection();
private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true);
CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ListDevices, "devlist", "Lists current managed devices",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive",
"Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator);
//public static List<Device> Devices { get { return _Devices; } }
//static List<Device> _Devices = new List<Device>();
CrestronConsole.AddNewConsoleCommand(SetDeviceStreamDebugging, "setdevicestreamdebug", "set comm debug [deviceKey] [off/rx/tx/both] ([minutes])", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => DisableAllDeviceStreamDebugging(), "disableallstreamdebug", "disables stream debugging on all devices", ConsoleAccessLevelEnum.AccessOperator);
}
private static readonly Dictionary<string, IKeyed> Devices = new Dictionary<string, IKeyed>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Returns a copy of all the devices in a list
/// </summary>
public static List<IKeyed> AllDevices { get { return new List<IKeyed>(Devices.Values); } }
public static bool AddDeviceEnabled;
public static void Initialize(CrestronControlSystem cs)
/// <summary>
/// Calls activate steps on all Device class items
/// </summary>
public static void ActivateAll()
{
try
{
AddDeviceEnabled = true;
CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ListDevices, "devlist", "Lists current managed devices",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive",
"Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator);
OnAllDevicesRegistered();
CrestronConsole.AddNewConsoleCommand(SetDeviceStreamDebugging, "setdevicestreamdebug", "set comm debug [deviceKey] [off/rx/tx/both] ([minutes])", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => DisableAllDeviceStreamDebugging(), "disableallstreamdebug", "disables stream debugging on all devices", ConsoleAccessLevelEnum.AccessOperator);
}
/// <summary>
/// Calls activate steps on all Device class items
/// </summary>
public static void ActivateAll()
{
try
{
OnAllDevicesRegistered();
DeviceCriticalSection.Enter();
AddDeviceEnabled = false;
// PreActivate all devices
Debug.LogMessage(LogEventLevel.Information, "****PreActivation starting...****");
foreach (var d in Devices.Values)
{
try
{
if (d is Device)
(d as Device).PreActivate();
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PreActivation failure: {0}", e.Message, d.Key);
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
}
}
Debug.LogMessage(LogEventLevel.Information, "****PreActivation complete****");
Debug.LogMessage(LogEventLevel.Information, "****Activation starting...****");
// Activate all devices
foreach (var d in Devices.Values)
{
try
{
if (d is Device)
(d as Device).Activate();
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} Activation failure: {0}", e.Message, d.Key);
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
}
}
Debug.LogMessage(LogEventLevel.Information, "****Activation complete****");
Debug.LogMessage(LogEventLevel.Information, "****PostActivation starting...****");
// PostActivate all devices
foreach (var d in Devices.Values)
{
try
{
if (d is Device)
(d as Device).PostActivate();
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key);
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
}
}
Debug.LogMessage(LogEventLevel.Information, "****PostActivation complete****");
OnAllDevicesActivated();
}
finally
{
DeviceCriticalSection.Leave();
}
}
private static void DeviceManager_Initialized(object sender, EventArgs e)
{
var allInitialized = Devices.Values.OfType<EssentialsDevice>().All(d => d.IsInitialized);
if (allInitialized)
{
Debug.LogMessage(LogEventLevel.Information, "****All Devices Initalized****");
OnAllDevicesInitialized();
}
}
private static void OnAllDevicesActivated()
{
var handler = AllDevicesActivated;
if (handler != null)
{
handler(null, new EventArgs());
}
}
private static void OnAllDevicesRegistered()
{
var handler = AllDevicesRegistered;
if (handler != null)
{
handler(null, new EventArgs());
}
}
private static void OnAllDevicesInitialized()
{
var handler = AllDevicesInitialized;
if (handler != null)
{
handler(null, new EventArgs());
}
}
/// <summary>
/// Calls activate on all Device class items
/// </summary>
public static void DeactivateAll()
{
try
{
DeviceCriticalSection.Enter();
foreach (var d in Devices.Values.OfType<Device>())
{
d.Deactivate();
}
}
finally
{
DeviceCriticalSection.Leave();
}
}
//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);
var sorted = Devices.Values.ToList();
sorted.Sort((a, b) => a.Key.CompareTo(b.Key));
foreach (var d in sorted)
{
var name = d is IKeyName ? (d as IKeyName).Name : "---";
Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name);
}
}
private static void ListDeviceFeedbacks(string devKey)
{
var dev = GetDeviceForKey(devKey);
if (dev == null)
{
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey);
return;
}
if (!(dev is IHasFeedback statusDev))
{
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey);
return;
}
statusDev.DumpFeedbacksToConsole(true);
}
//static void ListDeviceCommands(string devKey)
//{
// var dev = GetDeviceForKey(devKey);
// if (dev == null)
// {
// Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey);
// return;
// }
// Debug.LogMessage(LogEventLevel.Information, "This needs to be reworked. Stay tuned.", devKey);
//}
private static void ListDeviceCommStatuses(string input)
{
foreach (var dev in Devices.Values.OfType<ICommunicationMonitor>())
{
CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}\r\n");
}
}
//static void DoDeviceCommand(string command)
//{
// Debug.LogMessage(LogEventLevel.Information, "Not yet implemented. Stay tuned");
//}
public static void AddDevice(IKeyed newDev)
{
try
{
if (!DeviceCriticalSection.TryEnter())
{
Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again");
return;
}
// Check for device with same key
//var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase));
////// If it exists, remove or warn??
//if (existingDevice != null)
if (!AddDeviceEnabled)
{
Debug.LogMessage(LogEventLevel.Information, "All devices have been activated. Adding new devices is not allowed.");
return;
}
if (Devices.ContainsKey(newDev.Key))
{
Debug.LogMessage(LogEventLevel.Information, newDev, "WARNING: A device with this key already exists. Not added to manager");
return;
}
Devices.Add(newDev.Key, newDev);
//if (!(_Devices.Contains(newDev)))
// _Devices.Add(newDev);
if (newDev is EssentialsDevice essentialsDev)
essentialsDev.Initialized += DeviceManager_Initialized;
}
finally
{
DeviceCriticalSection.Leave();
}
}
public static void AddDevice(IEnumerable<IKeyed> devicesToAdd)
{
try
{
if (!DeviceCriticalSection.TryEnter())
{
Debug.LogMessage(LogEventLevel.Information,
"Currently unable to add devices to Device Manager. Please try again");
return;
}
if (!AddDeviceEnabled)
{
Debug.LogMessage(LogEventLevel.Information,
"All devices have been activated. Adding new devices is not allowed.");
return;
}
foreach (var dev in devicesToAdd)
{
try
{
Devices.Add(dev.Key, dev);
}
catch (ArgumentException ex)
{
Debug.LogMessage(LogEventLevel.Information, "Error adding device with key {0} to Device Manager: {1}\r\nStack Trace: {2}",
dev.Key, ex.Message, ex.StackTrace);
}
}
}
finally
{
DeviceCriticalSection.Leave();
}
}
public static void RemoveDevice(IKeyed newDev)
{
try
{
DeviceCriticalSection.Enter();
if (newDev == null)
return;
if (Devices.ContainsKey(newDev.Key))
Devices.Remove(newDev.Key);
//if (_Devices.Contains(newDev))
// _Devices.Remove(newDev);
else
Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key);
}
finally
{
DeviceCriticalSection.Leave();
}
}
public static IEnumerable<string> GetDeviceKeys()
{
//return _Devices.Select(d => d.Key).ToList();
return Devices.Keys;
}
public static IEnumerable<IKeyed> GetDevices()
{
//return _Devices.Select(d => d.Key).ToList();
return Devices.Values;
}
public static IKeyed GetDeviceForKey(string key)
{
//return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
if (key != null && Devices.ContainsKey(key))
return Devices[key];
return null;
}
/// <summary>
/// Console handler that simulates com port data receive
/// </summary>
/// <param name="s"></param>
public static void SimulateComReceiveOnDevice(string s)
{
// devcomsim:1 xyzabc
var match = Regex.Match(s, @"(\S*)\s*(.*)");
if (match.Groups.Count < 3)
{
CrestronConsole.ConsoleCommandResponse(" Format: devsimreceive:P <device key> <string to send>");
return;
}
//Debug.LogMessage(LogEventLevel.Verbose, "**** {0} - {1} ****", match.Groups[1].Value, match.Groups[2].Value);
if (!(GetDeviceForKey(match.Groups[1].Value) is ComPortController com))
{
CrestronConsole.ConsoleCommandResponse("'{0}' is not a comm port device", match.Groups[1].Value);
return;
}
com.SimulateReceive(match.Groups[2].Value);
}
/// <summary>
/// Prints a list of routing inputs and outputs by device key.
/// </summary>
/// <param name="s">Device key from which to report data</param>
public static void GetRoutingPorts(string s)
{
var device = GetDeviceForKey(s);
if (device == null) return;
var inputPorts = ((device as IRoutingInputs) != null) ? (device as IRoutingInputs).InputPorts : null;
var outputPorts = ((device as IRoutingOutputs) != null) ? (device as IRoutingOutputs).OutputPorts : null;
if (inputPorts != null)
{
CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Input Ports:{2}", s, inputPorts.Count, CrestronEnvironment.NewLine);
foreach (var routingInputPort in inputPorts)
{
CrestronConsole.ConsoleCommandResponse("{0}{1}", routingInputPort.Key, CrestronEnvironment.NewLine);
}
}
if (outputPorts == null) return;
CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Output Ports:{2}", s, outputPorts.Count, CrestronEnvironment.NewLine);
foreach (var routingOutputPort in outputPorts)
{
CrestronConsole.ConsoleCommandResponse("{0}{1}", routingOutputPort.Key, CrestronEnvironment.NewLine);
}
}
/// <summary>
/// Attempts to set the debug level of a device
/// </summary>
/// <param name="s"></param>
public static void SetDeviceStreamDebugging(string s)
{
if (String.IsNullOrEmpty(s) || s.Contains("?"))
{
CrestronConsole.ConsoleCommandResponse(
@"SETDEVICESTREAMDEBUG [{deviceKey}] [OFF |TX | RX | BOTH] [timeOutInMinutes]
{deviceKey} [OFF | TX | RX | BOTH] - Device to set stream debugging on, and which setting to use
timeOutInMinutes - Set timeout for stream debugging. Default is 30 minutes");
return;
}
var args = s.Split(' ');
var deviceKey = args[0];
var setting = args[1];
var timeout = String.Empty;
if (args.Length >= 3)
{
timeout = args[2];
}
if (!(GetDeviceForKey(deviceKey) is IStreamDebugging device))
{
CrestronConsole.ConsoleCommandResponse("Unable to get device with key: {0}", deviceKey);
return;
}
eStreamDebuggingSetting debugSetting;
try
{
debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), setting, true);
}
catch
{
CrestronConsole.ConsoleCommandResponse("Unable to convert setting value. Please use off/rx/tx/both");
return;
}
if (!string.IsNullOrEmpty(timeout))
DeviceCriticalSection.Enter();
AddDeviceEnabled = false;
// PreActivate all devices
Debug.LogMessage(LogEventLevel.Information, "****PreActivation starting...****");
foreach (var d in Devices.Values)
{
try
{
var min = Convert.ToUInt32(timeout);
device.StreamDebugging.SetDebuggingWithSpecificTimeout(debugSetting, min);
CrestronConsole.ConsoleCommandResponse("Device: '{0}' debug level set to {1} for {2} minutes", deviceKey, debugSetting, min);
{
if (d is Device)
(d as Device).PreActivate();
}
catch (Exception e)
{
CrestronConsole.ConsoleCommandResponse("Unable to convert minutes or settings value. Please use an integer value for minutes. Error: {0}", e);
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PreActivation failure: {0}", e.Message, d.Key);
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
}
}
else
Debug.LogMessage(LogEventLevel.Information, "****PreActivation complete****");
Debug.LogMessage(LogEventLevel.Information, "****Activation starting...****");
// Activate all devices
foreach (var d in Devices.Values)
{
device.StreamDebugging.SetDebuggingWithDefaultTimeout(debugSetting);
CrestronConsole.ConsoleCommandResponse("Device: '{0}' debug level set to {1} for default time (30 minutes)", deviceKey, debugSetting);
try
{
if (d is Device)
(d as Device).Activate();
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} Activation failure: {0}", e.Message, d.Key);
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
}
}
Debug.LogMessage(LogEventLevel.Information, "****Activation complete****");
Debug.LogMessage(LogEventLevel.Information, "****PostActivation starting...****");
// PostActivate all devices
foreach (var d in Devices.Values)
{
try
{
if (d is Device)
(d as Device).PostActivate();
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key);
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
}
}
Debug.LogMessage(LogEventLevel.Information, "****PostActivation complete****");
OnAllDevicesActivated();
}
finally
{
DeviceCriticalSection.Leave();
}
}
private static void DeviceManager_Initialized(object sender, EventArgs e)
{
var allInitialized = Devices.Values.OfType<EssentialsDevice>().All(d => d.IsInitialized);
if (allInitialized)
{
Debug.LogMessage(LogEventLevel.Information, "****All Devices Initalized****");
OnAllDevicesInitialized();
}
}
private static void OnAllDevicesActivated()
{
var handler = AllDevicesActivated;
if (handler != null)
{
handler(null, new EventArgs());
}
}
private static void OnAllDevicesRegistered()
{
var handler = AllDevicesRegistered;
if (handler != null)
{
handler(null, new EventArgs());
}
}
private static void OnAllDevicesInitialized()
{
var handler = AllDevicesInitialized;
if (handler != null)
{
handler(null, new EventArgs());
}
}
/// <summary>
/// Calls activate on all Device class items
/// </summary>
public static void DeactivateAll()
{
try
{
DeviceCriticalSection.Enter();
foreach (var d in Devices.Values.OfType<Device>())
{
d.Deactivate();
}
}
/// <summary>
/// Sets stream debugging settings to off for all devices
/// </summary>
public static void DisableAllDeviceStreamDebugging()
finally
{
foreach (var device in AllDevices)
DeviceCriticalSection.Leave();
}
}
//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);
var sorted = Devices.Values.ToList();
sorted.Sort((a, b) => a.Key.CompareTo(b.Key));
foreach (var d in sorted)
{
var name = d is IKeyName ? (d as IKeyName).Name : "---";
Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name);
}
}
private static void ListDeviceFeedbacks(string devKey)
{
var dev = GetDeviceForKey(devKey);
if (dev == null)
{
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey);
return;
}
if (!(dev is IHasFeedback statusDev))
{
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey);
return;
}
statusDev.DumpFeedbacksToConsole(true);
}
//static void ListDeviceCommands(string devKey)
//{
// var dev = GetDeviceForKey(devKey);
// if (dev == null)
// {
// Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey);
// return;
// }
// Debug.LogMessage(LogEventLevel.Information, "This needs to be reworked. Stay tuned.", devKey);
//}
private static void ListDeviceCommStatuses(string input)
{
foreach (var dev in Devices.Values.OfType<ICommunicationMonitor>())
{
CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}\r\n");
}
}
//static void DoDeviceCommand(string command)
//{
// Debug.LogMessage(LogEventLevel.Information, "Not yet implemented. Stay tuned");
//}
public static void AddDevice(IKeyed newDev)
{
try
{
if (!DeviceCriticalSection.TryEnter())
{
if (device is IStreamDebugging streamDevice)
Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again");
return;
}
// Check for device with same key
//var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase));
////// If it exists, remove or warn??
//if (existingDevice != null)
if (!AddDeviceEnabled)
{
Debug.LogMessage(LogEventLevel.Information, "All devices have been activated. Adding new devices is not allowed.");
return;
}
if (Devices.ContainsKey(newDev.Key))
{
Debug.LogMessage(LogEventLevel.Information, newDev, "WARNING: A device with this key already exists. Not added to manager");
return;
}
Devices.Add(newDev.Key, newDev);
//if (!(_Devices.Contains(newDev)))
// _Devices.Add(newDev);
if (newDev is EssentialsDevice essentialsDev)
essentialsDev.Initialized += DeviceManager_Initialized;
}
finally
{
DeviceCriticalSection.Leave();
}
}
public static void AddDevice(IEnumerable<IKeyed> devicesToAdd)
{
try
{
if (!DeviceCriticalSection.TryEnter())
{
Debug.LogMessage(LogEventLevel.Information,
"Currently unable to add devices to Device Manager. Please try again");
return;
}
if (!AddDeviceEnabled)
{
Debug.LogMessage(LogEventLevel.Information,
"All devices have been activated. Adding new devices is not allowed.");
return;
}
foreach (var dev in devicesToAdd)
{
try
{
streamDevice.StreamDebugging.SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting.Off);
Devices.Add(dev.Key, dev);
}
catch (ArgumentException ex)
{
Debug.LogMessage(LogEventLevel.Information, "Error adding device with key {0} to Device Manager: {1}\r\nStack Trace: {2}",
dev.Key, ex.Message, ex.StackTrace);
}
}
}
finally
{
DeviceCriticalSection.Leave();
}
}
public static void RemoveDevice(IKeyed newDev)
{
try
{
DeviceCriticalSection.Enter();
if (newDev == null)
return;
if (Devices.ContainsKey(newDev.Key))
Devices.Remove(newDev.Key);
//if (_Devices.Contains(newDev))
// _Devices.Remove(newDev);
else
Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key);
}
finally
{
DeviceCriticalSection.Leave();
}
}
public static IEnumerable<string> GetDeviceKeys()
{
//return _Devices.Select(d => d.Key).ToList();
return Devices.Keys;
}
public static IEnumerable<IKeyed> GetDevices()
{
//return _Devices.Select(d => d.Key).ToList();
return Devices.Values;
}
public static IKeyed GetDeviceForKey(string key)
{
//return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
if (key != null && Devices.ContainsKey(key))
return Devices[key];
return null;
}
/// <summary>
/// Console handler that simulates com port data receive
/// </summary>
/// <param name="s"></param>
public static void SimulateComReceiveOnDevice(string s)
{
// devcomsim:1 xyzabc
var match = Regex.Match(s, @"(\S*)\s*(.*)");
if (match.Groups.Count < 3)
{
CrestronConsole.ConsoleCommandResponse(" Format: devsimreceive:P <device key> <string to send>");
return;
}
//Debug.LogMessage(LogEventLevel.Verbose, "**** {0} - {1} ****", match.Groups[1].Value, match.Groups[2].Value);
if (!(GetDeviceForKey(match.Groups[1].Value) is ComPortController com))
{
CrestronConsole.ConsoleCommandResponse("'{0}' is not a comm port device", match.Groups[1].Value);
return;
}
com.SimulateReceive(match.Groups[2].Value);
}
/// <summary>
/// Prints a list of routing inputs and outputs by device key.
/// </summary>
/// <param name="s">Device key from which to report data</param>
public static void GetRoutingPorts(string s)
{
var device = GetDeviceForKey(s);
if (device == null) return;
var inputPorts = ((device as IRoutingInputs) != null) ? (device as IRoutingInputs).InputPorts : null;
var outputPorts = ((device as IRoutingOutputs) != null) ? (device as IRoutingOutputs).OutputPorts : null;
if (inputPorts != null)
{
CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Input Ports:{2}", s, inputPorts.Count, CrestronEnvironment.NewLine);
foreach (var routingInputPort in inputPorts)
{
CrestronConsole.ConsoleCommandResponse("{0}{1}", routingInputPort.Key, CrestronEnvironment.NewLine);
}
}
if (outputPorts == null) return;
CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Output Ports:{2}", s, outputPorts.Count, CrestronEnvironment.NewLine);
foreach (var routingOutputPort in outputPorts)
{
CrestronConsole.ConsoleCommandResponse("{0}{1}", routingOutputPort.Key, CrestronEnvironment.NewLine);
}
}
/// <summary>
/// Attempts to set the debug level of a device
/// </summary>
/// <param name="s"></param>
public static void SetDeviceStreamDebugging(string s)
{
if (String.IsNullOrEmpty(s) || s.Contains("?"))
{
CrestronConsole.ConsoleCommandResponse(
@"SETDEVICESTREAMDEBUG [{deviceKey}] [OFF |TX | RX | BOTH] [timeOutInMinutes]
{deviceKey} [OFF | TX | RX | BOTH] - Device to set stream debugging on, and which setting to use
timeOutInMinutes - Set timeout for stream debugging. Default is 30 minutes");
return;
}
var args = s.Split(' ');
var deviceKey = args[0];
var setting = args[1];
var timeout = String.Empty;
if (args.Length >= 3)
{
timeout = args[2];
}
if (!(GetDeviceForKey(deviceKey) is IStreamDebugging device))
{
CrestronConsole.ConsoleCommandResponse("Unable to get device with key: {0}", deviceKey);
return;
}
eStreamDebuggingSetting debugSetting;
try
{
debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), setting, true);
}
catch
{
CrestronConsole.ConsoleCommandResponse("Unable to convert setting value. Please use off/rx/tx/both");
return;
}
if (!string.IsNullOrEmpty(timeout))
{
try
{
var min = Convert.ToUInt32(timeout);
device.StreamDebugging.SetDebuggingWithSpecificTimeout(debugSetting, min);
CrestronConsole.ConsoleCommandResponse("Device: '{0}' debug level set to {1} for {2} minutes", deviceKey, debugSetting, min);
}
catch (Exception e)
{
CrestronConsole.ConsoleCommandResponse("Unable to convert minutes or settings value. Please use an integer value for minutes. Error: {0}", e);
}
}
else
{
device.StreamDebugging.SetDebuggingWithDefaultTimeout(debugSetting);
CrestronConsole.ConsoleCommandResponse("Device: '{0}' debug level set to {1} for default time (30 minutes)", deviceKey, debugSetting);
}
}
/// <summary>
/// Sets stream debugging settings to off for all devices
/// </summary>
public static void DisableAllDeviceStreamDebugging()
{
foreach (var device in AllDevices)
{
if (device is IStreamDebugging streamDevice)
{
streamDevice.StreamDebugging.SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting.Off);
}
}
}

View File

@@ -9,190 +9,300 @@ using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.IO;
namespace PepperDash.Essentials.Core
namespace PepperDash.Essentials.Core;
/// <summary>
/// Represents a wrapper for a device factory, encapsulating the type of device, a description, and a factory method
/// for creating device instances.
/// </summary>
/// <remarks>This class is designed to provide a convenient way to store and manage metadata and factory methods
/// for creating devices based on a given configuration.</remarks>
public class DeviceFactoryWrapper
{
public class DeviceFactoryWrapper
{
public Type Type { get; set; }
public string Description { get; set; }
public Func<DeviceConfig, IKeyed> FactoryMethod { get; set; }
/// <summary>
/// Gets or sets the type associated with the current instance.
/// </summary>
public Type Type { get; set; }
public DeviceFactoryWrapper()
{
Type = null;
Description = "Not Available";
}
/// <summary>
/// Gets or sets the description associated with the object.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets or sets the factory method used to create an <see cref="IKeyed"/> instance based on the provided <see
/// cref="DeviceConfig"/>.
/// </summary>
/// <remarks>The factory method allows customization of how <see cref="IKeyed"/> instances are created for
/// specific <see cref="DeviceConfig"/> inputs. Ensure the delegate is not null before invoking it.</remarks>
public Func<DeviceConfig, IKeyed> FactoryMethod { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DeviceFactoryWrapper"/> class with default values.
/// </summary>
/// <remarks>The <see cref="Type"/> property is initialized to <see langword="null"/>, and the <see
/// cref="Description"/> property is set to "Not Available".</remarks>
public DeviceFactoryWrapper()
{
Type = null;
Description = "Not Available";
}
}
public class DeviceFactory
{
public DeviceFactory()
{
var assy = Assembly.GetExecutingAssembly();
PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy);
/// <summary>
/// Provides functionality for managing and registering device factories, including loading plugin-based factories and
/// retrieving devices based on their configuration.
/// </summary>
/// <remarks>The <see cref="DeviceFactory"/> class is responsible for discovering and registering device factories
/// from plugins, as well as providing methods to retrieve devices based on their configuration. It maintains a
/// collection of factory methods that are keyed by device type names, allowing for extensibility through plugins. This
/// class also handles metadata retrieval and secret management for device configurations.</remarks>
public class DeviceFactory
{
/// <summary>
/// Initializes a new instance of the <see cref="DeviceFactory"/> class and loads all available device factories
/// from the current assembly.
/// </summary>
/// <remarks>This constructor scans the executing assembly for types that implement the <see
/// cref="IDeviceFactory"/> interface and are not abstract or interfaces. For each valid type, an instance is
/// created and passed to the <c>LoadDeviceFactories</c> method for further processing. If a type cannot be
/// instantiated, an informational log message is generated, and the process continues with the remaining
/// types.</remarks>
public DeviceFactory()
{
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 type in types)
{
try
{
var factory = (IDeviceFactory)Activator.CreateInstance(type);
factory.LoadTypeFactories();
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, "Unable to load type: '{1}' DeviceFactory: {0}", e, type.Name);
}
}
}
}
/// <summary>
/// A dictionary of factory methods, keyed by config types, added by plugins.
/// These methods are looked up and called by GetDevice in this class.
/// </summary>
static Dictionary<string, DeviceFactoryWrapper> FactoryMethods =
new Dictionary<string, DeviceFactoryWrapper>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Adds a plugin factory method
/// </summary>
/// <param name="dc"></param>
/// <returns></returns>
public static void AddFactoryForType(string typeName, Func<DeviceConfig, IKeyed> method)
{
//Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName);
DeviceFactory.FactoryMethods.Add(typeName, new DeviceFactoryWrapper() { FactoryMethod = method});
}
public static void AddFactoryForType(string typeName, string description, Type Type, Func<DeviceConfig, IKeyed> method)
{
//Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName);
if(FactoryMethods.ContainsKey(typeName))
{
Debug.LogMessage(LogEventLevel.Information, "Unable to add type: '{0}'. Already exists in DeviceFactory", typeName);
return;
}
var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method };
DeviceFactory.FactoryMethods.Add(typeName, wrapper);
}
private static void CheckForSecrets(IEnumerable<JProperty> obj)
{
foreach (var prop in obj.Where(prop => prop.Value as JObject != null))
{
if (prop.Name.ToLower() == "secret")
{
var secret = GetSecret(prop.Children().First().ToObject<SecretsPropertiesConfig>());
//var secret = GetSecret(JsonConvert.DeserializeObject<SecretsPropertiesConfig>(prop.Children().First().ToString()));
prop.Parent.Replace(secret);
}
var recurseProp = prop.Value as JObject;
if (recurseProp == null) return;
CheckForSecrets(recurseProp.Properties());
}
}
private static string GetSecret(SecretsPropertiesConfig data)
{
var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider);
if (secretProvider == null) return null;
var secret = secretProvider.GetSecret(data.Key);
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;
}
/// <summary>
/// The factory method for Core "things". Also iterates the Factory methods that have
/// been loaded from plugins
/// </summary>
/// <param name="dc"></param>
/// <returns></returns>
public static IKeyed GetDevice(DeviceConfig dc)
foreach(var assembly in programAssemblies)
{
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)
{
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);
Assembly.LoadFrom(assembly);
}
catch (Exception ex)
catch (Exception e)
{
Debug.LogMessage(ex, "Exception occurred while creating device {0}: {1}", null, dc.Key, ex.Message);
return null;
Debug.LogError("Unable to load assembly: {assemblyName} - {message}", assembly, e.Message);
}
}
/// <summary>
/// Prints the type names and associated metadata from the FactoryMethods collection.
/// </summary>
/// <param name="filter"></param>
public static void GetDeviceFactoryTypes(string filter)
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
// Loop through all loaded assemblies that contain at least 1 type that implements IDeviceFactory
foreach (var assembly in loadedAssemblies)
{
Debug.LogDebug("loaded assembly: {assemblyName}", assembly.GetName().Name);
PluginLoader.AddLoadedAssembly(assembly.GetName().Name, assembly);
var types = assembly.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract);
if(types == null || !types.Any())
{
Debug.LogDebug("No DeviceFactory types found in assembly: {assemblyName}", assembly.GetName().Name);
continue;
}
foreach (var type in types)
{
try
{
var factory = (IDeviceFactory)Activator.CreateInstance(type);
LoadDeviceFactories(factory);
}
catch (Exception e)
{
Debug.LogError("Unable to load type: '{message}' DeviceFactory: {type}", e.Message, type.Name);
}
}
}
}
/// <summary>
/// Loads device factories from the specified plugin device factory and registers them for use.
/// </summary>
/// <remarks>This method retrieves metadata from the provided <paramref name="deviceFactory"/>, including
/// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type
/// names are converted to lowercase for registration.</remarks>
/// <param name="deviceFactory">The plugin device factory that provides the device types, descriptions, and factory methods to be registered.</param>
private static void LoadDeviceFactories(IDeviceFactory deviceFactory)
{
foreach (var typeName in deviceFactory.TypeNames)
{
var types = !string.IsNullOrEmpty(filter)
? FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value)
: FactoryMethods;
//Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName);
var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[];
string description = descriptionAttribute[0].Description;
var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[];
AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice);
}
}
/// <summary>
/// A dictionary of factory methods, keyed by config types, added by plugins.
/// These methods are looked up and called by GetDevice in this class.
/// </summary>
private static readonly Dictionary<string, DeviceFactoryWrapper> FactoryMethods =
new(StringComparer.OrdinalIgnoreCase);
CrestronConsole.ConsoleCommandResponse("Device Types:");
/// <summary>
/// Registers a factory method for creating instances of a specific type.
/// </summary>
/// <remarks>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.</remarks>
/// <param name="typeName">The name of the type for which the factory method is being registered. This value cannot be null or empty.</param>
/// <param name="method">A delegate that defines the factory method. The delegate takes a <see cref="DeviceConfig"/> parameter and
/// returns an instance of <see cref="IKeyed"/>.</param>
public static void AddFactoryForType(string typeName, Func<DeviceConfig, IKeyed> method)
{
FactoryMethods.Add(typeName, new DeviceFactoryWrapper() { FactoryMethod = method});
}
foreach (var type in types.OrderBy(t => t.Key))
/// <summary>
/// Registers a factory method for creating instances of a specific device type.
/// </summary>
/// <remarks>If a factory method for the specified <paramref name="typeName"/> already exists, the method
/// will not overwrite it and will log an informational message instead.</remarks>
/// <param name="typeName">The unique name of the device type. This serves as the key for identifying the factory method.</param>
/// <param name="description">A brief description of the device type. This is used for informational purposes.</param>
/// <param name="Type">The <see cref="Type"/> of the device being registered. This represents the runtime type of the device.</param>
/// <param name="method">A factory method that takes a <see cref="DeviceConfig"/> as input and returns an instance of <see
/// cref="IKeyed"/>.</param>
public static void AddFactoryForType(string typeName, string description, Type Type, Func<DeviceConfig, IKeyed> method)
{
if(FactoryMethods.ContainsKey(typeName))
{
Debug.LogInformation("Unable to add type: '{typeName}'. Already exists in DeviceFactory", typeName);
return;
}
var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method };
FactoryMethods.Add(typeName, wrapper);
}
private static void CheckForSecrets(IEnumerable<JProperty> obj)
{
foreach (var prop in obj.Where(prop => prop.Value as JObject != null))
{
if (prop.Name.Equals("secret", StringComparison.CurrentCultureIgnoreCase))
{
var description = type.Value.Description;
var Type = "Not Specified by Plugin";
var secret = GetSecret(prop.Children().First().ToObject<SecretsPropertiesConfig>());
prop.Parent.Replace(secret);
}
if (prop.Value is not JObject recurseProp) return;
CheckForSecrets(recurseProp.Properties());
}
}
if (type.Value.Type != null)
{
Type = type.Value.Type.FullName;
}
private static string GetSecret(SecretsPropertiesConfig data)
{
var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider);
if (secretProvider == null) return null;
var secret = secretProvider.GetSecret(data.Key);
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;
}
CrestronConsole.ConsoleCommandResponse(
@"Type: '{0}'
/// <summary>
/// Creates and returns a device instance based on the provided <see cref="DeviceConfig"/>.
/// </summary>
/// <remarks>This method attempts to create a device using the type specified in the <paramref name="dc"/>
/// parameter. If the type corresponds to a registered factory method, the device is created and returned. If the
/// type is unrecognized or an exception occurs, the method logs the error and returns <see
/// langword="null"/>.</remarks>
/// <param name="dc">The configuration object containing the key, name, type, and properties required to create the device.</param>
/// <returns>An instance of a device that implements <see cref="IKeyed"/>, or <see langword="null"/> if the device type is
/// not recognized or an error occurs during creation.</returns>
public static IKeyed GetDevice(DeviceConfig dc)
{
try
{
var localDc = new DeviceConfig(dc);
var key = localDc.Key;
var name = localDc.Name;
var type = localDc.Type;
var properties = localDc.Properties;
var typeName = localDc.Type.ToLower();
if (properties is JObject jObject)
{
var jProp = jObject.Properties();
CheckForSecrets(jProp);
}
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.LogError(ex, "Exception occurred while creating device {0}: {1}", null, dc.Key, ex.Message);
return null;
}
}
/// <summary>
/// Displays a list of device factory types that match the specified filter.
/// </summary>
/// <remarks>The method outputs the filtered list of device factory types to the console, including their
/// key, type, and description. If a type is not specified by the plugin, it will be displayed as "Not Specified by
/// Plugin."</remarks>
/// <param name="filter">A string used to filter the device factory types by their keys. If the filter is null or empty, all device
/// factory types are displayed.</param>
public static void GetDeviceFactoryTypes(string filter)
{
var types = !string.IsNullOrEmpty(filter)
? FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value)
: FactoryMethods;
CrestronConsole.ConsoleCommandResponse("Device Types:");
foreach (var type in types.OrderBy(t => t.Key))
{
var description = type.Value.Description;
var Type = "Not Specified by Plugin";
if (type.Value.Type != null)
{
Type = type.Value.Type.FullName;
}
CrestronConsole.ConsoleCommandResponse(
@"Type: '{0}'
Type: '{1}'
Description: {2}{3}", type.Key, Type, description, CrestronEnvironment.NewLine);
}
}
/// <summary>
/// Returns the device factory dictionary
/// </summary>
/// <param name="filter"></param>
/// <returns></returns>
public static Dictionary<string, DeviceFactoryWrapper> GetDeviceFactoryDictionary(string filter)
{
return string.IsNullOrEmpty(filter)
? FactoryMethods
: FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value);
}
}
/// <summary>
/// Retrieves a dictionary of device factory wrappers, optionally filtered by a specified string.
/// </summary>
/// <param name="filter">A string used to filter the dictionary keys. Only entries with keys containing the specified filter will be
/// included. If <see langword="null"/> or empty, all entries are returned.</param>
/// <returns>A dictionary where the keys are strings representing device identifiers and the values are <see
/// cref="DeviceFactoryWrapper"/> instances. The dictionary may be empty if no entries match the filter.</returns>
public static Dictionary<string, DeviceFactoryWrapper> GetDeviceFactoryDictionary(string filter)
{
return string.IsNullOrEmpty(filter)
? FactoryMethods
: FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value);
}
}

View File

@@ -10,143 +10,143 @@ using System.Linq;
namespace PepperDash.Essentials.Core;
public class ProcessorExtensionDeviceFactory
public class ProcessorExtensionDeviceFactory
{
public ProcessorExtensionDeviceFactory() {
var assy = Assembly.GetExecutingAssembly();
PluginLoader.AddLoadedAssembly(assy.GetName().Name, assy);
var extensions = assy.GetTypes().Where(ct => typeof(IProcessorExtensionDeviceFactory)
.IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract);
if (extensions != null )
{
foreach ( var extension in extensions )
{
try
{
var factory = (IProcessorExtensionDeviceFactory)Activator.CreateInstance(extension);
factory.LoadFactories();
}
catch( Exception e )
{
Debug.LogMessage(LogEventLevel.Information, "Unable to load extension device: '{1}' ProcessorExtensionDeviceFactory: {0}", e, extension.Name);
}
}
}
}
/// <summary>
/// A dictionary of factory methods, keyed by config types, added by plugins.
/// These methods are looked up and called by GetDevice in this class.
/// </summary>
static Dictionary<string, DeviceFactoryWrapper> ProcessorExtensionFactoryMethods =
new Dictionary<string, DeviceFactoryWrapper>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Adds a plugin factory method
/// </summary>
/// <param name="dc"></param>
/// <returns></returns>
public static void AddFactoryForType(string extensionName, Func<DeviceConfig, IKeyed> method)
{
public ProcessorExtensionDeviceFactory() {
var assy = Assembly.GetExecutingAssembly();
PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy);
//Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName);
ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, new DeviceFactoryWrapper() { FactoryMethod = method });
}
var extensions = assy.GetTypes().Where(ct => typeof(IProcessorExtensionDeviceFactory)
.IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract);
public static void AddFactoryForType(string extensionName, string description, Type Type, Func<DeviceConfig, IKeyed> method)
{
//Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName);
if (extensions != null )
if (ProcessorExtensionFactoryMethods.ContainsKey(extensionName))
{
Debug.LogMessage(LogEventLevel.Information, "Unable to add extension device: '{0}'. Already exists in ProcessorExtensionDeviceFactory", extensionName);
return;
}
var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method };
ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, wrapper);
}
private static void CheckForSecrets(IEnumerable<Newtonsoft.Json.Linq.JProperty> obj)
{
foreach (var prop in obj.Where(prop => prop.Value as Newtonsoft.Json.Linq.JObject != null))
{
if (prop.Name.ToLower() == "secret")
{
foreach ( var extension in extensions )
{
try
{
var factory = (IProcessorExtensionDeviceFactory)Activator.CreateInstance(extension);
factory.LoadFactories();
}
catch( Exception e )
{
Debug.LogMessage(LogEventLevel.Information, "Unable to load extension device: '{1}' ProcessorExtensionDeviceFactory: {0}", e, extension.Name);
}
}
var secret = GetSecret(prop.Children().First().ToObject<SecretsPropertiesConfig>());
//var secret = GetSecret(JsonConvert.DeserializeObject<SecretsPropertiesConfig>(prop.Children().First().ToString()));
prop.Parent.Replace(secret);
}
var recurseProp = prop.Value as Newtonsoft.Json.Linq.JObject;
if (recurseProp == null) return;
CheckForSecrets(recurseProp.Properties());
}
}
/// <summary>
/// A dictionary of factory methods, keyed by config types, added by plugins.
/// These methods are looked up and called by GetDevice in this class.
/// </summary>
static Dictionary<string, DeviceFactoryWrapper> ProcessorExtensionFactoryMethods =
new Dictionary<string, DeviceFactoryWrapper>(StringComparer.OrdinalIgnoreCase);
private static string GetSecret(SecretsPropertiesConfig data)
{
var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider);
if (secretProvider == null) return null;
var secret = secretProvider.GetSecret(data.Key);
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;
}
/// <summary>
/// Adds a plugin factory method
/// </summary>
/// <param name="dc"></param>
/// <returns></returns>
public static void AddFactoryForType(string extensionName, Func<DeviceConfig, IKeyed> method)
/// <summary>
/// The factory method for processor extension devices. Also iterates the Factory methods that have
/// been loaded from plugins
/// </summary>
/// <param name="dc"></param>
/// <returns></returns>
public static IKeyed GetExtensionDevice(DeviceConfig dc)
{
try
{
//Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName);
ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, new DeviceFactoryWrapper() { FactoryMethod = method });
}
Debug.LogMessage(LogEventLevel.Information, "Loading '{0}' from Essentials Core", dc.Type);
public static void AddFactoryForType(string extensionName, string description, Type Type, Func<DeviceConfig, IKeyed> method)
{
//Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName);
var localDc = new DeviceConfig(dc);
if (ProcessorExtensionFactoryMethods.ContainsKey(extensionName))
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 Newtonsoft.Json.Linq.JObject;
if (jObject != null)
{
Debug.LogMessage(LogEventLevel.Information, "Unable to add extension device: '{0}'. Already exists in ProcessorExtensionDeviceFactory", extensionName);
return;
var jProp = jObject.Properties();
CheckForSecrets(jProp);
}
var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method };
ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, wrapper);
Debug.LogMessage(LogEventLevel.Verbose, "typeName = {0}", typeName);
// Check for types that have been added by plugin dlls.
return !ProcessorExtensionFactoryMethods.ContainsKey(typeName) ? null : ProcessorExtensionFactoryMethods[typeName].FactoryMethod(localDc);
}
private static void CheckForSecrets(IEnumerable<Newtonsoft.Json.Linq.JProperty> obj)
catch (Exception ex)
{
foreach (var prop in obj.Where(prop => prop.Value as Newtonsoft.Json.Linq.JObject != null))
Debug.LogMessage(LogEventLevel.Information, "Exception occurred while creating device {0}: {1}", dc.Key, ex.Message);
Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.StackTrace);
if (ex.InnerException == null)
{
if (prop.Name.ToLower() == "secret")
{
var secret = GetSecret(prop.Children().First().ToObject<SecretsPropertiesConfig>());
//var secret = GetSecret(JsonConvert.DeserializeObject<SecretsPropertiesConfig>(prop.Children().First().ToString()));
prop.Parent.Replace(secret);
}
var recurseProp = prop.Value as Newtonsoft.Json.Linq.JObject;
if (recurseProp == null) return;
CheckForSecrets(recurseProp.Properties());
}
}
private static string GetSecret(SecretsPropertiesConfig data)
{
var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider);
if (secretProvider == null) return null;
var secret = secretProvider.GetSecret(data.Key);
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;
}
/// <summary>
/// The factory method for processor extension devices. Also iterates the Factory methods that have
/// been loaded from plugins
/// </summary>
/// <param name="dc"></param>
/// <returns></returns>
public static IKeyed GetExtensionDevice(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 Newtonsoft.Json.Linq.JObject;
if (jObject != null)
{
var jProp = jObject.Properties();
CheckForSecrets(jProp);
}
Debug.LogMessage(LogEventLevel.Verbose, "typeName = {0}", typeName);
// Check for types that have been added by plugin dlls.
return !ProcessorExtensionFactoryMethods.ContainsKey(typeName) ? null : ProcessorExtensionFactoryMethods[typeName].FactoryMethod(localDc);
}
catch (Exception ex)
{
Debug.LogMessage(LogEventLevel.Information, "Exception occurred while creating device {0}: {1}", dc.Key, ex.Message);
Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.StackTrace);
if (ex.InnerException == null)
{
return null;
}
Debug.LogMessage(LogEventLevel.Information, "Inner exception while creating device {0}: {1}", dc.Key,
ex.InnerException.Message);
Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.InnerException.StackTrace);
return null;
}
Debug.LogMessage(LogEventLevel.Information, "Inner exception while creating device {0}: {1}", dc.Key,
ex.InnerException.Message);
Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.InnerException.StackTrace);
return null;
}
}
}

View File

@@ -7,33 +7,51 @@ using PepperDash.Core;
using PepperDash.Essentials.Core;
using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common
namespace PepperDash.Essentials.Devices.Common;
public class DeviceFactory
{
public class DeviceFactory
public DeviceFactory()
{
var assy = Assembly.GetExecutingAssembly();
PluginLoader.AddLoadedAssembly(assy.GetName().Name, assy);
var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract);
public DeviceFactory()
if (types != null)
{
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)
foreach (var type in types)
{
foreach (var type in types)
try
{
try
{
var factory = (IDeviceFactory)Activator.CreateInstance(type);
factory.LoadTypeFactories();
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, "Unable to load type: '{1}' DeviceFactory: {0}", e, type.Name);
}
var factory = (IDeviceFactory)Activator.CreateInstance(type);
LoadDeviceFactories(factory);
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, "Unable to load type: '{1}' DeviceFactory: {0}", e, type.Name);
}
}
}
}
/// <summary>
/// Loads device factories from the specified plugin device factory and registers them for use.
/// </summary>
/// <remarks>This method retrieves metadata from the provided <paramref name="deviceFactory"/>, including
/// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type
/// names are converted to lowercase for registration.</remarks>
/// <param name="deviceFactory">The plugin device factory that provides the device types, descriptions, and factory methods to be registered.</param>
private static void LoadDeviceFactories(IDeviceFactory deviceFactory)
{
foreach (var typeName in deviceFactory.TypeNames)
{
//Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName);
var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[];
string description = descriptionAttribute[0].Description;
var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[];
Core.DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice);
}
}
}

View File

@@ -4,36 +4,54 @@ using System;
using System.Linq;
using System.Reflection;
namespace PepperDash.Essentials
namespace PepperDash.Essentials;
public class MobileControlFactory
{
public class MobileControlFactory
public MobileControlFactory()
{
public MobileControlFactory()
var assembly = Assembly.GetExecutingAssembly();
PluginLoader.AddLoadedAssembly(assembly.GetName().Name, assembly);
var types = assembly.GetTypes().Where(t => typeof(IDeviceFactory).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
if (types == null)
{
var assembly = Assembly.GetExecutingAssembly();
return;
}
PluginLoader.SetEssentialsAssembly(assembly.GetName().Name, assembly);
var types = assembly.GetTypes().Where(t => typeof(IDeviceFactory).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
if (types == null)
foreach (var type in types)
{
try
{
return;
var factory = (IDeviceFactory)Activator.CreateInstance(type);
LoadDeviceFactories(factory);
}
foreach (var type in types)
catch (Exception ex)
{
try
{
var factory = (IDeviceFactory)Activator.CreateInstance(type);
factory.LoadTypeFactories();
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Unable to load type '{type}' DeviceFactory: {factory}", null, type.Name);
}
Debug.LogMessage(ex, "Unable to load type '{type}' DeviceFactory: {factory}", null, type.Name);
}
}
}
/// <summary>
/// Loads device factories from the specified plugin device factory and registers them for use.
/// </summary>
/// <remarks>This method retrieves metadata from the provided <paramref name="deviceFactory"/>, including
/// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type
/// names are converted to lowercase for registration.</remarks>
/// <param name="deviceFactory">The plugin device factory that provides the device types, descriptions, and factory methods to be registered.</param>
private static void LoadDeviceFactories(IDeviceFactory deviceFactory)
{
foreach (var typeName in deviceFactory.TypeNames)
{
//Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName);
var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[];
string description = descriptionAttribute[0].Description;
var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[];
Core.DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice);
}
}
}

View File

@@ -14,267 +14,268 @@ using System;
using System.Linq;
using Serilog.Events;
using PepperDash.Essentials.Core.Routing;
using System.Threading;
using Timeout = Crestron.SimplSharp.Timeout;
namespace PepperDash.Essentials
namespace PepperDash.Essentials;
/// <summary>
/// Represents the main control system for the application, providing initialization, configuration loading, and device
/// management functionality.
/// </summary>
/// <remarks>This class extends <see cref="CrestronControlSystem"/> and serves as the entry point for the control
/// system. It manages the initialization of devices, rooms, tie lines, and other system components. Additionally, it
/// provides methods for platform determination, configuration loading, and system teardown.</remarks>
public class ControlSystem : CrestronControlSystem, ILoadConfig
{
public class ControlSystem : CrestronControlSystem, ILoadConfig
private HttpLogoServer LogoServer;
private Timer _startTimer;
private ManualResetEventSlim _initializeEvent;
private const long StartupTime = 500;
/// <summary>
/// Initializes a new instance of the <see cref="ControlSystem"/> class, setting up the system's global state and
/// dependencies.
/// </summary>
/// <remarks>This constructor configures the control system by initializing key components such as the
/// device manager and secrets manager, and sets global properties like the maximum number of user threads and the
/// program initialization state. It also adjusts the error log's minimum debug level based on the device
/// platform.</remarks>
public ControlSystem()
: base()
{
try
{
Crestron.SimplSharpPro.CrestronThread.Thread.MaxNumberOfUserThreads = 400;
Global.ControlSystem = this;
DeviceManager.Initialize(this);
SecretsManager.Initialize();
SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true;
Debug.SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? LogEventLevel.Warning : LogEventLevel.Verbose);
}
catch (Exception e)
{
Debug.LogError(e, "FATAL INITIALIZE ERROR. System is in an inconsistent state");
}
}
/// <summary>
/// Initializes the control system and prepares it for operation.
/// </summary>
/// <remarks>This method ensures that all devices in the system are properly registered and initialized
/// before the system is fully operational. If the control system is of a DMPS type, the method waits for all
/// devices to activate, allowing HD-BaseT DM endpoints to register before completing initialization. For non-DMPS
/// systems, initialization proceeds without waiting.</remarks>
public override void InitializeSystem()
{
HttpLogoServer LogoServer;
private CTimer _startTimer;
private CEvent _initializeEvent;
private const long StartupTime = 500;
public ControlSystem()
: base()
// If the control system is a DMPS type, we need to wait to exit this method until all devices have had time to activate
// to allow any HD-BaseT DM endpoints to register first.
bool preventInitializationComplete = Global.ControlSystemIsDmpsType;
if (preventInitializationComplete)
{
Debug.LogMessage(LogEventLevel.Debug, "******************* Initializing System **********************");
Thread.MaxNumberOfUserThreads = 400;
Global.ControlSystem = this;
DeviceManager.Initialize(this);
SecretsManager.Initialize();
SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true;
_startTimer = new Timer(StartSystem, preventInitializationComplete, StartupTime, Timeout.Infinite);
Debug.SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? LogEventLevel.Warning : LogEventLevel.Verbose);
_initializeEvent = new ManualResetEventSlim(false);
DeviceManager.AllDevicesRegistered += (o, a) =>
{
_initializeEvent.Set();
};
_initializeEvent.Wait(30000);
Debug.LogMessage(LogEventLevel.Debug, "******************* System Initialization Complete **********************");
// AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;
SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true;
}
else
{
_startTimer = new Timer(StartSystem, preventInitializationComplete, StartupTime, Timeout.Infinite);
}
}
private void StartSystem(object preventInitialization)
{
DeterminePlatform();
// Print .NET runtime version
Debug.LogMessage(LogEventLevel.Information, "Running on .NET runtime version: {0}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);
if (Debug.DoNotLoadConfigOnNextBoot)
{
CrestronConsole.AddNewConsoleCommand(s => CrestronInvoke.BeginInvoke((o) => GoWithLoad()), "go", "Loads configuration file",
ConsoleAccessLevelEnum.AccessOperator);
}
private System.Reflection.Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
CrestronConsole.AddNewConsoleCommand(PluginLoader.ReportAssemblyVersions, "reportversions", "Reports the versions of the loaded assemblies", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(Core.DeviceFactory.GetDeviceFactoryTypes, "gettypes", "Gets the device types that can be built. Accepts a filter string.", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(BridgeHelper.PrintJoinMap, "getjoinmap", "map(s) for bridge or device on bridge [brKey [devKey]]", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(BridgeHelper.JoinmapMarkdown, "getjoinmapmarkdown"
, "generate markdown of map(s) for bridge or device on bridge [brKey [devKey]]", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => Debug.LogMessage(LogEventLevel.Information, "CONSOLE MESSAGE: {0}", s), "appdebugmessage", "Writes message to log", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s =>
{
var assemblyName = new System.Reflection.AssemblyName(args.Name).Name;
if (assemblyName == "PepperDash_Core")
{
return System.Reflection.Assembly.LoadFrom("PepperDashCore.dll");
}
foreach (var tl in TieLineCollection.Default)
CrestronConsole.ConsoleCommandResponse(" {0}{1}", tl, CrestronEnvironment.NewLine);
},
"listtielines", "Prints out all tie lines", ConsoleAccessLevelEnum.AccessOperator);
if (assemblyName == "PepperDash_Essentials_Core")
{
return System.Reflection.Assembly.LoadFrom("PepperDash.Essentials.Core.dll");
}
CrestronConsole.AddNewConsoleCommand(s =>
{
CrestronConsole.ConsoleCommandResponse
("Current running configuration. This is the merged system and template configuration" + CrestronEnvironment.NewLine);
CrestronConsole.ConsoleCommandResponse(Newtonsoft.Json.JsonConvert.SerializeObject
(ConfigReader.ConfigObject, Newtonsoft.Json.Formatting.Indented));
}, "showconfig", "Shows the current running merged config", ConsoleAccessLevelEnum.AccessOperator);
if (assemblyName == "Essentials Devices Common")
{
return System.Reflection.Assembly.LoadFrom("PepperDash.Essentials.Devices.Common.dll");
}
CrestronConsole.AddNewConsoleCommand(s =>
CrestronConsole.ConsoleCommandResponse(
"This system can be found at the following URLs:{2}" +
"System URL: {0}{2}" +
"Template URL: {1}{2}",
ConfigReader.ConfigObject.SystemUrl,
ConfigReader.ConfigObject.TemplateUrl,
CrestronEnvironment.NewLine),
"portalinfo",
"Shows portal URLS from configuration",
ConsoleAccessLevelEnum.AccessOperator);
return null;
CrestronConsole.AddNewConsoleCommand(DeviceManager.GetRoutingPorts,
"getroutingports", "Reports all routing ports, if any. Requires a device key", ConsoleAccessLevelEnum.AccessOperator);
if (!Debug.DoNotLoadConfigOnNextBoot)
{
GoWithLoad();
return;
}
/// <summary>
/// Entry point for the program
/// </summary>
public override void InitializeSystem()
if (!(bool)preventInitialization)
{
// If the control system is a DMPS type, we need to wait to exit this method until all devices have had time to activate
// to allow any HD-BaseT DM endpoints to register first.
bool preventInitializationComplete = Global.ControlSystemIsDmpsType;
if (preventInitializationComplete)
SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true;
}
}
/// <summary>
/// Determines if the program is running on a processor (appliance) or server (VC-4).
///
/// Sets Global.FilePathPrefix and Global.ApplicationDirectoryPathPrefix based on platform
/// </summary>
public void DeterminePlatform()
{
try
{
Debug.LogMessage(LogEventLevel.Information, "Determining Platform...");
string filePathPrefix;
var dirSeparator = Global.DirectorySeparator;
string directoryPrefix;
directoryPrefix = Directory.GetApplicationRootDirectory();
Global.SetAssemblyVersion(PluginLoader.GetAssemblyVersion(Assembly.GetExecutingAssembly()));
if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Server) // Handles 3-series running Windows CE OS
{
Debug.LogMessage(LogEventLevel.Debug, "******************* InitializeSystem() Entering **********************");
_startTimer = new CTimer(StartSystem, preventInitializationComplete, StartupTime);
_initializeEvent = new CEvent(true, false);
DeviceManager.AllDevicesRegistered += (o, a) =>
string userFolder = "user";
string nvramFolder = "nvram";
Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on {processorSeries:l} Appliance", Global.AssemblyVersion, "4-series");
//Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{0} on {1} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series");
// Check if User/ProgramX exists
if (Directory.Exists(Global.ApplicationDirectoryPathPrefix + dirSeparator + userFolder
+ dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber)))
{
_initializeEvent.Set();
};
_initializeEvent.Wait(30000);
Debug.LogMessage(LogEventLevel.Debug, "******************* InitializeSystem() Exiting **********************");
SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true;
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;
}
// Check if Nvram/Programx exists
else if (Directory.Exists(directoryPrefix + dirSeparator + nvramFolder
+ 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;
}
// If neither exists, set path to User/ProgramX
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;
}
}
else // Handles Linux OS (Virtual Control)
{
//Debug.SetDebugLevel(2);
Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on Virtual Control Server", Global.AssemblyVersion);
// Set path to User/
filePathPrefix = directoryPrefix + dirSeparator + "User" + dirSeparator;
}
Global.SetFilePathPrefix(filePathPrefix);
}
catch (Exception e)
{
Debug.LogMessage(e, "Unable to determine platform due to exception");
}
}
/// <summary>
/// Begins the process of loading resources including plugins and configuration data
/// </summary>
public void GoWithLoad()
{
try
{
Debug.SetDoNotLoadConfigOnNextBoot(false);
PluginLoader.AddProgramAssemblies();
_ = new Core.DeviceFactory();
Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration");
var filesReady = SetupFilesystem();
if (filesReady)
{
Debug.LogMessage(LogEventLevel.Information, "Checking for plugins");
PluginLoader.LoadPlugins();
Debug.LogMessage(LogEventLevel.Information, "Folder structure verified. Loading config...");
if (!ConfigReader.LoadConfig2())
{
Debug.LogMessage(LogEventLevel.Information, "Essentials Load complete with errors");
return;
}
Load();
Debug.LogMessage(LogEventLevel.Information, "Essentials load complete");
}
else
{
_startTimer = new CTimer(StartSystem, preventInitializationComplete, StartupTime);
}
}
private void StartSystem(object preventInitialization)
{
Debug.SetErrorLogMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose);
DeterminePlatform();
// Print .NET runtime version
Debug.LogMessage(LogEventLevel.Information, "Running on .NET runtime version: {0}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);
if (Debug.DoNotLoadConfigOnNextBoot)
{
CrestronConsole.AddNewConsoleCommand(s => CrestronInvoke.BeginInvoke((o) => GoWithLoad()), "go", "Loads configuration file",
ConsoleAccessLevelEnum.AccessOperator);
}
CrestronConsole.AddNewConsoleCommand(PluginLoader.ReportAssemblyVersions, "reportversions", "Reports the versions of the loaded assemblies", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(Core.DeviceFactory.GetDeviceFactoryTypes, "gettypes", "Gets the device types that can be built. Accepts a filter string.", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(BridgeHelper.PrintJoinMap, "getjoinmap", "map(s) for bridge or device on bridge [brKey [devKey]]", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(BridgeHelper.JoinmapMarkdown, "getjoinmapmarkdown"
, "generate markdown of map(s) for bridge or device on bridge [brKey [devKey]]", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => Debug.LogMessage(LogEventLevel.Information, "CONSOLE MESSAGE: {0}", s), "appdebugmessage", "Writes message to log", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s =>
{
foreach (var tl in TieLineCollection.Default)
CrestronConsole.ConsoleCommandResponse(" {0}{1}", tl, CrestronEnvironment.NewLine);
},
"listtielines", "Prints out all tie lines", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s =>
{
CrestronConsole.ConsoleCommandResponse
("Current running configuration. This is the merged system and template configuration" + CrestronEnvironment.NewLine);
CrestronConsole.ConsoleCommandResponse(Newtonsoft.Json.JsonConvert.SerializeObject
(ConfigReader.ConfigObject, Newtonsoft.Json.Formatting.Indented));
}, "showconfig", "Shows the current running merged config", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s =>
CrestronConsole.ConsoleCommandResponse(
"This system can be found at the following URLs:{2}" +
"System URL: {0}{2}" +
"Template URL: {1}{2}",
ConfigReader.ConfigObject.SystemUrl,
ConfigReader.ConfigObject.TemplateUrl,
CrestronEnvironment.NewLine),
"portalinfo",
"Shows portal URLS from configuration",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(DeviceManager.GetRoutingPorts,
"getroutingports", "Reports all routing ports, if any. Requires a device key", ConsoleAccessLevelEnum.AccessOperator);
//DeviceManager.AddDevice(new EssentialsWebApi("essentialsWebApi", "Essentials Web API"));
if (!Debug.DoNotLoadConfigOnNextBoot)
{
GoWithLoad();
return;
}
if (!(bool)preventInitialization)
{
SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true;
}
}
/// <summary>
/// Determines if the program is running on a processor (appliance) or server (VC-4).
///
/// Sets Global.FilePathPrefix and Global.ApplicationDirectoryPathPrefix based on platform
/// </summary>
public void DeterminePlatform()
{
try
{
Debug.LogMessage(LogEventLevel.Information, "Determining Platform...");
string filePathPrefix;
var dirSeparator = Global.DirectorySeparator;
string directoryPrefix;
directoryPrefix = Directory.GetApplicationRootDirectory();
Global.SetAssemblyVersion(PluginLoader.GetAssemblyVersion(Assembly.GetExecutingAssembly()));
if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Server) // Handles 3-series running Windows CE OS
{
string userFolder = "user";
string nvramFolder = "nvram";
Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on {processorSeries:l} Appliance", Global.AssemblyVersion, "4-series");
//Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{0} on {1} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series");
// Check if User/ProgramX exists
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);
filePathPrefix = directoryPrefix + dirSeparator + userFolder
+ dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator;
}
// Check if Nvram/Programx exists
else if (Directory.Exists(directoryPrefix + dirSeparator + nvramFolder
+ 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;
}
// If neither exists, set path to User/ProgramX
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;
}
}
else // Handles Linux OS (Virtual Control)
{
//Debug.SetDebugLevel(2);
Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on Virtual Control Server", Global.AssemblyVersion);
// Set path to User/
filePathPrefix = directoryPrefix + dirSeparator + "User" + dirSeparator;
}
Global.SetFilePathPrefix(filePathPrefix);
}
catch (Exception e)
{
Debug.LogMessage(e, "Unable to determine platform due to exception");
}
}
/// <summary>
/// Begins the process of loading resources including plugins and configuration data
/// </summary>
public void GoWithLoad()
{
try
{
Debug.SetDoNotLoadConfigOnNextBoot(false);
PluginLoader.AddProgramAssemblies();
_ = new Core.DeviceFactory();
_ = new Devices.Common.DeviceFactory();
_ = new DeviceFactory();
_ = new ProcessorExtensionDeviceFactory();
_ = new MobileControlFactory();
Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration");
var filesReady = SetupFilesystem();
if (filesReady)
{
Debug.LogMessage(LogEventLevel.Information, "Checking for plugins");
PluginLoader.LoadPlugins();
Debug.LogMessage(LogEventLevel.Information, "Folder structure verified. Loading config...");
if (!ConfigReader.LoadConfig2())
{
Debug.LogMessage(LogEventLevel.Information, "Essentials Load complete with errors");
return;
}
Load();
Debug.LogMessage(LogEventLevel.Information, "Essentials load complete");
}
else
{
Debug.LogMessage(LogEventLevel.Information,
@"----------------------------------------------
Debug.LogMessage(LogEventLevel.Information,
@"----------------------------------------------
------------------------------------------------
------------------------------------------------
Essentials file structure setup completed.
@@ -283,51 +284,51 @@ namespace PepperDash.Essentials
------------------------------------------------
------------------------------------------------
------------------------------------------------");
}
}
catch (Exception e)
{
Debug.LogMessage(e, "FATAL INITIALIZE ERROR. System is in an inconsistent state");
}
finally
{
// Notify the OS that the program intitialization has completed
SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true;
}
}
/// <summary>
/// Verifies filesystem is set up. IR, SGD, and programX folders
/// </summary>
bool SetupFilesystem()
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, "Verifying and/or creating folder structure");
var configDir = Global.FilePathPrefix;
Debug.LogMessage(e, "FATAL INITIALIZE ERROR. System is in an inconsistent state");
}
finally
{
// Notify the OS that the program intitialization has completed
SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true;
}
Debug.LogMessage(LogEventLevel.Information, "FilePathPrefix: {filePathPrefix:l}", configDir);
var configExists = Directory.Exists(configDir);
if (!configExists)
Directory.Create(configDir);
}
var irDir = Global.FilePathPrefix + "ir";
if (!Directory.Exists(irDir))
Directory.Create(irDir);
var sgdDir = Global.FilePathPrefix + "sgd";
/// <summary>
/// Verifies filesystem is set up. IR, SGD, and programX folders
/// </summary>
bool SetupFilesystem()
{
Debug.LogMessage(LogEventLevel.Information, "Verifying and/or creating folder structure");
var configDir = Global.FilePathPrefix;
Debug.LogMessage(LogEventLevel.Information, "FilePathPrefix: {filePathPrefix:l}", configDir);
var configExists = Directory.Exists(configDir);
if (!configExists)
Directory.Create(configDir);
var irDir = Global.FilePathPrefix + "ir";
if (!Directory.Exists(irDir))
Directory.Create(irDir);
var sgdDir = Global.FilePathPrefix + "sgd";
if (!Directory.Exists(sgdDir))
Directory.Create(sgdDir);
var pluginDir = Global.FilePathPrefix + "plugins";
if (!Directory.Exists(pluginDir))
Directory.Create(pluginDir);
var pluginDir = Global.FilePathPrefix + "plugins";
if (!Directory.Exists(pluginDir))
Directory.Create(pluginDir);
var joinmapDir = Global.FilePathPrefix + "joinmaps";
if(!Directory.Exists(joinmapDir))
Directory.Create(joinmapDir);
var joinmapDir = Global.FilePathPrefix + "joinmaps";
if(!Directory.Exists(joinmapDir))
Directory.Create(joinmapDir);
return configExists;
}
@@ -360,199 +361,198 @@ namespace PepperDash.Essentials
DeviceManager.ActivateAll();
LoadTieLines();
LoadTieLines();
/*var mobileControl = GetMobileControlDevice();
/*var mobileControl = GetMobileControlDevice();
if (mobileControl == null) return;
mobileControl.LinkSystemMonitorToAppServer();*/
mobileControl.LinkSystemMonitorToAppServer();*/
}
/// <summary>
/// Reads all devices from config and adds them to DeviceManager
/// </summary>
public void LoadDevices()
/// <summary>
/// Reads all devices from config and adds them to DeviceManager
/// </summary>
public void LoadDevices()
{
// Build the processor wrapper class
DeviceManager.AddDevice(new Core.Devices.CrestronProcessor("processor"));
DeviceManager.AddDevice(new RoutingFeedbackManager($"routingFeedbackManager", "Routing Feedback Manager"));
// Add global System Monitor device
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance)
{
DeviceManager.AddDevice(
new Core.Monitoring.SystemMonitorController("systemMonitor"));
}
// Build the processor wrapper class
DeviceManager.AddDevice(new Core.Devices.CrestronProcessor("processor"));
foreach (var devConf in ConfigReader.ConfigObject.Devices)
{
IKeyed newDev = null;
DeviceManager.AddDevice(new RoutingFeedbackManager($"routingFeedbackManager", "Routing Feedback Manager"));
// Add global System Monitor device
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance)
try
{
DeviceManager.AddDevice(
new Core.Monitoring.SystemMonitorController("systemMonitor"));
}
foreach (var devConf in ConfigReader.ConfigObject.Devices)
{
IKeyed newDev = null;
try
Debug.LogMessage(LogEventLevel.Information, "Creating device '{deviceKey:l}', type '{deviceType:l}'", devConf.Key, devConf.Type);
// Skip this to prevent unnecessary warnings
if (devConf.Key == "processor")
{
Debug.LogMessage(LogEventLevel.Information, "Creating device '{deviceKey:l}', type '{deviceType:l}'", devConf.Key, devConf.Type);
// Skip this to prevent unnecessary warnings
if (devConf.Key == "processor")
{
var prompt = Global.ControlSystem.ControllerPrompt;
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,
"WARNING: Config file defines processor type as '{deviceType:l}' but actual processor is '{processorType:l}'! Some ports may not be available",
devConf.Type.ToUpper(), Global.ControlSystem.ControllerPrompt.ToUpper());
if (!typeMatch)
Debug.LogMessage(LogEventLevel.Information,
"WARNING: Config file defines processor type as '{deviceType:l}' but actual processor is '{processorType:l}'! Some ports may not be available",
devConf.Type.ToUpper(), Global.ControlSystem.ControllerPrompt.ToUpper());
continue;
}
continue;
}
if (newDev == null)
newDev = Core.DeviceFactory.GetDevice(devConf);
if (newDev == null)
newDev = Core.DeviceFactory.GetDevice(devConf);
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(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(LogEventLevel.Information, "All Devices Loaded.");
}
Debug.LogMessage(LogEventLevel.Information, "All Devices Loaded.");
}
/// <summary>
/// Helper method to load tie lines. This should run after devices have loaded
/// </summary>
public void LoadTieLines()
/// <summary>
/// Helper method to load tie lines. This should run after devices have loaded
/// </summary>
public void LoadTieLines()
{
// In the future, we can't necessarily just clear here because devices
// might be making their own internal sources/tie lines
var tlc = TieLineCollection.Default;
if (ConfigReader.ConfigObject.TieLines == null)
{
// In the future, we can't necessarily just clear here because devices
// might be making their own internal sources/tie lines
var tlc = TieLineCollection.Default;
if (ConfigReader.ConfigObject.TieLines == null)
{
return;
}
foreach (var tieLineConfig in ConfigReader.ConfigObject.TieLines)
{
var newTL = tieLineConfig.GetTieLine();
if (newTL != null)
tlc.Add(newTL);
}
Debug.LogMessage(LogEventLevel.Information, "All Tie Lines Loaded.");
return;
}
/// <summary>
/// Reads all rooms from config and adds them to DeviceManager
/// </summary>
public void LoadRooms()
{
if (ConfigReader.ConfigObject.Rooms == null)
foreach (var tieLineConfig in ConfigReader.ConfigObject.TieLines)
{
var newTL = tieLineConfig.GetTieLine();
if (newTL != null)
tlc.Add(newTL);
}
Debug.LogMessage(LogEventLevel.Information, "All Tie Lines Loaded.");
}
/// <summary>
/// Reads all rooms from config and adds them to DeviceManager
/// </summary>
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)
{
try
{
Debug.LogMessage(LogEventLevel.Information, "Notice: Configuration contains no rooms - Is this intentional? This may be a valid configuration.");
return;
}
var room = Core.DeviceFactory.GetDevice(roomConfig);
foreach (var roomConfig in ConfigReader.ConfigObject.Rooms)
{
try
if(room == null)
{
var room = Core.DeviceFactory.GetDevice(roomConfig);
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)
{
Debug.LogMessage(ex, "Exception loading room {roomKey}:{roomType}", null, roomConfig.Key, roomConfig.Type);
Debug.LogWarning("ERROR: Cannot load unknown room type '{roomType:l}', key '{roomKey:l}'.", roomConfig.Type, roomConfig.Key);
continue;
}
}
Debug.LogMessage(LogEventLevel.Information, "All Rooms Loaded.");
}
/// <summary>
/// Fires up a logo server if not already running
/// </summary>
void LoadLogoServer()
{
if (ConfigReader.ConfigObject.Rooms == null)
DeviceManager.AddDevice(room);
} catch (Exception ex)
{
Debug.LogMessage(LogEventLevel.Information, "No rooms configured. Bypassing Logo server startup.");
return;
}
if (
!ConfigReader.ConfigObject.Rooms.Any(
CheckRoomConfig))
{
Debug.LogMessage(LogEventLevel.Information, "No rooms configured to use system Logo server. Bypassing Logo server startup");
return;
}
try
{
LogoServer = new HttpLogoServer(8080, Global.DirectorySeparator + "html" + Global.DirectorySeparator + "logo");
}
catch (Exception)
{
Debug.LogMessage(LogEventLevel.Information, "NOTICE: Logo server cannot be started. Likely already running in another program");
Debug.LogMessage(ex, "Exception loading room {roomKey}:{roomType}", null, roomConfig.Key, roomConfig.Type);
continue;
}
}
private bool CheckRoomConfig(DeviceConfig c)
Debug.LogMessage(LogEventLevel.Information, "All Rooms Loaded.");
}
/// <summary>
/// Fires up a logo server if not already running
/// </summary>
void LoadLogoServer()
{
if (ConfigReader.ConfigObject.Rooms == null)
{
string logoDark = null;
string logoLight = null;
string logo = null;
Debug.LogMessage(LogEventLevel.Information, "No rooms configured. Bypassing Logo server startup.");
return;
}
try
if (
!ConfigReader.ConfigObject.Rooms.Any(
CheckRoomConfig))
{
Debug.LogMessage(LogEventLevel.Information, "No rooms configured to use system Logo server. Bypassing Logo server startup");
return;
}
try
{
LogoServer = new HttpLogoServer(8080, Global.DirectorySeparator + "html" + Global.DirectorySeparator + "logo");
}
catch (Exception)
{
Debug.LogMessage(LogEventLevel.Information, "NOTICE: Logo server cannot be started. Likely already running in another program");
}
}
private bool CheckRoomConfig(DeviceConfig c)
{
string logoDark = null;
string logoLight = null;
string logo = null;
try
{
if (c.Properties["logoDark"] != null)
{
if (c.Properties["logoDark"] != null)
{
logoDark = c.Properties["logoDark"].Value<string>("type");
}
if (c.Properties["logoLight"] != null)
{
logoLight = c.Properties["logoLight"].Value<string>("type");
}
if (c.Properties["logo"] != null)
{
logo = c.Properties["logo"].Value<string>("type");
}
return ((logoDark != null && logoDark == "system") ||
(logoLight != null && logoLight == "system") || (logo != null && logo == "system"));
logoDark = c.Properties["logoDark"].Value<string>("type");
}
catch
if (c.Properties["logoLight"] != null)
{
Debug.LogMessage(LogEventLevel.Information, "Unable to find logo information in any room config");
return false;
logoLight = c.Properties["logoLight"].Value<string>("type");
}
if (c.Properties["logo"] != null)
{
logo = c.Properties["logo"].Value<string>("type");
}
return ((logoDark != null && logoDark == "system") ||
(logoLight != null && logoLight == "system") || (logo != null && logo == "system"));
}
catch
{
Debug.LogMessage(LogEventLevel.Information, "Unable to find logo information in any room config");
return false;
}
}
}

View File

@@ -14,36 +14,54 @@ using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials
namespace PepperDash.Essentials;
/// <summary>
/// Responsible for loading all of the device types for this library
/// </summary>
public class DeviceFactory
{
/// <summary>
/// Responsible for loading all of the device types for this library
/// </summary>
public class DeviceFactory
public DeviceFactory()
{
var assy = Assembly.GetExecutingAssembly();
PluginLoader.AddLoadedAssembly(assy.GetName().Name, assy);
public DeviceFactory()
var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract);
if (types != null)
{
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)
foreach (var type in types)
{
foreach (var type in types)
try
{
try
{
var factory = (IDeviceFactory)Activator.CreateInstance(type);
factory.LoadTypeFactories();
}
catch (Exception e)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Error, "Unable to load type: '{exception}' DeviceFactory: {factoryName}", e, type.Name);
}
var factory = (IDeviceFactory)Activator.CreateInstance(type);
LoadDeviceFactories(factory);
}
catch (Exception e)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Error, "Unable to load type: '{exception}' DeviceFactory: {factoryName}", e, type.Name);
}
}
}
}
/// <summary>
/// Loads device factories from the specified plugin device factory and registers them for use.
/// </summary>
/// <remarks>This method retrieves metadata from the provided <paramref name="deviceFactory"/>, including
/// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type
/// names are converted to lowercase for registration.</remarks>
/// <param name="deviceFactory">The plugin device factory that provides the device types, descriptions, and factory methods to be registered.</param>
private static void LoadDeviceFactories(IDeviceFactory deviceFactory)
{
foreach (var typeName in deviceFactory.TypeNames)
{
//Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName);
var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[];
string description = descriptionAttribute[0].Description;
var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[];
Core.DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice);
}
}
}

View File

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