using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using Crestron.SimplSharp; using Crestron.SimplSharpPro.CrestronThread; using Crestron.SimplSharpPro.DeviceSupport; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.DeviceTypeInterfaces; using PepperDash.Essentials.Core.Routing; using PepperDash.Essentials.Devices.Common.Cameras; using PepperDash.Essentials.Devices.Common.Codec; using PepperDash.Essentials.Devices.Common.VideoCodec; using PepperDash.Essentials.Core.Queues; namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { enum eCommandType { SessionStart, SessionEnd, Command, GetStatus, GetConfiguration }; public enum eExternalSourceType {camera, desktop, document_camera, mediaplayer, PC, whiteboard, other} public enum eExternalSourceMode {Ready, NotReady, Hidden, Error} public class CiscoSparkCodec : VideoCodecBase, IHasCallHistory, IHasCallFavorites, IHasDirectory, IHasScheduleAwareness, IOccupancyStatusProvider, IHasCodecLayouts, IHasCodecSelfView, ICommunicationMonitor, IRouting, IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets, IHasExternalSourceSwitching, IHasBranding, IHasCameraOff, IHasCameraMute { private bool _externalSourceChangeRequested; public event EventHandler DirectoryResultReturned; private CTimer _brandingTimer; public CommunicationGather PortGather { get; private set; } public StatusMonitorBase CommunicationMonitor { get; private set; } private GenericQueue ReceiveQueue; public BoolFeedback PresentationViewMaximizedFeedback { get; private set; } string CurrentPresentationView; public BoolFeedback RoomIsOccupiedFeedback { get; private set; } public IntFeedback PeopleCountFeedback { get; private set; } public BoolFeedback CameraAutoModeIsOnFeedback { get; private set; } public BoolFeedback SelfviewIsOnFeedback { get; private set; } public StringFeedback SelfviewPipPositionFeedback { get; private set; } public StringFeedback LocalLayoutFeedback { get; private set; } public BoolFeedback LocalLayoutIsProminentFeedback { get; private set; } public BoolFeedback FarEndIsSharingContentFeedback { get; private set; } private CodecCommandWithLabel CurrentSelfviewPipPosition; private CodecCommandWithLabel CurrentLocalLayout; /// /// List the available positions for the selfview PIP window /// public List SelfviewPipPositions = new List() { new CodecCommandWithLabel("CenterLeft", "Center Left"), new CodecCommandWithLabel("CenterRight", "Center Right"), new CodecCommandWithLabel("LowerLeft", "Lower Left"), new CodecCommandWithLabel("LowerRight", "Lower Right"), new CodecCommandWithLabel("UpperCenter", "Upper Center"), new CodecCommandWithLabel("UpperLeft", "Upper Left"), new CodecCommandWithLabel("UpperRight", "Upper Right"), }; /// /// Lists the available options for local layout /// public List LocalLayouts = new List() { //new CodecCommandWithLabel("auto", "Auto"), //new CiscoCodecLocalLayout("custom", "Custom"), // Left out for now new CodecCommandWithLabel("equal","Equal"), new CodecCommandWithLabel("overlay","Overlay"), new CodecCommandWithLabel("prominent","Prominent"), new CodecCommandWithLabel("single","Single") }; private CiscoCodecConfiguration.RootObject CodecConfiguration = new CiscoCodecConfiguration.RootObject(); private CiscoCodecStatus.RootObject CodecStatus = new CiscoCodecStatus.RootObject(); public CodecCallHistory CallHistory { get; private set; } public CodecCallFavorites CallFavorites { get; private set; } /// /// The root level of the directory /// public CodecDirectory DirectoryRoot { get; private set; } /// /// Represents the current state of the directory and is computed on get /// public CodecDirectory CurrentDirectoryResult { get { if (DirectoryBrowseHistory.Count > 0) return DirectoryBrowseHistory[DirectoryBrowseHistory.Count - 1]; else return DirectoryRoot; } } public BoolFeedback CurrentDirectoryResultIsNotDirectoryRoot { get; private set; } /// /// Tracks the directory browse history when browsing beyond the root directory /// public List DirectoryBrowseHistory { get; private set; } public CodecScheduleAwareness CodecSchedule { get; private set; } /// /// Gets and returns the scaled volume of the codec /// protected override Func VolumeLevelFeedbackFunc { get { return () => CrestronEnvironment.ScaleWithLimits(CodecStatus.Status.Audio.Volume.IntValue, 100, 0, 65535, 0); } } protected override Func PrivacyModeIsOnFeedbackFunc { get { return () => CodecStatus.Status.Audio.Microphones.Mute.BoolValue; } } protected override Func StandbyIsOnFeedbackFunc { get { return () => CodecStatus.Status.Standby.State.BoolValue; } } /// /// Gets the value of the currently shared source, or returns null /// protected override Func SharingSourceFeedbackFunc { get { return () => PresentationSourceKey; } } protected override Func SharingContentIsOnFeedbackFunc { get { return () => CodecStatus.Status.Conference.Presentation.Mode.BoolValue; } } protected Func FarEndIsSharingContentFeedbackFunc { get { return () => CodecStatus.Status.Conference.Presentation.Mode.Value == "Receiving"; } } protected override Func MuteFeedbackFunc { get { return () => CodecStatus.Status.Audio.VolumeMute.BoolValue; } } protected Func RoomIsOccupiedFeedbackFunc { get { return () => CodecStatus.Status.RoomAnalytics.PeoplePresence.BoolValue; } } protected Func PeopleCountFeedbackFunc { get { return () => CodecStatus.Status.RoomAnalytics.PeopleCount.Current.IntValue; } } protected Func SpeakerTrackIsOnFeedbackFunc { get { return () => CodecStatus.Status.Cameras.SpeakerTrack.Status.BoolValue; } } protected Func SelfViewIsOnFeedbackFunc { get { return () => CodecStatus.Status.Video.Selfview.Mode.BoolValue; } } protected Func SelfviewPipPositionFeedbackFunc { get { return () => CurrentSelfviewPipPosition.Label; } } protected Func LocalLayoutFeedbackFunc { get { return () => CurrentLocalLayout.Label; } } protected Func LocalLayoutIsProminentFeedbackFunc { get { return () => CurrentLocalLayout.Label == "Prominent"; } } private string CliFeedbackRegistrationExpression; private CodecSyncState SyncState; public CodecPhonebookSyncState PhonebookSyncState { get; private set; } private StringBuilder JsonMessage; private bool JsonFeedbackMessageIsIncoming; public bool CommDebuggingIsOn; string Delimiter = "\r\n"; /// /// Used to track the current connector used for the presentation source /// int PresentationSource; string PresentationSourceKey; string PhonebookMode = "Local"; // Default to Local uint PhonebookResultsLimit = 255; // Could be set later by config. CTimer LoginMessageReceivedTimer; CTimer RetryConnectionTimer; // **___________________________________________________________________** // Timers to be moved to the global system timer at a later point.... CTimer BookingsRefreshTimer; CTimer PhonebookRefreshTimer; // **___________________________________________________________________** public RoutingInputPort CodecOsdIn { get; private set; } public RoutingInputPort HdmiIn2 { get; private set; } public RoutingInputPort HdmiIn3 { get; private set; } public RoutingOutputPort HdmiOut1 { get; private set; } public RoutingOutputPort HdmiOut2 { get; private set; } // Constructor for IBasicCommunication public CiscoSparkCodec(DeviceConfig config, IBasicCommunication comm) : base(config) { var props = JsonConvert.DeserializeObject(config.Properties.ToString()); // Use the configured phonebook results limit if present if (props.PhonebookResultsLimit > 0) { PhonebookResultsLimit = props.PhonebookResultsLimit; } // The queue that will collect the repsonses in the order they are received ReceiveQueue = new GenericQueue(this.Key + "-rxQueue", 25); RoomIsOccupiedFeedback = new BoolFeedback(RoomIsOccupiedFeedbackFunc); PeopleCountFeedback = new IntFeedback(PeopleCountFeedbackFunc); CameraAutoModeIsOnFeedback = new BoolFeedback(SpeakerTrackIsOnFeedbackFunc); SelfviewIsOnFeedback = new BoolFeedback(SelfViewIsOnFeedbackFunc); SelfviewPipPositionFeedback = new StringFeedback(SelfviewPipPositionFeedbackFunc); LocalLayoutFeedback = new StringFeedback(LocalLayoutFeedbackFunc); LocalLayoutIsProminentFeedback = new BoolFeedback(LocalLayoutIsProminentFeedbackFunc); FarEndIsSharingContentFeedback = new BoolFeedback(FarEndIsSharingContentFeedbackFunc); CameraIsOffFeedback = new BoolFeedback(() => CodecStatus.Status.Video.Input.MainVideoMute.BoolValue); CameraIsMutedFeedback = CameraIsOffFeedback; PresentationViewMaximizedFeedback = new BoolFeedback(() => CurrentPresentationView == "Maximized"); Communication = comm; if (props.CommunicationMonitorProperties != null) { CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, props.CommunicationMonitorProperties); } else { CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 30000, 120000, 300000, "xStatus SystemUnit Software Version\r"); } if (props.Sharing != null) AutoShareContentWhileInCall = props.Sharing.AutoShareContentWhileInCall; ShowSelfViewByDefault = props.ShowSelfViewByDefault; DeviceManager.AddDevice(CommunicationMonitor); PhonebookMode = props.PhonebookMode; SyncState = new CodecSyncState(Key + "--Sync"); PhonebookSyncState = new CodecPhonebookSyncState(Key + "--PhonebookSync"); SyncState.InitialSyncCompleted += new EventHandler(SyncState_InitialSyncCompleted); PortGather = new CommunicationGather(Communication, Delimiter); PortGather.IncludeDelimiter = true; PortGather.LineReceived += this.Port_LineReceived; CodecInfo = new CiscoCodecInfo(CodecStatus, CodecConfiguration); CallHistory = new CodecCallHistory(); if (props.Favorites != null) { CallFavorites = new CodecCallFavorites(); CallFavorites.Favorites = props.Favorites; } DirectoryRoot = new CodecDirectory(); DirectoryBrowseHistory = new List(); CurrentDirectoryResultIsNotDirectoryRoot = new BoolFeedback(() => DirectoryBrowseHistory.Count > 0); CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); CodecSchedule = new CodecScheduleAwareness(); //Set Feedback Actions SetFeedbackActions(); CodecOsdIn = new RoutingInputPort(RoutingPortNames.CodecOsd, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, new Action(StopSharing), this); HdmiIn2 = new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, new Action(SelectPresentationSource1), this); HdmiIn3 = new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, new Action(SelectPresentationSource2), this); HdmiOut1 = new RoutingOutputPort(RoutingPortNames.HdmiOut1, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, null, this); HdmiOut2 = new RoutingOutputPort(RoutingPortNames.HdmiOut2, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, null, this); InputPorts.Add(CodecOsdIn); InputPorts.Add(HdmiIn2); InputPorts.Add(HdmiIn3); OutputPorts.Add(HdmiOut1); SetUpCameras(); CreateOsdSource(); ExternalSourceListEnabled = props.ExternalSourceListEnabled; ExternalSourceInputPort = props.ExternalSourceInputPort; if (props.UiBranding == null) { return; } Debug.Console(2, this, "Setting branding properties enable: {0} _brandingUrl {1}", props.UiBranding.Enable, props.UiBranding.BrandingUrl); BrandingEnabled = props.UiBranding.Enable; _brandingUrl = props.UiBranding.BrandingUrl; } private void SetFeedbackActions() { CodecStatus.Status.Audio.Volume.ValueChangedAction = VolumeLevelFeedback.FireUpdate; CodecStatus.Status.Audio.VolumeMute.ValueChangedAction = MuteFeedback.FireUpdate; CodecStatus.Status.Audio.Microphones.Mute.ValueChangedAction = PrivacyModeIsOnFeedback.FireUpdate; CodecStatus.Status.Standby.State.ValueChangedAction = StandbyIsOnFeedback.FireUpdate; CodecStatus.Status.RoomAnalytics.PeoplePresence.ValueChangedAction = RoomIsOccupiedFeedback.FireUpdate; CodecStatus.Status.RoomAnalytics.PeopleCount.Current.ValueChangedAction = PeopleCountFeedback.FireUpdate; CodecStatus.Status.Cameras.SpeakerTrack.Status.ValueChangedAction = CameraAutoModeIsOnFeedback.FireUpdate; CodecStatus.Status.Video.Selfview.Mode.ValueChangedAction = SelfviewIsOnFeedback.FireUpdate; CodecStatus.Status.Video.Selfview.PIPPosition.ValueChangedAction = ComputeSelfviewPipStatus; CodecStatus.Status.Video.Layout.LayoutFamily.Local.ValueChangedAction = ComputeLocalLayout; CodecStatus.Status.Conference.Presentation.Mode.ValueChangedAction = SharingContentIsOnFeedback.FireUpdate; CodecStatus.Status.Conference.Presentation.Mode.ValueChangedAction = FarEndIsSharingContentFeedback.FireUpdate; try { CodecStatus.Status.Video.Input.MainVideoMute.ValueChangedAction = CameraIsOffFeedback.FireUpdate; } catch (Exception ex) { Debug.Console(0, this, "Error setting MainVideuMute Action: {0}", ex); if (ex.InnerException != null) { Debug.Console(0, this, "Error setting MainVideuMute Action: {0}", ex); } } } /// /// Creates the fake OSD source, and connects it's AudioVideo output to the CodecOsdIn input /// to enable routing /// void CreateOsdSource() { OsdSource = new DummyRoutingInputsDevice(Key + "[osd]"); DeviceManager.AddDevice(OsdSource); var tl = new TieLine(OsdSource.AudioVideoOutputPort, CodecOsdIn); TieLineCollection.Default.Add(tl); } public void InitializeBranding(string roomKey) { Debug.Console(1, this, "Initializing Branding for room {0}", roomKey); if (!BrandingEnabled) { return; } var mcBridgeKey = String.Format("mobileControlBridge-{0}", roomKey); var mcBridge = DeviceManager.GetDeviceForKey(mcBridgeKey) as IMobileControlRoomBridge; if (!String.IsNullOrEmpty(_brandingUrl)) { Debug.Console(1, this, "Branding URL found: {0}", _brandingUrl); if (_brandingTimer != null) { _brandingTimer.Stop(); _brandingTimer.Dispose(); } _brandingTimer = new CTimer((o) => { if (_sendMcUrl) { SendMcBrandingUrl(mcBridge); _sendMcUrl = false; } else { SendBrandingUrl(); _sendMcUrl = true; } }, 0, 15000); } else if (String.IsNullOrEmpty(_brandingUrl)) { Debug.Console(1, this, "No Branding URL found"); if (mcBridge == null) return; Debug.Console(2, this, "Setting QR code URL: {0}", mcBridge.QrCodeUrl); mcBridge.UserCodeChanged += (o, a) => SendMcBrandingUrl(mcBridge); mcBridge.UserPromptedForCode += (o, a) => DisplayUserCode(mcBridge.UserCode); SendMcBrandingUrl(mcBridge); } } /// /// Displays the code for the specified duration /// /// Mobile Control user code private void DisplayUserCode(string code) { SendText(string.Format("xcommand userinterface message alert display title:\"Mobile Control User Code:\" text:\"{0}\" duration: 30", code)); } private void SendMcBrandingUrl(IMobileControlRoomBridge mcBridge) { if (mcBridge == null) { return; } Debug.Console(1, this, "Sending url: {0}", mcBridge.QrCodeUrl); SendText("xconfiguration userinterface custommessage: \"Scan the QR code with a mobile phone to get started\""); SendText("xconfiguration userinterface osd halfwakemessage: \"Tap the touch panel or scan the QR code with a mobile phone to get started\""); var checksum = !String.IsNullOrEmpty(mcBridge.QrCodeChecksum) ? String.Format("checksum: {0} ", mcBridge.QrCodeChecksum) : String.Empty; SendText(String.Format( "xcommand userinterface branding fetch {1}type: branding url: {0}", mcBridge.QrCodeUrl, checksum)); SendText(String.Format( "xcommand userinterface branding fetch {1}type: halfwakebranding url: {0}", mcBridge.QrCodeUrl, checksum)); } private void SendBrandingUrl() { Debug.Console(1, this, "Sending url: {0}", _brandingUrl); SendText(String.Format("xcommand userinterface branding fetch type: branding url: {0}", _brandingUrl)); SendText(String.Format("xcommand userinterface branding fetch type: halfwakebranding url: {0}", _brandingUrl)); } /// /// Starts the HTTP feedback server and syncronizes state of codec /// /// public override bool CustomActivate() { CrestronConsole.AddNewConsoleCommand(SetCommDebug, "SetCodecCommDebug", "0 for Off, 1 for on", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(GetPhonebook, "GetCodecPhonebook", "Triggers a refresh of the codec phonebook", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(GetBookings, "GetCodecBookings", "Triggers a refresh of the booking data for today", ConsoleAccessLevelEnum.AccessOperator); var socket = Communication as ISocketStatus; if (socket != null) { socket.ConnectionChange += new EventHandler(socket_ConnectionChange); } Communication.Connect(); CommunicationMonitor.Start(); string prefix = "xFeedback register "; CliFeedbackRegistrationExpression = prefix + "/Configuration" + Delimiter + prefix + "/Status/Audio" + Delimiter + prefix + "/Status/Call" + Delimiter + prefix + "/Status/Conference/Presentation" + Delimiter + prefix + "/Status/Cameras/SpeakerTrack" + Delimiter + prefix + "/Status/RoomAnalytics" + Delimiter + prefix + "/Status/RoomPreset" + Delimiter + prefix + "/Status/Standby" + Delimiter + prefix + "/Status/Video/Selfview" + Delimiter + prefix + "/Status/Video/Layout" + Delimiter + prefix + "/Status/Video/Input/MainVideoMute" + Delimiter + prefix + "/Bookings" + Delimiter + prefix + "/Event/CallDisconnect" + Delimiter + prefix + "/Event/Bookings" + Delimiter + prefix + "/Event/CameraPresetListUpdated" + Delimiter + prefix + "/Event/UserInterface/Presentation/ExternalSource/Selected/SourceIdentifier" + Delimiter; return base.CustomActivate(); } /// /// Fires when initial codec sync is completed. Used to then send commands to get call history, phonebook, bookings, etc. /// /// /// void SyncState_InitialSyncCompleted(object sender, EventArgs e) { // Fire the ready event SetIsReady(); //CommDebuggingIsOn = false; GetCallHistory(); PhonebookRefreshTimer = new CTimer(CheckCurrentHour, 3600000, 3600000); // check each hour to see if the phonebook should be downloaded GetPhonebook(null); BookingsRefreshTimer = new CTimer(GetBookings, 900000, 900000); // 15 minute timer to check for new booking info GetBookings(null); } public void SetCommDebug(string s) { if (s == "1") { CommDebuggingIsOn = true; Debug.Console(0, this, "Comm Debug Enabled."); } else { CommDebuggingIsOn = false; Debug.Console(0, this, "Comm Debug Disabled."); } } void socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) { Debug.Console(1, this, "Socket status change {0}", e.Client.ClientStatus); if (e.Client.IsConnected) { if(!SyncState.LoginMessageWasReceived) LoginMessageReceivedTimer = new CTimer(o => DisconnectClientAndReconnect(), 5000); } else { SyncState.CodecDisconnected(); PhonebookSyncState.CodecDisconnected(); if (PhonebookRefreshTimer != null) { PhonebookRefreshTimer.Stop(); PhonebookRefreshTimer = null; } if (BookingsRefreshTimer != null) { BookingsRefreshTimer.Stop(); BookingsRefreshTimer = null; } } } void DisconnectClientAndReconnect() { Debug.Console(1, this, "Retrying connection to codec."); Communication.Disconnect(); RetryConnectionTimer = new CTimer(o => Communication.Connect(), 2000); //CrestronEnvironment.Sleep(2000); //Communication.Connect(); } /// /// Gathers responses from the codec (including the delimiter. Responses are checked to see if they contain JSON data and if so, the data is collected until a complete JSON /// message is received before forwarding the message to be deserialized. /// /// /// void Port_LineReceived(object dev, GenericCommMethodReceiveTextArgs args) { if (CommDebuggingIsOn) { 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; if (CommDebuggingIsOn) Debug.Console(1, this, "Incoming JSON message..."); JsonMessage = new StringBuilder(); } else if (args.Text == "}" + Delimiter) // Check for the end of a JSON message { JsonFeedbackMessageIsIncoming = false; JsonMessage.Append(args.Text); if (CommDebuggingIsOn) 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)); return; } if(JsonFeedbackMessageIsIncoming) { JsonMessage.Append(args.Text); //Debug.Console(1, this, "Building JSON:\n{0}", JsonMessage.ToString()); return; } if (!SyncState.InitialSyncComplete) { switch (args.Text.Trim().ToLower()) // remove the whitespace { case "*r login successful": { SyncState.LoginMessageReceived(); if(LoginMessageReceivedTimer != null) LoginMessageReceivedTimer.Stop(); SendText("xPreferences outputmode json"); break; } case "xpreferences outputmode json": { if (!SyncState.InitialStatusMessageWasReceived) SendText("xStatus"); break; } case "xfeedback register /event/calldisconnect": { SyncState.FeedbackRegistered(); break; } } } } public void SendText(string command) { if (CommDebuggingIsOn) Debug.Console(1, this, "Sending: '{0}'", command); Communication.SendText(command + Delimiter); } void DeserializeResponse(string response) { try { //// Serializer settings. We want to ignore null values and missing members //JsonSerializerSettings settings = new JsonSerializerSettings(); //settings.NullValueHandling = NullValueHandling.Ignore; //settings.MissingMemberHandling = MissingMemberHandling.Ignore; //settings.ObjectCreationHandling = ObjectCreationHandling.Auto; if (response.IndexOf("\"Status\":{") > -1 || response.IndexOf("\"Status\": {") > -1) { // Status Message // Temp object so we can inpsect for call data before simply deserializing CiscoCodecStatus.RootObject tempCodecStatus = new CiscoCodecStatus.RootObject(); JsonConvert.PopulateObject(response, tempCodecStatus); // Check to see if the message contains /Status/Conference/Presentation/LocalInstance and extract source value var conference = tempCodecStatus.Status.Conference; if (conference.Presentation.LocalInstance.Count > 0) { if (!string.IsNullOrEmpty(conference.Presentation.LocalInstance[0].ghost)) PresentationSource = 0; else if (conference.Presentation.LocalInstance[0].Source != null) { PresentationSource = conference.Presentation.LocalInstance[0].Source.IntValue; } } // Check to see if this is a call status message received after the initial status message if (tempCodecStatus.Status.Call.Count > 0) { // Iterate through the call objects in the response foreach (CiscoCodecStatus.Call call in tempCodecStatus.Status.Call) { var tempActiveCall = ActiveCalls.FirstOrDefault(c => c.Id.Equals(call.id)); if (tempActiveCall != null) { bool changeDetected = false; eCodecCallStatus newStatus = eCodecCallStatus.Unknown; // Update properties of ActiveCallItem if(call.Status != null) if (!string.IsNullOrEmpty(call.Status.Value)) { tempActiveCall.Status = CodecCallStatus.ConvertToStatusEnum(call.Status.Value); if (newStatus == eCodecCallStatus.Connected) GetCallHistory(); changeDetected = true; } if (call.CallType != null) if (!string.IsNullOrEmpty(call.CallType.Value)) { tempActiveCall.Type = CodecCallType.ConvertToTypeEnum(call.CallType.Value); changeDetected = true; } if (call.DisplayName != null) if (!string.IsNullOrEmpty(call.DisplayName.Value)) { tempActiveCall.Name = call.DisplayName.Value; changeDetected = true; } if (call.Direction != null) { if (!string.IsNullOrEmpty(call.Direction.Value)) { tempActiveCall.Direction = CodecCallDirection.ConvertToDirectionEnum(call.Direction.Value); changeDetected = true; } } if (changeDetected) { SetSelfViewMode(); OnCallStatusChange(tempActiveCall); ListCalls(); } } else if( call.ghost == null ) // if the ghost value is present the call has ended already { // Create a new call item var newCallItem = new CodecActiveCallItem() { Id = call.id, Status = CodecCallStatus.ConvertToStatusEnum(call.Status.Value), Name = call.DisplayName.Value, Number = call.RemoteNumber.Value, Type = CodecCallType.ConvertToTypeEnum(call.CallType.Value), Direction = CodecCallDirection.ConvertToDirectionEnum(call.Direction.Value) }; // Add it to the ActiveCalls List ActiveCalls.Add(newCallItem); ListCalls(); SetSelfViewMode(); OnCallStatusChange(newCallItem); } } } // Check for Room Preset data (comes in partial, so we need to handle these responses differently to prevent appending duplicate items var tempPresets = tempCodecStatus.Status.RoomPreset; if (tempPresets.Count > 0) { // Create temporary list to store the existing items from the CiscoCodecStatus.RoomPreset collection List existingRoomPresets = new List(); // Add the existing items to the temporary list existingRoomPresets.AddRange(CodecStatus.Status.RoomPreset); // Populate the CodecStatus object (this will append new values to the RoomPreset collection JsonConvert.PopulateObject(response, CodecStatus); JObject jResponse = JObject.Parse(response); IList roomPresets = jResponse["Status"]["RoomPreset"].Children().ToList(); // Iterate the new items in this response agains the temporary list. Overwrite any existing items and add new ones. foreach (var preset in tempPresets) { // First fine the existing preset that matches the id var existingPreset = existingRoomPresets.FirstOrDefault(p => p.id.Equals(preset.id)); if (existingPreset != null) { Debug.Console(1, this, "Existing Room Preset with ID: {0} found. Updating.", existingPreset.id); JToken updatedPreset = null; // Find the JToken from the response with the matching id foreach (var jPreset in roomPresets) { if (jPreset["id"].Value() == existingPreset.id) updatedPreset = jPreset; } if (updatedPreset != null) { // use PopulateObject to overlay the partial data onto the existing object JsonConvert.PopulateObject(updatedPreset.ToString(), existingPreset); } } else { Debug.Console(1, this, "New Room Preset with ID: {0}. Adding.", preset.id); existingRoomPresets.Add(preset); } } // Replace the list in the CodecStatus object with the processed list CodecStatus.Status.RoomPreset = existingRoomPresets; // Generecise the list NearEndPresets = RoomPresets.GetGenericPresets(CodecStatus.Status.RoomPreset); var handler = CodecRoomPresetsListHasChanged; if (handler != null) { handler(this, new EventArgs()); } } else { JsonConvert.PopulateObject(response, CodecStatus); } if (!SyncState.InitialStatusMessageWasReceived) { SyncState.InitialStatusMessageReceived(); if (!SyncState.InitialConfigurationMessageWasReceived) SendText("xConfiguration"); } } else if (response.IndexOf("\"Configuration\":{") > -1 || response.IndexOf("\"Configuration\": {") > -1) { // Configuration Message JsonConvert.PopulateObject(response, CodecConfiguration); if (!SyncState.InitialConfigurationMessageWasReceived) { SyncState.InitialConfigurationMessageReceived(); if (!SyncState.FeedbackWasRegistered) { SendText(CliFeedbackRegistrationExpression); } } } else if (response.IndexOf("\"Event\":{") > -1 || response.IndexOf("\"Event\": {") > -1) { if (response.IndexOf("\"CallDisconnect\":{") > -1 || response.IndexOf("\"CallDisconnect\": {") > -1) { CiscoCodecEvents.RootObject eventReceived = new CiscoCodecEvents.RootObject(); JsonConvert.PopulateObject(response, eventReceived); EvalutateDisconnectEvent(eventReceived); } else if (response.IndexOf("\"Bookings\":{") > -1 || response.IndexOf("\"Bookings\": {") > -1) // The list has changed, reload it { GetBookings(null); } else if (response.IndexOf("\"UserInterface\":{") > -1 || response.IndexOf("\"UserInterface\": {") > -1) // External Source Trigger { CiscoCodecEvents.RootObject eventReceived = new CiscoCodecEvents.RootObject(); JsonConvert.PopulateObject(response, eventReceived); Debug.Console(2, this, "*** Got an External Source Selection {0} {1}", eventReceived, eventReceived.Event.UserInterface, eventReceived.Event.UserInterface.Presentation.ExternalSource.Selected.SourceIdentifier.Value); if (RunRouteAction != null && !_externalSourceChangeRequested) { RunRouteAction(eventReceived.Event.UserInterface.Presentation.ExternalSource.Selected.SourceIdentifier.Value, null); } _externalSourceChangeRequested = false; } } else if (response.IndexOf("\"CommandResponse\":{") > -1 || response.IndexOf("\"CommandResponse\": {") > -1) { // CommandResponse Message if (response.IndexOf("\"CallHistoryRecentsResult\":{") > -1 || response.IndexOf("\"CallHistoryRecentsResult\": {") > -1) { var codecCallHistory = new CiscoCallHistory.RootObject(); JsonConvert.PopulateObject(response, codecCallHistory); CallHistory.ConvertCiscoCallHistoryToGeneric(codecCallHistory.CommandResponse.CallHistoryRecentsResult.Entry); } else if (response.IndexOf("\"CallHistoryDeleteEntryResult\":{") > -1 || response.IndexOf("\"CallHistoryDeleteEntryResult\": {") > -1) { GetCallHistory(); } else if (response.IndexOf("\"PhonebookSearchResult\":{") > -1 || response.IndexOf("\"PhonebookSearchResult\": {") > -1) { var codecPhonebookResponse = new CiscoCodecPhonebook.RootObject(); JsonConvert.PopulateObject(response, codecPhonebookResponse); if (!PhonebookSyncState.InitialPhonebookFoldersWasReceived) { // Check if the phonebook has any folders PhonebookSyncState.InitialPhonebookFoldersReceived(); PhonebookSyncState.SetPhonebookHasFolders(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.Folder.Count > 0); if (PhonebookSyncState.PhonebookHasFolders) { DirectoryRoot.AddFoldersToDirectory(CiscoCodecPhonebook.GetRootFoldersFromSearchResult(codecPhonebookResponse.CommandResponse.PhonebookSearchResult)); } // Get the number of contacts in the phonebook GetPhonebookContacts(); } else if (!PhonebookSyncState.NumberOfContactsWasReceived) { // Store the total number of contacts in the phonebook PhonebookSyncState.SetNumberOfContacts(Int32.Parse(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.ResultInfo.TotalRows.Value)); DirectoryRoot.AddContactsToDirectory(CiscoCodecPhonebook.GetRootContactsFromSearchResult(codecPhonebookResponse.CommandResponse.PhonebookSearchResult)); PhonebookSyncState.PhonebookRootEntriesReceived(); PrintDirectory(DirectoryRoot); } else if (PhonebookSyncState.InitialSyncComplete) { var directoryResults = new CodecDirectory(); if(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.ResultInfo.TotalRows.Value != "0") directoryResults = CiscoCodecPhonebook.ConvertCiscoPhonebookToGeneric(codecPhonebookResponse.CommandResponse.PhonebookSearchResult); PrintDirectory(directoryResults); DirectoryBrowseHistory.Add(directoryResults); OnDirectoryResultReturned(directoryResults); } } else if (response.IndexOf("\"BookingsListResult\":{") > -1) { var codecBookings = new CiscoCodecBookings.RootObject(); JsonConvert.PopulateObject(response, codecBookings); if(codecBookings.CommandResponse.BookingsListResult.ResultInfo.TotalRows.Value != "0") CodecSchedule.Meetings = CiscoCodecBookings.GetGenericMeetingsFromBookingResult(codecBookings.CommandResponse.BookingsListResult.Booking); BookingsRefreshTimer.Reset(900000, 900000); } } } catch (Exception ex) { Debug.Console(1, this, "Error Deserializing feedback from codec: {0}", ex); } } /// /// Call when directory results are updated /// /// void OnDirectoryResultReturned(CodecDirectory result) { CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); // This will return the latest results to all UIs. Multiple indendent UI Directory browsing will require a different methodology var handler = DirectoryResultReturned; if (handler != null) { handler(this, new DirectoryEventArgs() { Directory = result, DirectoryIsOnRoot = !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue }); } PrintDirectory(result); } /// /// Evaluates an event received from the codec /// /// void EvalutateDisconnectEvent(CiscoCodecEvents.RootObject eventReceived) { if (eventReceived.Event.CallDisconnect != null) { var tempActiveCall = ActiveCalls.FirstOrDefault(c => c.Id.Equals(eventReceived.Event.CallDisconnect.CallId.Value)); // Remove the call from the Active calls list if (tempActiveCall != null) { ActiveCalls.Remove(tempActiveCall); ListCalls(); SetSelfViewMode(); // Notify of the call disconnection SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, tempActiveCall); GetCallHistory(); } } } /// /// /// /// public override void ExecuteSwitch(object selector) { (selector as Action)(); PresentationSourceKey = selector.ToString(); } /// /// This is necessary for devices that are "routers" in the middle of the path, even though it only has one output and /// may only have one input. /// public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) { ExecuteSwitch(inputSelector); PresentationSourceKey = inputSelector.ToString(); } /// /// Gets the ID of the last connected call /// /// public string GetCallId() { string callId = null; if (ActiveCalls.Count > 1) { var lastCallIndex = ActiveCalls.Count - 1; callId = ActiveCalls[lastCallIndex].Id; } else if (ActiveCalls.Count == 1) callId = ActiveCalls[0].Id; return callId; } public void GetCallHistory() { SendText("xCommand CallHistory Recents Limit: 20 Order: OccurrenceTime"); } /// /// Required for IHasScheduleAwareness /// public void GetSchedule() { GetBookings(null); } /// /// Gets the bookings for today /// /// public void GetBookings(object command) { Debug.Console(1, this, "Retrieving Booking Info from Codec. Current Time: {0}", DateTime.Now.ToLocalTime()); SendText("xCommand Bookings List Days: 1 DayOffset: 0"); } /// /// Checks to see if it is 2am (or within that hour) and triggers a download of the phonebook /// /// public void CheckCurrentHour(object o) { if (DateTime.Now.Hour == 2) { Debug.Console(1, this, "Checking hour to see if phonebook should be downloaded. Current hour is {0}", DateTime.Now.Hour); GetPhonebook(null); PhonebookRefreshTimer.Reset(3600000, 3600000); } } /// /// Triggers a refresh of the codec phonebook /// /// Just to allow this method to be called from a console command public void GetPhonebook(string command) { PhonebookSyncState.CodecDisconnected(); DirectoryRoot = new CodecDirectory(); GetPhonebookFolders(); } 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)); } 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)); } /// /// Searches the codec phonebook for all contacts matching the search string /// /// public void SearchDirectory(string searchString) { SendText(string.Format("xCommand Phonebook Search SearchString: \"{0}\" PhonebookType: {1} ContactType: Contact Limit: {2}", searchString, PhonebookMode, PhonebookResultsLimit)); } /// /// // Get contents of a specific folder in the phonebook /// /// public void GetDirectoryFolderContents(string folderId) { SendText(string.Format("xCommand Phonebook Search FolderId: {0} PhonebookType: {1} ContactType: Any Limit: {2}", folderId, PhonebookMode, PhonebookResultsLimit)); } /// /// Sets the parent folder contents or the directory root as teh current directory and fires the event. Used to browse up a level /// /// public void GetDirectoryParentFolderContents() { var currentDirectory = new CodecDirectory(); if (DirectoryBrowseHistory.Count > 0) { var lastItemIndex = DirectoryBrowseHistory.Count - 1; var parentDirectoryContents = DirectoryBrowseHistory[lastItemIndex]; DirectoryBrowseHistory.Remove(DirectoryBrowseHistory[lastItemIndex]); currentDirectory = parentDirectoryContents; } else { currentDirectory = DirectoryRoot; } OnDirectoryResultReturned(currentDirectory); } /// /// Clears the session browse history and fires the event with the directory root /// public void SetCurrentDirectoryToRoot() { DirectoryBrowseHistory.Clear(); OnDirectoryResultReturned(DirectoryRoot); } /// /// Prints the directory to console /// /// void PrintDirectory(CodecDirectory directory) { if (Debug.Level > 0) { Debug.Console(1, this, "Directory Results:\n"); foreach (DirectoryItem item in directory.CurrentDirectoryResults) { if (item is DirectoryFolder) { Debug.Console(1, this, "[+] {0}", item.Name); } else if (item is DirectoryContact) { Debug.Console(1, this, "{0}", item.Name); } } Debug.Console(1, this, "Directory is on Root Level: {0}", !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue); } } /// /// Simple dial method /// /// public override void Dial(string number) { SendText(string.Format("xCommand Dial Number: \"{0}\"", number)); } /// /// Dials a specific meeting /// /// public override void Dial(Meeting meeting) { foreach (Call c in meeting.Calls) { Dial(c.Number, c.Protocol, c.CallRate, c.CallType, meeting.Id); } } /// /// Detailed dial method /// /// /// /// /// /// public void Dial(string number, string protocol, string callRate, string callType, string meetingId) { SendText(string.Format("xCommand Dial Number: \"{0}\" Protocol: {1} CallRate: {2} CallType: {3} BookingId: {4}", number, protocol, callRate, callType, meetingId)); } public override void EndCall(CodecActiveCallItem activeCall) { SendText(string.Format("xCommand Call Disconnect CallId: {0}", activeCall.Id)); } public override void EndAllCalls() { foreach (CodecActiveCallItem activeCall in ActiveCalls) { SendText(string.Format("xCommand Call Disconnect CallId: {0}", activeCall.Id)); } } public override void AcceptCall(CodecActiveCallItem item) { SendText("xCommand Call Accept"); } public override void RejectCall(CodecActiveCallItem item) { SendText("xCommand Call Reject"); } public override void SendDtmf(string s) { SendText(string.Format("xCommand Call DTMFSend CallId: {0} DTMFString: \"{1}\"", GetCallId(), s)); } public void SelectPresentationSource(int source) { PresentationSource = source; StartSharing(); } /// /// Select source 1 as the presetnation source /// public void SelectPresentationSource1() { SelectPresentationSource(2); } /// /// Select source 2 as the presetnation source /// public void SelectPresentationSource2() { SelectPresentationSource(3); } /// /// Starts presentation sharing /// public override void StartSharing() { string sendingMode = string.Empty; if (IsInCall) sendingMode = "LocalRemote"; else sendingMode = "LocalOnly"; if(PresentationSource > 0) SendText(string.Format("xCommand Presentation Start PresentationSource: {0} SendingMode: {1}", PresentationSource, sendingMode)); } /// /// Stops sharing the current presentation /// public override void StopSharing() { PresentationSource = 0; SendText("xCommand Presentation Stop"); } public override void PrivacyModeOn() { SendText("xCommand Audio Microphones Mute"); } public override void PrivacyModeOff() { SendText("xCommand Audio Microphones Unmute"); } public override void PrivacyModeToggle() { SendText("xCommand Audio Microphones ToggleMute"); } public override void MuteOff() { SendText("xCommand Audio Volume Unmute"); } public override void MuteOn() { SendText("xCommand Audio Volume Mute"); } public override void MuteToggle() { SendText("xCommand Audio Volume ToggleMute"); } /// /// Increments the voluem /// /// public override void VolumeUp(bool pressRelease) { SendText("xCommand Audio Volume Increase"); } /// /// Decrements the volume /// /// public override void VolumeDown(bool pressRelease) { SendText("xCommand Audio Volume Decrease"); } /// /// Scales the level and sets the codec to the specified level within its range /// /// level from slider (0-65535 range) public override void SetVolume(ushort level) { var scaledLevel = CrestronEnvironment.ScaleWithLimits(level, 65535, 0, 100, 0); SendText(string.Format("xCommand Audio Volume Set Level: {0}", scaledLevel)); } /// /// Recalls the default volume on the codec /// public void VolumeSetToDefault() { SendText("xCommand Audio Volume SetToDefault"); } /// /// Puts the codec in standby mode /// public override void StandbyActivate() { SendText("xCommand Standby Activate"); } /// /// Wakes the codec from standby /// public override void StandbyDeactivate() { SendText("xCommand Standby Deactivate"); } public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) { LinkVideoCodecToApi(this, trilist, joinStart, joinMapKey, bridge); } /// /// Reboots the codec /// public void Reboot() { SendText("xCommand SystemUnit Boot Action: Restart"); } /// /// Sets SelfView Mode based on config /// void SetSelfViewMode() { if (!IsInCall) { SelfViewModeOff(); } else { if (ShowSelfViewByDefault) SelfViewModeOn(); else SelfViewModeOff(); } } /// /// Turns on Selfview Mode /// public void SelfViewModeOn() { SendText("xCommand Video Selfview Set Mode: On"); } /// /// Turns off Selfview Mode /// public void SelfViewModeOff() { SendText("xCommand Video Selfview Set Mode: Off"); } /// /// Toggles Selfview mode on/off /// public void SelfViewModeToggle() { string mode = string.Empty; if (CodecStatus.Status.Video.Selfview.Mode.BoolValue) mode = "Off"; else mode = "On"; SendText(string.Format("xCommand Video Selfview Set Mode: {0}", mode)); } /// /// Sets a specified position for the selfview PIP window /// /// public void SelfviewPipPositionSet(CodecCommandWithLabel position) { SendText(string.Format("xCommand Video Selfview Set Mode: On PIPPosition: {0}", position.Command)); } /// /// Toggles to the next selfview PIP position /// public void SelfviewPipPositionToggle() { if (CurrentSelfviewPipPosition != null) { 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; SelfviewPipPositionSet(SelfviewPipPositions[nextPipPositionIndex]); } } /// /// Sets a specific local layout /// /// public void LocalLayoutSet(CodecCommandWithLabel layout) { SendText(string.Format("xCommand Video Layout LayoutFamily Set Target: local LayoutFamily: {0}", layout.Command)); } /// /// Toggles to the next local layout /// public void LocalLayoutToggle() { if(CurrentLocalLayout != null) { 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; LocalLayoutSet(LocalLayouts[nextLocalLayoutIndex]); } } /// /// Toggles between single/prominent layouts /// public void LocalLayoutToggleSingleProminent() { if (CurrentLocalLayout != null) { if (CurrentLocalLayout.Label != "Prominent") LocalLayoutSet(LocalLayouts.FirstOrDefault(l => l.Label.Equals("Prominent"))); else LocalLayoutSet(LocalLayouts.FirstOrDefault(l => l.Label.Equals("Single"))); } } /// /// /// public void MinMaxLayoutToggle() { if (PresentationViewMaximizedFeedback.BoolValue) CurrentPresentationView = "Minimized"; else CurrentPresentationView = "Maximized"; SendText(string.Format("xCommand Video PresentationView Set View: {0}", CurrentPresentationView)); PresentationViewMaximizedFeedback.FireUpdate(); } /// /// Calculates the current selfview PIP position /// void ComputeSelfviewPipStatus() { CurrentSelfviewPipPosition = SelfviewPipPositions.FirstOrDefault(p => p.Command.ToLower().Equals(CodecStatus.Status.Video.Selfview.PIPPosition.Value.ToLower())); if(CurrentSelfviewPipPosition != null) SelfviewIsOnFeedback.FireUpdate(); } /// /// Calculates the current local Layout /// void ComputeLocalLayout() { CurrentLocalLayout = LocalLayouts.FirstOrDefault(l => l.Command.ToLower().Equals(CodecStatus.Status.Video.Layout.LayoutFamily.Local.Value.ToLower())); if (CurrentLocalLayout != null) LocalLayoutFeedback.FireUpdate(); } public void RemoveCallHistoryEntry(CodecCallHistory.CallHistoryEntry entry) { SendText(string.Format("xCommand CallHistory DeleteEntry CallHistoryId: {0} AcknowledgeConsecutiveDuplicates: True", entry.OccurrenceHistoryId)); } #region IHasCameraSpeakerTrack public void CameraAutoModeToggle() { if (!CameraAutoModeIsOnFeedback.BoolValue) { SendText("xCommand Cameras SpeakerTrack Activate"); } else { SendText("xCommand Cameras SpeakerTrack Deactivate"); } } public void CameraAutoModeOn() { if (CameraIsOffFeedback.BoolValue) { CameraMuteOff(); } SendText("xCommand Cameras SpeakerTrack Activate"); } public void CameraAutoModeOff() { if (CameraIsOffFeedback.BoolValue) { CameraMuteOff(); } SendText("xCommand Cameras SpeakerTrack Deactivate"); } #endregion /// /// Builds the cameras List. Could later be modified to build from config data /// void SetUpCameras() { // Add the internal camera Cameras = new List(); var internalCamera = new CiscoSparkCamera(Key + "-camera1", "Near End", this, 1); if(CodecStatus.Status.Cameras.Camera.Count > 0) internalCamera.SetCapabilites(CodecStatus.Status.Cameras.Camera[0].Capabilities.Options.Value); else // Somehow subscribe to the event on the Options.Value property and update when it changes. Cameras.Add(internalCamera); // Add the far end camera var farEndCamera = new CiscoFarEndCamera(Key + "-cameraFar", "Far End", this); Cameras.Add(farEndCamera); SelectedCameraFeedback = new StringFeedback(() => SelectedCamera.Key); ControllingFarEndCameraFeedback = new BoolFeedback(() => SelectedCamera is IAmFarEndCamera); DeviceManager.AddDevice(internalCamera); DeviceManager.AddDevice(farEndCamera); NearEndPresets = new List(15); FarEndRoomPresets = new List(15); // Add the far end presets for (int i = 1; i <= FarEndRoomPresets.Capacity; i++) { var label = string.Format("Far End Preset {0}", i); FarEndRoomPresets.Add(new CodecRoomPreset(i, label, true, false)); } SelectedCamera = internalCamera; ; // call the method to select the camera and ensure the feedbacks get updated. } #region IHasCodecCameras Members public event EventHandler CameraSelected; public List Cameras { get; private set; } public StringFeedback SelectedCameraFeedback { get; private set; } private CameraBase _selectedCamera; /// /// Returns the selected camera /// public CameraBase SelectedCamera { get { return _selectedCamera; } private set { _selectedCamera = value; SelectedCameraFeedback.FireUpdate(); ControllingFarEndCameraFeedback.FireUpdate(); if (CameraIsOffFeedback.BoolValue) CameraMuteOff(); var handler = CameraSelected; if (handler != null) { handler(this, new CameraSelectedEventArgs(SelectedCamera)); } } } public void SelectCamera(string key) { var camera = Cameras.FirstOrDefault(c => c.Key.IndexOf(key, StringComparison.OrdinalIgnoreCase) > -1); if (camera != null) { Debug.Console(2, this, "Selected Camera with key: '{0}'", camera.Key); SelectedCamera = camera; } else Debug.Console(2, this, "Unable to select camera with key: '{0}'", key); } public CameraBase FarEndCamera { get; private set; } public BoolFeedback ControllingFarEndCameraFeedback { get; private set; } #endregion /// /// /// public class CiscoCodecInfo : VideoCodecInfo { public CiscoCodecStatus.RootObject CodecStatus { get; private set; } public CiscoCodecConfiguration.RootObject CodecConfiguration { get; private set; } public override bool MultiSiteOptionIsEnabled { get { if (!string.IsNullOrEmpty(CodecStatus.Status.SystemUnit.Software.OptionKeys.MultiSite.Value) && CodecStatus.Status.SystemUnit.Software.OptionKeys.MultiSite.Value.ToLower() == "true") return true; else return false; } } public override string IpAddress { get { if (CodecConfiguration.Configuration.Network != null) { if (CodecConfiguration.Configuration.Network.Count > 0) return CodecConfiguration.Configuration.Network[0].IPv4.Address.Value; } return string.Empty; } } public override string E164Alias { get { if (CodecConfiguration.Configuration.H323 != null && CodecConfiguration.Configuration.H323.H323Alias.E164 != null) { return CodecConfiguration.Configuration.H323.H323Alias.E164.Value; } else { return string.Empty; } } } public override string H323Id { get { if (CodecConfiguration.Configuration.H323 != null && CodecConfiguration.Configuration.H323.H323Alias != null && CodecConfiguration.Configuration.H323.H323Alias.ID != null) { return CodecConfiguration.Configuration.H323.H323Alias.ID.Value; } else { return string.Empty; } } } public override string SipPhoneNumber { get { if (CodecStatus.Status.SIP != null && CodecStatus.Status.SIP.Registration.Count > 0) { var match = Regex.Match(CodecStatus.Status.SIP.Registration[0].URI.Value, @"(\d+)"); // extract numbers only if (match.Success) { Debug.Console(1, "Extracted phone number as '{0}' from string '{1}'", match.Groups[1].Value, CodecStatus.Status.SIP.Registration[0].URI.Value); return match.Groups[1].Value; } else { Debug.Console(1, "Unable to extract phone number from string: '{0}'", CodecStatus.Status.SIP.Registration[0].URI.Value); return string.Empty; } } else { Debug.Console(1, "Unable to extract phone number. No SIP Registration items found"); return string.Empty; } } } public override string SipUri { get { if (CodecStatus.Status.SIP != null && CodecStatus.Status.SIP.AlternateURI.Primary.URI.Value != null) { return CodecStatus.Status.SIP.AlternateURI.Primary.URI.Value; } else if (CodecStatus.Status.UserInterface != null && CodecStatus.Status.UserInterface.ContactInfo.ContactMethod[0].Number.Value != null) { return CodecStatus.Status.UserInterface.ContactInfo.ContactMethod[0].Number.Value; } else return string.Empty; } } public override bool AutoAnswerEnabled { get { if (CodecConfiguration.Configuration.Conference.AutoAnswer.Mode.Value.ToLower() == "on") return true; else return false; } } public CiscoCodecInfo(CiscoCodecStatus.RootObject status, CiscoCodecConfiguration.RootObject configuration) { CodecStatus = status; CodecConfiguration = configuration; } } #region IHasCameraPresets Members public event EventHandler CodecRoomPresetsListHasChanged; public List NearEndPresets { get; private set; } public List FarEndRoomPresets { get; private set; } public void CodecRoomPresetSelect(int preset) { Debug.Console(1, this, "Selecting Preset: {0}", preset); if (SelectedCamera is IAmFarEndCamera) SelectFarEndPreset(preset); else SendText(string.Format("xCommand RoomPreset Activate PresetId: {0}", preset)); } public void CodecRoomPresetStore(int preset, string description) { SendText(string.Format("xCommand RoomPreset Store PresetId: {0} Description: \"{1}\" Type: All", preset, description)); } #endregion public void SelectFarEndPreset(int preset) { SendText(string.Format("xCommand Call FarEndControl RoomPreset Activate CallId: {0} PresetId: {1}", GetCallId(), preset)); } #region IHasExternalSourceSwitching Members /// /// Wheather the Cisco supports External Source Lists or not /// public bool ExternalSourceListEnabled { get; private set; } /// /// The name of the RoutingInputPort to which the upstream external switcher is connected /// public string ExternalSourceInputPort { get; private set; } public bool BrandingEnabled { get; private set; } private string _brandingUrl; private bool _sendMcUrl; /// /// Adds an external source to the Cisco /// /// /// /// public void AddExternalSource(string connectorId, string key, string name, eExternalSourceType type) { int id = 2; if (connectorId.ToLower() == "hdmiin3") { id = 3; } SendText(string.Format("xCommand UserInterface Presentation ExternalSource Add ConnectorId: {0} SourceIdentifier: \"{1}\" Name: \"{2}\" Type: {3}", id, key, name, type.ToString())); // SendText(string.Format("xCommand UserInterface Presentation ExternalSource State Set SourceIdentifier: \"{0}\" State: Ready", key)); Debug.Console(2, this, "Adding ExternalSource {0} {1}", connectorId, name); } /// /// Sets the state of the External Source /// /// /// public void SetExternalSourceState(string key, eExternalSourceMode mode) { SendText(string.Format("xCommand UserInterface Presentation ExternalSource State Set SourceIdentifier: \"{0}\" State: {1}", key, mode.ToString())); } /// /// Clears all external sources on the codec /// public void ClearExternalSources() { SendText("xCommand UserInterface Presentation ExternalSource RemoveAll"); } /// /// Sets the selected source of the available external sources on teh Touch10 UI /// public void SetSelectedSource(string key) { SendText(string.Format("xCommand UserInterface Presentation ExternalSource Select SourceIdentifier: {0}", key)); _externalSourceChangeRequested = true; } /// /// Action that will run when the External Source is selected. /// public Action RunRouteAction { private get; set; } #endregion #region ExternalDevices #endregion #region IHasCameraOff Members public BoolFeedback CameraIsOffFeedback { get; private set; } public void CameraOff() { CameraMuteOn(); } #endregion public BoolFeedback CameraIsMutedFeedback { get; private set; } /// /// Mutes the outgoing camera video /// public void CameraMuteOn() { SendText("xCommand Video Input MainVideo Mute"); } /// /// Unmutes the outgoing camera video /// public void CameraMuteOff() { SendText("xCommand Video Input MainVideo Unmute"); } /// /// Toggles the camera mute state /// public void CameraMuteToggle() { if (CameraIsMutedFeedback.BoolValue) CameraMuteOff(); else CameraMuteOn(); } } /// /// Represents a codec command that might need to have a friendly label applied for UI feedback purposes /// public class CodecCommandWithLabel { public string Command { get; set; } public string Label { get; set; } public CodecCommandWithLabel(string command, string label) { Command = command; Label = label; } } /// /// Tracks the initial sycnronization state of the codec when making a connection /// public class CodecSyncState : IKeyed { bool _InitialSyncComplete; public event EventHandler InitialSyncCompleted; public string Key { get; private set; } public bool InitialSyncComplete { get { return _InitialSyncComplete; } private set { if (value == true) { var handler = InitialSyncCompleted; if (handler != null) handler(this, new EventArgs()); } _InitialSyncComplete = value; } } public bool LoginMessageWasReceived { get; private set; } public bool InitialStatusMessageWasReceived { get; private set; } public bool InitialConfigurationMessageWasReceived { get; private set; } public bool FeedbackWasRegistered { get; private set; } public CodecSyncState(string key) { Key = key; CodecDisconnected(); } public void LoginMessageReceived() { LoginMessageWasReceived = true; Debug.Console(1, this, "Login Message Received."); CheckSyncStatus(); } public void InitialStatusMessageReceived() { InitialStatusMessageWasReceived = true; Debug.Console(1, this, "Initial Codec Status Message Received."); CheckSyncStatus(); } public void InitialConfigurationMessageReceived() { InitialConfigurationMessageWasReceived = true; Debug.Console(1, this, "Initial Codec Configuration Message Received."); CheckSyncStatus(); } public void FeedbackRegistered() { FeedbackWasRegistered = true; Debug.Console(1, this, "Initial Codec Feedback Registration Successful."); CheckSyncStatus(); } public void CodecDisconnected() { LoginMessageWasReceived = false; InitialConfigurationMessageWasReceived = false; InitialStatusMessageWasReceived = false; FeedbackWasRegistered = false; InitialSyncComplete = false; } void CheckSyncStatus() { if (LoginMessageWasReceived && InitialConfigurationMessageWasReceived && InitialStatusMessageWasReceived && FeedbackWasRegistered) { InitialSyncComplete = true; Debug.Console(1, this, "Initial Codec Sync Complete!"); } else InitialSyncComplete = false; } } public class CiscoSparkCodecFactory : EssentialsDeviceFactory { public CiscoSparkCodecFactory() { TypeNames = new List() { "ciscospark", "ciscowebex", "ciscowebexpro", "ciscoroomkit", "ciscosparkpluscodec" }; } public override EssentialsDevice BuildDevice(DeviceConfig dc) { Debug.Console(1, "Factory Attempting to create new Cisco Codec Device"); var comm = CommFactory.CreateCommForDevice(dc); return new VideoCodec.Cisco.CiscoSparkCodec(dc, comm); } } }