Compare commits

..

11 Commits

Author SHA1 Message Date
Andrew Welker
2f3babef91 build(force-patch): add props to package with Essentials version 2025-04-30 11:01:10 -05:00
Andrew Welker
ec1b99498a feat: set base MinimumEssentialsFrameworkVersion to Essentials version 2025-04-30 09:51:55 -05:00
Neil Dorin
2368f0c8cc Merge pull request #1262 from PepperDash/temp-to-dev 2025-04-28 11:37:01 -06:00
Neil Dorin
be58a0bc29 Merge pull request #1260 from PepperDash/temp-to-dev 2025-04-24 09:32:32 -06:00
Neil Dorin
8406f69e0d Merge pull request #1257 from PepperDash/temp-to-dev 2025-04-18 11:46:18 -06:00
Andrew Welker
116d83394a Merge pull request #1255 from PepperDash/temp-to-dev
Temp to dev
2025-04-14 11:15:27 -05:00
Neil Dorin
9b8e452eb4 Merge pull request #1251 from PepperDash/temp-to-dev 2025-04-11 14:00:02 -06:00
Neil Dorin
c9d86bd5dd Merge pull request #1248 from PepperDash/temp-to-dev 2025-04-11 11:55:35 -06:00
Neil Dorin
b2b257020f Merge pull request #1246 from PepperDash/temp-to-dev 2025-04-08 13:48:10 -06:00
Neil Dorin
6f58e18d14 Merge pull request #1244 from PepperDash/temp-to-dev
Temp to dev
2025-04-04 10:30:10 -06:00
Neil Dorin
60fc0298ec Merge pull request #1242 from PepperDash/temp-to-dev
Temp to dev
2025-04-02 11:14:45 -06:00
46 changed files with 1565 additions and 2918 deletions

3
.gitignore vendored
View File

@@ -393,5 +393,4 @@ essentials-framework/Essentials Interfaces/PepperDash_Essentials_Interfaces/Pepp
/._PepperDash.Essentials.sln
.vscode/settings.json
_site/
api/
*.DS_Store
api/

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.4.0-local</Version>
<Version>2.4.0-local</Version>
<InformationalVersion>$(Version)</InformationalVersion>
<Authors>PepperDash Technology</Authors>
<Company>PepperDash Technology</Company>
@@ -19,5 +19,6 @@
<ItemGroup>
<None Include="..\..\LICENSE.md" Pack="true" PackagePath=""/>
<None Include="..\..\README.md" Pack="true" PackagePath=""/>
<None Include=".\build\**" Pack="true" PackagePath="build"/>
</ItemGroup>
</Project>

View File

@@ -11,35 +11,35 @@ namespace PepperDash.Core
public class Device : IKeyName
{
/// <summary>
/// Unique Key
/// </summary>
/// <summary>
/// Unique Key
/// </summary>
public string Key { get; protected set; }
/// <summary>
/// Name of the devie
/// </summary>
public string Name { get; protected set; }
/// <summary>
///
/// </summary>
public string Name { get; protected set; }
/// <summary>
///
/// </summary>
public bool Enabled { get; protected set; }
///// <summary>
///// A place to store reference to the original config object, if any. These values should
///// NOT be used as properties on the device as they are all publicly-settable values.
///// </summary>
//public DeviceConfig Config { get; private set; }
///// <summary>
///// Helper method to check if Config exists
///// </summary>
//public bool HasConfig { get { return Config != null; } }
///// <summary>
///// A place to store reference to the original config object, if any. These values should
///// NOT be used as properties on the device as they are all publicly-settable values.
///// </summary>
//public DeviceConfig Config { get; private set; }
///// <summary>
///// Helper method to check if Config exists
///// </summary>
//public bool HasConfig { get { return Config != null; } }
List<Action> _PreActivationActions;
List<Action> _PostActivationActions;
/// <summary>
///
/// </summary>
/// <summary>
///
/// </summary>
public static Device DefaultDevice { get { return _DefaultDevice; } }
static Device _DefaultDevice = new Device("Default", "Default");
@@ -54,27 +54,27 @@ namespace PepperDash.Core
Name = "";
}
/// <summary>
/// Constructor with key and name
/// </summary>
/// <param name="key"></param>
/// <param name="name"></param>
/// <summary>
/// Constructor with key and name
/// </summary>
/// <param name="key"></param>
/// <param name="name"></param>
public Device(string key, string name) : this(key)
{
Name = name;
}
//public Device(DeviceConfig config)
// : this(config.Key, config.Name)
//{
// Config = config;
//}
//public Device(DeviceConfig config)
// : this(config.Key, config.Name)
//{
// Config = config;
//}
/// <summary>
/// Adds a pre activation action
/// </summary>
/// <param name="act"></param>
/// <summary>
/// Adds a pre activation action
/// </summary>
/// <param name="act"></param>
public void AddPreActivationAction(Action act)
{
if (_PreActivationActions == null)
@@ -82,10 +82,10 @@ namespace PepperDash.Core
_PreActivationActions.Add(act);
}
/// <summary>
/// Adds a post activation action
/// </summary>
/// <param name="act"></param>
/// <summary>
/// Adds a post activation action
/// </summary>
/// <param name="act"></param>
public void AddPostActivationAction(Action act)
{
if (_PostActivationActions == null)
@@ -93,58 +93,55 @@ namespace PepperDash.Core
_PostActivationActions.Add(act);
}
/// <summary>
/// Executes the preactivation actions
/// </summary>
public void PreActivate()
{
if (_PreActivationActions != null)
_PreActivationActions.ForEach(a =>
{
/// <summary>
/// Executes the preactivation actions
/// </summary>
public void PreActivate()
{
if (_PreActivationActions != null)
_PreActivationActions.ForEach(a => {
try
{
a.Invoke();
}
catch (Exception e)
{
} catch (Exception e)
{
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
}
});
}
});
}
/// <summary>
/// Gets this device ready to be used in the system. Runs any added pre-activation items, and
/// all post-activation at end. Classes needing additional logic to
/// run should override CustomActivate()
/// </summary>
public bool Activate()
public bool Activate()
{
//if (_PreActivationActions != null)
// _PreActivationActions.ForEach(a => a.Invoke());
//if (_PreActivationActions != null)
// _PreActivationActions.ForEach(a => a.Invoke());
var result = CustomActivate();
//if(result && _PostActivationActions != null)
// _PostActivationActions.ForEach(a => a.Invoke());
return result;
//if(result && _PostActivationActions != null)
// _PostActivationActions.ForEach(a => a.Invoke());
return result;
}
/// <summary>
/// Executes the postactivation actions
/// </summary>
public void PostActivate()
{
if (_PostActivationActions != null)
_PostActivationActions.ForEach(a =>
{
try
{
a.Invoke();
}
catch (Exception e)
{
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
}
});
}
/// <summary>
/// Executes the postactivation actions
/// </summary>
public void PostActivate()
{
if (_PostActivationActions != null)
_PostActivationActions.ForEach(a => {
try
{
a.Invoke();
}
catch (Exception e)
{
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
}
});
}
/// <summary>
/// Called in between Pre and PostActivationActions when Activate() is called.
@@ -161,14 +158,14 @@ namespace PepperDash.Core
/// <returns></returns>
public virtual bool Deactivate() { return true; }
/// <summary>
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
/// </summary>
public virtual void Initialize()
{
}
/// <summary>
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
/// </summary>
public virtual void Initialize()
{
}
/// <summary>
/// <summary>
/// Helper method to check object for bool value false and fire an Action method
/// </summary>
/// <param name="o">Should be of type bool, others will be ignored</param>
@@ -178,15 +175,5 @@ namespace PepperDash.Core
if (o is bool && !(bool)o) a();
}
/// <summary>
/// Returns a string representation of the object, including its key and name.
/// </summary>
/// <remarks>The returned string is formatted as "{Key} - {Name}". If the <c>Name</c> property is
/// null or empty, "---" is used in place of the name.</remarks>
/// <returns>A string that represents the object, containing the key and name in the format "{Key} - {Name}".</returns>
public override string ToString()
{
return string.Format("{0} - {1}", Key, string.IsNullOrEmpty(Name) ? "---" : Name);
}
}
}

View File

@@ -1,107 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
{
/// <summary>
/// This defines a device that has screens with layouts
/// Simply decorative
/// </summary>
public interface IHasScreensWithLayouts
{
/// <summary>
/// A dictionary of screens, keyed by screen ID, that contains information about each screen and its layouts.
/// </summary>
Dictionary<uint, ScreenInfo> Screens { get; }
/// <summary>
/// Applies a specific layout to a screen based on the provided screen ID and layout index.
/// </summary>
/// <param name="screenId"></param>
/// <param name="layoutIndex"></param>
void ApplyLayout(uint screenId, uint layoutIndex);
}
/// <summary>
/// Represents information about a screen and its layouts.
/// </summary>
public class ScreenInfo
{
/// <summary>
/// Indicates whether the screen is enabled or not.
/// </summary>
[JsonProperty("enabled")]
public bool Enabled { get; set; }
/// <summary>
/// The name of the screen.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// The index of the screen.
/// </summary>
[JsonProperty("screenIndex")]
public int ScreenIndex { get; set; }
/// <summary>
/// A dictionary of layout information for the screen, keyed by layout ID.
/// </summary>
[JsonProperty("layouts")]
public Dictionary<uint, LayoutInfo> Layouts { get; set; }
}
/// <summary>
/// Represents information about a layout on a screen.
/// </summary>
public class LayoutInfo
{
/// <summary>
/// The name of the layout.
/// </summary>
[JsonProperty("layoutName")]
public string LayoutName { get; set; }
/// <summary>
/// The index of the layout.
/// </summary>
[JsonProperty("layoutIndex")]
public int LayoutIndex { get; set; }
/// <summary>
/// The type of the layout, which can be "single", "double", "triple", or "quad".
/// </summary>
[JsonProperty("layoutType")]
public string LayoutType { get; set; }
/// <summary>
/// A dictionary of window configurations for the layout, keyed by window ID.
/// </summary>
[JsonProperty("windows")]
public Dictionary<uint, WindowConfig> Windows { get; set; }
}
/// <summary>
/// Represents the configuration of a window within a layout on a screen.
/// </summary>
public class WindowConfig
{
/// <summary>
/// The display label for the window
/// </summary>
[JsonProperty("label")]
public string Label { get; set; }
/// <summary>
/// The input for the window
/// </summary>
[JsonProperty("input")]
public string Input { get; set; }
}
}

View File

@@ -1,90 +0,0 @@
using Crestron.SimplSharpPro.DM.Streaming;
using System;
using System.Collections.Generic;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a collection of network port information and provides notifications when the information changes.
/// </summary>
/// <remarks>This interface is designed to provide access to a list of network port details and to notify
/// subscribers when the port information is updated. Implementations of this interface should ensure that the <see
/// cref="PortInformationChanged"/> event is raised whenever the <see cref="NetworkPorts"/> collection
/// changes.</remarks>
public interface INvxNetworkPortInformation
{
/// <summary>
/// Occurs when the port information changes.
/// </summary>
/// <remarks>This event is triggered whenever there is a change in the port information, such as
/// updates to port settings or status. Subscribers can handle this event to respond to such changes.</remarks>
event EventHandler PortInformationChanged;
/// <summary>
/// Gets the collection of network port information associated with the current instance.
/// </summary>
/// <remarks>The collection provides information about the network ports, such as their status,
/// configuration, or other relevant details. The returned list is read-only and cannot be modified
/// directly.</remarks>
List<NvxNetworkPortInformation> NetworkPorts { get; }
}
/// <summary>
/// Represents information about a network port, including its configuration and associated system details.
/// </summary>
/// <remarks>This class provides properties to describe various attributes of a network port, such as its
/// name, description, VLAN configuration, and management IP address. It is typically used to store and retrieve
/// metadata about network ports in a managed environment.</remarks>
public class NvxNetworkPortInformation
{
private readonly DmNvxBaseClass.DmNvx35xNetwork.DmNvxNetworkLldpPort port;
/// <summary>
/// Gets or sets the index of the device port.
/// </summary>
public uint DevicePortIndex { get; }
/// <summary>
/// Gets or sets the name of the port used for communication.
/// </summary>
public string PortName => port.PortNameFeedback.StringValue;
/// <summary>
/// Gets or sets the description of the port.
/// </summary>
public string PortDescription => port.PortNameDescriptionFeedback.StringValue;
/// <summary>
/// Gets or sets the name of the VLAN (Virtual Local Area Network).
/// </summary>
public string VlanName => port.VlanNameFeedback.StringValue;
/// <summary>
/// Gets the IP management address associated with the port.
/// </summary>
public string IpManagementAddress => port.IpManagementAddressFeedback.StringValue;
/// <summary>
/// Gets the name of the system as reported by the associated port.
/// </summary>
public string SystemName => port.SystemNameFeedback.StringValue;
/// <summary>
/// Gets the description of the system name.
/// </summary>
public string SystemNameDescription => port.SystemNameDescriptionFeedback.StringValue;
/// <summary>
/// Initializes a new instance of the <see cref="NvxNetworkPortInformation"/> class with the specified network port
/// and device port index.
/// </summary>
/// <param name="port">The network port associated with the device. Cannot be <see langword="null"/>.</param>
/// <param name="devicePortIndex">The index of the device port.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="port"/> is <see langword="null"/>.</exception>
public NvxNetworkPortInformation(DmNvxBaseClass.DmNvx35xNetwork.DmNvxNetworkLldpPort port, uint devicePortIndex)
{
this.port = port ?? throw new ArgumentNullException(nameof(port), "Port cannot be null");
DevicePortIndex = devicePortIndex;
}
}
}

View File

@@ -248,7 +248,7 @@ namespace PepperDash.Essentials.Core
foreach (var dev in Devices.Values.OfType<ICommunicationMonitor>())
{
CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}\r\n");
CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}{Environment.NewLine}");
}
}

View File

@@ -20,20 +20,19 @@ namespace PepperDash.Essentials.Core
public event EventHandler Initialized;
private bool _isInitialized;
public bool IsInitialized
{
public bool IsInitialized {
get { return _isInitialized; }
private set
{
private set
{
if (_isInitialized == value) return;
_isInitialized = value;
if (_isInitialized)
{
Initialized?.Invoke(this, new EventArgs());
}
}
}
}
protected EssentialsDevice(string key)
@@ -81,9 +80,8 @@ namespace PepperDash.Essentials.Core
/// <summary>
/// Override this method to build and create custom Mobile Control Messengers during the Activation phase
/// </summary>
protected virtual void CreateMobileControlMessengers()
{
protected virtual void CreateMobileControlMessengers() {
}
}
@@ -198,10 +196,18 @@ namespace PepperDash.Essentials.Core
/// </summary>
public abstract class EssentialsPluginDeviceFactory<T> : EssentialsDeviceFactory<T>, IPluginDeviceFactory where T : EssentialsDevice
{
#if ESSENTIALS_VERSION
/// <summary>
/// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33")
/// </summary>
public string MinimumEssentialsFrameworkVersion { get; protected set; } = ESSENTIALS_VERSION
#else
/// <summary>
/// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33")
/// </summary>
public string MinimumEssentialsFrameworkVersion { get; protected set; }
#endif
}
public abstract class EssentialsPluginDevelopmentDeviceFactory<T> : EssentialsDeviceFactory<T>, IPluginDevelopmentDeviceFactory where T : EssentialsDevice

View File

@@ -9,15 +9,10 @@ using PepperDash.Essentials.Core.Devices;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a level control item in a list, which can be used to control volume or mute functionality.
/// </summary>
public class LevelControlListItem : AudioControlListItemBase
{
/// <summary>
/// A reference to the IBasicVolumeWithFeedback device for control.
/// </summary>
[JsonIgnore]
public IBasicVolumeWithFeedback LevelControl
{
@@ -60,7 +55,7 @@ namespace PepperDash.Essentials.Core
{
get
{
if (string.IsNullOrEmpty(ItemKey)) return ParentDeviceKey;
if(string.IsNullOrEmpty(ItemKey)) return ParentDeviceKey;
else
{
return DeviceManager.AllDevices.
@@ -75,39 +70,13 @@ namespace PepperDash.Essentials.Core
[JsonProperty("type")]
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public eLevelControlType Type { get; set; }
/// <summary>
/// Indicates if the item is a mic or not.
/// </summary>
[JsonProperty("isMic", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsMic { get; set; }
/// <summary>
/// Indicates if the item should show the raw level in the UI.
/// </summary>
[JsonProperty("showRawLevel", NullValueHandling = NullValueHandling.Ignore)]
public bool? ShowRawLevel { get; set; }
}
/// <summary>
/// Indicates the type of level control item.
/// </summary>
[Flags]
public enum eLevelControlType
{
/// <summary>
/// Indicates that the item is a level control only
/// </summary>
Level = 1,
/// <summary>
/// Indicates that the item is a mute control only
/// </summary>
Mute = 2,
/// <summary>
/// Indicates that the item is both a level and mute control
/// </summary>
LevelAndMute = Level | Mute,
}
}

View File

@@ -10,18 +10,7 @@ namespace PepperDash.Essentials.Core
/// </summary>
public enum eSourceListItemType
{
/// <summary>
/// Represents a typical route.
/// </summary>
Route,
/// <summary>
/// Represents an off route.
/// </summary>
Off,
/// <summary>
/// Represents some other type of route
/// </summary>
Other,
Route, Off, Other, SomethingAwesomerThanThese
}
/// <summary>
@@ -29,9 +18,6 @@ namespace PepperDash.Essentials.Core
/// </summary>
public class SourceListItem
{
/// <summary>
/// The key of the source item, which is used to identify it in the DeviceManager
/// </summary>
[JsonProperty("sourceKey")]
public string SourceKey { get; set; }
@@ -131,9 +117,6 @@ namespace PepperDash.Essentials.Core
[JsonProperty("disableRoutedSharing")]
public bool DisableRoutedSharing { get; set; }
/// <summary>
///
/// </summary>
[JsonProperty("destinations")]
public List<eSourceListItemDestinationTypes> Destinations { get; set; }
/// <summary>
@@ -166,56 +149,31 @@ namespace PepperDash.Essentials.Core
[JsonProperty("disableSimpleRouting")]
public bool DisableSimpleRouting { get; set; }
/// <summary>
/// Default constructor for SourceListItem, initializes the Icon to "Blank"
/// </summary>
public SourceListItem()
{
Icon = "Blank";
}
/// <summary>
/// Returns a string representation of the SourceListItem, including the SourceKey and Name
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"{SourceKey}:{Name}";
}
}
/// <summary>
/// Represents a route in a source list item, which defines the source and destination keys and the type of signal being routed
/// </summary>
public class SourceRouteListItem
{
/// <summary>
/// The key of the source device to route from
/// </summary>
[JsonProperty("sourceKey")]
public string SourceKey { get; set; }
/// <summary>
/// The key of the source port to route from
/// </summary>
[JsonProperty("sourcePortKey")]
public string SourcePortKey { get; set; }
/// <summary>
/// The key of the destination device to route to
/// </summary>
[JsonProperty("destinationKey")]
public string DestinationKey { get; set; }
/// <summary>
/// The key of the destination port to route to
/// </summary>
[JsonProperty("destinationPortKey")]
public string DestinationPortKey { get; set; }
/// <summary>
/// The type of signal being routed, such as audio or video
/// </summary>
[JsonProperty("type")]
public eRoutingSignalType Type { get; set; }
}
@@ -225,85 +183,15 @@ namespace PepperDash.Essentials.Core
/// </summary>
public enum eSourceListItemDestinationTypes
{
/// <summary>
/// Default display, used for the main video output in a room
/// </summary>
defaultDisplay,
/// <summary>
/// Left display
/// </summary>
leftDisplay,
/// <summary>
/// Right display
/// </summary>
rightDisplay,
/// <summary>
/// Center display
/// </summary>
centerDisplay,
/// <summary>
/// Program audio, used for the main audio output in a room
/// </summary>
programAudio,
/// <summary>
/// Codec content, used for sharing content to the far end in a video call
/// </summary>
codecContent,
/// <summary>
/// Front left display, used for rooms with multiple displays
/// </summary>
frontLeftDisplay,
/// <summary>
/// Front right display, used for rooms with multiple displays
/// </summary>
frontRightDisplay,
/// <summary>
/// Rear left display, used for rooms with multiple displays
/// </summary>
rearLeftDisplay,
/// <summary>
/// Rear right display, used for rooms with multiple displays
/// </summary>
rearRightDisplay,
/// <summary>
/// Auxiliary display 1, used for additional displays in a room
/// </summary>
auxDisplay1,
/// <summary>
/// Auxiliary display 2, used for additional displays in a room
/// </summary>
auxDisplay2,
/// <summary>
/// Auxiliary display 3, used for additional displays in a room
/// </summary>
auxDisplay3,
/// <summary>
/// Auxiliary display 4, used for additional displays in a room
/// </summary>
auxDisplay4,
/// <summary>
/// Auxiliary display 5, used for additional displays in a room
/// </summary>
auxDisplay5,
/// <summary>
/// Auxiliary display 6, used for additional displays in a room
/// </summary>
auxDisplay6,
/// <summary>
/// Auxiliary display 7, used for additional displays in a room
/// </summary>
auxDisplay7,
/// <summary>
/// Auxiliary display 8, used for additional displays in a room
/// </summary>
auxDisplay8,
/// <summary>
/// Auxiliary display 9, used for additional displays in a room
/// </summary>
auxDisplay9,
/// <summary>
/// Auxiliary display 10, used for additional displays in a room
/// </summary>
auxDisplay10,
}
}

View File

@@ -1,86 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Extensions for IPAddress to provide additional functionality such as getting broadcast address, network address, and checking if two addresses are in the same subnet.
/// </summary>
public static class IPAddressExtensions
{
/// <summary>
/// Get the broadcast address for a given IP address and subnet mask.
/// </summary>
/// <param name="address">Address to check</param>
/// <param name="subnetMask">Subnet mask in a.b.c.d format</param>
/// <returns>Broadcast address</returns>
/// <remarks>
/// If the input IP address is 192.168.1.100 and the subnet mask is 255.255.255.0, the broadcast address will be 192.168.1.255
/// </remarks>
/// <exception cref="ArgumentException"></exception>
public static IPAddress GetBroadcastAddress(this IPAddress address, IPAddress subnetMask)
{
byte[] ipAdressBytes = address.GetAddressBytes();
byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
if (ipAdressBytes.Length != subnetMaskBytes.Length)
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
for (int i = 0; i < broadcastAddress.Length; i++)
{
broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255));
}
return new IPAddress(broadcastAddress);
}
/// <summary>
/// Get the network address for a given IP address and subnet mask.
/// </summary>
/// <param name="address">Address to check</param>
/// <param name="subnetMask">Subnet mask in a.b.c.d</param>
/// <returns>Network Address</returns>
/// /// <remarks>
/// If the input IP address is 192.168.1.100 and the subnet mask is 255.255.255.0, the network address will be 192.168.1.0
/// </remarks>
/// <exception cref="ArgumentException"></exception>
public static IPAddress GetNetworkAddress(this IPAddress address, IPAddress subnetMask)
{
byte[] ipAdressBytes = address.GetAddressBytes();
byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
if (ipAdressBytes.Length != subnetMaskBytes.Length)
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
for (int i = 0; i < broadcastAddress.Length; i++)
{
broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
}
return new IPAddress(broadcastAddress);
}
/// <summary>
/// Determine if two IP addresses are in the same subnet.
/// </summary>
/// <param name="address2">Address to check</param>
/// <param name="address">Second address to check</param>
/// <param name="subnetMask">Subnet mask to use to compare the 2 IP Address</param>
/// <returns>True if addresses are in the same subnet</returns>
/// <remarks>
/// If the input IP addresses are 192.168.1.100 and 192.168.1.200, and the subnet mask is 255.255.255.0, this will return true.
/// If the input IP addresses are 10.1.1.100 and 192.168.1.100, and the subnet mask is 255.255.255.0, this will return false.
/// </remarks>
public static bool IsInSameSubnet(this IPAddress address2, IPAddress address, IPAddress subnetMask)
{
IPAddress network1 = address.GetNetworkAddress(subnetMask);
IPAddress network2 = address2.GetNetworkAddress(subnetMask);
return network1.Equals(network2);
}
}
}

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configurations>Debug;Release;Debug 4.7.2</Configurations>
<DefineConstants>$(DefineConstants);ESSENTIALS_VERSION=$(Version)</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net472</TargetFramework>

View File

@@ -10,13 +10,6 @@ using System.Threading.Tasks;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a device that manages room combinations by controlling partitions and scenarios.
/// </summary>
/// <remarks>The <see cref="EssentialsRoomCombiner"/> allows for dynamic configuration of room
/// combinations based on partition states and predefined scenarios. It supports both automatic and manual modes
/// for managing room combinations. In automatic mode, the device determines the current room combination scenario
/// based on partition sensor states. In manual mode, scenarios can be set explicitly by the user.</remarks>
public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombiner
{
private EssentialsRoomCombinerPropertiesConfig _propertiesConfig;
@@ -25,9 +18,6 @@ namespace PepperDash.Essentials.Core
private List<IEssentialsRoom> _rooms;
/// <summary>
/// Gets a list of rooms represented as key-name pairs.
/// </summary>
public List<IKeyName> Rooms
{
get
@@ -38,12 +28,6 @@ namespace PepperDash.Essentials.Core
private bool _isInAutoMode;
/// <summary>
/// Gets or sets a value indicating whether the system is operating in automatic mode.
/// </summary>
/// <remarks>Changing this property triggers an update event via
/// <c>IsInAutoModeFeedback.FireUpdate()</c>. Ensure that any event listeners are properly configured to handle
/// this update.</remarks>
public bool IsInAutoMode
{
get
@@ -62,36 +46,12 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Gets a value indicating whether automatic mode is disabled.
/// </summary>
public bool DisableAutoMode
{
get
{
return _propertiesConfig.DisableAutoMode;
}
}
private CTimer _scenarioChangeDebounceTimer;
private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s
private Mutex _scenarioChange = new Mutex();
/// <summary>
/// Initializes a new instance of the <see cref="EssentialsRoomCombiner"/> class, which manages room combination
/// scenarios and partition states.
/// </summary>
/// <remarks>The <see cref="EssentialsRoomCombiner"/> class is designed to handle dynamic room
/// combination scenarios based on partition states. It supports both automatic and manual modes for managing
/// room combinations. By default, the instance starts in automatic mode unless the <paramref name="props"/>
/// specifies otherwise. After activation, the room combiner initializes partition state providers and sets up
/// the initial room configuration. Additionally, it subscribes to the <see
/// cref="DeviceManager.AllDevicesInitialized"/> event to ensure proper initialization of dependent devices
/// before determining or setting the room combination scenario.</remarks>
/// <param name="key">The unique identifier for the room combiner instance.</param>
/// <param name="props">The configuration properties for the room combiner, including default settings and debounce times.</param>
public EssentialsRoomCombiner(string key, EssentialsRoomCombinerPropertiesConfig props)
: base(key)
{
@@ -286,16 +246,8 @@ namespace PepperDash.Essentials.Core
#region IEssentialsRoomCombiner Members
/// <summary>
/// Occurs when the room combination scenario changes.
/// </summary>
/// <remarks>This event is triggered whenever the configuration or state of the room combination
/// changes. Subscribers can use this event to update their logic or UI based on the new scenario.</remarks>
public event EventHandler<EventArgs> RoomCombinationScenarioChanged;
/// <summary>
/// Gets the current room combination scenario.
/// </summary>
public IRoomCombinationScenario CurrentScenario
{
get
@@ -304,25 +256,10 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Gets the feedback indicating whether the system is currently in auto mode.
/// </summary>
public BoolFeedback IsInAutoModeFeedback { get; private set; }
/// <summary>
/// Enables auto mode for the room combiner and its partitions, allowing automatic room combination scenarios to
/// be determined.
/// </summary>
/// <remarks>Auto mode allows the room combiner to automatically adjust its configuration based on
/// the state of its partitions. If auto mode is disabled in the configuration, this method logs a warning and
/// does not enable auto mode.</remarks>
public void SetAutoMode()
{
if(_propertiesConfig.DisableAutoMode)
{
this.LogWarning("Auto mode is disabled for this room combiner. Cannot set to auto mode.");
return;
}
IsInAutoMode = true;
foreach (var partition in Partitions)
@@ -333,12 +270,6 @@ namespace PepperDash.Essentials.Core
DetermineRoomCombinationScenario();
}
/// <summary>
/// Switches the system to manual mode, disabling automatic operations.
/// </summary>
/// <remarks>This method sets the system to manual mode by updating the mode state and propagates
/// the change to all partitions. Once in manual mode, automatic operations are disabled for the system and its
/// partitions.</remarks>
public void SetManualMode()
{
IsInAutoMode = false;
@@ -349,11 +280,6 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Toggles the current mode between automatic and manual.
/// </summary>
/// <remarks>If the current mode is automatic, this method switches to manual mode. If the
/// current mode is manual, it switches to automatic mode.</remarks>
public void ToggleMode()
{
if (IsInAutoMode)
@@ -366,22 +292,10 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Gets the collection of room combination scenarios.
/// </summary>
public List<IRoomCombinationScenario> RoomCombinationScenarios { get; private set; }
/// <summary>
/// Gets the collection of partition controllers managed by this instance.
/// </summary>
public List<IPartitionController> Partitions { get; private set; }
/// <summary>
/// Toggles the state of the partition identified by the specified partition key.
/// </summary>
/// <remarks>If no partition with the specified key exists, the method performs no
/// action.</remarks>
/// <param name="partitionKey">The key of the partition whose state is to be toggled. This value cannot be null or empty.</param>
public void TogglePartitionState(string partitionKey)
{
var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionKey));
@@ -392,17 +306,6 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Sets the room combination scenario based on the specified scenario key.
/// </summary>
/// <remarks>This method manually adjusts the partition states according to the specified
/// scenario. If the application is in auto mode, the operation will not proceed, and a log message will be
/// generated indicating that the mode must be set to manual first. If the specified scenario key does not
/// match any existing scenario, a debug log message will be generated. For each partition state in the
/// scenario, the corresponding partition will be updated to either "Present" or "Not Present" based on the
/// scenario's configuration. If a partition key in the scenario cannot be found, a debug log message will be
/// generated.</remarks>
/// <param name="scenarioKey">The key identifying the room combination scenario to apply. This must match the key of an existing scenario.</param>
public void SetRoomCombinationScenario(string scenarioKey)
{
if (IsInAutoMode)
@@ -451,32 +354,13 @@ namespace PepperDash.Essentials.Core
#endregion
}
/// <summary>
/// Provides a factory for creating instances of <see cref="EssentialsRoomCombiner"/> devices.
/// </summary>
/// <remarks>This factory is responsible for constructing <see cref="EssentialsRoomCombiner"/> devices
/// based on the provided configuration. It supports the type name "essentialsroomcombiner" for device
/// creation.</remarks>
public class EssentialsRoomCombinerFactory : EssentialsDeviceFactory<EssentialsRoomCombiner>
{
/// <summary>
/// Initializes a new instance of the <see cref="EssentialsRoomCombinerFactory"/> class.
/// </summary>
/// <remarks>This factory is used to create instances of room combiners with the specified type
/// names. By default, the factory includes the type name "essentialsroomcombiner".</remarks>
public EssentialsRoomCombinerFactory()
{
TypeNames = new List<string> { "essentialsroomcombiner" };
}
/// <summary>
/// Creates and initializes a new instance of the <see cref="EssentialsRoomCombiner"/> device.
/// </summary>
/// <remarks>This method uses the provided device configuration to extract the properties and
/// create an <see cref="EssentialsRoomCombiner"/> device. Ensure that the configuration contains valid
/// properties for the device to be created successfully.</remarks>
/// <param name="dc">The device configuration containing the key and properties required to build the device.</param>
/// <returns>A new instance of <see cref="EssentialsRoomCombiner"/> initialized with the specified configuration.</returns>
public override EssentialsDevice BuildDevice(PepperDash.Essentials.Core.Config.DeviceConfig dc)
{
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new EssentialsRoomCombiner Device");

View File

@@ -1,4 +1,10 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
@@ -11,14 +17,6 @@ namespace PepperDash.Essentials.Core
/// </summary>
public class EssentialsRoomCombinerPropertiesConfig
{
/// <summary>
/// Gets or sets a value indicating whether the system operates in automatic mode.
/// <remarks>Some systems don't have partitions sensors, and show shouldn't allow auto mode to be turned on. When this is true in the configuration,
/// auto mode won't be allowed to be turned on.</remarks>
/// </summary>
[JsonProperty("disableAutoMode")]
public bool DisableAutoMode { get; set; }
/// <summary>
/// The list of partitions that device the rooms
/// </summary>
@@ -49,9 +47,6 @@ namespace PepperDash.Essentials.Core
[JsonProperty("defaultScenarioKey")]
public string defaultScenarioKey { get; set; }
/// <summary>
/// Gets or sets the debounce time, in seconds, for scenario changes.
/// </summary>
[JsonProperty("scenarioChangeDebounceTimeSeconds")]
public int ScenarioChangeDebounceTimeSeconds { get; set; }
}
@@ -61,15 +56,9 @@ namespace PepperDash.Essentials.Core
/// </summary>
public class PartitionConfig : IKeyName
{
/// <summary>
/// Gets or sets the unique key associated with the object.
/// </summary>
[JsonProperty("key")]
public string Key { get; set; }
/// <summary>
/// Gets or sets the name associated with the object.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
@@ -91,21 +80,12 @@ namespace PepperDash.Essentials.Core
/// </summary>
public class RoomCombinationScenarioConfig : IKeyName
{
/// <summary>
/// Gets or sets the key associated with the object.
/// </summary>
[JsonProperty("key")]
public string Key { get; set; }
/// <summary>
/// Gets or sets the name associated with the object.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the collection of partition states.
/// </summary>
[JsonProperty("partitionStates")]
public List<PartitionState> PartitionStates { get; set; }
@@ -115,15 +95,9 @@ namespace PepperDash.Essentials.Core
[JsonProperty("uiMap")]
public Dictionary<string, string> UiMap { get; set; }
/// <summary>
/// Gets or sets the list of actions to be performed during device activation.
/// </summary>
[JsonProperty("activationActions")]
public List<DeviceActionWrapper> ActivationActions { get; set; }
/// <summary>
/// Gets or sets the list of actions to be performed when a device is deactivated.
/// </summary>
[JsonProperty("deactivationActions")]
public List<DeviceActionWrapper> DeactivationActions { get; set; }
}
@@ -133,15 +107,9 @@ namespace PepperDash.Essentials.Core
/// </summary>
public class PartitionState
{
/// <summary>
/// Gets or sets the partition key used to group and organize data within a storage system.
/// </summary>
[JsonProperty("partitionKey")]
public string PartitionKey { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a partition is currently present.
/// </summary>
[JsonProperty("partitionSensedState")]
public bool PartitionPresent { get; set; }
}

View File

@@ -28,20 +28,9 @@ namespace PepperDash.Essentials.Core
[JsonIgnore]
BoolFeedback IsInAutoModeFeedback {get;}
/// <summary>
/// Gets a value indicating whether the automatic mode is disabled.
/// </summary>
[JsonProperty("disableAutoMode")]
bool DisableAutoMode { get; }
/// <summary>
/// Gets a value indicating whether the system is operating in automatic mode.
/// </summary>
[JsonProperty("isInAutoMode")]
bool IsInAutoMode { get; }
/// <summary>
/// Gets the collection of rooms associated with the current object.
/// </summary>
[JsonProperty("rooms")]
List<IKeyName> Rooms { get; }
@@ -85,13 +74,6 @@ namespace PepperDash.Essentials.Core
void SetRoomCombinationScenario(string scenarioKey);
}
/// <summary>
/// Represents a scenario for combining rooms, including activation, deactivation, and associated state.
/// </summary>
/// <remarks>This interface defines the behavior for managing room combination scenarios, including
/// activation and deactivation, tracking the active state, and managing related partition states and UI mappings.
/// Implementations of this interface are expected to handle the logic for room combinations based on the provided
/// partition states and UI mappings.</remarks>
public interface IRoomCombinationScenario : IKeyName
{
/// <summary>
@@ -100,9 +82,6 @@ namespace PepperDash.Essentials.Core
[JsonIgnore]
BoolFeedback IsActiveFeedback { get; }
/// <summary>
/// Gets a value indicating whether the entity is active.
/// </summary>
[JsonProperty("isActive")]
bool IsActive { get; }

View File

@@ -18,15 +18,8 @@ namespace PepperDash.Essentials.Core
/// </summary>
public static class Extensions
{
/// <summary>
/// Stores pending route requests, keyed by the destination device key.
/// Used primarily to handle routing requests while a device is cooling down.
/// </summary>
private static readonly Dictionary<string, RouteRequest> RouteRequests = new Dictionary<string, RouteRequest>();
/// <summary>
/// A queue to process route requests and releases sequentially.
/// </summary>
private static readonly GenericQueue routeRequestQueue = new GenericQueue("routingQueue");
/// <summary>
@@ -45,49 +38,16 @@ namespace PepperDash.Essentials.Core
ReleaseAndMakeRoute(destination, source, signalType, inputPort, outputPort);
}
/// <summary>
/// Will release the existing route to the destination, if a route is found. This does not CLEAR the route, only stop counting usage time on any output ports that have a usage tracker set
/// </summary>
/// <param name="destination">destination to clear</param>
public static void ReleaseRoute(this IRoutingInputs destination)
{
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty, false));
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty));
}
/// <summary>
/// Will release the existing route to the destination, if a route is found. This does not CLEAR the route, only stop counting usage time on any output ports that have a usage tracker set
/// </summary>
/// <param name="destination">destination to clear</param>
/// <param name="inputPortKey">Input to use to find existing route</param>
public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey)
{
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey, false));
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey));
}
/// <summary>
/// Clears the route on the destination. This will remove any routes that are currently in use
/// </summary>
/// <param name="destination">Destination</param>
public static void ClearRoute(this IRoutingInputs destination)
{
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty, true));
}
/// <summary>
/// Clears the route on the destination. This will remove any routes that are currently in use
/// </summary>
/// <param name="destination">destination</param>
/// <param name="inputPortKey">input to use to find existing route</param>
public static void ClearRoute(this IRoutingInputs destination, string inputPortKey)
{
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey, true));
}
/// <summary>
/// Removes the route request for the destination. This will remove any routes that are currently in use
/// </summary>
/// <param name="destinationKey">destination device key</param>
public static void RemoveRouteRequestForDestination(string destinationKey)
{
Debug.LogMessage(LogEventLevel.Information, "Removing route request for {destination}", null, destinationKey);
@@ -170,15 +130,6 @@ namespace PepperDash.Essentials.Core
return (audioRouteDescriptor, videoRouteDescriptor);
}
/// <summary>
/// Internal method to handle the logic for releasing an existing route and making a new one.
/// Handles devices with cooling states by queueing the request.
/// </summary>
/// <param name="destination">The destination device.</param>
/// <param name="source">The source device.</param>
/// <param name="signalType">The type of signal to route.</param>
/// <param name="destinationPort">The specific destination input port (optional).</param>
/// <param name="sourcePort">The specific source output port (optional).</param>
private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null)
{
if (destination == null) throw new ArgumentNullException(nameof(destination));
@@ -233,16 +184,11 @@ namespace PepperDash.Essentials.Core
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
}
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty, false));
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty));
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
}
/// <summary>
/// Executes the actual routing based on a <see cref="RouteRequest"/>.
/// Finds the route path, adds it to the collection, and executes the switches.
/// </summary>
/// <param name="request">The route request details.</param>
private static void RunRouteRequest(RouteRequest request)
{
try
@@ -270,15 +216,14 @@ namespace PepperDash.Essentials.Core
{
Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request);
}
}
}
/// <summary>
/// Will release the existing route on the destination, if it is found in RouteDescriptorCollection.DefaultCollection
/// Will release the existing route on the destination, if it is found in
/// RouteDescriptorCollection.DefaultCollection
/// </summary>
/// <param name="destination"></param>
/// <param name="inputPortKey"> The input port key to use to find the route. If empty, will use the first available input port</param>
/// <param name="clearRoute"> If true, will clear the route on the destination. This will remove any routes that are currently in use</param>
private static void ReleaseRouteInternal(IRoutingInputs destination, string inputPortKey, bool clearRoute)
/// <param name="destination"></param>
private static void ReleaseRouteInternal(IRoutingInputs destination, string inputPortKey)
{
try
{
@@ -297,7 +242,7 @@ namespace PepperDash.Essentials.Core
if (current != null)
{
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
current.ReleaseRoutes(clearRoute);
current.ReleaseRoutes();
}
} catch (Exception ex)
{

View File

@@ -7,14 +7,8 @@
{
}
/// <summary>
/// For fixed-source endpoint devices with an input port
/// </summary>
public interface IRoutingSinkWithInputPort :IRoutingSink
{
/// <summary>
/// Gets the current input port for this routing sink.
/// </summary>
RoutingInputPort CurrentInputPort { get; }
}
/*/// <summary>

View File

@@ -1,7 +1,7 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Marker interface to identify a device that acts as the origin of a signal path (<see cref="IRoutingOutputs"/>).
/// Defines an IRoutingOutputs devices as being a source - the start of the chain
/// </summary>
public interface IRoutingSource : IRoutingOutputs
{

View File

@@ -1,8 +1,5 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines a routing device (<see cref="IRouting"/>) that supports explicitly clearing a route on an output.
/// </summary>
public interface IRoutingWithClear : IRouting
{
/// <summary>

View File

@@ -3,25 +3,14 @@ using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Delegate for handling route change events on devices implementing <see cref="IRoutingWithFeedback"/>.
/// </summary>
/// <param name="midpoint">The routing device where the change occurred.</param>
/// <param name="newRoute">A descriptor of the new route that was established.</param>
public delegate void RouteChangedEventHandler(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute);
/// <summary>
/// Defines a routing device (<see cref="IRouting"/>) that provides feedback about its current routes.
/// Defines an IRouting with a feedback event
/// </summary>
public interface IRoutingWithFeedback : IRouting
{
/// <summary>
/// Gets a list describing the currently active routes on this device.
/// </summary>
List<RouteSwitchDescriptor> CurrentRoutes { get; }
/// <summary>
/// Event triggered when a route changes on this device.
/// </summary>
event RouteChangedEventHandler RouteChanged;
}
}

View File

@@ -1,18 +1,8 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a routing device (typically a transmitter or source) that provides numeric feedback for its current route.
/// Extends <see cref="IRoutingNumeric"/>.
/// </summary>
public interface ITxRouting : IRoutingNumeric
{
/// <summary>
/// Feedback indicating the currently routed video source by its numeric identifier.
/// </summary>
IntFeedback VideoSourceNumericFeedback { get; }
/// <summary>
/// Feedback indicating the currently routed audio source by its numeric identifier.
/// </summary>
IntFeedback AudioSourceNumericFeedback { get; }
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharpPro;
@@ -10,63 +9,35 @@ using Serilog.Events;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a collection of individual route steps between a Source and a Destination device for a specific signal type.
/// Represents an collection of individual route steps between Source and Destination
/// </summary>
public class RouteDescriptor
{
/// <summary>
/// The destination device (sink or midpoint) for the route.
/// </summary>
public IRoutingInputs Destination { get; private set; }
/// <summary>
/// The specific input port on the destination device used for this route. Can be null if not specified or applicable.
/// </summary>
public RoutingInputPort InputPort { get; private set; }
/// <summary>
/// The source device for the route.
/// </summary>
public IRoutingOutputs Source { get; private set; }
/// <summary>
/// The type of signal being routed (e.g., Audio, Video). This descriptor represents a single signal type.
/// </summary>
public eRoutingSignalType SignalType { get; private set; }
/// <summary>
/// A list of individual switching steps required to establish the route.
/// </summary>
public List<RouteSwitchDescriptor> Routes { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="RouteDescriptor"/> class for a route without a specific destination input port.
/// </summary>
/// <param name="source">The source device.</param>
/// <param name="destination">The destination device.</param>
/// <param name="signalType">The type of signal being routed.</param>
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) : this(source, destination, null, signalType)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RouteDescriptor"/> class for a route with a specific destination input port.
/// </summary>
/// <param name="source">The source device.</param>
/// <param name="destination">The destination device.</param>
/// <param name="inputPort">The destination input port (optional).</param>
/// <param name="signalType">The signal type for this route.</param>
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType)
{
Destination = destination;
InputPort = inputPort;
Source = source;
SignalType = signalType;
InputPort = inputPort;
Routes = new List<RouteSwitchDescriptor>();
}
/// <summary>
/// Executes all the switching steps defined in the <see cref="Routes"/> list.
/// Executes all routes described in this collection. Typically called via
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
/// </summary>
public void ExecuteRoutes()
{
@@ -92,27 +63,15 @@ namespace PepperDash.Essentials.Core
}
/// <summary>
/// Releases the usage tracking for the route and optionally clears the route on the switching devices.
/// Releases all routes in this collection. Typically called via
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
/// </summary>
/// <param name="clearRoute">If true, attempts to clear the route on the switching devices (e.g., set input to null/0).</param>
public void ReleaseRoutes(bool clearRoute = false)
public void ReleaseRoutes()
{
foreach (var route in Routes.Where(r => r.SwitchingDevice is IRouting))
{
if (route.SwitchingDevice is IRouting switchingDevice)
{
if(clearRoute)
{
try
{
switchingDevice.ExecuteSwitch(null, route.OutputPort.Selector, SignalType);
}
catch (Exception e)
{
Debug.LogError("Error executing switch: {exception}", e.Message);
}
}
if (route.OutputPort == null)
{
continue;
@@ -131,10 +90,6 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Returns a string representation of the route descriptor, including source, destination, and individual route steps.
/// </summary>
/// <returns>A string describing the route.</returns>
public override string ToString()
{
var routesText = Routes.Select(r => r.ToString()).ToArray();

View File

@@ -4,42 +4,15 @@ using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a request to establish a route between a source and a destination device.
/// </summary>
public class RouteRequest
{
/// <summary>
/// The specific input port on the destination device to use for the route. Can be null if the port should be automatically determined or is not applicable.
/// </summary>
public RoutingInputPort DestinationPort { get; set; }
/// <summary>
/// The specific output port on the source device to use for the route. Can be null if the port should be automatically determined or is not applicable.
/// </summary>
public RoutingOutputPort SourcePort { get; set; }
/// <summary>
/// The destination device (sink or midpoint) for the route.
/// </summary>
public IRoutingInputs Destination { get; set; }
/// <summary>
/// The source device for the route.
/// </summary>
public IRoutingOutputs Source { get; set; }
/// <summary>
/// The type of signal being routed (e.g., Audio, Video, AudioVideo).
/// </summary>
public eRoutingSignalType SignalType { get; set; }
/// <summary>
/// Handles the route request after a device's cooldown period has finished.
/// This method is typically subscribed to the IsCoolingDownFeedback.OutputChange event.
/// </summary>
/// <param name="sender">The object that triggered the event (usually the cooling device).</param>
/// <param name="args">Event arguments indicating the cooldown state change.</param>
public void HandleCooldown(object sender, FeedbackEventArgs args)
{
try
@@ -66,10 +39,6 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Returns a string representation of the route request.
/// </summary>
/// <returns>A string describing the source and destination of the route request.</returns>
public override string ToString()
{
return $"Route {Source?.Key ?? "No Source Device"}:{SourcePort?.Key ?? "auto"} to {Destination?.Key ?? "No Destination Device"}:{DestinationPort?.Key ?? "auto"}";

View File

@@ -5,34 +5,17 @@ using Serilog.Events;
namespace PepperDash.Essentials.Core.Routing
{
/// <summary>
/// Represents an item in the route request queue.
/// </summary>
public class RouteRequestQueueItem : IQueueMessage
{
/// <summary>
/// The action to perform for the route request.
/// </summary>
private readonly Action<RouteRequest> action;
/// <summary>
/// The route request data.
/// </summary>
private readonly RouteRequest routeRequest;
/// <summary>
/// Initializes a new instance of the <see cref="RouteRequestQueueItem"/> class.
/// </summary>
/// <param name="routeAction">The action to perform.</param>
/// <param name="request">The route request data.</param>
public RouteRequestQueueItem(Action<RouteRequest> routeAction, RouteRequest request)
{
action = routeAction;
routeRequest = request;
}
/// <summary>
/// Dispatches the route request action.
/// </summary>
public void Dispatch()
{
Debug.LogMessage(LogEventLevel.Information, "Dispatching route request {routeRequest}", null, routeRequest);
@@ -40,50 +23,23 @@ namespace PepperDash.Essentials.Core.Routing
}
}
/// <summary>
/// Represents an item in the queue for releasing a route.
/// </summary>
public class ReleaseRouteQueueItem : IQueueMessage
{
/// <summary>
/// The action to perform for releasing the route.
/// </summary>
private readonly Action<IRoutingInputs, string, bool> action;
/// <summary>
/// The destination device whose route is being released.
/// </summary>
private readonly Action<IRoutingInputs, string> action;
private readonly IRoutingInputs destination;
/// <summary>
/// The specific input port key on the destination to release, or null/empty for any/default.
/// </summary>
private readonly string inputPortKey;
/// <summary>
/// Indicates whether to clear the route (send null) or just release the usage tracking.
/// </summary>
private readonly bool clearRoute;
/// <summary>
/// Initializes a new instance of the <see cref="ReleaseRouteQueueItem"/> class.
/// </summary>
/// <param name="action">The action to perform.</param>
/// <param name="destination">The destination device.</param>
/// <param name="inputPortKey">The input port key.</param>
/// <param name="clearRoute">True to clear the route, false to just release.</param>
public ReleaseRouteQueueItem(Action<IRoutingInputs, string, bool> action, IRoutingInputs destination, string inputPortKey, bool clearRoute)
public ReleaseRouteQueueItem(Action<IRoutingInputs, string> action, IRoutingInputs destination, string inputPortKey)
{
this.action = action;
this.destination = destination;
this.inputPortKey = inputPortKey;
this.clearRoute = clearRoute;
}
/// <summary>
/// Dispatches the release route action.
/// </summary>
public void Dispatch()
{
Debug.LogMessage(LogEventLevel.Information, "Dispatching release route request for {destination}:{inputPortKey}", null, destination?.Key ?? "no destination", string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
action(destination, inputPortKey, clearRoute);
action(destination, inputPortKey);
}
}
}

View File

@@ -1,47 +1,25 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a single switching step within a larger route, detailing the switching device, input port, and optionally the output port.
/// Represents an individual link for a route
/// </summary>
public class RouteSwitchDescriptor
{
/// <summary>
/// The device performing the switch (derived from the InputPort's parent).
/// </summary>
public IRoutingInputs SwitchingDevice { get { return InputPort?.ParentDevice; } }
/// <summary>
/// The output port being switched from (relevant for matrix switchers). Null for sink devices.
/// </summary>
public RoutingOutputPort OutputPort { get; set; }
/// <summary>
/// The input port being switched to.
/// </summary>
public RoutingInputPort InputPort { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="RouteSwitchDescriptor"/> class for sink devices (no output port).
/// </summary>
/// <param name="inputPort">The input port being switched to.</param>
public RouteSwitchDescriptor(RoutingInputPort inputPort)
{
InputPort = inputPort;
}
/// <summary>
/// Initializes a new instance of the <see cref="RouteSwitchDescriptor"/> class for matrix switchers.
/// </summary>
/// <param name="outputPort">The output port being switched from.</param>
/// <param name="inputPort">The input port being switched to.</param>
public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort)
{
InputPort = inputPort;
OutputPort = outputPort;
}
/// <summary>
/// Returns a string representation of the route switch descriptor.
/// </summary>
/// <returns>A string describing the switch operation.</returns>
public override string ToString()
{
if (SwitchingDevice is IRouting)

View File

@@ -5,17 +5,8 @@ using System.Linq;
namespace PepperDash.Essentials.Core.Routing
{
/// <summary>
/// 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
{
/// <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)
{
AddPreActivationAction(SubscribeForMidpointFeedback);
@@ -23,9 +14,6 @@ namespace PepperDash.Essentials.Core.Routing
}
/// <summary>
/// Subscribes to the RouteChanged event on all devices implementing <see cref="IRoutingWithFeedback"/>.
/// </summary>
private void SubscribeForMidpointFeedback()
{
var midpointDevices = DeviceManager.AllDevices.OfType<IRoutingWithFeedback>();
@@ -36,9 +24,6 @@ namespace PepperDash.Essentials.Core.Routing
}
}
/// <summary>
/// Subscribes to the InputChanged event on all devices implementing <see cref="IRoutingSinkWithSwitchingWithInputPort"/>.
/// </summary>
private void SubscribeForSinkFeedback()
{
var sinkDevices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
@@ -49,12 +34,6 @@ namespace PepperDash.Essentials.Core.Routing
}
}
/// <summary>
/// Handles the RouteChanged event from a midpoint device.
/// Triggers an update for all sink devices.
/// </summary>
/// <param name="midpoint">The midpoint device that reported a route change.</param>
/// <param name="newRoute">The descriptor of the new route.</param>
private void HandleMidpointUpdate(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute)
{
try
@@ -72,12 +51,6 @@ namespace PepperDash.Essentials.Core.Routing
}
}
/// <summary>
/// Handles the InputChanged event from a sink device.
/// Triggers an update for the specific sink device.
/// </summary>
/// <param name="sender">The sink device that reported an input change.</param>
/// <param name="currentInputPort">The new input port selected on the sink device.</param>
private void HandleSinkUpdate(IRoutingSinkWithSwitching sender, RoutingInputPort currentInputPort)
{
try
@@ -90,12 +63,6 @@ namespace PepperDash.Essentials.Core.Routing
}
}
/// <summary>
/// Updates the CurrentSourceInfo and CurrentSourceInfoKey properties on a destination (sink) device
/// based on its currently selected input port by tracing the route back through tie lines.
/// </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)
{
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this,destination?.Key, inputPort?.Key);
@@ -232,12 +199,6 @@ namespace PepperDash.Essentials.Core.Routing
}
/// <summary>
/// Recursively traces a route back from a given tie line to find the root source tie line.
/// It navigates through midpoint devices (<see cref="IRoutingWithFeedback"/>) by checking their current routes.
/// </summary>
/// <param name="tieLine">The starting tie line (typically connected to a sink or midpoint).</param>
/// <returns>The <see cref="TieLine"/> connected to the original source device, or null if the source cannot be determined.</returns>
private TieLine GetRootTieLine(TieLine tieLine)
{
TieLine nextTieLine = null;

View File

@@ -5,7 +5,7 @@ using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a basic routing input port on a device.
/// Basic RoutingInput with no statuses.
/// </summary>
public class RoutingInputPort : RoutingPort
{
@@ -41,10 +41,6 @@ namespace PepperDash.Essentials.Core
ParentDevice = parent;
}
/// <summary>
/// Returns a string representation of the input port.
/// </summary>
/// <returns>A string in the format "ParentDeviceKey|PortKey|SignalType|ConnectionType".</returns>
public override string ToString()
{
return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}";

View File

@@ -1,25 +1,24 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a routing input port that provides video status feedback (e.g., sync, resolution).
/// Suitable for devices like DM transmitters or DM input cards.
/// A RoutingInputPort for devices like DM-TX and DM input cards.
/// Will provide video statistics on connected signals
/// </summary>
public class RoutingInputPortWithVideoStatuses : RoutingInputPort
{
/// <summary>
/// Provides feedback outputs for video statuses associated with this port.
/// Video statuses attached to this port
/// </summary>
public VideoStatusOutputs VideoStatus { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="RoutingInputPortWithVideoStatuses"/> class.
/// Constructor
/// </summary>
/// <param name="key">The unique key for this port.</param>
/// <param name="type">The signal type supported by this port.</param>
/// <param name="connType">The physical connection type of this port.</param>
/// <param name="selector">An object used to refer to this port in the parent device's ExecuteSwitch method.</param>
/// <param name="parent">The <see cref="IRoutingInputs"/> device this port belongs to.</param>
/// <param name="funcs">A <see cref="VideoStatusFuncsWrapper"/> containing delegates to retrieve video status values.</param>
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
/// May be string, number, whatever</param>
/// <param name="parent">The IRoutingInputs object this lives on</param>
/// <param name="funcs">A VideoStatusFuncsWrapper used to assign the callback funcs that will get
/// the values for the various stats</param>
public RoutingInputPortWithVideoStatuses(string key,
eRoutingSignalType type, eRoutingPortConnectionType connType, object selector,
IRoutingInputs parent, VideoStatusFuncsWrapper funcs) :

View File

@@ -3,72 +3,32 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Provides event arguments for routing changes, potentially including numeric or port object references.
/// </summary>
public class RoutingNumericEventArgs : EventArgs
{
/// <summary>
/// The numeric representation of the output, if applicable.
/// </summary>
public uint? Output { get; set; }
/// <summary>
/// The numeric representation of the input, if applicable.
/// </summary>
public uint? Input { get; set; }
/// <summary>
/// The type of signal involved in the routing change.
/// </summary>
public eRoutingSignalType SigType { get; set; }
/// <summary>
/// The input port involved in the routing change, if applicable.
/// </summary>
public RoutingInputPort InputPort { get; set; }
/// <summary>
/// The output port involved in the routing change, if applicable.
/// </summary>
public RoutingOutputPort OutputPort { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="RoutingNumericEventArgs"/> class using numeric identifiers.
/// </summary>
/// <param name="output">The numeric output identifier.</param>
/// <param name="input">The numeric input identifier.</param>
/// <param name="sigType">The signal type.</param>
public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RoutingNumericEventArgs"/> class using port objects.
/// </summary>
/// <param name="outputPort">The output port object.</param>
/// <param name="inputPort">The input port object.</param>
/// <param name="sigType">The signal type.</param>
public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort,
eRoutingSignalType sigType)
: this(null, null, outputPort, inputPort, sigType)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RoutingNumericEventArgs"/> class with default values.
/// </summary>
public RoutingNumericEventArgs()
: this(null, null, null, null, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RoutingNumericEventArgs"/> class with potentially mixed identifiers.
/// </summary>
/// <param name="output">The numeric output identifier (optional).</param>
/// <param name="input">The numeric input identifier (optional).</param>
/// <param name="outputPort">The output port object (optional).</param>
/// <param name="inputPort">The input port object (optional).</param>
/// <param name="sigType">The signal type.</param>
public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort,
RoutingInputPort inputPort, eRoutingSignalType sigType)
{

View File

@@ -4,46 +4,29 @@ using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a basic routing output port on a device.
/// </summary>
public class RoutingOutputPort : RoutingPort
{
/// <summary>
/// The IRoutingOutputs object this port lives on.
/// The IRoutingOutputs object this port lives on
/// </summary>
///
[JsonIgnore]
public IRoutingOutputs ParentDevice { get; private set; }
/// <summary>
/// Tracks which destinations are currently using this output port.
/// </summary>
public InUseTracking InUseTracker { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="RoutingOutputPort"/> class.
/// </summary>
/// <param name="key">The unique key for this port.</param>
/// <param name="type">The signal type supported by this port.</param>
/// <param name="connType">The physical connection type of this port.</param>
/// <param name="selector">An object used to refer to this port in the parent device's ExecuteSwitch method.</param>
/// <param name="parent">The <see cref="IRoutingOutputs"/> device this port belongs to.</param>
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
/// May be string, number, whatever</param>
/// <param name="parent">The IRoutingOutputs object this port lives on</param>
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
object selector, IRoutingOutputs parent)
: this(key, type, connType, selector, parent, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RoutingOutputPort"/> class, potentially marking it as internal.
/// </summary>
/// <param name="key">The unique key for this port.</param>
/// <param name="type">The signal type supported by this port.</param>
/// <param name="connType">The physical connection type of this port.</param>
/// <param name="selector">An object used to refer to this port in the parent device's ExecuteSwitch method.</param>
/// <param name="parent">The <see cref="IRoutingOutputs"/> device this port belongs to.</param>
/// <param name="isInternal">True if this port represents an internal connection within a device (e.g., card to backplane).</param>
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
object selector, IRoutingOutputs parent, bool isInternal)
: base(key, type, connType, selector, isInternal)
@@ -52,10 +35,6 @@ namespace PepperDash.Essentials.Core
InUseTracker = new InUseTracking();
}
/// <summary>
/// Returns a string representation of the output port.
/// </summary>
/// <returns>A string in the format "ParentDeviceKey|PortKey|SignalType|ConnectionType".</returns>
public override string ToString()
{
return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}";

View File

@@ -4,47 +4,18 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Base class for <see cref="RoutingInputPort"/> and <see cref="RoutingOutputPort"/>.
/// Base class for RoutingInput and Output ports
/// </summary>
public abstract class RoutingPort : IKeyed
{
/// <summary>
/// The unique key identifying this port within its parent device.
/// </summary>
public string Key { get; private set; }
/// <summary>
/// The type of signal this port handles (e.g., Audio, Video, AudioVideo).
/// </summary>
public eRoutingSignalType Type { get; private set; }
/// <summary>
/// The physical connection type of this port (e.g., Hdmi, Rca, Dm).
/// </summary>
public eRoutingPortConnectionType ConnectionType { get; private set; }
/// <summary>
/// An object (often a number or string) used by the parent routing device to select this port during switching.
/// </summary>
public readonly object Selector;
/// <summary>
/// Indicates if this port represents an internal connection within a device (e.g., card to backplane).
/// </summary>
public bool IsInternal { get; private set; }
/// <summary>
/// An object used to match feedback values to this port, if applicable.
/// </summary>
public object FeedbackMatchObject { get; set; }
/// <summary>
/// A reference to the underlying hardware port object (e.g., SimplSharpPro Port), if applicable.
/// </summary>
public object Port { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="RoutingPort"/> class.
/// </summary>
/// <param name="key">The unique key for this port.</param>
/// <param name="type">The signal type supported by this port.</param>
/// <param name="connType">The physical connection type of this port.</param>
/// <param name="selector">The selector object for switching.</param>
/// <param name="isInternal">True if this port is internal.</param>
public RoutingPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, object selector, bool isInternal)
{
Key = key;

View File

@@ -7,8 +7,7 @@ using Crestron.SimplSharp;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines constant string values for common routing port keys.
/// These should correspond directly with the portNames var in the config tool.
/// These should correspond directly with the portNames var in the config tool.
/// </summary>
public class RoutingPortNames
{

View File

@@ -4,23 +4,14 @@ using System.Collections.Generic;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a connection (tie line) between a <see cref="RoutingOutputPort"/> and a <see cref="RoutingInputPort"/>.
/// </summary>
public class TieLine
{
/// <summary>
/// The source output port of the tie line.
/// </summary>
public RoutingOutputPort SourcePort { get; private set; }
/// <summary>
/// The destination input port of the tie line.
/// </summary>
public RoutingInputPort DestinationPort { get; private set; }
//public int InUseCount { get { return DestinationUsingThis.Count; } }
/// <summary>
/// Gets the type of this tie line. Will either be the type of the destination port
/// Gets the type of this tie line. Will either be the type of the desination port
/// or the type of OverrideType when it is set.
/// </summary>
public eRoutingSignalType Type
@@ -44,27 +35,20 @@ namespace PepperDash.Essentials.Core
//List<IRoutingInputs> DestinationUsingThis = new List<IRoutingInputs>();
/// <summary>
/// Gets a value indicating whether this tie line represents an internal connection within a device (both source and destination ports are internal).
/// For tie lines that represent internal links, like from cards to the matrix in a DM.
/// This property is true if SourcePort and DestinationPort IsInternal
/// property are both true
/// </summary>
public bool IsInternal { get { return SourcePort.IsInternal && DestinationPort.IsInternal; } }
/// <summary>
/// Gets a value indicating whether the signal types of the source and destination ports differ.
/// </summary>
public bool TypeMismatch { get { return SourcePort.Type != DestinationPort.Type; } }
/// <summary>
/// Gets a value indicating whether the connection types of the source and destination ports differ.
/// </summary>
public bool ConnectionTypeMismatch { get { return SourcePort.ConnectionType != DestinationPort.ConnectionType; } }
/// <summary>
/// A descriptive note about any type mismatch, if applicable.
/// </summary>
public string TypeMismatchNote { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="TieLine"/> class.
///
/// </summary>
/// <param name="sourcePort">The source output port.</param>
/// <param name="destinationPort">The destination input port.</param>
/// <param name="sourcePort"></param>
/// <param name="destinationPort"></param>
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort)
{
if (sourcePort == null || destinationPort == null)
@@ -74,11 +58,9 @@ namespace PepperDash.Essentials.Core
}
/// <summary>
/// Creates a tie line with an overriding Type. See help for OverrideType property for info.
/// Creates a tie line with an overriding Type. See help for OverrideType property for info
/// </summary>
/// <param name="sourcePort">The source output port.</param>
/// <param name="destinationPort">The destination input port.</param>
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type for routing calculations.</param>
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type</param>
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType? overrideType) :
this(sourcePort, destinationPort)
{
@@ -86,11 +68,9 @@ namespace PepperDash.Essentials.Core
}
/// <summary>
/// Creates a tie line with an overriding Type. See help for OverrideType property for info.
/// Creates a tie line with an overriding Type. See help for OverrideType property for info
/// </summary>
/// <param name="sourcePort">The source output port.</param>
/// <param name="destinationPort">The destination input port.</param>
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type for routing calculations.</param>
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type</param>
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType overrideType) :
this(sourcePort, destinationPort)
{
@@ -98,25 +78,18 @@ namespace PepperDash.Essentials.Core
}
/// <summary>
/// Will link up video status from supporting inputs to connected outputs.
/// Will link up video status from supporting inputs to connected outputs
/// </summary>
public void Activate()
{
// Now does nothing
}
/// <summary>
/// Deactivates the tie line.
/// </summary>
public void Deactivate()
{
// Now does nothing
}
/// <summary>
/// Returns a string representation of the tie line.
/// </summary>
/// <returns>A string describing the source, destination, and type of the tie line.</returns>
public override string ToString()
{
return string.Format("Tie line: {0}:{1} --> {2}:{3} {4}", SourcePort.ParentDevice.Key, SourcePort.Key,
@@ -126,14 +99,8 @@ namespace PepperDash.Essentials.Core
//********************************************************************************
/// <summary>
/// Represents a collection of <see cref="TieLine"/> objects.
/// </summary>
public class TieLineCollection : List<TieLine>
{
/// <summary>
/// Gets the default singleton instance of the <see cref="TieLineCollection"/>.
/// </summary>
public static TieLineCollection Default
{
get
@@ -144,9 +111,6 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Backing field for the singleton instance.
/// </summary>
[JsonIgnore]
private static TieLineCollection _Default;
}

View File

@@ -1,4 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
@@ -13,44 +15,15 @@ using Serilog.Events;
namespace PepperDash.Essentials.Core.Config
{
/// <summary>
/// Represents the configuration data for a single tie line between two routing ports.
/// </summary>
public class TieLineConfig
{
/// <summary>
/// The key of the source device.
/// </summary>
public string SourceKey { get; set; }
/// <summary>
/// The key of the source card (if applicable, e.g., in a modular chassis).
/// </summary>
public string SourceCard { get; set; }
/// <summary>
/// The key of the source output port.
/// </summary>
public string SourcePort { get; set; }
/// <summary>
/// The key of the destination device.
/// </summary>
public string DestinationKey { get; set; }
/// <summary>
/// The key of the destination card (if applicable).
/// </summary>
public string DestinationCard { get; set; }
/// <summary>
/// The key of the destination input port.
/// </summary>
public string DestinationPort { get; set; }
/// <summary>
/// Optional override for the signal type of the tie line. If set, this overrides the destination port's type for routing calculations.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
public eRoutingSignalType? OverrideType { get; set; }
@@ -100,19 +73,11 @@ namespace PepperDash.Essentials.Core.Config
return new TieLine(sourceOutputPort, destinationInputPort, OverrideType);
}
/// <summary>
/// Logs an error message related to creating this tie line configuration.
/// </summary>
/// <param name="msg">The specific error message.</param>
void LogError(string msg)
{
Debug.LogMessage(LogEventLevel.Error, "WARNING: Cannot create tie line: {message}:\r {tieLineConfig}",null, msg, this);
}
/// <summary>
/// Returns a string representation of the tie line configuration.
/// </summary>
/// <returns>A string describing the source and destination of the configured tie line.</returns>
public override string ToString()
{
return string.Format("{0}.{1}.{2} --> {3}.{4}.{5}", SourceKey, SourceCard, SourcePort,

View File

@@ -227,7 +227,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
SendCameraPresetNamesToApi(presetsCamera, joinMap, trilist);
for (int i = 0; i < joinMap.PresetRecallStart.JoinSpan; i++)
for (int i = 0; i < joinMap.NumberOfPresets.JoinSpan; i++)
{
int tempNum = i;

View File

@@ -1,95 +0,0 @@
using PepperDash.Core;
using PepperDash.Essentials.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Devices.Common.Codec.Cisco
{
/// <summary>
/// Describes the available tracking modes for a Cisco codec's Presenter Track feature.
/// </summary>
public enum ePresenterTrackMode
{
/// <summary>
/// Presenter Track is turned off.
/// </summary>
Off,
/// <summary>
/// Presenter Track follows the speaker's movements.
/// </summary>
Follow,
/// <summary>
/// Presenter Track is set to background mode, where it tracks the speaker but does not actively follow.
/// </summary>
Background,
/// <summary>
/// Presenter Track is set to persistent mode, where it maintains a fixed position or focus on the speaker.
/// </summary>
Persistent
}
/// <summary>
/// Describes the Presenter Track controls for a Cisco codec.
/// </summary>
public interface IPresenterTrack : IKeyed
{
/// <summary>
///
/// </summary>
bool PresenterTrackAvailability { get; }
/// <summary>
/// Feedback indicating whether Presenter Track is available.
/// </summary>
BoolFeedback PresenterTrackAvailableFeedback { get; }
/// <summary>
/// Feedback indicating the current status of Presenter Track is off
/// </summary>
BoolFeedback PresenterTrackStatusOffFeedback { get; }
/// <summary>
/// Feedback indicating the current status of Presenter Track is follow
/// </summary>
BoolFeedback PresenterTrackStatusFollowFeedback { get; }
/// <summary>
/// Feedback indicating the current status of Presenter Track is background
/// </summary>
BoolFeedback PresenterTrackStatusBackgroundFeedback { get; }
/// <summary>
/// Feedback indicating the current status of Presenter Track is persistent
/// </summary>
BoolFeedback PresenterTrackStatusPersistentFeedback { get; }
/// <summary>
/// Indicates the current status of Presenter Track.
/// </summary>
bool PresenterTrackStatus { get; }
/// <summary>
/// Turns off Presenter Track.
/// </summary>
void PresenterTrackOff();
/// <summary>
/// Turns on Presenter Track in follow mode.
/// </summary>
void PresenterTrackFollow();
/// <summary>
/// Turns on Presenter Track in background mode.
/// </summary>
void PresenterTrackBackground();
/// <summary>
/// Turns on Presenter Track in persistent mode.
/// </summary>
void PresenterTrackPersistent();
}
}

View File

@@ -1,40 +0,0 @@
using PepperDash.Core;
using PepperDash.Essentials.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Devices.Common.Codec.Cisco
{
/// <summary>
/// Describes the available tracking modes for a Cisco codec
/// </summary>
public interface ISpeakerTrack : IKeyed
{
/// <summary>
/// Indicates whether Speaker Track is available on the codec.
/// </summary>
bool SpeakerTrackAvailability { get; }
/// <summary>
///
/// </summary>
BoolFeedback SpeakerTrackAvailableFeedback { get; }
/// <summary>
/// Feedback indicating the current status of Speaker Track is off
/// </summary>
bool SpeakerTrackStatus { get; }
/// <summary>
/// Turns Speaker Track off
/// </summary>
void SpeakerTrackOff();
/// <summary>
/// Turns Speaker Track on
/// </summary>
void SpeakerTrackOn();
}
}

View File

@@ -18,13 +18,9 @@ namespace PepperDash.Essentials.Devices.Common.DSP
public Dictionary<string, DspControlPoint> SwitcherControlPoints { get; private set; }
public DspBase(string key, string name) :
base(key, name)
{
LevelControlPoints = new Dictionary<string, IBasicVolumeWithFeedback>();
DialerControlPoints = new Dictionary<string, DspControlPoint>();
SwitcherControlPoints = new Dictionary<string, DspControlPoint>();
public DspBase(string key, string name) :
base(key, name)
{
}

View File

@@ -7,7 +7,7 @@ using System.Collections.Generic;
namespace PepperDash.Essentials.Devices.Common.Generic
{
public class GenericSink : EssentialsDevice, IRoutingSinkWithInputPort
public class GenericSink : EssentialsDevice, IRoutingSink
{
public GenericSink(string key, string name) : base(key, name)
{

View File

@@ -8,10 +8,9 @@ using System.Linq;
namespace PepperDash.Essentials.Devices.Common.SoftCodec
{
public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSinkWithSwitchingWithInputPort
public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingOutputs, IRoutingSinkWithSwitching
{
private RoutingInputPort _currentInputPort;
public RoutingInputPort CurrentInputPort {
get => _currentInputPort;
set

View File

@@ -12,45 +12,20 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
/// </summary>
public interface IHasCodecRoomPresets
{
/// <summary>
/// Event that is raised when the list of room presets has changed.
/// </summary>
event EventHandler<EventArgs> CodecRoomPresetsListHasChanged;
/// <summary>
/// List of near end presets that can be recalled.
/// </summary>
List<CodecRoomPreset> NearEndPresets { get; }
/// <summary>
/// List of far end presets that can be recalled.
/// </summary>
List<CodecRoomPreset> FarEndRoomPresets { get; }
/// <summary>
/// Selects a near end preset by its ID.
/// </summary>
/// <param name="preset"></param>
void CodecRoomPresetSelect(int preset);
/// <summary>
/// Stores a near end preset with the given ID and description.
/// </summary>
/// <param name="preset"></param>
/// <param name="description"></param>
void CodecRoomPresetStore(int preset, string description);
void CodecRoomPresetStore(int preset, string description);
/// <summary>
/// Selects a far end preset by its ID. This is typically used to recall a preset that has been defined on the far end codec.
/// </summary>
/// <param name="preset"></param>
void SelectFarEndPreset(int preset);
}
/// <summary>
/// Static class for converting non-generic RoomPresets to generic CameraPresets.
/// </summary>
public static class RoomPresets
public static class RoomPresets
{
/// <summary>
/// Converts non-generic RoomPresets to generic CameraPresets
@@ -72,13 +47,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
/// </summary>
public class CodecRoomPreset : PresetBase
{
/// <summary>
///
/// </summary>
/// <param name="id"></param>
/// <param name="description"></param>
/// <param name="def"></param>
/// <param name="isDef"></param>
public CodecRoomPreset(int id, string description, bool def, bool isDef)
: base(id, description, def, isDef)
{

View File

@@ -2,69 +2,27 @@
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core.DeviceInfo;
using System.Timers;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Facilitates communication of device information by providing mechanisms for status updates and device
/// information reporting.
/// </summary>
/// <remarks>The <see cref="DeviceInfoMessenger"/> class integrates with an <see
/// cref="IDeviceInfoProvider"/> to manage device-specific information. It uses a debounce timer to limit the
/// frequency of updates, ensuring efficient communication. The timer is initialized with a 1-second interval and
/// is disabled by default. This class also subscribes to device information change events and provides actions for
/// reporting full device status and triggering updates.</remarks>
public class DeviceInfoMessenger : MessengerBase
{
private readonly IDeviceInfoProvider _deviceInfoProvider;
private readonly Timer debounceTimer;
/// <summary>
/// Initializes a new instance of the <see cref="DeviceInfoMessenger"/> class, which facilitates communication
/// of device information.
/// </summary>
/// <remarks>The messenger uses a debounce timer to limit the frequency of certain operations. The
/// timer is initialized with a 1-second interval and is disabled by default.</remarks>
/// <param name="key">A unique identifier for the messenger instance.</param>
/// <param name="messagePath">The path used for sending and receiving messages.</param>
/// <param name="device">An implementation of <see cref="IDeviceInfoProvider"/> that provides device-specific information.</param>
public DeviceInfoMessenger(string key, string messagePath, IDeviceInfoProvider device) : base(key, messagePath, device as Device)
{
_deviceInfoProvider = device;
}
debounceTimer = new Timer(1000)
{
Enabled = false,
AutoReset = false
};
debounceTimer.Elapsed += DebounceTimer_Elapsed;
}
private void DebounceTimer_Elapsed(object sender, ElapsedEventArgs e)
{
PostStatusMessage(JToken.FromObject(new
{
deviceInfo = _deviceInfoProvider.DeviceInfo
}));
}
/// <summary>
/// Registers actions and event handlers for device information updates and status reporting.
/// </summary>
/// <remarks>This method sets up actions for handling device status updates and reporting full
/// device status. It also subscribes to the <see cref="IDeviceInfoProvider.DeviceInfoChanged"/> event to
/// trigger debounced updates when the device information changes.</remarks>
protected override void RegisterActions()
{
base.RegisterActions();
_deviceInfoProvider.DeviceInfoChanged += (o, a) =>
{
debounceTimer.Stop();
debounceTimer.Start();
PostStatusMessage(JToken.FromObject(new
{
deviceInfo = a.DeviceInfo
}));
};
AddAction("/fullStatus", (id, context) => PostStatusMessage(new DeviceInfoStateMessage
@@ -76,12 +34,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
}
}
/// <summary>
/// Represents a message containing the state information of a device, including detailed device information.
/// </summary>
/// <remarks>This class is used to encapsulate the state of a device along with its associated
/// information. It extends <see cref="DeviceStateMessageBase"/> to provide additional details about the
/// device.</remarks>
public class DeviceInfoStateMessage : DeviceStateMessageBase
{
[JsonProperty("deviceInfo")]

View File

@@ -8,42 +8,16 @@ using System.Collections.Generic;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides messaging functionality for managing room combination scenarios and partition states in an <see
/// cref="IEssentialsRoomCombiner"/> instance. Enables external systems to interact with the room combiner via
/// predefined actions and status updates.
/// </summary>
/// <remarks>This class facilitates communication with an <see cref="IEssentialsRoomCombiner"/> by
/// exposing actions for toggling modes, managing partitions, and setting room combination scenarios. It also
/// listens for feedback changes and broadcasts status updates to connected systems. Typical usage involves
/// registering actions for external commands and handling feedback events to synchronize state changes.</remarks>
public class IEssentialsRoomCombinerMessenger : MessengerBase
{
private readonly IEssentialsRoomCombiner _roomCombiner;
/// <summary>
/// Initializes a new instance of the <see cref="IEssentialsRoomCombinerMessenger"/> class, which facilitates
/// messaging for an <see cref="IEssentialsRoomCombiner"/> instance.
/// </summary>
/// <remarks>This class is designed to enable communication and interaction with an <see
/// cref="IEssentialsRoomCombiner"/> through the specified messaging path. Ensure that the <paramref
/// name="roomCombiner"/> parameter is not null when creating an instance.</remarks>
/// <param name="key">The unique key identifying this messenger instance.</param>
/// <param name="messagePath">The path used for messaging operations.</param>
/// <param name="roomCombiner">The <see cref="IEssentialsRoomCombiner"/> instance associated with this messenger.</param>
public IEssentialsRoomCombinerMessenger(string key, string messagePath, IEssentialsRoomCombiner roomCombiner)
: base(key, messagePath, roomCombiner as IKeyName)
{
_roomCombiner = roomCombiner;
}
/// <summary>
/// Registers actions and event handlers for managing room combination scenarios and partition states.
/// </summary>
/// <remarks>This method sets up various actions that can be triggered via specific endpoints,
/// such as toggling modes, setting room combination scenarios, and managing partition states. It also
/// subscribes to feedback events to update the status when changes occur in room combination scenarios or
/// partition states.</remarks>
protected override void RegisterActions()
{
AddAction("/fullStatus", (id, content) => SendFullStatus());
@@ -133,7 +107,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
var message = new IEssentialsRoomCombinerStateMessage
{
DisableAutoMode = _roomCombiner.DisableAutoMode,
IsInAutoMode = _roomCombiner.IsInAutoMode,
CurrentScenario = _roomCombiner.CurrentScenario,
Rooms = rooms,
@@ -159,48 +132,20 @@ namespace PepperDash.Essentials.AppServer.Messengers
}
}
/// <summary>
/// Represents the state message for a room combiner system, providing information about the current configuration,
/// operational mode, and associated rooms, partitions, and scenarios.
/// </summary>
/// <remarks>This class is used to encapsulate the state of a room combiner system, including its current
/// mode of operation, active room combination scenario, and the list of rooms and partitions involved. It is
/// typically serialized and transmitted to communicate the state of the system.</remarks>
public class IEssentialsRoomCombinerStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Gets or sets a value indicating whether automatic mode is disabled.
/// </summary>
[JsonProperty("disableAutoMode", NullValueHandling = NullValueHandling.Ignore)]
public bool DisableAutoMode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the system is operating in automatic mode.
/// </summary>
[JsonProperty("isInAutoMode", NullValueHandling = NullValueHandling.Ignore)]
public bool IsInAutoMode { get; set; }
/// <summary>
/// Gets or sets the current room combination scenario.
/// </summary>
[JsonProperty("currentScenario", NullValueHandling = NullValueHandling.Ignore)]
public IRoomCombinationScenario CurrentScenario { get; set; }
/// <summary>
/// Gets or sets the collection of rooms associated with the entity.
/// </summary>
[JsonProperty("rooms", NullValueHandling = NullValueHandling.Ignore)]
public List<IKeyName> Rooms { get; set; }
/// <summary>
/// Gets or sets the collection of room combination scenarios.
/// </summary>
[JsonProperty("roomCombinationScenarios", NullValueHandling = NullValueHandling.Ignore)]
public List<IRoomCombinationScenario> RoomCombinationScenarios { get; set; }
/// <summary>
/// Gets or sets the collection of partition controllers.
/// </summary>
[JsonProperty("partitions", NullValueHandling = NullValueHandling.Ignore)]
public List<IPartitionController> Partitions { get; set; }
}

View File

@@ -491,7 +491,7 @@ namespace PepperDash.Essentials.Touchpanel
{
public MobileControlTouchpanelControllerFactory()
{
TypeNames = new List<string>() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mcxpanel", "mcdge1000" };
TypeNames = new List<string>() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mcxpanel" };
MinimumEssentialsFrameworkVersion = "2.0.0";
}
@@ -555,10 +555,7 @@ namespace PepperDash.Essentials.Touchpanel
return new Tsw1070(id, Global.ControlSystem);
else if (type == "ts1070")
return new Ts1070(id, Global.ControlSystem);
else if (type == "dge1000")
return new Dge1000(id, Global.ControlSystem);
else
else
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "WARNING: Cannot create TSW controller with type '{0}'", type);
return null;

View File

@@ -1,145 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.AppServer.Messengers;
using PepperDash.Essentials.RoomBridges;
using Serilog.Events;
using System;
using System.Text.RegularExpressions;
using WebSocketSharp;
using WebSocketSharp.Server;
using ErrorEventArgs = WebSocketSharp.ErrorEventArgs;
namespace PepperDash.Essentials.WebSocketServer
{
/// <summary>
/// Represents the behaviour to associate with a UiClient for WebSocket communication
/// </summary>
public class UiClient : WebSocketBehavior
{
public MobileControlSystemController Controller { get; set; }
public string RoomKey { get; set; }
private string _clientId;
private DateTime _connectionTime;
public TimeSpan ConnectedDuration
{
get
{
if (Context.WebSocket.IsAlive)
{
return DateTime.Now - _connectionTime;
}
else
{
return new TimeSpan(0);
}
}
}
public UiClient()
{
}
protected override void OnOpen()
{
base.OnOpen();
var url = Context.WebSocket.Url;
Debug.LogMessage(LogEventLevel.Verbose, "New WebSocket Connection from: {0}", null, url);
var match = Regex.Match(url.AbsoluteUri, "(?:ws|wss):\\/\\/.*(?:\\/mc\\/api\\/ui\\/join\\/)(.*)");
if (!match.Success)
{
_connectionTime = DateTime.Now;
return;
}
var clientId = match.Groups[1].Value;
_clientId = clientId;
if (Controller == null)
{
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");
_connectionTime = DateTime.Now;
}
var clientJoinedMessage = new MobileControlMessage
{
Type = "/system/clientJoined",
Content = JToken.FromObject(new
{
clientId,
roomKey = RoomKey,
})
};
Controller.HandleClientMessage(JsonConvert.SerializeObject(clientJoinedMessage));
var bridge = Controller.GetRoomBridge(RoomKey);
if (bridge == null) return;
SendUserCodeToClient(bridge, clientId);
bridge.UserCodeChanged -= Bridge_UserCodeChanged;
bridge.UserCodeChanged += Bridge_UserCodeChanged;
// TODO: Future: Check token to see if there's already an open session using that token and reject/close the session
}
private void Bridge_UserCodeChanged(object sender, EventArgs e)
{
SendUserCodeToClient((MobileControlEssentialsRoomBridge)sender, _clientId);
}
private void SendUserCodeToClient(MobileControlBridgeBase bridge, string clientId)
{
var content = new
{
userCode = bridge.UserCode,
qrUrl = bridge.QrCodeUrl,
};
var message = new MobileControlMessage
{
Type = "/system/userCodeChanged",
ClientId = clientId,
Content = JToken.FromObject(content)
};
Controller.SendMessageObjectToDirectClient(message);
}
protected override void OnMessage(MessageEventArgs e)
{
base.OnMessage(e);
if (e.IsText && e.Data.Length > 0 && Controller != null)
{
// Forward the message to the controller to be put on the receive queue
Controller.HandleClientMessage(e.Data);
}
}
protected override void OnClose(CloseEventArgs e)
{
base.OnClose(e);
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Closing: {0} reason: {1}", null, e.Code, e.Reason);
}
protected override void OnError(ErrorEventArgs e)
{
base.OnError(e);
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Error: {exception} message: {message}", e.Exception, e.Message);
}
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project>
<EssentialsVersion>$(Version)</EssentialsVersion>
</Project>