feat: Add new messengers for various device interfaces and remove VideoCodecBaseMessenger

- Implemented IHasMeetingInfoMessenger to handle meeting information updates.
- Created IHasReadyMessenger for devices indicating readiness status.
- Added IHasStandbyModeMessenger to manage standby mode actions.
- Introduced IPrivacyMessenger for privacy mode control.
- Developed IVideoCodecInfoMessenger to provide codec information.
- Removed the obsolete VideoCodecBaseMessenger class.
- Updated MessengerFactoryRegistry to register new messengers for IHasReady, IHasMeetingInfo, IHasStartMeeting, IHasStandbyMode, IPrivacy, and IVideoCodecInfo interfaces.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Neil Dorin 2026-05-08 12:10:56 -06:00
parent b41c30cdd0
commit 91aa0efa5f
22 changed files with 1197 additions and 567 deletions

View file

@ -0,0 +1,21 @@
using System.Collections.Generic;
namespace PepperDash.Essentials.Devices.Common.Codec;
/// <summary>
/// Defines call control functionality for a codec, extending the base dialer interface
/// with active call list access and meeting dialing.
/// </summary>
public interface ICodecCallControls : IHasDialer
{
/// <summary>
/// Gets the list of currently active, dialing, or incoming calls
/// </summary>
List<CodecActiveCallItem> ActiveCalls { get; }
/// <summary>
/// Dials the specified meeting
/// </summary>
/// <param name="meeting">The meeting to dial</param>
void Dial(Meeting meeting);
}

View file

@ -53,5 +53,7 @@ public interface IHasDialer
/// Gets a value indicating whether the device is currently in a call /// Gets a value indicating whether the device is currently in a call
/// </summary> /// </summary>
bool IsInCall { get; } bool IsInCall { get; }
} }

View file

@ -5,7 +5,7 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// <summary> /// <summary>
/// Implements a common set of data about a codec /// Implements a common set of data about a codec
/// </summary> /// </summary>
public interface iVideoCodecInfo public interface IVideoCodecInfo
{ {
/// <summary> /// <summary>
/// Gets the codec information /// Gets the codec information

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using Crestron.SimplSharp;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Core.Intersystem; using PepperDash.Core.Intersystem;
using PepperDash.Core.Intersystem.Tokens; using PepperDash.Core.Intersystem.Tokens;
@ -29,7 +28,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec;
/// Also contains the logic to link commonly implemented interfaces to the API bridge. /// Also contains the logic to link commonly implemented interfaces to the API bridge.
/// </summary> /// </summary>
public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutputs, public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutputs,
IUsageTracking, IHasDialer, IHasContentSharing, ICodecAudio, iVideoCodecInfo, IBridgeAdvanced, IHasStandbyMode IUsageTracking, ICodecCallControls, IHasContentSharing, ICodecAudio, IVideoCodecInfo, IBridgeAdvanced, IHasStandbyMode, IHasReady
{ {
private const int XSigEncoding = 28591; private const int XSigEncoding = 28591;
@ -338,7 +337,7 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
/// <summary> /// <summary>
/// Fired when the Codec is ready to be used /// Fired when the Codec is ready to be used
/// </summary> /// </summary>
public event EventHandler<EventArgs> IsReadyChange; public event EventHandler<IsReadyEventArgs> IsReadyEvent;
/// <summary> /// <summary>
/// Dials the specified meeting /// Dials the specified meeting
@ -405,7 +404,7 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
try try
{ {
IsReady = true; IsReady = true;
IsReadyChange?.Invoke(this, new EventArgs()); IsReadyEvent?.Invoke(this, new IsReadyEventArgs(IsReady));
} }
catch (Exception e) catch (Exception e)
{ {
@ -498,7 +497,7 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
LinkVideoCodecInfoToApi(trilist, joinMap); LinkVideoCodecInfoToApi(trilist, joinMap);
// Register for this event to link any functions that require the codec to be ready first // Register for this event to link any functions that require the codec to be ready first
codec.IsReadyChange += (o, a) => codec.IsReadyEvent += (o, a) =>
{ {
if (codec is IHasCodecCameras) if (codec is IHasCodecCameras)
{ {

View file

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.Codec;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides a messaging bridge for devices implementing <see cref="ICodecCallControls"/>
/// </summary>
public class ICallControlsMessenger : MessengerBase
{
private readonly ICodecCallControls _callControls;
/// Initializes a new instance of the <see cref="ICallControlsMessenger"/> class.
public ICallControlsMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_callControls = device as ICodecCallControls ?? throw new ArgumentNullException(nameof(device));
_callControls.CallStatusChange += CallControls_CallStatusChange;
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/callControlsStatus", (id, content) => SendFullStatus(id));
AddAction("/dialMeeting", (id, content) =>
_callControls.Dial(content.ToObject<Meeting>()));
AddAction("/endCallById", (id, content) =>
{
var s = content.ToObject<MobileControlSimpleContent<string>>();
var call = GetCallWithId(s.Value);
if (call != null)
_callControls.EndCall(call);
});
AddAction("/rejectById", (id, content) =>
{
var s = content.ToObject<MobileControlSimpleContent<string>>();
var call = GetCallWithId(s.Value);
if (call != null)
_callControls.RejectCall(call);
});
AddAction("/acceptById", (id, content) =>
{
var s = content.ToObject<MobileControlSimpleContent<string>>();
var call = GetCallWithId(s.Value);
if (call != null)
_callControls.AcceptCall(call);
});
}
private void CallControls_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e)
{
try
{
PostStatusMessage(BuildState());
}
catch (Exception ex)
{
this.LogError(ex, "Error posting call controls status");
}
}
private void SendFullStatus(string id = null)
{
try
{
Task.Run(() => PostStatusMessage(BuildState(), id));
}
catch (Exception ex)
{
this.LogError(ex, "Error sending call controls full status");
}
}
private ICallControlsStateMessage BuildState()
{
return new ICallControlsStateMessage
{
Calls = _callControls.ActiveCalls,
};
}
private CodecActiveCallItem GetCallWithId(string id)
{
return _callControls.ActiveCalls?.FirstOrDefault(c => c.Id == id);
}
}
/// <summary>
/// State message for <see cref="ICodecCallControls"/>
/// </summary>
public class ICallControlsStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Gets or sets the list of active calls. Null if unknown or not applicable.
/// </summary>
[JsonProperty("calls", NullValueHandling = NullValueHandling.Ignore)]
public List<CodecActiveCallItem> Calls { get; set; }
}
}

View file

@ -65,7 +65,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
PostStatusMessage(new IHasCallHistoryStateMessage PostStatusMessage(new IHasCallHistoryStateMessage
{ {
RecentCalls = recents RecentCalls = recents,
}); });
} }
} }
@ -76,9 +76,22 @@ namespace PepperDash.Essentials.AppServer.Messengers
} }
} }
/// <summary>
/// State message for <see cref="IHasCallHistory"/>
///
/// </summary>
public class IHasCallHistoryStateMessage : DeviceStateMessageBase public class IHasCallHistoryStateMessage : DeviceStateMessageBase
{ {
/// <summary>
/// Gets or sets the list of recent calls. Null if unknown or not applicable.
/// </summary>
[JsonProperty("recentCalls", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("recentCalls", NullValueHandling = NullValueHandling.Ignore)]
public List<CodecCallHistory.CallHistoryEntry> RecentCalls { get; set; } public List<CodecCallHistory.CallHistoryEntry> RecentCalls { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the device has call history functionality. Null if unknown or not applicable.
/// </summary>
[JsonProperty("hasRecents", NullValueHandling = NullValueHandling.Ignore)]
public bool? HasRecents { get; set; } = true; // Since this messenger should only be used for devices with call history, default to true unless specified otherwise.
} }
} }

View file

@ -81,7 +81,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
foreach (var cam in CameraController.Cameras) foreach (var cam in CameraController.Cameras)
{ {
cameraList.Add(new KeyName{ cameraList.Add(new KeyName
{
Key = cam.Key, Key = cam.Key,
Name = cam.Name Name = cam.Name
}); });
@ -96,10 +97,27 @@ namespace PepperDash.Essentials.AppServer.Messengers
}; };
} }
string mode = "";
if (CameraController is IHasCameraAutoMode speakerTrackCodec)
{
mode = speakerTrackCodec.CameraAutoModeIsOnFeedback.BoolValue
? eCameraControlMode.Auto.ToString().ToLower()
: eCameraControlMode.Manual.ToString().ToLower();
}
if (CameraController is IHasCameraOff cameraOffCodec)
{
if (cameraOffCodec.CameraIsOffFeedback.BoolValue)
mode = eCameraControlMode.Off.ToString().ToLower();
}
var state = new IHasCamerasWithControlsStateMessage var state = new IHasCamerasWithControlsStateMessage
{ {
CameraList = cameraList, CameraList = cameraList,
SelectedCamera = selectedCamera SelectedCamera = selectedCamera,
CameraMode = mode,
HasCameraAutoMode = CameraController is IHasCameraAutoMode,
}; };
PostStatusMessage(state, clientId); PostStatusMessage(state, clientId);
@ -122,6 +140,18 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// </summary> /// </summary>
[JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)]
public IKeyName SelectedCamera { get; set; } public IKeyName SelectedCamera { get; set; }
/// <summary>
/// Indicates whether the device has any cameras. Null if unknown or not applicable.
/// </summary>
[JsonProperty("hasCameras", NullValueHandling = NullValueHandling.Ignore)]
public bool HasCameras { get; set; } = true; // Since this messenger should only be used for devices with cameras, default to true unless specified otherwise.
[JsonProperty("hasCameraAutoMode", NullValueHandling = NullValueHandling.Ignore)]
public bool? HasCameraAutoMode { get; set; }
[JsonProperty("cameraMode", NullValueHandling = NullValueHandling.Ignore)]
public string CameraMode { get; set; }
} }
class KeyName : IKeyName class KeyName : IKeyName

View file

@ -249,6 +249,35 @@ namespace PepperDash.Essentials.AppServer.Messengers
return presetsCodec.NearEndPresets; return presetsCodec.NearEndPresets;
} }
private Camera GetSelectedCamera(IHasCodecCameras camerasCodec)
{
var camera = new Camera();
if (camerasCodec.SelectedCameraFeedback != null)
camera.Key = camerasCodec.SelectedCameraFeedback.StringValue;
if (camerasCodec.SelectedCamera != null)
{
camera.Name = camerasCodec.SelectedCamera.Name;
if(camerasCodec.SelectedCamera is IHasCameraPtzControl cameraControls)
{
camera.Capabilities = new CameraCapabilities()
{
CanPan = cameraControls is IHasCameraPanControl,
CanTilt = cameraControls is IHasCameraTiltControl,
CanZoom = cameraControls is IHasCameraZoomControl,
CanFocus = cameraControls is IHasCameraFocusControl,
};
};
}
if (camerasCodec.ControllingFarEndCameraFeedback != null)
camera.IsFarEnd = camerasCodec.ControllingFarEndCameraFeedback.BoolValue;
return camera;
}
private void PostCameraMode() private void PostCameraMode()
{ {
try try
@ -294,6 +323,31 @@ namespace PepperDash.Essentials.AppServer.Messengers
this.LogError(ex, "Error posting camera presets"); this.LogError(ex, "Error posting camera presets");
} }
} }
private void SendFullStatus()
{
try
{
PostStatusMessage(new IHasCodecCamerasStateMessage
{
CameraMode = GetCameraMode(),
Cameras = new CameraStatus
{
CameraManualIsSupported = true,
CameraAutoIsSupported = _codec.SupportsCameraAutoMode,
CameraOffIsSupported = _codec.SupportsCameraOff,
CameraMode = GetCameraMode(),
Cameras = _cameraCodec.Cameras,
SelectedCamera = GetSelectedCamera(_cameraCodec)
},
Presets = GetCurrentPresets()
});
}
catch (Exception ex)
{
this.LogError(ex, "Error sending full camera status");
}
}
} }
public class IHasCodecCamerasStateMessage : DeviceStateMessageBase public class IHasCodecCamerasStateMessage : DeviceStateMessageBase
@ -306,5 +360,96 @@ namespace PepperDash.Essentials.AppServer.Messengers
[JsonProperty("presets", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("presets", NullValueHandling = NullValueHandling.Ignore)]
public List<CodecRoomPreset> Presets { get; set; } public List<CodecRoomPreset> Presets { get; set; }
[JsonProperty("cameraSupportsAutoMode", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraSupportsAutoMode { get; set; }
[JsonProperty("cameraSupportsOffMode", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraSupportsOffMode { get; set; }
}
/// <summary>
/// Represents a CameraStatus
/// </summary>
public class CameraStatus
{
[JsonProperty("cameraManualSupported", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraManualIsSupported { get; set; }
[JsonProperty("cameraAutoSupported", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraAutoIsSupported { get; set; }
[JsonProperty("cameraOffSupported", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraOffIsSupported { get; set; }
/// <summary>
/// Gets or sets the CameraMode
/// </summary>
[JsonProperty("cameraMode", NullValueHandling = NullValueHandling.Ignore)]
public string CameraMode { get; set; }
/// <summary>
/// Gets or sets the Cameras
/// </summary>
[JsonProperty("cameraList", NullValueHandling = NullValueHandling.Ignore)]
public List<IHasCameraControls> Cameras { get; set; }
/// <summary>
/// Gets or sets the SelectedCamera
/// </summary>
[JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)]
public Camera SelectedCamera { get; set; }
}
/// <summary>
/// Represents a Camera
/// </summary>
public class Camera
{
/// <summary>
/// Gets or sets the Key
/// </summary>
[JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)]
public string Key { get; set; }
/// <summary>
/// Gets or sets the Name
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
[JsonProperty("isFarEnd", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsFarEnd { get; set; }
/// <summary>
/// Gets or sets the Capabilities
/// </summary>
[JsonProperty("capabilities", NullValueHandling = NullValueHandling.Ignore)]
public CameraCapabilities Capabilities { get; set; }
}
/// <summary>
/// Represents a CameraCapabilities
/// </summary>
public class CameraCapabilities
{
[JsonProperty("canPan", NullValueHandling = NullValueHandling.Ignore)]
public bool? CanPan { get; set; }
[JsonProperty("canTilt", NullValueHandling = NullValueHandling.Ignore)]
public bool? CanTilt { get; set; }
[JsonProperty("canZoom", NullValueHandling = NullValueHandling.Ignore)]
public bool? CanZoom { get; set; }
[JsonProperty("canFocus", NullValueHandling = NullValueHandling.Ignore)]
public bool? CanFocus { get; set; }
} }
} }

View file

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.Cameras;
using PepperDash.Essentials.Devices.Common.VideoCodec;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides a messaging bridge for devices implementing <see cref="IHasCodecRoomPresets"/>
/// </summary>
public class IHasCodecRoomPresetsMessenger : MessengerBase
{
private readonly IHasCodecRoomPresets _presets;
private readonly EssentialsDevice _device;
public IHasCodecRoomPresetsMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_device = device ?? throw new ArgumentNullException(nameof(device));
_presets = device as IHasCodecRoomPresets ?? throw new ArgumentNullException(nameof(device));
_presets.CodecRoomPresetsListHasChanged += Presets_ListHasChanged;
}
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
}
private void Presets_ListHasChanged(object sender, EventArgs e)
{
try
{
PostStatusMessage(new IHasCodecRoomPresetsStateMessage
{
Presets = GetCurrentPresets()
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting codec room presets");
}
}
private void SendFullStatus(string id = null)
{
try
{
var state = new IHasCodecRoomPresetsStateMessage
{
Presets = GetCurrentPresets()
};
Task.Run(() => PostStatusMessage(state, id));
}
catch (Exception ex)
{
this.LogError(ex, "Error sending room presets full status");
}
}
private List<CodecRoomPreset> GetCurrentPresets()
{
if (_device is IHasFarEndCameraControl farEndControl &&
farEndControl.ControllingFarEndCameraFeedback.BoolValue)
return _presets.FarEndRoomPresets;
return _presets.NearEndPresets;
}
}
public class IHasCodecRoomPresetsStateMessage : DeviceStateMessageBase
{
[JsonProperty("presets", NullValueHandling = NullValueHandling.Ignore)]
public List<CodecRoomPreset> Presets { get; set; }
}
}

View file

@ -53,7 +53,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
PostStatusMessage(new IHasCodecSelfViewStateMessage PostStatusMessage(new IHasCodecSelfViewStateMessage
{ {
CameraSelfViewIsOn = _selfView.SelfviewIsOnFeedback.BoolValue CameraSelfViewIsOn = _selfView.SelfviewIsOnFeedback.BoolValue,
ShowSelfViewByDefault = _selfView.ShowSelfViewByDefault
}); });
} }
catch (Exception ex) catch (Exception ex)
@ -63,9 +64,23 @@ namespace PepperDash.Essentials.AppServer.Messengers
} }
} }
/// <summary>
/// State message for <see cref="IHasCodecSelfView"/>
/// </summary>
public class IHasCodecSelfViewStateMessage : DeviceStateMessageBase public class IHasCodecSelfViewStateMessage : DeviceStateMessageBase
{ {
/// <summary>
/// Gets or sets a value indicating whether the codec's self view is currently on. Null if unknown or not applicable.
///
/// </summary>
[JsonProperty("cameraSelfView", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("cameraSelfView", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraSelfViewIsOn { get; set; } public bool? CameraSelfViewIsOn { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the codec is set to show self view by default. Null if unknown or not applicable.
///
/// </summary>
[JsonProperty("showSelfViewByDefault", NullValueHandling = NullValueHandling.Ignore)]
public bool? ShowSelfViewByDefault { get; set; }
} }
} }

View file

@ -0,0 +1,93 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.Codec;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides a messaging bridge for devices implementing <see cref="IHasContentSharing"/>
/// </summary>
public class IHasContentSharingMessenger : MessengerBase
{
private readonly IHasContentSharing _sharing;
public IHasContentSharingMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_sharing = device as IHasContentSharing ?? throw new ArgumentNullException(nameof(device));
_sharing.SharingContentIsOnFeedback.OutputChange += SharingContentIsOnFeedback_OutputChange;
_sharing.SharingSourceFeedback.OutputChange += SharingSourceFeedback_OutputChange;
}
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/sharingStart", (id, content) => _sharing.StartSharing());
AddAction("/sharingStop", (id, content) => _sharing.StopSharing());
}
private void SharingContentIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
try
{
PostStatusMessage(new IHasContentSharingStateMessage
{
SharingContentIsOn = e.BoolValue
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting sharing content is on");
}
}
private void SharingSourceFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
try
{
PostStatusMessage(new IHasContentSharingStateMessage
{
SharingSource = e.StringValue
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting sharing source");
}
}
private void SendFullStatus(string id = null)
{
try
{
var state = new IHasContentSharingStateMessage
{
SharingContentIsOn = _sharing.SharingContentIsOnFeedback.BoolValue,
SharingSource = _sharing.SharingSourceFeedback.StringValue
};
Task.Run(() => PostStatusMessage(state, id));
}
catch (Exception ex)
{
this.LogError(ex, "Error sending content sharing full status");
}
}
}
public class IHasContentSharingStateMessage : DeviceStateMessageBase
{
[JsonProperty("sharingContentIsOn", NullValueHandling = NullValueHandling.Ignore)]
public bool? SharingContentIsOn { get; set; }
[JsonProperty("sharingSource", NullValueHandling = NullValueHandling.Ignore)]
public string SharingSource { get; set; }
}
}

View file

@ -0,0 +1,99 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.Codec;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides a messaging bridge for devices implementing <see cref="IHasDialer"/>
/// </summary>
public class IHasDialerMessenger : MessengerBase
{
private readonly IHasDialer _dialer;
///
public IHasDialerMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_dialer = device as IHasDialer ?? throw new ArgumentNullException(nameof(device));
_dialer.CallStatusChange += Dialer_CallStatusChange;
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/dialStatus", (id, content) => SendFullStatus(id));
AddAction("/dial", (id, content) =>
{
var value = content.ToObject<MobileControlSimpleContent<string>>();
_dialer.Dial(value.Value);
});
AddAction("/endAllCalls", (id, content) => _dialer.EndAllCalls());
AddAction("/dtmf", (id, content) =>
{
var s = content.ToObject<MobileControlSimpleContent<string>>();
_dialer.SendDtmf(s.Value);
});
}
private void Dialer_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e)
{
try
{
var state = new IHasDialerStateMessage
{
IsInCall = _dialer.IsInCall
};
PostStatusMessage(state);
}
catch (Exception ex)
{
this.LogError(ex, "Error posting dialer call status");
}
}
private void SendFullStatus(string id = null)
{
try
{
var state = new IHasDialerStateMessage
{
IsInCall = _dialer.IsInCall,
};
Task.Run(() => PostStatusMessage(state, id));
}
catch (Exception ex)
{
this.LogError(ex, "Error sending dialer full status");
}
}
}
/// <summary>
/// Message class representing the state of a device implementing <see cref="IHasDialer"/>
/// </summary>
public class IHasDialerStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Indicates whether the device is currently in a call
/// </summary>
[JsonProperty("isInCall", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsInCall { get; set; }
}
}

View file

@ -147,5 +147,11 @@ namespace PepperDash.Essentials.AppServer.Messengers
[JsonProperty("hasDirectorySearch", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("hasDirectorySearch", NullValueHandling = NullValueHandling.Ignore)]
public bool? HasDirectorySearch { get; set; } public bool? HasDirectorySearch { get; set; }
/// <summary>
/// Gets or sets the DirectorySelectedFolderName
/// </summary>
[JsonProperty("directorySelectedFolderName", NullValueHandling = NullValueHandling.Ignore)]
public string DirectorySelectedFolderName { get; set; }
} }
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Core.Logging; using PepperDash.Core.Logging;
@ -12,7 +13,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// </summary> /// </summary>
public class IHasFarEndContentStatusMessenger : MessengerBase public class IHasFarEndContentStatusMessenger : MessengerBase
{ {
private readonly IHasFarEndContentStatus _device; private readonly IHasFarEndContentStatus device;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="IHasFarEndContentStatusMessenger"/> class. /// Initializes a new instance of the <see cref="IHasFarEndContentStatusMessenger"/> class.
@ -20,7 +21,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
public IHasFarEndContentStatusMessenger(string key, string messagePath, EssentialsDevice device) public IHasFarEndContentStatusMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device) : base(key, messagePath, device)
{ {
_device = device as IHasFarEndContentStatus ?? throw new ArgumentException("device must implement IHasFarEndContentStatus", nameof(device)); this.device = device as IHasFarEndContentStatus ?? throw new ArgumentException("device must implement IHasFarEndContentStatus", nameof(device));
} }
/// <inheritdoc /> /// <inheritdoc />
@ -28,7 +29,11 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
base.RegisterActions(); base.RegisterActions();
_device.ReceivingContent.OutputChange += (sender, args) => PostReceivingContent(args.BoolValue); AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/farEndContentStatus", (id, content) => SendFullStatus(id));
device.ReceivingContent.OutputChange += (sender, args) => PostReceivingContent(args.BoolValue);
} }
private void PostReceivingContent(bool receivingContent) private void PostReceivingContent(bool receivingContent)
@ -45,10 +50,33 @@ namespace PepperDash.Essentials.AppServer.Messengers
this.LogError(ex, "Error posting receiving content"); this.LogError(ex, "Error posting receiving content");
} }
} }
private void SendFullStatus(string id = null)
{
try
{
var state = new IHasFarEndContentStatusStateMessage
{
ReceivingContent = device.ReceivingContent.BoolValue
};
Task.Run(() => PostStatusMessage(state, id));
}
catch (Exception ex)
{
this.LogError(ex, "Error posting full status");
}
}
} }
/// <summary>
/// Message class representing the state of a device implementing <see cref="IHasFarEndContentStatus"/>
/// </summary>
public class IHasFarEndContentStatusStateMessage : DeviceStateMessageBase public class IHasFarEndContentStatusStateMessage : DeviceStateMessageBase
{ {
/// <summary>
/// Indicates whether the device is currently receiving content from the far end
/// </summary>
[JsonProperty("receivingContent", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("receivingContent", NullValueHandling = NullValueHandling.Ignore)]
public bool? ReceivingContent { get; set; } public bool? ReceivingContent { get; set; }
} }

View file

@ -0,0 +1,86 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides a messaging bridge for devices implementing <see cref="IHasMeetingInfo"/>
/// </summary>
public class IHasMeetingInfoMessenger : MessengerBase
{
private readonly IHasMeetingInfo _meetingInfo;
/// <summary>
/// Initializes a new instance of the <see cref="IHasMeetingInfoMessenger"/> class.
/// </summary>
/// <param name="key"></param>
/// <param name="messagePath"></param>
/// <param name="device"></param>
/// <exception cref="ArgumentNullException"></exception>
public IHasMeetingInfoMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_meetingInfo = device as IHasMeetingInfo ?? throw new ArgumentNullException(nameof(device));
_meetingInfo.MeetingInfoChanged += MeetingInfo_Changed;
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/meetingInfoStatus", (id, content) => SendFullStatus(id));
}
private void MeetingInfo_Changed(object sender, MeetingInfoEventArgs e)
{
try
{
PostStatusMessage(new IHasMeetingInfoStateMessage
{
MeetingInfo = _meetingInfo.MeetingInfo
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting meeting info");
}
}
private void SendFullStatus(string id = null)
{
try
{
var state = new IHasMeetingInfoStateMessage
{
MeetingInfo = _meetingInfo.MeetingInfo
};
Task.Run(() => PostStatusMessage(state, id));
}
catch (Exception ex)
{
this.LogError(ex, "Error sending meeting info full status");
}
}
}
/// <summary>
/// Message class for devices implementing <see cref="IHasMeetingInfo"/>
/// </summary>
public class IHasMeetingInfoStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Gets or sets the MeetingInfo
/// </summary>
[JsonProperty("meetingInfo", NullValueHandling = NullValueHandling.Ignore)]
public MeetingInfo MeetingInfo { get; set; }
}
}

View file

@ -0,0 +1,70 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides a messaging bridge for devices implementing <see cref="IHasReady"/>
/// </summary>
public class IHasReadyMessenger : MessengerBase
{
private readonly IHasReady _hasReady;
public IHasReadyMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_hasReady = device as IHasReady ?? throw new ArgumentNullException(nameof(device));
_hasReady.IsReadyEvent += HasReady_IsReadyEvent;
}
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/isReady", (id, content) => SendFullStatus(id));
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
}
private void HasReady_IsReadyEvent(object sender, IsReadyEventArgs e)
{
try
{
PostStatusMessage(new IHasReadyStateMessage
{
IsReady = e.IsReady
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting ready state");
}
}
private void SendFullStatus(string id = null)
{
try
{
var state = new IHasReadyStateMessage
{
IsReady = _hasReady.IsReady
};
Task.Run(() => PostStatusMessage(state, id));
}
catch (Exception ex)
{
this.LogError(ex, "Error sending ready full status");
}
}
}
public class IHasReadyStateMessage : DeviceStateMessageBase
{
[JsonProperty("isReady", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsReady { get; set; }
}
}

View file

@ -0,0 +1,83 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.VideoCodec;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides a messaging bridge for devices implementing <see cref="IHasStandbyMode"/>
/// </summary>
public class IHasStandbyModeMessenger : MessengerBase
{
private readonly IHasStandbyMode _standby;
/// <summary>
/// Initializes a new instance of the <see cref="IHasStandbyModeMessenger"/> class.
/// </summary>
public IHasStandbyModeMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_standby = device as IHasStandbyMode ?? throw new ArgumentNullException(nameof(device));
_standby.StandbyIsOnFeedback.OutputChange += StandbyIsOnFeedback_OutputChange;
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/standbyStatus", (id, content) => SendFullStatus(id));
AddAction("/standbyOn", (id, content) => _standby.StandbyActivate());
AddAction("/standbyOff", (id, content) => _standby.StandbyDeactivate());
}
private void StandbyIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
try
{
PostStatusMessage(new IHasStandbyModeStateMessage
{
StandbyIsOn = e.BoolValue
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting standby state");
}
}
private void SendFullStatus(string id = null)
{
try
{
var state = new IHasStandbyModeStateMessage
{
StandbyIsOn = _standby.StandbyIsOnFeedback.BoolValue
};
Task.Run(() => PostStatusMessage(state, id));
}
catch (Exception ex)
{
this.LogError(ex, "Error sending standby full status");
}
}
}
/// <summary>
/// State message for <see cref="IHasStandbyMode"/>
/// </summary>
public class IHasStandbyModeStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Indicates whether the device is in standby mode. Null if unknown or not applicable.
/// </summary>
[JsonProperty("standbyIsOn", NullValueHandling = NullValueHandling.Ignore)]
public bool? StandbyIsOn { get; set; }
}
}

View file

@ -0,0 +1,85 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides a messaging bridge for devices implementing <see cref="IHasStartMeeting"/>
/// </summary>
public class IHasStartMeetingMessenger : MessengerBase
{
private readonly IHasStartMeeting _startMeeting;
/// <summary>
/// Initializes a new instance of the <see cref="IHasStartMeetingMessenger"/> class.
/// </summary>
/// <param name="key">The key for the messenger.</param>
/// <param name="messagePath">The message path for the messenger.</param>
/// <param name="device">The device implementing <see cref="IHasStartMeeting"/>.</param>
/// <exception cref="ArgumentNullException">Thrown if the device does not implement <see cref="IHasStartMeeting"/>.</exception>
public IHasStartMeetingMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_startMeeting = device as IHasStartMeeting ?? throw new ArgumentNullException(nameof(device));
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/startMeetingStatus", (id, content) => SendFullStatus(id));
AddAction("/startMeeting", (id, content) =>
{
var msg = content.ToObject<MobileControlSimpleContent<uint>>();
_startMeeting.StartMeeting(msg?.Value ?? _startMeeting.DefaultMeetingDurationMin);
});
AddAction("/leaveMeeting", (id, content) => _startMeeting.LeaveMeeting());
}
private void SendFullStatus(string id = null)
{
try
{
var state = new IHasStartMeetingStateMessage
{
DefaultMeetingDurationMin = _startMeeting.DefaultMeetingDurationMin
};
Task.Run(() => PostStatusMessage(state, id));
}
catch (Exception ex)
{
this.LogError(ex, "Error sending start meeting full status");
}
}
}
/// <summary>
/// State message for devices implementing <see cref="IHasStartMeeting"/>
/// </summary>
public class IHasStartMeetingStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Indicates whether the device supports ad-hoc meetings (meetings started from the device rather than an external calendar invite)
///
/// </summary>
[JsonProperty("supportsAdHocMeeting", NullValueHandling = NullValueHandling.Ignore)]
public bool SupportsAdHocMeeting { get; set; } = true;
/// <summary>
/// The default meeting duration in minutes for meetings started from the device. Null if unknown or not applicable.
/// </summary>
[JsonProperty("defaultMeetingDurationMin", NullValueHandling = NullValueHandling.Ignore)]
public uint DefaultMeetingDurationMin { get; set; }
}
}

View file

@ -0,0 +1,89 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides a messaging bridge for devices implementing <see cref="IPrivacy"/>
/// </summary>
public class IPrivacyMessenger : MessengerBase
{
private readonly IPrivacy _privacy;
/// <summary>
/// Initializes a new instance of the <see cref="IPrivacyMessenger"/> class.
/// </summary>
/// <param name="key">The key for the messenger.</param>
/// <param name="messagePath">The message path for the messenger.</param>
/// <param name="device">The device implementing <see cref="IPrivacy"/>.</param>
public IPrivacyMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_privacy = device as IPrivacy ?? throw new ArgumentNullException(nameof(device));
_privacy.PrivacyModeIsOnFeedback.OutputChange += PrivacyModeIsOnFeedback_OutputChange;
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/privacyStatus", (id, content) => SendFullStatus(id));
AddAction("/privacyModeOn", (id, content) => _privacy.PrivacyModeOn());
AddAction("/privacyModeOff", (id, content) => _privacy.PrivacyModeOff());
AddAction("/privacyModeToggle", (id, content) => _privacy.PrivacyModeToggle());
}
private void PrivacyModeIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
try
{
PostStatusMessage(new IPrivacyStateMessage
{
PrivacyModeIsOn = e.BoolValue
});
}
catch (Exception ex)
{
this.LogError(ex, "Error posting privacy mode state");
}
}
private void SendFullStatus(string id = null)
{
try
{
var state = new IPrivacyStateMessage
{
PrivacyModeIsOn = _privacy.PrivacyModeIsOnFeedback.BoolValue
};
Task.Run(() => PostStatusMessage(state, id));
}
catch (Exception ex)
{
this.LogError(ex, "Error sending privacy full status");
}
}
}
/// <summary>
/// State message for <see cref="IPrivacy"/>
/// </summary>
public class IPrivacyStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Gets or sets a value indicating whether privacy mode is on. Null if unknown or not
/// applicable.
/// </summary>
[JsonProperty("privacyModeIsOn", NullValueHandling = NullValueHandling.Ignore)]
public bool? PrivacyModeIsOn { get; set; }
}
}

View file

@ -0,0 +1,71 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.Codec;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides a messaging bridge for devices implementing <see cref="IVideoCodecInfo"/>
/// </summary>
public class IVideoCodecInfoMessenger : MessengerBase
{
private readonly IVideoCodecInfo _codecInfo;
/// <summary>
/// Initializes a new instance of the <see cref="IVideoCodecInfoMessenger"/> class.
/// </summary>
/// <param name="key"></param>
/// <param name="messagePath"></param>
/// <param name="device"></param>
/// <exception cref="ArgumentNullException"></exception>
public IVideoCodecInfoMessenger(string key, string messagePath, EssentialsDevice device)
: base(key, messagePath, device)
{
_codecInfo = device as IVideoCodecInfo ?? throw new ArgumentNullException(nameof(device));
}
/// <inheritdoc />
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/codecInfoStatus", (id, content) => SendFullStatus(id));
}
private void SendFullStatus(string id = null)
{
try
{
var state = new iVideoCodecInfoStateMessage
{
Info = _codecInfo.CodecInfo
};
Task.Run(() => PostStatusMessage(state, id));
}
catch (Exception ex)
{
this.LogError(ex, "Error sending codec info full status");
}
}
}
/// <summary>
/// State message for <see cref="IVideoCodecInfo"/>
/// </summary>
public class iVideoCodecInfoStateMessage : DeviceStateMessageBase
{
/// <summary>
/// Gets or sets the codec information. Null if unknown or not applicable.
/// </summary>
[JsonProperty("info", NullValueHandling = NullValueHandling.Ignore)]
public VideoCodecInfo Info { get; set; }
}
}

View file

@ -1,550 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Crestron.SimplSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Devices.Common.Cameras;
using PepperDash.Essentials.Devices.Common.Codec;
using PepperDash.Essentials.Devices.Common.VideoCodec;
using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Provides a messaging bridge for a VideoCodecBase device
/// </summary>
public class VideoCodecBaseMessenger : MessengerBase
{
/// <summary>
///
/// </summary>
protected VideoCodecBase Codec { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="codec"></param>
/// <param name="messagePath"></param>
public VideoCodecBaseMessenger(string key, VideoCodecBase codec, string messagePath)
: base(key, messagePath, codec)
{
Codec = codec ?? throw new ArgumentNullException("codec");
codec.CallStatusChange += Codec_CallStatusChange;
codec.IsReadyChange += Codec_IsReadyChange;
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Codec_IsReadyChange(object sender, EventArgs e)
{
try
{
var state = new VideoCodecBaseStateMessage
{
IsReady = true
};
PostStatusMessage(state);
SendFullStatus();
}
catch (Exception ex)
{
this.LogError(ex, "Error sending codec ready status");
}
}
/// <summary>
/// Called from base's RegisterWithAppServer method
/// </summary>
protected override void RegisterActions()
{
try
{
base.RegisterActions();
AddAction("/isReady", (id, content) => SendIsReady());
AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/codecStatus", (id, content) => SendFullStatus(id));
AddAction("/dial", (id, content) =>
{
var value = content.ToObject<MobileControlSimpleContent<string>>();
Codec.Dial(value.Value);
});
AddAction("/dialMeeting", (id, content) => Codec.Dial(content.ToObject<Meeting>()));
AddAction("/endCallById", (id, content) =>
{
var s = content.ToObject<MobileControlSimpleContent<string>>();
var call = GetCallWithId(s.Value);
if (call != null)
Codec.EndCall(call);
});
AddAction("/endAllCalls", (id, content) => Codec.EndAllCalls());
AddAction("/dtmf", (id, content) =>
{
var s = content.ToObject<MobileControlSimpleContent<string>>();
Codec.SendDtmf(s.Value);
});
AddAction("/rejectById", (id, content) =>
{
var s = content.ToObject<MobileControlSimpleContent<string>>();
var call = GetCallWithId(s.Value);
if (call != null)
Codec.RejectCall(call);
});
AddAction("/acceptById", (id, content) =>
{
var s = content.ToObject<MobileControlSimpleContent<string>>();
var call = GetCallWithId(s.Value);
if (call != null)
Codec.AcceptCall(call);
});
Codec.SharingContentIsOnFeedback.OutputChange += SharingContentIsOnFeedback_OutputChange;
Codec.SharingSourceFeedback.OutputChange += SharingSourceFeedback_OutputChange;
this.LogVerbose("Adding Privacy & Standby Actions");
AddAction("/privacyModeOn", (id, content) => Codec.PrivacyModeOn());
AddAction("/privacyModeOff", (id, content) => Codec.PrivacyModeOff());
AddAction("/privacyModeToggle", (id, content) => Codec.PrivacyModeToggle());
AddAction("/sharingStart", (id, content) => Codec.StartSharing());
AddAction("/sharingStop", (id, content) => Codec.StopSharing());
AddAction("/standbyOn", (id, content) => Codec.StandbyActivate());
AddAction("/standbyOff", (id, content) => Codec.StandbyDeactivate());
}
catch (Exception e)
{
this.LogException(e, "Exception adding paths");
}
}
private void SharingSourceFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
try
{
var state = new VideoCodecBaseStateMessage
{
SharingSource = e.StringValue
};
PostStatusMessage(state);
}
catch (Exception ex)
{
this.LogError(ex, "Error posting sharing source");
}
}
private void SharingContentIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
try
{
var state = new VideoCodecBaseStateMessage
{
SharingContentIsOn = e.BoolValue
};
PostStatusMessage(state);
}
catch (Exception ex)
{
this.LogError(ex, "Error posting sharing content");
}
}
/// <summary>
/// Handler for codec changes
/// </summary>
private void Codec_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e)
{
SendFullStatus();
}
/// <summary>
///
/// </summary>
private void SendIsReady()
{
try
{
var status = new VideoCodecBaseStateMessage();
var codecType = Codec.GetType();
status.IsReady = Codec.IsReady;
status.IsZoomRoom = codecType.GetInterface("IHasZoomRoomLayouts") != null;
PostStatusMessage(status);
}
catch (Exception ex)
{
this.LogError(ex, "Error sending codec ready status");
}
}
/// <summary>
/// Helper method to build call status for vtc
/// </summary>
/// <returns></returns>
protected VideoCodecBaseStateMessage GetStatus()
{
try
{
var status = new VideoCodecBaseStateMessage();
if (Codec is IHasCodecCameras camerasCodec)
{
status.Cameras = new CameraStatus
{
CameraManualIsSupported = true,
CameraAutoIsSupported = Codec.SupportsCameraAutoMode,
CameraOffIsSupported = Codec.SupportsCameraOff,
CameraMode = GetCameraMode(),
Cameras = camerasCodec.Cameras,
SelectedCamera = GetSelectedCamera(camerasCodec)
};
}
var codecType = Codec.GetType();
status.CameraSelfViewIsOn = Codec is IHasCodecSelfView && (Codec as IHasCodecSelfView).SelfviewIsOnFeedback.BoolValue;
status.IsInCall = Codec.IsInCall;
status.PrivacyModeIsOn = Codec.PrivacyModeIsOnFeedback.BoolValue;
status.SharingContentIsOn = Codec.SharingContentIsOnFeedback.BoolValue;
status.SharingSource = Codec.SharingSourceFeedback.StringValue;
status.StandbyIsOn = Codec.StandbyIsOnFeedback.BoolValue;
status.Calls = Codec.ActiveCalls;
status.Info = Codec.CodecInfo;
status.ShowSelfViewByDefault = Codec.ShowSelfViewByDefault;
status.SupportsAdHocMeeting = Codec is IHasStartMeeting;
status.HasRecents = Codec is IHasCallHistory;
status.HasCameras = Codec is IHasCamerasWithControls;
status.Presets = GetCurrentPresets();
status.IsZoomRoom = codecType.GetInterface("IHasZoomRoomLayouts") != null;
status.ReceivingContent = Codec is IHasFarEndContentStatus && (Codec as IHasFarEndContentStatus).ReceivingContent.BoolValue;
if (Codec is IHasMeetingInfo meetingInfoCodec)
{
status.MeetingInfo = meetingInfoCodec.MeetingInfo;
}
return status;
}
catch (Exception ex)
{
this.LogError(ex, "Error getting codec status");
return null;
}
}
/// <summary>
/// Sends the full status of the codec, including active calls, camera status, and directory info if applicable
/// </summary>
/// <param name="id"></param>
protected virtual void SendFullStatus(string id = null)
{
if (!Codec.IsReady)
{
return;
}
Task.Run(() => PostStatusMessage(GetStatus(), id));
}
/// <summary>
/// Helper to grab a call with string ID
/// </summary>
private CodecActiveCallItem GetCallWithId(string id)
{
return Codec.ActiveCalls.FirstOrDefault(c => c.Id == id);
}
private string GetCameraMode()
{
string m = "";
if (Codec is IHasCameraAutoMode speakerTrackCodec)
{
m = speakerTrackCodec.CameraAutoModeIsOnFeedback.BoolValue
? eCameraControlMode.Auto.ToString().ToLower()
: eCameraControlMode.Manual.ToString().ToLower();
}
if (Codec is IHasCameraOff cameraOffCodec)
{
if (cameraOffCodec.CameraIsOffFeedback.BoolValue)
m = eCameraControlMode.Off.ToString().ToLower();
}
return m;
}
private Camera GetSelectedCamera(IHasCodecCameras camerasCodec)
{
var camera = new Camera();
if (camerasCodec.SelectedCameraFeedback != null)
camera.Key = camerasCodec.SelectedCameraFeedback.StringValue;
if (camerasCodec.SelectedCamera != null)
{
camera.Name = camerasCodec.SelectedCamera.Name;
if(camerasCodec.SelectedCamera is IHasCameraPtzControl cameraControls)
{
camera.Capabilities = new CameraCapabilities()
{
CanPan = cameraControls is IHasCameraPanControl,
CanTilt = cameraControls is IHasCameraTiltControl,
CanZoom = cameraControls is IHasCameraZoomControl,
CanFocus = cameraControls is IHasCameraFocusControl,
};
};
}
if (camerasCodec.ControllingFarEndCameraFeedback != null)
camera.IsFarEnd = camerasCodec.ControllingFarEndCameraFeedback.BoolValue;
return camera;
}
private List<CodecRoomPreset> GetCurrentPresets()
{
var presetsCodec = Codec as IHasCodecRoomPresets;
List<CodecRoomPreset> currentPresets = null;
if (presetsCodec != null && Codec is IHasFarEndCameraControl &&
(Codec as IHasFarEndCameraControl).ControllingFarEndCameraFeedback.BoolValue)
currentPresets = presetsCodec.FarEndRoomPresets;
else if (presetsCodec != null) currentPresets = presetsCodec.NearEndPresets;
return currentPresets;
}
}
/// <summary>
/// Represents a VideoCodecBaseStateMessage
/// </summary>
public class VideoCodecBaseStateMessage : DeviceStateMessageBase
{
[JsonProperty("calls", NullValueHandling = NullValueHandling.Ignore)]
public List<CodecActiveCallItem> Calls { get; set; }
[JsonProperty("cameraMode", NullValueHandling = NullValueHandling.Ignore)]
public string CameraMode { get; set; }
[JsonProperty("cameraSelfView", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraSelfViewIsOn { get; set; }
[JsonProperty("cameras", NullValueHandling = NullValueHandling.Ignore)]
/// <summary>
/// Gets or sets the Cameras
/// </summary>
public CameraStatus Cameras { get; set; }
[JsonProperty("cameraSupportsAutoMode", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraSupportsAutoMode { get; set; }
[JsonProperty("cameraSupportsOffMode", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraSupportsOffMode { get; set; }
/// <summary>
/// Gets or sets the CurrentDialString
/// </summary>
[JsonProperty("currentDialString", NullValueHandling = NullValueHandling.Ignore)]
public string CurrentDialString { get; set; }
/// <summary>
/// Gets or sets the DirectorySelectedFolderName
/// </summary>
[JsonProperty("directorySelectedFolderName", NullValueHandling = NullValueHandling.Ignore)]
public string DirectorySelectedFolderName { get; set; }
[JsonProperty("hasCameras", NullValueHandling = NullValueHandling.Ignore)]
public bool? HasCameras { get; set; }
[JsonProperty("hasPresets", NullValueHandling = NullValueHandling.Ignore)]
public bool? HasPresets { get; set; }
[JsonProperty("hasRecents", NullValueHandling = NullValueHandling.Ignore)]
public bool? HasRecents { get; set; }
[JsonProperty("initialPhonebookSyncComplete", NullValueHandling = NullValueHandling.Ignore)]
public bool? InitialPhonebookSyncComplete { get; set; }
/// <summary>
/// Gets or sets the Info
/// </summary>
[JsonProperty("info", NullValueHandling = NullValueHandling.Ignore)]
public VideoCodecInfo Info { get; set; }
[JsonProperty("isInCall", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsInCall { get; set; }
[JsonProperty("isReady", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsReady { get; set; }
[JsonProperty("isZoomRoom", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsZoomRoom { get; set; }
/// <summary>
/// Gets or sets the MeetingInfo
/// </summary>
[JsonProperty("meetingInfo", NullValueHandling = NullValueHandling.Ignore)]
public MeetingInfo MeetingInfo { get; set; }
/// <summary>
/// Gets or sets the Presets
/// </summary>
[JsonProperty("presets", NullValueHandling = NullValueHandling.Ignore)]
public List<CodecRoomPreset> Presets { get; set; }
[JsonProperty("privacyModeIsOn", NullValueHandling = NullValueHandling.Ignore)]
public bool? PrivacyModeIsOn { get; set; }
[JsonProperty("receivingContent", NullValueHandling = NullValueHandling.Ignore)]
public bool? ReceivingContent { get; set; }
[JsonProperty("recentCalls", NullValueHandling = NullValueHandling.Ignore)]
public List<CodecCallHistory.CallHistoryEntry> RecentCalls { get; set; }
[JsonProperty("sharingContentIsOn", NullValueHandling = NullValueHandling.Ignore)]
public bool? SharingContentIsOn { get; set; }
/// <summary>
/// Gets or sets the SharingSource
/// </summary>
[JsonProperty("sharingSource", NullValueHandling = NullValueHandling.Ignore)]
public string SharingSource { get; set; }
[JsonProperty("showCamerasWhenNotInCall", NullValueHandling = NullValueHandling.Ignore)]
public bool? ShowCamerasWhenNotInCall { get; set; }
[JsonProperty("showSelfViewByDefault", NullValueHandling = NullValueHandling.Ignore)]
public bool? ShowSelfViewByDefault { get; set; }
[JsonProperty("standbyIsOn", NullValueHandling = NullValueHandling.Ignore)]
public bool? StandbyIsOn { get; set; }
[JsonProperty("supportsAdHocMeeting", NullValueHandling = NullValueHandling.Ignore)]
public bool? SupportsAdHocMeeting { get; set; }
}
/// <summary>
/// Represents a CameraStatus
/// </summary>
public class CameraStatus
{
[JsonProperty("cameraManualSupported", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraManualIsSupported { get; set; }
[JsonProperty("cameraAutoSupported", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraAutoIsSupported { get; set; }
[JsonProperty("cameraOffSupported", NullValueHandling = NullValueHandling.Ignore)]
public bool? CameraOffIsSupported { get; set; }
/// <summary>
/// Gets or sets the CameraMode
/// </summary>
[JsonProperty("cameraMode", NullValueHandling = NullValueHandling.Ignore)]
public string CameraMode { get; set; }
/// <summary>
/// Gets or sets the Cameras
/// </summary>
[JsonProperty("cameraList", NullValueHandling = NullValueHandling.Ignore)]
public List<IHasCameraControls> Cameras { get; set; }
/// <summary>
/// Gets or sets the SelectedCamera
/// </summary>
[JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)]
public Camera SelectedCamera { get; set; }
}
/// <summary>
/// Represents a Camera
/// </summary>
public class Camera
{
/// <summary>
/// Gets or sets the Key
/// </summary>
[JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)]
public string Key { get; set; }
/// <summary>
/// Gets or sets the Name
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
[JsonProperty("isFarEnd", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsFarEnd { get; set; }
/// <summary>
/// Gets or sets the Capabilities
/// </summary>
[JsonProperty("capabilities", NullValueHandling = NullValueHandling.Ignore)]
public CameraCapabilities Capabilities { get; set; }
}
/// <summary>
/// Represents a CameraCapabilities
/// </summary>
public class CameraCapabilities
{
[JsonProperty("canPan", NullValueHandling = NullValueHandling.Ignore)]
public bool? CanPan { get; set; }
[JsonProperty("canTilt", NullValueHandling = NullValueHandling.Ignore)]
public bool? CanTilt { get; set; }
[JsonProperty("canZoom", NullValueHandling = NullValueHandling.Ignore)]
public bool? CanZoom { get; set; }
[JsonProperty("canFocus", NullValueHandling = NullValueHandling.Ignore)]
public bool? CanFocus { get; set; }
}
}

View file

@ -14,6 +14,7 @@ using PepperDash.Essentials.Devices.Common.Codec;
using PepperDash.Essentials.Devices.Common.Displays; using PepperDash.Essentials.Devices.Common.Displays;
using PepperDash.Essentials.Devices.Common.SoftCodec; using PepperDash.Essentials.Devices.Common.SoftCodec;
using PepperDash.Essentials.Devices.Common.VideoCodec; using PepperDash.Essentials.Devices.Common.VideoCodec;
using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces;
using PepperDash.Essentials.Room.MobileControl; using PepperDash.Essentials.Room.MobileControl;
namespace PepperDash.Essentials namespace PepperDash.Essentials
@ -161,9 +162,39 @@ namespace PepperDash.Essentials
// ── Codecs ─────────────────────────────────────────────────────────────── // ── Codecs ───────────────────────────────────────────────────────────────
new MessengerFactoryEntry( new MessengerFactoryEntry(
typeof(VideoCodecBase), typeof(IHasReady),
(d, mp, ck) => new VideoCodecBaseMessenger( (d, mp, ck) => new IHasReadyMessenger(
$"{d.Key}-videoCodec-{ck}", (VideoCodecBase)d, mp) $"{d.Key}-ready-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(IHasDialer),
(d, mp, ck) => new IHasDialerMessenger(
$"{d.Key}-dialer-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(ICodecCallControls),
(d, mp, ck) => new ICallControlsMessenger(
$"{d.Key}-callControls-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(IHasContentSharing),
(d, mp, ck) => new IHasContentSharingMessenger(
$"{d.Key}-contentSharing-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(IHasStandbyMode),
(d, mp, ck) => new IHasStandbyModeMessenger(
$"{d.Key}-standby-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(IPrivacy),
(d, mp, ck) => new IPrivacyMessenger(
$"{d.Key}-privacy-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(IVideoCodecInfo),
(d, mp, ck) => new IVideoCodecInfoMessenger(
$"{d.Key}-codecInfo-{ck}", mp, d)
), ),
new MessengerFactoryEntry( new MessengerFactoryEntry(
typeof(IHasDirectory), typeof(IHasDirectory),
@ -186,6 +217,11 @@ namespace PepperDash.Essentials
$"{d.Key}-codecCameras-{ck}", mp, (VideoCodecBase)d), $"{d.Key}-codecCameras-{ck}", mp, (VideoCodecBase)d),
predicate: d => d is VideoCodecBase && d is IHasCodecCameras predicate: d => d is VideoCodecBase && d is IHasCodecCameras
), ),
new MessengerFactoryEntry(
typeof(IHasCodecRoomPresets),
(d, mp, ck) => new IHasCodecRoomPresetsMessenger(
$"{d.Key}-codecRoomPresets-{ck}", mp, d)
),
new MessengerFactoryEntry( new MessengerFactoryEntry(
typeof(IHasCodecSelfView), typeof(IHasCodecSelfView),
(d, mp, ck) => new IHasCodecSelfViewMessenger( (d, mp, ck) => new IHasCodecSelfViewMessenger(
@ -201,6 +237,16 @@ namespace PepperDash.Essentials
(d, mp, ck) => new IHasFarEndContentStatusMessenger( (d, mp, ck) => new IHasFarEndContentStatusMessenger(
$"{d.Key}-farEndContent-{ck}", mp, d) $"{d.Key}-farEndContent-{ck}", mp, d)
), ),
new MessengerFactoryEntry(
typeof(IHasMeetingInfo),
(d, mp, ck) => new IHasMeetingInfoMessenger(
$"{d.Key}-meetingInfo-{ck}", mp, d)
),
new MessengerFactoryEntry(
typeof(IHasStartMeeting),
(d, mp, ck) => new IHasStartMeetingMessenger(
$"{d.Key}-startMeeting-{ck}", mp, d)
),
new MessengerFactoryEntry( new MessengerFactoryEntry(
typeof(IDialerCallStatus), typeof(IDialerCallStatus),
(d, mp, ck) => new IDialerCallStatusMessenger( (d, mp, ck) => new IDialerCallStatusMessenger(