From 0ff29695e73f388c685879ffa8b2407eb5dc9b3a Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Thu, 11 Nov 2021 21:05:57 -0700 Subject: [PATCH] feat(essentials): #865 Updates join map and bridge for new features Adds control and feedback for presentation source Updates camera setup, selection and feedback Adds ringtone volume control/feedback Adds call hold/resume/join control to bridge Adds new config properties for camera info --- .../JoinMaps/VideoCodecControllerJoinMap.cs | 115 +++++++- .../VideoCodec/CiscoCodec/CiscoCamera.cs | 2 +- .../CiscoCodec/CiscoCodecJoinMap.cs | 56 ++++ .../VideoCodec/CiscoCodec/CiscoSparkCodec.cs | 270 ++++++++++++------ .../CiscoSparkCodecPropertiesConfig.cs | 20 +- .../VideoCodec/CiscoCodec/xStatus.cs | 37 ++- .../VideoCodec/VideoCodecBase.cs | 108 ++++++- 7 files changed, 490 insertions(+), 118 deletions(-) diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs index d8610047..a0729352 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs @@ -342,6 +342,48 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); + [JoinName("EndCallStart")] + public JoinDataComplete EndCallStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 81, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "End a specific call by call index. ", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("JoinAllCalls")] + public JoinDataComplete JoinAllCalls = new JoinDataComplete( + new JoinData + { + JoinNumber = 90, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "End a specific call by call index. ", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("JoinCallStart")] + public JoinDataComplete JoinCallStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 91, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "End a specific call by call index. ", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + [JoinName("DirectorySearchBusy")] public JoinDataComplete DirectorySearchBusy = new JoinDataComplete( new JoinData @@ -931,6 +973,34 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); + [JoinName("HoldCallsStart")] + public JoinDataComplete HoldCallsStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 221, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "Holds Call at specified index", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("ResumeCallsStart")] + public JoinDataComplete ResumeCallsStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 231, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "Resume Call at specified index", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + [JoinName("ParticipantAudioMuteToggleStart")] public JoinDataComplete ParticipantAudioMuteToggleStart = new JoinDataComplete( new JoinData @@ -989,24 +1059,11 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "Sets the selected Call. Valid values 1-8", + Description = "Sets the selected Call for DTMF commands. Valid values 1-8", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); - [JoinName("EndCall")] - public JoinDataComplete EndCall = new JoinDataComplete( - new JoinData - { - JoinNumber = 24, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "End a specific call by call index. Valid values 1-8", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Analog - }); [JoinName("ConnectedCallCount")] public JoinDataComplete ConnectedCallCount = new JoinDataComplete( @@ -1045,11 +1102,25 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "Camera Number Select/FB", + Description = "Camera Number Select/FB. 1 based index. Valid range is 1 to the value reported by CameraCount.", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + [JoinName("CameraCount")] + public JoinDataComplete CameraCount = new JoinDataComplete( + new JoinData + { + JoinNumber = 61, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Reports the number of cameras", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); + [JoinName("DirectoryRowCount")] public JoinDataComplete DirectoryRowCount = new JoinDataComplete( new JoinData @@ -1323,6 +1394,20 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Serial }); + [JoinName("CameraNamesFb")] + public JoinDataComplete CameraNamesFb = new JoinDataComplete( + new JoinData + { + JoinNumber = 161, + JoinSpan = 10 + }, + new JoinMetadata + { + Description = "Camera Name Fb", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + [JoinName("CurrentSource")] public JoinDataComplete CurrentSource = new JoinDataComplete( new JoinData diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCamera.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCamera.cs index 6f68b369..67312df8 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCamera.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCamera.cs @@ -116,7 +116,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// /// The ID of the camera on the codec /// - protected uint CameraId { get; private set; } + public uint CameraId { get; private set; } /// /// Valid range 1-15 diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCodecJoinMap.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCodecJoinMap.cs index e4945ce8..44256ca4 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCodecJoinMap.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCodecJoinMap.cs @@ -9,6 +9,34 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { #region Digital + [JoinName("PresentationLocalOnly")] + public JoinDataComplete PresentationLocalOnly = new JoinDataComplete( + new JoinData + { + JoinNumber = 205, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Presentation Local Only Feedback", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("PresentationLocalRemote")] + public JoinDataComplete PresentationLocalRemote = new JoinDataComplete( + new JoinData + { + JoinNumber = 206, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Presentation Local Only Feedback", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + [JoinName("ActivateDoNotDisturbMode")] public JoinDataComplete ActivateDoNotDisturbMode = new JoinDataComplete( new JoinData @@ -112,6 +140,34 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco #region Analog + [JoinName("RingtoneVolume")] + public JoinDataComplete RingtoneVolume = new JoinDataComplete( + new JoinData + { + JoinNumber = 21, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Ringtone volume set/FB. Valid values are 0 - 100 in increments of 5 (5, 10, 15, 20, etc.)", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("PresentationSource")] + public JoinDataComplete PresentationSource = new JoinDataComplete( + new JoinData + { + JoinNumber = 201, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Presentation set/FB. Valid values are 0 - 6 depending on the codec model.", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + #endregion diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs index 7b44b47a..4c842f22 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs @@ -42,11 +42,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public StatusMonitorBase CommunicationMonitor { get; private set; } - private GenericQueue ReceiveQueue; + private GenericQueue _receiveQueue; public BoolFeedback PresentationViewMaximizedFeedback { get; private set; } - string CurrentPresentationView; + private string _currentPresentationView; public BoolFeedback RoomIsOccupiedFeedback { get; private set; } @@ -66,9 +66,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public IntFeedback RingtoneVolumeFeedback { get; private set; } - private CodecCommandWithLabel CurrentSelfviewPipPosition; + private CodecCommandWithLabel _currentSelfviewPipPosition; - private CodecCommandWithLabel CurrentLocalLayout; + private CodecCommandWithLabel _currentLocalLayout; /// /// List the available positions for the selfview PIP window @@ -167,7 +167,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { get { - return () => PresentationSourceKey; + return () => _presentationSourceKey; } } @@ -231,7 +231,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { get { - return () => CurrentSelfviewPipPosition.Label; + return () => _currentSelfviewPipPosition.Label; } } @@ -239,7 +239,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { get { - return () => CurrentLocalLayout.Label; + return () => _currentLocalLayout.Label; } } @@ -247,43 +247,58 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { get { - return () => CurrentLocalLayout.Label == "Prominent"; + return () => _currentLocalLayout.Label == "Prominent"; } } - private string CliFeedbackRegistrationExpression; + private string _cliFeedbackRegistrationExpression; - private CodecSyncState SyncState; + private CodecSyncState _syncState; public CodecPhonebookSyncState PhonebookSyncState { get; private set; } - private StringBuilder JsonMessage; + private StringBuilder _jsonMessage; - private bool JsonFeedbackMessageIsIncoming; + private bool _jsonFeedbackMessageIsIncoming; public bool CommDebuggingIsOn; string Delimiter = "\r\n"; + public IntFeedback PresentationSourceFeedback { get; private set; } + + public BoolFeedback PresentationSendingLocalOnlyFeedback { get; private set; } + + public BoolFeedback PresentationSendingLocalRemoteFeedback { get; private set; } + /// /// Used to track the current connector used for the presentation source /// - int PresentationSource; + private int _presentationSource; - string PresentationSourceKey; + /// + /// Used to track the connector that is desired to be the current presentation source (until the command is send) + /// + private int _desiredPresentationSource; - string PhonebookMode = "Local"; // Default to Local + private string _presentationSourceKey; - uint PhonebookResultsLimit = 255; // Could be set later by config. + private bool _presentationLocalOnly; - CTimer LoginMessageReceivedTimer; - CTimer RetryConnectionTimer; + private bool _presentationLocalRemote; + + private string _phonebookMode = "Local"; // Default to Local + + private uint _phonebookResultsLimit = 255; // Could be set later by config. + + private CTimer _loginMessageReceivedTimer; + private CTimer _retryConnectionTimer; // **___________________________________________________________________** // Timers to be moved to the global system timer at a later point.... - CTimer BookingsRefreshTimer; - CTimer PhonebookRefreshTimer; + private CTimer BookingsRefreshTimer; + private CTimer PhonebookRefreshTimer; // **___________________________________________________________________** public RoutingInputPort CodecOsdIn { get; private set; } @@ -302,11 +317,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco // Use the configured phonebook results limit if present if (props.PhonebookResultsLimit > 0) { - PhonebookResultsLimit = props.PhonebookResultsLimit; + _phonebookResultsLimit = props.PhonebookResultsLimit; } // The queue that will collect the repsonses in the order they are received - ReceiveQueue = new GenericQueue(this.Key + "-rxQueue", 25); + _receiveQueue = new GenericQueue(this.Key + "-rxQueue", 25); RoomIsOccupiedFeedback = new BoolFeedback(RoomIsOccupiedFeedbackFunc); PeopleCountFeedback = new IntFeedback(PeopleCountFeedbackFunc); @@ -324,10 +339,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco HalfWakeModeIsOnFeedback = new BoolFeedback(() => CodecStatus.Status.Standby.State.Value.ToLower() == "halfwake"); EnteringStandbyModeFeedback = new BoolFeedback(() => CodecStatus.Status.Standby.State.Value.ToLower() == "enteringstandby"); - PresentationViewMaximizedFeedback = new BoolFeedback(() => CurrentPresentationView == "Maximized"); + PresentationViewMaximizedFeedback = new BoolFeedback(() => _currentPresentationView == "Maximized"); RingtoneVolumeFeedback = new IntFeedback(() => CodecConfiguration.Configuration.Audio.SoundsAndAlerts.RingVolume.Volume); + PresentationSourceFeedback = new IntFeedback(() => _presentationSource); + PresentationSendingLocalOnlyFeedback = new BoolFeedback(() => _presentationLocalOnly); + PresentationSendingLocalRemoteFeedback = new BoolFeedback(() => _presentationLocalRemote); + Communication = comm; if (props.CommunicationMonitorProperties != null) @@ -346,13 +365,13 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco DeviceManager.AddDevice(CommunicationMonitor); - PhonebookMode = props.PhonebookMode; + _phonebookMode = props.PhonebookMode; - SyncState = new CodecSyncState(Key + "--Sync"); + _syncState = new CodecSyncState(Key + "--Sync"); PhonebookSyncState = new CodecPhonebookSyncState(Key + "--PhonebookSync"); - SyncState.InitialSyncCompleted += new EventHandler(SyncState_InitialSyncCompleted); + _syncState.InitialSyncCompleted += new EventHandler(SyncState_InitialSyncCompleted); PortGather = new CommunicationGather(Communication, Delimiter); PortGather.IncludeDelimiter = true; @@ -399,7 +418,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco InputPorts.Add(HdmiIn3); OutputPorts.Add(HdmiOut1); - SetUpCameras(); + SetUpCameras(props.CameraInfo); CreateOsdSource(); @@ -589,7 +608,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco const string prefix = "xFeedback register "; - CliFeedbackRegistrationExpression = + _cliFeedbackRegistrationExpression = prefix + "/Configuration" + Delimiter + prefix + "/Status/Audio" + Delimiter + prefix + "/Status/Call" + Delimiter + @@ -651,12 +670,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco Debug.Console(1, this, "Socket status change {0}", e.Client.ClientStatus); if (e.Client.IsConnected) { - if(!SyncState.LoginMessageWasReceived) - LoginMessageReceivedTimer = new CTimer(o => DisconnectClientAndReconnect(), 5000); + if(!_syncState.LoginMessageWasReceived) + _loginMessageReceivedTimer = new CTimer(o => DisconnectClientAndReconnect(), 5000); } else { - SyncState.CodecDisconnected(); + _syncState.CodecDisconnected(); PhonebookSyncState.CodecDisconnected(); if (PhonebookRefreshTimer != null) @@ -679,7 +698,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco Communication.Disconnect(); - RetryConnectionTimer = new CTimer(o => Communication.Connect(), 2000); + _retryConnectionTimer = new CTimer(o => Communication.Connect(), 2000); //CrestronEnvironment.Sleep(2000); @@ -696,66 +715,66 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { if (CommDebuggingIsOn) { - if (!JsonFeedbackMessageIsIncoming) + if (!_jsonFeedbackMessageIsIncoming) Debug.Console(1, this, "RX: '{0}'", args.Text); } if (args.Text == "{" + Delimiter) // Check for the beginning of a new JSON message { - JsonFeedbackMessageIsIncoming = true; + _jsonFeedbackMessageIsIncoming = true; if (CommDebuggingIsOn) Debug.Console(1, this, "Incoming JSON message..."); - JsonMessage = new StringBuilder(); + _jsonMessage = new StringBuilder(); } else if (args.Text == "}" + Delimiter) // Check for the end of a JSON message { - JsonFeedbackMessageIsIncoming = false; + _jsonFeedbackMessageIsIncoming = false; - JsonMessage.Append(args.Text); + _jsonMessage.Append(args.Text); if (CommDebuggingIsOn) - Debug.Console(1, this, "Complete JSON Received:\n{0}", JsonMessage.ToString()); + Debug.Console(1, this, "Complete JSON Received:\n{0}", _jsonMessage.ToString()); // Enqueue the complete message to be deserialized - ReceiveQueue.Enqueue(new ProcessStringMessage(JsonMessage.ToString(), DeserializeResponse)); + _receiveQueue.Enqueue(new ProcessStringMessage(_jsonMessage.ToString(), DeserializeResponse)); return; } - if(JsonFeedbackMessageIsIncoming) + if(_jsonFeedbackMessageIsIncoming) { - JsonMessage.Append(args.Text); + _jsonMessage.Append(args.Text); //Debug.Console(1, this, "Building JSON:\n{0}", JsonMessage.ToString()); return; } - if (!SyncState.InitialSyncComplete) + if (!_syncState.InitialSyncComplete) { switch (args.Text.Trim().ToLower()) // remove the whitespace { case "*r login successful": { - SyncState.LoginMessageReceived(); + _syncState.LoginMessageReceived(); - if(LoginMessageReceivedTimer != null) - LoginMessageReceivedTimer.Stop(); + if(_loginMessageReceivedTimer != null) + _loginMessageReceivedTimer.Stop(); SendText("xPreferences outputmode json"); break; } case "xpreferences outputmode json": { - if (!SyncState.InitialStatusMessageWasReceived) + if (!_syncState.InitialStatusMessageWasReceived) SendText("xStatus"); break; } case "xfeedback register /event/calldisconnect": { - SyncState.FeedbackRegistered(); + _syncState.FeedbackRegistered(); break; } } @@ -801,11 +820,18 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco if (conference.Presentation.LocalInstance.Count > 0) { if (!string.IsNullOrEmpty(conference.Presentation.LocalInstance[0].ghost)) - PresentationSource = 0; + _presentationSource = 0; else if (conference.Presentation.LocalInstance[0].Source != null) { - PresentationSource = conference.Presentation.LocalInstance[0].Source.IntValue; + _presentationSource = conference.Presentation.LocalInstance[0].Source.IntValue; } + + _presentationLocalOnly = conference.Presentation.LocalInstance.Any((i) => i.SendingMode.LocalOnly); + _presentationLocalRemote = conference.Presentation.LocalInstance.Any((i) => i.SendingMode.LocalRemote); + + PresentationSourceFeedback.FireUpdate(); + PresentationSendingLocalOnlyFeedback.FireUpdate(); + PresentationSendingLocalRemoteFeedback.FireUpdate(); } // Check to see if this is a call status message received after the initial status message @@ -966,11 +992,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco JsonConvert.PopulateObject(response, CodecStatus); } - if (!SyncState.InitialStatusMessageWasReceived) + if (!_syncState.InitialStatusMessageWasReceived) { - SyncState.InitialStatusMessageReceived(); + _syncState.InitialStatusMessageReceived(); - if (!SyncState.InitialConfigurationMessageWasReceived) + if (!_syncState.InitialConfigurationMessageWasReceived) SendText("xConfiguration"); } } @@ -980,12 +1006,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco JsonConvert.PopulateObject(response, CodecConfiguration); - if (!SyncState.InitialConfigurationMessageWasReceived) + if (!_syncState.InitialConfigurationMessageWasReceived) { - SyncState.InitialConfigurationMessageReceived(); - if (!SyncState.FeedbackWasRegistered) + _syncState.InitialConfigurationMessageReceived(); + if (!_syncState.FeedbackWasRegistered) { - SendText(CliFeedbackRegistrationExpression); + SendText(_cliFeedbackRegistrationExpression); } } @@ -1158,7 +1184,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public override void ExecuteSwitch(object selector) { (selector as Action)(); - PresentationSourceKey = selector.ToString(); + _presentationSourceKey = selector.ToString(); } /// @@ -1168,7 +1194,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) { ExecuteSwitch(inputSelector); - PresentationSourceKey = inputSelector.ToString(); + _presentationSourceKey = inputSelector.ToString(); } @@ -1247,13 +1273,13 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco private void GetPhonebookFolders() { // Get Phonebook Folders (determine local/corporate from config, and set results limit) - SendText(string.Format("xCommand Phonebook Search PhonebookType: {0} ContactType: Folder", PhonebookMode)); + SendText(string.Format("xCommand Phonebook Search PhonebookType: {0} ContactType: Folder", _phonebookMode)); } private void GetPhonebookContacts() { // Get Phonebook Folders (determine local/corporate from config, and set results limit) - SendText(string.Format("xCommand Phonebook Search PhonebookType: {0} ContactType: Contact Limit: {1}", PhonebookMode, PhonebookResultsLimit)); + SendText(string.Format("xCommand Phonebook Search PhonebookType: {0} ContactType: Contact Limit: {1}", _phonebookMode, _phonebookResultsLimit)); } /// @@ -1262,7 +1288,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// public void SearchDirectory(string searchString) { - SendText(string.Format("xCommand Phonebook Search SearchString: \"{0}\" PhonebookType: {1} ContactType: Contact Limit: {2}", searchString, PhonebookMode, PhonebookResultsLimit)); + SendText(string.Format("xCommand Phonebook Search SearchString: \"{0}\" PhonebookType: {1} ContactType: Contact Limit: {2}", searchString, _phonebookMode, _phonebookResultsLimit)); } /// @@ -1271,7 +1297,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// public void GetDirectoryFolderContents(string folderId) { - SendText(string.Format("xCommand Phonebook Search FolderId: {0} PhonebookType: {1} ContactType: Any Limit: {2}", folderId, PhonebookMode, PhonebookResultsLimit)); + SendText(string.Format("xCommand Phonebook Search FolderId: {0} PhonebookType: {1} ContactType: Any Limit: {2}", folderId, _phonebookMode, _phonebookResultsLimit)); } /// @@ -1449,14 +1475,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// /// /// - public void SendDtmf(string s, CodecActiveCallItem activeCall) + public override void SendDtmf(string s, CodecActiveCallItem activeCall) { SendText(string.Format("xCommand Call DTMFSend CallId: {0} DTMFString: \"{1}\"", activeCall.Id, s)); } public void SelectPresentationSource(int source) { - PresentationSource = source; + _desiredPresentationSource = source; StartSharing(); } @@ -1467,6 +1493,18 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// level from 0 - 100 in increments of 5 public void SetRingtoneVolume(int volume) { + if (volume < 0 || volume > 100) + { + Debug.Console(0, this, "Cannot set ringtone volume to '{0}'. Value must be between 0 - 100", volume); + return; + } + + if (volume % 5 != 0) + { + Debug.Console(0, this, "Cannot set ringtone volume to '{0}'. Value must be between 0 - 100 and a multiple of 5", volume); + return; + } + SendText(string.Format("xConfiguration Audio SoundsAndAlerts RingVolume: [0]", volume)); } @@ -1500,8 +1538,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco else sendingMode = "LocalOnly"; - if(PresentationSource > 0) - SendText(string.Format("xCommand Presentation Start PresentationSource: {0} SendingMode: {1}", PresentationSource, sendingMode)); + if (_desiredPresentationSource > 0) + SendText(string.Format("xCommand Presentation Start PresentationSource: {0} SendingMode: {1}", _desiredPresentationSource, sendingMode)); } /// @@ -1509,7 +1547,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// public override void StopSharing() { - PresentationSource = 0; + _desiredPresentationSource = 0; SendText("xCommand Presentation Stop"); } @@ -1645,7 +1683,16 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco trilist.SetSigFalseAction(joinMap.ActivateHalfWakeMode.JoinNumber, () => halfwakeCodec.HalfwakeActivate()); } - // TODO: Add mechanism to select a call instance to be able to direct DTMF tones to... + // Ringtone volume + trilist.SetUShortSigAction(joinMap.RingtoneVolume.JoinNumber, (u) => SetRingtoneVolume(u)); + RingtoneVolumeFeedback.LinkInputSig(trilist.UShortInput[joinMap.RingtoneVolume.JoinNumber]); + + // Presentation Source + trilist.SetUShortSigAction(joinMap.PresentationSource.JoinNumber, (u) => SelectPresentationSource(u)); + PresentationSourceFeedback.LinkInputSig(trilist.UShortInput[joinMap.PresentationSource.JoinNumber]); + + PresentationSendingLocalOnlyFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PresentationLocalOnly.JoinNumber]); + PresentationSendingLocalRemoteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PresentationLocalRemote.JoinNumber]); } /// @@ -1719,9 +1766,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// public void SelfviewPipPositionToggle() { - if (CurrentSelfviewPipPosition != null) + if (_currentSelfviewPipPosition != null) { - var nextPipPositionIndex = SelfviewPipPositions.IndexOf(CurrentSelfviewPipPosition) + 1; + var nextPipPositionIndex = SelfviewPipPositions.IndexOf(_currentSelfviewPipPosition) + 1; if (nextPipPositionIndex >= SelfviewPipPositions.Count) // Check if we need to loop back to the first item in the list nextPipPositionIndex = 0; @@ -1744,9 +1791,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// public void LocalLayoutToggle() { - if(CurrentLocalLayout != null) + if(_currentLocalLayout != null) { - var nextLocalLayoutIndex = LocalLayouts.IndexOf(CurrentLocalLayout) + 1; + var nextLocalLayoutIndex = LocalLayouts.IndexOf(_currentLocalLayout) + 1; if (nextLocalLayoutIndex >= LocalLayouts.Count) // Check if we need to loop back to the first item in the list nextLocalLayoutIndex = 0; @@ -1760,9 +1807,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// public void LocalLayoutToggleSingleProminent() { - if (CurrentLocalLayout != null) + if (_currentLocalLayout != null) { - if (CurrentLocalLayout.Label != "Prominent") + if (_currentLocalLayout.Label != "Prominent") LocalLayoutSet(LocalLayouts.FirstOrDefault(l => l.Label.Equals("Prominent"))); else LocalLayoutSet(LocalLayouts.FirstOrDefault(l => l.Label.Equals("Single"))); @@ -1776,11 +1823,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public void MinMaxLayoutToggle() { if (PresentationViewMaximizedFeedback.BoolValue) - CurrentPresentationView = "Minimized"; + _currentPresentationView = "Minimized"; else - CurrentPresentationView = "Maximized"; + _currentPresentationView = "Maximized"; - SendText(string.Format("xCommand Video PresentationView Set View: {0}", CurrentPresentationView)); + SendText(string.Format("xCommand Video PresentationView Set View: {0}", _currentPresentationView)); PresentationViewMaximizedFeedback.FireUpdate(); } @@ -1789,9 +1836,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// void ComputeSelfviewPipStatus() { - CurrentSelfviewPipPosition = SelfviewPipPositions.FirstOrDefault(p => p.Command.ToLower().Equals(CodecStatus.Status.Video.Selfview.PIPPosition.Value.ToLower())); + _currentSelfviewPipPosition = SelfviewPipPositions.FirstOrDefault(p => p.Command.ToLower().Equals(CodecStatus.Status.Video.Selfview.PIPPosition.Value.ToLower())); - if(CurrentSelfviewPipPosition != null) + if(_currentSelfviewPipPosition != null) SelfviewIsOnFeedback.FireUpdate(); } @@ -1800,9 +1847,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// void ComputeLocalLayout() { - CurrentLocalLayout = LocalLayouts.FirstOrDefault(l => l.Command.ToLower().Equals(CodecStatus.Status.Video.Layout.LayoutFamily.Local.Value.ToLower())); + _currentLocalLayout = LocalLayouts.FirstOrDefault(l => l.Command.ToLower().Equals(CodecStatus.Status.Video.Layout.LayoutFamily.Local.Value.ToLower())); - if (CurrentLocalLayout != null) + if (_currentLocalLayout != null) LocalLayoutFeedback.FireUpdate(); } @@ -1850,19 +1897,59 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// /// Builds the cameras List. Could later be modified to build from config data /// - void SetUpCameras() + void SetUpCameras(List cameraInfo) { // Add the internal camera Cameras = new List(); - var internalCamera = new CiscoSparkCamera(Key + "-camera1", "Near End", this, 1); + var camCount = CodecStatus.Status.Cameras.Camera.Count; - if(CodecStatus.Status.Cameras.Camera.Count > 0) - internalCamera.SetCapabilites(CodecStatus.Status.Cameras.Camera[0].Capabilities.Options.Value); + Debug.Console(0, this, "Codec reports {0} cameras", camCount); + + + // Deal with the case of 1 or no reported cameras + if (camCount <= 1) + { + var internalCamera = new CiscoSparkCamera(Key + "-camera1", "Near End", this, 1); + + if (CodecStatus.Status.Cameras.Camera[0] != null && CodecStatus.Status.Cameras.Camera[0].Capabilities != null) + { + internalCamera.SetCapabilites(CodecStatus.Status.Cameras.Camera[0].Capabilities.Options.Value); + } + + Cameras.Add(internalCamera); + DeviceManager.AddDevice(internalCamera); + } else - // Somehow subscribe to the event on the Options.Value property and update when it changes. + { + // Setup all the cameras + for (int i = 0; i < camCount; i++) + { + var cam = CodecStatus.Status.Cameras.Camera[i]; - Cameras.Add(internalCamera); + var id = (uint)i; + var name = string.Format("Camera {0}", id); + + // Check for a config object that matches the camera number + var camInfo = cameraInfo.FirstOrDefault(c => c.CameraNumber == i + 1); + if (camInfo != null) + { + id = (uint)camInfo.SourceId; + name = camInfo.Name; + } + + var key = string.Format("{0}-camera{1}", Key, id); + var camera = new CiscoSparkCamera(key, name, this, id); + + if (cam.Capabilities != null) + { + camera.SetCapabilites(cam.Capabilities.Options.Value); + } + + Cameras.Add(camera); + DeviceManager.AddDevice(camera); + } + } // Add the far end camera var farEndCamera = new CiscoFarEndCamera(Key + "-cameraFar", "Far End", this); @@ -1872,7 +1959,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco ControllingFarEndCameraFeedback = new BoolFeedback(() => SelectedCamera is IAmFarEndCamera); - DeviceManager.AddDevice(internalCamera); DeviceManager.AddDevice(farEndCamera); NearEndPresets = new List(15); @@ -1886,7 +1972,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco FarEndRoomPresets.Add(new CodecRoomPreset(i, label, true, false)); } - SelectedCamera = internalCamera; ; // call the method to select the camera and ensure the feedbacks get updated. + SelectedCamera = Cameras[0]; ; // call the method to select the camera and ensure the feedbacks get updated. } #region IHasCodecCameras Members @@ -1934,6 +2020,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco } else Debug.Console(2, this, "Unable to select camera with key: '{0}'", key); + + var ciscoCam = camera as CiscoSparkCamera; + if (ciscoCam != null) + { + SendText(string.Format("xCommand Video Input SetMainVideoSource SourceId: {0}", ciscoCam.CameraId)); + } } public CameraBase FarEndCamera { get; private set; } diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodecPropertiesConfig.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodecPropertiesConfig.cs index 1836bafb..7f36b0bf 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodecPropertiesConfig.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodecPropertiesConfig.cs @@ -50,8 +50,16 @@ namespace PepperDash.Essentials.Devices.Common.Codec public uint PhonebookResultsLimit { get; set; } [JsonProperty("UiBranding")] - public BrandingLogoProperties UiBranding { get; set; } + public BrandingLogoProperties UiBranding { get; set; } + [JsonProperty("cameraInfo")] + public List CameraInfo { get; set; } + + + public CiscoSparkCodecPropertiesConfig() + { + CameraInfo = new List(); + } } public class SharingProperties @@ -68,4 +76,14 @@ namespace PepperDash.Essentials.Devices.Common.Codec [JsonProperty("brandingUrl")] public string BrandingUrl { get; set; } } + + /// + /// Describes configuration information for the near end cameras + /// + public class CameraInfo + { + public int CameraNumber { get; set; } + public string Name { get; set; } + public int SourceId { get; set; } + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/xStatus.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/xStatus.cs index ca98c1fc..6db3c2e3 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/xStatus.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/xStatus.cs @@ -558,9 +558,41 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco } } - public class SendingMode + public class SendingMode : ValueProperty { - public string Value { get; set; } + string _Value; + + /// + /// Sets Value and triggers the action when set + /// + public string Value + { + get + { + return _Value; + } + set + { + _Value = value; + OnValueChanged(); + } + } + + public bool LocalOnly + { + get + { + return _Value.ToLower() == "localonly"; + } + } + + public bool LocalRemote + { + get + { + return _Value.ToLower() == "localremote"; + } + } } public class LocalInstance @@ -573,6 +605,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public LocalInstance() { Source = new Source2(); + SendingMode = new SendingMode(); } } 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 5771d868..b87e47f6 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 @@ -982,18 +982,22 @@ ScreenIndexIsPinnedTo: {8} (a{17}) //End All calls trilist.SetSigFalseAction(joinMap.EndAllCalls.JoinNumber, EndAllCalls); - //End a specific call, specified by index - trilist.SetUShortSigAction(joinMap.EndCall.JoinNumber, (i) => - { - if (i > 0 && i <= 8) + //End a specific call, specified by index. Maximum 8 calls supported + for (int i = 0; i < joinMap.EndCallStart.JoinSpan; i++) + { + trilist.SetSigFalseAction((uint)(joinMap.EndCallStart.JoinNumber + i), () => { - var call = ActiveCalls[i - 1]; + var call = ActiveCalls[i]; if (call != null) { EndCall(call); } - } - }); + else + { + Debug.Console(0, this, "[End Call] Unable to find call at index '{0}'", i); + } + }); + } trilist.SetBool(joinMap.HookState.JoinNumber, IsInCall); @@ -1015,6 +1019,61 @@ ScreenIndexIsPinnedTo: {8} (a{17}) trilist.SetUshort(joinMap.ConnectedCallCount.JoinNumber, (ushort)ActiveCalls.Count); }; + + var joinCodec = this as IJoinCalls; + if (joinCodec != null) + { + trilist.SetSigFalseAction(joinMap.JoinAllCalls.JoinNumber, () => joinCodec.JoinAllCalls()); + + for (int i = 0; i < joinMap.JoinCallStart.JoinSpan; i++) + { + trilist.SetSigFalseAction((uint)(joinMap.JoinCallStart.JoinNumber + i), () => + { + var call = ActiveCalls[i]; + if (call != null) + { + joinCodec.JoinCall(call); + } + else + { + Debug.Console(0, this, "[Join Call] Unable to find call at index '{0}'", i); + } + }); + } + } + + var holdCodec = this as IHasCallHold; + if (holdCodec != null) + { + for (int i = 0; i < joinMap.JoinCallStart.JoinSpan; i++) + { + trilist.SetSigFalseAction((uint)(joinMap.HoldCallsStart.JoinNumber + i), () => + { + var call = ActiveCalls[i]; + if (call != null) + { + holdCodec.HoldCall(call); + } + else + { + Debug.Console(0, this, "[Hold Call] Unable to find call at index '{0}'", i); + } + }); + + trilist.SetSigFalseAction((uint)(joinMap.ResumeCallsStart.JoinNumber + i), () => + { + var call = ActiveCalls[i]; + if (call != null) + { + holdCodec.ResumeCall(call); + } + else + { + Debug.Console(0, this, "[Resume Call] Unable to find call at index '{0}'", i); + } + }); + } + } } private string UpdateCallStatusXSig() @@ -1296,18 +1355,47 @@ ScreenIndexIsPinnedTo: {8} (a{17}) camera.TriggerAutoFocus(); }); + // Camera count + trilist.SetUshort(joinMap.CameraCount.JoinNumber, (ushort)codec.Cameras.Count); + + // Camera names + for (uint i = 0; i < joinMap.CameraNamesFb.JoinSpan; i++) + { + if (codec.Cameras[(int)i] != null) + { + trilist.SetString(joinMap.CameraNamesFb.JoinNumber + i, codec.Cameras[(int)i].Name); + } + else + { + trilist.SetString(joinMap.CameraNamesFb.JoinNumber + i, ""); + } + } + //Camera Select trilist.SetUShortSigAction(joinMap.CameraNumberSelect.JoinNumber, (i) => { - if (codec.SelectedCamera == null) return; - - codec.SelectCamera(codec.Cameras[i].Key); + if (i > 0 && i <= codec.Cameras.Count) + { + codec.SelectCamera(codec.Cameras[i - 1].Key); + } + else + { + Debug.Console(0, this, "Unable to select. No camera found at index {0}", i); + } }); + // Set initial selected camera feedback + if (codec.SelectedCamera != null) + { + trilist.SetUshort(joinMap.CameraNumberSelect.JoinNumber, (ushort)codec.Cameras.FindIndex((c) => c.Key == codec.SelectedCamera.Key)); + } + codec.CameraSelected += (sender, args) => { var i = (ushort)codec.Cameras.FindIndex((c) => c.Key == args.SelectedCamera.Key); + trilist.SetUshort(joinMap.CameraNumberSelect.JoinNumber, (ushort)(i + 1)); + if (codec is IHasCodecRoomPresets) { return;