diff --git a/src/V2/CallStatusJoinMap.cs b/src/V2/CallStatusJoinMap.cs new file mode 100644 index 0000000..09d3653 --- /dev/null +++ b/src/V2/CallStatusJoinMap.cs @@ -0,0 +1,12 @@ +using PepperDash.Essentials.Core; + +namespace epi_videoCodec_ciscoExtended.V2 +{ + public class CallStatusJoinMap : JoinMapBaseAdvanced + { + public CallStatusJoinMap(uint joinStart) + : base(joinStart, typeof(CallStatusJoinMap)) + { + } + } +} \ No newline at end of file diff --git a/src/V2/CiscoAudio.cs b/src/V2/CiscoAudio.cs new file mode 100644 index 0000000..16b8c93 --- /dev/null +++ b/src/V2/CiscoAudio.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace epi_videoCodec_ciscoExtended.V2 +{ + /*xstatus audio +*s Audio Input Connectors HDMI 1 Mute: Off +*s Audio Input Connectors HDMI 2 Mute: Off +*s Audio Input Connectors HDMI 3 Mute: On +*s Audio Input Connectors Microphone 1 ConnectionStatus: Connected +*s Audio Input Connectors Microphone 1 EcReferenceDelay: 40 +*s Audio Input Connectors Microphone 1 Mute: Off +*s Audio Input Connectors Microphone 2 ConnectionStatus: NotConnected +*s Audio Input Connectors Microphone 2 EcReferenceDelay: 0 +*s Audio Input Connectors Microphone 2 Mute: Off +*s Audio Input Connectors Microphone 3 ConnectionStatus: NotConnected +*s Audio Input Connectors Microphone 3 EcReferenceDelay: 0 +*s Audio Input Connectors Microphone 3 Mute: Off +*s Audio Microphones MusicMode: Off +*s Audio Microphones Mute: Off +*s Audio Microphones NoiseRemoval: On +*s Audio Output Connectors ARC 1 DelayMs: 30 +*s Audio Output Connectors ARC 1 Mode: On +*s Audio Output Connectors HDMI 1 DelayMs: 0 +*s Audio Output Connectors HDMI 1 MicPassthrough: Off +*s Audio Output Connectors HDMI 1 Mode: Off +*s Audio Output Connectors HDMI 2 DelayMs: 0 +*s Audio Output Connectors HDMI 2 MicPassthrough: Off +*s Audio Output Connectors HDMI 2 Mode: Off +*s Audio Output Connectors Line 1 ConnectionStatus: NotConnected +*s Audio Output Connectors Line 1 DelayMs: 30 +*s Audio Output MeasuredHdmiArcDelay: 0 +*s Audio Output MeasuredHdmiDelay: 30 +*s Audio Output ReportedHdmiCecDelay: 0 +*s Audio Ultrasound Volume: 70 +*s Audio Volume: 0 +*s Audio VolumeMute: Off +** end + * *s Audio Microphones Mute: Off +*/ + + public class CiscoAudio : CiscoRoomOsFeature, IHasPolls, IHasEventSubscriptions, IHandlesResponses, IBasicVolumeWithFeedback, IPrivacy + { + private readonly CiscoRoomOsDevice parent; + private bool privacyIsOn; + private bool muteIsOn; + private int level; + + public CiscoAudio(CiscoRoomOsDevice parent) : base(parent.Key + "-audio") + { + this.parent = parent; + + Subscriptions = new List + { + "Status/Audio/Volume", + "Status/Audio/VolumeMute", + "Status/Audio/Microphones" + }; + + Polls = new List + { + "xStatus Audio VolumeMute", + "xStatus Audio Volume" + }; + + MuteFeedback = new BoolFeedback("MuteIsOn", () => muteIsOn); + VolumeLevelFeedback = new IntFeedback("CurrentVolume", () => CrestronEnvironment.ScaleWithLimits(level, 100, 0, 65535, 0)); + PrivacyModeIsOnFeedback = new BoolFeedback("Privacy", () => privacyIsOn); + + MuteFeedback.RegisterForDebug(parent); + VolumeLevelFeedback.RegisterForDebug(parent); ; + PrivacyModeIsOnFeedback.RegisterForDebug(parent); + } + + public IEnumerable Polls { get; private set; } + public IEnumerable Subscriptions { get; private set; } + + public bool HandlesResponse(string response) + { + return response.IndexOf("*s Audio", StringComparison.Ordinal) > -1; + } + + public void HandleResponse(string response) + { + foreach (var part in response.Split('|').Where(p => p.Length > 0)) + { + if (part == "*s Audio VolumeMute: Off") + { + muteIsOn = false; + MuteFeedback.FireUpdate(); + } + else if (part == "*s Audio VolumeMute: On") + { + muteIsOn = true; + MuteFeedback.FireUpdate(); + } + else if (part.StartsWith("*s Audio Volume:")) + { + var result = part.Replace("*s Audio Volume:", "").Trim(); + level = Convert.ToInt32(result); + VolumeLevelFeedback.FireUpdate(); + } + else if (part == "*s Audio Microphones Mute: Off") + { + privacyIsOn = false; + PrivacyModeIsOnFeedback.FireUpdate(); + } + else if (part == "*s Audio Microphones Mute: On") + { + privacyIsOn = true; + PrivacyModeIsOnFeedback.FireUpdate(); + } + else + { + Debug.Console(2, parent, "Unknown audio response:{0}", part); + } + } + } + + public void VolumeUp(bool pressRelease) + { + if (pressRelease) + parent.SendText("xCommand Audio Volume Increase"); + } + + public void VolumeDown(bool pressRelease) + { + if (pressRelease) + parent.SendText("xCommand Audio Volume Decrease"); + } + + public void MuteToggle() + { + parent.SendText("xCommand Audio Volume ToggleMute"); + } + + public void MuteOn() + { + parent.SendText("xCommand Audio Volume Mute"); + } + + public void MuteOff() + { + parent.SendText("xCommand Audio Volume Unmute"); + } + + public void SetVolume(ushort level) + { + if (level == 0) + return; + + var scaledLevel = CrestronEnvironment.ScaleWithLimits(level, 65535, 0, 100, 0); + var command = "xCommand Audio Volume Set Level: " + scaledLevel; + parent.SendText(command); + } + + public BoolFeedback MuteFeedback { get; private set; } + public IntFeedback VolumeLevelFeedback { get; private set; } + + public void PrivacyModeOn() + { + const string command = "xCommand Audio Microphones Mute"; + parent.SendText(command); + } + + public void PrivacyModeOff() + { + const string command = "xCommand Audio Microphones Unmute"; + parent.SendText(command); + } + + public void PrivacyModeToggle() + { + const string command = "xCommand Audio Microphones ToggleMute"; + parent.SendText(command); + } + + public BoolFeedback PrivacyModeIsOnFeedback { get; private set; } + } +} \ No newline at end of file diff --git a/src/V2/CiscoCallStatus.cs b/src/V2/CiscoCallStatus.cs index 6dfbf1d..f11da11 100644 --- a/src/V2/CiscoCallStatus.cs +++ b/src/V2/CiscoCallStatus.cs @@ -5,7 +5,7 @@ using System.Text.RegularExpressions; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; -using PDT.Plugins.Cisco.RoomOs.V2; +using PepperDash.Core; using PepperDash.Core.Intersystem; using PepperDash.Core.Intersystem.Tokens; using PepperDash.Essentials.Core; @@ -14,25 +14,20 @@ namespace epi_videoCodec_ciscoExtended.V2 { - public class CiscoCallStatusJoinMap : JoinMapBaseAdvanced - { - - - public CiscoCallStatusJoinMap(uint joinStart) - : base(joinStart, typeof(CiscoCallStatusJoinMap)) - { - } - } - public class CiscoCallStatus : CiscoRoomOsFeature, IJoinCalls, IHasCallHold, IHasDialer + public class CiscoCallStatus : CiscoRoomOsFeature, IJoinCalls, IHasCallHold, IHasDialer, IHasPolls, + IHasEventSubscriptions, IHandlesResponses { private readonly CCriticalSection activeCallItemsSync = new CCriticalSection(); - private readonly IDictionary activeCallItems = - new Dictionary(); + private readonly IDictionary activeCallItems; private readonly CiscoRoomOsDevice parent; + public readonly StringFeedback CallStatusXSig; - private StringFeedback callStatusXsig; + public IEnumerable ActiveCalls + { + get { return activeCallItems.Values.ToList(); } + } private static readonly List PollStrings = new List { @@ -41,32 +36,68 @@ public class CiscoCallStatus : CiscoRoomOsFeature, IJoinCalls, IHasCallHold, IHa private static readonly List EventSubscriptions = new List { - "/Status/Call" + "Status/Call" }; public CiscoCallStatus(CiscoRoomOsDevice parent) : base(parent.Key + "-calls") { this.parent = parent; - callStatusXsig = new StringFeedback(UpdateCallStatusXSig); + activeCallItems = new Dictionary(); + + CallStatusXSig = new StringFeedback(UpdateCallStatusXSig); + NumberOfActiveCalls = new IntFeedback("NumberOfCalls", () => activeCallItems.Count); + CallIsConnectedOrConnecting = new BoolFeedback("CallIsConnected/Connecting", () => activeCallItems.Any()); + CallIsIncoming = new BoolFeedback("CallIncoming", () => activeCallItems.Any(item => item.Value.Direction == eCodecCallDirection.Incoming)); + IncomingCallName = new StringFeedback("IncomingCallName", () => + { + var incoming = + activeCallItems.Values.FirstOrDefault(item => item.Direction == eCodecCallDirection.Incoming); + + return incoming == null ? string.Empty : incoming.Name; + }); + + IncomingCallNumber = new StringFeedback("IncomingCallNumber", () => + { + var incoming = + activeCallItems.Values.FirstOrDefault(item => item.Direction == eCodecCallDirection.Incoming); + + return incoming == null ? string.Empty : incoming.Number; + }); + + CallIsIncoming.RegisterForDebug(parent); + NumberOfActiveCalls.RegisterForDebug(parent); + CallIsConnectedOrConnecting.RegisterForDebug(parent); + IncomingCallName.RegisterForDebug(parent); + IncomingCallNumber.RegisterForDebug(parent); + + CallStatusChange += + (sender, args) => + Debug.Console(1, parent, "Call Status Change:{0} {1}", args.CallItem.Name, args.CallItem.Status); } - public override IEnumerable Polls + public readonly BoolFeedback CallIsIncoming; + public readonly IntFeedback NumberOfActiveCalls; + public readonly BoolFeedback CallIsConnectedOrConnecting; + public readonly StringFeedback IncomingCallName; + public readonly StringFeedback IncomingCallNumber; + + public IEnumerable Polls { get { return PollStrings; } } - public override IEnumerable Subscriptions + public IEnumerable Subscriptions { get { return EventSubscriptions; } } - public override bool HandlesResponse(string response) + public bool HandlesResponse(string response) { return response.IndexOf("*s Call", StringComparison.Ordinal) > -1; } - public override void HandleResponse(string response) + public void HandleResponse(string response) { const string pattern = @"\*s Call (\d+) (\w+ ?\w*): (.*)"; const string ghostPattern = @"\*s Call (\d+) \(ghost=(True|False)\):"; @@ -104,7 +135,7 @@ public override void HandleResponse(string response) if (match.Success) { var callId = match.Groups[1].Value; - var property = match.Groups[2].Value; + var property = match.Groups[2].Value.Trim(new []{ ' ', '\"' }); var value = match.Groups[3].Value; CodecActiveCallItem activeCall; @@ -119,31 +150,40 @@ public override void HandleResponse(string response) activeCallItems.Add(callId, activeCall); } - switch (property) - { - case "DisplayName": - activeCallItems[callId].Name = value; - break; - case "RemoteNumber": - activeCallItems[callId].Number = value; - break; - case "CallType": - activeCallItems[callId].Type = - (eCodecCallType)Enum.Parse(typeof(eCodecCallType), value, true); - break; - case "Status": - activeCallItems[callId].Status = - value == "Dialling" - ? eCodecCallStatus.Dialing - : (eCodecCallStatus)Enum.Parse(typeof (eCodecCallStatus), value, true); - break; - case "Direction": - activeCallItems[callId].Direction = - (eCodecCallDirection)Enum.Parse(typeof(eCodecCallDirection), value, true); - break; - case "PlacedOnHold": - activeCallItems[callId].IsOnHold = bool.Parse(value); - break; + try + { + switch (property) + { + case "DisplayName": + activeCallItems[callId].Name = value; + break; + case "RemoteNumber": + activeCallItems[callId].Number = value; + break; + case "CallType": + activeCallItems[callId].Type = + (eCodecCallType) Enum.Parse(typeof (eCodecCallType), value, true); + break; + case "Status": + activeCallItems[callId].Status = + value == "Dialling" + ? eCodecCallStatus.Dialing + : (eCodecCallStatus) Enum.Parse(typeof (eCodecCallStatus), value, true); + break; + case "Direction": + activeCallItems[callId].Direction = + (eCodecCallDirection) Enum.Parse(typeof (eCodecCallDirection), value, true); + break; + case "PlacedOnHold": + activeCallItems[callId].IsOnHold = bool.Parse(value); + break; + default: + break; + } + } + catch (Exception ex) + { + Debug.Console(0, this, "Failed parsing the call status:{0}", ex); } if (!changedCallItems.ContainsKey(callId)) @@ -152,42 +192,100 @@ public override void HandleResponse(string response) } } } - - callStatusXsig.FireUpdate(); } finally { activeCallItemsSync.Leave(); } + CallIsIncoming.FireUpdate(); + CallIsConnectedOrConnecting.FireUpdate(); + NumberOfActiveCalls.FireUpdate(); + IncomingCallName.FireUpdate(); + IncomingCallNumber.FireUpdate(); + CallStatusXSig.FireUpdate(); + var handler = CallStatusChange; - if (handler != null) + if (handler == null) return; + + foreach (var callItem in changedCallItems.Values) { - foreach (var callItem in changedCallItems.Values) - { - handler(parent, new CodecCallStatusItemChangeEventArgs(callItem)); - } + handler(parent, new CodecCallStatusItemChangeEventArgs(callItem)); } } public void JoinCall(CodecActiveCallItem activeCall) { - throw new NotImplementedException(); + var command = "xCommand Call Join CallId:" + activeCall.Id; + + var ids = activeCallItems + .Values + .Aggregate(new StringBuilder(), (builder, item) => + { + if (item.IsActiveCall) + builder.Append(" CallId:{0}" + item.Id); + + return builder; + }); + + parent.SendText(command + ids); } public void JoinAllCalls() { - throw new NotImplementedException(); + var ids = new StringBuilder(); + + foreach (var call in activeCallItems.Values) + { + ids.Append(string.Format(" CallId:{0}", call.Id)); + } + + if (ids.Length <= 0) return; + + var command = "xCommand Call Join" + ids; + parent.SendText(command); } public void HoldCall(CodecActiveCallItem activeCall) { - throw new NotImplementedException(); + var command = "xCommand Call Hold CallId: " + activeCall.Id; + parent.SendText(command); + } + + public void HoldAllCalls() + { + var ids = new StringBuilder(); + + foreach (var call in activeCallItems.Values) + { + ids.Append(string.Format(" CallId:{0}", call.Id)); + } + + if (ids.Length <= 0) return; + + var command = "xCommand Call Hold" + ids; + parent.SendText(command); } public void ResumeCall(CodecActiveCallItem activeCall) { - throw new NotImplementedException(); + var command = "xCommand Call Resume CallId: " + activeCall.Id; + parent.SendText(command); + } + + public void ResumeAllCalls() + { + var ids = new StringBuilder(); + + foreach (var call in activeCallItems.Values) + { + ids.Append(string.Format(" CallId:{0}", call.Id)); + } + + if (ids.Length <= 0) return; + + var command = "xCommand Call Resume" + ids; + parent.SendText(command); } private string UpdateCallStatusXSig() @@ -197,14 +295,14 @@ private string UpdateCallStatusXSig() const int maxDigitals = 2; const int offset = maxStrings + maxDigitals; var stringIndex = 0; - var digitalIndex = maxStrings * maxCalls; + var digitalIndex = maxStrings*maxCalls; var arrayIndex = 0; - var tokenArray = new XSigToken[maxCalls * offset]; //set array size for number of calls * pieces of info + var tokenArray = new XSigToken[maxCalls*offset]; //set array size for number of calls * pieces of info foreach (var call in activeCallItems.Values) { - if (arrayIndex >= maxCalls * offset) + if (arrayIndex >= maxCalls*offset) break; //digitals @@ -226,7 +324,7 @@ private string UpdateCallStatusXSig() digitalIndex += maxDigitals; } - while (arrayIndex < maxCalls * offset) + while (arrayIndex < maxCalls*offset) { //digitals tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false); @@ -253,7 +351,7 @@ private string UpdateCallStatusXSig() private static string GetXSigString(XSigToken[] tokenArray) { using (var s = new MemoryStream()) - using (var tw = new XSigTokenStreamWriter(s, true)) + using (var tw = new XSigTokenStreamWriter(s, false)) { tw.WriteXSigData(tokenArray); var xSig = s.ToArray(); @@ -263,32 +361,61 @@ private static string GetXSigString(XSigToken[] tokenArray) public void Dial(string number) { - throw new NotImplementedException(); + const string format = "xCommand Dial Number: \"{0}\""; + var command = string.Format(format, number); + parent.SendText(command); } public void EndCall(CodecActiveCallItem activeCall) { - throw new NotImplementedException(); + var command = "xCommand Call Disconnect CallId: " + activeCall.Id; + parent.SendText(command); } public void EndAllCalls() { - throw new NotImplementedException(); + foreach (var callItem in activeCallItems.Values) + { + EndCall(callItem); + } } public void AcceptCall(CodecActiveCallItem item) { - throw new NotImplementedException(); + parent.SendText("xCommand Call Accept CallId: " + item.Id); + } + + public void AcceptCall() + { + parent.SendText("xCommand Call Accept"); } public void RejectCall(CodecActiveCallItem item) { - throw new NotImplementedException(); + parent.SendText("xCommand Call Reject CallId:" + item.Id); + } + + public void RejectCall() + { + parent.SendText("xCommand Call Reject"); } public void SendDtmf(string digit) { - throw new NotImplementedException(); + var activeCall = GetActiveCallId(); + const string format = "xCommand Call DTMFSend CallId: {0} DTMFString: \"{1}\""; + var command = string.Format(format, activeCall, digit); + parent.SendText(command); + } + + public string GetActiveCallId() + { + var calls = ActiveCalls.ToList(); + if (calls.Count <= 1) + return calls.Count == 1 ? calls[0].Id : string.Empty; + + var lastCallIndex = calls.Count - 1; + return calls[lastCallIndex].Id; } public bool IsInCall diff --git a/src/V2/CiscoCameras.cs b/src/V2/CiscoCameras.cs index eeb38d8..820f0dd 100644 --- a/src/V2/CiscoCameras.cs +++ b/src/V2/CiscoCameras.cs @@ -3,16 +3,24 @@ using System.Linq; using System.Text.RegularExpressions; using Crestron.SimplSharp; -using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Core; using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Devices.Common.Cameras; -namespace PDT.Plugins.Cisco.RoomOs.V2 +namespace epi_videoCodec_ciscoExtended.V2 { - internal class CiscoCameras : CiscoRoomOsFeature, IHasCodecCameras, IBridgeAdvanced + /* *s Cameras SpeakerTrack State: Off + *s Cameras SpeakerTrack Status: Active */ + internal class CiscoCameras : CiscoRoomOsFeature, IHasCodecCameras, IHasPolls, IHasEventSubscriptions, IHandlesResponses, IHasCameraAutoMode { + [Flags] + public enum TrackingCapabilities + { + None = 0, + PresenterTrack = 1, + SpeakerTrack = 2 + } + private static readonly List PollsList = new List { "xStatus Cameras" @@ -20,49 +28,67 @@ internal class CiscoCameras : CiscoRoomOsFeature, IHasCodecCameras, IBridgeAdvan private readonly List cameras = new List(); + // Auto mode is an abstraction for Speaker Track + private bool speakerTrackIsOn; + private bool speakerTrackIsAvailable; + + private TrackingCapabilities trackingCapabilities = TrackingCapabilities.None; + private readonly CiscoRoomOsDevice parent; private readonly CiscoFarEndCamera farEndCamera; private readonly CCriticalSection listSync = new CCriticalSection(); private readonly CCriticalSection selectedCameraSync = new CCriticalSection(); + private CameraBase selectedCamera; public CiscoCameras(CiscoRoomOsDevice parent) : base(parent.Key + "-cameras") { this.parent = parent; farEndCamera = new CiscoFarEndCamera(parent.Key + "-cameraFar", "Far End", parent); - ControllingFarEndCameraFeedback = new BoolFeedback(() => selectedCamera is CiscoFarEndCamera); - SelectedCameraFeedback = new StringFeedback(() => string.Empty); - } + ControllingFarEndCameraFeedback = new BoolFeedback("ControllingFarEndCamera", () => selectedCamera is CiscoFarEndCamera); + SelectedCameraFeedback = new StringFeedback("SelectedCamera", () => selectedCamera == null ? string.Empty : selectedCamera.Name); + CameraAutoModeIsOnFeedback = new BoolFeedback("SpeakerTrackEnabled", () => speakerTrackIsOn); + SpeakerTrackIsAvailable = new BoolFeedback("SpeakerTrackAvailable", () => speakerTrackIsAvailable); - public override IEnumerable Polls - { - get { return PollsList; } - } + ControllingFarEndCameraFeedback.RegisterForDebug(parent); + SelectedCameraFeedback.RegisterForDebug(parent); + CameraAutoModeIsOnFeedback.RegisterForDebug(parent); + SpeakerTrackIsAvailable.RegisterForDebug(parent); - public override IEnumerable Subscriptions - { - get { return Enumerable.Empty(); } + Subscriptions = new List + { + "Event/CameraPresetListUpdated" + }; } - public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + public IEnumerable Polls { - throw new NotImplementedException(); + get { return PollsList; } } + public IEnumerable Subscriptions { get; private set; } + public void SelectCamera(string key) { selectedCameraSync.Enter(); CameraBase nextCamera; + try { - nextCamera = cameras.Find(camera => camera.Key == key); - if (selectedCamera == null) + nextCamera = Cameras.Find(camera => camera.Key == key); + if (nextCamera == null) { Debug.Console(1, this, "Could not find selected camera with key:{0}", key); + return; } else { selectedCamera = nextCamera; + var cameraToSwitch = selectedCamera as CiscoNearEndCamera; + if (cameraToSwitch != null) + { + parent.SendText("xCommand Video Input SetMainVideoSource ConnectorId: " + cameraToSwitch.Connector); + } } } finally @@ -70,16 +96,12 @@ public void SelectCamera(string key) selectedCameraSync.Leave(); } - if (nextCamera != null) - { - SelectedCameraFeedback.SetValueFunc(() => nextCamera.Name); - SelectedCameraFeedback.FireUpdate(); + SelectedCameraFeedback.FireUpdate(); - EventHandler handler = CameraSelected; - if (handler != null) - { - handler(this, new CameraSelectedEventArgs(nextCamera)); - } + var handler = CameraSelected; + if (handler != null) + { + handler(this, new CameraSelectedEventArgs(nextCamera)); } } @@ -90,7 +112,7 @@ public List Cameras listSync.Enter(); try { - return cameras.Cast().ToList(); + return cameras.OfType().ToList(); } finally { @@ -126,20 +148,21 @@ public CameraBase FarEndCamera public BoolFeedback ControllingFarEndCameraFeedback { get; private set; } - public override bool HandlesResponse(string response) + public bool HandlesResponse(string response) { return response.IndexOf("*s Cameras", StringComparison.Ordinal) > -1; } - public override void HandleResponse(string response) + public void HandleResponse(string response) { - const string pattern = @"\*s Cameras Camera (\d+) (\w+): (.+)"; - - foreach (var line in response.Split('|')) + listSync.Enter(); + try { - listSync.Enter(); - try + var cameraIds = new List(); + + foreach (var line in response.Split('|')) { + const string pattern = @"\*s Cameras Camera (\d+) (.*?): (.+)"; var match = Regex.Match(line, pattern); if (match.Success) { @@ -147,6 +170,9 @@ public override void HandleResponse(string response) var property = match.Groups[2].Value; var value = match.Groups[3].Value; + if (!cameraIds.Contains(cameraIndex)) + cameraIds.Add(cameraIndex); + var camera = cameras.Find(cam => cam.CameraId == cameraIndex); if (camera == null) { @@ -157,17 +183,135 @@ public override void HandleResponse(string response) switch (property) { + case "DetectedConnector": + camera.Connector = Convert.ToInt32(value); + Debug.Console(1, this, "Camera:{0} | DetectedConnector {1}", cameraIndex, value); + break; + case "Capabilities Options": + camera.SetCapabilites(value); + Debug.Console(1, this, "Camera:{0} | Capabilities {1}", cameraIndex, value); + break; default: - Debug.Console(2, this, "Camera:{0} | Property:{1}:{2}", cameraIndex, property, value); + Debug.Console(1, this, "Camera:{0} | Property:{1} = {2}", cameraIndex, property, value); break; } } + else if (line.Equals("*s Cameras SpeakerTrack Availability:")) + { + var parts = line.Split(':'); + speakerTrackIsAvailable = parts[1].Trim().Equals("Available"); + SpeakerTrackIsAvailable.FireUpdate(); + + if (trackingCapabilities == TrackingCapabilities.None) + { + trackingCapabilities = TrackingCapabilities.SpeakerTrack; + } + else + { + trackingCapabilities = trackingCapabilities | TrackingCapabilities.SpeakerTrack; + } + } + else if (line.Contains("*s Cameras SpeakerTrack Status:")) + { + var parts = line.Split(':'); + speakerTrackIsOn = parts[1].Trim().Equals("Active"); + CameraAutoModeIsOnFeedback.FireUpdate(); + } + else if (line.Contains("*s Cameras SpeakerTrack ActiveConnector:")) + { + var parts = line.Split(':'); + var connector = Convert.ToInt32(parts[1]); + foreach (var camera in cameras) + { + camera.IsSpeakerTrack = camera.Connector == connector; + } + } + else if (line.Equals("*s Cameras PresenterTrack Availability:")) + { + /* + var parts = line.Split(':'); + speakerTrackIsAvailable = parts[1].Trim().Equals("Available"); + SpeakerTrackIsAvailable.FireUpdate();*/ + + if (trackingCapabilities == TrackingCapabilities.None) + { + trackingCapabilities = TrackingCapabilities.SpeakerTrack; + } + else + { + trackingCapabilities = trackingCapabilities | TrackingCapabilities.SpeakerTrack; + } + } + else if (line.Contains("*s Cameras PresenterTrack Status:")) + { + /* + var parts = line.Split(':'); + speakerTrackIsOn = parts[1].Trim().Equals("Active"); + CameraAutoModeIsOnFeedback.FireUpdate();*/ + } + else if (line.Contains("*s Cameras PresenterTrack ActiveConnector:")) + { + var parts = line.Split(':'); + var connector = Convert.ToInt32(parts[1]); + foreach (var camera in cameras) + { + camera.IsPresenterTrack = camera.Connector == connector; + } + } } - finally + + var camerasToRemove = + from camera in cameras + where !cameraIds.Contains(camera.CameraId) + select camera; + + foreach (var camera in camerasToRemove) { - listSync.Leave(); + cameras.Remove(camera); } } + finally + { + listSync.Leave(); + } + + if (cameras.Count == 1) + { + SelectCamera(cameras[0].Key); + } + + if (selectedCamera == null) + { + selectedCamera = cameras.FirstOrDefault(); + } + + SelectedCameraFeedback.FireUpdate(); } + + public void CameraAutoModeOn() + { + parent.SendText("xCommand Cameras SpeakerTrack Activate"); + } + + public void CameraAutoModeOff() + { + parent.SendText("xCommand Cameras SpeakerTrack Deactivate"); + } + + public void CameraAutoModeToggle() + { + if (!CameraAutoModeIsOnFeedback.BoolValue) + { + CameraAutoModeOn(); + } + else + { + CameraAutoModeOff(); + } + } + + public BoolFeedback CameraAutoModeIsOnFeedback { get; private set; } + + public BoolFeedback SpeakerTrackIsAvailable { get; private set; } } } \ No newline at end of file diff --git a/src/V2/CiscoDirectory.cs b/src/V2/CiscoDirectory.cs new file mode 100644 index 0000000..5238393 --- /dev/null +++ b/src/V2/CiscoDirectory.cs @@ -0,0 +1,305 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronIO; +using PepperDash.Core; +using PepperDash.Core.Intersystem; +using PepperDash.Core.Intersystem.Tokens; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; + +namespace epi_videoCodec_ciscoExtended.V2 +{ + public class CiscoDirectory : CiscoRoomOsFeature, IHasDirectory + { + private int searchinProgress; + + private int phonebookResultsLimit = 10; + private bool phoneBookIsLocal; + private bool currentDirectoryIsNotRoot; + + private readonly CTimer searchTimeout; + private readonly CiscoRoomOsDevice parent; + + private int selectedEntry; + private int selectedEntryCallMethod; + + public CiscoDirectory(CiscoRoomOsDevice parent) : base(parent.Key + "-directory") + { + this.parent = parent; + + SearchIsInProgress = new BoolFeedback("SearchInProgress", () => searchinProgress > 0); + + searchTimeout = new CTimer(_ => + { + Interlocked.Exchange(ref searchinProgress, 0); + SearchIsInProgress.FireUpdate(); + }, Timeout.Infinite); + + DirectoryBrowseHistory = new List(); + PhonebookSyncState = new CodecPhonebookSyncState(parent.Key + "-phonebook-sync"); + DirectoryRoot = new CodecDirectory(); + + CurrentDirectoryResultIsNotDirectoryRoot = new BoolFeedback(() => currentDirectoryIsNotRoot); + } + + public void SearchDirectory(string searchString) + { + if (Interlocked.CompareExchange(ref searchinProgress, 1, 0) == 0) + { + var nameToSearch = string.IsNullOrEmpty(searchString) + ? "\"\"" + : "\"" + searchString + "\""; + + var phonebookMode = phoneBookIsLocal ? "Local" : "Corporate"; + + var command = string.Format( + "xCommand Phonebook Search SearchString: {0} PhonebookType: {1} Limit: {2}", + nameToSearch, phonebookMode, phonebookResultsLimit); + + parent.SendTaggedRequest(command, result => + { + try + { + var newPhonebook = ParsePhonebookSearchResult(result); + CurrentDirectoryResult = newPhonebook; + CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); + + var handler = DirectoryResultReturned; + if (handler == null) + return; + + handler(this, new DirectoryEventArgs { Directory = newPhonebook, DirectoryIsOnRoot = !currentDirectoryIsNotRoot }); + } + catch (Exception ex) + { + Debug.Console(0, parent, "Caught an exception parsing a phonebook result: {0}", ex); + } + finally + { + searchTimeout.Reset(); + } + }); + + searchTimeout.Reset(30000); + SearchIsInProgress.FireUpdate(); + } + } + + public void SearchDirectory(string searchString, Action onResult) + { + var nameToSearch = string.IsNullOrEmpty(searchString) + ? "" + : "\"" + searchString + "\""; + + var phonebookMode = phoneBookIsLocal ? "Local" : "Corporate"; + + var command = string.Format( + "xCommand Phonebook Search SearchString: {0} PhonebookType: {1} Limit: {2}", + nameToSearch, phonebookMode, phonebookResultsLimit); + + parent.SendTaggedRequest(command, result => + { + try + { + var newPhonebook = ParsePhonebookSearchResult(result); + onResult(newPhonebook); + } + catch (Exception ex) + { + Debug.Console(0, parent, "Caught an exception parsing a phonebook result: {0}", ex); + } + }); + } + + private CodecDirectory ParsePhonebookSearchResult(string result) + { + // *r PhonebookSearchResult Contact 1 Name: "Nick" + var directoryResults = new CodecDirectory(); + var items = new Dictionary(); + var folders = new Dictionary(); + + foreach (var line in result.Split('|')) + { + const string pattern = @"\*r PhonebookSearchResult Contact (\d+) (.*?): ""([^""]+)"""; + var match = Regex.Match(line, pattern); + if (!match.Success) + { + continue; + } + + var itemIndex = match.Groups[1].Value; + var property = match.Groups[2].Value; + var value = match.Groups[3].Value; + + DirectoryContact currentItem; + if (!items.TryGetValue(itemIndex, out currentItem)) + { + currentItem = new DirectoryContact { ParentFolderId = "root", FolderId = "", Name = "", Title = "", ContactId = "" }; + items.Add(itemIndex, currentItem); + } + + if (property.Equals("Name")) + { + currentItem.Name = value.Trim(new []{ ' ', '\"' }); + Debug.Console(1, parent, "Directory Item:{0} | Name {1}", itemIndex, value); + } + else if (property.Contains("ContactMethod")) + { + ParseContactMethods(property, value, currentItem); + } + } + + directoryResults.AddContactsToDirectory(items.Values.Cast().ToList()); + directoryResults.AddFoldersToDirectory(folders.Values.Cast().ToList()); + + return directoryResults; + } + + private void ParseContactMethods(string property, string value, DirectoryContact item) + { + try + { + // *r PhonebookSearchResult Contact 13 ContactMethod 1 ContactMethodId: "1" + + Debug.Console(1, parent, "Parsing contact method:{0}", property); + + var index = property.Split(' ')[1]; + + var contactMethod = item.ContactMethods.Find(m => m.ContactMethodId == value); + if (contactMethod == null) + { + contactMethod = new ContactMethod {CallType = eContactMethodCallType.Video, ContactMethodId = index}; + item.ContactMethods.Add(contactMethod); + } + + if (property.Contains("Number")) + { + contactMethod.Number = value; + } + } + catch (Exception ex) + { + Debug.Console(0, parent, "Caught and exception parsing the contact methods:{0} {1}", value, ex); + } + } + + public void GetDirectoryFolderContents(string folderId) + { + // throw new NotImplementedException(); + } + + public void SetCurrentDirectoryToRoot() + { + // throw new NotImplementedException(); + } + + public void GetDirectoryParentFolderContents() + { + // throw new NotImplementedException(); + } + + public BoolFeedback SearchIsInProgress { get; private set; } + + public CodecDirectory DirectoryRoot { get; private set; } + public CodecDirectory CurrentDirectoryResult { get; private set; } + + public CodecPhonebookSyncState PhonebookSyncState { get; private set; } + public BoolFeedback CurrentDirectoryResultIsNotDirectoryRoot { get; private set; } + public List DirectoryBrowseHistory { get; private set; } + + public event EventHandler DirectoryResultReturned; + + public static string UpdateDirectoryXSig(CodecDirectory directory, bool isRoot) + { + const int xSigMaxIndex = 1023; + var tokenArray = new XSigToken[directory.CurrentDirectoryResults.Count > xSigMaxIndex + ? xSigMaxIndex + : directory.CurrentDirectoryResults.Count]; + + var contacts = directory.CurrentDirectoryResults.Count > xSigMaxIndex + ? directory.CurrentDirectoryResults.Take(xSigMaxIndex) + : directory.CurrentDirectoryResults; + + var contactsToDisplay = isRoot + ? contacts.Where(c => c.ParentFolderId == "root") + : contacts.Where(c => c.ParentFolderId != "root"); + + var counterIndex = 1; + foreach (var entry in contactsToDisplay) + { + var arrayIndex = counterIndex - 1; + var entryIndex = counterIndex; + + if (entry is DirectoryFolder) + { + tokenArray[arrayIndex] = new XSigSerialToken(entryIndex, String.Format("[+] {0}", entry.Name)); + counterIndex++; + continue; + } + + tokenArray[arrayIndex] = new XSigSerialToken(entryIndex, entry.Name); + counterIndex++; + } + + return GetXSigString(tokenArray); + } + + public string UpdateContactMethodsXSig(DirectoryContact contact) + { + const int maxMethods = 10; + const int maxStrings = 3; + const int offset = maxStrings; + var stringIndex = 0; + var arrayIndex = 0; + // Create a new token array and set the size to the number of methods times the total number of signals + var tokenArray = new XSigToken[maxMethods * offset]; + + Debug.Console(2, this, "Creating XSIG token array with size {0}", maxMethods * offset); + + // TODO: Add code to generate XSig data + foreach (var method in contact.ContactMethods) + { + if (arrayIndex >= maxMethods * offset) + break; + + //serials + tokenArray[arrayIndex + 1] = new XSigSerialToken(stringIndex + 1, method.Number); + tokenArray[arrayIndex + 2] = new XSigSerialToken(stringIndex + 2, method.ContactMethodId.ToString()); + tokenArray[arrayIndex + 3] = new XSigSerialToken(stringIndex + 3, method.Device.ToString()); + + arrayIndex += offset; + stringIndex += maxStrings; + } + + while (arrayIndex < maxMethods) + { + tokenArray[arrayIndex + 1] = new XSigSerialToken(stringIndex + 1, String.Empty); + tokenArray[arrayIndex + 2] = new XSigSerialToken(stringIndex + 2, String.Empty); + tokenArray[arrayIndex + 3] = new XSigSerialToken(stringIndex + 3, String.Empty); + + arrayIndex += offset; + stringIndex += maxStrings; + } + + return GetXSigString(tokenArray); + } + + private static string GetXSigString(XSigToken[] tokenArray) + { + const int xSigEncoding = 28591; + + using (var s = new MemoryStream()) + using (var tw = new XSigTokenStreamWriter(s, false)) + { + tw.WriteXSigData(tokenArray); + var xSig = s.ToArray(); + return Encoding.GetEncoding(xSigEncoding).GetString(xSig, 0, xSig.Length); + } + } + } +} \ No newline at end of file diff --git a/src/V2/CiscoDoNotDisturb.cs b/src/V2/CiscoDoNotDisturb.cs new file mode 100644 index 0000000..618a7ad --- /dev/null +++ b/src/V2/CiscoDoNotDisturb.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Devices.Common.Codec; + +namespace epi_videoCodec_ciscoExtended.V2 +{ + public class CiscoDoNotDisturb : CiscoRoomOsFeature, IHasPolls, IHasEventSubscriptions, IHandlesResponses, IHasDoNotDisturbMode + { + private readonly CiscoRoomOsDevice parent; + + public CiscoDoNotDisturb(CiscoRoomOsDevice parent) : base(parent.Key + "-DND") + { + this.parent = parent; + + Polls = new List + { + "xStatus Conference DoNotDisturb" + }; + + Subscriptions = new List + { + "Status/Conference/DoNotDisturb" + }; + + DoNotDisturbModeIsOnFeedback = new BoolFeedback("DND", () => doNotDisturbIsOn); + DoNotDisturbModeIsOnFeedback.RegisterForDebug(parent); + } + + private bool doNotDisturbIsOn; + + public IEnumerable Polls { get; private set; } + public IEnumerable Subscriptions { get; private set; } + + public bool HandlesResponse(string response) + { + return response.IndexOf("*s Conference DoNotDisturb", StringComparison.Ordinal) > -1; + } + + public void HandleResponse(string response) + { + var parts = response.Split('|'); + foreach (var line in parts) + { + switch (line) + { + case "*s Conference DoNotDisturb: Inactive": + doNotDisturbIsOn = false; + DoNotDisturbModeIsOnFeedback.FireUpdate(); + break; + case "*s Conference DoNotDisturb: Active": + doNotDisturbIsOn = true; + DoNotDisturbModeIsOnFeedback.FireUpdate(); + break; + } + } + } + + public void ActivateDoNotDisturbMode() + { + parent.SendText("xCommand Conference DoNotDisturb Activate"); + } + + public void DeactivateDoNotDisturbMode() + { + parent.SendText("xCommand Conference DoNotDisturb Deactivate"); + } + + public void ToggleDoNotDisturbMode() + { + if (doNotDisturbIsOn) + { + DeactivateDoNotDisturbMode(); + } + else + { + ActivateDoNotDisturbMode(); + } + } + + public BoolFeedback DoNotDisturbModeIsOnFeedback { get; private set; } + } +} \ No newline at end of file diff --git a/src/V2/CiscoFarEndCamera.cs b/src/V2/CiscoFarEndCamera.cs index 30ac737..a1719a7 100644 --- a/src/V2/CiscoFarEndCamera.cs +++ b/src/V2/CiscoFarEndCamera.cs @@ -1,9 +1,10 @@ using System; +using System.Collections.Generic; using PepperDash.Essentials.Devices.Common.Cameras; -namespace PDT.Plugins.Cisco.RoomOs.V2 +namespace epi_videoCodec_ciscoExtended.V2 { - internal class CiscoFarEndCamera : CameraBase, IHasCameraPtzControl, IAmFarEndCamera + internal class CiscoFarEndCamera : CameraBase, IHasCameraPtzControl, IAmFarEndCamera, IHasCameraPresets { private readonly CiscoRoomOsDevice parent; @@ -11,56 +12,100 @@ public CiscoFarEndCamera(string key, string name, CiscoRoomOsDevice parent) : base(key, name) { this.parent = parent; + Presets = new List(); } public void PanLeft() { - throw new NotImplementedException(); + var command = string.Format("xCommand Call FarEndControl Camera Move Value: Left CallId: {0}", parent.CallStatus.GetActiveCallId()); + parent.SendText(command); } public void PanRight() { - throw new NotImplementedException(); + var command = string.Format("xCommand Call FarEndControl Camera Move Value: Right CallId: {0}", parent.CallStatus.GetActiveCallId()); + parent.SendText(command); } public void PanStop() { - throw new NotImplementedException(); + var command = string.Format("xCommand Call FarEndControl Camera Stop CallId: {0}", parent.CallStatus.GetActiveCallId()); + parent.SendText(command); } - public void TiltDown() + public void TiltUp() { - throw new NotImplementedException(); + var command = string.Format("xCommand Call FarEndControl Camera Move Value: Up CallId: {0}", parent.CallStatus.GetActiveCallId()); + parent.SendText(command); } - public void TiltUp() + public void TiltDown() { - throw new NotImplementedException(); + var command = string.Format("xCommand Call FarEndControl Camera Move Value: Down CallId: {0}", parent.CallStatus.GetActiveCallId()); + parent.SendText(command); } public void TiltStop() { - throw new NotImplementedException(); + //"xCommand Call FarEndControl Camera Stop CallId: {0}" + var command = string.Format("xCommand Call FarEndControl Camera Stop CallId: {0}", parent.CallStatus.GetActiveCallId()); + parent.SendText(command); } public void ZoomIn() { - throw new NotImplementedException(); + var command = string.Format("xCommand Call FarEndControl Camera Move Value: ZoomIn CallId: {0}", parent.CallStatus.GetActiveCallId()); + parent.SendText(command); } public void ZoomOut() { - throw new NotImplementedException(); + var command = string.Format("xCommand Call FarEndControl Camera Move Value: ZoomOut CallId: {0}", parent.CallStatus.GetActiveCallId()); + parent.SendText(command); } public void ZoomStop() { - throw new NotImplementedException(); + var command = string.Format("xCommand Call FarEndControl Camera Stop CallId: {0}", parent.CallStatus.GetActiveCallId()); + parent.SendText(command); } public void PositionHome() { - throw new NotImplementedException(); + + } + + public void PresetSelect(int preset) + { + if (preset == 0) + return; + + // xCommand Call FarEndControl RoomPreset Activate CallId: value ParticipantId: value PresetId: value + var activeCall = parent.CallStatus.GetActiveCallId(); + + var command = + string.Format( + "xCommand Call FarEndControl RoomPreset Activate CallId: {0} PresetId: {1}", activeCall, preset); + + parent.SendText(command); + } + + public void PresetStore(int preset, string description) + { + if (preset == 0) + return; + + // xCommand Call FarEndControl RoomPreset Activate CallId: value ParticipantId: value PresetId: value + var activeCall = parent.CallStatus.GetActiveCallId(); + + var command = + string.Format( + "xCommand Call FarEndControl RoomPreset Store CallId: {0} PresetId: {1}", activeCall, preset); + + parent.SendText(command); } + + public List Presets { get; private set; } + public event EventHandler PresetsListHasChanged; } } \ No newline at end of file diff --git a/src/V2/CiscoNearEndCamera.cs b/src/V2/CiscoNearEndCamera.cs index 3515c6b..099000a 100644 --- a/src/V2/CiscoNearEndCamera.cs +++ b/src/V2/CiscoNearEndCamera.cs @@ -1,11 +1,17 @@ using System; +using System.Collections.Generic; +using PepperDash.Essentials.Core; using PepperDash.Essentials.Devices.Common.Cameras; -namespace PDT.Plugins.Cisco.RoomOs.V2 +namespace epi_videoCodec_ciscoExtended.V2 { - internal class CiscoNearEndCamera : CameraBase, IHasCameraPtzControl, IAmFarEndCamera + // xCommand Camera Ramp CameraId: value Focus: value Pan: value PanSpeed: value Tilt: value TiltSpeed: value Zoom: value ZoomSpeed: value + internal class CiscoNearEndCamera : CameraBase, IHasCameraPtzControl, IHasCameraFocusControl, IHasCameraPresets { public int CameraId { get; private set; } + public int Connector { get; set; } + public bool IsSpeakerTrack { get; set; } + public bool IsPresenterTrack { get; set; } private readonly CiscoRoomOsDevice parent; @@ -13,57 +19,136 @@ public CiscoNearEndCamera(string key, string name, int cameraId, CiscoRoomOsDevi : base(key, name) { CameraId = cameraId; + Presets = new List(); this.parent = parent; } public void PanLeft() { - throw new NotImplementedException(); + var command = string.Format("xCommand Camera Ramp CameraId:{0} Pan: Left PanSpeed: 7", CameraId); + parent.SendText(command); } public void PanRight() { - throw new NotImplementedException(); + var command = string.Format("xCommand Camera Ramp CameraId:{0} Pan: Right PanSpeed: 7", CameraId); + parent.SendText(command); } public void PanStop() { - throw new NotImplementedException(); + var command = string.Format("xCommand Camera Ramp CameraId:{0} Pan: Stop", CameraId); + parent.SendText(command); } - public void TiltDown() + public void TiltUp() { - throw new NotImplementedException(); + var command = string.Format("xCommand Camera Ramp CameraId:{0} Tilt: Up TiltSpeed: 7", CameraId); + parent.SendText(command); } - public void TiltUp() + public void TiltDown() { - throw new NotImplementedException(); + var command = string.Format("xCommand Camera Ramp CameraId:{0} Tilt: Down TiltSpeed: 7", CameraId); + parent.SendText(command); } public void TiltStop() { - throw new NotImplementedException(); + var command = string.Format("xCommand Camera Ramp CameraId:{0} Tilt: Stop", CameraId); + parent.SendText(command); } public void ZoomIn() { - throw new NotImplementedException(); + var command = string.Format("xCommand Camera Ramp CameraId:{0} Zoom: In ZoomSpeed: 7", CameraId); + parent.SendText(command); } public void ZoomOut() { - throw new NotImplementedException(); + var command = string.Format("xCommand Camera Ramp CameraId:{0} Zoom: Out ZoomSpeed: 7", CameraId); + parent.SendText(command); } public void ZoomStop() { - throw new NotImplementedException(); + var command = string.Format("xCommand Camera Ramp CameraId:{0} Zoom: Stop", CameraId); + parent.SendText(command); } public void PositionHome() { - throw new NotImplementedException(); + var command = + string.Format("xCommand Camera Preset ActivateDefaultPosition CameraId:{1}", CameraId); + + parent.SendText(command); + } + + public void FocusNear() + { + var command = string.Format("xCommand Camera Ramp CameraId:{0} Focus: Near", CameraId); + parent.SendText(command); + } + + public void FocusFar() + { + var command = string.Format("xCommand Camera Ramp CameraId:{0} Focus: Far", CameraId); + parent.SendText(command); + } + + public void FocusStop() + { + var command = string.Format("xCommand Camera Ramp CameraId:{0} Focus: Stop", CameraId); + parent.SendText(command); } + + public void TriggerAutoFocus() + { + var command = string.Format("xCommand Camera TriggerAutofocus CameraId: {0}", CameraId); + parent.SendText(command); + } + + public void SetCapabilites(string capabilites) + { + var c = capabilites.ToLower(); + + if (c.Contains("p")) + Capabilities = Capabilities | eCameraCapabilities.Pan; + + if (c.Contains("t")) + Capabilities = Capabilities | eCameraCapabilities.Tilt; + + if (c.Contains("z")) + Capabilities = Capabilities | eCameraCapabilities.Zoom; + + if (c.Contains("f")) + Capabilities = Capabilities | eCameraCapabilities.Focus; + } + + public void PresetSelect(int preset) + { + if (preset == 0) + return; + + var command = + string.Format("xCommand Camera Preset Activate PresetId:{1}", preset); + + parent.SendText(command); + } + + public void PresetStore(int preset, string description) + { + if (preset == 0) + return; + + var command = + string.Format("xCommand Camera Preset Store PresetId:{1} Description: {2}", preset, description); + + parent.SendText(command); + } + + public List Presets { get; private set; } + public event EventHandler PresetsListHasChanged; } } \ No newline at end of file diff --git a/src/V2/CiscoPresentation.cs b/src/V2/CiscoPresentation.cs new file mode 100644 index 0000000..59fa3a7 --- /dev/null +++ b/src/V2/CiscoPresentation.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.DeviceTypeInterfaces; +using PepperDash.Essentials.Devices.Common.Codec; + +namespace epi_videoCodec_ciscoExtended.V2 +{ + /* + * + * *e PresentationStarted Cause: userRequested + *e PresentationStarted ConferenceId: 7 + *e PresentationStarted Mode: Sending + *e PresentationStarted CallId: 1 + *e PresentationStarted LocalInstance: 1 + *e PresentationStarted LocalSource: 2 + ** end + *e PresentationStopped Cause: userRequested + *e PresentationStopped ConferenceId: 7 + *e PresentationStopped Mode: Sending + *e PresentationStopped CallId: 1 + *e PresentationStopped LocalInstance: 1 + ** end + * *s Conference Presentation LocalInstance 1 SendingMode: LocalRemote + *s Conference Presentation LocalInstance 1 Source: 2 + *s Conference Presentation LocalInstance 1 (ghost=True): + */ + + public class CiscoPresentation : CiscoRoomOsFeature, IHasContentSharing, IHasPolls, IHasEventSubscriptions, IHandlesResponses, IHasFarEndContentStatus + { + public class CiscoPresentationInfo + { + public string SendingMode { get; set; } + public int LocalInstance { get; set; } + public string LocalSource { get; set; } + } + + public enum CiscoPresentationMode + { + Off, Sending, Receiving + } + + private readonly IDictionary presentations; + + private readonly CiscoRoomOsDevice parent; + + private readonly bool defaultToLocalOnly; + + private string shareSourceId; + + private bool isReceivingContent; + + public CiscoPresentation(CiscoRoomOsDevice parent, bool defaultToLocalOnly) : this(parent) + { + this.defaultToLocalOnly = defaultToLocalOnly; + } + + public CiscoPresentation(CiscoRoomOsDevice parent) : base(parent + "-Presentation") + { + this.parent = parent; + + presentations = new Dictionary(); + + Polls = new List + { + "xStatus Conference Presentation", + "xConfiguration Video Presentation DefaultSource" + }; + + Subscriptions = new List + { + //"Event/PresentationStarted", + //"Event/PresentationStopped", + "Status/Conference/Presentation", + }; + + SharingContentIsOnFeedback = new BoolFeedback("SharingActive", () => presentations.Any()); + SharingSourceFeedback = new StringFeedback("SharingSource", () => + { + var firstPresentation = presentations.Values.FirstOrDefault(); + return firstPresentation != null + ? firstPresentation.LocalSource + : "None"; + }); + ReceivingContent = new BoolFeedback("IsReceivingContent", () => isReceivingContent); + + SharingSourceIntFeedback = new IntFeedback(() => + { + if (presentations.DefaultIfEmpty() == null) + { + return 0; + } + + switch (SharingSourceFeedback.StringValue) + { + case "2": + return 2; + case "3": + return 3; + case "Airplay": + return 4; + default: + return 0; + } + }); + + SharingSourceFeedback.RegisterForDebug(parent); + SharingContentIsOnFeedback.RegisterForDebug(parent); + ReceivingContent.RegisterForDebug(parent); + + AutoShareContentWhileInCall = false; + } + + public IEnumerable Polls { get; private set; } + + public IEnumerable Subscriptions { get; private set; } + + public bool HandlesResponse(string response) + { + // *s Conference Presentation Mode: Sending + return response.IndexOf("*s Conference Presentation ", StringComparison.Ordinal) > -1; + } + + public void HandleResponse(string response) + { + if (response.StartsWith("*s Conference Presentation LocalInstance ")) + { + isReceivingContent = false; + ParseLocalInstanceResponse(response); + } + else if (response == "*s Conference Presentation Mode: Receiving") + { + isReceivingContent = true; + presentations.Clear(); + } + else if (response == "*s Conference Presentation Mode: Off") + { + isReceivingContent = false; + presentations.Clear(); + } + else + { + + } + + ReceivingContent.FireUpdate(); + SharingContentIsOnFeedback.FireUpdate(); + SharingSourceFeedback.FireUpdate(); + SharingSourceIntFeedback.FireUpdate(); + } + + private void ParseLocalInstanceResponse(string response) + { + const string pattern = @"LocalInstance (\d+)"; + var parts = response.Split('|'); + var firstLine = parts[0]; + + var match = Regex.Match(firstLine, pattern); + if (match.Success) + { + var localInstance = Convert.ToInt32(match.Groups[1].Value); + if (firstLine.Contains("(ghost=True)")) + { + presentations.Remove(localInstance); + } + else + { + CiscoPresentationInfo info; + if (!presentations.TryGetValue(localInstance, out info)) + { + info = new CiscoPresentationInfo {LocalInstance = localInstance}; + presentations.Add(localInstance, info); + } + + foreach (var line in parts) + { + if (line.Contains("SendingMode: LocalRemote")) + { + info.SendingMode = "LocalRemote"; + } + else if (line.Contains("SendingMode: Local")) + { + info.SendingMode = "Local"; + } + else if (line.Contains("Source: 2")) + { + info.LocalSource = "2"; + } + else if (line.Contains("Source: 3")) + { + info.LocalSource = "3"; + } + } + } + } + } + + public void StartSharing() + { + // xCommand Presentation Start ConnectorId: value Instance: value Layout: value PresentationSource: value SendingMode: value + // SendingMode + // LocalRemote, LocalOnly Default: LocalRemote + + var command = ""; + + if (string.IsNullOrEmpty(shareSourceId)) + { + command = string.Format("xCommand Presentation Start SendingMode: {0}", + defaultToLocalOnly ? "LocalOnly" : "LocalRemote"); + } + else + { + command = string.Format("xCommand Presentation Start PresentationSource: {0} SendingMode: {1}", + shareSourceId, defaultToLocalOnly ? "LocalOnly" : "LocalRemote"); + } + + parent.SendText(command); + } + + public void StopSharing() + { + const string command = "xCommand Presentation Stop"; + parent.SendText(command); + } + + public void SetShareSource(int sourceId) + { + Debug.Console(1, parent, "Attempting to set share source to:{0}", sourceId); + + if (sourceId > 0 && sourceId < 4) + { + shareSourceId = Convert.ToString(sourceId); + } + else + { + Debug.Console(1, parent, "Invalid share source id:{0} || for now valid choices are 2 and 3", sourceId); + } + } + + public BoolFeedback SharingContentIsOnFeedback { get; private set; } + public IntFeedback SharingSourceIntFeedback { get; private set; } + public StringFeedback SharingSourceFeedback { get; private set; } + public bool AutoShareContentWhileInCall { get; private set; } + public BoolFeedback ReceivingContent { get; private set; } + } +} \ No newline at end of file diff --git a/src/V2/CiscoRecents.cs b/src/V2/CiscoRecents.cs new file mode 100644 index 0000000..5f89553 --- /dev/null +++ b/src/V2/CiscoRecents.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; + +namespace epi_videoCodec_ciscoExtended.V2 +{ + + public class CiscoRecents : CiscoRoomOsFeature, IHasEventSubscriptions, IHandlesResponses, IHasCallHistory, IHasPolls + { + private readonly CiscoRoomOsDevice parent; + private readonly CodecCallHistory callhistory; + + private int selectedRecent; + public int SelectedRecent + { + get { return selectedRecent; } + set + { + selectedRecent = value; + SelectedRecentName.FireUpdate(); + SelectedRecentNumber.FireUpdate(); + } + } + + internal readonly List Feedbacks; + internal readonly StringFeedback SelectedRecentName; + internal readonly StringFeedback SelectedRecentNumber; + + + public CiscoRecents(CiscoRoomOsDevice parent) : base (parent.Key + "-Recents") + { + this.parent = parent; + + this.parent.CallStatus.CallIsConnectedOrConnecting.OutputChange += (sender, args) => + { + if (args.BoolValue) + { + return; + } + + parent.SendText("xCommand CallHistory Get Limit:10"); + }; + + Polls = new List + { + "xCommand CallHistory Get Limit:10" + }; + + Subscriptions = new List(); + callhistory = new CodecCallHistory(); + + Feedbacks = new List(); + for (var x = 0; x < 10; ++x) + { + var index = x; + Feedbacks.Add(new StringFeedback("Recent:" + index, () => + { + var recent = callhistory.RecentCalls.ElementAt(index); + return recent == null ? string.Empty : recent.Name; + })); + } + + callhistory.RecentCallsListHasChanged += (sender, args) => + { + foreach (var feedback in Feedbacks) + { + feedback.FireUpdate(); + } + }; + + SelectedRecentName = new StringFeedback("SelectedRecentName", () => + { + if (SelectedRecent == 0) + { + return string.Empty; + } + + var result = callhistory.RecentCalls.ElementAtOrDefault(SelectedRecent - 1); + return result == null ? string.Empty : result.Name; + }); + + SelectedRecentNumber = new StringFeedback("SelectedRecentName", () => + { + if (SelectedRecent == 0) + { + return string.Empty; + } + + var result = callhistory.RecentCalls.ElementAtOrDefault(SelectedRecent - 1); + return result == null ? string.Empty : result.Number; + }); + } + + public IEnumerable Subscriptions { get; private set; } + + public bool HandlesResponse(string response) + { + return response.IndexOf("*r CallHistoryGetResult", StringComparison.Ordinal) > -1; + } + + public void HandleResponse(string response) + { + var historyItems = new Dictionary(); + foreach (var line in response.Split('|')) + { + const string pattern = @"\*r CallHistoryGetResult Entry (\d+) (.*?): (.+)"; + + var match = Regex.Match(line, pattern); + if (!match.Success) + { + continue; + } + + var itemIndex = match.Groups[1].Value; + var property = match.Groups[2].Value; + var value = match.Groups[3].Value.Trim(new []{ ' ', '\"' }); + + CiscoCallHistory.Entry currentItem; + if (!historyItems.TryGetValue(itemIndex, out currentItem)) + { + currentItem = new CiscoCallHistory.Entry { id = itemIndex }; + historyItems.Add(itemIndex, currentItem); + } + + /**r CallHistoryGetResult (status=OK): +*r CallHistoryGetResult Entry 0 CallHistoryId: 73 +*r CallHistoryGetResult Entry 0 CallbackNumber: "" +*r CallHistoryGetResult Entry 0 DisplayName: "" +*r CallHistoryGetResult Entry 0 StartTime: "2024-01-20T20:14:03" +*r CallHistoryGetResult Entry 0 DaysAgo: 0 +*r CallHistoryGetResult Entry 0 OccurrenceType: Placed +*r CallHistoryGetResult Entry 0 IsAcknowledged: Acknowledged +*r CallHistoryGetResult Entry 1 CallHistoryId: 72 +*r CallHistoryGetResult Entry 1 CallbackNumber: "" +*r CallHistoryGetResult Entry 1 DisplayName: "" +*r CallHistoryGetResult Entry 1 StartTime: "" +*r CallHistoryGetResult Entry 1 DaysAgo: 0*/ + + if (property.Equals("CallbackNumber")) + { + currentItem.CallbackNumber = new CiscoCallHistory.CallbackNumber { Value = value }; + Debug.Console(1, parent, "Recents Item:{0} | CallbackNumber {1}", itemIndex, value); + } + else if (property.Contains("DisplayName")) + { + currentItem.DisplayName = new CiscoCallHistory.DisplayName { Value = value }; + Debug.Console(1, parent, "Recents Item:{0} | DisplayName {1}", itemIndex, value); + } + else if (property.Contains("StartTime")) + { + currentItem.LastOccurrenceStartTime = new CiscoCallHistory.LastOccurrenceStartTime { Value = DateTime.Parse(value) }; + Debug.Console(1, parent, "Recents Item:{0} | StartTime {1}", itemIndex, value); + } + else if (property.Contains("DaysAgo")) + { + currentItem.LastOccurrenceDaysAgo = new CiscoCallHistory.LastOccurrenceDaysAgo { Value = value }; + Debug.Console(1, parent, "Recents Item:{0} | DaysAgo {1}", itemIndex, value); + } + else if (property.Contains("IsAcknowledged")) + { + currentItem.IsAcknowledged = new CiscoCallHistory.IsAcknowledged { Value = value }; + Debug.Console(1, parent, "Recents Item:{0} | IsAcknowledged {1}", itemIndex, value); + } + else if (property.Contains("OccurrenceType")) + { + currentItem.OccurrenceType = new CiscoCallHistory.OccurrenceType { Value = value }; + Debug.Console(1, parent, "Recents Item:{0} | OccurrenceType {1}", itemIndex, value); + } + else + { + currentItem.LastOccurrenceHistoryId = new CiscoCallHistory.LastOccurrenceHistoryId {Value = itemIndex}; + } + } + + callhistory.ConvertCiscoCallHistoryToGeneric(historyItems.Values.ToList()); + } + + public void RemoveCallHistoryEntry(CodecCallHistory.CallHistoryEntry entry) + { + var command = string.Format( + "xCommand CallHistory DeleteEntry CallHistoryId: {0} AcknowledgeConsecutiveDuplicates: True", + entry.OccurrenceHistoryId); + + parent.SendText(command); + } + + public CodecCallHistory CallHistory + { + get + { + return callhistory; + } + } + + public IEnumerable Polls { get; private set; } + } +} \ No newline at end of file diff --git a/src/V2/CiscoRoomOsDevice.cs b/src/V2/CiscoRoomOsDevice.cs index e11eb04..b0aff7b 100644 --- a/src/V2/CiscoRoomOsDevice.cs +++ b/src/V2/CiscoRoomOsDevice.cs @@ -5,34 +5,30 @@ using System.Text.RegularExpressions; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; -using epi_videoCodec_ciscoExtended; -using epi_videoCodec_ciscoExtended.V2; +using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Core; +using PepperDash.Core.Intersystem; using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Devices.Common.Cameras; +using PepperDash.Essentials.Devices.Common.Codec; -namespace PDT.Plugins.Cisco.RoomOs.V2 +namespace epi_videoCodec_ciscoExtended.V2 { - public class CiscoRoomOsPluginFactory : EssentialsPluginDeviceFactory - { - public CiscoRoomOsPluginFactory() - { - TypeNames = new List {"ciscoRoomOsV2"}; - } - - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - return new CiscoRoomOsDevice(dc); - } - } - - public class CiscoRoomOsDevice : EssentialsDevice, IHasCodecCameras, IOnline, ICommunicationMonitor + public class CiscoRoomOsDevice : EssentialsDevice, IHasCodecCameras, IOnline, ICommunicationMonitor, IBridgeAdvanced, IBasicVolumeWithFeedback, IPrivacy, IHasDoNotDisturbMode { public const string Delimiter = "\r\n"; internal readonly CiscoCameras CodecCameras; - internal readonly CiscoStandby CodecStandby; + internal readonly CiscoStandby Standby; + internal readonly CiscoCallStatus CallStatus; + internal readonly CiscoPresentation Presentation; + internal readonly CiscoAudio Audio; + internal readonly CiscoDirectory Directory; + internal readonly CiscoRecents Recents; + internal readonly CiscoDoNotDisturb DoNotDisturb; + internal readonly CiscoSelfView SelfView; private readonly IBasicCommunication communications; private readonly IList features = new List(); @@ -50,6 +46,7 @@ public CiscoRoomOsDevice(DeviceConfig config) : base(config.Key, config.Name) password = props.Password ?? string.Empty; communications = CommFactory.CreateCommForDevice(config); + var gather = new CommunicationGather(communications, Delimiter); gather.LineReceived += GatherOnLineReceived; @@ -63,24 +60,38 @@ public CiscoRoomOsDevice(DeviceConfig config) : base(config.Key, config.Name) } else { - const string pollString = "xStatus SystemUnit\r"; + const string pollString = "xStatus SystemUnit Hardware\r"; CommunicationMonitor = new GenericCommunicationMonitor( this, communications, - 30000, - 120000, - 300000, + 10000, + 600000, + 1200000, pollString); } CodecCameras = new CiscoCameras(this); - CodecCameras.CameraSelected += CameraSelected; - - CodecStandby = new CiscoStandby(this); + Standby = new CiscoStandby(this); + CallStatus = new CiscoCallStatus(this); + Presentation = new CiscoPresentation(this); + Audio = new CiscoAudio(this); + Directory = new CiscoDirectory(this); + Recents = new CiscoRecents(this); + DoNotDisturb = new CiscoDoNotDisturb(this); + SelfView = new CiscoSelfView(this); features.Add(CodecCameras); - features.Add(CodecStandby); + features.Add(Standby); + features.Add(CallStatus); + features.Add(Presentation); + features.Add(Audio); + features.Add(Directory); + features.Add(DoNotDisturb); + features.Add(SelfView); + features.Add(Recents); + + CodecCameras.CameraSelected += CameraSelected; } private void GatherOnLineReceived(object sender, GenericCommMethodReceiveTextArgs args) @@ -96,7 +107,7 @@ private void GatherOnLineReceived(object sender, GenericCommMethodReceiveTextArg } if ( - (data.StartsWith("*s SystemUnit") || data.Contains("*r Login successful")) + (data.StartsWith("*s SystemUnit") || data.StartsWith("*r Login successful")) && !isLoggedIn && (communications as ISocketStatus) == null) // RS232 Login Sucessful { @@ -107,17 +118,23 @@ private void GatherOnLineReceived(object sender, GenericCommMethodReceiveTextArg handler(this, EventArgs.Empty); } - if (data.Contains("login:")) + if (data == "** end") { - // handles login for non-delimited + var dataToProcess = buffer.ToString(); + buffer = new StringBuilder(); + + if (!string.IsNullOrEmpty(dataToProcess)) + { + ProcessResponse(dataToProcess); + } } - else if (data.Contains("{") || data.Contains("}")) + else if (data.Contains("login:") || data.Contains("Password:")) { - SendText("xPreferences outputmode terminal"); + // handles login for non-delimited } - else if (data.Contains("Password:")) + else if (data.StartsWith("{") || data.EndsWith("}")) { - // handles login for non-delimited + SendText("xPreferences outputmode terminal"); } else if (data.StartsWith("tshell:")) { @@ -132,12 +149,6 @@ private void GatherOnLineReceived(object sender, GenericCommMethodReceiveTextArg { Debug.Console(0, this, "Incorrect Login"); } - else if (data == "** end") - { - var dataToProcess = buffer.ToString(); - buffer = new StringBuilder(); - ProcessResponse(dataToProcess); - } else { buffer.Append(data); @@ -147,53 +158,31 @@ private void GatherOnLineReceived(object sender, GenericCommMethodReceiveTextArg private void ProcessResponse(string response) { - const string pattern = @"resultId:\s*\""(?\d+)\"""; + const string pattern = @"resultId:\s*\""(?[a-fA-F0-9]+)\"""; - var match = Regex.Match(response, pattern); - if (match.Success) + try { - var result = match.Groups["id"].Value; - var resultId = Int32.Parse(result); - - Debug.Console(0, this, "Found a result id:{0}", resultId); - - Action callback; - if (requestCallbacks.TryGetValue(resultId, out callback)) + var match = Regex.Match(response, pattern); + if (match.Success) { - try - { - callback(response); - } - catch (Exception ex) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, - "Caught an exception handling a response:{0}", ex); - } - finally - { - requestCallbacks.Remove(resultId); - } + var result = match.Groups["id"].Value; + var resultId = result.Trim(); + + Debug.Console(2, this, "Found a result id:{0}", resultId); + HandleResponse(resultId, response); } else { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, - "Couldn't find a matching callback for resultId:{0}", resultId); - } - } - else - { - foreach (var handler in features.Where(feature => feature.HandlesResponse(response))) - { - try + foreach (var handler in features.OfType().Where(feature => feature.HandlesResponse(response))) { handler.HandleResponse(response); } - catch (Exception ex) - { - Debug.Console(0, handler, Debug.ErrorLogLevel.Notice, "Caught and exception handling a response:{0}", ex); - } } } + catch (Exception ex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Caught an exception handling a response{0} {1}", response, ex); + } } public void SelectCamera(string key) @@ -248,7 +237,7 @@ public override void Initialize() if (args.Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) { - pollTimer.Reset(250, 60000); + pollTimer.Reset(250, 30000); } else { @@ -263,7 +252,7 @@ public override void Initialize() else { communications.TextReceived += CommunicationsOnTextReceived; - Rs232LoggedIn += (sender, args) => pollTimer.Reset(250, 60000); + Rs232LoggedIn += (sender, args) => pollTimer.Reset(250, 30000); CommunicationMonitor.Start(); } @@ -296,7 +285,7 @@ private void CommunicationsOnTextReceived(object sender, GenericCommMethodReceiv else if (data.Contains("Password:")) { isLoggedIn = false; - if (string.IsNullOrEmpty(username)) + if (string.IsNullOrEmpty(password)) { Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Prompted for a password but none is configured"); } @@ -312,15 +301,99 @@ public void SendText(string text) if (string.IsNullOrEmpty(text)) return; - const string delimiter = "\r"; - var textToSend = text.Trim() + delimiter; + var textToSend = PreProcessStringToSend(text); communications.SendText(textToSend); } + class CiscoRoomOsRequestHandler + { + private readonly Action action; + public readonly DateTime At; + + public CiscoRoomOsRequestHandler(Action action) + { + this.action = action; + At = DateTime.UtcNow; + } + + public void HandleResponse(string response) + { + action(response); + } + } + + private readonly CCriticalSection requestSync = new CCriticalSection(); + private readonly Dictionary requestCallbacks = new Dictionary(); + + public void SendTaggedRequest(string request) + { + SendTaggedRequest(request, (s => Debug.Console(1, this, "Unhandled response:{0}", s))); + } + + public void SendTaggedRequest(string request, Action onResponse) + { + var requestId = Guid.NewGuid().ToString("N"); + var textToSend = request.Trim() + ("|resultid=" + requestId); + + Action callback = response => + { + var cb = onResponse ?? (s => Debug.Console(1, this, "Unhandled response:{0}", s)); + try + { + cb(response); + } + catch (Exception ex) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Caught an exception in a callback for{0} {1}", request, ex); + } + }; + + requestSync.Enter(); + try + { + requestCallbacks.Add(requestId, new CiscoRoomOsRequestHandler(callback)); + } + finally + { + requestSync.Leave(); + } + + SendText(textToSend); + } + + public void HandleResponse(string resultId, string response) + { + CiscoRoomOsRequestHandler handler; + requestSync.Enter(); + try + { + if (!requestCallbacks.TryGetValue(resultId, out handler)) + { + Debug.Console(0, this, "Unhandled response:{0}", response); + return; + } + + requestCallbacks.Remove(resultId); + } + finally + { + requestSync.Leave(); + } + + handler.HandleResponse(response); + } + + public static string PreProcessStringToSend(string text) + { + const string delimiter = "\r"; + return text.Trim() + delimiter; + } + public void PollFeatures() { - var polls = from feature in features - from poll in feature.Polls + var polls = + from feature in features.OfType() + from poll in feature.Polls.Distinct() select poll; foreach (var item in polls) @@ -333,61 +406,1850 @@ public void PollForFeedback() { Action parseFeedbackRequest = response => { - var feedbackSubscriptions = from feature in features - from sub in feature.Subscriptions + var feedbackSubscriptions = + from feature in features.OfType() + from sub in feature.Subscriptions.Distinct() select sub; foreach (var subscription in feedbackSubscriptions .Where(sub => response.IndexOf(sub, StringComparison.OrdinalIgnoreCase) < 0)) { - Debug.Console(1, this, "Registering for feedback:{0}", subscription); + Debug.Console(0, this, "Registering for feedback:{0}", subscription); SendText("xFeedback register " + subscription); } }; - SendRequest("xFeedback list", parseFeedbackRequest); + SendTaggedRequest("xFeedback list", parseFeedbackRequest); } - private static int lastRequestId; - - private readonly Dictionary> requestCallbacks = new Dictionary>(); - - private void SendRequest(string request) + public BoolFeedback IsOnline { - SendRequest(request, (s => Debug.Console(1, this, "Unhandled response:{0}", s))); + get { return CommunicationMonitor.IsOnlineFeedback; } } - private void SendRequest(string request, Action onResponse) + public StatusMonitorBase CommunicationMonitor { get; private set; } + + public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) { - var requestId = Interlocked.Increment(ref lastRequestId); - var textToSend = request.Trim() + ("|resultid=" + requestId); - var callback = onResponse ?? (s => Debug.Console(0, this, "Unhandled response:{0}", s)); + var joinMap = new CiscoBaseJoinMap(joinStart); + if (bridge != null) + bridge.AddJoinMap(Key, joinMap); - requestCallbacks.Add(requestId, callback); + IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); - CTimer timer = null; - timer = new CTimer(_ => + trilist.SetSigTrueAction(joinMap.DtmfPound.JoinNumber, () => CallStatus.SendDtmf("#")); + trilist.SetSigTrueAction(joinMap.DtmfStar.JoinNumber, () => CallStatus.SendDtmf("*")); + + for (uint x = 0; x < joinMap.DtmfJoins.JoinSpan; ++x) { - if (requestCallbacks.ContainsKey(requestId)) + var joinNumber = joinMap.DtmfJoins.JoinNumber + x; + var dtmfNumber = Convert.ToString(x + 1); + if (dtmfNumber == "10") + { + Debug.Console(1, this, "Linkig DTMF:{0} to Join:{1}", "0", joinNumber); + trilist.SetSigTrueAction(joinNumber, () => CallStatus.SendDtmf("0")); + } + else { - Debug.Console(1, this, "Removing request due to timeout:{0}", requestId); - requestCallbacks.Remove(requestId); + Debug.Console(1, this, "Linkig DTMF:{0} to Join:{1}", dtmfNumber, joinNumber); + trilist.SetSigTrueAction(joinNumber, () => CallStatus.SendDtmf(dtmfNumber)); } + } - if (timer != null) - timer.Dispose(); + trilist.SetSigTrueAction(joinMap.EndAllCalls.JoinNumber, () => CallStatus.EndAllCalls()); - }, 120000); + CallStatus.CallIsConnectedOrConnecting.LinkInputSig(trilist.BooleanInput[joinMap.CallIsConnectedOrConnecting.JoinNumber]); + CallStatus.CallIsIncoming.LinkInputSig(trilist.BooleanInput[joinMap.CallIsIncoming.JoinNumber]); + CallStatus.CallStatusXSig.LinkInputSig(trilist.StringInput[joinMap.CallStatusXSig.JoinNumber]); + CallStatus.IncomingCallName.LinkInputSig(trilist.StringInput[joinMap.IncomingName.JoinNumber]); + CallStatus.IncomingCallNumber.LinkInputSig(trilist.StringInput[joinMap.IncomingNumber.JoinNumber]); + CallStatus.NumberOfActiveCalls.LinkInputSig(trilist.UShortInput[joinMap.NumberOfActiveCalls.JoinNumber]); - SendText(textToSend); - } + trilist.SetSigTrueAction(joinMap.AnswerIncoming.JoinNumber, () => CallStatus.AcceptCall()); + trilist.SetSigTrueAction(joinMap.RejectIncoming.JoinNumber, () => CallStatus.RejectCall()); + trilist.SetSigTrueAction(joinMap.JoinAllCalls.JoinNumber, () => CallStatus.JoinAllCalls()); + trilist.SetSigTrueAction(joinMap.HoldAllCalls.JoinNumber, () => CallStatus.HoldAllCalls()); + trilist.SetSigTrueAction(joinMap.ResumeAllCalls.JoinNumber, () => CallStatus.ResumeAllCalls()); - public BoolFeedback IsOnline - { - get { return CommunicationMonitor.IsOnlineFeedback; } - } + trilist.SetSigTrueAction(joinMap.NearEndPresentationStart.JoinNumber, () => Presentation.StartSharing()); + trilist.SetSigTrueAction(joinMap.NearEndPresentationStop.JoinNumber, () => Presentation.StopSharing()); - public StatusMonitorBase CommunicationMonitor { get; private set; } + trilist.SetSigTrueAction(joinMap.StandbyOn.JoinNumber, () => Standby.StandbyActivate()); + trilist.SetSigTrueAction(joinMap.StandbyOff.JoinNumber, () => Standby.StandbyDeactivate()); + Standby.StandbyIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.StandbyOn.JoinNumber]); + Standby.StandbyIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.StandbyOn.JoinNumber]); + Standby.EnteringStandbyModeFeedback.LinkInputSig(trilist.BooleanInput[joinMap.EnteringStandby.JoinNumber]); + + trilist.SetSigTrueAction(joinMap.MicMuteOn.JoinNumber, PrivacyModeOn); + trilist.SetSigTrueAction(joinMap.MicMuteOff.JoinNumber, PrivacyModeOff); + trilist.SetSigTrueAction(joinMap.MicMuteToggle.JoinNumber, PrivacyModeToggle); + + trilist.SetBoolSigAction(joinMap.VolumeUp.JoinNumber, VolumeUp); + trilist.SetBoolSigAction(joinMap.VolumeDown.JoinNumber, VolumeDown); + trilist.SetSigTrueAction(joinMap.VolumeMuteOn.JoinNumber, MuteOn); + trilist.SetSigTrueAction(joinMap.VolumeMuteOff.JoinNumber, MuteOff); + trilist.SetSigTrueAction(joinMap.VolumeMuteToggle.JoinNumber, MuteToggle); + trilist.SetUShortSigAction(joinMap.Volume.JoinNumber, SetVolume); + VolumeLevelFeedback.LinkInputSig(trilist.UShortInput[joinMap.Volume.JoinNumber]); + + MuteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.VolumeMuteOn.JoinNumber]); + MuteFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.VolumeMuteOff.JoinNumber]); + + PrivacyModeIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.MicMuteOn.JoinNumber]); + PrivacyModeIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.MicMuteOff.JoinNumber]); + + trilist.SetSigTrueAction(joinMap.SpeakerTrackEnabled.JoinNumber, () => CodecCameras.CameraAutoModeOn()); + trilist.SetSigTrueAction(joinMap.SpeakerTrackDisabled.JoinNumber, () => CodecCameras.CameraAutoModeOff()); + CodecCameras.SpeakerTrackIsAvailable.LinkInputSig(trilist.BooleanInput[joinMap.SpeakerTrackAvailable.JoinNumber]); + + for (uint x = 0; x < joinMap.HangUpCall.JoinSpan; ++x) + { + var joinNumber = joinMap.HangUpCall.JoinNumber + x; + var index = (int) x; + + Debug.Console(1, this, "Linking End Call:{0} to Join:{1}", index, joinNumber); + trilist.SetSigTrueAction(joinNumber, () => + { + var call = CallStatus.ActiveCalls.ElementAtOrDefault(index); + if (call != null) + { + CallStatus.EndCall(call); + } + }); + } + + for (uint x = 0; x < joinMap.JoinCall.JoinSpan; ++x) + { + var joinNumber = joinMap.JoinCall.JoinNumber + x; + var index = (int)x; + + Debug.Console(1, this, "Linking Join Call:{0} to Join:{1}", index, joinNumber); + trilist.SetSigTrueAction(joinNumber, () => + { + var call = CallStatus.ActiveCalls.ElementAtOrDefault(index); + if (call != null) + { + CallStatus.JoinCall(call); + } + }); + } + + for (uint x = 0; x < joinMap.HoldCall.JoinSpan; ++x) + { + var joinNumber = joinMap.HoldCall.JoinNumber + x; + var index = (int)x; + + Debug.Console(1, this, "Linking HoldCall:{0} to Join:{1}", index, joinNumber); + trilist.SetSigTrueAction(joinNumber, () => + { + var call = CallStatus.ActiveCalls.ElementAtOrDefault(index); + if (call != null) + { + CallStatus.HoldCall(call); + } + }); + } + + for (uint x = 0; x < joinMap.ResumeCall.JoinSpan; ++x) + { + var joinNumber = joinMap.ResumeCall.JoinNumber + x; + var index = (int)x; + + Debug.Console(1, this, "Linking ResumeCall:{0} to Join:{1}", index, joinNumber); + trilist.SetSigTrueAction(joinNumber, () => + { + var call = CallStatus.ActiveCalls.ElementAtOrDefault(index); + if (call != null) + { + CallStatus.ResumeCall(call); + } + }); + } + + Action selectCameraFeedback = camera => + { + if (camera == null) + { + return; + } + + var selectedCameraIndex = CodecCameras.Cameras.IndexOf(camera) + 1; + trilist.SetUshort(joinMap.CameraSelect.JoinNumber, (ushort) selectedCameraIndex); + }; + + CodecCameras.CameraSelected += (sender, args) => selectCameraFeedback(args.SelectedCamera); + selectCameraFeedback(CodecCameras.SelectedCamera); + + trilist.SetUShortSigAction(joinMap.CameraPresetActivate.JoinNumber, value => + { + var camera = CodecCameras.SelectedCamera as IHasCameraPresets; + if (camera != null) + { + camera.PresetSelect(value); + } + }); + + trilist.SetUShortSigAction(joinMap.FarEndCameraPresetActivate.JoinNumber, value => + { + var camera = CodecCameras.FarEndCamera as IHasCameraPresets; + if (camera != null) + { + camera.PresetSelect(value); + } + }); + + trilist.SetUShortSigAction(joinMap.CameraPresetStore.JoinNumber, value => + { + var camera = CodecCameras.SelectedCamera as IHasCameraPresets; + if (camera != null) + { + camera.PresetStore(value, "preset" + value); + } + }); + + trilist.SetUShortSigAction(joinMap.CameraSelect.JoinNumber, value => + { + if (value == 0) + { + return; + } + + var cameraToSelect = CodecCameras.Cameras.ElementAtOrDefault(value - 1); + if (cameraToSelect != null) + { + CodecCameras.SelectCamera(cameraToSelect.Key); + } + }); + + trilist.SetBoolSigAction(joinMap.NearEndCameraUp.JoinNumber, value => + { + var selectedCamera = CodecCameras.SelectedCamera as IHasCameraPtzControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.TiltUp(); + } + else + { + selectedCamera.TiltStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.NearEndCameraDown.JoinNumber, value => + { + var selectedCamera = CodecCameras.SelectedCamera as IHasCameraPtzControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.TiltDown(); + } + else + { + selectedCamera.TiltStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.NearEndCameraLeft.JoinNumber, value => + { + var selectedCamera = CodecCameras.SelectedCamera as IHasCameraPtzControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.PanLeft(); + } + else + { + selectedCamera.PanStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.NearEndCameraRight.JoinNumber, value => + { + var selectedCamera = CodecCameras.SelectedCamera as IHasCameraPtzControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.PanRight(); + } + else + { + selectedCamera.PanStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.NearEndCameraZoomIn.JoinNumber, value => + { + var selectedCamera = CodecCameras.SelectedCamera as IHasCameraPtzControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.ZoomIn(); + } + else + { + selectedCamera.ZoomStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.NearEndCameraZoomOut.JoinNumber, value => + { + var selectedCamera = CodecCameras.SelectedCamera as IHasCameraPtzControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.ZoomOut(); + } + else + { + selectedCamera.ZoomStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.NearEndCameraFocusIn.JoinNumber, value => + { + var selectedCamera = CodecCameras.SelectedCamera as IHasCameraFocusControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.FocusNear(); + } + else + { + selectedCamera.FocusStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.NearEndCameraFocusOut.JoinNumber, value => + { + var selectedCamera = CodecCameras.SelectedCamera as IHasCameraFocusControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.FocusFar(); + } + else + { + selectedCamera.FocusStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.FarEndCameraUp.JoinNumber, value => + { + var selectedCamera = CodecCameras.FarEndCamera as IHasCameraPtzControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.TiltUp(); + } + else + { + selectedCamera.TiltStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.FarEndCameraDown.JoinNumber, value => + { + var selectedCamera = CodecCameras.FarEndCamera as IHasCameraPtzControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.TiltDown(); + } + else + { + selectedCamera.TiltStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.FarEndCameraLeft.JoinNumber, value => + { + var selectedCamera = CodecCameras.FarEndCamera as IHasCameraPtzControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.PanLeft(); + } + else + { + selectedCamera.PanStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.FarEndCameraRight.JoinNumber, value => + { + var selectedCamera = CodecCameras.FarEndCamera as IHasCameraPtzControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.PanRight(); + } + else + { + selectedCamera.PanStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.FarEndCameraZoomIn.JoinNumber, value => + { + var selectedCamera = CodecCameras.FarEndCamera as IHasCameraPtzControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.ZoomIn(); + } + else + { + selectedCamera.ZoomStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.FarEndCameraZoomOut.JoinNumber, value => + { + var selectedCamera = CodecCameras.FarEndCamera as IHasCameraPtzControl; + if (selectedCamera == null) + { + return; + } + + if (value) + { + selectedCamera.ZoomOut(); + } + else + { + selectedCamera.ZoomStop(); + } + }); + + var dialNumber = string.Empty; + + trilist.SetStringSigAction(joinMap.DialNumber.JoinNumber, s => + { + dialNumber = s; + }); + + trilist.SetSigTrueAction(joinMap.ManualDial.JoinNumber, () => CallStatus.Dial(dialNumber)); + + const int xSigEncoding = 28591; + + Directory.DirectoryResultReturned += (sender, args) => + { + var argsCount = args.DirectoryIsOnRoot + ? args.Directory.CurrentDirectoryResults.Count(a => a.ParentFolderId.Equals("root")) + : args.Directory.CurrentDirectoryResults.Count; + + trilist.SetUshort(joinMap.DirectoryNumberOfRows.JoinNumber, (ushort)argsCount); + + var clearBytes = XSigHelpers.ClearOutputs(); + + trilist.SetString(joinMap.DirectoryXSig.JoinNumber, + Encoding.GetEncoding(xSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); + + var directoryXSig = CiscoDirectory.UpdateDirectoryXSig(args.Directory, args.DirectoryIsOnRoot); + trilist.SetString(joinMap.DirectoryXSig.JoinNumber, directoryXSig); + }; + + DirectoryItem selectedContact = null; + ContactMethod selectedContactMethod = null; + + var selectedItemNameFeedback = new StringFeedback(Key + "-SelectedContact-" + trilist.ID, () => + selectedContact == null ? string.Empty : selectedContact.Name); + + var selectedItemNumberOfContactMethods = new IntFeedback(Key + "-NumberOfContactMethods-" + trilist.ID, () => + selectedContact as DirectoryContact == null ? 0 : (selectedContact as DirectoryContact).ContactMethods.Count); + + var selectedItemCallMethodsFeedback = new StringFeedback(() => + { + var clearBytes = XSigHelpers.ClearOutputs(); + var clearString = Encoding.GetEncoding(xSigEncoding).GetString(clearBytes, 0, clearBytes.Length); + if (selectedContact == null) + { + return clearString; + } + + var result = selectedContact as DirectoryContact; + return result == null ? clearString : Directory.UpdateContactMethodsXSig(result); + }); + + trilist.SetUShortSigAction(joinMap.DirectorySelectContact.JoinNumber, + value => + { + selectedContactMethod = null; + if (value == 0) + { + selectedContact = null; + } + + selectedContact = Directory.CurrentDirectoryResult.Contacts.ElementAtOrDefault(value - 1); + selectedItemNameFeedback.FireUpdate(); + selectedItemNumberOfContactMethods.FireUpdate(); + selectedItemCallMethodsFeedback.FireUpdate(); + }); + + trilist.SetUShortSigAction(joinMap.DirectorySelectContactMethod.JoinNumber, + value => + { + selectedContactMethod = null; + if (value == 0) + { + return; + } + + var contact = selectedContact as DirectoryContact; + if (contact == null) + { + return; + } + + selectedContactMethod = contact.ContactMethods.ElementAtOrDefault(value - 1); + }); + + trilist.SetSigTrueAction(joinMap.DialSelectedContact.JoinNumber, () => + { + var contact = selectedContact as DirectoryContact; + if (contact == null) + { + return; + } + + var contactMethod = selectedContactMethod ?? contact.ContactMethods.FirstOrDefault(); + if (contactMethod == null) + { + return; + } + + CallStatus.Dial(contactMethod.Number); + }); + + selectedItemNameFeedback.LinkInputSig(trilist.StringInput[joinMap.SelectedContactName.JoinNumber]); + selectedItemNumberOfContactMethods.LinkInputSig(trilist.UShortInput[joinMap.SelectedDirectoryItemContactMethodsXsig.JoinNumber]); + selectedItemCallMethodsFeedback.LinkInputSig(trilist.StringInput[joinMap.SelectedDirectoryItemContactMethodsXsig.JoinNumber]); + + selectedItemNameFeedback.RegisterForDebug(this); + selectedItemNumberOfContactMethods.RegisterForDebug(this); + + Directory.SearchIsInProgress.LinkInputSig(trilist.BooleanInput[joinMap.SearchIsBusy.JoinNumber]); + trilist.SetStringSigAction(joinMap.SearchDirectory.JoinNumber, s => Directory.SearchDirectory(s)); + + trilist.SetUShortSigAction(joinMap.SelectRecentCall.JoinNumber, value => Recents.SelectedRecent = value); + Recents.SelectedRecentName.LinkInputSig(trilist.StringInput[joinMap.SelectRecentName.JoinNumber]); + Recents.SelectedRecentNumber.LinkInputSig(trilist.StringInput[joinMap.SelectRecentNumber.JoinNumber]); + + for (var x = 0; x < joinMap.Recents.JoinSpan; ++x) + { + var join = joinMap.Recents.JoinNumber + x; + var index = x; + var feedback = Recents.Feedbacks.ElementAtOrDefault(index); + if (feedback == null) + { + continue; + } + + Debug.Console(1, this, "Linking recent call index:{0} Join{1}", index, join); + feedback.LinkInputSig(trilist.StringInput[(uint)join]); + } + + DoNotDisturbModeIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.DoNotDisturbOn.JoinNumber]); + DoNotDisturbModeIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.DoNotDisturbOff.JoinNumber]); + + trilist.SetSigTrueAction(joinMap.DoNotDisturbOn.JoinNumber, ActivateDoNotDisturbMode); + trilist.SetSigTrueAction(joinMap.DoNotDisturbOff.JoinNumber, DeactivateDoNotDisturbMode); + trilist.SetSigTrueAction(joinMap.DoNotDisturbToggle.JoinNumber, ToggleDoNotDisturbMode); + + // we may need these, just need to verify what's available on the bridge + // trilist.SetSigTrueAction(joinMap.SelfviewOn.JoinNumber, () => SelfView.SelfViewModeOn()); + // trilist.SetSigTrueAction(joinMap.SelfviewOff.JoinNumber, () => SelfView.SelfViewModeOff()); + + trilist.SetSigTrueAction(joinMap.SelfviewToggle.JoinNumber, () => SelfView.SelfViewModeToggle()); + SelfView.SelfviewIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.SelfviewToggle.JoinNumber]); + + + trilist.SetUShortSigAction(joinMap.NearEndPresentationSource.JoinNumber, value => Presentation.SetShareSource(value)); + Presentation.SharingSourceIntFeedback.LinkInputSig(trilist.UShortInput[joinMap.NearEndPresentationSource.JoinNumber]); + + communications.TextReceived += (sender, args) => trilist.SetString(joinMap.Coms.JoinNumber, args.Text); + trilist.SetStringSigAction(joinMap.Coms.JoinNumber, value => communications.SendText(value)); + } + + public void VolumeUp(bool pressRelease) + { + ((IBasicVolumeControls) Audio).VolumeUp(pressRelease); + } + + public void VolumeDown(bool pressRelease) + { + ((IBasicVolumeControls) Audio).VolumeDown(pressRelease); + } + + public void MuteToggle() + { + ((IBasicVolumeControls) Audio).MuteToggle(); + } + + public void MuteOn() + { + ((IBasicVolumeWithFeedback) Audio).MuteOn(); + } + + public void MuteOff() + { + ((IBasicVolumeWithFeedback) Audio).MuteOff(); + } + + public void SetVolume(ushort level) + { + ((IBasicVolumeWithFeedback) Audio).SetVolume(level); + } + + public BoolFeedback MuteFeedback + { + get { return Audio.MuteFeedback; } + } + + public IntFeedback VolumeLevelFeedback + { + get { return Audio.VolumeLevelFeedback; } + } + + public void PrivacyModeOn() + { + ((IPrivacy) Audio).PrivacyModeOn(); + } + + public void PrivacyModeOff() + { + ((IPrivacy) Audio).PrivacyModeOff(); + } + + public void PrivacyModeToggle() + { + ((IPrivacy) Audio).PrivacyModeToggle(); + } + + public BoolFeedback PrivacyModeIsOnFeedback + { + get { return Audio.PrivacyModeIsOnFeedback; } + } + + public void ActivateDoNotDisturbMode() + { + ((IHasDoNotDisturbMode) DoNotDisturb).ActivateDoNotDisturbMode(); + } + + public void DeactivateDoNotDisturbMode() + { + ((IHasDoNotDisturbMode) DoNotDisturb).DeactivateDoNotDisturbMode(); + } + + public void ToggleDoNotDisturbMode() + { + ((IHasDoNotDisturbMode) DoNotDisturb).ToggleDoNotDisturbMode(); + } + + public BoolFeedback DoNotDisturbModeIsOnFeedback + { + get { return DoNotDisturb.DoNotDisturbModeIsOnFeedback; } + } + } + + public class CiscoBaseJoinMap : JoinMapBaseAdvanced + { + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete( + new JoinData + { + JoinNumber = 1, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "IsOnline", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("DtmfJoins")] + public JoinDataComplete DtmfJoins = new JoinDataComplete( + new JoinData + { + JoinNumber = 11, + JoinSpan = 10 + }, + new JoinMetadata + { + Description = "DtmfJoins", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("DtmfStar")] + public JoinDataComplete DtmfStar = new JoinDataComplete( + new JoinData + { + JoinNumber = 21, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "DtmfStar", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("DtmfPound")] + public JoinDataComplete DtmfPound = new JoinDataComplete( + new JoinData + { + JoinNumber = 22, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "DtmfPound", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("NumberOfActiveCalls")] + public JoinDataComplete NumberOfActiveCalls = new JoinDataComplete( + new JoinData + { + JoinNumber = 25, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "NumberOfActiveCalls", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("EndAllCalls")] + public JoinDataComplete EndAllCalls = new JoinDataComplete( + new JoinData + { + JoinNumber = 24, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "EndAllCalls", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("CallIsConnectedOrConnecting")] + public JoinDataComplete CallIsConnectedOrConnecting = new JoinDataComplete( + new JoinData + { + JoinNumber = 31, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "CallIsConnectedOrConnecting", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("CallIsIncoming")] + public JoinDataComplete CallIsIncoming = new JoinDataComplete( + new JoinData + { + JoinNumber = 50, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "CallIsIncoming", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("AnswerIncoming")] + public JoinDataComplete AnswerIncoming = new JoinDataComplete( + new JoinData + { + JoinNumber = 51, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "AnswerIncoming", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("RejectIncoming")] + public JoinDataComplete RejectIncoming = new JoinDataComplete( + new JoinData + { + JoinNumber = 52, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "RejectIncoming", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("IncomingName")] + public JoinDataComplete IncomingName = new JoinDataComplete( + new JoinData + { + JoinNumber = 51, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "IncomingName", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("IncomingNumber")] + public JoinDataComplete IncomingNumber = new JoinDataComplete( + new JoinData + { + JoinNumber = 52, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "IncomingNumber", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("HangUpCall")] + public JoinDataComplete HangUpCall = new JoinDataComplete( + new JoinData + { + JoinNumber = 81, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "HangUpCall", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("JoinAllCalls")] + public JoinDataComplete JoinAllCalls = new JoinDataComplete( + new JoinData + { + JoinNumber = 90, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "JoinAllCalls", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("JoinCall")] + public JoinDataComplete JoinCall = new JoinDataComplete( + new JoinData + { + JoinNumber = 91, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "JoinCall", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("HoldAllCalls")] + public JoinDataComplete HoldAllCalls = new JoinDataComplete( + new JoinData + { + JoinNumber = 220, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "HoldAllCalls", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("HoldCall")] + public JoinDataComplete HoldCall = new JoinDataComplete( + new JoinData + { + JoinNumber = 221, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "HoldCall", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("ResumeAllCalls")] + public JoinDataComplete ResumeAllCalls = new JoinDataComplete( + new JoinData + { + JoinNumber = 230, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "ResumeAllCalls", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("ResumeCall")] + public JoinDataComplete ResumeCall = new JoinDataComplete( + new JoinData + { + JoinNumber = 231, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "JoinCall", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("NearEndPresentationSource")] + public JoinDataComplete NearEndPresentationSource = new JoinDataComplete( + new JoinData + { + JoinNumber = 201, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "NearEndPresentationSource", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("NearEndPresentationStart")] + public JoinDataComplete NearEndPresentationStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 201, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "NearEndPresentationStart", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("NearEndPresentationStop")] + public JoinDataComplete NearEndPresentationStop = new JoinDataComplete( + new JoinData + { + JoinNumber = 202, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "NearEndPresentationStop", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("StandbyOn")] + public JoinDataComplete StandbyOn = new JoinDataComplete( + new JoinData + { + JoinNumber = 246, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "StandbyOn", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("StandbyOff")] + public JoinDataComplete StandbyOff = new JoinDataComplete( + new JoinData + { + JoinNumber = 247, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "StandbyOff", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("EnteringStandby")] + public JoinDataComplete EnteringStandby = new JoinDataComplete( + new JoinData + { + JoinNumber = 248, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "EnteringStandby", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("SearchIsBusy")] + public JoinDataComplete SearchIsBusy = new JoinDataComplete( + new JoinData + { + JoinNumber = 100, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SearchIsBusy", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("MicMuteOn")] + public JoinDataComplete MicMuteOn = new JoinDataComplete( + new JoinData + { + JoinNumber = 171, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "MicMuteOn", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("MicMuteOff")] + public JoinDataComplete MicMuteOff = new JoinDataComplete( + new JoinData + { + JoinNumber = 172, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "MicMuteOff", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("MicMuteToggle")] + public JoinDataComplete MicMuteToggle = new JoinDataComplete( + new JoinData + { + JoinNumber = 173, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "MicMuteToggle", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("VolumeUp")] + public JoinDataComplete VolumeUp = new JoinDataComplete( + new JoinData + { + JoinNumber = 174, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "VolumeUp", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("VolumeDown")] + public JoinDataComplete VolumeDown = new JoinDataComplete( + new JoinData + { + JoinNumber = 175, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "VolumeDown", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("Volume")] + public JoinDataComplete Volume = new JoinDataComplete( + new JoinData + { + JoinNumber = 174, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Volume", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("VolumeMuteOn")] + public JoinDataComplete VolumeMuteOn = new JoinDataComplete( + new JoinData + { + JoinNumber = 176, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "VolumeMuteOn", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("VolumeMuteOff")] + public JoinDataComplete VolumeMuteOff = new JoinDataComplete( + new JoinData + { + JoinNumber = 177, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "VolumeMuteOff", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("VolumeMuteToggle")] + public JoinDataComplete VolumeMuteToggle = new JoinDataComplete( + new JoinData + { + JoinNumber = 178, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "VolumeMuteToggle", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("DoNotDisturbOn")] + public JoinDataComplete DoNotDisturbOn = new JoinDataComplete( + new JoinData + { + JoinNumber = 241, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "DoNotDisturbOn", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("DoNotDisturbOff")] + public JoinDataComplete DoNotDisturbOff = new JoinDataComplete( + new JoinData + { + JoinNumber = 242, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "DoNotDisturbOff", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("DoNotDisturbToggle")] + public JoinDataComplete DoNotDisturbToggle = new JoinDataComplete( + new JoinData + { + JoinNumber = 243, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "DoNotDisturbToggle", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + /* We will probably need these one day + [JoinName("SelfviewOn")] + public JoinDataComplete SelfviewOn = new JoinDataComplete( + new JoinData + { + JoinNumber = 241, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SelfviewOn", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("SelfviewOff")] + public JoinDataComplete SelfviewOff = new JoinDataComplete( + new JoinData + { + JoinNumber = 242, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SelfviewOff", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + * */ + + [JoinName("SelfviewToggle")] + public JoinDataComplete SelfviewToggle = new JoinDataComplete( + new JoinData + { + JoinNumber = 141, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SelfviewToggle", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + + [JoinName("NearEndCameraUp")] + public JoinDataComplete NearEndCameraUp = new JoinDataComplete( + new JoinData + { + JoinNumber = 111, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "NearEndCameraUp", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("NearEndCameraDown")] + public JoinDataComplete NearEndCameraDown = new JoinDataComplete( + new JoinData + { + JoinNumber = 112, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "NearEndCameraDown", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("NearEndCameraLeft")] + public JoinDataComplete NearEndCameraLeft = new JoinDataComplete( + new JoinData + { + JoinNumber = 113, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "NearEndCameraLeft", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("NearEndCameraRight")] + public JoinDataComplete NearEndCameraRight = new JoinDataComplete( + new JoinData + { + JoinNumber = 114, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "NearEndCameraRight", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("NearEndCameraZoomIn")] + public JoinDataComplete NearEndCameraZoomIn = new JoinDataComplete( + new JoinData + { + JoinNumber = 115, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "NearEndCameraZoomIn", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("NearEndCameraZoomOut")] + public JoinDataComplete NearEndCameraZoomOut = new JoinDataComplete( + new JoinData + { + JoinNumber = 116, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "NearEndCameraUp", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("NearEndCameraFocusIn")] + public JoinDataComplete NearEndCameraFocusIn = new JoinDataComplete( + new JoinData + { + JoinNumber = 117, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "NearEndCameraFocusIn", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("NearEndCameraFocusOut")] + public JoinDataComplete NearEndCameraFocusOut = new JoinDataComplete( + new JoinData + { + JoinNumber = 121, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "NearEndCameraFocusOut", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + + [JoinName("FarEndCameraUp")] + public JoinDataComplete FarEndCameraUp = new JoinDataComplete( + new JoinData + { + JoinNumber = 122, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "FarEndCameraUp", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("FarEndCameraDown")] + public JoinDataComplete FarEndCameraDown = new JoinDataComplete( + new JoinData + { + JoinNumber = 123, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "FarEndCameraDown", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("FarEndCameraLeft")] + public JoinDataComplete FarEndCameraLeft = new JoinDataComplete( + new JoinData + { + JoinNumber = 124, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "FarEndCameraLeft", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("FarEndCameraRight")] + public JoinDataComplete FarEndCameraRight = new JoinDataComplete( + new JoinData + { + JoinNumber = 125, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "FarEndCameraRight", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("FarEndCameraZoomIn")] + public JoinDataComplete FarEndCameraZoomIn = new JoinDataComplete( + new JoinData + { + JoinNumber = 126, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "FarEndCameraZoomIn", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("FarEndCameraZoomOut")] + public JoinDataComplete FarEndCameraZoomOut = new JoinDataComplete( + new JoinData + { + JoinNumber = 127, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "FarEndCameraUp", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + + [JoinName("SpeakerTrackEnabled")] + public JoinDataComplete SpeakerTrackEnabled = new JoinDataComplete( + new JoinData + { + JoinNumber = 131, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SpeakerTrackEnabled", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("SpeakerTrackDisabled")] + public JoinDataComplete SpeakerTrackDisabled = new JoinDataComplete( + new JoinData + { + JoinNumber = 132, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SpeakerTrackDisabled", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("SpeakerTrackAvailable")] + public JoinDataComplete SpeakerTrackAvailable = new JoinDataComplete( + new JoinData + { + JoinNumber = 143, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SpeakerTrackAvailable", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("ManualDial")] + public JoinDataComplete ManualDial = new JoinDataComplete( + new JoinData + { + JoinNumber = 71, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "ManualDial", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("DialNumber")] + public JoinDataComplete DialNumber = new JoinDataComplete( + new JoinData + { + JoinNumber = 1, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "DialNumber", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("CallStatusXSig")] + public JoinDataComplete CallStatusXSig = new JoinDataComplete( + new JoinData + { + JoinNumber = 2, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "CallStatusXSig", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("DirectoryXSig")] + public JoinDataComplete DirectoryXSig = new JoinDataComplete( + new JoinData + { + JoinNumber = 101, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "DirectoryXSig", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("SearchDirectory")] + public JoinDataComplete SearchDirectory = new JoinDataComplete( + new JoinData + { + JoinNumber = 100, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SearchDirectory", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("DirectoryNumberOfRows")] + public JoinDataComplete DirectoryNumberOfRows = new JoinDataComplete( + new JoinData + { + JoinNumber = 101, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "DirectoryNumberOfRows", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("DirectorySelectContact")] + public JoinDataComplete DirectorySelectContact = new JoinDataComplete( + new JoinData + { + JoinNumber = 101, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "DirectorySelectContact", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("SelectedContactName")] + public JoinDataComplete SelectedContactName = new JoinDataComplete( + new JoinData + { + JoinNumber = 102, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SelectedContactName", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("DirectorySelectContactMethod")] + public JoinDataComplete DirectorySelectContactMethod = new JoinDataComplete( + new JoinData + { + JoinNumber = 103, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "DirectorySelectContactMethod", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("SelectedDirectoryItemNumberOfContactMethods")] + public JoinDataComplete SelectedDirectoryItemNumberOfContactMethods = new JoinDataComplete( + new JoinData + { + JoinNumber = 102, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SelectedDirectoryItemNumberOfContactMethods", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("SelectedDirectoryItemContactMethodsXsig")] + public JoinDataComplete SelectedDirectoryItemContactMethodsXsig = new JoinDataComplete( + new JoinData + { + JoinNumber = 103, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SelectedDirectoryItemContactMethodsXsig", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("SelectedContactNumber")] + public JoinDataComplete SelectedContactNumber = new JoinDataComplete( + new JoinData + { + JoinNumber = 104, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SelectedContactNumber", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("ClearPhonebookSearch")] + public JoinDataComplete ClearPhonebookSearch = new JoinDataComplete( + new JoinData + { + JoinNumber = 110, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "ClearPhonebookSearch", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("DialSelectedContact")] + public JoinDataComplete DialSelectedContact = new JoinDataComplete( + new JoinData + { + JoinNumber = 106, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "DialSelectedContact", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + + [JoinName("SelectRecentCall")] + public JoinDataComplete SelectRecentCall = new JoinDataComplete( + new JoinData + { + JoinNumber = 180, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SelectRecentCall", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("SelectRecentName")] + public JoinDataComplete SelectRecentName = new JoinDataComplete( + new JoinData + { + JoinNumber = 171, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SelectRecentName", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("SelectRecentNumber")] + public JoinDataComplete SelectRecentNumber = new JoinDataComplete( + new JoinData + { + JoinNumber = 171, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SelectRecentNumber", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("Recents")] + public JoinDataComplete Recents = new JoinDataComplete( + new JoinData + { + JoinNumber = 181, + JoinSpan = 10 + }, + new JoinMetadata + { + Description = "Recents", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("CameraSelect")] + public JoinDataComplete CameraSelect = new JoinDataComplete( + new JoinData + { + JoinNumber = 60, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "CameraSelect", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("CameraPresetActivate")] + public JoinDataComplete CameraPresetActivate = new JoinDataComplete( + new JoinData + { + JoinNumber = 121, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "CameraPresetActivate", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("FarEndCameraPresetActivate")] + public JoinDataComplete FarEndCameraPresetActivate = new JoinDataComplete( + new JoinData + { + JoinNumber = 122, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "FarEndCameraPresetActivate", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("CameraPresetStore")] + public JoinDataComplete CameraPresetStore = new JoinDataComplete( + new JoinData + { + JoinNumber = 123, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "CameraPresetStore", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("Coms")] + public JoinDataComplete Coms = new JoinDataComplete( + new JoinData + { + JoinNumber = 5, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Coms", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Serial + }); + + public CiscoBaseJoinMap(uint joinStart) + : base(joinStart, typeof(CiscoBaseJoinMap)) + { + } } } \ No newline at end of file diff --git a/src/V2/CiscoRoomOsFeature.cs b/src/V2/CiscoRoomOsFeature.cs index adae2fd..7348fd1 100644 --- a/src/V2/CiscoRoomOsFeature.cs +++ b/src/V2/CiscoRoomOsFeature.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using PepperDash.Core; -namespace PDT.Plugins.Cisco.RoomOs.V2 +namespace epi_videoCodec_ciscoExtended.V2 { public abstract class CiscoRoomOsFeature : IKeyed { @@ -10,10 +10,22 @@ protected CiscoRoomOsFeature(string key) Key = key; } - public abstract IEnumerable Polls { get; } - public abstract IEnumerable Subscriptions { get; } public string Key { get; private set; } - public abstract bool HandlesResponse(string response); - public abstract void HandleResponse(string response); + } + + public interface IHasPolls : IKeyed + { + IEnumerable Polls { get; } + } + + public interface IHasEventSubscriptions : IKeyed + { + IEnumerable Subscriptions { get; } + } + + public interface IHandlesResponses : IKeyed + { + bool HandlesResponse(string response); + void HandleResponse(string response); } } \ No newline at end of file diff --git a/src/V2/CiscoRoomOsPluginFactory.cs b/src/V2/CiscoRoomOsPluginFactory.cs new file mode 100644 index 0000000..e62cfbd --- /dev/null +++ b/src/V2/CiscoRoomOsPluginFactory.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; + +namespace epi_videoCodec_ciscoExtended.V2 +{ + public class CiscoRoomOsPluginFactory : EssentialsPluginDeviceFactory + { + public CiscoRoomOsPluginFactory() + { + TypeNames = new List {"ciscoRoomOs"}; + } + + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + return new CiscoRoomOsDevice(dc); + } + } +} \ No newline at end of file diff --git a/src/V2/CiscoRoomPresets.cs b/src/V2/CiscoRoomPresets.cs new file mode 100644 index 0000000..b3dcf30 --- /dev/null +++ b/src/V2/CiscoRoomPresets.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using PepperDash.Essentials.Devices.Common.VideoCodec; + +namespace epi_videoCodec_ciscoExtended.V2 +{ + public class CiscoRoomPresets : CiscoRoomOsFeature, IHasCodecRoomPresets, IHasPolls, IHasEventSubscriptions + { + private readonly CiscoRoomOsDevice parent; + + public CiscoRoomPresets(CiscoRoomOsDevice parent) : base(parent.Key + "-presets") + { + this.parent = parent; + Polls = new List + { + "xStatus RoomPreset" + }; + + Subscriptions = new List + { + "Status/RoomPreset" + }; + + // todo: maybe we need to parse this out someday + NearEndPresets = new List(); + FarEndRoomPresets = new List(); + } + + public void CodecRoomPresetSelect(int preset) + { + if (preset == 0) + return; + + var command = "xCommand RoomPreset Activate PresetId:" + preset; + parent.SendText(command); + } + + public void CodecRoomPresetStore(int preset, string description) + { + if (preset == 0) + return; + + var command = "xCommand RoomPreset Store PresetId:" + preset + " Description:" + description; + parent.SendText(command); + } + + public void SelectFarEndPreset(int preset) + { + if (preset == 0) + return; + + // xCommand Call FarEndControl RoomPreset Activate CallId: value ParticipantId: value PresetId: value + var activeCall = parent.CallStatus.GetActiveCallId(); + + var command = + string.Format( + "xCommand Call FarEndControl RoomPreset Activate CallId: {0} PresetId: {1}", activeCall, preset); + + parent.SendText(command); + + } + + public List NearEndPresets { get; private set; } + public List FarEndRoomPresets { get; private set; } + + public event EventHandler CodecRoomPresetsListHasChanged; + + public IEnumerable Polls { get; private set; } + public IEnumerable Subscriptions { get; private set; } + } +} \ No newline at end of file diff --git a/src/V2/CiscoSelfView.cs b/src/V2/CiscoSelfView.cs new file mode 100644 index 0000000..7225339 --- /dev/null +++ b/src/V2/CiscoSelfView.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.DeviceTypeInterfaces; +using PepperDash.Essentials.Devices.Common.VideoCodec; + +namespace epi_videoCodec_ciscoExtended.V2 +{ + /* + * *s Video Selfview FullscreenMode: Off + *s Video Selfview Mode: Off + *s Video Selfview OnMonitorRole: First + *s Video Selfview PIPPosition: CenterRight + */ + public class CiscoSelfView : CiscoRoomOsFeature, IHasPolls, IHasEventSubscriptions, IHandlesResponses, IHasSelfviewPosition, IHasCodecSelfView + { + private readonly CiscoRoomOsDevice parent; + + public CiscoSelfView(CiscoRoomOsDevice parent) + : base(parent.Key + "-DND") + { + this.parent = parent; + + Polls = new List + { + "xStatus Video Selfview" + }; + + Subscriptions = new List + { + "Status/Video/Selfview" + }; + + SelfviewIsOnFeedback = new BoolFeedback("SelfView", () => selfViewIsOn); + SelfviewIsFullscreen = new BoolFeedback("SelfViewFullscreen", () => selfViewIsFullScreen); + SelfviewPipPositionFeedback = new StringFeedback("SelfViewPipPosition", () => "Not implemented"); + + SelfviewIsOnFeedback.RegisterForDebug(parent); + SelfviewIsFullscreen.RegisterForDebug(parent); + + parent.CallStatus.CallIsConnectedOrConnecting.OutputChange += (sender, args) => + { + if (args.BoolValue && ShowSelfViewByDefault) + { + SelfViewModeOn(); + } + }; + } + + private bool selfViewIsOn; + private bool selfViewIsFullScreen; + + public IEnumerable Polls { get; private set; } + public IEnumerable Subscriptions { get; private set; } + + public bool HandlesResponse(string response) + { + return response.IndexOf("*s Video Selfview", StringComparison.Ordinal) > -1; + } + + public void HandleResponse(string response) + { + var parts = response.Split('|'); + foreach (var line in parts) + { + switch (line) + { + case "*s Video Selfview Mode: Off": + selfViewIsOn = false; + SelfviewIsOnFeedback.FireUpdate(); + break; + case "*s Video Selfview Mode: On": + selfViewIsOn = true; + SelfviewIsOnFeedback.FireUpdate(); + break; + case "*s Video Selfview FullscreenMode: Off": + selfViewIsFullScreen = false; + SelfviewIsFullscreen.FireUpdate(); + break; + case "*s Video Selfview FullscreenMode: On": + selfViewIsFullScreen = true; + SelfviewIsFullscreen.FireUpdate(); + break; + } + } + } + + public void SelfviewPipPositionSet(CodecCommandWithLabel position) + { + + } + + public void SelfviewPipPositionToggle() + { + // throw new NotImplementedException(); + } + + public StringFeedback SelfviewPipPositionFeedback { get; private set; } + + public void SelfviewFullSreenOn() + { + parent.SendText("xCommand Video Selfview Set FullscreenMode: On"); + } + + public void SelfviewFullSreenOff() + { + parent.SendText("xCommand Video Selfview Set FullscreenMode: Off"); + } + + public void SelfviewFullSreenToggle() + { + if (selfViewIsFullScreen) + { + SelfviewFullSreenOff(); + } + else + { + SelfviewFullSreenOn(); + } + } + + public BoolFeedback SelfviewIsFullscreen { get; private set; } + + public void SelfViewModeOn() + { + parent.SendText("xCommand Video Selfview Set Mode: On"); + } + + public void SelfViewModeOff() + { + parent.SendText("xCommand Video Selfview Set Mode: Off"); + } + + public void SelfViewModeToggle() + { + if (selfViewIsOn) + { + SelfViewModeOff(); + } + else + { + SelfViewModeOn(); + } + } + + public BoolFeedback SelfviewIsOnFeedback { get; private set; } + + public bool ShowSelfViewByDefault { get; private set; } + } +} \ No newline at end of file diff --git a/src/V2/CiscoStandby.cs b/src/V2/CiscoStandby.cs index 86d635f..aec3de2 100644 --- a/src/V2/CiscoStandby.cs +++ b/src/V2/CiscoStandby.cs @@ -1,14 +1,13 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; -using PDT.Plugins.Cisco.RoomOs.V2; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Devices.Common.VideoCodec; namespace epi_videoCodec_ciscoExtended.V2 { - public class CiscoStandby : CiscoRoomOsFeature, IHasHalfWakeMode + public class CiscoStandby : CiscoRoomOsFeature, IHasHalfWakeMode, IHasPolls, IHasEventSubscriptions, IHandlesResponses { private string standbyState; private readonly CiscoRoomOsDevice parent; @@ -28,27 +27,31 @@ public CiscoStandby(CiscoRoomOsDevice parent) { this.parent = parent; - StandbyIsOnFeedback = new BoolFeedback(() => standbyState != null && standbyState != "Off"); - HalfWakeModeIsOnFeedback = new BoolFeedback(() => standbyState == "Halfwake"); - EnteringStandbyModeFeedback = new BoolFeedback(() => standbyState == "EnteringStandby"); + StandbyIsOnFeedback = new BoolFeedback("Standby", () => standbyState != null && standbyState != "Off"); + HalfWakeModeIsOnFeedback = new BoolFeedback("HalfWake", () => standbyState == "Halfwake"); + EnteringStandbyModeFeedback = new BoolFeedback("Entering Standby", () => standbyState == "EnteringStandby"); + + StandbyIsOnFeedback.RegisterForDebug(parent); + HalfWakeModeIsOnFeedback.RegisterForDebug(parent); + EnteringStandbyModeFeedback.RegisterForDebug(parent); } - public override IEnumerable Polls + public IEnumerable Polls { get { return PollStrings; } } - public override IEnumerable Subscriptions + public IEnumerable Subscriptions { get { return EventSubscriptions; } } - public override bool HandlesResponse(string response) + public bool HandlesResponse(string response) { return response.StartsWith("*s Standby State"); } - public override void HandleResponse(string response) + public void HandleResponse(string response) { const string pattern = @"Standby State:\s*(?\w+)"; var match = Regex.Match(response, pattern); @@ -60,8 +63,6 @@ public override void HandleResponse(string response) StandbyIsOnFeedback.FireUpdate(); HalfWakeModeIsOnFeedback.FireUpdate(); EnteringStandbyModeFeedback.FireUpdate(); - - Debug.Console(1, this, "Standby State:{0}", standbyState); } } diff --git a/src/V2/FeedbackDebugExt.cs b/src/V2/FeedbackDebugExt.cs new file mode 100644 index 0000000..c822ee9 --- /dev/null +++ b/src/V2/FeedbackDebugExt.cs @@ -0,0 +1,23 @@ +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace epi_videoCodec_ciscoExtended.V2 +{ + public static class FeedbackDebugExt + { + public static void RegisterForDebug(this Feedback feedback, IKeyed parent) + { + feedback.OutputChange += (sender, args) => + { + if (sender is BoolFeedback) + Debug.Console(0, parent, "Received {0} Update : '{1}'", feedback.Key, args.BoolValue); + + if (sender is IntFeedback) + Debug.Console(0, parent, "Received {0} Update : '{1}'", feedback.Key, args.IntValue); + + if (sender is StringFeedback) + Debug.Console(0, parent, "Received {0} Update : '{1}'", feedback.Key, args.StringValue); + }; + } + } +} \ No newline at end of file