using System; using System.Linq; using System.Reflection; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.CrestronThread; using Crestron.SimplSharpPro.Diagnostics; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Routing; using PepperDash.Essentials.Core.Web; using Serilog.Events; namespace PepperDash.Essentials { /// /// Main control system class that inherits from CrestronControlSystem and manages program lifecycle /// public class ControlSystem : CrestronControlSystem, ILoadConfig { HttpLogoServer LogoServer; private CTimer _startTimer; private CEvent _initializeEvent; private const long StartupTime = 500; // private const string minimumFirmwareVersion = "2.8006.00110"; /// /// Initializes a new instance of the ControlSystem class /// public ControlSystem() : base() { Thread.MaxNumberOfUserThreads = 400; Global.ControlSystem = this; DeviceManager.Initialize(this); SecretsManager.Initialize(); SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; } /// /// InitializeSystem method /// /// public override void InitializeSystem() { // Get FW version and stop if it's too low to run this version of Essentials. Must be greater than v2.8006.00110 // var fwVersion = InitialParametersClass.FirmwareVersion; // Debug.LogInformation("Control System Hardware Version: {fwVersion}", fwVersion); // // split the version into parts and compare against minimumFirmwareVersion // var versionParts = fwVersion.Split('.').Select(int.Parse).ToArray(); // var minParts = minimumFirmwareVersion.Split('.').Select(int.Parse).ToArray(); // if (versionParts.Length < minParts.Length // || versionParts[0] < minParts[0] // || (versionParts[0] == minParts[0] && versionParts[1] < minParts[1]) // || (versionParts[0] == minParts[0] && versionParts[1] == minParts[1] && versionParts[2] <= minParts[2])) // { // Debug.LogFatal("Firmware version {fwVersion} is too low to run this version of Essentials. Please upgrade to greater than v{minimumFirmwareVersion}.", fwVersion, minimumFirmwareVersion); // return; // } // 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, "******************* InitializeSystem() Entering **********************"); _startTimer = new CTimer(StartSystem, preventInitializationComplete, StartupTime); _initializeEvent = new CEvent(true, false); DeviceManager.AllDevicesRegistered += (o, a) => { _initializeEvent.Set(); }; _initializeEvent.Wait(30000); Debug.LogMessage(LogEventLevel.Debug, "******************* InitializeSystem() Exiting **********************"); SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; } else { _startTimer = new CTimer(StartSystem, preventInitializationComplete, StartupTime); } } private void StartSystem(object preventInitialization) { DeterminePlatform(); 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(ListTieLines, "listtielines", "Prints out all tie lines. Usage: listtielines [signaltype]", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(VisualizeRoutes, "visualizeroutes", "Visualizes routes by signal type", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(VisualizeCurrentRoutes, "visualizecurrentroutes", "Visualizes current active routes from DefaultCollection", 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).Replace(Environment.NewLine, "\r\n")); }, "showconfig", "Shows the current running merged config", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand( PrintPortalInfo, "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; } } private void PrintPortalInfo(string args) { if(ConfigReader.ConfigObject == null) { CrestronConsole.ConsoleCommandResponse("No configuration loaded. Cannot show portal URLs."); return; } if (string.IsNullOrEmpty(ConfigReader.ConfigObject.SystemUrl) && string.IsNullOrEmpty(ConfigReader.ConfigObject.TemplateUrl)) { CrestronConsole.ConsoleCommandResponse("No portal URLs defined in config."); return; } 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); } /// /// DeterminePlatform method /// 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; string nvramFolder; bool is4series = false; if (eCrestronSeries.Series4 == (Global.ProcessorSeries & eCrestronSeries.Series4)) // Handle 4-series { is4series = true; // Set path to user/ userFolder = "user"; nvramFolder = "nvram"; } else { userFolder = "User"; nvramFolder = "Nvram"; } Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on {processorSeries:l} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series"); //Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{0} on {1} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series"); // 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"); } } /// /// GoWithLoad method /// public void GoWithLoad() { try { Debug.SetDoNotLoadConfigOnNextBoot(false); PluginLoader.AddProgramAssemblies(); _ = new Core.DeviceFactory(); LoadAssets(Global.ApplicationDirectoryPathPrefix, Global.FilePathPrefix); 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() || ConfigReader.ConfigObject == null) { Debug.LogMessage(LogEventLevel.Warning, "Unable to load config file."); } Load(); Debug.LogMessage(LogEventLevel.Information, "Essentials load complete"); } else { Debug.LogMessage(LogEventLevel.Information, @"---------------------------------------------- ------------------------------------------------ ------------------------------------------------ Essentials file structure setup completed. Please load config, sgd and ir files and restart program. ------------------------------------------------ ------------------------------------------------ ------------------------------------------------"); } } 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; } } /// /// Verifies filesystem is set up. IR, SGD, and programX folders /// 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 joinmapDir = Global.FilePathPrefix + "joinmaps"; if (!Directory.Exists(joinmapDir)) Directory.Create(joinmapDir); return configExists; } /// /// TearDown method /// public void TearDown() { Debug.LogMessage(LogEventLevel.Information, "Tearing down existing system"); DeviceManager.DeactivateAll(); TieLineCollection.Default.Clear(); foreach (var key in DeviceManager.GetDevices()) DeviceManager.RemoveDevice(key); Debug.LogMessage(LogEventLevel.Information, "Tear down COMPLETE"); } /// /// /// void Load() { LoadDevices(); LoadRooms(); LoadLogoServer(); DeviceManager.ActivateAll(); LoadTieLines(); /*var mobileControl = GetMobileControlDevice(); if (mobileControl == null) return; mobileControl.LinkSystemMonitorToAppServer();*/ } /// /// LoadDevices method /// 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")); } if (ConfigReader.ConfigObject is null) { Debug.LogMessage(LogEventLevel.Warning, "LoadDevices: ConfigObject is null. Cannot load devices."); return; } 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") { var prompt = Global.ControlSystem.ControllerPrompt; 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()); continue; } 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, "All Devices Loaded."); } /// /// LoadTieLines method /// 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) { 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."); Extensions.MapDestinationsToSources(); Debug.LogMessage(LogEventLevel.Information, "All Routes Mapped."); } /// /// Visualizes routes in a tree format for better understanding of signal paths /// private void ListTieLines(string args) { try { if (!string.IsNullOrEmpty(args) && args.Contains("?")) { CrestronConsole.ConsoleCommandResponse("Usage: listtielines [signaltype]\r\n"); CrestronConsole.ConsoleCommandResponse("Signal types: Audio, Video, SecondaryAudio, AudioVideo, UsbInput, UsbOutput\r\n"); return; } eRoutingSignalType? signalTypeFilter = null; if (!string.IsNullOrEmpty(args)) { eRoutingSignalType parsedType; if (Enum.TryParse(args.Trim(), true, out parsedType)) { signalTypeFilter = parsedType; } else { CrestronConsole.ConsoleCommandResponse("Invalid signal type: {0}\r\n", args.Trim()); CrestronConsole.ConsoleCommandResponse("Valid types: Audio, Video, SecondaryAudio, AudioVideo, UsbInput, UsbOutput\r\n"); return; } } var tielines = signalTypeFilter.HasValue ? TieLineCollection.Default.Where(tl => tl.Type.HasFlag(signalTypeFilter.Value)) : TieLineCollection.Default; var count = 0; foreach (var tl in tielines) { CrestronConsole.ConsoleCommandResponse(" {0}{1}", tl, CrestronEnvironment.NewLine); count++; } CrestronConsole.ConsoleCommandResponse("\r\nTotal: {0} tieline{1}{2}", count, count == 1 ? "" : "s", CrestronEnvironment.NewLine); } catch (Exception ex) { CrestronConsole.ConsoleCommandResponse("Error listing tielines: {0}\r\n", ex.Message); } } private void VisualizeRoutes(string args) { try { if (!string.IsNullOrEmpty(args) && args.Contains("?")) { CrestronConsole.ConsoleCommandResponse("Usage: visualizeroutes [signaltype] [-s source] [-d destination]\r\n"); CrestronConsole.ConsoleCommandResponse(" signaltype: Audio, Video, AudioVideo, etc.\r\n"); CrestronConsole.ConsoleCommandResponse(" -s: Filter by source key (partial match)\r\n"); CrestronConsole.ConsoleCommandResponse(" -d: Filter by destination key (partial match)\r\n"); return; } ParseRouteFilters(args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter); CrestronConsole.ConsoleCommandResponse("\r\n+===========================================================================+\r\n"); CrestronConsole.ConsoleCommandResponse("| ROUTE VISUALIZATION |\r\n"); CrestronConsole.ConsoleCommandResponse("+===========================================================================+\r\n\r\n"); foreach (var descriptorCollection in Extensions.RouteDescriptors.Where(kv => kv.Value.Descriptors.Count() > 0)) { // Filter by signal type if specified if (signalTypeFilter.HasValue && descriptorCollection.Key != signalTypeFilter.Value) continue; CrestronConsole.ConsoleCommandResponse("\r\n+--- Signal Type: {0} ({1} routes) ---\r\n", descriptorCollection.Key, descriptorCollection.Value.Descriptors.Count()); foreach (var descriptor in descriptorCollection.Value.Descriptors) { // Filter by source/dest if specified if (sourceFilter != null && !descriptor.Source.Key.ToLower().Contains(sourceFilter)) continue; if (destFilter != null && !descriptor.Destination.Key.ToLower().Contains(destFilter)) continue; VisualizeRouteDescriptor(descriptor); } } CrestronConsole.ConsoleCommandResponse("\r\n"); } catch (Exception ex) { CrestronConsole.ConsoleCommandResponse("Error visualizing routes: {0}\r\n", ex.Message); } } private void VisualizeCurrentRoutes(string args) { try { if (!string.IsNullOrEmpty(args) && args.Contains("?")) { CrestronConsole.ConsoleCommandResponse("Usage: visualizecurrentroutes [signaltype] [-s source] [-d destination]\r\n"); CrestronConsole.ConsoleCommandResponse(" signaltype: Audio, Video, AudioVideo, etc.\r\n"); CrestronConsole.ConsoleCommandResponse(" -s: Filter by source key (partial match)\r\n"); CrestronConsole.ConsoleCommandResponse(" -d: Filter by destination key (partial match)\r\n"); return; } ParseRouteFilters(args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter); CrestronConsole.ConsoleCommandResponse("\r\n+===========================================================================+\r\n"); CrestronConsole.ConsoleCommandResponse("| CURRENT ROUTES VISUALIZATION |\r\n"); CrestronConsole.ConsoleCommandResponse("+===========================================================================+\r\n\r\n"); var hasRoutes = false; // Get all descriptors from DefaultCollection var allDescriptors = RouteDescriptorCollection.DefaultCollection.Descriptors; // Group by signal type var groupedByType = allDescriptors.GroupBy(d => d.SignalType); foreach (var group in groupedByType) { var signalType = group.Key; // Filter by signal type if specified if (signalTypeFilter.HasValue && signalType != signalTypeFilter.Value) continue; var filteredDescriptors = group.Where(d => { if (sourceFilter != null && !d.Source.Key.ToLower().Contains(sourceFilter)) return false; if (destFilter != null && !d.Destination.Key.ToLower().Contains(destFilter)) return false; return true; }).ToList(); if (filteredDescriptors.Count == 0) continue; hasRoutes = true; CrestronConsole.ConsoleCommandResponse("\r\n+--- Signal Type: {0} ({1} routes) ---\r\n", signalType, filteredDescriptors.Count); foreach (var descriptor in filteredDescriptors) { VisualizeRouteDescriptor(descriptor); } } if (!hasRoutes) { CrestronConsole.ConsoleCommandResponse("\r\nNo active routes found in current state.\r\n"); } CrestronConsole.ConsoleCommandResponse("\r\n"); } catch (Exception ex) { CrestronConsole.ConsoleCommandResponse("Error visualizing current state: {0}\r\n", ex.Message); } } /// /// Parses route filter arguments from command line /// /// Command line arguments /// Parsed signal type filter (if any) /// Parsed source filter (if any) /// Parsed destination filter (if any) private void ParseRouteFilters(string args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter) { signalTypeFilter = null; sourceFilter = null; destFilter = null; if (string.IsNullOrEmpty(args)) return; var parts = args.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < parts.Length; i++) { var part = parts[i]; // Check for flags if (part == "-s" && i + 1 < parts.Length) { sourceFilter = parts[++i].ToLower(); } else if (part == "-d" && i + 1 < parts.Length) { destFilter = parts[++i].ToLower(); } // Try to parse as signal type if not a flag and no signal type set yet else if (!part.StartsWith("-") && !signalTypeFilter.HasValue) { if (Enum.TryParse(part, true, out eRoutingSignalType parsedType)) { signalTypeFilter = parsedType; } } } } /// /// Visualizes a single route descriptor in a tree format /// private void VisualizeRouteDescriptor(RouteDescriptor descriptor) { CrestronConsole.ConsoleCommandResponse("|\r\n"); CrestronConsole.ConsoleCommandResponse("|-- {0} --> {1}\r\n", descriptor.Source.Key, descriptor.Destination.Key); if (descriptor.Routes == null || descriptor.Routes.Count == 0) { CrestronConsole.ConsoleCommandResponse("| +-- (No switching steps)\r\n"); return; } for (int i = 0; i < descriptor.Routes.Count; i++) { var route = descriptor.Routes[i]; var isLast = i == descriptor.Routes.Count - 1; var prefix = isLast ? "+" : "|"; var continuation = isLast ? " " : "|"; if (route.SwitchingDevice != null) { CrestronConsole.ConsoleCommandResponse("| {0}-- [{1}] {2}\r\n", prefix, route.SwitchingDevice.Key, GetSwitchDescription(route)); // Add visual connection line for non-last items if (!isLast) CrestronConsole.ConsoleCommandResponse("| {0} |\r\n", continuation); } else { CrestronConsole.ConsoleCommandResponse("| {0}-- {1}\r\n", prefix, route.ToString()); } } } /// /// Gets a readable description of the switching operation /// private string GetSwitchDescription(RouteSwitchDescriptor route) { if (route.OutputPort != null && route.InputPort != null) { return string.Format("{0} -> {1}", route.OutputPort.Key, route.InputPort.Key); } else if (route.InputPort != null) { return string.Format("-> {0}", route.InputPort.Key); } else { return "(passthrough)"; } } /// /// LoadRooms method /// public void LoadRooms() { if (ConfigReader.ConfigObject?.Rooms == null) { Debug.LogMessage(LogEventLevel.Information, "Notice: Configuration contains no rooms - Is this intentional? This may be a valid configuration."); return; } foreach (var roomConfig in ConfigReader.ConfigObject.Rooms) { try { 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); continue; } } Debug.LogMessage(LogEventLevel.Information, "All Rooms Loaded."); } /// /// Fires up a logo server if not already running /// void LoadLogoServer() { if (ConfigReader.ConfigObject?.Rooms == null) { Debug.LogMessage(LogEventLevel.Information, "No rooms configured. Bypassing Logo server startup."); return; } if (ConfigReader.ConfigObject?.Rooms == null || !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) { logoDark = c.Properties["logoDark"].Value("type"); } if (c.Properties["logoLight"] != null) { logoLight = c.Properties["logoLight"].Value("type"); } if (c.Properties["logo"] != null) { logo = c.Properties["logo"].Value("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; } } internal static void LoadAssets(string applicationDirectoryPath, string filePathPrefix) => AssetLoader.Load(applicationDirectoryPath, filePathPrefix); } }