diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/Interfaces/ICodecCallControls.cs b/src/PepperDash.Essentials.Devices.Common/Codec/Interfaces/ICodecCallControls.cs new file mode 100644 index 00000000..6110bb1a --- /dev/null +++ b/src/PepperDash.Essentials.Devices.Common/Codec/Interfaces/ICodecCallControls.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace PepperDash.Essentials.Devices.Common.Codec; + +/// +/// Defines call control functionality for a codec, extending the base dialer interface +/// with active call list access and meeting dialing. +/// +public interface ICodecCallControls : IHasDialer +{ + /// + /// Gets the list of currently active, dialing, or incoming calls + /// + List ActiveCalls { get; } + + /// + /// Dials the specified meeting + /// + /// The meeting to dial + void Dial(Meeting meeting); +} diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/iHasDialer.cs b/src/PepperDash.Essentials.Devices.Common/Codec/iHasDialer.cs index f378e621..95371404 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/iHasDialer.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/iHasDialer.cs @@ -53,5 +53,7 @@ public interface IHasDialer /// Gets a value indicating whether the device is currently in a call /// bool IsInCall { get; } + + } diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/iVideoCodecInfo.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/iVideoCodecInfo.cs index 8b1be9e9..a2dd9e55 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/iVideoCodecInfo.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/iVideoCodecInfo.cs @@ -5,7 +5,7 @@ namespace PepperDash.Essentials.Devices.Common.Codec /// /// Implements a common set of data about a codec /// - public interface iVideoCodecInfo + public interface IVideoCodecInfo { /// /// Gets the codec information diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs index 8ed740d3..59f6097b 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharpPro.DeviceSupport; -using Crestron.SimplSharp; using PepperDash.Core; using PepperDash.Core.Intersystem; 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. /// 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; @@ -338,7 +337,7 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu /// /// Fired when the Codec is ready to be used /// - public event EventHandler IsReadyChange; + public event EventHandler IsReadyEvent; /// /// Dials the specified meeting @@ -405,7 +404,7 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu try { IsReady = true; - IsReadyChange?.Invoke(this, new EventArgs()); + IsReadyEvent?.Invoke(this, new IsReadyEventArgs(IsReady)); } catch (Exception e) { @@ -498,7 +497,7 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu LinkVideoCodecInfoToApi(trilist, joinMap); // 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) { diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCallControlsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCallControlsMessenger.cs new file mode 100644 index 00000000..c28cbede --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCallControlsMessenger.cs @@ -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 +{ + /// + /// Provides a messaging bridge for devices implementing + /// + public class ICallControlsMessenger : MessengerBase + { + private readonly ICodecCallControls _callControls; + + /// Initializes a new instance of the 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; + } + + /// + 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())); + + AddAction("/endCallById", (id, content) => + { + var s = content.ToObject>(); + var call = GetCallWithId(s.Value); + if (call != null) + _callControls.EndCall(call); + }); + + AddAction("/rejectById", (id, content) => + { + var s = content.ToObject>(); + var call = GetCallWithId(s.Value); + if (call != null) + _callControls.RejectCall(call); + }); + + AddAction("/acceptById", (id, content) => + { + var s = content.ToObject>(); + 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); + } + } + + /// + /// State message for + /// + public class ICallControlsStateMessage : DeviceStateMessageBase + { + /// + /// Gets or sets the list of active calls. Null if unknown or not applicable. + /// + [JsonProperty("calls", NullValueHandling = NullValueHandling.Ignore)] + public List Calls { get; set; } + + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCallHistoryMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCallHistoryMessenger.cs index 10e2f8d6..388f6a3c 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCallHistoryMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCallHistoryMessenger.cs @@ -65,7 +65,7 @@ namespace PepperDash.Essentials.AppServer.Messengers { PostStatusMessage(new IHasCallHistoryStateMessage { - RecentCalls = recents + RecentCalls = recents, }); } } @@ -76,9 +76,22 @@ namespace PepperDash.Essentials.AppServer.Messengers } } + /// + /// State message for + /// + /// public class IHasCallHistoryStateMessage : DeviceStateMessageBase { + /// + /// Gets or sets the list of recent calls. Null if unknown or not applicable. + /// [JsonProperty("recentCalls", NullValueHandling = NullValueHandling.Ignore)] public List RecentCalls { get; set; } + + /// + /// Gets or sets a value indicating whether the device has call history functionality. Null if unknown or not applicable. + /// + [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. } } diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasWithControlsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasWithControlsMessenger.cs index e20eabdc..cb83a26e 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasWithControlsMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasWithControlsMessenger.cs @@ -81,7 +81,8 @@ namespace PepperDash.Essentials.AppServer.Messengers foreach (var cam in CameraController.Cameras) { - cameraList.Add(new KeyName{ + cameraList.Add(new KeyName + { Key = cam.Key, 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 { CameraList = cameraList, - SelectedCamera = selectedCamera + SelectedCamera = selectedCamera, + CameraMode = mode, + HasCameraAutoMode = CameraController is IHasCameraAutoMode, }; PostStatusMessage(state, clientId); @@ -122,6 +140,18 @@ namespace PepperDash.Essentials.AppServer.Messengers /// [JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)] public IKeyName SelectedCamera { get; set; } + + /// + /// Indicates whether the device has any cameras. Null if unknown or not applicable. + /// + [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 diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecCamerasMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecCamerasMessenger.cs index c3f8d3fd..9ea151bc 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecCamerasMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecCamerasMessenger.cs @@ -249,6 +249,35 @@ namespace PepperDash.Essentials.AppServer.Messengers 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() { try @@ -294,6 +323,31 @@ namespace PepperDash.Essentials.AppServer.Messengers 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 @@ -306,5 +360,96 @@ namespace PepperDash.Essentials.AppServer.Messengers [JsonProperty("presets", NullValueHandling = NullValueHandling.Ignore)] public List Presets { get; set; } + + [JsonProperty("cameraSupportsAutoMode", NullValueHandling = NullValueHandling.Ignore)] + public bool? CameraSupportsAutoMode { get; set; } + + [JsonProperty("cameraSupportsOffMode", NullValueHandling = NullValueHandling.Ignore)] + public bool? CameraSupportsOffMode { get; set; } + } + + /// + /// Represents a CameraStatus + /// + 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; } + + + /// + /// Gets or sets the CameraMode + /// + [JsonProperty("cameraMode", NullValueHandling = NullValueHandling.Ignore)] + public string CameraMode { get; set; } + + + /// + /// Gets or sets the Cameras + /// + [JsonProperty("cameraList", NullValueHandling = NullValueHandling.Ignore)] + public List Cameras { get; set; } + + + /// + /// Gets or sets the SelectedCamera + /// + [JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)] + public Camera SelectedCamera { get; set; } + } + + /// + /// Represents a Camera + /// + public class Camera + { + + /// + /// Gets or sets the Key + /// + [JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)] + public string Key { get; set; } + + + /// + /// Gets or sets the Name + /// + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name { get; set; } + + [JsonProperty("isFarEnd", NullValueHandling = NullValueHandling.Ignore)] + public bool? IsFarEnd { get; set; } + + + /// + /// Gets or sets the Capabilities + /// + [JsonProperty("capabilities", NullValueHandling = NullValueHandling.Ignore)] + public CameraCapabilities Capabilities { get; set; } + } + + /// + /// Represents a CameraCapabilities + /// + 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; } + } } diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecRoomPresetsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecRoomPresetsMessenger.cs new file mode 100644 index 00000000..4bcbe254 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecRoomPresetsMessenger.cs @@ -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 +{ + /// + /// Provides a messaging bridge for devices implementing + /// + 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 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 Presets { get; set; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecSelfViewMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecSelfViewMessenger.cs index dd31e547..85e103f7 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecSelfViewMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecSelfViewMessenger.cs @@ -53,7 +53,8 @@ namespace PepperDash.Essentials.AppServer.Messengers { PostStatusMessage(new IHasCodecSelfViewStateMessage { - CameraSelfViewIsOn = _selfView.SelfviewIsOnFeedback.BoolValue + CameraSelfViewIsOn = _selfView.SelfviewIsOnFeedback.BoolValue, + ShowSelfViewByDefault = _selfView.ShowSelfViewByDefault }); } catch (Exception ex) @@ -63,9 +64,23 @@ namespace PepperDash.Essentials.AppServer.Messengers } } + /// + /// State message for + /// public class IHasCodecSelfViewStateMessage : DeviceStateMessageBase { + /// + /// Gets or sets a value indicating whether the codec's self view is currently on. Null if unknown or not applicable. + /// + /// [JsonProperty("cameraSelfView", NullValueHandling = NullValueHandling.Ignore)] public bool? CameraSelfViewIsOn { get; set; } + + /// + /// Gets or sets a value indicating whether the codec is set to show self view by default. Null if unknown or not applicable. + /// + /// + [JsonProperty("showSelfViewByDefault", NullValueHandling = NullValueHandling.Ignore)] + public bool? ShowSelfViewByDefault { get; set; } } } diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasContentSharingMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasContentSharingMessenger.cs new file mode 100644 index 00000000..159c9a02 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasContentSharingMessenger.cs @@ -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 +{ + /// + /// Provides a messaging bridge for devices implementing + /// + 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; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDialerMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDialerMessenger.cs new file mode 100644 index 00000000..143f953e --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDialerMessenger.cs @@ -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 +{ + /// + /// Provides a messaging bridge for devices implementing + /// + 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; + } + + /// + 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>(); + _dialer.Dial(value.Value); + }); + + + AddAction("/endAllCalls", (id, content) => _dialer.EndAllCalls()); + + AddAction("/dtmf", (id, content) => + { + var s = content.ToObject>(); + _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"); + } + } + } + + /// + /// Message class representing the state of a device implementing + /// + public class IHasDialerStateMessage : DeviceStateMessageBase + { + /// + /// Indicates whether the device is currently in a call + /// + [JsonProperty("isInCall", NullValueHandling = NullValueHandling.Ignore)] + public bool? IsInCall { get; set; } + + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDirectoryMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDirectoryMessenger.cs index 8859a9fc..a19a4ae8 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDirectoryMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDirectoryMessenger.cs @@ -129,7 +129,7 @@ namespace PepperDash.Essentials.AppServer.Messengers InitialPhonebookSyncComplete = _directory.PhonebookSyncState.InitialSyncComplete, HasDirectory = true, HasDirectorySearch = true, - }); + }); } } @@ -147,5 +147,11 @@ namespace PepperDash.Essentials.AppServer.Messengers [JsonProperty("hasDirectorySearch", NullValueHandling = NullValueHandling.Ignore)] public bool? HasDirectorySearch { get; set; } + /// + /// Gets or sets the DirectorySelectedFolderName + /// + [JsonProperty("directorySelectedFolderName", NullValueHandling = NullValueHandling.Ignore)] + public string DirectorySelectedFolderName { get; set; } + } } diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasFarEndContentStatusMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasFarEndContentStatusMessenger.cs index 0e307a6f..c736bf82 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasFarEndContentStatusMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasFarEndContentStatusMessenger.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PepperDash.Core.Logging; @@ -12,7 +13,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// public class IHasFarEndContentStatusMessenger : MessengerBase { - private readonly IHasFarEndContentStatus _device; + private readonly IHasFarEndContentStatus device; /// /// Initializes a new instance of the class. @@ -20,7 +21,7 @@ namespace PepperDash.Essentials.AppServer.Messengers public IHasFarEndContentStatusMessenger(string key, string messagePath, EssentialsDevice 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)); } /// @@ -28,7 +29,11 @@ namespace PepperDash.Essentials.AppServer.Messengers { 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) @@ -45,10 +50,33 @@ namespace PepperDash.Essentials.AppServer.Messengers 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"); + } + } } + /// + /// Message class representing the state of a device implementing + /// public class IHasFarEndContentStatusStateMessage : DeviceStateMessageBase { + /// + /// Indicates whether the device is currently receiving content from the far end + /// [JsonProperty("receivingContent", NullValueHandling = NullValueHandling.Ignore)] public bool? ReceivingContent { get; set; } } diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasMeetingInfoMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasMeetingInfoMessenger.cs new file mode 100644 index 00000000..7aeb6765 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasMeetingInfoMessenger.cs @@ -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 +{ + /// + /// Provides a messaging bridge for devices implementing + /// + public class IHasMeetingInfoMessenger : MessengerBase + { + private readonly IHasMeetingInfo _meetingInfo; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + 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; + } + + /// + 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"); + } + } + } + + /// + /// Message class for devices implementing + /// + public class IHasMeetingInfoStateMessage : DeviceStateMessageBase + { + /// + /// Gets or sets the MeetingInfo + /// + [JsonProperty("meetingInfo", NullValueHandling = NullValueHandling.Ignore)] + public MeetingInfo MeetingInfo { get; set; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasReadyMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasReadyMessenger.cs new file mode 100644 index 00000000..3b704087 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasReadyMessenger.cs @@ -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 +{ + /// + /// Provides a messaging bridge for devices implementing + /// + 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; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasStandbyModeMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasStandbyModeMessenger.cs new file mode 100644 index 00000000..da493d4d --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasStandbyModeMessenger.cs @@ -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 +{ + /// + /// Provides a messaging bridge for devices implementing + /// + public class IHasStandbyModeMessenger : MessengerBase + { + private readonly IHasStandbyMode _standby; + + /// + /// Initializes a new instance of the class. + /// + 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; + } + + /// + 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"); + } + } + } + + /// + /// State message for + /// + public class IHasStandbyModeStateMessage : DeviceStateMessageBase + { + /// + /// Indicates whether the device is in standby mode. Null if unknown or not applicable. + /// + [JsonProperty("standbyIsOn", NullValueHandling = NullValueHandling.Ignore)] + public bool? StandbyIsOn { get; set; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasStartMeetingMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasStartMeetingMessenger.cs new file mode 100644 index 00000000..bc8559c0 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasStartMeetingMessenger.cs @@ -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 +{ + /// + /// Provides a messaging bridge for devices implementing + /// + public class IHasStartMeetingMessenger : MessengerBase + { + private readonly IHasStartMeeting _startMeeting; + + /// + /// Initializes a new instance of the class. + /// + /// The key for the messenger. + /// The message path for the messenger. + /// The device implementing . + /// Thrown if the device does not implement . + public IHasStartMeetingMessenger(string key, string messagePath, EssentialsDevice device) + : base(key, messagePath, device) + { + _startMeeting = device as IHasStartMeeting ?? throw new ArgumentNullException(nameof(device)); + } + + /// + 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>(); + _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"); + } + } + } + + /// + /// State message for devices implementing + /// + public class IHasStartMeetingStateMessage : DeviceStateMessageBase + { + /// + /// Indicates whether the device supports ad-hoc meetings (meetings started from the device rather than an external calendar invite) + /// + /// + [JsonProperty("supportsAdHocMeeting", NullValueHandling = NullValueHandling.Ignore)] + public bool SupportsAdHocMeeting { get; set; } = true; + + /// + /// The default meeting duration in minutes for meetings started from the device. Null if unknown or not applicable. + /// + [JsonProperty("defaultMeetingDurationMin", NullValueHandling = NullValueHandling.Ignore)] + public uint DefaultMeetingDurationMin { get; set; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IPrivacyMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IPrivacyMessenger.cs new file mode 100644 index 00000000..7657606e --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IPrivacyMessenger.cs @@ -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 +{ + /// + /// Provides a messaging bridge for devices implementing + /// + public class IPrivacyMessenger : MessengerBase + { + private readonly IPrivacy _privacy; + + /// + /// Initializes a new instance of the class. + /// + /// The key for the messenger. + /// The message path for the messenger. + /// The device implementing . + 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; + } + + /// + 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"); + } + } + } + + /// + /// State message for + /// + public class IPrivacyStateMessage : DeviceStateMessageBase + { + /// + /// Gets or sets a value indicating whether privacy mode is on. Null if unknown or not + /// applicable. + /// + [JsonProperty("privacyModeIsOn", NullValueHandling = NullValueHandling.Ignore)] + public bool? PrivacyModeIsOn { get; set; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IVideoCodecInfoMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IVideoCodecInfoMessenger.cs new file mode 100644 index 00000000..8a8d51fa --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IVideoCodecInfoMessenger.cs @@ -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 +{ + /// + /// Provides a messaging bridge for devices implementing + /// + public class IVideoCodecInfoMessenger : MessengerBase + { + private readonly IVideoCodecInfo _codecInfo; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + public IVideoCodecInfoMessenger(string key, string messagePath, EssentialsDevice device) + : base(key, messagePath, device) + { + _codecInfo = device as IVideoCodecInfo ?? throw new ArgumentNullException(nameof(device)); + } + + /// + 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"); + } + } + } + + /// + /// State message for + /// + public class iVideoCodecInfoStateMessage : DeviceStateMessageBase + { + /// + /// Gets or sets the codec information. Null if unknown or not applicable. + /// + [JsonProperty("info", NullValueHandling = NullValueHandling.Ignore)] + public VideoCodecInfo Info { get; set; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/VideoCodecBaseMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/VideoCodecBaseMessenger.cs deleted file mode 100644 index 58e5da2f..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/VideoCodecBaseMessenger.cs +++ /dev/null @@ -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 -{ - /// - /// Provides a messaging bridge for a VideoCodecBase device - /// - public class VideoCodecBaseMessenger : MessengerBase - { - /// - /// - /// - protected VideoCodecBase Codec { get; private set; } - - /// - /// - /// - /// - /// - /// - 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; - } - - /// - /// - /// - /// - /// - 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"); - } - } - - /// - /// Called from base's RegisterWithAppServer method - /// - 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>(); - - Codec.Dial(value.Value); - }); - - AddAction("/dialMeeting", (id, content) => Codec.Dial(content.ToObject())); - - AddAction("/endCallById", (id, content) => - { - var s = content.ToObject>(); - 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>(); - Codec.SendDtmf(s.Value); - }); - - AddAction("/rejectById", (id, content) => - { - var s = content.ToObject>(); - - var call = GetCallWithId(s.Value); - if (call != null) - Codec.RejectCall(call); - }); - - AddAction("/acceptById", (id, content) => - { - var s = content.ToObject>(); - - 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"); - } - } - - /// - /// Handler for codec changes - /// - private void Codec_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e) - { - SendFullStatus(); - } - - /// - /// - /// - 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"); - } - } - - /// - /// Helper method to build call status for vtc - /// - /// - 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; - } - } - - /// - /// Sends the full status of the codec, including active calls, camera status, and directory info if applicable - /// - /// - protected virtual void SendFullStatus(string id = null) - { - if (!Codec.IsReady) - { - return; - } - - Task.Run(() => PostStatusMessage(GetStatus(), id)); - } - - /// - /// Helper to grab a call with string ID - /// - 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 GetCurrentPresets() - { - var presetsCodec = Codec as IHasCodecRoomPresets; - - List 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; - } - } - - /// - /// Represents a VideoCodecBaseStateMessage - /// - public class VideoCodecBaseStateMessage : DeviceStateMessageBase - { - - [JsonProperty("calls", NullValueHandling = NullValueHandling.Ignore)] - public List 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)] - /// - /// Gets or sets the Cameras - /// - 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; } - - - /// - /// Gets or sets the CurrentDialString - /// - [JsonProperty("currentDialString", NullValueHandling = NullValueHandling.Ignore)] - public string CurrentDialString { get; set; } - - - - /// - /// Gets or sets the DirectorySelectedFolderName - /// - [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; } - - - /// - /// Gets or sets the Info - /// - [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; } - - - /// - /// Gets or sets the MeetingInfo - /// - [JsonProperty("meetingInfo", NullValueHandling = NullValueHandling.Ignore)] - public MeetingInfo MeetingInfo { get; set; } - - - /// - /// Gets or sets the Presets - /// - [JsonProperty("presets", NullValueHandling = NullValueHandling.Ignore)] - public List 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 RecentCalls { get; set; } - - [JsonProperty("sharingContentIsOn", NullValueHandling = NullValueHandling.Ignore)] - public bool? SharingContentIsOn { get; set; } - - - /// - /// Gets or sets the SharingSource - /// - [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; } - } - - /// - /// Represents a CameraStatus - /// - 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; } - - - /// - /// Gets or sets the CameraMode - /// - [JsonProperty("cameraMode", NullValueHandling = NullValueHandling.Ignore)] - public string CameraMode { get; set; } - - - /// - /// Gets or sets the Cameras - /// - [JsonProperty("cameraList", NullValueHandling = NullValueHandling.Ignore)] - public List Cameras { get; set; } - - - /// - /// Gets or sets the SelectedCamera - /// - [JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)] - public Camera SelectedCamera { get; set; } - } - - /// - /// Represents a Camera - /// - public class Camera - { - - /// - /// Gets or sets the Key - /// - [JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)] - public string Key { get; set; } - - - /// - /// Gets or sets the Name - /// - [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string Name { get; set; } - - [JsonProperty("isFarEnd", NullValueHandling = NullValueHandling.Ignore)] - public bool? IsFarEnd { get; set; } - - - /// - /// Gets or sets the Capabilities - /// - [JsonProperty("capabilities", NullValueHandling = NullValueHandling.Ignore)] - public CameraCapabilities Capabilities { get; set; } - } - - /// - /// Represents a CameraCapabilities - /// - 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; } - - } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/MessengerFactoryRegistry.cs b/src/PepperDash.Essentials.MobileControl/MessengerFactoryRegistry.cs index 96bc8ee5..ae757962 100644 --- a/src/PepperDash.Essentials.MobileControl/MessengerFactoryRegistry.cs +++ b/src/PepperDash.Essentials.MobileControl/MessengerFactoryRegistry.cs @@ -14,6 +14,7 @@ using PepperDash.Essentials.Devices.Common.Codec; using PepperDash.Essentials.Devices.Common.Displays; using PepperDash.Essentials.Devices.Common.SoftCodec; using PepperDash.Essentials.Devices.Common.VideoCodec; +using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; using PepperDash.Essentials.Room.MobileControl; namespace PepperDash.Essentials @@ -161,9 +162,39 @@ namespace PepperDash.Essentials // ── Codecs ─────────────────────────────────────────────────────────────── new MessengerFactoryEntry( - typeof(VideoCodecBase), - (d, mp, ck) => new VideoCodecBaseMessenger( - $"{d.Key}-videoCodec-{ck}", (VideoCodecBase)d, mp) + typeof(IHasReady), + (d, mp, ck) => new IHasReadyMessenger( + $"{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( typeof(IHasDirectory), @@ -186,6 +217,11 @@ namespace PepperDash.Essentials $"{d.Key}-codecCameras-{ck}", mp, (VideoCodecBase)d), 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( typeof(IHasCodecSelfView), (d, mp, ck) => new IHasCodecSelfViewMessenger( @@ -201,6 +237,16 @@ namespace PepperDash.Essentials (d, mp, ck) => new IHasFarEndContentStatusMessenger( $"{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( typeof(IDialerCallStatus), (d, mp, ck) => new IDialerCallStatusMessenger(