From 1676f5a9560335e0543c1cb8716a46b779bb6e4b Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 8 Oct 2020 17:09:59 -0600 Subject: [PATCH] add phone call logic and commands to Zoom Room --- .../JoinMaps/VideoCodecControllerJoinMap.cs | 40 +++++++++ .../DeviceTypeInterfaces/IHasPhoneDialing.cs | 14 +++ .../PepperDash_Essentials_Core.csproj | 1 + .../VideoCodec/VideoCodecBase.cs | 21 +++++ .../VideoCodec/ZoomRoom/ResponseObjects.cs | 80 +++++++++++++++++ .../VideoCodec/ZoomRoom/ZoomRoom.cs | 87 ++++++++++++++++--- 6 files changed, 232 insertions(+), 11 deletions(-) create mode 100644 essentials-framework/Essentials Core/PepperDashEssentialsBase/DeviceTypeInterfaces/IHasPhoneDialing.cs diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs index 50813700..8054a077 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs @@ -760,6 +760,46 @@ namespace PepperDash_Essentials_Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); + [JoinName("DialPhoneCall")] + public JoinDataComplete DialPhone = + new JoinDataComplete(new JoinData { JoinNumber = 72, JoinSpan = 1 }, + new JoinMetadata + { + Description = "Dial Phone", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("PhoneHookState")] + public JoinDataComplete PhoneHookState = + new JoinDataComplete(new JoinData { JoinNumber = 72, JoinSpan = 1 }, + new JoinMetadata + { + Description = "Dial Phone", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("EndPhoneCall")] + public JoinDataComplete HangUpPhone = + new JoinDataComplete(new JoinData { JoinNumber = 73, JoinSpan = 1 }, + new JoinMetadata + { + Description = "Hang Up PHone", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("PhoneString")] + public JoinDataComplete PhoneDialString = + new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata + { + Description = "Phone Dial String", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Serial + }); + public VideoCodecControllerJoinMap(uint joinStart) : base(joinStart, typeof (VideoCodecControllerJoinMap)) { } diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/DeviceTypeInterfaces/IHasPhoneDialing.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/DeviceTypeInterfaces/IHasPhoneDialing.cs new file mode 100644 index 00000000..2b7af8ad --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/DeviceTypeInterfaces/IHasPhoneDialing.cs @@ -0,0 +1,14 @@ +using PepperDash.Essentials.Core; + +namespace PepperDash_Essentials_Core.DeviceTypeInterfaces +{ + public interface IHasPhoneDialing + { + BoolFeedback PhoneOffHookFeedback { get; } + StringFeedback CallerIdNameFeedback { get; } + StringFeedback CallerIdNumberFeedback { get; } + void DialPhoneCall(string number); + void EndPhoneCall(); + void SendDtmfToPhone(string digit); + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj index 7524c8d7..9cc4365f 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj @@ -197,6 +197,7 @@ + diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/VideoCodecBase.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/VideoCodecBase.cs index b0f6395d..8b7b3bce 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/VideoCodecBase.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/VideoCodecBase.cs @@ -19,6 +19,7 @@ using PepperDash.Essentials.Devices.Common.Cameras; using PepperDash.Essentials.Devices.Common.Codec; using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; using PepperDash_Essentials_Core.Bridges.JoinMaps; +using PepperDash_Essentials_Core.DeviceTypeInterfaces; using Feedback = PepperDash.Essentials.Core.Feedback; namespace PepperDash.Essentials.Devices.Common.VideoCodec @@ -348,6 +349,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec (codec as IHasFarEndContentStatus).ReceivingContent.LinkInputSig(trilist.BooleanInput[joinMap.RecievingContent.JoinNumber]); } + if (codec is IHasPhoneDialing) + { + LinkVideoCodecPhoneToApi(codec as IHasPhoneDialing, trilist, joinMap); + } + trilist.OnlineStatusChange += (device, args) => { if (!args.DeviceOnLine) return; @@ -389,6 +395,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec (codec as IHasCameraOff).CameraIsOffFeedback.FireUpdate(); } + if (codec is IHasPhoneDialing) + { + (codec as IHasPhoneDialing).PhoneOffHookFeedback.FireUpdate(); + } + SharingContentIsOnFeedback.FireUpdate(); trilist.SetBool(joinMap.HookState.JoinNumber, IsInCall); @@ -397,6 +408,16 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec }; } + private void LinkVideoCodecPhoneToApi(IHasPhoneDialing codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + codec.PhoneOffHookFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PhoneHookState.JoinNumber]); + + trilist.SetSigFalseAction(joinMap.DialPhone.JoinNumber, + () => codec.DialPhoneCall(trilist.StringOutput[joinMap.PhoneDialString.JoinNumber].StringValue)); + + trilist.SetSigFalseAction(joinMap.HangUpPhone.JoinNumber, codec.EndPhoneCall); + } + private void LinkVideoCodecSelfviewPositionToApi(IHasSelfviewPosition codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { trilist.SetSigFalseAction(joinMap.SelfviewPosition.JoinNumber, codec.SelfviewPipPositionToggle); diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ResponseObjects.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ResponseObjects.cs index 5f316523..45c4193c 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ResponseObjects.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ResponseObjects.cs @@ -58,6 +58,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public List AudioInputs { get; set; } public List AudioOuputs { get; set; } public List Cameras { get; set; } + public zEvent.PhoneCallStatus PhoneCall { get; set; } public ZoomRoomStatus() { @@ -74,6 +75,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom AudioInputs = new List(); AudioOuputs = new List(); Cameras = new List(); + PhoneCall = new zEvent.PhoneCallStatus(); } } @@ -700,6 +702,84 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom [JsonProperty("why_cannot_pin_share")] public string WhyCannotPinShare { get; set; } } + + public class PhoneCallStatus:NotifiableObject + { + private bool _isIncomingCall; + private string _peerDisplayName; + private string _peerNumber; + + private bool _offHook; + + public string CallId { get; set; } + public bool IsIncomingCall { + get { return _isIncomingCall; } + set + { + if(value == _isIncomingCall) return; + + _isIncomingCall = value; + NotifyPropertyChanged("IsIncomingCall"); + } } + + public string PeerDisplayName + { + get { return _peerDisplayName; } + set + { + if (value == _peerDisplayName) return; + _peerDisplayName = value; + NotifyPropertyChanged("PeerDisplayName"); + } + } + + public string PeerNumber + { + get { return _peerNumber; } + set + { + if (value == _peerNumber) return; + + _peerNumber = value; + NotifyPropertyChanged("PeerNumber"); + } + } + + public string PeerUri { get; set; } + + private ePhoneCallStatus _status; + public ePhoneCallStatus Status + { + get { return _status; } + set + { + _status = value; + OffHook = _status == ePhoneCallStatus.PhoneCallStatus_Accepted || + _status == ePhoneCallStatus.PhoneCallStatus_InCall || + _status == ePhoneCallStatus.PhoneCallStatus_Init; + } + } + + public bool OffHook + { + get { return _offHook; } + set + { + if (value == _offHook) return; + + _offHook = value; + NotifyPropertyChanged("OffHook"); + } + } + } + + public enum ePhoneCallStatus + { + PhoneCallStatus_Terminated, + PhoneCallStatus_Accepted, + PhoneCallStatus_InCall, + PhoneCallStatus_Init, + } } /// diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs index 578e3dae..b8d29077 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs @@ -17,12 +17,14 @@ using PepperDash.Essentials.Devices.Common.Cameras; using PepperDash.Essentials.Devices.Common.Codec; using PepperDash.Essentials.Devices.Common.VideoCodec.Cisco; using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; +using PepperDash_Essentials_Core.DeviceTypeInterfaces; namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { public class ZoomRoom : VideoCodecBase, IHasCodecSelfView, IHasDirectoryHistoryStack, ICommunicationMonitor, IRouting, - IHasScheduleAwareness, IHasCodecCameras, IHasParticipants, IHasCameraOff, IHasCameraAutoMode, IHasFarEndContentStatus, IHasSelfviewPosition + IHasScheduleAwareness, IHasCodecCameras, IHasParticipants, IHasCameraOff, IHasCameraAutoMode, + IHasFarEndContentStatus, IHasSelfviewPosition, IHasPhoneDialing { private const long MeetingRefreshTimer = 60000; private const uint DefaultMeetingDurationMin = 30; @@ -114,6 +116,10 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom SupportsCameraOff = _props.SupportsCameraOff; SupportsCameraAutoMode = _props.SupportsCameraAutoMode; + + PhoneOffHookFeedback = new BoolFeedback(PhoneOffHookFeedbackFunc); + CallerIdNameFeedback = new StringFeedback(CallerIdNameFeedbackFunc); + CallerIdNumberFeedback = new StringFeedback(CallerIdNumberFeedbackFunc); } public CommunicationGather PortGather { get; private set; } @@ -195,7 +201,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom protected Func CameraAutoModeIsOnFeedbackFunc { get { return () => false; } - } + } protected Func SelfviewPipPositionFeedbackFunc { @@ -506,6 +512,28 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom break; } }; + + Status.PhoneCall.PropertyChanged += (o, a) => + { + switch (a.PropertyName) + { + case "IsIncomingCall": + Debug.Console(1, this, "Incoming Phone Call: {0}", Status.PhoneCall.IsIncomingCall); + break; + case "PeerDisplayName": + Debug.Console(1, this, "Peer Display Name: {0}", Status.PhoneCall.PeerDisplayName); + CallerIdNameFeedback.FireUpdate(); + break; + case "PeerNumber": + Debug.Console(1, this, "Peer Number: {0}", Status.PhoneCall.PeerNumber); + CallerIdNumberFeedback.FireUpdate(); + break; + case "OffHook": + Debug.Console(1, this, "Phone is OffHook: {0}", Status.PhoneCall.OffHook); + PhoneOffHookFeedback.FireUpdate(); + break; + } + }; } private void SetUpDirectory() @@ -555,8 +583,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom if (!_props.DisablePhonebookAutoDownload) { CrestronConsole.AddNewConsoleCommand(s => SendText("zCommand Phonebook List Offset: 0 Limit: 512"), - "GetZoomRoomContacts", "Triggers a refresh of the codec phonebook", - ConsoleAccessLevelEnum.AccessOperator); + "GetZoomRoomContacts", "Triggers a refresh of the codec phonebook", + ConsoleAccessLevelEnum.AccessOperator); } CrestronConsole.AddNewConsoleCommand(s => GetBookings(), "GetZoomRoomBookings", @@ -1066,7 +1094,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom case "sharingstate": { JsonConvert.PopulateObject(responseObj.ToString(), Status.Call.Sharing); - + SetLayout(); break; @@ -1181,6 +1209,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom break; } + case "phonecallstatus": + { + JsonConvert.PopulateObject(responseObj.ToString(), Status.PhoneCall); + break; + } default: { break; @@ -1294,8 +1327,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom private void SetLayout() { - if(!_props.AutoDefaultLayouts) return; - + if (!_props.AutoDefaultLayouts) return; + if ( (Status.Call.Sharing.State == zEvent.eSharingState.Receiving || Status.Call.Sharing.State == zEvent.eSharingState.Sending)) @@ -1354,7 +1387,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom if (ActiveCalls.Count == 0) { - if (callStatus == zStatus.eCallStatus.CONNECTING_MEETING || callStatus == zStatus.eCallStatus.IN_MEETING ) + if (callStatus == zStatus.eCallStatus.CONNECTING_MEETING || + callStatus == zStatus.eCallStatus.IN_MEETING) { var newStatus = eCodecCallStatus.Unknown; @@ -1431,6 +1465,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom SetLayout(); } } + public override void StartSharing() { SendText("zCommand Call Sharing HDMI Start"); @@ -1633,7 +1668,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public override void SendDtmf(string s) { - throw new NotImplementedException(); + SendDtmfToPhone(s); } /// @@ -1713,6 +1748,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom #endregion #region Implementation of IHasCameraAutoMode + //Zoom doesn't support camera auto modes. Setting this to just unmute video public void CameraAutoModeOn() { @@ -1757,7 +1793,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { var nextPipPositionIndex = SelfviewPipPositions.IndexOf(_currentSelfviewPipPosition) + 1; - if (nextPipPositionIndex >= SelfviewPipPositions.Count) // Check if we need to loop back to the first item in the list + if (nextPipPositionIndex >= SelfviewPipPositions.Count) + // Check if we need to loop back to the first item in the list nextPipPositionIndex = 0; SelfviewPipPositionSet(SelfviewPipPositions[nextPipPositionIndex]); @@ -1772,12 +1809,40 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom new CodecCommandWithLabel("DownLeft", "Lower Left") }; - void ComputeSelfviewPipStatus() + private void ComputeSelfviewPipStatus() { _currentSelfviewPipPosition = SelfviewPipPositions.FirstOrDefault( p => p.Command.ToLower().Equals(Configuration.Call.Layout.Position.ToString().ToLower())); } + + #endregion + + #region Implementation of IHasPhoneDialing + + private Func PhoneOffHookFeedbackFunc {get {return () => Status.PhoneCall.OffHook; }} + private Func CallerIdNameFeedbackFunc { get { return () => Status.PhoneCall.PeerDisplayName; } } + private Func CallerIdNumberFeedbackFunc { get { return () => Status.PhoneCall.PeerNumber; } } + + public BoolFeedback PhoneOffHookFeedback { get; private set; } + public StringFeedback CallerIdNameFeedback { get; private set; } + public StringFeedback CallerIdNumberFeedback { get; private set; } + + public void DialPhoneCall(string number) + { + SendText(String.Format("zCommand Dial PhoneCallOut Number: {0}", number)); + } + + public void EndPhoneCall() + { + SendText(String.Format("zCommand Dial PhoneHangUp CallId: {0}", Status.PhoneCall.CallId)); + } + + public void SendDtmfToPhone(string digit) + { + SendText(String.Format("zCommand SendSipDTMF CallId: {0} Key: {1}", Status.PhoneCall.CallId, digit)); + } + #endregion }