Refactor audio codec and display messaging interfaces

- Introduced IDialerCallStatus interface to streamline call status handling in audio codecs.
- Updated AudioCodecBase to implement IDialerCallStatus, providing a common structure for call status events.
- Added IDisplayCurrentInput interface for display devices to expose current input information.
- Created corresponding messenger classes for IDialerCallStatus and IDisplayCurrentInput to facilitate communication.
- Implemented CameraControlMessenger for camera devices to manage camera controls and presets.
- Added IWarmingCoolingMessenger for warming and cooling devices to handle status updates.
- Updated MessengerFactoryRegistry to register new messenger classes and ensure proper device messaging.
- Cleaned up CameraBase and removed unnecessary comments and whitespace in various files.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Neil Dorin 2026-05-06 17:13:02 -06:00
parent dfc1d2bb21
commit f80aa95649
14 changed files with 185 additions and 60 deletions

View file

@ -6,11 +6,19 @@ using PepperDash.Essentials.Devices.Common.Codec;
namespace PepperDash.Essentials.Devices.Common.AudioCodec; namespace PepperDash.Essentials.Devices.Common.AudioCodec;
public abstract class AudioCodecBase : EssentialsDevice, IHasDialer, IUsageTracking, IAudioCodecInfo /// <summary>
/// Base class for audio codecs. Provides common properties and methods for audio codecs,
/// as well as a common implementation of IDialerCallStatus to allow the AudioCodecBaseMessenger
/// to get call status information without requiring the full AudioCodecBase class.
/// This is useful for devices that have dialer call status information but do not need to implement
/// the full AudioCodecBase class.
/// </summary>
public abstract class AudioCodecBase : EssentialsDevice, IDialerCallStatus, IUsageTracking, IAudioCodecInfo
{ {
/// <inheritdoc />
public event EventHandler<CodecCallStatusItemChangeEventArgs> CallStatusChange; public event EventHandler<CodecCallStatusItemChangeEventArgs> CallStatusChange;
/// <inheritdoc />
public AudioCodecInfo CodecInfo { get; protected set; } public AudioCodecInfo CodecInfo { get; protected set; }
#region IUsageTracking Members #region IUsageTracking Members
@ -41,8 +49,14 @@ public abstract class AudioCodecBase : EssentialsDevice, IHasDialer, IUsageTrack
} }
// In most cases only a single call can be active // In most cases only a single call can be active
/// <inheritdoc />
public List<CodecActiveCallItem> ActiveCalls { get; set; } public List<CodecActiveCallItem> ActiveCalls { get; set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="key"></param>
/// <param name="name"></param>
public AudioCodecBase(string key, string name) public AudioCodecBase(string key, string name)
: base(key, name) : base(key, name)
{ {
@ -83,16 +97,22 @@ public abstract class AudioCodecBase : EssentialsDevice, IHasDialer, IUsageTrack
#region IHasDialer Members #region IHasDialer Members
/// <inheritdoc />
public abstract void Dial(string number); public abstract void Dial(string number);
/// <inheritdoc />
public abstract void EndCall(CodecActiveCallItem activeCall); public abstract void EndCall(CodecActiveCallItem activeCall);
/// <inheritdoc />
public abstract void EndAllCalls(); public abstract void EndAllCalls();
/// <inheritdoc />
public abstract void AcceptCall(CodecActiveCallItem item); public abstract void AcceptCall(CodecActiveCallItem item);
public abstract void RejectCall(CodecActiveCallItem item); public abstract void RejectCall(CodecActiveCallItem item);
public abstract void SendDtmf(string digit); public abstract void SendDtmf(string digit);
#endregion #endregion

View file

@ -0,0 +1,20 @@
using System.Collections.Generic;
using PepperDash.Essentials.Devices.Common.Codec;
namespace PepperDash.Essentials.Devices.Common.AudioCodec;
/// <summary>
/// Defines the contract for a device that has dialer call status information. This is used to provide a common interface for the AudioCodecBaseMessenger to get call status information without requiring the full AudioCodecBase class
/// </summary>
public interface IDialerCallStatus : IHasDialer
{
/// <summary>
///
/// </summary>
AudioCodecInfo CodecInfo { get; }
/// <summary>
/// Gets or sets the list of active calls for the device.
/// </summary>
List<CodecActiveCallItem> ActiveCalls { get; set; }
}

View file

@ -1,6 +1,4 @@
 using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;

View file

@ -0,0 +1,14 @@
using PepperDash.Core;
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
{
/// <summary>
/// Defines the contract for a display device that has current input information. This is used to provide a common interface for the TwoWayDisplayBaseMessenger to get current input information without requiring the full TwoWayDisplayBase class
/// </summary>
public interface IDisplayCurrentInput : IKeyName
{
/// <summary>
/// Gets the Current Input feedback for the display device.
/// </summary>
StringFeedback CurrentInputFeedback { get; }
}

View file

@ -11,7 +11,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// <summary> /// <summary>
/// Messenger for a CameraBase device /// Messenger for a CameraBase device
/// </summary> /// </summary>
public class CameraBaseMessenger<T> : MessengerBase where T : IKeyed public class CameraControlMessenger<T> : MessengerBase where T : IKeyed
{ {
/// <summary> /// <summary>
/// Gets or sets the Camera /// Gets or sets the Camera
@ -24,7 +24,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="camera"></param> /// <param name="camera"></param>
/// <param name="messagePath"></param> /// <param name="messagePath"></param>
public CameraBaseMessenger(string key, T camera, string messagePath) public CameraControlMessenger(string key, T camera, string messagePath)
: base(key, messagePath, camera as IKeyName) : base(key, messagePath, camera as IKeyName)
{ {
if (camera == null) if (camera == null)

View file

@ -1,20 +1,21 @@
using System; using System;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Devices.Common.AudioCodec; using PepperDash.Essentials.Devices.Common.AudioCodec;
using PepperDash.Essentials.Devices.Common.Codec; using PepperDash.Essentials.Devices.Common.Codec;
namespace PepperDash.Essentials.AppServer.Messengers namespace PepperDash.Essentials.AppServer.Messengers
{ {
/// <summary> /// <summary>
/// Provides a messaging bridge for an AudioCodecBase device /// Provides a messaging bridge for an IDialerCallStatus device
/// </summary> /// </summary>
public class AudioCodecBaseMessenger : MessengerBase public class IDialerCallStatusMessenger : MessengerBase
{ {
/// <summary> /// <summary>
/// Device being bridged /// Device being bridged
/// </summary> /// </summary>
public AudioCodecBase Codec { get; private set; } public IDialerCallStatus Codec { get; private set; }
/// <summary> /// <summary>
/// Constuctor /// Constuctor
@ -22,8 +23,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="codec"></param> /// <param name="codec"></param>
/// <param name="messagePath"></param> /// <param name="messagePath"></param>
public AudioCodecBaseMessenger(string key, AudioCodecBase codec, string messagePath) public IDialerCallStatusMessenger(string key, IDialerCallStatus codec, string messagePath)
: base(key, messagePath, codec) : base(key, messagePath, codec as IKeyName)
{ {
Codec = codec ?? throw new ArgumentNullException("codec"); Codec = codec ?? throw new ArgumentNullException("codec");
codec.CallStatusChange += Codec_CallStatusChange; codec.CallStatusChange += Codec_CallStatusChange;

View file

@ -1,25 +1,27 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Devices.Common.Displays; using PepperDash.Essentials.Devices.Common.Displays;
namespace PepperDash.Essentials.AppServer.Messengers namespace PepperDash.Essentials.AppServer.Messengers
{ {
/// <summary> /// <summary>
/// Represents a TwoWayDisplayBaseMessenger /// Represents a messenger for a display device that has current input information.
/// </summary> /// </summary>
public class TwoWayDisplayBaseMessenger : MessengerBase public class IDisplayCurrentInputMessenger : MessengerBase
{ {
private readonly TwoWayDisplayBase _display; private readonly IDisplayCurrentInput _display;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TwoWayDisplayBaseMessenger"/> class. /// Initializes a new instance of the <see cref="IDisplayCurrentInputMessenger"/> class.
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="messagePath"></param> /// <param name="messagePath"></param>
/// <param name="display"></param> /// <param name="display"></param>
public TwoWayDisplayBaseMessenger(string key, string messagePath, TwoWayDisplayBase display) public IDisplayCurrentInputMessenger(string key, string messagePath, IDisplayCurrentInput display)
: base(key, messagePath, display) : base(key, messagePath, display as IKeyName)
{ {
_display = display; _display = display;
} }
@ -31,9 +33,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// </summary> /// </summary>
public void SendFullStatus(string id = null) public void SendFullStatus(string id = null)
{ {
var messageObj = new TwoWayDisplayBaseStateMessage var messageObj = new CurrentInputStateMessage
{ {
//PowerState = _display.PowerIsOnFeedback.BoolValue,
CurrentInput = _display.CurrentInputFeedback.StringValue CurrentInput = _display.CurrentInputFeedback.StringValue
}; };
@ -47,11 +48,9 @@ namespace PepperDash.Essentials.AppServer.Messengers
AddAction("/fullStatus", (id, content) => SendFullStatus(id)); AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/displayStatus", (id, content) => SendFullStatus(id)); AddAction("/currentInputStatus", (id, content) => SendFullStatus(id));
_display.CurrentInputFeedback.OutputChange += CurrentInputFeedbackOnOutputChange; _display.CurrentInputFeedback.OutputChange += CurrentInputFeedbackOnOutputChange;
_display.IsCoolingDownFeedback.OutputChange += IsCoolingFeedbackOnOutputChange;
_display.IsWarmingUpFeedback.OutputChange += IsWarmingFeedbackOnOutputChange;
} }
private void CurrentInputFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs) private void CurrentInputFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs)
@ -61,24 +60,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
currentInput = feedbackEventArgs.StringValue currentInput = feedbackEventArgs.StringValue
}) })
); );
}
private void IsWarmingFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs)
{
PostStatusMessage(JToken.FromObject(new
{
isWarming = feedbackEventArgs.BoolValue
})
);
}
private void IsCoolingFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs)
{
PostStatusMessage(JToken.FromObject(new
{
isCooling = feedbackEventArgs.BoolValue
})
);
} }
@ -89,7 +70,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// <summary> /// <summary>
/// Represents a TwoWayDisplayBaseStateMessage /// Represents a TwoWayDisplayBaseStateMessage
/// </summary> /// </summary>
public class TwoWayDisplayBaseStateMessage : DeviceStateMessageBase public class CurrentInputStateMessage : DeviceStateMessageBase
{ {
//[JsonProperty("powerState", NullValueHandling = NullValueHandling.Ignore)] //[JsonProperty("powerState", NullValueHandling = NullValueHandling.Ignore)]
//public bool? PowerState { get; set; } //public bool? PowerState { get; set; }

View file

@ -11,17 +11,17 @@ namespace PepperDash.Essentials.AppServer.Messengers;
/// <summary> /// <summary>
/// Represents a RoomEventScheduleMessenger /// Represents a RoomEventScheduleMessenger
/// </summary> /// </summary>
public class RoomEventScheduleMessenger : MessengerBase public class IRoomEventScheduleMessenger : MessengerBase
{ {
private readonly IRoomEventSchedule _room; private readonly IRoomEventSchedule _room;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RoomEventScheduleMessenger"/> class. /// Initializes a new instance of the <see cref="IRoomEventScheduleMessenger"/> class.
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="messagePath"></param> /// <param name="messagePath"></param>
/// <param name="room"></param> /// <param name="room"></param>
public RoomEventScheduleMessenger(string key, string messagePath, IRoomEventSchedule room) public IRoomEventScheduleMessenger(string key, string messagePath, IRoomEventSchedule room)
: base(key, messagePath, room as IKeyName) : base(key, messagePath, room as IKeyName)
{ {
_room = room; _room = room;

View file

@ -10,19 +10,19 @@ using PepperDash.Essentials.Core.Presets;
namespace PepperDash.Essentials.AppServer.Messengers namespace PepperDash.Essentials.AppServer.Messengers
{ {
/// <summary> /// <summary>
/// Represents a DevicePresetsModelMessenger /// Represents a ITvPresetsProviderMessenger
/// </summary> /// </summary>
public class DevicePresetsModelMessenger : MessengerBase public class ITvPresetsProviderMessenger : MessengerBase
{ {
private readonly ITvPresetsProvider _presetsDevice; private readonly ITvPresetsProvider _presetsDevice;
/// <summary> /// <summary>
/// Constructor for DevicePresetsModelMessenger /// Constructor for ITvPresetsProviderMessenger
/// </summary> /// </summary>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <param name="messagePath">The message path.</param> /// <param name="messagePath">The message path.</param>
/// <param name="presetsDevice">The presets device.</param> /// <param name="presetsDevice">The presets device.</param>
public DevicePresetsModelMessenger(string key, string messagePath, ITvPresetsProvider presetsDevice) public ITvPresetsProviderMessenger(string key, string messagePath, ITvPresetsProvider presetsDevice)
: base(key, messagePath, presetsDevice as Device) : base(key, messagePath, presetsDevice as Device)
{ {
_presetsDevice = presetsDevice; _presetsDevice = presetsDevice;

View file

@ -0,0 +1,86 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.AppServer.Messengers;
/// <summary>
/// Messenger for devices that implement <see cref="IWarmingCooling"/>
/// </summary>
public class IWarmingCoolingMessenger : MessengerBase
{
private readonly IWarmingCooling device;
/// <summary>
/// Initializes a new instance of the <see cref="IWarmingCoolingMessenger"/> class.
/// </summary>
/// <param name="key"></param>
/// <param name="messagePath"></param>
/// <param name="device"></param>
public IWarmingCoolingMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
this.device = device as IWarmingCooling;
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/warmingCoolingStatus", (id, content) => SendFullStatus(id));
device.IsWarmingUpFeedback.OutputChange += IsWarmingFeedbackOnOutputChange;
device.IsCoolingDownFeedback.OutputChange += IsCoolingFeedbackOnOutputChange;
}
private void IsWarmingFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs)
{
PostStatusMessage(JToken.FromObject(new
{
isWarming = feedbackEventArgs.BoolValue
}));
}
private void IsCoolingFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs)
{
PostStatusMessage(JToken.FromObject(new
{
isCooling = feedbackEventArgs.BoolValue
}));
}
private void SendFullStatus(string id = null)
{
var messageObj = new IWarmingCoolingStateMessage
{
IsWarming = device.IsWarmingUpFeedback.BoolValue,
IsCooling = device.IsCoolingDownFeedback.BoolValue
};
PostStatusMessage(messageObj, id);
}
}
/// <summary>
/// Message object for warming/cooling status
/// </summary>
public class IWarmingCoolingStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Indicates whether the device is currently warming up.
/// </summary>
[JsonProperty("isWarming", NullValueHandling = NullValueHandling.Ignore)]
public bool IsWarming { get; set; }
/// <summary>
/// Indicates whether the device is currently cooling down.
/// </summary>
[JsonProperty("isCooling", NullValueHandling = NullValueHandling.Ignore)]
public bool IsCooling { get; set; }
}

View file

@ -83,14 +83,14 @@ namespace PepperDash.Essentials
// CameraBase only when the device does NOT also implement IHasCameraControls // CameraBase only when the device does NOT also implement IHasCameraControls
new MessengerFactoryEntry( new MessengerFactoryEntry(
typeof(CameraBase), typeof(CameraBase),
(d, mp, ck) => new CameraBaseMessenger<CameraBase>( (d, mp, ck) => new CameraControlMessenger<CameraBase>(
$"{d.Key}-cameraBase-{ck}", (CameraBase)d, mp), $"{d.Key}-cameraBase-{ck}", (CameraBase)d, mp),
predicate: d => d is CameraBase && !(d is IHasCameraControls) predicate: d => d is CameraBase && !(d is IHasCameraControls)
), ),
new MessengerFactoryEntry( new MessengerFactoryEntry(
typeof(IHasCameraControls), typeof(IHasCameraControls),
(d, mp, ck) => new CameraBaseMessenger<IHasCameraControls>( (d, mp, ck) => new CameraControlMessenger<IHasCameraControls>(
$"{d.Key}-hasCamerasWithControls-{ck}", (IHasCameraControls)d, mp) $"{d.Key}-hasCamerasWithControls-{ck}", (IHasCameraControls)d, mp)
), ),
new MessengerFactoryEntry( new MessengerFactoryEntry(
@ -102,7 +102,7 @@ namespace PepperDash.Essentials
// ── Routing ────────────────────────────────────────────────────────────── // ── Routing ──────────────────────────────────────────────────────────────
// BlueJeansPc implements IRunRouteAction // BlueJeansPc implements IRunRouteAction
new MessengerFactoryEntry( new MessengerFactoryEntry(
typeof(BlueJeansPc), typeof(IRunRouteAction),
(d, mp, ck) => new RunRouteActionMessenger( (d, mp, ck) => new RunRouteActionMessenger(
$"{d.Key}-runRouteAction-{ck}", (IRunRouteAction)d, mp) $"{d.Key}-runRouteAction-{ck}", (IRunRouteAction)d, mp)
), ),
@ -110,7 +110,7 @@ namespace PepperDash.Essentials
// ── Presets ────────────────────────────────────────────────────────────── // ── Presets ──────────────────────────────────────────────────────────────
new MessengerFactoryEntry( new MessengerFactoryEntry(
typeof(ITvPresetsProvider), typeof(ITvPresetsProvider),
(d, mp, ck) => new DevicePresetsModelMessenger( (d, mp, ck) => new ITvPresetsProviderMessenger(
$"{d.Key}-presets-{ck}", mp, (ITvPresetsProvider)d) $"{d.Key}-presets-{ck}", mp, (ITvPresetsProvider)d)
), ),
@ -121,9 +121,14 @@ namespace PepperDash.Essentials
$"{d.Key}-displayBase-{ck}", mp, (IRoutingSinkWithSwitching)d) $"{d.Key}-displayBase-{ck}", mp, (IRoutingSinkWithSwitching)d)
), ),
new MessengerFactoryEntry( new MessengerFactoryEntry(
typeof(TwoWayDisplayBase), typeof(IDisplayCurrentInput),
(d, mp, ck) => new TwoWayDisplayBaseMessenger( (d, mp, ck) => new IDisplayCurrentInputMessenger(
$"{d.Key}-twoWayDisplay-{ck}", mp, (TwoWayDisplayBase)d) $"{d.Key}-twoWayDisplay-{ck}", mp, (IDisplayCurrentInput)d)
),
new MessengerFactoryEntry(
typeof(IWarmingCooling),
(d, mp, ck) => new IWarmingCoolingMessenger(
$"{d.Key}-warmingCooling-{ck}", mp, d)
), ),
// ── Audio / Video ───────────────────────────────────────────────────────── // ── Audio / Video ─────────────────────────────────────────────────────────
@ -160,9 +165,9 @@ namespace PepperDash.Essentials
$"{d.Key}-videoCodec-{ck}", (VideoCodecBase)d, mp) $"{d.Key}-videoCodec-{ck}", (VideoCodecBase)d, mp)
), ),
new MessengerFactoryEntry( new MessengerFactoryEntry(
typeof(AudioCodecBase), typeof(IDialerCallStatus),
(d, mp, ck) => new AudioCodecBaseMessenger( (d, mp, ck) => new IDialerCallStatusMessenger(
$"{d.Key}-audioCodec-{ck}", (AudioCodecBase)d, mp) $"{d.Key}-audioCodec-{ck}", (IDialerCallStatus)d, mp)
), ),
// ── Set-top box controls ────────────────────────────────────────────────── // ── Set-top box controls ──────────────────────────────────────────────────
@ -294,7 +299,7 @@ namespace PepperDash.Essentials
// ── Event schedule ──────────────────────────────────────────────────────── // ── Event schedule ────────────────────────────────────────────────────────
new MessengerFactoryEntry( new MessengerFactoryEntry(
typeof(IRoomEventSchedule), typeof(IRoomEventSchedule),
(d, mp, ck) => new RoomEventScheduleMessenger( (d, mp, ck) => new IRoomEventScheduleMessenger(
$"{d.Key}-schedule-{ck}", mp, (IRoomEventSchedule)d) $"{d.Key}-schedule-{ck}", mp, (IRoomEventSchedule)d)
), ),