Files
Essentials/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs

2772 lines
103 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro.CrestronThread;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Routing;
using PepperDash.Essentials.Devices.Common.Cameras;
using PepperDash.Essentials.Devices.Common.Codec;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.VideoCodec;
namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco
{
enum eCommandType { SessionStart, SessionEnd, Command, GetStatus, GetConfiguration };
public class CiscoSparkCodec : VideoCodecBase, IHasCallHistory, IHasCallFavorites, IHasDirectory,
IHasScheduleAwareness, IOccupancyStatusProvider, IHasCodecLayouts, IHasCodecSelfView,
ICommunicationMonitor, IRouting, IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets
{
public event EventHandler<DirectoryEventArgs> DirectoryResultReturned;
public CommunicationGather PortGather { get; private set; }
public StatusMonitorBase CommunicationMonitor { get; private set; }
private CrestronQueue<string> ReceiveQueue;
private Thread ReceiveThread;
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;
/// <summary>
/// List the available positions for the selfview PIP window
/// </summary>
public List<CodecCommandWithLabel> SelfviewPipPositions = new List<CodecCommandWithLabel>()
{
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"),
};
/// <summary>
/// Lists the available options for local layout
/// </summary>
public List<CodecCommandWithLabel> LocalLayouts = new List<CodecCommandWithLabel>()
{
//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; }
/// <summary>
/// The root level of the directory
/// </summary>
public CodecDirectory DirectoryRoot { get; private set; }
/// <summary>
/// Represents the current state of the directory and is computed on get
/// </summary>
public CodecDirectory CurrentDirectoryResult
{
get
{
if (DirectoryBrowseHistory.Count > 0)
return DirectoryBrowseHistory[DirectoryBrowseHistory.Count - 1];
else
return DirectoryRoot;
}
}
public BoolFeedback CurrentDirectoryResultIsNotDirectoryRoot { get; private set; }
/// <summary>
/// Tracks the directory browse history when browsing beyond the root directory
/// </summary>
public List<CodecDirectory> DirectoryBrowseHistory { get; private set; }
public CodecScheduleAwareness CodecSchedule { get; private set; }
/// <summary>
/// Gets and returns the scaled volume of the codec
/// </summary>
protected override Func<int> VolumeLevelFeedbackFunc
{
get
{
return () => CrestronEnvironment.ScaleWithLimits(CodecStatus.Status.Audio.Volume.IntValue, 100, 0, 65535, 0);
}
}
protected override Func<bool> PrivacyModeIsOnFeedbackFunc
{
get
{
return () => CodecStatus.Status.Audio.Microphones.Mute.BoolValue;
}
}
protected override Func<bool> StandbyIsOnFeedbackFunc
{
get
{
return () => CodecStatus.Status.Standby.State.BoolValue;
}
}
/// <summary>
/// Gets the value of the currently shared source, or returns null
/// </summary>
protected override Func<string> SharingSourceFeedbackFunc
{
get
{
return () => PresentationSourceKey;
}
}
protected override Func<bool> SharingContentIsOnFeedbackFunc
{
get
{
return () => CodecStatus.Status.Conference.Presentation.Mode.BoolValue;
}
}
protected Func<bool> FarEndIsSharingContentFeedbackFunc
{
get
{
return () => CodecStatus.Status.Conference.Presentation.Mode.Value == "Receiving";
}
}
protected override Func<bool> MuteFeedbackFunc
{
get
{
return () => CodecStatus.Status.Audio.VolumeMute.BoolValue;
}
}
protected Func<bool> RoomIsOccupiedFeedbackFunc
{
get
{
return () => CodecStatus.Status.RoomAnalytics.PeoplePresence.BoolValue;
}
}
protected Func<int> PeopleCountFeedbackFunc
{
get
{
return () => CodecStatus.Status.RoomAnalytics.PeopleCount.Current.IntValue;
}
}
protected Func<bool> SpeakerTrackIsOnFeedbackFunc
{
get
{
return () => CodecStatus.Status.Cameras.SpeakerTrack.Status.BoolValue;
}
}
protected Func<bool> SelfViewIsOnFeedbackFunc
{
get
{
return () => CodecStatus.Status.Video.Selfview.Mode.BoolValue;
}
}
protected Func<string> SelfviewPipPositionFeedbackFunc
{
get
{
return () => CurrentSelfviewPipPosition.Label;
}
}
protected Func<string> LocalLayoutFeedbackFunc
{
get
{
return () => CurrentLocalLayout.Label;
}
}
protected Func<bool> 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";
/// <summary>
/// Used to track the current connector used for the presentation source
/// </summary>
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<Codec.CiscoSparkCodecPropertiesConfig>(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 CrestronQueue<string>(25);
// The thread responsible for dequeuing and processing the messages
ReceiveThread = new Thread((o) => ProcessQueue(), null);
ReceiveThread.Priority = Thread.eThreadPriority.MediumPriority;
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);
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<EventArgs>(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<CodecDirectory>();
CurrentDirectoryResultIsNotDirectoryRoot = new BoolFeedback(() => DirectoryBrowseHistory.Count > 0);
CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate();
CodecSchedule = new CodecScheduleAwareness();
//Set Feedback Actions
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;
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();
}
/// <summary>
/// Runs in it's own thread to dequeue messages in the order they were received to be processed
/// </summary>
/// <returns></returns>
object ProcessQueue()
{
try
{
while (true)
{
var message = ReceiveQueue.Dequeue();
DeserializeResponse(message);
}
}
catch (Exception e)
{
Debug.Console(1, this, "Error Processing Queue: {0}", e);
}
return null;
}
/// <summary>
/// Creates the fake OSD source, and connects it's AudioVideo output to the CodecOsdIn input
/// to enable routing
/// </summary>
void CreateOsdSource()
{
OsdSource = new DummyRoutingInputsDevice(Key + "[osd]");
DeviceManager.AddDevice(OsdSource);
var tl = new TieLine(OsdSource.AudioVideoOutputPort, CodecOsdIn);
TieLineCollection.Default.Add(tl);
}
/// <summary>
/// Starts the HTTP feedback server and syncronizes state of codec
/// </summary>
/// <returns></returns>
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<GenericSocketStatusChageEventArgs>(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 + "/Bookings" + Delimiter +
prefix + "/Event/CallDisconnect" + Delimiter +
prefix + "/Event/Bookings" + Delimiter +
prefix + "/Event/CameraPresetListUpdated" + Delimiter;
return base.CustomActivate();
}
/// <summary>
/// Fires when initial codec sync is completed. Used to then send commands to get call history, phonebook, bookings, etc.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="dev"></param>
/// <param name="args"></param>
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(JsonMessage.ToString());
//DeserializeResponse(JsonMessage.ToString());
if (ReceiveThread.ThreadState != Thread.eThreadStates.ThreadRunning)
ReceiveThread.Start();
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<CiscoCodecStatus.RoomPreset> existingRoomPresets = new List<CiscoCodecStatus.RoomPreset>();
// 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<JToken> 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<string>() == 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("\"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);
}
}
/// <summary>
/// Call when directory results are updated
/// </summary>
/// <param name="result"></param>
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);
}
/// <summary>
/// Evaluates an event received from the codec
/// </summary>
/// <param name="eventReceived"></param>
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();
}
}
}
/// <summary>
///
/// </summary>
/// <param name="selector"></param>
public override void ExecuteSwitch(object selector)
{
(selector as Action)();
PresentationSourceKey = selector.ToString();
}
/// <summary>
/// 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.
/// </summary>
public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType)
{
ExecuteSwitch(inputSelector);
PresentationSourceKey = inputSelector.ToString();
}
/// <summary>
/// Gets the ID of the last connected call
/// </summary>
/// <returns></returns>
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");
}
/// <summary>
/// Required for IHasScheduleAwareness
/// </summary>
public void GetSchedule()
{
GetBookings(null);
}
/// <summary>
/// Gets the bookings for today
/// </summary>
/// <param name="command"></param>
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");
}
/// <summary>
/// Checks to see if it is 2am (or within that hour) and triggers a download of the phonebook
/// </summary>
/// <param name="o"></param>
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);
}
}
/// <summary>
/// Triggers a refresh of the codec phonebook
/// </summary>
/// <param name="command">Just to allow this method to be called from a console command</param>
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));
}
/// <summary>
/// Searches the codec phonebook for all contacts matching the search string
/// </summary>
/// <param name="searchString"></param>
public void SearchDirectory(string searchString)
{
SendText(string.Format("xCommand Phonebook Search SearchString: \"{0}\" PhonebookType: {1} ContactType: Contact Limit: {2}", searchString, PhonebookMode, PhonebookResultsLimit));
}
/// <summary>
/// // Get contents of a specific folder in the phonebook
/// </summary>
/// <param name="folderId"></param>
public void GetDirectoryFolderContents(string folderId)
{
SendText(string.Format("xCommand Phonebook Search FolderId: {0} PhonebookType: {1} ContactType: Any Limit: {2}", folderId, PhonebookMode, PhonebookResultsLimit));
}
/// <summary>
/// Sets the parent folder contents or the directory root as teh current directory and fires the event. Used to browse up a level
/// </summary>
/// <returns></returns>
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);
}
/// <summary>
/// Clears the session browse history and fires the event with the directory root
/// </summary>
public void SetCurrentDirectoryToRoot()
{
DirectoryBrowseHistory.Clear();
OnDirectoryResultReturned(DirectoryRoot);
}
/// <summary>
/// Prints the directory to console
/// </summary>
/// <param name="directory"></param>
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);
}
}
/// <summary>
/// Simple dial method
/// </summary>
/// <param name="number"></param>
public override void Dial(string number)
{
SendText(string.Format("xCommand Dial Number: \"{0}\"", number));
}
/// <summary>
/// Dials a specific meeting
/// </summary>
/// <param name="meeting"></param>
public override void Dial(Meeting meeting)
{
foreach (Call c in meeting.Calls)
{
Dial(c.Number, c.Protocol, c.CallRate, c.CallType, meeting.Id);
}
}
/// <summary>
/// Detailed dial method
/// </summary>
/// <param name="number"></param>
/// <param name="protocol"></param>
/// <param name="callRate"></param>
/// <param name="callType"></param>
/// <param name="meetingId"></param>
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)
{
if (CallFavorites != null)
{
SendText(string.Format("xCommand Call DTMFSend CallId: {0} DTMFString: \"{1}\"", GetCallId(), s));
}
}
public void SelectPresentationSource(int source)
{
PresentationSource = source;
StartSharing();
}
/// <summary>
/// Select source 1 as the presetnation source
/// </summary>
public void SelectPresentationSource1()
{
SelectPresentationSource(2);
}
/// <summary>
/// Select source 2 as the presetnation source
/// </summary>
public void SelectPresentationSource2()
{
SelectPresentationSource(3);
}
/// <summary>
/// Starts presentation sharing
/// </summary>
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));
}
/// <summary>
/// Stops sharing the current presentation
/// </summary>
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");
}
/// <summary>
/// Increments the voluem
/// </summary>
/// <param name="pressRelease"></param>
public override void VolumeUp(bool pressRelease)
{
SendText("xCommand Audio Volume Increase");
}
/// <summary>
/// Decrements the volume
/// </summary>
/// <param name="pressRelease"></param>
public override void VolumeDown(bool pressRelease)
{
SendText("xCommand Audio Volume Decrease");
}
/// <summary>
/// Scales the level and sets the codec to the specified level within its range
/// </summary>
/// <param name="level">level from slider (0-65535 range)</param>
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));
}
/// <summary>
/// Recalls the default volume on the codec
/// </summary>
public void VolumeSetToDefault()
{
SendText("xCommand Audio Volume SetToDefault");
}
/// <summary>
/// Puts the codec in standby mode
/// </summary>
public override void StandbyActivate()
{
SendText("xCommand Standby Activate");
}
/// <summary>
/// Wakes the codec from standby
/// </summary>
public override void StandbyDeactivate()
{
SendText("xCommand Standby Deactivate");
}
/// <summary>
/// Reboots the codec
/// </summary>
public void Reboot()
{
SendText("xCommand SystemUnit Boot Action: Restart");
}
/// <summary>
/// Sets SelfView Mode based on config
/// </summary>
void SetSelfViewMode()
{
if (!IsInCall)
{
SelfViewModeOff();
}
else
{
if (ShowSelfViewByDefault)
SelfViewModeOn();
else
SelfViewModeOff();
}
}
/// <summary>
/// Turns on Selfview Mode
/// </summary>
public void SelfViewModeOn()
{
SendText("xCommand Video Selfview Set Mode: On");
}
/// <summary>
/// Turns off Selfview Mode
/// </summary>
public void SelfViewModeOff()
{
SendText("xCommand Video Selfview Set Mode: Off");
}
/// <summary>
/// Toggles Selfview mode on/off
/// </summary>
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));
}
/// <summary>
/// Sets a specified position for the selfview PIP window
/// </summary>
/// <param name="position"></param>
public void SelfviewPipPositionSet(CodecCommandWithLabel position)
{
SendText(string.Format("xCommand Video Selfview Set Mode: On PIPPosition: {0}", position.Command));
}
/// <summary>
/// Toggles to the next selfview PIP position
/// </summary>
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]);
}
}
/// <summary>
/// Sets a specific local layout
/// </summary>
/// <param name="layout"></param>
public void LocalLayoutSet(CodecCommandWithLabel layout)
{
SendText(string.Format("xCommand Video Layout LayoutFamily Set Target: local LayoutFamily: {0}", layout.Command));
}
/// <summary>
/// Toggles to the next local layout
/// </summary>
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]);
}
}
/// <summary>
/// Toggles between single/prominent layouts
/// </summary>
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")));
}
}
/// <summary>
///
/// </summary>
public void MinMaxLayoutToggle()
{
if (PresentationViewMaximizedFeedback.BoolValue)
CurrentPresentationView = "Minimized";
else
CurrentPresentationView = "Maximized";
SendText(string.Format("xCommand Video PresentationView Set View: {0}", CurrentPresentationView));
PresentationViewMaximizedFeedback.FireUpdate();
}
/// <summary>
/// Calculates the current selfview PIP position
/// </summary>
void ComputeSelfviewPipStatus()
{
CurrentSelfviewPipPosition = SelfviewPipPositions.FirstOrDefault(p => p.Command.ToLower().Equals(CodecStatus.Status.Video.Selfview.PIPPosition.Value.ToLower()));
if(CurrentSelfviewPipPosition != null)
SelfviewIsOnFeedback.FireUpdate();
}
/// <summary>
/// Calculates the current local Layout
/// </summary>
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()
{
SendText("xCommand Cameras SpeakerTrack Activate");
}
public void CameraAutoModeOff()
{
SendText("xCommand Cameras SpeakerTrack Deactivate");
}
#endregion
/// <summary>
/// Builds the cameras List. Could later be modified to build from config data
/// </summary>
void SetUpCameras()
{
// Add the internal camera
Cameras = new List<CameraBase>();
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<CodecRoomPreset>(15);
FarEndRoomPresets = new List<CodecRoomPreset>(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<CameraSelectedEventArgs> CameraSelected;
public List<CameraBase> Cameras { get; private set; }
public StringFeedback SelectedCameraFeedback { get; private set; }
private CameraBase _selectedCamera;
/// <summary>
/// Returns the selected camera
/// </summary>
public CameraBase SelectedCamera
{
get
{
return _selectedCamera;
}
private set
{
_selectedCamera = value;
SelectedCameraFeedback.FireUpdate();
ControllingFarEndCameraFeedback.FireUpdate();
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
/// <summary>
///
/// </summary>
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.H323Alias.E164 != null)
return CodecConfiguration.Configuration.H323.H323Alias.E164.Value;
else
return string.Empty;
}
}
public override string H323Id
{
get
{
if (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;
}
}
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);
}
}
/// <summary>
/// Call when directory results are updated
/// </summary>
/// <param name="result"></param>
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);
}
/// <summary>
/// Evaluates an event received from the codec
/// </summary>
/// <param name="eventReceived"></param>
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();
}
}
}
/// <summary>
///
/// </summary>
/// <param name="selector"></param>
public override void ExecuteSwitch(object selector)
{
(selector as Action)();
PresentationSourceKey = selector.ToString();
}
/// <summary>
/// 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.
/// </summary>
public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType)
{
ExecuteSwitch(inputSelector);
PresentationSourceKey = inputSelector.ToString();
}
/// <summary>
/// Gets the ID of the last connected call
/// </summary>
/// <returns></returns>
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");
}
/// <summary>
/// Required for IHasScheduleAwareness
/// </summary>
public void GetSchedule()
{
GetBookings(null);
}
/// <summary>
/// Gets the bookings for today
/// </summary>
/// <param name="command"></param>
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");
}
/// <summary>
/// Checks to see if it is 2am (or within that hour) and triggers a download of the phonebook
/// </summary>
/// <param name="o"></param>
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);
}
}
/// <summary>
/// Triggers a refresh of the codec phonebook
/// </summary>
/// <param name="command">Just to allow this method to be called from a console command</param>
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));
}
/// <summary>
/// Searches the codec phonebook for all contacts matching the search string
/// </summary>
/// <param name="searchString"></param>
public void SearchDirectory(string searchString)
{
SendText(string.Format("xCommand Phonebook Search SearchString: \"{0}\" PhonebookType: {1} ContactType: Contact Limit: {2}", searchString, PhonebookMode, PhonebookResultsLimit));
}
/// <summary>
/// // Get contents of a specific folder in the phonebook
/// </summary>
/// <param name="folderId"></param>
public void GetDirectoryFolderContents(string folderId)
{
SendText(string.Format("xCommand Phonebook Search FolderId: {0} PhonebookType: {1} ContactType: Any Limit: {2}", folderId, PhonebookMode, PhonebookResultsLimit));
}
/// <summary>
/// Sets the parent folder contents or the directory root as teh current directory and fires the event. Used to browse up a level
/// </summary>
/// <returns></returns>
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);
}
/// <summary>
/// Clears the session browse history and fires the event with the directory root
/// </summary>
public void SetCurrentDirectoryToRoot()
{
DirectoryBrowseHistory.Clear();
OnDirectoryResultReturned(DirectoryRoot);
}
/// <summary>
/// Prints the directory to console
/// </summary>
/// <param name="directory"></param>
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);
}
}
/// <summary>
/// Simple dial method
/// </summary>
/// <param name="number"></param>
public override void Dial(string number)
{
SendText(string.Format("xCommand Dial Number: \"{0}\"", number));
}
/// <summary>
/// Dials a specific meeting
/// </summary>
/// <param name="meeting"></param>
public override void Dial(Meeting meeting)
{
foreach (Call c in meeting.Calls)
{
Dial(c.Number, c.Protocol, c.CallRate, c.CallType, meeting.Id);
}
}
/// <summary>
/// Detailed dial method
/// </summary>
/// <param name="number"></param>
/// <param name="protocol"></param>
/// <param name="callRate"></param>
/// <param name="callType"></param>
/// <param name="meetingId"></param>
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)
{
if (CallFavorites != null)
{
SendText(string.Format("xCommand Call DTMFSend CallId: {0} DTMFString: \"{1}\"", GetCallId(), s));
}
}
public void SelectPresentationSource(int source)
{
PresentationSource = source;
StartSharing();
}
/// <summary>
/// Select source 1 as the presetnation source
/// </summary>
public void SelectPresentationSource1()
{
SelectPresentationSource(2);
}
/// <summary>
/// Select source 2 as the presetnation source
/// </summary>
public void SelectPresentationSource2()
{
SelectPresentationSource(3);
}
/// <summary>
/// Starts presentation sharing
/// </summary>
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));
}
/// <summary>
/// Stops sharing the current presentation
/// </summary>
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");
}
/// <summary>
/// Increments the voluem
/// </summary>
/// <param name="pressRelease"></param>
public override void VolumeUp(bool pressRelease)
{
SendText("xCommand Audio Volume Increase");
}
/// <summary>
/// Decrements the volume
/// </summary>
/// <param name="pressRelease"></param>
public override void VolumeDown(bool pressRelease)
{
SendText("xCommand Audio Volume Decrease");
}
/// <summary>
/// Scales the level and sets the codec to the specified level within its range
/// </summary>
/// <param name="level">level from slider (0-65535 range)</param>
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));
}
/// <summary>
/// Recalls the default volume on the codec
/// </summary>
public void VolumeSetToDefault()
{
SendText("xCommand Audio Volume SetToDefault");
}
/// <summary>
/// Puts the codec in standby mode
/// </summary>
public override void StandbyActivate()
{
SendText("xCommand Standby Activate");
}
/// <summary>
/// Wakes the codec from standby
/// </summary>
public override void StandbyDeactivate()
{
SendText("xCommand Standby Deactivate");
}
/// <summary>
/// Reboots the codec
/// </summary>
public void Reboot()
{
SendText("xCommand SystemUnit Boot Action: Restart");
}
/// <summary>
/// Sets SelfView Mode based on config
/// </summary>
void SetSelfViewMode()
{
if (!IsInCall)
{
SelfViewModeOff();
}
else
{
if (ShowSelfViewByDefault)
SelfViewModeOn();
else
SelfViewModeOff();
}
}
/// <summary>
/// Turns on Selfview Mode
/// </summary>
public void SelfViewModeOn()
{
SendText("xCommand Video Selfview Set Mode: On");
}
/// <summary>
/// Turns off Selfview Mode
/// </summary>
public void SelfViewModeOff()
{
SendText("xCommand Video Selfview Set Mode: Off");
}
/// <summary>
/// Toggles Selfview mode on/off
/// </summary>
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));
}
/// <summary>
/// Sets a specified position for the selfview PIP window
/// </summary>
/// <param name="position"></param>
public void SelfviewPipPositionSet(CodecCommandWithLabel position)
{
SendText(string.Format("xCommand Video Selfview Set Mode: On PIPPosition: {0}", position.Command));
}
/// <summary>
/// Toggles to the next selfview PIP position
/// </summary>
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]);
}
}
/// <summary>
/// Sets a specific local layout
/// </summary>
/// <param name="layout"></param>
public void LocalLayoutSet(CodecCommandWithLabel layout)
{
SendText(string.Format("xCommand Video Layout LayoutFamily Set Target: local LayoutFamily: {0}", layout.Command));
}
/// <summary>
/// Toggles to the next local layout
/// </summary>
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]);
}
}
/// <summary>
/// Toggles between single/prominent layouts
/// </summary>
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")));
}
}
/// <summary>
///
/// </summary>
public void MinMaxLayoutToggle()
{
if (PresentationViewMaximizedFeedback.BoolValue)
CurrentPresentationView = "Minimized";
else
CurrentPresentationView = "Maximized";
SendText(string.Format("xCommand Video PresentationView Set View: {0}", CurrentPresentationView));
PresentationViewMaximizedFeedback.FireUpdate();
}
/// <summary>
/// Calculates the current selfview PIP position
/// </summary>
void ComputeSelfviewPipStatus()
{
CurrentSelfviewPipPosition = SelfviewPipPositions.FirstOrDefault(p => p.Command.ToLower().Equals(CodecStatus.Status.Video.Selfview.PIPPosition.Value.ToLower()));
if(CurrentSelfviewPipPosition != null)
SelfviewIsOnFeedback.FireUpdate();
}
/// <summary>
/// Calculates the current local Layout
/// </summary>
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()
{
SendText("xCommand Cameras SpeakerTrack Activate");
}
public void CameraAutoModeOff()
{
SendText("xCommand Cameras SpeakerTrack Deactivate");
}
#endregion
/// <summary>
/// Builds the cameras List. Could later be modified to build from config data
/// </summary>
void SetUpCameras()
{
// Add the internal camera
Cameras = new List<CameraBase>();
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<CodecRoomPreset>(15);
FarEndRoomPresets = new List<CodecRoomPreset>(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<CameraSelectedEventArgs> CameraSelected;
public List<CameraBase> Cameras { get; private set; }
public StringFeedback SelectedCameraFeedback { get; private set; }
private CameraBase _selectedCamera;
/// <summary>
/// Returns the selected camera
/// </summary>
public CameraBase SelectedCamera
{
get
{
return _selectedCamera;
}
private set
{
_selectedCamera = value;
SelectedCameraFeedback.FireUpdate();
ControllingFarEndCameraFeedback.FireUpdate();
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
/// <summary>
///
/// </summary>
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.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<EventArgs> CodecRoomPresetsListHasChanged;
public List<CodecRoomPreset> NearEndPresets { get; private set; }
public List<CodecRoomPreset> 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));
}
}
/// <summary>
/// Represents a codec command that might need to have a friendly label applied for UI feedback purposes
/// </summary>
public class CodecCommandWithLabel
{
public string Command { get; set; }
public string Label { get; set; }
public CodecCommandWithLabel(string command, string label)
{
Command = command;
Label = label;
}
}
/// <summary>
/// Tracks the initial sycnronization state of the codec when making a connection
/// </summary>
public class CodecSyncState : IKeyed
{
bool _InitialSyncComplete;
public event EventHandler<EventArgs> 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<CiscoSparkCodec>
{
public CiscoSparkCodecFactory()
{
TypeNames = new List<string>() { "ciscospark", "ciscowebex", "ciscowebexpro", "ciscoroomkit" };
}
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);
}
}
}