Merge pull request #1289 from PepperDash/meter-feedback-interface

meter feedback interface
This commit is contained in:
Andrew Welker
2025-07-21 15:20:29 -05:00
committed by GitHub
10 changed files with 423 additions and 119 deletions

9
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"recommendations": [
"ms-dotnettools.vscode-dotnet-runtime",
"ms-dotnettools.csharp",
"ms-dotnettools.csdevkit",
"vivaxy.vscode-conventional-commits",
"mhutchie.git-graph"
]
}

View File

@@ -2,7 +2,14 @@
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
{
public interface IDisplay: IHasFeedback, IRoutingSinkWithSwitching, IHasPowerControl, IWarmingCooling, IUsageTracking, IKeyName
/// <summary>
/// Interface for display devices that can be controlled and monitored.
/// This interface combines functionality for feedback, routing, power control,
/// warming/cooling, usage tracking, and key name management.
/// It is designed to be implemented by devices that require these capabilities,
/// such as projectors, displays, and other visual output devices.
/// </summary>
public interface IDisplay : IHasFeedback, IRoutingSinkWithSwitching, IHasPowerControl, IWarmingCooling, IUsageTracking, IKeyName
{
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
{
/// <summary>
/// Interface for devices that provide audio meter feedback.
/// This interface is used to standardize access to meter feedback across different devices.
/// </summary>
public interface IMeterFeedback
{
/// <summary>
/// Gets the meter feedback for the device.
/// This property provides an IntFeedback that represents the current audio level or meter value.
/// </summary>
IntFeedback MeterFeedback { get; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
{
/// <summary>
/// Interface for devices that provide state feedback.
/// This interface is used to standardize access to state feedback across different devices.
/// </summary>
public interface IStateFeedback
{
/// <summary>
/// Gets the state feedback for the device.
/// This property provides a BoolFeedback that represents the current state (on/off) of the device.
/// </summary>
BoolFeedback StateFeedback { get; }
}
}

View File

@@ -5,19 +5,34 @@ using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a destination item in a routing system that can receive audio/video signals.
/// Contains information about the destination device, its properties, and location settings.
/// </summary>
public class DestinationListItem
{
/// <summary>
/// Gets or sets the key identifier for the sink device that this destination represents.
/// </summary>
[JsonProperty("sinkKey")]
public string SinkKey { get; set; }
private EssentialsDevice _sinkDevice;
/// <summary>
/// Gets the actual device instance for this destination.
/// Lazily loads the device from the DeviceManager using the SinkKey.
/// </summary>
[JsonIgnore]
public EssentialsDevice SinkDevice
{
get { return _sinkDevice ?? (_sinkDevice = DeviceManager.GetDeviceForKey(SinkKey) as EssentialsDevice); }
}
/// <summary>
/// Gets the preferred display name for this destination.
/// Returns the custom Name if set, otherwise returns the SinkDevice name, or "---" if no device is found.
/// </summary>
[JsonProperty("preferredName")]
public string PreferredName
{
@@ -32,31 +47,71 @@ namespace PepperDash.Essentials.Core
}
}
/// <summary>
/// Gets or sets the custom name for this destination.
/// If set, this name will be used as the PreferredName instead of the device name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this destination should be included in destination lists.
/// </summary>
[JsonProperty("includeInDestinationList")]
public bool IncludeInDestinationList { get; set; }
/// <summary>
/// Gets or sets the display order for this destination in lists.
/// Lower values appear first in sorted lists.
/// </summary>
[JsonProperty("order")]
public int Order { get; set; }
/// <summary>
/// Gets or sets the surface location identifier for this destination.
/// Used to specify which surface or screen this destination is located on.
/// </summary>
[JsonProperty("surfaceLocation")]
public int SurfaceLocation { get; set; }
/// <summary>
/// Gets or sets the vertical location position for this destination.
/// Used for spatial positioning in multi-display configurations.
/// </summary>
[JsonProperty("verticalLocation")]
public int VerticalLocation { get; set; }
/// <summary>
/// Gets or sets the horizontal location position for this destination.
/// Used for spatial positioning in multi-display configurations.
/// </summary>
[JsonProperty("horizontalLocation")]
public int HorizontalLocation { get; set; }
/// <summary>
/// Gets or sets the signal type that this destination can receive (Audio, Video, AudioVideo, etc.).
/// </summary>
[JsonProperty("sinkType")]
public eRoutingSignalType SinkType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this destination is used for codec content sharing.
/// </summary>
[JsonProperty("isCodecContentDestination")]
public bool isCodecContentDestination { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this destination is used for program audio output.
/// </summary>
[JsonProperty("isProgramAudioDestination")]
public bool isProgramAudioDestination { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this destination supports USB connections.
/// Indicates if the destination can handle USB functionality, such as USB signal routing or device connections.
/// This property is used to determine compatibility with USB-based devices or systems.
/// </summary>
[JsonProperty("supportsUsb")]
public bool SupportsUsb { get; set; }
}
}

View File

@@ -1,12 +1,14 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using PepperDash.Core;
using System.Collections.Generic;
namespace PepperDash.Essentials.Core
{
/// <summary>
///
/// Defines the type of source list item, which can be a route, off, or other.
/// This is used to categorize the source list items in a room.
/// The type is serialized to JSON and can be used to determine how the item should be displayed or handled in the UI.
/// </summary>
public enum eSourceListItemType
{
@@ -166,6 +168,19 @@ namespace PepperDash.Essentials.Core
[JsonProperty("disableSimpleRouting")]
public bool DisableSimpleRouting { get; set; }
/// <summary>
/// The key of the device that provides video sync for this source item
/// </summary>
[JsonProperty("syncProviderDeviceKey")]
public string SyncProviderDeviceKey { get; set; }
/// <summary>
/// Indicates if the source supports USB connections
/// </summary>
[JsonProperty("supportsUsb")]
public bool SupportsUsb { get; set; }
/// <summary>
/// Default constructor for SourceListItem, initializes the Icon to "Blank"
/// </summary>
@@ -177,7 +192,7 @@ namespace PepperDash.Essentials.Core
/// <summary>
/// Returns a string representation of the SourceListItem, including the SourceKey and Name
/// </summary>
/// <returns></returns>
/// <returns> A string representation of the SourceListItem</returns>
public override string ToString()
{
return $"{SourceKey}:{Name}";

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Core.Routing
{
/// <summary>
/// The current sources for the room, keyed by eRoutingSignalType.
/// This allows for multiple sources to be tracked, such as audio and video.
/// </summary>
/// <remarks>
/// This interface is used to provide access to the current sources in a room,
/// allowing for more complex routing scenarios where multiple signal types are involved.
/// </remarks>
public interface ICurrentSources
{
/// <summary>
/// Gets the current sources for the room, keyed by eRoutingSignalType.
/// This dictionary contains the current source for each signal type, such as audio, video, and control signals.
/// </summary>
Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; }
/// <summary>
/// Gets the current source keys for the room, keyed by eRoutingSignalType.
/// This dictionary contains the keys for the current source for each signal type, such as audio, video, and control signals.
/// </summary>
Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; }
}
}

View File

@@ -9,6 +9,8 @@ using PepperDash.Essentials.Core.Routing;
using PepperDash.Essentials.Core.Routing;
using PepperDash.Essentials.Core.Routing.Interfaces
*/
using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
@@ -21,10 +23,24 @@ namespace PepperDash.Essentials.Core
/// <summary>
/// For rooms with a single presentation source, change event
/// </summary>
[Obsolete("Use ICurrentSources instead")]
public interface IHasCurrentSourceInfoChange
{
/// <summary>
/// The key for the current source info, used to look up the source in the SourceList
/// </summary>
string CurrentSourceInfoKey { get; set; }
/// <summary>
/// The current source info for the room, used to look up the source in the SourceList
/// </summary>
SourceListItem CurrentSourceInfo { get; set; }
/// <summary>
/// Event that is raised when the current source info changes.
/// This is used to notify the system of changes to the current source info.
/// The event handler receives the new source info and the type of change that occurred.
/// </summary>
event SourceInfoChangeHandler CurrentSourceChange;
}
}

View File

@@ -1,29 +1,29 @@
namespace PepperDash.Essentials.Core
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// For fixed-source endpoint devices
/// </summary>
public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange
{
{
}
/// <summary>
/// For fixed-source endpoint devices with an input port
/// </summary>
public interface IRoutingSinkWithInputPort :IRoutingSink
public interface IRoutingSinkWithInputPort : IRoutingSink
{
/// <summary>
/// Gets the current input port for this routing sink.
/// </summary>
RoutingInputPort CurrentInputPort { get; }
}
/*/// <summary>
/// For fixed-source endpoint devices
/// </summary>
public interface IRoutingSink<TSelector> : IRoutingInputs<TSelector>, IHasCurrentSourceInfoChange
{
void UpdateRouteRequest<TOutputSelector>(RouteRequest<TSelector, TOutputSelector> request);
RouteRequest<TSelector, TOutputSelector> GetRouteRequest<TOutputSelector>();
}*/
/// <summary>
/// Interface for routing sinks that have access to the current source information.
/// </summary>
public interface IRoutingSinkWithCurrentSources : IRoutingSink, ICurrentSources
{
}
}

View File

@@ -1,110 +1,199 @@
using Crestron.SimplSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using Feedback = PepperDash.Essentials.Core.Feedback;
namespace PepperDash.Essentials.Devices.Common.Displays
{
public abstract class DisplayBase : EssentialsDevice, IDisplay
/// <summary>
/// Abstract base class for display devices that provides common display functionality
/// including power control, input switching, and routing capabilities.
/// </summary>
public abstract class DisplayBase : EssentialsDevice, IDisplay, ICurrentSources
{
private RoutingInputPort _currentInputPort;
public RoutingInputPort CurrentInputPort
{
get
{
return _currentInputPort;
}
private RoutingInputPort _currentInputPort;
protected set
{
if (_currentInputPort == value) return;
/// <summary>
/// Gets or sets the current input port that is selected on the display.
/// </summary>
public RoutingInputPort CurrentInputPort
{
get
{
return _currentInputPort;
}
_currentInputPort = value;
protected set
{
if (_currentInputPort == value) return;
InputChanged?.Invoke(this, _currentInputPort);
}
}
_currentInputPort = value;
public event InputChangedEventHandler InputChanged;
InputChanged?.Invoke(this, _currentInputPort);
}
}
public event SourceInfoChangeHandler CurrentSourceChange;
/// <summary>
/// Event that is raised when the input changes on the display.
/// </summary>
public event InputChangedEventHandler InputChanged;
public string CurrentSourceInfoKey { get; set; }
public SourceListItem CurrentSourceInfo
{
get
{
return _CurrentSourceInfo;
}
set
{
if (value == _CurrentSourceInfo) return;
/// <summary>
/// Event that is raised when the current source information changes.
/// </summary>
public event SourceInfoChangeHandler CurrentSourceChange;
var handler = CurrentSourceChange;
/// <summary>
/// Gets or sets the key of the current source information.
/// </summary>
public string CurrentSourceInfoKey { get; set; }
if (handler != null)
handler(_CurrentSourceInfo, ChangeType.WillChange);
/// <summary>
/// Gets or sets the current source information for the display.
/// </summary>
public SourceListItem CurrentSourceInfo
{
get
{
return _CurrentSourceInfo;
}
set
{
if (value == _CurrentSourceInfo) return;
_CurrentSourceInfo = value;
var handler = CurrentSourceChange;
if (handler != null)
handler(_CurrentSourceInfo, ChangeType.DidChange);
}
}
SourceListItem _CurrentSourceInfo;
if (handler != null)
handler(_CurrentSourceInfo, ChangeType.WillChange);
_CurrentSourceInfo = value;
if (handler != null)
handler(_CurrentSourceInfo, ChangeType.DidChange);
}
}
SourceListItem _CurrentSourceInfo;
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; private set; }
/// <inheritdoc/>
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
/// <summary>
/// Gets feedback indicating whether the display is currently cooling down after being powered off.
/// </summary>
public BoolFeedback IsCoolingDownFeedback { get; protected set; }
/// <summary>
/// Gets feedback indicating whether the display is currently warming up after being powered on.
/// </summary>
public BoolFeedback IsWarmingUpFeedback { get; private set; }
public UsageTracking UsageTracker { get; set; }
/// <summary>
/// Gets or sets the usage tracking instance for monitoring display usage statistics.
/// </summary>
public UsageTracking UsageTracker { get; set; }
/// <summary>
/// Gets or sets the warmup time in milliseconds for the display to become ready after power on.
/// </summary>
public uint WarmupTime { get; set; }
/// <summary>
/// Gets or sets the cooldown time in milliseconds for the display to fully power down.
/// </summary>
public uint CooldownTime { get; set; }
/// <summary>
/// Bool Func that will provide a value for the PowerIsOn Output. Must be implemented
/// by concrete sub-classes
/// Abstract function that must be implemented by derived classes to provide the cooling down feedback value.
/// Must be implemented by concrete sub-classes.
/// </summary>
abstract protected Func<bool> IsCoolingDownFeedbackFunc { get; }
abstract protected Func<bool> IsWarmingUpFeedbackFunc { get; }
/// <summary>
/// Abstract function that must be implemented by derived classes to provide the warming up feedback value.
/// Must be implemented by concrete sub-classes.
/// </summary>
abstract protected Func<bool> IsWarmingUpFeedbackFunc { get; }
/// <summary>
/// Timer used for managing display warmup timing.
/// </summary>
protected CTimer WarmupTimer;
/// <summary>
/// Timer used for managing display cooldown timing.
/// </summary>
protected CTimer CooldownTimer;
#region IRoutingInputs Members
/// <summary>
/// Gets the collection of input ports available on this display device.
/// </summary>
public RoutingPortCollection<RoutingInputPort> InputPorts { get; private set; }
#endregion
protected DisplayBase(string key, string name)
: base(key, name)
/// <summary>
/// Initializes a new instance of the DisplayBase class.
/// </summary>
/// <param name="key">The unique key identifier for this display device.</param>
/// <param name="name">The friendly name for this display device.</param>
protected DisplayBase(string key, string name)
: base(key, name)
{
IsCoolingDownFeedback = new BoolFeedback("IsCoolingDown", IsCoolingDownFeedbackFunc);
IsWarmingUpFeedback = new BoolFeedback("IsWarmingUp", IsWarmingUpFeedbackFunc);
InputPorts = new RoutingPortCollection<RoutingInputPort>();
CurrentSources = new Dictionary<eRoutingSignalType, SourceListItem>
{
{ eRoutingSignalType.Audio, null },
{ eRoutingSignalType.Video, null },
};
CurrentSourceKeys = new Dictionary<eRoutingSignalType, string>
{
{ eRoutingSignalType.Audio, string.Empty },
{ eRoutingSignalType.Video, string.Empty },
};
}
/// <summary>
/// Powers on the display device. Must be implemented by derived classes.
/// </summary>
public abstract void PowerOn();
/// <summary>
/// Powers off the display device. Must be implemented by derived classes.
/// </summary>
public abstract void PowerOff();
/// <summary>
/// Toggles the power state of the display device. Must be implemented by derived classes.
/// </summary>
public abstract void PowerToggle();
public virtual FeedbackCollection<Feedback> Feedbacks
/// <summary>
/// Gets the collection of feedback objects for this display device.
/// </summary>
public virtual FeedbackCollection<Feedback> Feedbacks
{
get
{
return new FeedbackCollection<Feedback>
return new FeedbackCollection<Feedback>
{
IsCoolingDownFeedback,
IsWarmingUpFeedback
@@ -112,30 +201,50 @@ namespace PepperDash.Essentials.Devices.Common.Displays
}
}
public abstract void ExecuteSwitch(object selector);
/// <summary>
/// Executes a switch to the specified input on the display device. Must be implemented by derived classes.
/// </summary>
/// <param name="selector">The selector object that identifies which input to switch to.</param>
public abstract void ExecuteSwitch(object selector);
protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey,
EiscApiAdvanced bridge)
{
var joinMap = new DisplayControllerJoinMap(joinStart);
/// <summary>
/// Links the display device to an API using a trilist, join start, join map key, and bridge.
/// This overload uses serialized join map configuration.
/// </summary>
/// <param name="displayDevice">The display device to link.</param>
/// <param name="trilist">The BasicTriList for communication.</param>
/// <param name="joinStart">The starting join number for the device.</param>
/// <param name="joinMapKey">The key for the join map configuration.</param>
/// <param name="bridge">The EISC API bridge instance.</param>
protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey,
EiscApiAdvanced bridge)
{
var joinMap = new DisplayControllerJoinMap(joinStart);
var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey);
var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey);
if (!string.IsNullOrEmpty(joinMapSerialized))
joinMap = JsonConvert.DeserializeObject<DisplayControllerJoinMap>(joinMapSerialized);
if (!string.IsNullOrEmpty(joinMapSerialized))
joinMap = JsonConvert.DeserializeObject<DisplayControllerJoinMap>(joinMapSerialized);
if (bridge != null)
{
bridge.AddJoinMap(Key, joinMap);
}
else
{
Debug.LogMessage(LogEventLevel.Information,this,"Please update config to use 'eiscapiadvanced' to get all join map features for this device.");
}
if (bridge != null)
{
bridge.AddJoinMap(Key, joinMap);
}
else
{
Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device.");
}
LinkDisplayToApi(displayDevice, trilist, joinMap);
}
}
/// <summary>
/// Links the display device to an API using a trilist and join map.
/// This overload uses a pre-configured join map instance.
/// </summary>
/// <param name="displayDevice">The display device to link.</param>
/// <param name="trilist">The BasicTriList for communication.</param>
/// <param name="joinMap">The join map configuration for the device.</param>
protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, DisplayControllerJoinMap joinMap)
{
Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X"));
@@ -268,68 +377,96 @@ namespace PepperDash.Essentials.Devices.Common.Displays
volumeDisplayWithFeedback.MuteFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.VolumeMuteOff.JoinNumber]);
}
}
}
public abstract class TwoWayDisplayBase : DisplayBase, IRoutingFeedback, IHasPowerControlWithFeedback
/// <summary>
/// Abstract base class for two-way display devices that provide feedback capabilities.
/// Extends DisplayBase with routing feedback and power control feedback functionality.
/// </summary>
public abstract class TwoWayDisplayBase : DisplayBase, IRoutingFeedback, IHasPowerControlWithFeedback
{
public StringFeedback CurrentInputFeedback { get; private set; }
/// <summary>
/// Gets feedback for the current input selection on the display.
/// </summary>
public StringFeedback CurrentInputFeedback { get; private set; }
abstract protected Func<string> CurrentInputFeedbackFunc { get; }
/// <summary>
/// Abstract function that must be implemented by derived classes to provide the current input feedback value.
/// Must be implemented by concrete sub-classes.
/// </summary>
abstract protected Func<string> CurrentInputFeedbackFunc { get; }
public BoolFeedback PowerIsOnFeedback { get; protected set; }
/// <summary>
/// Gets feedback indicating whether the display is currently powered on.
/// </summary>
public BoolFeedback PowerIsOnFeedback { get; protected set; }
abstract protected Func<bool> PowerIsOnFeedbackFunc { get; }
/// <summary>
/// Abstract function that must be implemented by derived classes to provide the power state feedback value.
/// Must be implemented by concrete sub-classes.
/// </summary>
abstract protected Func<bool> PowerIsOnFeedbackFunc { get; }
public static MockDisplay DefaultDisplay
{
get
/// <summary>
/// Gets the default mock display instance for testing and development purposes.
/// </summary>
public static MockDisplay DefaultDisplay
{
get
{
if (_DefaultDisplay == null)
_DefaultDisplay = new MockDisplay("default", "Default Display");
return _DefaultDisplay;
}
}
}
static MockDisplay _DefaultDisplay;
/// <summary>
/// Initializes a new instance of the TwoWayDisplayBase class.
/// </summary>
/// <param name="key">The unique key identifier for this display device.</param>
/// <param name="name">The friendly name for this display device.</param>
public TwoWayDisplayBase(string key, string name)
: base(key, name)
{
CurrentInputFeedback = new StringFeedback(CurrentInputFeedbackFunc);
CurrentInputFeedback = new StringFeedback(CurrentInputFeedbackFunc);
WarmupTime = 7000;
CooldownTime = 15000;
PowerIsOnFeedback = new BoolFeedback("PowerOnFeedback", PowerIsOnFeedbackFunc);
PowerIsOnFeedback = new BoolFeedback("PowerOnFeedback", PowerIsOnFeedbackFunc);
Feedbacks.Add(CurrentInputFeedback);
Feedbacks.Add(PowerIsOnFeedback);
Feedbacks.Add(CurrentInputFeedback);
Feedbacks.Add(PowerIsOnFeedback);
PowerIsOnFeedback.OutputChange += PowerIsOnFeedback_OutputChange;
PowerIsOnFeedback.OutputChange += PowerIsOnFeedback_OutputChange;
}
void PowerIsOnFeedback_OutputChange(object sender, EventArgs e)
{
if (UsageTracker != null)
{
if (PowerIsOnFeedback.BoolValue)
UsageTracker.StartDeviceUsage();
else
UsageTracker.EndDeviceUsage();
}
}
void PowerIsOnFeedback_OutputChange(object sender, EventArgs e)
{
if (UsageTracker != null)
{
if (PowerIsOnFeedback.BoolValue)
UsageTracker.StartDeviceUsage();
else
UsageTracker.EndDeviceUsage();
}
}
public event EventHandler<RoutingNumericEventArgs> NumericSwitchChange;
/// <summary>
/// Event that is raised when a numeric switch change occurs on the display.
/// </summary>
public event EventHandler<RoutingNumericEventArgs> NumericSwitchChange;
/// <summary>
/// Raise an event when the status of a switch object changes.
/// </summary>
/// <param name="e">Arguments defined as IKeyName sender, output, input, and eRoutingSignalType</param>
protected void OnSwitchChange(RoutingNumericEventArgs e)
{
var newEvent = NumericSwitchChange;
if (newEvent != null) newEvent(this, e);
}
/// <summary>
/// Raise an event when the status of a switch object changes.
/// </summary>
/// <param name="e">Arguments defined as IKeyName sender, output, input, and eRoutingSignalType</param>
protected void OnSwitchChange(RoutingNumericEventArgs e)
{
var newEvent = NumericSwitchChange;
if (newEvent != null) newEvent(this, e);
}
}
}