Compare commits

..

2 Commits

Author SHA1 Message Date
Neil Dorin
b4d53dbe0e refactor: routing interfaces and implementations to support current sources
- Updated ICurrentSources interface to use IRoutingSource instead of SourceListItem.
- Introduced CurrentSourcesChangedEventArgs for detailed event notifications.
- Modified IRoutingOutputs and IRoutingSource interfaces to include JSON properties for serialization.
- Enhanced IRoutingSink and related interfaces to implement ICurrentSources for better source management.
- Refactored RoutingFeedbackManager to utilize new current source handling.
- Updated GenericAudioOut, GenericSink, and BlueJeansPc classes to implement new current source logic.
- Adjusted MobileControl messengers to accommodate changes in current source handling.
- Removed deprecated destination handling in MobileControlEssentialsRoomBridge.
2026-03-26 13:49:23 -06:00
Neil Dorin
43a9661e08 Refactor: Removing unused classes that reference Crestron HTTP classes
- Removed the HttpLogoServer class and its related functionality from ControlSystem.
- Updated ControlSystem to eliminate references to the logo server, including initialization and device checks.
- Cleaned up unused variables and methods related to logo server handling.
2026-03-23 11:20:44 -06:00
26 changed files with 446 additions and 2143 deletions

View File

@@ -131,12 +131,6 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
/// </summary>
public string ClientStatusText { get { return ClientStatus.ToString(); } }
/// <summary>
/// Ushort representation of client status
/// </summary>
[Obsolete]
public ushort UClientStatus { get { return (ushort)ClientStatus; } }
/// <summary>
/// Connection failure reason
/// </summary>

View File

@@ -1,86 +0,0 @@
using System;
namespace PepperDash.Core.WebApi.Presets;
/// <summary>
/// Represents a preset
/// </summary>
public class Preset
{
/// <summary>
/// ID of preset
/// </summary>
public int Id { get; set; }
/// <summary>
/// User ID
/// </summary>
public int UserId { get; set; }
/// <summary>
/// Room Type ID
/// </summary>
public int RoomTypeId { get; set; }
/// <summary>
/// Preset Name
/// </summary>
public string PresetName { get; set; }
/// <summary>
/// Preset Number
/// </summary>
public int PresetNumber { get; set; }
/// <summary>
/// Preset Data
/// </summary>
public string Data { get; set; }
/// <summary>
/// Constructor
/// </summary>
public Preset()
{
PresetName = "";
PresetNumber = 1;
Data = "{}";
}
}
/// <summary>
/// Represents a PresetReceivedEventArgs
/// </summary>
public class PresetReceivedEventArgs : EventArgs
{
/// <summary>
/// True when the preset is found
/// </summary>
public bool LookupSuccess { get; private set; }
/// <summary>
/// S+ helper
/// </summary>
public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } }
/// <summary>
/// The preset
/// </summary>
public Preset Preset { get; private set; }
/// <summary>
/// For Simpl+
/// </summary>
public PresetReceivedEventArgs() { }
/// <summary>
/// Constructor
/// </summary>
/// <param name="preset"></param>
/// <param name="success"></param>
public PresetReceivedEventArgs(Preset preset, bool success)
{
LookupSuccess = success;
Preset = preset;
}
}

View File

@@ -1,92 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Core.WebApi.Presets;
/// <summary>
///
/// </summary>
public class User
{
/// <summary>
///
/// </summary>
public int Id { get; set; }
/// <summary>
///
/// </summary>
public string ExternalId { get; set; }
/// <summary>
///
/// </summary>
public string FirstName { get; set; }
/// <summary>
///
/// </summary>
public string LastName { get; set; }
}
/// <summary>
///
/// </summary>
public class UserReceivedEventArgs : EventArgs
{
/// <summary>
/// True when user is found
/// </summary>
public bool LookupSuccess { get; private set; }
/// <summary>
/// For stupid S+
/// </summary>
public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } }
/// <summary>
///
/// </summary>
public User User { get; private set; }
/// <summary>
/// For Simpl+
/// </summary>
public UserReceivedEventArgs() { }
/// <summary>
/// Constructor
/// </summary>
/// <param name="user"></param>
/// <param name="success"></param>
public UserReceivedEventArgs(User user, bool success)
{
LookupSuccess = success;
User = user;
}
}
/// <summary>
/// Represents a UserAndRoomMessage
/// </summary>
public class UserAndRoomMessage
{
/// <summary>
///
/// </summary>
public int UserId { get; set; }
/// <summary>
///
/// </summary>
public int RoomTypeId { get; set; }
/// <summary>
///
/// </summary>
public int PresetNumber { get; set; }
}

View File

@@ -1,280 +0,0 @@
extern alias NewtonsoftJson;
using System;
using Crestron.SimplSharp; // For Basic SIMPL# Classes
using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharp.Net.Http;
using Crestron.SimplSharp.Net.Https;
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using PepperDash.Core.JsonToSimpl;
namespace PepperDash.Core.WebApi.Presets;
/// <summary>
/// Passcode client for the WebApi
/// </summary>
public class WebApiPasscodeClient : IKeyed
{
/// <summary>
/// Notifies when user received
/// </summary>
public event EventHandler<UserReceivedEventArgs> UserReceived;
/// <summary>
/// Notifies when Preset received
/// </summary>
public event EventHandler<PresetReceivedEventArgs> PresetReceived;
/// <summary>
/// Unique identifier for this instance
/// </summary>
public string Key { get; private set; }
//string JsonMasterKey;
/// <summary>
/// An embedded JsonToSimpl master object.
/// </summary>
JsonToSimplGenericMaster J2SMaster;
string UrlBase;
string DefaultPresetJsonFilePath;
User CurrentUser;
Preset CurrentPreset;
/// <summary>
/// SIMPL+ can only execute the default constructor. If you have variables that require initialization, please
/// use an Initialize method
/// </summary>
public WebApiPasscodeClient()
{
}
/// <summary>
/// Initializes the instance
/// </summary>
/// <param name="key"></param>
/// <param name="jsonMasterKey"></param>
/// <param name="urlBase"></param>
/// <param name="defaultPresetJsonFilePath"></param>
public void Initialize(string key, string jsonMasterKey, string urlBase, string defaultPresetJsonFilePath)
{
Key = key;
//JsonMasterKey = jsonMasterKey;
UrlBase = urlBase;
DefaultPresetJsonFilePath = defaultPresetJsonFilePath;
J2SMaster = new JsonToSimplGenericMaster();
J2SMaster.SaveCallback = this.SaveCallback;
J2SMaster.Initialize(jsonMasterKey);
}
/// <summary>
/// Gets the user for a passcode
/// </summary>
/// <param name="passcode"></param>
public void GetUserForPasscode(string passcode)
{
// Bullshit duplicate code here... These two cases should be the same
// except for https/http and the certificate ignores
if (!UrlBase.StartsWith("https"))
return;
var req = new HttpsClientRequest();
req.Url = new UrlParser(UrlBase + "/api/users/dopin");
req.RequestType = Crestron.SimplSharp.Net.Https.RequestType.Post;
req.Header.AddHeader(new HttpsHeader("Content-Type", "application/json"));
req.Header.AddHeader(new HttpsHeader("Accept", "application/json"));
var jo = new JObject();
jo.Add("pin", passcode);
req.ContentString = jo.ToString();
var client = new HttpsClient();
client.HostVerification = false;
client.PeerVerification = false;
var resp = client.Dispatch(req);
var handler = UserReceived;
if (resp.Code == 200)
{
//CrestronConsole.PrintLine("Received: {0}", resp.ContentString);
var user = JsonConvert.DeserializeObject<User>(resp.ContentString);
CurrentUser = user;
if (handler != null)
UserReceived(this, new UserReceivedEventArgs(user, true));
}
else
if (handler != null)
UserReceived(this, new UserReceivedEventArgs(null, false));
}
/// <summary>
///
/// </summary>
/// <param name="roomTypeId"></param>
/// <param name="presetNumber"></param>
/// <summary>
/// GetPresetForThisUser method
/// </summary>
public void GetPresetForThisUser(int roomTypeId, int presetNumber)
{
if (CurrentUser == null)
{
CrestronConsole.PrintLine("GetPresetForThisUser no user loaded");
return;
}
var msg = new UserAndRoomMessage
{
UserId = CurrentUser.Id,
RoomTypeId = roomTypeId,
PresetNumber = presetNumber
};
var handler = PresetReceived;
try
{
if (!UrlBase.StartsWith("https"))
return;
var req = new HttpsClientRequest();
req.Url = new UrlParser(UrlBase + "/api/presets/userandroom");
req.RequestType = Crestron.SimplSharp.Net.Https.RequestType.Post;
req.Header.AddHeader(new HttpsHeader("Content-Type", "application/json"));
req.Header.AddHeader(new HttpsHeader("Accept", "application/json"));
req.ContentString = JsonConvert.SerializeObject(msg);
var client = new HttpsClient();
client.HostVerification = false;
client.PeerVerification = false;
// ask for the preset
var resp = client.Dispatch(req);
if (resp.Code == 200) // got it
{
//Debug.Console(1, this, "Received: {0}", resp.ContentString);
var preset = JsonConvert.DeserializeObject<Preset>(resp.ContentString);
CurrentPreset = preset;
//if there's no preset data, load the template
if (preset.Data == null || preset.Data.Trim() == string.Empty || JObject.Parse(preset.Data).Count == 0)
{
//Debug.Console(1, this, "Loaded preset has no data. Loading default template.");
LoadDefaultPresetData();
return;
}
J2SMaster.LoadWithJson(preset.Data);
if (handler != null)
PresetReceived(this, new PresetReceivedEventArgs(preset, true));
}
else // no existing preset
{
CurrentPreset = new Preset();
LoadDefaultPresetData();
if (handler != null)
PresetReceived(this, new PresetReceivedEventArgs(null, false));
}
}
catch (HttpException e)
{
var resp = e.Response;
Debug.Console(1, this, "No preset received (code {0}). Loading default template", resp.Code);
LoadDefaultPresetData();
if (handler != null)
PresetReceived(this, new PresetReceivedEventArgs(null, false));
}
}
void LoadDefaultPresetData()
{
CurrentPreset = null;
if (!File.Exists(DefaultPresetJsonFilePath))
{
Debug.Console(0, this, "Cannot load default preset file. Saving will not work");
return;
}
using (StreamReader sr = new StreamReader(DefaultPresetJsonFilePath))
{
try
{
var data = sr.ReadToEnd();
J2SMaster.SetJsonWithoutEvaluating(data);
CurrentPreset = new Preset() { Data = data, UserId = CurrentUser.Id };
}
catch (Exception e)
{
Debug.Console(0, this, "Error reading default preset JSON: \r{0}", e);
}
}
}
/// <summary>
///
/// </summary>
/// <param name="roomTypeId"></param>
/// <param name="presetNumber"></param>
/// <summary>
/// SavePresetForThisUser method
/// </summary>
public void SavePresetForThisUser(int roomTypeId, int presetNumber)
{
if (CurrentPreset == null)
LoadDefaultPresetData();
//return;
//// A new preset needs to have its numbers set
//if (CurrentPreset.IsNewPreset)
//{
CurrentPreset.UserId = CurrentUser.Id;
CurrentPreset.RoomTypeId = roomTypeId;
CurrentPreset.PresetNumber = presetNumber;
//}
J2SMaster.Save(); // Will trigger callback when ready
}
/// <summary>
/// After save operation on JSON master happens, send it to server
/// </summary>
/// <param name="json"></param>
void SaveCallback(string json)
{
CurrentPreset.Data = json;
if (!UrlBase.StartsWith("https"))
return;
var req = new HttpsClientRequest();
req.RequestType = Crestron.SimplSharp.Net.Https.RequestType.Post;
req.Url = new UrlParser(string.Format("{0}/api/presets/addorchange", UrlBase));
req.Header.AddHeader(new HttpsHeader("Content-Type", "application/json"));
req.Header.AddHeader(new HttpsHeader("Accept", "application/json"));
req.ContentString = JsonConvert.SerializeObject(CurrentPreset);
var client = new HttpsClient();
client.HostVerification = false;
client.PeerVerification = false;
try
{
var resp = client.Dispatch(req);
// 201=created
// 204=empty content
if (resp.Code == 201)
CrestronConsole.PrintLine("Preset added");
else if (resp.Code == 204)
CrestronConsole.PrintLine("Preset updated");
else if (resp.Code == 209)
CrestronConsole.PrintLine("Preset already exists. Cannot save as new.");
else
CrestronConsole.PrintLine("Preset save failed: {0}\r", resp.Code, resp.ContentString);
}
catch (HttpException e)
{
CrestronConsole.PrintLine("Preset save exception {0}", e.Response.Code);
}
}
}

View File

@@ -1,259 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Crestron.SimplSharp.Net.Http;
using Crestron.SimplSharpPro.Diagnostics;
using PepperDash.Core;
using Serilog.Events;
namespace PepperDash.Essentials.Core.Config;
public static class ConfigUpdater
{
public static event EventHandler<ConfigStatusEventArgs> ConfigStatusChanged;
public static void GetConfigFromServer(string url)
{
Debug.LogMessage(LogEventLevel.Information, "Attempting to get new config from '{0}'", url);
// HTTP GET
var req = new HttpClientRequest();
try
{
req.RequestType = RequestType.Get;
req.Url.Parse(url);
new HttpClient().DispatchAsync(req, (r, e) =>
{
if (e == HTTP_CALLBACK_ERROR.COMPLETED)
{
if (r.Code == 200)
{
var newConfig = r.ContentString;
OnStatusUpdate(eUpdateStatus.ConfigFileReceived);
ArchiveExistingPortalConfigs();
CheckForLocalConfigAndDelete();
WriteConfigToFile(newConfig);
RestartProgram();
}
else
{
Debug.LogMessage(LogEventLevel.Information, "Config Update Process Stopped. Failed to get config file from server: {0}", r.Code);
OnStatusUpdate(eUpdateStatus.UpdateFailed);
}
}
else
Debug.LogMessage(LogEventLevel.Information, "Request for config from Server Failed: {0}", e);
});
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Debug, "Error Getting Config from Server: {0}", e);
}
}
static void OnStatusUpdate(eUpdateStatus status)
{
var handler = ConfigStatusChanged;
if(handler != null)
{
handler(typeof(ConfigUpdater), new ConfigStatusEventArgs(status));
}
}
static void WriteConfigToFile(string configData)
{
var filePath = Global.FilePathPrefix+ "configurationFile-updated.json";
try
{
var config = JObject.Parse(configData).ToObject<EssentialsConfig>();
ConfigWriter.WriteFile(filePath, configData);
OnStatusUpdate(eUpdateStatus.WritingConfigFile);
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Debug, "Error parsing new config: {0}", e);
OnStatusUpdate(eUpdateStatus.UpdateFailed);
}
}
/// <summary>
/// Checks for any existing portal config files and archives them
/// </summary>
static void ArchiveExistingPortalConfigs()
{
var filePath = Global.FilePathPrefix + Global.ConfigFileName;
var configFiles = ConfigReader.GetConfigFiles(filePath);
if (configFiles != null)
{
Debug.LogMessage(LogEventLevel.Information, "Existing config files found. Moving to Archive folder.");
OnStatusUpdate(eUpdateStatus.ArchivingConfigs);
MoveFilesToArchiveFolder(configFiles);
}
else
{
Debug.LogMessage(LogEventLevel.Information, "No Existing config files found in '{0}'. Nothing to archive", filePath);
}
}
/// <summary>
/// Checks for presence of archive folder and if found deletes contents.
/// Moves any config files to the archive folder and adds a .bak suffix
/// </summary>
/// <param name="files"></param>
static void MoveFilesToArchiveFolder(FileInfo[] files)
{
string archiveDirectoryPath = Global.FilePathPrefix + "archive";
if (!Directory.Exists(archiveDirectoryPath))
{
// Directory does not exist, create it
Directory.Create(archiveDirectoryPath);
}
else
{
// Directory exists, first clear any contents
var archivedConfigFiles = ConfigReader.GetConfigFiles(archiveDirectoryPath + Global.DirectorySeparator + Global.ConfigFileName + ".bak");
if(archivedConfigFiles != null || archivedConfigFiles.Length > 0)
{
Debug.LogMessage(LogEventLevel.Information, "{0} Existing files found in archive folder. Deleting.", archivedConfigFiles.Length);
for (int i = 0; i < archivedConfigFiles.Length; i++ )
{
var file = archivedConfigFiles[i];
Debug.LogMessage(LogEventLevel.Information, "Deleting archived file: '{0}'", file.FullName);
file.Delete();
}
}
}
// Move any files from the program folder to the archive folder
foreach (var file in files)
{
Debug.LogMessage(LogEventLevel.Information, "Moving config file '{0}' to archive folder", file.FullName);
// Moves the file and appends the .bak extension
var fileDest = archiveDirectoryPath + "/" + file.Name + ".bak";
if(!File.Exists(fileDest))
{
file.MoveTo(fileDest);
}
else
Debug.LogMessage(LogEventLevel.Information, "Cannot move file to archive folder. Existing file already exists with same name: '{0}'", fileDest);
}
}
/// <summary>
/// Checks for LocalConfig folder in file system and deletes if found
/// </summary>
static void CheckForLocalConfigAndDelete()
{
var folderPath = Global.FilePathPrefix + ConfigWriter.LocalConfigFolder;
if (Directory.Exists(folderPath))
{
OnStatusUpdate(eUpdateStatus.DeletingLocalConfig);
Directory.Delete(folderPath);
Debug.LogMessage(LogEventLevel.Information, "Local Config Found in '{0}'. Deleting.", folderPath);
}
}
/// <summary>
/// Connects to the processor via SSH and restarts the program
/// </summary>
static void RestartProgram()
{
Debug.LogMessage(LogEventLevel.Information, "Attempting to Reset Program");
OnStatusUpdate(eUpdateStatus.RestartingProgram);
string response = string.Empty;
CrestronConsole.SendControlSystemCommand(string.Format("progreset -p:{0}", InitialParametersClass.ApplicationNumber), ref response);
Debug.LogMessage(LogEventLevel.Debug, "Console Response: {0}", response);
}
}
/// <summary>
/// Enumeration of eUpdateStatus values
/// </summary>
public enum eUpdateStatus
{
/// <summary>
/// UpdateStarted status
/// </summary>
UpdateStarted,
/// <summary>
/// ConfigFileReceived status
/// </summary>
ConfigFileReceived,
/// <summary>
/// ArchivingConfigs status
/// </summary>
ArchivingConfigs,
/// <summary>
/// DeletingLocalConfig status
/// </summary>
DeletingLocalConfig,
/// <summary>
/// WritingConfigFile status
/// </summary>
WritingConfigFile,
/// <summary>
/// RestartingProgram status
/// </summary>
RestartingProgram,
/// <summary>
/// UpdateSucceeded status
/// </summary>
UpdateSucceeded,
/// <summary>
/// UpdateFailed status
/// </summary>
UpdateFailed
}
public class ConfigStatusEventArgs : EventArgs
{
public eUpdateStatus UpdateStatus { get; private set; }
public ConfigStatusEventArgs(eUpdateStatus status)
{
UpdateStatus = status;
}
}

View File

@@ -2,6 +2,7 @@
using Newtonsoft.Json;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.Core;
@@ -11,23 +12,22 @@ namespace PepperDash.Essentials.Core;
/// </summary>
public class DestinationListItem
{
/// <summary>
/// Gets or sets the key identifier for the sink device that this destination represents.
/// </summary>
[JsonProperty("sinkKey")]
public string SinkKey { get; set; }
private EssentialsDevice _sinkDevice;
private IRoutingSink _sinkDevice;
/// <summary>
/// Gets the actual device instance for this destination.
/// Lazily loads the device from the DeviceManager using the SinkKey.
/// </summary>
[JsonIgnore]
public EssentialsDevice SinkDevice
public IRoutingSink SinkDevice
{
get { return _sinkDevice ?? (_sinkDevice = DeviceManager.GetDeviceForKey(SinkKey) as EssentialsDevice); }
get { return _sinkDevice ?? (_sinkDevice = DeviceManager.GetDeviceForKey(SinkKey) as IRoutingSink); }
}
/// <summary>

View File

@@ -43,17 +43,17 @@ public class SourceListItem
/// Returns the source Device for this, if it exists in DeviceManager
/// </summary>
[JsonIgnore]
public Device SourceDevice
public IRoutingSource SourceDevice
{
get
{
if (_SourceDevice == null)
_SourceDevice = DeviceManager.GetDeviceForKey(SourceKey) as Device;
_SourceDevice = DeviceManager.GetDeviceForKey<IRoutingSource>(SourceKey);
return _SourceDevice;
}
}
private Device _SourceDevice;
private IRoutingSource _SourceDevice;
/// <summary>
/// Gets either the source's Name or this AlternateName property, if

View File

@@ -1236,11 +1236,14 @@ namespace PepperDash.Essentials.Core.Fusion
uint i = 0;
foreach (var kvp in setTopBoxes)
{
TryAddRouteActionSigs(JoinMap.Display1SetTopBoxSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1SetTopBoxSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice);
i++;
if (i > JoinMap.Display1SetTopBoxSourceStart.JoinSpan) // We only have five spots
if (kvp.Value.SourceDevice is Device device)
{
break;
TryAddRouteActionSigs(JoinMap.Display1SetTopBoxSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1SetTopBoxSourceStart.JoinNumber + i, kvp.Key, device);
i++;
if (i > JoinMap.Display1SetTopBoxSourceStart.JoinSpan) // We only have five spots
{
break;
}
}
}
@@ -1248,11 +1251,14 @@ namespace PepperDash.Essentials.Core.Fusion
i = 0;
foreach (var kvp in discPlayers)
{
TryAddRouteActionSigs(JoinMap.Display1DiscPlayerSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1DiscPlayerSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice);
i++;
if (i > JoinMap.Display1DiscPlayerSourceStart.JoinSpan) // We only have five spots
if (kvp.Value.SourceDevice is Device device)
{
break;
TryAddRouteActionSigs(JoinMap.Display1DiscPlayerSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1DiscPlayerSourceStart.JoinNumber + i, kvp.Key, device);
i++;
if (i > JoinMap.Display1DiscPlayerSourceStart.JoinSpan) // We only have five spots
{
break;
}
}
}
@@ -1260,11 +1266,14 @@ namespace PepperDash.Essentials.Core.Fusion
i = 0;
foreach (var kvp in laptops)
{
TryAddRouteActionSigs(JoinMap.Display1LaptopSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1LaptopSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice);
i++;
if (i > JoinMap.Display1LaptopSourceStart.JoinSpan) // We only have ten spots???
if (kvp.Value.SourceDevice is Device device)
{
break;
TryAddRouteActionSigs(JoinMap.Display1LaptopSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1LaptopSourceStart.JoinNumber + i, kvp.Key, device);
i++;
if (i > JoinMap.Display1LaptopSourceStart.JoinSpan) // We only have ten spots???
{
break;
}
}
}
@@ -1745,22 +1754,24 @@ namespace PepperDash.Essentials.Core.Fusion
return;
}
var dev = info.SourceDevice;
if (type == ChangeType.WillChange)
if (info.SourceDevice is Device dev)
{
if (_sourceToFeedbackSigs.ContainsKey(dev))
if (type == ChangeType.WillChange)
{
_sourceToFeedbackSigs[dev].BoolValue = false;
if (_sourceToFeedbackSigs.ContainsKey(dev))
{
_sourceToFeedbackSigs[dev].BoolValue = false;
}
}
}
else
{
if (_sourceToFeedbackSigs.ContainsKey(dev))
else
{
_sourceToFeedbackSigs[dev].BoolValue = true;
if (_sourceToFeedbackSigs.ContainsKey(dev))
{
_sourceToFeedbackSigs[dev].BoolValue = true;
}
//var name = (room == null ? "" : room.Name);
CurrentRoomSourceNameSig.InputSig.StringValue = dev.Name;
}
//var name = (room == null ? "" : room.Name);
CurrentRoomSourceNameSig.InputSig.StringValue = info.SourceDevice.Name;
}
}

View File

@@ -28,18 +28,6 @@ public interface IHasDefaultDisplay
IRoutingSink DefaultDisplay { get; }
}
/// <summary>
/// For rooms with multiple displays
/// </summary>
[Obsolete("This interface is being deprecated in favor of using destination lists for routing to multiple displays.")]
public interface IHasMultipleDisplays
{
/// <summary>
/// A dictionary of displays in the room with the key being the type of display (presentation, calling, etc.) and the value being the display device
/// </summary>
Dictionary<eSourceListItemDestinationTypes, IRoutingSink> Displays { get; }
}
/// <summary>
/// For rooms with routing
/// </summary>

View File

@@ -6,6 +6,7 @@ namespace PepperDash.Essentials.Core.Routing
/// <summary>
/// The current sources for the room, keyed by eRoutingSignalType.
/// This allows for multiple sources to be tracked, such as audio and video.
/// Intended to be implemented on a DestinationListItem to provide access to the current sources for the room in its context.
/// </summary>
/// <remarks>
/// This interface is used to provide access to the current sources in a room,
@@ -17,7 +18,7 @@ namespace PepperDash.Essentials.Core.Routing
/// Gets the current sources for the room, keyed by eRoutingSignalType.
/// This dictionary contains the current source for each signal type, such as audio, video, and control signals.
/// </summary>
Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; }
Dictionary<eRoutingSignalType, IRoutingSource> CurrentSources { get; }
/// <summary>
/// Gets the current source keys for the room, keyed by eRoutingSignalType.
@@ -28,16 +29,51 @@ namespace PepperDash.Essentials.Core.Routing
/// <summary>
/// Event raised when the current sources change.
/// </summary>
event EventHandler CurrentSourcesChanged;
event EventHandler<CurrentSourcesChangedEventArgs> CurrentSourcesChanged;
/// <summary>
/// Sets the current source for a specific signal type.
/// This method updates the current source for the specified signal type and notifies any subscribers of the change.
/// </summary>
/// <param name="signalType">The signal type to update.</param>
/// <param name="sourceListKey">The key for the source list.</param>
/// <param name="sourceListItem">The source list item to set as the current source.</param>
void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem);
/// <param name="sourceDevice">The source device to set as the current source.</param>
void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice);
}
}
/// <summary>
/// Event arguments for the CurrentSourcesChanged event, providing details about the signal type and the previous and new sources.
/// </summary>
public class CurrentSourcesChangedEventArgs : EventArgs
{
/// <summary>
/// Gets the signal type for which the current source has changed.
/// </summary>
public eRoutingSignalType SignalType { get; }
/// <summary>
/// Gets the previous source for the signal type before the change occurred.
/// </summary>
public IRoutingSource PreviousSource { get; }
/// <summary>
/// Gets the new source for the signal type after the change occurred.
/// </summary>
public IRoutingSource NewSource { get; }
/// <summary>
/// Initializes a new instance of the CurrentSourcesChangedEventArgs class with the specified signal type, previous source, and new source.
/// </summary>
/// <param name="signalType">The signal type for which the current source has changed.</param>
/// <param name="previousSource">The previous source for the signal type before the change occurred.</param>
/// <param name="newSource">The new source for the signal type after the change occurred.</param>
public CurrentSourcesChangedEventArgs(eRoutingSignalType signalType, IRoutingSource previousSource, IRoutingSource newSource)
{
SignalType = signalType;
PreviousSource = previousSource;
NewSource = newSource;
}
}
}

View File

@@ -1,4 +1,5 @@
using PepperDash.Core;
using Newtonsoft.Json;
using PepperDash.Core;
namespace PepperDash.Essentials.Core;
@@ -11,6 +12,7 @@ public interface IRoutingOutputs : IKeyed
/// <summary>
/// Collection of Output Ports
/// </summary>
[JsonProperty("outputPorts")]
RoutingPortCollection<RoutingOutputPort> OutputPorts { get; }
}

View File

@@ -1,3 +1,4 @@
using PepperDash.Core;
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.Core;
@@ -5,7 +6,7 @@ namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines the contract for IRoutingSink
/// </summary>
public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange
public interface IRoutingSink : IRoutingInputs, IKeyName, ICurrentSources
{
}
@@ -20,10 +21,4 @@ public interface IRoutingSinkWithInputPort : IRoutingSink
RoutingInputPort CurrentInputPort { get; }
}
/// <summary>
/// Interface for routing sinks that have access to the current source information.
/// </summary>
public interface IRoutingSinkWithCurrentSources : IRoutingSink, ICurrentSources
{
}

View File

@@ -5,8 +5,9 @@
/// <summary>
/// Defines the contract for IRoutingSinkWithFeedback
/// </summary>
public interface IRoutingSinkWithFeedback : IRoutingSinkWithSwitching
public interface IRoutingSinkWithFeedback : IRoutingSink
{
}

View File

@@ -1,8 +1,10 @@
namespace PepperDash.Essentials.Core;
using PepperDash.Core;
namespace PepperDash.Essentials.Core;
/// <summary>
/// Marker interface to identify a device that acts as the origin of a signal path (<see cref="IRoutingOutputs"/>).
/// </summary>
public interface IRoutingSource : IRoutingOutputs
public interface IRoutingSource : IRoutingOutputs, IKeyName
{
}

View File

@@ -9,15 +9,15 @@ namespace PepperDash.Essentials.Core.Routing;
/// Manages routing feedback by subscribing to route changes on midpoint and sink devices,
/// tracing the route back to the original source, and updating the CurrentSourceInfo on sink devices.
/// </summary>
public class RoutingFeedbackManager: EssentialsDevice
public class RoutingFeedbackManager : EssentialsDevice
{
/// <summary>
/// Initializes a new instance of the <see cref="RoutingFeedbackManager"/> class.
/// </summary>
/// <param name="key">The unique key for this manager device.</param>
/// <param name="name">The name of this manager device.</param>
public RoutingFeedbackManager(string key, string name): base(key, name)
{
public RoutingFeedbackManager(string key, string name) : base(key, name)
{
AddPreActivationAction(SubscribeForMidpointFeedback);
AddPreActivationAction(SubscribeForSinkFeedback);
}
@@ -41,12 +41,12 @@ public class RoutingFeedbackManager: EssentialsDevice
/// </summary>
private void SubscribeForSinkFeedback()
{
var sinkDevices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
var sinkDevices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
foreach (var device in sinkDevices)
{
device.InputChanged += HandleSinkUpdate;
}
foreach (var device in sinkDevices)
{
device.InputChanged += HandleSinkUpdate;
}
}
/// <summary>
@@ -59,7 +59,7 @@ public class RoutingFeedbackManager: EssentialsDevice
{
try
{
var devices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
var devices = DeviceManager.AllDevices.OfType<IRoutingSinkWithInputPort>();
foreach (var device in devices)
{
@@ -96,13 +96,13 @@ public class RoutingFeedbackManager: EssentialsDevice
/// </summary>
/// <param name="destination">The destination sink device to update.</param>
/// <param name="inputPort">The currently selected input port on the destination device.</param>
private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort)
{
private void UpdateDestination(IRoutingSink destination, RoutingInputPort inputPort)
{
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this,destination?.Key, inputPort?.Key);
if(inputPort == null)
if (inputPort == null)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "Destination {destination} has not reported an input port yet", this,destination.Key);
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "Destination {destination} has not reported an input port yet", this, destination.Key);
return;
}
@@ -116,19 +116,10 @@ public class RoutingFeedbackManager: EssentialsDevice
if (firstTieLine == null)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No tieline found for inputPort {inputPort}. Clearing current source", this, inputPort);
var tempSourceListItem = new SourceListItem
{
SourceKey = "$transient",
Name = inputPort.Key,
};
destination.CurrentSourceInfo = tempSourceListItem; ;
destination.CurrentSourceInfoKey = "$transient";
return;
}
} catch (Exception ex)
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Error getting first tieline: {Exception}", this, ex);
return;
@@ -136,6 +127,7 @@ public class RoutingFeedbackManager: EssentialsDevice
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Getting source for first TieLine {tieLine}", this, firstTieLine);
TieLine sourceTieLine;
try
{
@@ -145,91 +137,34 @@ public class RoutingFeedbackManager: EssentialsDevice
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route found to source for inputPort {inputPort}. Clearing current source", this, inputPort);
var tempSourceListItem = new SourceListItem
{
SourceKey = "$transient",
Name = "None",
};
destination.CurrentSourceInfo = tempSourceListItem;
destination.CurrentSourceInfoKey = string.Empty;
destination.SetCurrentSource(sourceTieLine.Type, null);
return;
}
} catch(Exception ex)
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Error getting sourceTieLine: {Exception}", this, ex);
return;
}
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root TieLine {tieLine}", this, sourceTieLine);
// Does not handle combinable scenarios or other scenarios where a display might be part of multiple rooms yet.
var room = DeviceManager.AllDevices.OfType<IEssentialsRoom>().FirstOrDefault((r) => {
if(r is IHasMultipleDisplays roomMultipleDisplays)
{
return roomMultipleDisplays.Displays.Any(d => d.Value.Key == destination.Key);
}
if(r is IHasDefaultDisplay roomDefaultDisplay)
{
return roomDefaultDisplay.DefaultDisplay.Key == destination.Key;
}
return false;
});
if(room == null)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No room found for display {destination}", this, destination.Key);
return;
}
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found room {room} for destination {destination}", this, room.Key, destination.Key);
var sourceList = ConfigReader.ConfigObject.GetSourceListForKey(room.SourceListKey);
if (sourceList == null)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No source list found for source list key {key}. Unable to find source for tieLine {sourceTieLine}", this, room.SourceListKey, sourceTieLine);
return;
}
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found sourceList for room {room}", this, room.Key);
var sourceListItem = sourceList.FirstOrDefault(sli => {
//// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose,
// "SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}",
// this,
// sli.Key,
// sli.Value.SourceKey,
// sourceTieLine.SourcePort.ParentDevice.Key);
return sli.Value.SourceKey.Equals(sourceTieLine.SourcePort.ParentDevice.Key,StringComparison.InvariantCultureIgnoreCase);
});
var source = sourceListItem.Value;
var sourceKey = sourceListItem.Key;
if (source == null)
if (sourceTieLine.SourcePort.ParentDevice == null)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No source found for device {key}. Creating transient source for {destination}", this, sourceTieLine.SourcePort.ParentDevice.Key, destination);
var tempSourceListItem = new SourceListItem
{
SourceKey = "$transient",
Name = sourceTieLine.SourcePort.Key,
};
destination.SetCurrentSource(sourceTieLine.Type, null);
destination.CurrentSourceInfoKey = "$transient";
destination.CurrentSourceInfo = tempSourceListItem;
return;
}
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Got Source {@source} with key {sourceKey}", this, source, sourceKey);
destination.CurrentSourceInfoKey = sourceKey;
destination.CurrentSourceInfo = source;
if (sourceTieLine.SourcePort.ParentDevice is IRoutingSource sourceDevice)
{
destination.SetCurrentSource(sourceTieLine.Type, sourceDevice);
}
}
/// <summary>
@@ -249,13 +184,14 @@ public class RoutingFeedbackManager: EssentialsDevice
{
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLine Source device {sourceDevice} is midpoint", this, midpoint);
if(midpoint.CurrentRoutes == null || midpoint.CurrentRoutes.Count == 0)
if (midpoint.CurrentRoutes == null || midpoint.CurrentRoutes.Count == 0)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Midpoint {midpointKey} has no routes",this, midpoint.Key);
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Midpoint {midpointKey} has no routes", this, midpoint.Key);
return null;
}
var currentRoute = midpoint.CurrentRoutes.FirstOrDefault(route => {
var currentRoute = midpoint.CurrentRoutes.FirstOrDefault(route =>
{
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Checking {route} against {tieLine}", this, route, tieLine);
return route.OutputPort != null && route.InputPort != null && route.OutputPort?.Key == tieLine.SourcePort.Key && route.OutputPort?.ParentDevice.Key == tieLine.SourcePort.ParentDevice.Key;
@@ -269,9 +205,11 @@ public class RoutingFeedbackManager: EssentialsDevice
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found currentRoute {currentRoute} through {midpoint}", this, currentRoute, midpoint);
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => {
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl =>
{
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Checking {route} against {tieLine}", tl.DestinationPort.Key, currentRoute.InputPort.Key);
return tl.DestinationPort.Key == currentRoute.InputPort.Key && tl.DestinationPort.ParentDevice.Key == currentRoute.InputPort.ParentDevice.Key; });
return tl.DestinationPort.Key == currentRoute.InputPort.Key && tl.DestinationPort.ParentDevice.Key == currentRoute.InputPort.ParentDevice.Key;
});
if (nextTieLine != null)
{
@@ -292,13 +230,14 @@ public class RoutingFeedbackManager: EssentialsDevice
return tieLine;
}
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => tl.DestinationPort.Key == tieLine.SourcePort.Key && tl.DestinationPort.ParentDevice.Key == tieLine.SourcePort.ParentDevice.Key );
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => tl.DestinationPort.Key == tieLine.SourcePort.Key && tl.DestinationPort.ParentDevice.Key == tieLine.SourcePort.ParentDevice.Key);
if (nextTieLine != null)
{
return GetRootTieLine(nextTieLine);
}
} catch (Exception ex)
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Error walking tieLines: {Exception}", this, ex);
return null;

View File

@@ -1,9 +1,10 @@
using System.Collections.Generic;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common;
@@ -13,33 +14,19 @@ namespace PepperDash.Essentials.Devices.Common;
/// </summary>
public class GenericAudioOut : EssentialsDevice, IRoutingSink
{
/// <inheritdoc/>
public RoutingInputPort CurrentInputPort => AnyAudioIn;
public event SourceInfoChangeHandler CurrentSourceChange;
public string CurrentSourceInfoKey { get; set; }
public SourceListItem CurrentSourceInfo
{
get
{
return _CurrentSourceInfo;
}
set
{
if (value == _CurrentSourceInfo) return;
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, IRoutingSource> CurrentSources { get; private set; }
var handler = CurrentSourceChange;
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
if (handler != null)
handler(_CurrentSourceInfo, ChangeType.WillChange);
/// <inheritdoc />
public event System.EventHandler<CurrentSourcesChangedEventArgs> CurrentSourcesChanged;
_CurrentSourceInfo = value;
if (handler != null)
handler(_CurrentSourceInfo, ChangeType.DidChange);
}
}
SourceListItem _CurrentSourceInfo;
/// <summary>
/// Gets or sets the AnyAudioIn
@@ -56,6 +43,66 @@ public class GenericAudioOut : EssentialsDevice, IRoutingSink
{
AnyAudioIn = new RoutingInputPort(RoutingPortNames.AnyAudioIn, eRoutingSignalType.Audio,
eRoutingPortConnectionType.LineAudio, null, this);
CurrentSources = new Dictionary<eRoutingSignalType, IRoutingSource>
{
{ eRoutingSignalType.Audio, null },
};
CurrentSourceKeys = new Dictionary<eRoutingSignalType, string>
{
{ eRoutingSignalType.Audio, string.Empty },
};
}
/// <inheritdoc />
public virtual void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice)
{
foreach (eRoutingSignalType type in System.Enum.GetValues(typeof(eRoutingSignalType)))
{
var flagValue = System.Convert.ToInt32(type);
// Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag).
// (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set.
if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0)
{
this.LogDebug("Skipping {type}", type);
continue;
}
this.LogDebug("setting {type}", type);
var previousSource = CurrentSources[type];
if (signalType.HasFlag(type))
{
UpdateCurrentSources(type, previousSource, sourceDevice);
}
}
}
private void UpdateCurrentSources(eRoutingSignalType signalType, IRoutingSource previousSource, IRoutingSource sourceDevice)
{
if (CurrentSources.ContainsKey(signalType))
{
CurrentSources[signalType] = sourceDevice;
}
else
{
CurrentSources.Add(signalType, sourceDevice);
}
// Update the current source key for the specified signal type
if (CurrentSourceKeys.ContainsKey(signalType))
{
CurrentSourceKeys[signalType] = sourceDevice.Key;
}
else
{
CurrentSourceKeys.Add(signalType, sourceDevice.Key);
}
// Raise the CurrentSourcesChanged event
CurrentSourcesChanged?.Invoke(this, new CurrentSourcesChangedEventArgs(signalType, previousSource, sourceDevice));
}
#region IRoutingInputs Members
@@ -116,13 +163,20 @@ public class GenericAudioOutWithVolume : GenericAudioOut, IHasVolumeDevice
}
/// <summary>
/// Factory for creating GenericAudioOutWithVolume devices
/// </summary>
public class GenericAudioOutWithVolumeFactory : EssentialsDeviceFactory<GenericAudioOutWithVolume>
{
/// <summary>
/// Constructor for GenericAudioOutWithVolumeFactory
/// </summary>
public GenericAudioOutWithVolumeFactory()
{
TypeNames = new List<string>() { "genericaudiooutwithvolume" };
}
/// <inheritdoc />
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new GenericAudioOutWithVolumeFactory Device");

View File

@@ -48,48 +48,20 @@ public abstract class DisplayBase : EssentialsDevice, IDisplay, ICurrentSources,
/// </summary>
public event InputChangedEventHandler InputChanged;
/// <summary>
/// Event that is raised when the current source information changes.
/// </summary>
public event SourceInfoChangeHandler CurrentSourceChange;
/// <summary>
/// Gets or sets the CurrentSourceInfoKey
/// </summary>
public string CurrentSourceInfoKey { get; set; }
/// <summary>
/// Gets or sets the current source information for the display.
/// </summary>
public SourceListItem CurrentSourceInfo
{
get
{
return _CurrentSourceInfo;
}
set
{
if (value == _CurrentSourceInfo) return;
var handler = CurrentSourceChange;
handler?.Invoke(_CurrentSourceInfo, ChangeType.WillChange);
_CurrentSourceInfo = value;
handler?.Invoke(_CurrentSourceInfo, ChangeType.DidChange);
}
}
SourceListItem _CurrentSourceInfo;
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; private set; }
public Dictionary<eRoutingSignalType, IRoutingSource> CurrentSources { get; private set; }
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
/// <inheritdoc />
public event EventHandler CurrentSourcesChanged;
public event EventHandler<CurrentSourcesChangedEventArgs> CurrentSourcesChanged;
/// <summary>
/// Gets feedback indicating whether the display is currently cooling down after being powered off.
@@ -163,7 +135,7 @@ public abstract class DisplayBase : EssentialsDevice, IDisplay, ICurrentSources,
InputPorts = new RoutingPortCollection<RoutingInputPort>();
CurrentSources = new Dictionary<eRoutingSignalType, SourceListItem>
CurrentSources = new Dictionary<eRoutingSignalType, IRoutingSource>
{
{ eRoutingSignalType.Audio, null },
{ eRoutingSignalType.Video, null },
@@ -388,7 +360,7 @@ public abstract class DisplayBase : EssentialsDevice, IDisplay, ICurrentSources,
}
/// <inheritdoc />
public virtual void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
public virtual void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice)
{
foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType)))
{
@@ -403,35 +375,38 @@ public abstract class DisplayBase : EssentialsDevice, IDisplay, ICurrentSources,
this.LogDebug("setting {type}", type);
var previousSource = CurrentSources[type];
if (signalType.HasFlag(type))
{
UpdateCurrentSources(type, sourceListKey, sourceListItem);
UpdateCurrentSources(type, previousSource, sourceDevice);
}
}
// Raise the CurrentSourcesChanged event
CurrentSourcesChanged?.Invoke(this, EventArgs.Empty);
}
private void UpdateCurrentSources(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
private void UpdateCurrentSources(eRoutingSignalType signalType, IRoutingSource previousSource, IRoutingSource sourceDevice)
{
if (CurrentSources.ContainsKey(signalType))
{
CurrentSources[signalType] = sourceListItem;
CurrentSources[signalType] = sourceDevice;
}
else
{
CurrentSources.Add(signalType, sourceListItem);
CurrentSources.Add(signalType, sourceDevice);
}
// Update the current source key for the specified signal type
if (CurrentSourceKeys.ContainsKey(signalType))
{
CurrentSourceKeys[signalType] = sourceListKey;
CurrentSourceKeys[signalType] = sourceDevice.Key;
}
else
{
CurrentSourceKeys.Add(signalType, sourceListKey);
CurrentSourceKeys.Add(signalType, sourceDevice.Key);
}
// Raise the CurrentSourcesChanged event
CurrentSourcesChanged?.Invoke(this, new CurrentSourcesChangedEventArgs(signalType, previousSource, sourceDevice));
}
}

View File

@@ -14,14 +14,14 @@ namespace PepperDash.Essentials.Devices.Common.Generic;
/// </summary>
public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputPort, ICurrentSources
{
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; private set; }
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, IRoutingSource> CurrentSources { get; private set; }
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
/// <inheritdoc />
public event EventHandler CurrentSourcesChanged;
/// <inheritdoc />
public event EventHandler<CurrentSourcesChangedEventArgs> CurrentSourcesChanged;
/// <summary>
/// Initializes a new instance of the GenericSink class
@@ -36,7 +36,7 @@ public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputP
InputPorts.Add(inputPort);
CurrentSources = new Dictionary<eRoutingSignalType, SourceListItem>
CurrentSources = new Dictionary<eRoutingSignalType, IRoutingSource>
{
{ eRoutingSignalType.Audio, null },
{ eRoutingSignalType.Video, null },
@@ -49,36 +49,55 @@ public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputP
};
}
/// <inheritdoc />
public void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
{
foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType)))
{
var flagValue = Convert.ToInt32(type);
// Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag).
// (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set.
if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0)
{
this.LogDebug("Skipping {type}", type);
continue;
}
/// <inheritdoc />
public virtual void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice)
{
foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType)))
{
var flagValue = Convert.ToInt32(type);
// Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag).
// (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set.
if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0)
{
this.LogDebug("Skipping {type}", type);
continue;
}
this.LogDebug("setting {type}", type);
this.LogDebug("setting {type}", type);
if (signalType.HasFlag(type))
{
UpdateCurrentSources(type, sourceListKey, sourceListItem);
}
}
// Raise the CurrentSourcesChanged event
CurrentSourcesChanged?.Invoke(this, EventArgs.Empty);
}
var previousSource = CurrentSources[type];
private void UpdateCurrentSources(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem)
{
CurrentSources[signalType] = sourceListItem;
CurrentSourceKeys[signalType] = sourceListKey;
}
if (signalType.HasFlag(type))
{
UpdateCurrentSources(type, previousSource, sourceDevice);
}
}
}
private void UpdateCurrentSources(eRoutingSignalType signalType, IRoutingSource previousSource, IRoutingSource sourceDevice)
{
if (CurrentSources.ContainsKey(signalType))
{
CurrentSources[signalType] = sourceDevice;
}
else
{
CurrentSources.Add(signalType, sourceDevice);
}
// Update the current source key for the specified signal type
if (CurrentSourceKeys.ContainsKey(signalType))
{
CurrentSourceKeys[signalType] = sourceDevice.Key;
}
else
{
CurrentSourceKeys.Add(signalType, sourceDevice.Key);
}
// Raise the CurrentSourcesChanged event
CurrentSourcesChanged?.Invoke(this, new CurrentSourcesChangedEventArgs(signalType, previousSource, sourceDevice));
}
/// <summary>
/// Gets or sets the InputPorts

View File

@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Routing;
using PepperDash.Essentials.Devices.Common.Sources;
using Serilog.Events;
@@ -22,10 +25,19 @@ public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink
/// <summary>
/// The currently active input port, which for this device is always AnyVideoIn
/// This is used by the routing system to determine where to route video sources when this device is a destination
/// This is used by the routing system to determine where to route video sources when this device is a destination
/// </summary>
public RoutingInputPort CurrentInputPort => AnyVideoIn;
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, IRoutingSource> CurrentSources { get; private set; }
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
/// <inheritdoc />
public event EventHandler<CurrentSourcesChangedEventArgs> CurrentSourcesChanged;
#region IRoutingInputs Members
/// <summary>
@@ -47,15 +59,77 @@ public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink
{
(AnyVideoIn = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.None, 0, this))
};
CurrentSources = new Dictionary<eRoutingSignalType, IRoutingSource>
{
{ eRoutingSignalType.Audio, null },
{ eRoutingSignalType.Video, null },
};
CurrentSourceKeys = new Dictionary<eRoutingSignalType, string>
{
{ eRoutingSignalType.Audio, string.Empty },
{ eRoutingSignalType.Video, string.Empty },
};
}
/// <inheritdoc />
public virtual void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice)
{
foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType)))
{
var flagValue = Convert.ToInt32(type);
// Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag).
// (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set.
if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0)
{
this.LogDebug("Skipping {type}", type);
continue;
}
this.LogDebug("setting {type}", type);
var previousSource = CurrentSources[type];
if (signalType.HasFlag(type))
{
UpdateCurrentSources(type, previousSource, sourceDevice);
}
}
}
private void UpdateCurrentSources(eRoutingSignalType signalType, IRoutingSource previousSource, IRoutingSource sourceDevice)
{
if (CurrentSources.ContainsKey(signalType))
{
CurrentSources[signalType] = sourceDevice;
}
else
{
CurrentSources.Add(signalType, sourceDevice);
}
// Update the current source key for the specified signal type
if (CurrentSourceKeys.ContainsKey(signalType))
{
CurrentSourceKeys[signalType] = sourceDevice.Key;
}
else
{
CurrentSourceKeys.Add(signalType, sourceDevice.Key);
}
// Raise the CurrentSourcesChanged event
CurrentSourcesChanged?.Invoke(this, new CurrentSourcesChangedEventArgs(signalType, previousSource, sourceDevice));
}
#region IRunRouteAction Members
/// <summary>
/// Runs a route action for the specified route key and source list key. Optionally, a callback can be provided to be executed upon successful completion.
/// </summary>
/// <param name="routeKey"></param>
/// <param name="sourceListKey"></param>
/// </summary>
/// <param name="routeKey"></param>
/// <param name="sourceListKey"></param>
public void RunRouteAction(string routeKey, string sourceListKey)
{
RunRouteAction(routeKey, sourceListKey, null);

View File

@@ -1,6 +1,9 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.SoftCodec;
@@ -27,6 +30,15 @@ public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSinkWi
}
}
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, IRoutingSource> CurrentSources { get; private set; }
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
/// <inheritdoc />
public event System.EventHandler<CurrentSourcesChangedEventArgs> CurrentSourcesChanged;
/// <summary>
/// Initializes a new instance of the <see cref="GenericSoftCodec"/> class
/// </summary>
@@ -49,6 +61,18 @@ public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSinkWi
InputPorts.Add(inputPort);
}
CurrentSources = new Dictionary<eRoutingSignalType, IRoutingSource>
{
{ eRoutingSignalType.Audio, null },
{ eRoutingSignalType.Video, null },
};
CurrentSourceKeys = new Dictionary<eRoutingSignalType, string>
{
{ eRoutingSignalType.Audio, string.Empty },
{ eRoutingSignalType.Video, string.Empty },
};
if (!props.HasCameraInputs)
{
return;
@@ -62,6 +86,56 @@ public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSinkWi
}
}
/// <inheritdoc />
public virtual void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice)
{
foreach (eRoutingSignalType type in System.Enum.GetValues(typeof(eRoutingSignalType)))
{
var flagValue = System.Convert.ToInt32(type);
// Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag).
// (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set.
if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0)
{
this.LogDebug("Skipping {type}", type);
continue;
}
this.LogDebug("setting {type}", type);
var previousSource = CurrentSources[type];
if (signalType.HasFlag(type))
{
UpdateCurrentSources(type, previousSource, sourceDevice);
}
}
}
private void UpdateCurrentSources(eRoutingSignalType signalType, IRoutingSource previousSource, IRoutingSource sourceDevice)
{
if (CurrentSources.ContainsKey(signalType))
{
CurrentSources[signalType] = sourceDevice;
}
else
{
CurrentSources.Add(signalType, sourceDevice);
}
// Update the current source key for the specified signal type
if (CurrentSourceKeys.ContainsKey(signalType))
{
CurrentSourceKeys[signalType] = sourceDevice.Key;
}
else
{
CurrentSourceKeys.Add(signalType, sourceDevice.Key);
}
// Raise the CurrentSourcesChanged event
CurrentSourcesChanged?.Invoke(this, new CurrentSourcesChangedEventArgs(signalType, previousSource, sourceDevice));
}
/// <summary>
/// Gets or sets the InputPorts
/// </summary>

View File

@@ -69,7 +69,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// </summary>
public class CurrentSourcesStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Gets or sets the CurrentSourceKey
/// </summary>
@@ -81,6 +80,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// Gets or sets the CurrentSource
/// </summary>
[JsonProperty("currentSources")]
public Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; set; }
public Dictionary<eRoutingSignalType, IRoutingSource> CurrentSources { get; set; }
}
}

View File

@@ -1,7 +1,5 @@
using System;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
@@ -17,29 +15,22 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// </summary>
public IRunRouteAction RoutingDevice { get; private set; }
/// <summary>
/// Initializes a new instance of the RunRouteActionMessenger class
/// </summary>
/// <param name="key">The key.</param>
/// <param name="routingDevice">The routing device.</param>
/// <param name="messagePath">The message path.</param>
/// <exception cref="ArgumentNullException"></exception>
public RunRouteActionMessenger(string key, IRunRouteAction routingDevice, string messagePath)
: base(key, messagePath, routingDevice as IKeyName)
{
RoutingDevice = routingDevice ?? throw new ArgumentNullException("routingDevice");
if (RoutingDevice is IRoutingSink routingSink)
{
routingSink.CurrentSourceChange += RoutingSink_CurrentSourceChange;
}
}
private void RoutingSink_CurrentSourceChange(SourceListItem info, ChangeType type)
{
SendRoutingFullMessageObject();
}
/// <inheritdoc />
protected override void RegisterActions()
{
AddAction("/fullStatus", (id, content) => SendRoutingFullMessageObject(id));
AddAction("/routingStatus", (id, content) => SendRoutingFullMessageObject(id));
AddAction("/source", (id, content) =>
{
var c = content.ToObject<SourceSelectMessageContent>();
@@ -55,41 +46,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
RoutingDevice.RunRouteAction(c.SourceListItemKey, sourceListKey);
});
if (RoutingDevice is IRoutingSink sinkDevice)
{
sinkDevice.CurrentSourceChange += (o, a) => SendRoutingFullMessageObject();
}
}
/// <summary>
/// Helper method to update full status of the routing device
/// </summary>
private void SendRoutingFullMessageObject(string id = null)
{
if (RoutingDevice is IRoutingSink sinkDevice)
{
var sourceKey = sinkDevice.CurrentSourceInfoKey;
if (string.IsNullOrEmpty(sourceKey))
sourceKey = "none";
PostStatusMessage(new RoutingStateMessage
{
SelectedSourceKey = sourceKey
});
}
}
}
/// <summary>
/// Represents a RoutingStateMessage
/// </summary>
public class RoutingStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Gets or sets the SelectedSourceKey
/// </summary>
[JsonProperty("selectedSourceKey")]
public string SelectedSourceKey { get; set; }
}
}

View File

@@ -719,29 +719,6 @@ namespace PepperDash.Essentials.RoomBridges
}
}
if (room is IHasDefaultDisplay defDisplayRoom)
{
this.LogVerbose("Getting default display config");
configuration.DefaultDisplayKey = defDisplayRoom.DefaultDisplay.Key;
configuration.Destinations.Add(eSourceListItemDestinationTypes.defaultDisplay, defDisplayRoom.DefaultDisplay.Key);
}
if (room is IHasMultipleDisplays multiDisplayRoom)
{
this.LogVerbose("Getting multiple display config");
if (multiDisplayRoom.Displays == null)
{
this.LogVerbose("Displays collection is null");
}
else
{
this.LogVerbose("Displays collection exists");
configuration.Destinations = multiDisplayRoom.Displays.ToDictionary(kv => kv.Key, kv => kv.Value.Key);
}
}
if (room is IHasAccessoryDevices accRoom)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Getting accessory devices config", this);
@@ -1015,13 +992,6 @@ namespace PepperDash.Essentials.RoomBridges
[JsonProperty("defaultDisplayKey", NullValueHandling = NullValueHandling.Ignore)]
public string DefaultDisplayKey { get; set; }
/// <summary>
/// Gets or sets the destinations dictionary keyed by destination type
/// </summary>
[JsonProperty("destinations", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<eSourceListItemDestinationTypes, string> Destinations { get; set; }
/// <summary>
/// Gets or sets the EnvironmentalDevices
/// </summary>
@@ -1106,7 +1076,6 @@ namespace PepperDash.Essentials.RoomBridges
/// </summary>
public RoomConfiguration()
{
Destinations = new Dictionary<eSourceListItemDestinationTypes, string>();
EnvironmentalDevices = new List<EnvironmentalDeviceConfiguration>();
SourceList = new Dictionary<string, SourceListItem>();
TouchpanelKeys = new List<string>();

View File

@@ -1,881 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ClassDiagram MajorVersion="1" MinorVersion="1">
<Class Name="PepperDash.Essentials.EssentialsRoomVolumesConfig" Collapsed="true">
<Position X="19.25" Y="3.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAQAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAIAgA=</HashCode>
<FileName>Audio\EssentialsVolumeLevelConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.EssentialsVolumeLevelConfig" Collapsed="true">
<Position X="22.75" Y="3.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAEAAAAAAAAAAAAAAAAIAAAAgBAAAAAAAAAAA=</HashCode>
<FileName>Audio\EssentialsVolumeLevelConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.ConfigReader" Collapsed="true">
<Position X="26.25" Y="0.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAIAAAAAAAABAAAACAAIAAAAQAAAAAAAAAAEAA=</HashCode>
<FileName>Config\ConfigReader.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.DeviceFactory" Collapsed="true">
<Position X="29.75" Y="1.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAA=</HashCode>
<FileName>Config\DeviceFactory.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.EssentialsConfig" Collapsed="true">
<Position X="33.25" Y="1.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAEAAAAAQAIAAAAAAAACAAAAAAAAAAAAAAEAAAA=</HashCode>
<FileName>Config\EssentialsConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.SystemTemplateConfigs" Collapsed="true">
<Position X="33.25" Y="7.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Config\EssentialsConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.ConfigTieLine" Collapsed="true">
<Position X="31.5" Y="0.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAgEAAAAAAEAAAAAAAAAAAAAAAAAAAAAEAIAIA=</HashCode>
<FileName>Configuration ORIGINAL\ConfigTieLine.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Configuration" Collapsed="true">
<Position X="33.25" Y="0.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AEAAAAAAAAAAgQAAAAAAgAAAAAAAAAAAAAAgEDAADAQ=</HashCode>
<FileName>Configuration ORIGINAL\Configuration.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.ConfigSourceList" Collapsed="true">
<Position X="29.75" Y="0.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAQAAAAAAAQAAACAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Configuration ORIGINAL\Configuration.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.ConfigSourceItem" Collapsed="true">
<Position X="28" Y="0.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAgAAAAAAAEAAAAAAAAAAAAAAAAAgAAAAAAA=</HashCode>
<FileName>Configuration ORIGINAL\Configuration.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.ConfigInfo" Collapsed="true">
<Position X="24.5" Y="0.5" Width="1.5" />
<TypeIdentifier>
<HashCode>IQAAAAAEAAAAAAADAAACABQAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Configuration ORIGINAL\Configuration.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.SourceListConfigProperties" Collapsed="true">
<Position X="24.5" Y="7.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAEAAAAAAAAAAAIAAAAAgAAAIAAA=</HashCode>
<FileName>Configuration ORIGINAL\ConfigurationHelpers.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.DmFactory" Collapsed="true">
<Position X="31.5" Y="1.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAQAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAA=</HashCode>
<FileName>Configuration ORIGINAL\Factories\DmFactory.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.FactoryHelper" Collapsed="true">
<Position X="26.25" Y="3.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAEDAAKAA=</HashCode>
<FileName>Configuration ORIGINAL\Factories\FactoryHelper.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.IrOutPortConfig" Collapsed="true">
<Position X="31.5" Y="4.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAABAAAA=</HashCode>
<FileName>Configuration ORIGINAL\Factories\FactoryHelper.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.ControlSystem" Collapsed="true">
<Position X="35" Y="0.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAoAgAAIAEAACAAEAIEIBICAAAAAAABAAAAAAAAAAAA=</HashCode>
<FileName>ControlSystem.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Amplifier" Collapsed="true" BaseTypeListCollapsed="true">
<Position X="21" Y="0.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAA=</HashCode>
<FileName>Devices\Amplifier.cs</FileName>
</TypeIdentifier>
<Lollipop Position="0.2" Collapsed="true" />
</Class>
<Class Name="PepperDash.Essentials.CotijaConfig" Collapsed="true">
<Position X="19.25" Y="1.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\CotijaConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.CotijaDdvc01RoomBridgePropertiesConfig" Collapsed="true">
<Position X="22.75" Y="1.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\CotijaConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.CotijaSystemController" Collapsed="true">
<Position X="24.5" Y="1.5" Width="1.5" />
<TypeIdentifier>
<HashCode>CAAABAABAgACCAKGBIAAEyBAFAAACYSAgIAAAAJkAAA=</HashCode>
<FileName>Room\Cotija\CotijaSystemController.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.CotijaBridgeBase" Collapsed="true">
<Position X="9.5" Y="3.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAQIQAAAAAAAAgAAAAAAQAAAAAAAAAAAAAAAABAAAAA=</HashCode>
<FileName>Room\Cotija\RoomBridges\CotijaBridgeBase.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.CotijaEssentialsHuddleSpaceRoomBridge" Collapsed="true">
<Position X="8.25" Y="5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAIAAAAIABAAkgAABgIAAAAAAAAAEAAAAAAgBAAACA=</HashCode>
<FileName>Room\Cotija\RoomBridges\CotijaEssentialsHuddleSpaceRoomBridge.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.SourceSelectMessageContent" Collapsed="true">
<Position X="26.25" Y="7.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\RoomBridges\CotijaEssentialsHuddleSpaceRoomBridge.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.EssentialsHuddleSpaceRoom" Collapsed="true">
<Position X="0.5" Y="9" Width="1.5" />
<TypeIdentifier>
<HashCode>iQFBQAIAAgAAgQAEQACAMABAAABAACAQUAAQAAgCgBA=</HashCode>
<FileName>Room\Types\EssentialsHuddleSpaceRoom.cs</FileName>
</TypeIdentifier>
<Lollipop Position="0.2" />
</Class>
<Class Name="PepperDash.Essentials.EssentialsHuddleVtc1Room" Collapsed="true">
<Position X="2.75" Y="9" Width="1.5" />
<TypeIdentifier>
<HashCode>iQFBQAIAIgQMgQAEQAigMABAAABAADIwUACQAAgCgTE=</HashCode>
<FileName>Room\Types\EssentialsHuddleVtc1Room.cs</FileName>
</TypeIdentifier>
<Lollipop Position="0.2" />
</Class>
<Class Name="PepperDash.Essentials.EssentialsPresentationRoom" Collapsed="true">
<Position X="5" Y="9" Width="1.5" />
<TypeIdentifier>
<HashCode>iQAEAAIACkAAAQAOQASgMAAJAABgAAAQQAAQAAgCgBA=</HashCode>
<FileName>Room\Types\EssentialsPresentationRoom.cs</FileName>
</TypeIdentifier>
<Lollipop Position="0.2" />
</Class>
<Class Name="PepperDash.Essentials.EssentialsRoomBase" Collapsed="true">
<Position X="2.75" Y="7.5" Width="1.5" />
<TypeIdentifier>
<HashCode>gQAAEAICECgCAQAEAAAUIwIyAAAAgACiAgAAAQECAgA=</HashCode>
<FileName>Room\Types\EssentialsRoomBase.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.CrestronTouchpanelPropertiesConfig" Collapsed="true">
<Position X="26.25" Y="1.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AICCIAAIGIAAAAAgCAAIAEAAAAAAAAAAAAEAIAAAAAA=</HashCode>
<FileName>UI\CrestronTouchpanelPropertiesConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.UiSetupPropertiesConfig" Collapsed="true">
<Position X="19.25" Y="8.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>UI\CrestronTouchpanelPropertiesConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.EssentialsTouchpanelController" Collapsed="true">
<Position X="21" Y="3.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAIAEAAAAAAAAAAAAAAAAEAAAAACKIBAAgACAAAAAA=</HashCode>
<FileName>UI\EssentialsTouchpanelController.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.HttpLogoServer" Collapsed="true">
<Position X="19.25" Y="4.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAQAAAACAAAAABAAAAAAAAAABAAAAAAAAAAkAAAAA=</HashCode>
<FileName>UI\HttpLogoServer.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.UIBoolJoin" Collapsed="true">
<Position X="35" Y="7.5" Width="1.5" />
<TypeIdentifier>
<HashCode>j+jWCNqEIGzi4UTaTgyn37kpncQJK7L42VMLmMgTE5A=</HashCode>
<FileName>UI\JoinConstants\UIBoolJoin.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.UISmartObjectJoin" Collapsed="true">
<Position X="21" Y="8.5" Width="1.5" />
<TypeIdentifier>
<HashCode>BAggBIABCQAAGAAQAAAACAACAAAAAAAAAAIAAAAAAAA=</HashCode>
<FileName>UI\JoinConstants\UISmartObjectJoin.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.UIStringJoin" Collapsed="true">
<Position X="22.75" Y="8.5" Width="1.5" />
<TypeIdentifier>
<HashCode>BkBgIAgAAggOQAFGAYQIABACgCEBjkSQAUEAASIABCE=</HashCode>
<FileName>UI\JoinConstants\UIStringlJoin.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.UIUshortJoin" Collapsed="true">
<Position X="24.5" Y="8.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAQAAAACAAAACAEAAIAAAAAAIABAIAAAABAAAEAAAAA=</HashCode>
<FileName>UI\JoinConstants\UIUshortJoin.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.SubpageReferenceListActivityItem" Collapsed="true">
<Position X="28" Y="7.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA=</HashCode>
<FileName>UI\SubpageReferenceListActivityItem.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.SubpageReferenceListButtonAndModeItem" Collapsed="true">
<Position X="29.75" Y="7.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA=</HashCode>
<FileName>UI\SubpageReferenceListCallStagingItem.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.SubpageReferenceListSourceItem" Collapsed="true">
<Position X="31.5" Y="7.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAABAAAAAAAAAAAAAAAAAAAAgAAAAACAAAAAABgAAA=</HashCode>
<FileName>UI\SubpageReferenceListSourceItem.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.PanelDriverBase" Collapsed="true">
<Position X="8.75" Y="0.5" Width="1.5" />
<TypeIdentifier>
<HashCode>CAAIkAAAAAAQEAAAEAACAAAAAIAEABAAAgAACAAAAAA=</HashCode>
<FileName>UIDrivers\enums and base.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.EssentialsPanelMainInterfaceDriver" Collapsed="true">
<Position X="1" Y="1.75" Width="1.5" />
<TypeIdentifier>
<HashCode>ABAAAAAAAhgAAAAAEAAAAAAAAIAEAAAAAAAAAAAAAAA=</HashCode>
<FileName>UIDrivers\Essentials\EssentialsPanelMainInterfaceDriver.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.EssentialsPresentationPanelAvFunctionsDriver" Collapsed="true">
<Position X="3.25" Y="1.75" Width="1.5" />
<TypeIdentifier>
<HashCode>gTEAIIAiCggFNCQ4EA4AWBkAKJCEqAAOS4CKMAQQJQA=</HashCode>
<FileName>UIDrivers\Essentials\EssentialsPresentationPanelAvFunctionsDriver.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.EssentialsHuddlePanelAvFunctionsDriver" Collapsed="true">
<Position X="7.75" Y="1.75" Width="1.5" />
<TypeIdentifier>
<HashCode>gRQAIICuAghENHQpEA4IWCkBMJDEsEAEC4CAMARQIBA=</HashCode>
<FileName>UIDrivers\EssentialsHuddle\EssentialsHuddlePanelAvFunctionsDriver.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.EssentialsHuddleVtc1PanelAvFunctionsDriver" Collapsed="true">
<Position X="12.25" Y="1.75" Width="1.5" />
<TypeIdentifier>
<HashCode>GRwAIYC+oghAeHStEDAIWCdBMADEsBAcDwCAMARYIBg=</HashCode>
<FileName>UIDrivers\EssentialsHuddleVTC\EssentialsHuddleVtc1PanelAvFunctionsDriver.cs</FileName>
</TypeIdentifier>
<Lollipop Position="0.2" />
</Class>
<Class Name="PepperDash.Essentials.JoinedSigInterlock" Collapsed="true">
<Position X="19.25" Y="5.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAQgAIAAAAAAAAAEAAAAAQAAAAEAAAAAAAAEAAAgAA=</HashCode>
<FileName>UIDrivers\JoinedSigInterlock.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.SingleSubpageModalAndBackDriver" Collapsed="true">
<Position X="14.5" Y="1.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAEAAAQAAABIAEAAAAAAAAAAAAAAA=</HashCode>
<FileName>UIDrivers\Page Drivers\SingleSubpageModalAndBackDriver.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.SingleSubpageModalDriver" Collapsed="true">
<Position X="16.75" Y="1.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAEAAAAAAABAAEAAAAAAAAAAAAAAA=</HashCode>
<FileName>UIDrivers\Page Drivers\SingleSubpageModalDriver.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.SigInterlock" Collapsed="true">
<Position X="19.25" Y="7.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAQAAIAAAAAAAAIEAAAAAAAAAAEAAAAAAAAEAAAgAA=</HashCode>
<FileName>UIDrivers\SigInterlock.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.SmartObjectRoomsList" Collapsed="true">
<Position X="21" Y="7.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAACAAACBAAAAAAAAAAAAAAAAAAAAIAAAAAAQA=</HashCode>
<FileName>UIDrivers\SmartObjectRoomsList.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.SmartObjectRoomsListItem" Collapsed="true">
<Position X="22.75" Y="7.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAQAAAAAAAAAAAABAAQAAAAAAAAAACAAEAAAAAAAA=</HashCode>
<FileName>UIDrivers\SmartObjectRoomsList.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.VolumeDeviceChangeEventArgs" Collapsed="true">
<Position X="26.25" Y="8.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAABAAAAAEAAAAAAAAAAAAAAAAAAAQAAAAAAAAA=</HashCode>
<FileName>UIDrivers\VolumeAndSourceChangeArgs.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase" Collapsed="true">
<Position X="8.25" Y="9.5" Width="1.5" />
<TypeIdentifier>
<HashCode>IAGigRBpCgZwAIMSBBIbIgAAImAPtEBiAAgECpJgKQo=</HashCode>
<FileName>OTHER\Fusion\EssentialsHuddleSpaceFusionSystemControllerBase.cs</FileName>
</TypeIdentifier>
<Lollipop Position="0.2" />
</Class>
<Class Name="PepperDash.Essentials.Fusion.FusionRoomExtensions" Collapsed="true">
<Position X="31.5" Y="3.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAACgAIQAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\EssentialsHuddleSpaceFusionSystemControllerBase.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.FusionStaticAssetExtensions" Collapsed="true">
<Position X="35" Y="3.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAQAAACAAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\EssentialsHuddleSpaceFusionSystemControllerBase.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.EssentialsHuddleVtc1FusionController" Collapsed="true">
<Position X="8.25" Y="11" Width="1.5" />
<TypeIdentifier>
<HashCode>AACAAAAAAgAAAIAAAAAAAAAgIBAQAAAAAAAAAAAAAQA=</HashCode>
<FileName>OTHER\Fusion\EssentialsHuddleVtc1FusionController.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.ScheduleChangeEventArgs" Collapsed="true">
<Position X="33.25" Y="6.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionEventHandlers.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.MeetingChangeEventArgs" Collapsed="true">
<Position X="26.25" Y="5.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionEventHandlers.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.ProcessorProgReg" Collapsed="true">
<Position X="19.25" Y="6.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionProcessorQueries.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.ProcessorProgramItem" Collapsed="true">
<Position X="35" Y="5.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAEAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionProcessorQueries.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.FusionRoomGuids" Collapsed="true">
<Position X="33.25" Y="3.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAICAAAAAAAAAAAAAAAAAACAAAATQAAAAAAAABAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.FusionOccupancySensorAsset" Collapsed="true">
<Position X="29.75" Y="3.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAQAQAAAAAAAAQAAAAAAQAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.FusionAsset" Collapsed="true">
<Position X="28" Y="3.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAQAQAAAAAAAAQAAAAAAQAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.RoomSchedule" Collapsed="true">
<Position X="31.5" Y="6.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.LocalTimeRequest" Collapsed="true">
<Position X="24.5" Y="5.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.RequestSchedule" Collapsed="true">
<Position X="22.75" Y="6.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAACACAAAAQAAAAAAAAAAAAAAAAAAAACAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.RequestAction" Collapsed="true">
<Position X="21" Y="6.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAACAAAgAAQAAAAAAAAAAAAAAAAAAIAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.ActionResponse" Collapsed="true">
<Position X="19.25" Y="0.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAgAAQAAAAAAAAAAAAAAAAAAIAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.Parameter" Collapsed="true">
<Position X="33.25" Y="5.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.ScheduleResponse" Collapsed="true">
<Position X="35" Y="6.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAACAAAAAAQAAAAAAAAAAgAAAAAAAAAAABAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.Event" Collapsed="true">
<Position X="24.5" Y="3.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AABCAAAAYEAAEBIBAIAAJAQAQKAYAAAIEAAAEAACCgg=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.Resources" Collapsed="true">
<Position X="26.25" Y="6.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.Rooms" Collapsed="true">
<Position X="29.75" Y="6.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.Room" Collapsed="true">
<Position X="28" Y="6.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAgAAAAAAAAAQAAAAAAAAAAAAAAAAEAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.Attendees" Collapsed="true">
<Position X="22.75" Y="0.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAABAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.Required" Collapsed="true">
<Position X="24.5" Y="6.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.Optional" Collapsed="true">
<Position X="31.5" Y="5.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.MeetingType" Collapsed="true">
<Position X="28" Y="5.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.MeetingTypes" Collapsed="true">
<Position X="29.75" Y="5.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.LiveMeeting" Collapsed="true">
<Position X="21" Y="5.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAgAQAAIAAAAAAACAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Fusion.LiveMeetingURL" Collapsed="true">
<Position X="22.75" Y="5.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>OTHER\Fusion\FusionRviDataClasses.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.DDVC01RoomPropertiesConfig" Collapsed="true">
<Position X="5" Y="6.25" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAIAgAAQAAAAAAAAEAAAAAAA=</HashCode>
<FileName>Room\Config\DDVC01RoomPropertiesConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.DDVC01SpeedDial" Collapsed="true">
<Position X="28" Y="1.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAEAAAAQAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Config\DDVC01RoomPropertiesConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsHuddleRoomPropertiesConfig" Collapsed="true">
<Position X="2.75" Y="4.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAgAAAAAAAAAAACAAAAAABAAAAAAAAABA=</HashCode>
<FileName>Room\Config\EssentialsHuddleRoomPropertiesConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsHuddleVtc1PropertiesConfig" Collapsed="true">
<Position X="5" Y="4.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAgAAAAAAAAAAACIAAAAABAAAAAAAAABA=</HashCode>
<FileName>Room\Config\EssentialsHuddleVtc1PropertiesConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsPresentationRoomPropertiesConfig" Collapsed="true">
<Position X="0.5" Y="4.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAACAIAAAAAAAAAAABAACAAAAAABAAAAAAAAABA=</HashCode>
<FileName>Room\Config\EssentialsPresentationPropertiesConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsRoomConfig" Collapsed="true">
<Position X="26.25" Y="2.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAEAAAAAA=</HashCode>
<FileName>Room\Config\EssentialsRoomConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsRoomPropertiesConfig" Collapsed="true">
<Position X="2.75" Y="3.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AABAEQAAAAEoAAEEAAQAAAACAAAAAAgggAAAAQAAAgA=</HashCode>
<FileName>Room\Config\EssentialsRoomConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsLightingPropertiesConfig" Collapsed="true">
<Position X="19.25" Y="2.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Config\EssentialsRoomConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsRoomMicrophonePrivacyConfig" Collapsed="true">
<Position X="31.5" Y="2.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAEAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Config\EssentialsRoomConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsHelpPropertiesConfig" Collapsed="true">
<Position X="35" Y="1.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAgABAAAAAIAAAAAA=</HashCode>
<FileName>Room\Config\EssentialsRoomConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsOneButtonMeetingPropertiesConfig" Collapsed="true">
<Position X="22.75" Y="2.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Config\EssentialsRoomConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsRoomAddressPropertiesConfig" Collapsed="true">
<Position X="24.5" Y="2.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAQAAAgAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Config\EssentialsRoomConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsLogoPropertiesConfig" Collapsed="true">
<Position X="21" Y="2.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIACQAAAAAAAAA=</HashCode>
<FileName>Room\Config\EssentialsRoomConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsRoomOccSensorConfig" Collapsed="true">
<Position X="33.25" Y="2.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AACAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Config\EssentialsRoomConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsRoomTechConfig" Collapsed="true">
<Position X="35" Y="2.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAA=</HashCode>
<FileName>Room\Config\EssentialsRoomConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsRoomEmergencyConfig" Collapsed="true">
<Position X="28" Y="2.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAABAACAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Config\EssentialsRoomEmergencyConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Config.EssentialsRoomEmergencyTriggerConfig" Collapsed="true">
<Position X="29.75" Y="2.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAABQAAAAAAAAA=</HashCode>
<FileName>Room\Config\EssentialsRoomEmergencyConfig.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Cotija.CotijaDdvc01DeviceBridge" Collapsed="true" BaseTypeListCollapsed="true">
<Position X="21" Y="1.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AUAAAAgAAAACwAYAAAAAAAEAAAAAAAAAIcAAAEAAAAA=</HashCode>
<FileName>Room\Cotija\CotijaDdvc01DeviceBridge.cs</FileName>
</TypeIdentifier>
<Lollipop Position="0.2" Collapsed="true" />
</Class>
<Class Name="PepperDash.Essentials.Room.Cotija.IChannelExtensions" Collapsed="true">
<Position X="21" Y="4.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\DeviceTypeInterfaces\IChannelExtensions.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Cotija.IColorExtensions" Collapsed="true">
<Position X="22.75" Y="4.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\DeviceTypeInterfaces\IColorExtensions.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Cotija.IDPadExtensions" Collapsed="true">
<Position X="24.5" Y="4.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\DeviceTypeInterfaces\IDPadExtensions.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Cotija.IDvrExtensions" Collapsed="true">
<Position X="26.25" Y="4.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\DeviceTypeInterfaces\IDvrExtensions.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Cotija.INumericExtensions" Collapsed="true">
<Position X="28" Y="4.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\DeviceTypeInterfaces\INumericExtensions.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Cotija.IPowerExtensions" Collapsed="true">
<Position X="29.75" Y="4.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\DeviceTypeInterfaces\IPowerExtensions.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Cotija.ISetTopBoxControlsExtensions" Collapsed="true">
<Position X="33.25" Y="4.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\DeviceTypeInterfaces\ISetTopBoxControlsExtensions.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Cotija.ITransportExtensions" Collapsed="true">
<Position X="35" Y="4.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\DeviceTypeInterfaces\ITransportExtensions.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.Room.Cotija.CotijaDdvc01RoomBridge" Collapsed="true">
<Position X="10.5" Y="5" Width="1.5" />
<TypeIdentifier>
<HashCode>gACIAAAAAAICAFAAAACAAAEIIAAAAAQAQAAAABAAAAA=</HashCode>
<FileName>Room\Cotija\RoomBridges\CotijaDdvc01RoomBridge.cs</FileName>
</TypeIdentifier>
<Lollipop Position="0.2" />
</Class>
<Class Name="PepperDash.Essentials.Room.EssentialsRoomEmergencyBase" Collapsed="true">
<Position X="8.25" Y="6.5" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Emergency\EsentialsRoomEmergencyContactClosure.cs</FileName>
</TypeIdentifier>
<Lollipop Position="0.2" />
</Class>
<Class Name="PepperDash.Essentials.Room.EssentialsRoomEmergencyContactClosure" Collapsed="true">
<Position X="8.25" Y="7.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAASBAAAAAAAAAAAAAABAAAAAAAEAA=</HashCode>
<FileName>Room\Emergency\EsentialsRoomEmergencyContactClosure.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.UIDrivers.EssentialsHuddleTechPageDriver" Collapsed="true">
<Position X="5.5" Y="1.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAQAAAAAiACDAAAGCAACgAIAABECBAgQAAAAQAAAAA=</HashCode>
<FileName>UIDrivers\EssentialsHuddle\EssentialsHuddleTechPageDriver.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="PepperDash.Essentials.UIDrivers.VC.EssentialsVideoCodecUiDriver" Collapsed="true">
<Position X="10" Y="1.75" Width="1.5" />
<TypeIdentifier>
<HashCode>XAASAoAiAagwAcBAGAUURWQEOHQFAKCmAABCNSSEDPA=</HashCode>
<FileName>UIDrivers\VC\EssentialsVideoCodecUiDriver.cs</FileName>
</TypeIdentifier>
</Class>
<Interface Name="PepperDash.Essentials.IHasCurrentSourceInfoChange" Collapsed="true">
<Position X="22.75" Y="9.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Types\EssentialsRoomBase.cs</FileName>
</TypeIdentifier>
</Interface>
<Interface Name="PepperDash.Essentials.IAVDriver" Collapsed="true">
<Position X="19.25" Y="9.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAgAAAAAAAAACBAAACAAAAIBAAAEAAAEAgAAAAAAIAA=</HashCode>
<FileName>UIDrivers\EssentialsHuddleVTC\EssentialsHuddleVtc1PanelAvFunctionsDriver.cs</FileName>
</TypeIdentifier>
</Interface>
<Interface Name="PepperDash.Essentials.Room.Cotija.IDelayedConfiguration" Collapsed="true">
<Position X="21" Y="9.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\Interfaces.cs</FileName>
</TypeIdentifier>
</Interface>
<Enum Name="PepperDash.Essentials.eShutdownType" Collapsed="true">
<Position X="29.75" Y="10.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAQAAAAAAACAAAAAAAAAAAAAAAAAAAAAEACAA=</HashCode>
<FileName>Room\Types\EssentialsRoomBase.cs</FileName>
</TypeIdentifier>
</Enum>
<Enum Name="PepperDash.Essentials.eVacancyMode" Collapsed="true">
<Position X="31.5" Y="10.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAACAAAAAAAAAAAAAAAAACAAAAAAAAAAEAAAA=</HashCode>
<FileName>Room\Types\EssentialsRoomBase.cs</FileName>
</TypeIdentifier>
</Enum>
<Enum Name="PepperDash.Essentials.eWarmingCoolingMode" Collapsed="true">
<Position X="33.25" Y="10.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAIEAAAA=</HashCode>
<FileName>Room\Types\EssentialsRoomBase.cs</FileName>
</TypeIdentifier>
</Enum>
<Enum Name="PepperDash.Essentials.eAvSubpageType" Collapsed="true">
<Position X="24.5" Y="10.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AIAAAAAAAAAAAAAAAhBAAAAAAAAAAACYAAAAAAAAAAA=</HashCode>
<FileName>UIDrivers\enums and base.cs</FileName>
</TypeIdentifier>
</Enum>
<Enum Name="PepperDash.Essentials.eAvSourceSubpageType" Collapsed="true">
<Position X="22.75" Y="10.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAABAAAAAAAAAAAAAAACAAAEAAAAAAAAA=</HashCode>
<FileName>UIDrivers\enums and base.cs</FileName>
</TypeIdentifier>
</Enum>
<Enum Name="PepperDash.Essentials.eCommonSubpageType" Collapsed="true">
<Position X="28" Y="10.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAEAAAAAAAAAIAAAAAAAAQAAAAQAAAAAAAAAAA=</HashCode>
<FileName>UIDrivers\enums and base.cs</FileName>
</TypeIdentifier>
</Enum>
<Enum Name="PepperDash.Essentials.eAvSmartObjects" Collapsed="true">
<Position X="21" Y="10.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAABAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>UIDrivers\enums and base.cs</FileName>
</TypeIdentifier>
</Enum>
<Enum Name="PepperDash.Essentials.eCommonSmartObjects" Collapsed="true">
<Position X="26.25" Y="10.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>UIDrivers\enums and base.cs</FileName>
</TypeIdentifier>
</Enum>
<Enum Name="PepperDash.Essentials.ChangeType" Collapsed="true">
<Position X="19.25" Y="10.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAgCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>UIDrivers\VolumeAndSourceChangeArgs.cs</FileName>
</TypeIdentifier>
</Enum>
<Delegate Name="PepperDash.Essentials.PressAndHoldAction" Collapsed="true">
<Position X="19.25" Y="11.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
<FileName>Room\Cotija\RoomBridges\CotijaEssentialsHuddleSpaceRoomBridge.cs</FileName>
</TypeIdentifier>
</Delegate>
<Delegate Name="PepperDash.Essentials.SourceInfoChangeHandler" Collapsed="true">
<Position X="21" Y="11.75" Width="1.5" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAACAAACAAAAAAAAAAAAAAAAAAACAAAAA=</HashCode>
<FileName>UIDrivers\VolumeAndSourceChangeArgs.cs</FileName>
</TypeIdentifier>
</Delegate>
<Font Name="Segoe UI" Size="9" />
</ClassDiagram>

View File

@@ -5,7 +5,6 @@ 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;
@@ -28,10 +27,8 @@ namespace PepperDash.Essentials;
/// provides methods for platform determination, configuration loading, and system teardown.</remarks>
public class ControlSystem : CrestronControlSystem, ILoadConfig
{
private HttpLogoServer LogoServer;
private Timer _startTimer;
private ManualResetEventSlim _initializeEvent;
private Timer startTimer;
private ManualResetEventSlim initializeEvent;
private const long StartupTime = 500;
/// <summary>
@@ -79,16 +76,16 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig
{
Debug.LogMessage(LogEventLevel.Debug, "******************* Initializing System **********************");
_startTimer = new Timer(StartSystem, preventInitializationComplete, StartupTime, Timeout.Infinite);
startTimer = new Timer(StartSystem, preventInitializationComplete, StartupTime, Timeout.Infinite);
_initializeEvent = new ManualResetEventSlim(false);
initializeEvent = new ManualResetEventSlim(false);
DeviceManager.AllDevicesRegistered += (o, a) =>
{
_initializeEvent.Set();
initializeEvent.Set();
};
_initializeEvent.Wait(30000);
initializeEvent.Wait(30000);
Debug.LogMessage(LogEventLevel.Debug, "******************* System Initialization Complete **********************");
@@ -96,7 +93,7 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig
}
else
{
_startTimer = new Timer(StartSystem, preventInitializationComplete, StartupTime, Timeout.Infinite);
startTimer = new Timer(StartSystem, preventInitializationComplete, StartupTime, Timeout.Infinite);
}
}
@@ -357,7 +354,6 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig
{
LoadDevices();
LoadRooms();
LoadLogoServer();
DeviceManager.ActivateAll();
@@ -495,67 +491,6 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig
}
/// <summary>
/// Fires up a logo server if not already running
/// </summary>
void LoadLogoServer()
{
if (ConfigReader.ConfigObject.Rooms == null)
{
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");
}
}
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<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"));
}
catch
{
Debug.LogMessage(LogEventLevel.Information, "Unable to find logo information in any room config");
return false;
}
}
private static void LoadAssets()
{

View File

@@ -1,123 +0,0 @@
using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharp.Net.Http;
using PepperDash.Core;
using Serilog.Events;
namespace PepperDash.Essentials;
public class HttpLogoServer
{
/// <summary>
///
/// </summary>
readonly HttpServer _server;
/// <summary>
///
/// </summary>
readonly string _fileDirectory;
/// <summary>
///
/// </summary>
public static Dictionary<string, string> ExtensionContentTypes;
/// <summary>
///
/// </summary>
/// <param name="port"></param>
/// <param name="directory"></param>
public HttpLogoServer(int port, string directory)
{
ExtensionContentTypes = new Dictionary<string, string>
{
//{ ".css", "text/css" },
//{ ".htm", "text/html" },
//{ ".html", "text/html" },
{ ".jpg", "image/jpeg" },
{ ".jpeg", "image/jpeg" },
//{ ".js", "application/javascript" },
//{ ".json", "application/json" },
//{ ".map", "application/x-navimap" },
{ ".pdf", "application/pdf" },
{ ".png", "image/png" },
//{ ".txt", "text/plain" },
};
_server = new HttpServer {Port = port};
_fileDirectory = directory;
_server.OnHttpRequest += Server_OnHttpRequest;
_server.Open();
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
}
/// <summary>
///
/// </summary>
void Server_OnHttpRequest(object sender, OnHttpRequestArgs args)
{
var path = args.Request.Path;
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "HTTP Request with path: '{requestPath:l}'", args.Request.Path);
try
{
if (File.Exists(_fileDirectory + path))
{
var filePath = path.Replace('/', '\\');
var localPath = string.Format(@"{0}{1}", _fileDirectory, filePath);
Debug.LogMessage(LogEventLevel.Verbose, "HTTP Logo Server attempting to find file: '{localPath:l}'", localPath);
if (File.Exists(localPath))
{
args.Response.Header.ContentType = GetContentType(new FileInfo(localPath).Extension);
args.Response.ContentStream = new FileStream(localPath, FileMode.Open, FileAccess.Read);
}
else
{
Debug.LogMessage(LogEventLevel.Verbose, "HTTP Logo Server Cannot find file '{localPath:l}'", localPath);
args.Response.ContentString = string.Format("Not found: '{0}'", filePath);
args.Response.Code = 404;
}
}
else
{
Debug.LogMessage(LogEventLevel.Verbose, "HTTP Logo Server: '{file:l}' does not exist", _fileDirectory + path);
args.Response.ContentString = string.Format("Not found: '{0}'", _fileDirectory + path);
args.Response.Code = 404;
}
}
catch (Exception ex)
{
Debug.LogMessage(LogEventLevel.Error, "Exception getting file: {exception}", ex.Message, ex.StackTrace);
Debug.LogMessage(LogEventLevel.Verbose, "Stack Trace: {stackTrace}", ex.StackTrace);
args.Response.Code = 400;
args.Response.ContentString = string.Format("invalid request");
}
}
/// <summary>
///
/// </summary>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType == eProgramStatusEventType.Stopping)
_server.Close();
}
/// <summary>
///
/// </summary>
/// <param name="extension"></param>
/// <returns></returns>
public static string GetContentType(string extension)
{
var type = ExtensionContentTypes.ContainsKey(extension) ? ExtensionContentTypes[extension] : "text/plain";
return type;
}
}