using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Collections.ObjectModel; using System.Threading; using Interop.Plantronics; /******* * * SpokesWrapper3.0.cs * * SpokesWrapper3.0.cs is a wrapper around the Plantronics Spokes COM Service API for C# .NET (.NET Framework 4 and higher). * * It's purpose is to make it easier and simpler to integrate support for Plantronics devices into any applications. * * It achieves this by hiding a lot of the more tricky aspects of integration behind the wrapper and presenting * a simple and consistent set of Event Handlers and functions for the core features of the SDK that the user * will typically be needing. * * *** WARNING !!! This source code is provided *As Is*! It is intented as a sample code to show ways of integrating * with the Spokes "COM Service .NET API". However in case of problems please feel free to contact Lewis Collins * directly via the PDC site at this address: http://developer.plantronics.com/people/lcollins/ *** * * The latest version of this file will also be maintained (and feel free to create your own Fork!) on Github, here: * * https://github.com/pltdev/Samples/tree/master/wrappers * * Read more about Plantronics Spokes at the Plantronics Developer Connection web site: * * http://developer.plantronics.com/community/devzone/ * * Lewis Collins * * http://developer.plantronics.com/people/lcollins * * VERSION HISTORY: * ******************************************************************************** * Version 1.5.45: * Date: 15th Dec 2016 * Tested with Plantronics Hub / SDK version(s): 3.9 nightly * Changed by: Lewis Collins * Changes: * - Removing FocusWorkaround features that were breaking Hub/BT600 (TT41072) * and also removing doubloon workaround. Neither are needed any more. These * are now released features of SDK. * * Version 1.5.44: * Date: 1st Sept 2016 * Tested with Plantronics Hub / SDK version(s): 3.8 latest * Changed by: Lewis Collins * Changes: * - Fixing the QD connector events * * Version 1.5.43: * Date: 7th June 2016 * Tested with Plantronics Hub / SDK version(s): 3.8 latest * Changed by: Lewis Collins * Changes: * - Added fix suggested on TT 39171 to only use ICallEvents call id * for call control rather than ISessionManagerEvents call id. * ICallEvents call id will match what is used by the application for * call control (incomingcall/outgoingcall, etc). * * Version 1.5.42: * Date: 23rd May 2016 * Tested with Plantronics Hub / SDK version(s): 3.8 latest * Changed by: Lewis Collins * Changes: * - Extended FocusWorkaround conditional to support interim Far * event support. * * Version 1.5.41: * Date: 26th February 2016 * Tested with Plantronics Hub / SDK version(s): 3.7 latest * Changed by: Lewis Collins * Changes: * - Added Get/SetMuteToneVolume and Get/SetMuteTone functions to * manipulate the mute tone settings of the headset. * - Added a fix to obtain DA80 initial connected state after 1 second * delay (on initial attach device it caused COM error) * * Version 1.5.40: * Date: 12th February 2016 * Tested with Plantronics Hub / SDK version(s): 3.7 latest * Changed by: Lewis Collins * Changes: * - Added optional FocusWorkaround conditional to add interim support * for docking and inrange/outofrange for Focus UC product. * * Version 1.5.39: * Date: 5th February 2016 * Tested with Plantronics Hub / SDK version(s): 3.7 latest * Changed by: Lewis Collins * Changes: * - Added exception handling to SetAudioSensing function (as it will * fail with unsupported exception with Voyager Focus). * * Version 1.5.38: * Date: 22nd January 2016 * Tested with Plantronics Hub / SDK version(s): 3.7 latest * Changed by: Lewis Collins * Changes: * - Added functions to get and set the default softphone so that * apps can set themselves to receive Calisto P240 callrequested event * * Version 1.5.37: * Date: 20th January 2016 * Tested with Plantronics Hub / SDK version(s): 3.7 latest * Changed by: Lewis Collins * Changes: * - Added support for reading serial numbers following change * to address serial number support in TT 34072 * * Version 1.5.36: * Date: 19th January 2016 * Tested with Plantronics Hub / SDK version(s): 3.7 latest * Changed by: Lewis Collins * Changes: * - Added support and available features for Voyager Focus UC. * Note: Focus UC lacks proximity and docking features currently as these * are not exposed through the public Spokes SDK with BT600/Voyager Focus UC. * * Version 1.5.35: * Date: 18th September 2015 * Tested with Plantronics Hub / SDK version(s): 3.6 latest * Changed by: Lewis Collins * Changes: * - Added catch of COMException affecting D100. * - Adding SetAudioSensing function to turn on/off device * audio sensing. Need to turn it off if you want to programatically * control line state via ConnectAudioLinkToDevice method. * * Version 1.5.34: * Date: 7th April 2015 * Tested with Plantronics Hub / SDK version(s): 3.4 latest * Changed by: Lewis Collins * Changes: * - Added some extra exception handling for a COMException that was arising * in device listener event callback. * * Version 1.5.33: * Date: 25th Feb 2015 * Tested with Plantronics Hub / SDK version(s): 3.4 nightly * Changed by: Lewis Collins * Changes: * - Fixing HeadsetConnectedState typo in SDK call * (had an extra n) * * Version 1.5.32: * Date: 24th Feb 2015 * Tested with Plantronics Hub / SDK version(s): 3.2 release SDK version * Changed by: Lewis Collins * Changes: * - Hiding with conditional newDASeries the ICOMDeviceSettingsExt member, which is * only usable on 3.4+ SDKs and is used to get the initial connected state of new * DA Series. * * Version 1.5.31: * Date: 9th Feb 2015 * Tested with Plantronics Hub / SDK version(s): 3.4.50921.12982 (22/01/2015 pre-release for DA Series) * Changed by: Lewis Collins * Changes: * - Adding Calisto P240 dial handling, via IDeviceListener.BaseButtonPressed DialedKey, etc. * - Removed some un-needed IDeviceEvents events, relying instead on IDeviceListener versions * (Talk button, mute, etc) * * Version 1.5.30: * Date: 23rd Jan 2015 * Tested with Plantronics Hub / SDK version(s): 3.4.50921.12982 (22/01/2015 pre-release for DA Series) * Changed by: Lewis Collins * Changes: * - Adding get "initial" QuickDisconnect QD state from Plantronics SDK ICOMDeviceSettingsExt. * HeadsetConnnectedState method. * * Version 1.5.29: * Date: 2nd Dec 2014 * Compatible with Spokes SDK version(s): 3.3.50862.10305 (24/11/2014 pre-release for DA Series) * Tested with Hub version: * Changed by: Lewis Collins * Changes: * - Adding knowledge of QD connector feature (new DA Series audio processors) * * Version 1.5.28: * Date: 22nd Aug 2014 * Compatible with Spokes SDK version(s): 3.x * Tested with Hub version: 3.0.50718.1966 * Changed by: Lewis Collins * Changes: * - Adding missing hold call/resume call functions * * Version 1.5.28: * Date: 19th Aug 2014 * Compatible with Spokes SDK version(s): 3.x * Tested with Hub version: 3.0.50718.1966 * Changed by: Lewis Collins * Changes: * - Fixed error in the initial docked/undocked state detection. * * Version 1.5.27: * Date: 18th Aug 2014 * Compatible with Spokes SDK version(s): 3.x * Tested with Hub version: 3.0.50718.1966 * Changed by: Lewis Collins * Changes: * - The missing "AnswerCall" method was added to this version. * * Version 1.5.26: * Date: 14th Nov 2013 * Compatible with Spokes SDK version(s): 3.x * Changed by: Lewis Collins * Changes: * - Fixing the reading of headset and base serial numbers - making * work same way as Spokes3GCOMSample sample code. * * Version 1.5.25: * Date: 8th Oct 2013 * Compatible with Spokes SDK version(s): 3.x * Changed by: Lewis Collins * Changes: * - Adding a callid argument value to the OnCall event so apps can know * id of call that has started. * - Commented out some duplicate Spokes event cases that were resulting * in double Spokes events being received by apps. * - Adding special case for hardcoded product id of Innovation Concept 1 * device. * * Version 1.5.24: * Date: 17th Sept 2013 * Compatible with Spokes SDK version(s): 3.1.85759.0 * Changed by: Lewis Collins * Changes: * - Restarting version numbering (1.5.x for Spokes 3.x version, 1.0.x for Spokes 2.x version) * Also skipping versions to 1.5.24 (3.x) = 1.0.24 (2.x), so the minor versions match * - Added knowledge of the Plantronics device capabilities through * deployment of supplementary file: "DeviceCapabilities.csv" * This file should be placed in the working directory of the calling * application. * * Version 1.1.1: * Date: 13th Sept 2013 * Compatible with Spokes SDK version(s): 3.1.85589.0 * Changed by: Lewis Collins * Changes: * - Added GetDevice member function to return current active Plantronics call control * device (if any) * * Version 1.1.0: * Date: 19th Mar 2013 * Compatible with Spokes SDK version(s): 3.0.24886.0 * Changed by: Lewis Collins * Changes: * - Ported to Spoke 3.0 SDK * * Version 1.0.11: * Date: 14th Mar 2013 * Compatible with Spokes SDK version(s): 2.8.24304.0, 2.7.14092.0 * Changed by: Lewis Collins * Changes: * - Added try/catch exception handling and debug around registering for extended headsetstatechange * and extended basestatechange events that were used for "asyncronous" method of receiving serial * numbers from device (works for base serial, NOT for headset serial) * - Also added "syncronous" method of obtaining serial numbers following discussion with Ramesh Mar 2013 * however this is NOT working for base OR headset serial! (So left in "asyncronous" method for now, * at least we have base!) * * Version 1.0.10: * Date: 05th Mar 2013 * Compatible with Spokes SDK version(s): 2.7.14092.0 * Changed by: Lewis Collins * Changes: * - Updated some internal methods to public (they didn't need to be internal) * * Version 1.0.9: * Date: 22nd Feb 2013 * Compatible with Spokes SDK version(s): 2.7.14092.0 * Changed by: Lewis Collins * Changes: * - Add proximity enabled / proximity disabled event handlers * * Version 1.0.8: * Date: 21st Feb 2013 * Compatible with Spokes SDK version(s): 2.7.14092.0 * Changed by: Lewis Collins * Changes: * - Add headset button events via new ButtonPressed event handler in the wrapper. * NOTE: you are advised NOT to use headset buttons e.g. talk button for * call control, but rather use the IncomingCall/OutgoingCall functions * and CallAnswered/CallEnded event handlers. Using talk button will * cause problems with multiline devices as talk button events for the * deskphone (+EHS) will also be received by your app through the SDK!!!! * Also bad interactions can occur with talk button and other softphones * on your system e.g. Lync if you try to use raw button events. * You have been warned. * - Add CallRequested event handler to obtain user call requested events from * dialpad devices (Calisto P240/800 series) * * Version 1.0.7: * Date: 19th Feb 2013 * Compatible with Spokes SDK version(s): 2.7.14092.0 * Changed by: Lewis Collins * Changes: * - Change namespace to Plantronics.UC.SpokesWrapper (from Plantronics.UC.Spokes) * * Version 1.0.6: * Date: 14th Feb 2013 * Compatible with Spokes SDK version(s): 2.7.14092.0 * Changed by: Lewis Collins * Changes: * - Minor fix to incorrect worn state in TakenOff event handler * * Version 1.0.5: * Date: 8th Feb 2013 * Compatible with Spokes SDK version(s): 2.7.14092.0 * Changed by: Lewis Collins * Changes: * - Added flag for don/doff/dock/undock event to say if it is the initial status * so an app can ignore the initial status if it wants to (i.e. not lock screen * when it first runs and receives initial status event!) * * Version 1.0.4: * Date: 6th Feb 2013 * Compatible with Spokes SDK version(s): 2.7.14092.0 * Changed by: Lewis Collins * Changes: * - Added new method to ask if link is active * * Version 1.0.3: * Date: 1st Feb 2013 * Compatible with Spokes SDK version(s): 2.7.14092.0 * Changed by: Lewis Collins * Changes: * - Added new "line active changed" event handler so apps can know when * line to device is active or not * * Version 1.0.2: * Date: 4th December 2012 * Compatible with Spokes SDK version(s): 2.7.14092.0 * Changed by: Lewis Collins * Changes: * - Adding multiline device features e.g. for Savi 7xx * * Version 1.0.1: * Date: 30th November 2012 * Compatible with Spokes SDK version(s): 2.7.14092.0 * Changed by: Lewis Collins * Changes: * - Fixed order of events for DetachDevice flow * - Fixed need to check for null serial number member * * Version 1.0: * Date: 30th November 2012 * Compatible with Spokes SDK version(s): 2.7.14092.0 * Changed by: Lewis Collins * Changes: * - Adds code to extract serial number (thanks Nemanja) * - Adds comments to all publicly expose methods and event handlers * ******************************************************************************** * **/ namespace Plantronics.UC.SpokesWrapper { /// /// interface to allow your application's class to handle log debug tracing from the SpokesWrapper... /// public interface DebugLogger { void DebugPrint(string methodname, string str); } /// /// Struct to hold info on Plantronics device capabilities /// public struct SpokesDeviceCaps { public bool HasProximity; public bool HasMobCallerId; // means mobile caller id and mobile call state public bool HasMobCallState; // a subset of mobile caller id, just the mobile call state public bool HasDocking; public bool HasWearingSensor; public bool HasMultiline; public bool IsWireless; public string ProductId; public bool HasQDConnector; // for CC products, a QD quick disconnect connector for headset /// /// Constructor: pass in boolean values for whether it has the given device capabilities or not /// public SpokesDeviceCaps(bool HasProximity, bool HasMobCallerId, bool HasMobCallState, bool HasDocking, bool HasWearingSensor, bool HasMultiline, bool IsWireless, bool HasQDConnector = false) { this.HasProximity = HasProximity; this.HasMobCallerId = HasMobCallerId; this.HasMobCallState = HasMobCallState; this.HasDocking = HasDocking; this.HasWearingSensor = HasWearingSensor; this.HasMultiline = HasMultiline; this.IsWireless = IsWireless; this.ProductId = ""; this.HasQDConnector = HasQDConnector; } /// /// Returns a nice string representation of device capabilites, e.g. for use in logs /// public override string ToString() { return "Proximity = " + HasProximity + "\r\n" + "Mobile Caller Id = " + HasMobCallerId + "\r\n" + "Mobile Caller State = " + HasMobCallState + "\r\n" + "Dockable = " + HasDocking + "\r\n" + "Wearing Sensor = " + HasWearingSensor + "\r\n" + "Multiline = " + HasMultiline + "\r\n" + "Is Wireless = " + IsWireless + "\r\n" + "QD Connector = " + HasQDConnector + "\r\n"; } } /// /// Event args for Mute Changed event handler /// public class MuteChangedArgs : EventArgs { public bool m_muteon = false; public MuteChangedArgs(bool isMuteOn) { m_muteon = isMuteOn; } } /// /// Event args for Line Active Changed event handler /// public class LineActiveChangedArgs : EventArgs { public bool m_lineactive = false; public LineActiveChangedArgs(bool isLineActive) { m_lineactive = isLineActive; } } /// /// Event args for Attached (device attached) event handler /// public class AttachedArgs : EventArgs { public ICOMDevice m_device = null; public AttachedArgs(ICOMDevice aDevice) { m_device = aDevice; } } /// /// Event args for TakenOff/PutOn events (wearing state) event handlers /// public class WearingStateArgs : EventArgs { public bool m_worn = false; public bool m_isInitialStateEvent = false; public WearingStateArgs(bool worn, bool isInitialStateEvent) { m_worn = worn; m_isInitialStateEvent = isInitialStateEvent; } } /// /// Event args for Connected/Disconnects events (QD make/break) event handlers /// public class ConnectedStateArgs : EventArgs { public bool m_connected = false; public bool m_isInitialStateEvent = false; public ConnectedStateArgs(bool connected, bool isInitialStateEvent) { m_connected = connected; m_isInitialStateEvent = isInitialStateEvent; } } /// /// Event args for Docked/UnDocked events (docking) event handlers /// public class DockedStateArgs : EventArgs { public bool m_docked = false; public bool m_isInitialStateEvent = false; public DockedStateArgs(bool docked, bool isInitialStateEvent) { m_docked = docked; m_isInitialStateEvent = isInitialStateEvent; } } /// /// Enumeration of call states /// public enum OnCallCallState { Ringing, OnCall, Idle } /// /// Event args for OnCall event handler /// public class OnCallArgs : EventArgs { public string CallSource; public bool Incoming; public OnCallCallState State; public int CallId { get; set; } public OnCallArgs(string source, bool isIncoming, OnCallCallState state, int callid) { CallSource = source; Incoming = isIncoming; State = state; CallId = callid; } } /// /// Enumeration of mobile call states /// public enum MobileCallState { Ringing, OnCall, Idle } /// /// Event args for OnMobileCall event handler /// public class OnMobileCallArgs : EventArgs { public bool Incoming; public MobileCallState State; public OnMobileCallArgs(bool isIncoming, MobileCallState state) { Incoming = isIncoming; State = state; } } /// /// Event args for MobileCallerId event handler /// public class MobileCallerIdArgs : EventArgs { public string MobileCallerId { get; set; } public MobileCallerIdArgs(string mobilecallerid) { MobileCallerId = mobilecallerid; } } /// /// Enumeration of serial numbers in a Plantronics device (i.e. Headset and base/usb adaptor) /// public enum SerialNumberTypes { Headset, Base } /// /// Event args for SerialNumber event handler /// public class SerialNumberArgs : EventArgs { public string SerialNumber { get; set; } public SerialNumberTypes SerialNumberType { get; set; } public SerialNumberArgs(string serialnumber, SerialNumberTypes serialnumtype) { SerialNumber = serialnumber.ToUpper(); SerialNumberType = serialnumtype; } } /// /// Event args for CallAnswered event handler /// public class CallAnsweredArgs : EventArgs { public int CallId { get; set; } public string CallSource { get; set; } public CallAnsweredArgs(int callid, string callsource) { CallId = callid; CallSource = callsource; } } /// /// Event args for CallEnded event handler /// public class CallEndedArgs : EventArgs { public int CallId { get; set; } public string CallSource { get; set; } public CallEndedArgs(int callid, string callsource) { CallId = callid; CallSource = callsource; } } /// /// Event args for NotOnCall event handler /// public class NotOnCallArgs : EventArgs { public int CallId { get; set; } public string CallSource { get; set; } public NotOnCallArgs(int callid, string callsource) { CallId = callid; CallSource = callsource; } } /// /// Used with MultiLineStateArgs to hold active/held status of multiple lines (PC, Mobile, Deskphone) /// public struct MultiLineStateFlags { public bool PCActive { get; set; } public bool MobileActive { get; set; } public bool DeskphoneActive { get; set; } public bool PCHeld { get; set; } public bool MobileHeld { get; set; } public bool DeskphoneHeld { get; set; } } /// /// EventArgs used with MultiLineStateChanged event handler to receive status of multiple lines (PC, Mobile, Deskphone) /// when the state of any of these lines changes. /// public class MultiLineStateArgs : EventArgs { public MultiLineStateFlags MultiLineState { get; set; } public MultiLineStateArgs(MultiLineStateFlags multilinestate) { MultiLineState = multilinestate; } } /// /// EventArgs used with ButtonPress event handler to receive details of which button /// was pressed /// public class ButtonPressArgs : EventArgs { public DeviceHeadsetButton headsetButton; public DeviceAudioState audioType; public bool mute; public ButtonPressArgs(DeviceHeadsetButton headsetButton, DeviceAudioState audioType, bool aMute) { this.headsetButton = headsetButton; this.audioType = audioType; this.mute = aMute; } } /// /// EventArgs used with BaseButtonPress event handler to receive details of which button /// was pressed /// public class BaseButtonPressArgs : EventArgs { public DeviceBaseButton baseButton; public short dialedKey; public BaseButtonPressArgs(DeviceBaseButton baseButton, short dialedKey) { this.baseButton = baseButton; this.dialedKey = dialedKey; } } /// /// EventArgs used with CallRequested event handler to receive details of the /// number requested to dial from dialpad device (Calisto P240/800 series) /// public class CallRequestedArgs : EventArgs { public COMContact m_contact { get; set; } public CallRequestedArgs(COMContact aContact) { m_contact = aContact; } } /// /// EventArgs used with RawDataReceived event handler to receive raw /// custom events (ODP/BR) from headset. /// public class RawDataReceivedArgs : EventArgs { public string m_datareporthex { get; set; } public byte[] m_datarawbytes { get; set; } public RawDataReceivedArgs(byte[] rawbytes, string report) { m_datarawbytes = rawbytes; m_datareporthex = report; } } /// /// Enumeration of multiline device line types /// public enum Multiline_LineType { PC, Mobile, Deskphone } /// /// Defines a Spokes object which you can use to communicate with Plantronics devices. /// Cannot instantiate directly. To obtain singleton call Spokes.Instance. /// Note: using singleton model to avoid possibility of multiple instantiation /// as specified in: http://msdn.microsoft.com/en-us/library/ff650316.aspx /// public sealed class Spokes { private static volatile Spokes instance; private static object syncRoot = new Object(); private List m_AllDeviceCapabilities; private Timer connectedTimer; private int connectedRetries = 0; /// /// Default constructor, cannot be called directly. To obtain singleton call Spokes.Instance. /// private Spokes() { m_debuglog = null; PreLoadAllDeviceCapabilities(); } private void ConnectedRetryCallback(object state) { GetLastConnectedStatus(); } private SpokesDeviceCaps GetMyDeviceCapabilities() { SpokesDeviceCaps retval = new SpokesDeviceCaps(); retval.HasProximity = false; retval.HasMobCallerId = false; retval.HasMobCallState = false; retval.HasDocking = false; retval.HasWearingSensor = false; retval.HasMultiline = false; retval.IsWireless = false; retval.ProductId = ""; retval.HasQDConnector = false; string prodidstr, prodidstr2; if (m_activeDevice!=null && m_AllDeviceCapabilities.Count()>0) { prodidstr = string.Format("{0:X}", m_activeDevice.ProductId).ToUpper(); prodidstr2 = string.Format("{0:X4}", m_activeDevice.ProductId).ToUpper(); foreach (SpokesDeviceCaps caps in m_AllDeviceCapabilities) { if (caps.ProductId.CompareTo(prodidstr)==0 || caps.ProductId.CompareTo(prodidstr2)==0) { // we got a match of our product! DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Got a match of our Plantronics device in DeviceCapabilities.csv:"); DebugPrint(MethodInfo.GetCurrentMethod().Name, prodidstr); retval = caps; break; } } } return retval; } private void PreLoadAllDeviceCapabilities() { string line; SpokesDeviceCaps devicecaps; m_AllDeviceCapabilities = new List(); try { System.IO.StreamReader in_stream = new System.IO.StreamReader("DeviceCapabilities.csv"); while((line = in_stream.ReadLine()) != null) { if (line.Length>0) { if (line.Substring(0,1).CompareTo("#")!=0 && line.Substring(0,1).CompareTo(",")!=0) { // not a comment line or empty line (with only commas) devicecaps = new SpokesDeviceCaps(); string[] words = line.Split(','); int i = 0; string token = ""; foreach (string word in words) { token = word.ToUpper(); switch(i) { case 0: devicecaps.ProductId = word; break; case 1: // no action - this is the device name we don't need break; case 2: devicecaps.HasProximity = token.CompareTo("YES")==0 ? true : false; break; case 3: devicecaps.HasMobCallerId = token.CompareTo("YES") == 0 ? true : false; break; case 4: devicecaps.HasMobCallState = token.CompareTo("YES") == 0 ? true : false; break; case 5: devicecaps.HasDocking = token.CompareTo("YES") == 0 ? true : false; break; case 6: devicecaps.HasWearingSensor = token.CompareTo("YES") == 0 ? true : false; break; case 7: devicecaps.HasMultiline = token.CompareTo("YES") == 0 ? true : false; break; case 8: devicecaps.IsWireless = token.CompareTo("YES") == 0 ? true : false; break; case 9: devicecaps.HasQDConnector = token.CompareTo("YES") == 0 ? true : false; // now, add the devicecaps to our list: m_AllDeviceCapabilities.Add(devicecaps); break; } i++; } //DebugPrint(__FUNCTION__, "got some tokens"); } } //devicecaps } in_stream.Close(); } catch(Exception e) { //std::cerr << "Exception opening/reading/closing file\n"; DebugPrint(MethodInfo.GetCurrentMethod().Name, "Exception reading DeviceCapabilities.csv. Does this file exist in current working directory?"); } } /// /// Default desctructor, disconnects from Spokes /// ~Spokes() { if (isConnected) Disconnect(); } /// /// Returns the single Instance of Spokes which you can use to communicate with Plantronics devices /// public static Spokes Instance { get { if (instance == null) { lock (syncRoot) { // Instantiate a singleton Spokes object if (instance == null) instance = new Spokes(); } } return instance; } } #region Spokes interfaces definitions static ICOMSessionManager m_sessionComManager = null; static COMSession m_comSession = null; static COMDevice m_activeDevice = null; static ICOMSessionManagerEvents_Event m_sessionManagerEvents; static ICOMCallEvents_Event m_sessionEvents; static ICOMDeviceEvents_Event m_deviceComEvents; static ICOMDeviceListenerEvents_Event m_deviceListenerEvents; static ICOMATDCommand m_atdCommand; static ICOMHostCommand m_hostCommand; static ICOMHostCommandExt m_hostCommandExt; static ICOMUserPreference m_userPreference; static ICOMAdvanceSettings m_advanceSettings; static ICOMDeviceSettings m_deviceSettings; static ICOMDeviceListener m_deviceListener; static ICOMDeviceSettingsExt m_deviceSettingsExt; public static string m_devicename = ""; #endregion DebugLogger m_debuglog = null; /// /// A property containing flags that indicate the capabilities of the attached Plantronics device (if any). /// public SpokesDeviceCaps DeviceCapabilities; /// /// Returns boolean to indicate whether there is currently a Plantronics device attached to the PC or not. /// public bool HasDevice { get { return (m_activeDevice != null); } } /// /// Returns a reference to the currently active Plantronics call control device attached to the PC (if any). /// public COMDevice GetDevice { get { return m_activeDevice; } } bool m_mobIncoming = false; // mobile call direction flag bool m_voipIncoming = false; // mobile call direction flag #region Base Device State member fields MultiLineStateFlags m_activeHeldFlags; #endregion // C# event handlers that can be used to register for Spokes events... // Wearing sensor: public delegate void TakenOffEventHandler(object sender, WearingStateArgs e); public delegate void PutOnEventHandler(object sender, WearingStateArgs e); // Proximity: public delegate void NearEventHandler(object sender, EventArgs e); public delegate void FarEventHandler(object sender, EventArgs e); public delegate void ProximityEnabledEventHandler(object sender, EventArgs e); public delegate void ProximityDisabledEventHandler(object sender, EventArgs e); public delegate void ProximityUnknownEventHandler(object sender, EventArgs e); // In range/out of range: public delegate void InRangeEventHandler(object sender, EventArgs e); public delegate void OutOfRangeEventHandler(object sender, EventArgs e); // Docked/undocked: public delegate void DockedEventHandler(object sender, DockedStateArgs e); public delegate void UnDockedEventHandler(object sender, DockedStateArgs e); // CC Connected/Disconnected QD make/break public delegate void ConnectedEventHandler(object sender, ConnectedStateArgs e); public delegate void DisconnectedEventHandler(object sender, ConnectedStateArgs e); // Mobile caller id: public delegate void MobileCallerIdEventHandler(object sender, MobileCallerIdArgs e); public delegate void OnMobileCallEventHandler(object sender, OnMobileCallArgs e); public delegate void NotOnMobileCallEventHandler(object sender, EventArgs e); // Serial number (receives as result of earlier request for serial number): public delegate void SerialNumberEventHandler(object sender, SerialNumberArgs e); // Call control (headset button call control notification from Spokes): public delegate void CallAnsweredEventHandler(object sender, CallAnsweredArgs e); public delegate void CallEndedEventHandler(object sender, CallEndedArgs e); public delegate void CallSwitchedEventHandler(object sender, EventArgs e); // Call state notification (is user on a call or not?): public delegate void OnCallEventHandler(object sender, OnCallArgs e); public delegate void NotOnCallEventHandler(object sender, NotOnCallArgs e); // Mute sync: public delegate void MuteChangedEventHandler(object sender, MuteChangedArgs e); // Line active awareness: public delegate void LineActiveChangedEventHandler(object sender, LineActiveChangedArgs e); // Device attach/detach: public delegate void AttachedEventHandler(object sender, AttachedArgs e); public delegate void DetachedEventHandler(object sender, EventArgs e); // Device capabilities changed (depends on type of device attached): public delegate void CapabilitiesChangedEventHandler(object sender, EventArgs e); // Multiline device line state changed (for multi-line device, e.g. Savi 7xx): public delegate void MultiLineStateChangedEventHandler(object sender, MultiLineStateArgs e); // Button press event: public delegate void ButtonPressEventHandler(object sender, ButtonPressArgs e); // Base button press event: public delegate void BaseButtonPressEventHandler(object sender, BaseButtonPressArgs e); // Call Requested event: public delegate void CallRequestedEventHandler(object sender, CallRequestedArgs e); // Battery Level Changed event: public delegate void BatteryLevelChangedEventHandler(object sender, EventArgs e); // Battery Level Changed event: public delegate void RawDataReceivedEventHandler(object sender, RawDataReceivedArgs e); // Definition of event handlers that clients can use to be notified whenever the // spokes event occurs: // Wearing sensor: ************************************************************ /// /// Triggered when the user takes off the headset (with products that support wearing sensor) /// public event TakenOffEventHandler TakenOff; /// /// Triggered when the user puts on the headset (with products that support wearing sensor) /// public event PutOnEventHandler PutOn; // Proximity: ************************************************************ /// /// Triggered when a Plantronics device comes near to PC dongle /// public event NearEventHandler Near; /// /// Triggered when a Plantronics device goes far from PC dongle /// public event FarEventHandler Far; /// /// Triggered when a Plantronics device proximity has been enabled /// public event ProximityEnabledEventHandler ProximityEnabled; /// /// Triggered when a Plantronics device proximity has been disabled /// public event ProximityDisabledEventHandler ProximityDisabled; /// /// Triggered when a Plantronics device proximity is unknown /// public event ProximityEnabledEventHandler ProximityUnknown; /// /// Triggered when a Plantronics device comes into range of PC dongle /// public event InRangeEventHandler InRange; /// /// Triggered when a Plantronics device goes out of range of PC dongle /// public event OutOfRangeEventHandler OutOfRange; /// /// Triggered when a Plantronics device is docked in its base or cradle /// public event DockedEventHandler Docked; /// /// Triggered when a Plantronics device is undocked from its base or cradle /// public event DockedEventHandler UnDocked; /// /// Triggered when a CC Plantronics headset is connected to the amplifier (QD connecter) /// public event ConnectedEventHandler Connected; /// /// Triggered when a CC Plantronics headset is disconnected from the amplifier (QD connecter) /// public event DisconnectedEventHandler Disconnected; // Mobile caller id: ************************************************************ /// /// Triggered when a caller id has been received /// public event MobileCallerIdEventHandler MobileCallerId; // Call state notification (is user on a call or not?): ************************************************************ /// /// Triggered when some mobile calling activity is detected with the device /// public event OnMobileCallEventHandler OnMobileCall; /// /// Triggered when mobile calling activity comes to an end with the device /// public event NotOnMobileCallEventHandler NotOnMobileCall; // Serial number (receives as result of earlier request for serial number): ************************************************************ /// /// Triggered when a serial number has been received from device /// public event SerialNumberEventHandler SerialNumber; // Call control (headset button call control notification from Spokes): ************************************************************ /// /// Triggered when the user answers a call using the headset device /// public event CallAnsweredEventHandler CallAnswered; /// /// Triggered when the user answers a call using the headset device /// public event CallEndedEventHandler CallEnded; /// /// Triggered when the user tries to switch call using the headset device by pressing switch (flash) button /// public event CallSwitchedEventHandler CallSwitched; // Call state notification (is user on a call or not?): ************************************************************ /// /// Triggered when some calling activity is detected with the device /// public event OnCallEventHandler OnCall; /// /// Triggered when calling activity comes to an end with the device /// public event NotOnCallEventHandler NotOnCall; // Mute sync: ************************************************************ /// /// Triggered when the user mutes or unmutes the headset device /// public event MuteChangedEventHandler MuteChanged; // Mute sync: ************************************************************ /// /// Triggered when the spokes activates or deactivates the line to the headset device /// public event LineActiveChangedEventHandler LineActiveChanged; // Device attach/detach: ************************************************************ /// /// Triggered when the user attached a Plantronics device /// public event AttachedEventHandler Attached; /// /// Triggered when the user detaches a Plantronics device /// public event DetachedEventHandler Detached; // Device capabilities changed (depends on type of device attached): ************************************************************ /// /// Triggered when the capabilities available on the device changes, e.g. asyncronous proximity registration is completed or mobile caller id registration is completed /// public event CapabilitiesChangedEventHandler CapabilitiesChanged; // Multiline device line state changed (for multi-line device, e.g. Savi 7xx): ************************************************************ /// /// Triggered when there is a change of the active or held states of any of the lines of multi-line device (e.g. Savi 7xx) /// public event MultiLineStateChangedEventHandler MultiLineStateChanged; // Triggered when a button press event is generated by device: ************************************************************ /// /// Triggered when a button press event is generated by device /// NOTE: you are advised NOT to use headset buttons e.g. talk button for /// call control, but rather use the IncomingCall/OutgoingCall functions /// and CallAnswered/CallEnded event handlers. (more notes in SpokesWrapper.cs) /// public event ButtonPressEventHandler ButtonPress; // Triggered when a base button press event is generated by device: ************************************************************ /// /// Triggered when a base button press event is generated by device /// public event BaseButtonPressEventHandler BaseButtonPress; // Triggered when a user call requested event is received from dialpad device: ************************************************************ /// /// Triggered when a user call requested event is received from dialpad device /// public event CallRequestedEventHandler CallRequested; // Triggered when the battery level or battery status changes on the device: ************************************************************ /// /// Triggered when the battery level or battery status changes on the device. /// In response you can call GetBatteryLevel method to /// get the battery level. /// public event BatteryLevelChangedEventHandler BatteryLevelChanged; // Triggered when the Plantronics device has sent a raw (ODP/BR) report to PC: ************************************************************ /// /// Triggered when the Plantronics device has sent a raw (ODP/BR) report to PC. /// public event RawDataReceivedEventHandler RawDataReceived; // Now for the implementation of the event handlers: // Wearing sensor: ************************************************************ // Invoke the Doffed event; called whenever user doffs (takes off) their headset private void OnTakenOff(WearingStateArgs e) { if (TakenOff != null) TakenOff(this, e); } private void OnPutOn(WearingStateArgs e) { if (PutOn != null) PutOn(this, e); } // Proximity: ************************************************************ private void OnNear(EventArgs e) { if (Near != null) Near(this, e); } private void OnFar(EventArgs e) { if (Far != null) Far(this, e); } private void OnProximityEnabled(EventArgs e) { if (ProximityEnabled != null) ProximityEnabled(this, e); } private void OnProximityDisabled(EventArgs e) { if (ProximityDisabled != null) ProximityDisabled(this, e); } private void OnProximityUnknown(EventArgs e) { if (ProximityUnknown != null) ProximityUnknown(this, e); } private void OnInRange(EventArgs e) { if (InRange != null) InRange(this, e); } private void OnOutOfRange(EventArgs e) { if (OutOfRange != null) OutOfRange(this, e); } private void OnDocked(DockedStateArgs e) { if (Docked != null) Docked(this, e); } private void OnUnDocked(DockedStateArgs e) { if (UnDocked != null) UnDocked(this, e); //if (e.m_isInitialStateEvent) //{ // m_battlevEventCount = 0; //} } private void OnConnected(ConnectedStateArgs e) { if (Connected != null) Connected(this, e); } private void OnDisconnected(ConnectedStateArgs e) { if (Disconnected != null) Disconnected(this, e); } // Mobile caller id: ************************************************************ private void OnMobileCallerId(MobileCallerIdArgs e) { if (MobileCallerId != null) MobileCallerId(this, e); } private void OnOnMobileCall(OnMobileCallArgs e) { if (OnMobileCall != null) OnMobileCall(this, e); } private void OnNotOnMobileCall(EventArgs e) { if (NotOnMobileCall != null) NotOnMobileCall(this, e); } // Serial number (receives as result of earlier request for serial number): ************************************************************ private void OnSerialNumber(SerialNumberArgs e) { if (SerialNumber != null) SerialNumber(this, e); } // Call control (headset button call control notification from Spokes): ************************************************************ private void OnCallAnswered(CallAnsweredArgs e) { if (CallAnswered != null) CallAnswered(this, e); } private void OnCallEnded(CallEndedArgs e) { if (CallEnded != null) CallEnded(this, e); } private void OnCallSwitched(EventArgs e) { if (CallSwitched != null) CallSwitched(this, e); } // Call state notification (is user on a call or not?): ************************************************************ private void OnOnCall(OnCallArgs e) { if (OnCall != null) OnCall(this, e); } private void OnNotOnCall(NotOnCallArgs e) { if (NotOnCall != null) NotOnCall(this, e); } // Mute sync: ************************************************************ private void OnMuteChanged(MuteChangedArgs e) { if (MuteChanged != null) MuteChanged(this, e); } // Line active: ************************************************************ private void OnLineActiveChanged(LineActiveChangedArgs e) { if (LineActiveChanged != null) LineActiveChanged(this, e); } // Device attach/detach: ************************************************************ private void OnAttached(AttachedArgs e) { if (Attached != null) Attached(this, e); } private void OnDetached(EventArgs e) { if (Detached != null) Detached(this, e); } // Device capabilities changed (depends on type of device attached): ************************************************************ private void OnCapabilitiesChanged(EventArgs e) { if (CapabilitiesChanged != null) CapabilitiesChanged(this, e); } // Multiline device line state changed (for multi-line device, e.g. Savi 7xx): ************************************************************ private void OnMultiLineStateChanged(MultiLineStateArgs e) { if (MultiLineStateChanged != null) MultiLineStateChanged(this, e); } // Triggered when a button press event is generated by device: ************************************************************ private void OnButtonPress(ButtonPressArgs e) { if (ButtonPress != null) ButtonPress(this, e); } // Triggered when a base button press event is generated by device: ************************************************************ private void OnBaseButtonPress(BaseButtonPressArgs e) { if (BaseButtonPress != null) BaseButtonPress(this, e); } // Triggered when a user call requested event is received from dialpad device: ************************************************************ private void OnCallRequested(CallRequestedArgs e) { if (CallRequested != null) CallRequested(this, e); } // Triggered when battery status changes on attached device: ************************************************************ private void OnBatteryLevelChanged(EventArgs e) { if (BatteryLevelChanged != null) BatteryLevelChanged(this, e); } // Triggered when the Plantronics device has sent a raw (ODP/BR) report to PC: ************************************************************ private void OnRawDataReceived(RawDataReceivedArgs e) { if (RawDataReceived != null) RawDataReceived(this, e); } bool isConnected = false; private bool m_lastdocked = false; private bool m_lastconnected = true; //private int m_battlevEventCount = 0; private bool m_firstlegenddockcheck = true; // will become false after first check of Legend docked state (via charging events) private bool m_ignorenextundockedevent = false; public string m_baseserial = ""; private string m_sessionName; //private bool m_ignorenextbattlevevent = false; /// /// If your application class implements the Spokes.DebugLogger interface you can pass a reference to your application class /// to the SetLogger method. This allows your class to be responsible for debug logging of Spokes related debug trace information. /// /// For this parameter pass the "this" reference of your class that implements Spokes.DebugLogger interface. public void SetLogger(DebugLogger aLogger) { m_debuglog = aLogger; } /// /// This method returns a boolean to indicate if the Spokes software runtime is currently installed on the system. /// If the return value is false then any subsequent attempt to call Spokes.Instance.Connect("My App") will fail /// because it means that Spokes is not installed so there is no out of proc COM Service for your app to connect to. /// Note: Is also called by default at start of Connect method, so it is not necessary to call this directly from /// your app, but you have the option. /// Note: current version of this function is designed for Spokes 2.x and 3.x. For future major releases would need updating /// in IsSpokesComSessionManagerClassRegistered private function below. /// public bool IsSpokesInstalled(int spokesMajorVersion = 3) // TODO: always insert the CORRECT major version for this Spokes Wrapper version here! { return IsSpokesComSessionManagerClassRegistered(spokesMajorVersion); } private bool IsSpokesComSessionManagerClassRegistered(int spokesMajorVersion) { bool foundCOMSessionManagerKey = false; DebugPrint(MethodInfo.GetCurrentMethod().Name, "About to look see if Spokes SessionManager is in registry"); try { // reg keys of interest... string Spokes2xKeyName = @"Plantronics.UC.Common.SessionComManager\CLSID"; string Spokes3xKeyName = @"Plantronics.COMSessionManager\CLSID"; string Spokes2xSubKeyName = @"{F9E7AE8D-31E2-4968-BA53-3CC5E5A3100A}"; string Spokes3xSubKeyName = @"{750B4A16-1338-4DB0-85BB-C6C89E4CB9AC}"; // open them all... Microsoft.Win32.RegistryKey Spokes2xKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(Spokes2xKeyName, false); // non writable Microsoft.Win32.RegistryKey Spokes3xKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(Spokes3xKeyName, false); // non writable DebugPrint(MethodInfo.GetCurrentMethod().Name, "About to check if Spokes is installed, Major Version = " + spokesMajorVersion + ".x"); switch (spokesMajorVersion) { case 2: // is Spokes 2x installed? if (Spokes2xKey != null) { if (Spokes2xKey.GetValue(null) != null) { // did we find Spokes 2x SessionCOMManager key? if (Spokes2xKey.GetValue(null).ToString() == Spokes2xSubKeyName) foundCOMSessionManagerKey = true; } Spokes2xKey.Close(); } break; case 3: // is Spokes 3x installed? if (Spokes3xKey != null) { if (Spokes3xKey.GetValue(null) != null) { // did we find Spokes 3x COMSessionManager key? if (Spokes3xKey.GetValue(null).ToString() == Spokes3xSubKeyName) foundCOMSessionManagerKey = true; } Spokes3xKey.Close(); } break; default: DebugPrint(MethodInfo.GetCurrentMethod().Name, "Attempt to check for unknown Spokes Major Version: " + spokesMajorVersion); break; } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "An exception was caught while looking to see if Spokes SessionManager is in registry.\r\nException = " + e.ToString()); } return foundCOMSessionManagerKey; } /// /// Instruct Spokes object to connect to Spokes runtime engine and register itself /// so that it can begin to communicate with the attached Plantronics device. /// /// Optional name of your appplication's session within Spokes runtime engine. If omitted it will default to "COM Session". public bool Connect(string SessionName = "COM Session") { m_sessionName = SessionName; if (!IsSpokesInstalled()) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "FATAL ERROR: cannot connect if Spokes COMSessionManager/SessionCOMManager class is not registered! Spokes not installed (or wrong major version installed for this Spokes Wrapper)!"); throw new Exception("Cannot connect if Spokes COM SessionManager class is not registered! Plantronics Hub not installed!"); } if (isConnected) return true; DeviceCapabilities = new SpokesDeviceCaps(false, false, false, false, false, false, false, false); // we don't yet know what the capabilities are OnCapabilitiesChanged(EventArgs.Empty); bool success = false; try { //////////////////////////////////////////////////////////////////////////////////////// // create session manager, and attach to session manager events m_sessionComManager = new COMSessionManager(); m_sessionManagerEvents = m_sessionComManager as ICOMSessionManagerEvents_Event; if (m_sessionManagerEvents != null) { m_sessionManagerEvents.onCallStateChanged += m_sessionComManager_CallStateChanged; m_sessionManagerEvents.onDeviceStateChanged += m_sessionComManager_DeviceStateChanged; } else success = false; //////////////////////////////////////////////////////////////////////////////////////// // register session to spokes m_sessionComManager.Register(SessionName, out m_comSession); if (m_comSession != null) { // attach to session call events m_sessionEvents = m_comSession as ICOMCallEvents_Event; if (m_sessionEvents != null) { m_sessionEvents.onCallRequested += m_sessionEvents_CallRequested; m_sessionEvents.onCallStateChanged += m_sessionEvents_CallStateChanged; } else success = false; //////////////////////////////////////////////////////////////////////////////////////// // Attach to active device and print all device information // and registers for proximity (if supported by device) AttachDevice(); // note: with latest Hub 3.9 it is hanging here is Hub was only started as a result of my COM request!!!!! success = true; } } catch (System.Exception e) { success = false; throw new Exception("Failed to connect to Spokes", e); } return success; } /// /// Instruct Spokes object to disconnect from Spokes runtime engine and unregister its /// session in Spokes. /// public void Disconnect() { DetachDevice(); try { if (m_comSession != null) { if (m_sessionEvents != null) { // release session events m_sessionEvents.onCallRequested -= m_sessionEvents_CallRequested; m_sessionEvents.onCallStateChanged -= m_sessionEvents_CallStateChanged; Marshal.ReleaseComObject(m_sessionEvents); m_sessionEvents = null; } // unregister session if (m_sessionManagerEvents != null) { m_sessionManagerEvents.onCallStateChanged -= m_sessionComManager_CallStateChanged; m_sessionManagerEvents.onDeviceStateChanged -= m_sessionComManager_DeviceStateChanged; } //#if DEBUG // NOTE this is failing in Spokes 3.0 // TODO so for now disable it in Release version // so it does not crash when exiting! m_sessionComManager.Unregister((COMSession)m_comSession); //#endif Marshal.ReleaseComObject(m_comSession); m_comSession = null; } if (m_sessionComManager != null) { Marshal.ReleaseComObject(m_sessionComManager); m_sessionComManager = null; } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Exception caught in disconnect: " + e.ToString()); } } #region Print Session and Device events to console // print session manager events void m_sessionComManager_DeviceStateChanged(COMStateDeviceEventArgs e) { // if our "Active device" was unplugged, detach from it and attach to new one (if available) if (e.DeviceState == DeviceChangeState.DeviceState_Removed && m_activeDevice != null) // && string.Compare(e.DevicePath, m_activeDevice.DevicePath, true) == 0) { DetachDevice(); AttachDevice(); // attach next available device (if any) } else if (e.DeviceState == DeviceChangeState.DeviceState_Added) { // if we previously had an attached device, first unattach it: if (m_activeDevice != null) { DetachDevice(); m_activeDevice = null; } // if device is plugged, and we don't have "Active device", just attach to it AttachDevice(); } } // print session manager events void m_sessionComManager_CallStateChanged(COMCallEventArgs e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: call state event = " + e.ToString()); if (e.CallSource != m_sessionName) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Processing session event from source = " + e.CallSource); switch (e.CallState) { case CallState.CallState_CallRinging: m_voipIncoming = true; // Getting here indicates user is ON A CALL! DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Calling activity detected!" + e.ToString()); OnOnCall(new OnCallArgs(e.CallSource, m_voipIncoming, OnCallCallState.Ringing, e.call.Id)); break; case CallState.CallState_MobileCallRinging: m_mobIncoming = true; // user incoming mobile call DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Mobile Calling activity detected!" + e.ToString()); OnOnMobileCall(new OnMobileCallArgs(m_mobIncoming, MobileCallState.Ringing)); break; case CallState.CallState_MobileCallInProgress: DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Mobile Calling activity detected!" + e.ToString()); OnOnMobileCall(new OnMobileCallArgs(m_mobIncoming, MobileCallState.OnCall)); break; case CallState.CallState_AcceptCall: case CallState.CallState_CallInProgress: DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Call was ansswered/in progress!" + e.ToString()); OnOnCall(new OnCallArgs(e.CallSource, m_voipIncoming, OnCallCallState.OnCall, e.call.Id)); OnCallAnswered(new CallAnsweredArgs(e.call.Id, e.CallSource)); break; case CallState.CallState_HoldCall: case CallState.CallState_Resumecall: case CallState.CallState_TransferToHeadSet: case CallState.CallState_TransferToSpeaker: // Getting here indicates user is ON A CALL! DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Calling activity detected!" + e.ToString()); OnOnCall(new OnCallArgs(e.CallSource, m_voipIncoming, OnCallCallState.OnCall, e.call.Id)); break; case CallState.CallState_MobileCallEnded: m_mobIncoming = false; // Getting here indicates user HAS FINISHED A CALL! DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Mobile Calling activity ended." + e.ToString()); OnNotOnMobileCall(EventArgs.Empty); break; case CallState.CallState_CallEnded: case CallState.CallState_CallIdle: case CallState.CallState_RejectCall: case CallState.CallState_TerminateCall: m_voipIncoming = false; // Getting here indicates user HAS FINISHED A CALL! DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Calling activity ended." + e.ToString()); OnNotOnCall(new NotOnCallArgs(e.call.Id, e.CallSource)); OnCallEnded(new CallEndedArgs(e.call.Id, e.CallSource)); break; default: // ignore other call state events break; } } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Ignoring session event from my session, as will come seperately via ICallEvents = " + e.CallSource); } } // used internally to get mobile caller id when we are notified of mobile caller id // event from Spokes private string GetMobileCallerID() { string retval = ""; if (m_atdCommand != null) { try { retval = m_atdCommand.CallerId; } catch (System.Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: INFO: Exception occured getting mobile caller id\r\nException = " + e.ToString()); } } return retval; } // print session events void m_sessionEvents_CallStateChanged(COMCallEventArgs e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: ICallEvents call state event = " + e.ToString()); switch (e.CallState) { case CallState.CallState_CallRinging: m_voipIncoming = true; // Getting here indicates user is ON A CALL! DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Calling activity detected!" + e.ToString()); OnOnCall(new OnCallArgs(e.CallSource, m_voipIncoming, OnCallCallState.Ringing, e.call.Id)); break; case CallState.CallState_MobileCallRinging: m_mobIncoming = true; // user incoming mobile call DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Mobile Calling activity detected!" + e.ToString()); OnOnMobileCall(new OnMobileCallArgs(m_mobIncoming, MobileCallState.Ringing)); break; case CallState.CallState_MobileCallInProgress: DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Mobile Calling activity detected!" + e.ToString()); OnOnMobileCall(new OnMobileCallArgs(m_mobIncoming, MobileCallState.OnCall)); break; case CallState.CallState_AcceptCall: case CallState.CallState_CallInProgress: DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Call was ansswered/in progress!" + e.ToString()); OnOnCall(new OnCallArgs(e.CallSource, m_voipIncoming, OnCallCallState.OnCall, e.call.Id)); OnCallAnswered(new CallAnsweredArgs(e.call.Id, e.CallSource)); break; case CallState.CallState_HoldCall: case CallState.CallState_Resumecall: case CallState.CallState_TransferToHeadSet: case CallState.CallState_TransferToSpeaker: // Getting here indicates user is ON A CALL! DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Calling activity detected!" + e.ToString()); OnOnCall(new OnCallArgs(e.CallSource, m_voipIncoming, OnCallCallState.OnCall, e.call.Id)); break; case CallState.CallState_MobileCallEnded: m_mobIncoming = false; // Getting here indicates user HAS FINISHED A CALL! DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Mobile Calling activity ended." + e.ToString()); OnNotOnMobileCall(EventArgs.Empty); break; case CallState.CallState_CallEnded: case CallState.CallState_CallIdle: case CallState.CallState_RejectCall: case CallState.CallState_TerminateCall: m_voipIncoming = false; // Getting here indicates user HAS FINISHED A CALL! DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Calling activity ended." + e.ToString()); OnNotOnCall(new NotOnCallArgs(e.call.Id, e.CallSource)); OnCallEnded(new CallEndedArgs(e.call.Id, e.CallSource)); break; default: // ignore other call state events break; } } // print session events void m_sessionEvents_CallRequested(COMCallRequestEventArgs e) { string contact = e.contact != null ? e.contact.Name : "none"; DebugPrint(MethodInfo.GetCurrentMethod().Name, string.Format("Session CallRequested event: Contact:({0})", contact)); OnCallRequested(new CallRequestedArgs(e.contact)); } // print device listner events void m_deviceListenerEvents_Handler(COMDeviceListenerEventArgs e) { try { switch (e.DeviceEventType) { case COMDeviceEventType.DeviceEventType_ATDButtonPressed: break; case COMDeviceEventType.DeviceEventType_ATDStateChanged: DeviceListener_ATDStateChanged(e); break; case COMDeviceEventType.DeviceEventType_BaseButtonPressed: DeviceListener_BaseButtonPressed(e); break; case COMDeviceEventType.DeviceEventType_BaseStateChanged: DeviceListener_BaseStateChanged(e); break; case COMDeviceEventType.DeviceEventType_HeadsetButtonPressed: DebugPrint(MethodInfo.GetCurrentMethod().Name, "DeviceEventType_HeadsetButtonPressed " + e.HeadsetButton.ToString()); OnButtonPress(new ButtonPressArgs(e.HeadsetButton, m_activeDevice.HostCommand.AudioState, m_activeDevice.HostCommand.mute)); break; case COMDeviceEventType.DeviceEventType_HeadsetStateChanged: default: break; } } catch (Exception exc) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception caught: " + exc.ToString()); } } // Respond to various base button presses by passing button pressed event to app event handler private void DeviceListener_BaseButtonPressed(COMDeviceListenerEventArgs e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseButtonPressed: {0}", e.BaseButton.ToString())); OnBaseButtonPress(new BaseButtonPressArgs(e.BaseButton, e.DialedKey)); } // Respond to various base state changes by updating our knowledge of multiline active/held states... void DeviceListener_BaseStateChanged(COMDeviceListenerEventArgs e) { // write your own code to react to the state change switch (e.BaseStateChange) { case DeviceBaseStateChange.BaseStateChange_Unknown: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: Unknown")); break; case DeviceBaseStateChange.BaseStateChange_PstnLinkEstablished: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: PstnLinkEstablished")); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; case DeviceBaseStateChange.BaseStateChange_PstnLinkDown: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: PstnLinkDown")); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; case DeviceBaseStateChange.BaseStateChange_VoipLinkEstablished: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: VoipLinkEstablished")); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; case DeviceBaseStateChange.BaseStateChange_VoipLinkDown: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: VoipLinkDown")); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; case DeviceBaseStateChange.BaseStateChange_AudioMixer: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: AudioMixer")); break; case DeviceBaseStateChange.BaseStateChange_RFLinkWideBand: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: RFLinkWideBand")); break; case DeviceBaseStateChange.BaseStateChange_RFLinkNarrowBand: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: RFLinkNarrowBand")); break; case DeviceBaseStateChange.BaseStateChange_MobileLinkEstablished: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: MobileLinkEstablished")); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; case DeviceBaseStateChange.BaseStateChange_MobileLinkDown: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: MobileLinkDown")); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; case DeviceBaseStateChange.BaseStateChange_InterfaceStateChanged: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: InterfaceStateChanged")); GetHoldStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; case DeviceBaseStateChange.BaseStateChange_AudioLocationChanged: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: AudioLocationChanged")); break; } } void m_deviceListenerEvents_HandlerMethods(COMDeviceListenerEventArgs e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Received Spokes Event: " + e.DeviceEventType.ToString()); switch (e.DeviceEventType) { case COMDeviceEventType.DeviceEventType_HeadsetStateChanged: switch (e.HeadsetStateChange) { case DeviceHeadsetStateChange.HeadsetStateChange_Don: OnPutOn(new WearingStateArgs(true, false)); break; case DeviceHeadsetStateChange.HeadsetStateChange_Doff: OnTakenOff(new WearingStateArgs(false, false)); break; case DeviceHeadsetStateChange.HeadsetStateChange_Near: OnNear(EventArgs.Empty); break; case DeviceHeadsetStateChange.HeadsetStateChange_Far: OnFar(EventArgs.Empty); break; case DeviceHeadsetStateChange.HeadsetStateChange_ProximityDisabled: // Note: intepret this event as that the mobile phone has gone out of Bluetooth // range and is no longer paired to the headset. // Lock the PC, but immediately re-enable proximity OnProximityDisabled(EventArgs.Empty); // Immediately re-enable proximity RegisterForProximity(true); break; case DeviceHeadsetStateChange.HeadsetStateChange_ProximityEnabled: OnProximityEnabled(EventArgs.Empty); break; case DeviceHeadsetStateChange.HeadsetStateChange_ProximityUnknown: OnProximityUnknown(EventArgs.Empty); break; case DeviceHeadsetStateChange.HeadsetStateChange_InRange: OnInRange(EventArgs.Empty); //// Immediately re-enable proximity //RegisterForProximity(true); //// Request headset serial number (maybe user paired with another?) //RequestSingleSerialNumber(SerialNumberTypes.Headset); // New, get all the device state info on inrange trigger: // now poll for current state (proximity, mobile call status, donned status, mute status) GetInitialDeviceState(); // tell app to look again at battery level (headset in range) OnBatteryLevelChanged(EventArgs.Empty); break; case DeviceHeadsetStateChange.HeadsetStateChange_OutofRange: OnOutOfRange(EventArgs.Empty); OnSerialNumber(new SerialNumberArgs("", SerialNumberTypes.Headset)); // tell app to look again at battery level (headset out of range / disconnected) OnBatteryLevelChanged(EventArgs.Empty); break; case DeviceHeadsetStateChange.HeadsetStateChange_BatteryLevel: // note; on legend we always get a battery level when docking... // what is in this event, anything we can use to infer docked? DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("HeadsetStateChanged: BatteryLevel")); // TODO - rework this code for Spokes 3.0 - it gets stuck in an endless loop! //if (!m_ignorenextbattlevevent && (!m_lastdocked && m_battlevEventCount < 10)) // TEST, allow 10 of these thru so it gets docking more quickly on legend! //{ // // Only if we were undocked and there were no battery level events // // since we last undocked should we detect batt charge status (docking status). // // This is so this only happens once, otherwise we get stuck in a loop // // Because looking at the battery info triggers another batterylevel changed // // event! // m_ignorenextundockedevent = true; // m_ignorenextbattlevevent = true; // m_lastdocked = DetectLegendDockedState(false); // NOTE: will ALWAYS trigger another batterylevel change event // // AND another UnDocked event if we are undocked! // m_battlevEventCount++; //} //else //{ // if (m_ignorenextbattlevevent) m_ignorenextbattlevevent = false; // if (m_lastdocked) m_ignorenextundockedevent = false; //} // tell app to look at battery level (it has changed) OnBatteryLevelChanged(EventArgs.Empty); break; case DeviceHeadsetStateChange.HeadsetStateChange_Docked: case DeviceHeadsetStateChange.HeadsetStateChange_DockedCharging: // new, for legend, but is v SLOW, sometimes never comes // only send to app the docked value if it is different to m_lastdocked (i.e. if it has changed) if (!m_lastdocked) OnDocked(new DockedStateArgs(true, false)); m_lastdocked = true; //m_battlevEventCount = 0; break; case DeviceHeadsetStateChange.HeadsetStateChange_UnDocked: if (!m_ignorenextundockedevent) { // only send to app the docked value if it is different to m_lastdocked (i.e. if it has changed) if (m_lastdocked) OnUnDocked(new DockedStateArgs(false, false)); // if (m_lastdocked) m_battlevEventCount = 0; // if we were docked before, set battlev event count to zero // // i.e. we would like to check the next battery level event m_lastdocked = false; } m_ignorenextundockedevent = false; break; case DeviceHeadsetStateChange.HeadsetStateChange_MuteON: OnMuteChanged(new MuteChangedArgs(true)); break; case DeviceHeadsetStateChange.HeadsetStateChange_MuteOFF: OnMuteChanged(new MuteChangedArgs(false)); break; case DeviceHeadsetStateChange.HeadsetStateChange_MonoON: OnLineActiveChanged(new LineActiveChangedArgs(true)); break; case DeviceHeadsetStateChange.HeadsetStateChange_MonoOFF: OnLineActiveChanged(new LineActiveChangedArgs(false)); break; // NEW CC events case DeviceHeadsetStateChange.HeadsetStateChange_Connected: case DeviceHeadsetStateChange.HeadsetStateChange_QDConnected: OnConnected(new ConnectedStateArgs(true, false)); break; case DeviceHeadsetStateChange.HeadsetStateChange_Disconnected: case DeviceHeadsetStateChange.HeadsetStateChange_QDDisconnected: OnDisconnected(new ConnectedStateArgs(false, false)); break; default: break; } break; default: break; } } private bool DetectLegendDockedState(bool getinitialstate = false) { bool isdocked = false; if (m_activeDevice != null && m_activeDevice.ProductName.ToUpper().Contains("BT300")) { try { if (m_hostCommandExt != null) { switch (m_hostCommandExt.BatteryInfo.ChargingStatus) { case DeviceChargingStatus.BTChargingStatus_ConnectedAndChargeError: case DeviceChargingStatus.BTChargingStatus_ConnectedAndFastCharging: case DeviceChargingStatus.BTChargingStatus_ConnectedAndTrickleCharging: case DeviceChargingStatus.BTChargingStatus_ConnectedNotCharging: // charging, so send docked event (for legend) DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BT300 received charger connected status: assume Legend is docked!")); // only send to app the docked value if it is different to m_lastdocked (i.e. if it has changed) if (IsFirstLegendDockedCheck() || !m_lastdocked) OnDocked(new DockedStateArgs(true, getinitialstate)); isdocked = true; break; case DeviceChargingStatus.BTChargingStatus_NotBatteryPowered: case DeviceChargingStatus.BTChargingStatus_NotConnected: case DeviceChargingStatus.BTChargingStatus_Unknown: // only send to app the docked value if it is different to m_lastdocked (i.e. if it has changed) if (IsFirstLegendDockedCheck() || m_lastdocked) OnUnDocked(new DockedStateArgs(false, getinitialstate)); isdocked = false; break; } } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception caught: " + e.ToString()); } } return isdocked; } private bool IsFirstLegendDockedCheck() { bool retval = m_firstlegenddockcheck; m_firstlegenddockcheck = false; return retval; } /// /// Request from Spokes information about the battery level in the attached wireless device. /// Typically your app will call this after receiving a BatteryLevel headset event. /// /// An BatteryLevel structure containing information about the battery level. public DeviceBatteryLevel GetBatteryLevel() { DeviceBatteryLevel level = DeviceBatteryLevel.BatteryLevel_Empty; try { if (m_activeDevice != null) { if (m_hostCommandExt != null) { level = m_hostCommandExt.BatteryLevel; } } } catch (Exception) { } return level; } // #region DeviceListener events void DeviceListener_ATDStateChanged(COMDeviceListenerEventArgs e) { //HANDLE ATD State changes switch (e.ATDStateChange) { case DeviceATDStateChange.ATDStateChange_MobileInComing: m_mobIncoming = true; // set on call flag OnOnMobileCall(new OnMobileCallArgs(m_mobIncoming, MobileCallState.Ringing)); break; case DeviceATDStateChange.ATDStateChange_MobileOnCall: OnOnMobileCall(new OnMobileCallArgs(m_mobIncoming, MobileCallState.OnCall)); break; case DeviceATDStateChange.ATDStateChange_MobileCallEnded: m_mobIncoming = false; // clear mobile call direction flag OnNotOnMobileCall(EventArgs.Empty); break; case DeviceATDStateChange.ATDStateChange_MobileCallerID: OnMobileCallerId(new MobileCallerIdArgs(GetMobileCallerID())); break; case DeviceATDStateChange.ATDStateChange_MobileOutGoing: break; case DeviceATDStateChange.ATDStateChange_PstnInComingCallRingOn: break; case DeviceATDStateChange.ATDStateChange_PstnInComingCallRingOff: break; } } // print device events void m_deviceComEvents_Handler(COMDeviceEventArgs e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, string.Format("Device Event: Audio:{0} Buton:{1} Mute:{2}", e.AudioState, e.ButtonPressed, e.mute)); switch (e.ButtonPressed) { case DeviceHeadsetButton.HeadsetButton_Flash: OnCallSwitched(EventArgs.Empty); break; //case DeviceHeadsetButton.HeadsetButton_Mute: // Not needed, now relying on IDeviceListener event for mute change // OnMuteChanged(new MuteChangedArgs(e.mute)); // break; } OnButtonPress(new ButtonPressArgs(e.ButtonPressed, e.AudioState, e.mute)); } #endregion // attach to device events private void AttachDevice() { try { m_activeDevice = m_comSession.GetActiveDevice(); } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Exception caught attaching to device: "+e.ToString()); m_activeDevice = null; } if (m_activeDevice != null) { // LC assume minimum first set of device capabilities... DeviceCapabilities = new SpokesDeviceCaps(false, false, false, false, false, false, false, false); OnCapabilitiesChanged(EventArgs.Empty); OnSerialNumber(new SerialNumberArgs("", SerialNumberTypes.Base)); OnSerialNumber(new SerialNumberArgs("", SerialNumberTypes.Headset)); // LC have seen case where ProductName was empty but InternalName was not... if (m_activeDevice.ProductName.Length > 0) { m_devicename = m_activeDevice.ProductName; } else if (m_activeDevice.InternalName.Length > 0) { m_devicename = m_activeDevice.InternalName; } else { m_devicename = "Could not determine device name"; } m_baseserial = m_activeDevice.SerialNumber; m_lastdocked = false; //m_battlevEventCount = 0; m_deviceComEvents = m_activeDevice as ICOMDeviceEvents_Event; if (m_deviceComEvents != null) { // Attach to device events m_deviceComEvents.onButtonPressed += m_deviceComEvents_Handler; // not needed, instead rely on IDeviceListenerEvents.onHeadsetButtonPressed //////m_deviceComEvents.onAudioStateChanged += m_deviceComEvents_Handler; //////m_deviceComEvents.onFlashButtonPressed += m_deviceComEvents_Handler; m_deviceComEvents.onMuteStateChanged += m_deviceComEvents_Handler; //////m_deviceComEvents.onSmartButtonPressed += m_deviceComEvents_Handler; //m_deviceComEvents.onTalkButtonPressed += m_deviceComEvents_Handler; // not needed, instead rely on IDeviceListenerEvents.onHeadsetButtonPressed // LC 11-7-2013 TT: 23171 Cannot receive OLMP/Bladerunner responses from headset - need to expose Device.DataReceived event to COM // Try adding onDataReceived event handler m_deviceComEvents.onDataReceived += new ICOMDeviceEvents_onDataReceivedEventHandler(m_deviceComEvents_onDataReceived); DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: AttachedEventHandler to device events"); } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error: unable to attach to device events"); return; } //// if the ActiveDevice is a Calisto device, need additional initialization //// TODO: work out if this is needed in Spokes 3.0 - it's not currently exposed via COM! //if (m_activeDevice.HostCommand.IsSupported(FeatureType.FeatureType_DisplayDevice)) //{ // InitDisplayDevice(); //} m_deviceListenerEvents = m_activeDevice.DeviceListener as ICOMDeviceListenerEvents_Event; if (m_deviceListenerEvents != null) { // Attach to device listener events m_deviceListenerEvents.onATDStateChanged += m_deviceListenerEvents_Handler; m_deviceListenerEvents.onBaseButtonPressed += m_deviceListenerEvents_Handler; m_deviceListenerEvents.onBaseStateChanged += m_deviceListenerEvents_Handler; m_deviceListenerEvents.onHeadsetButtonPressed += m_deviceListenerEvents_Handler; m_deviceListenerEvents.onHeadsetStateChanged += m_deviceListenerEvents_HandlerMethods; } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error: unable to attach to device listener events"); return; } // The below call (now with exception catching) tries to register // for extended headsetstatechange and base events that were used // for reading serial numbers from device using "asyncronous" method. // Following discussion with Ramesh Feb 2013 the Spokes Wrapper // primarily tries to read serial numbers using the "syncronous" // method based on HeadsetStateChange/BaseStateChange serial number // events and GetSerialNumber_2 method. However, this has been left // in because GetSerialNumber_2 is not working! (At least this way the // base serial number is obtainable! LC 14th Mar 2013) RegisterForExtendedEvents(); m_hostCommand = m_activeDevice.HostCommand; if (m_hostCommand == null) DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error: unable to obtain host command interface"); m_atdCommand = m_activeDevice.HostCommand as ICOMATDCommand; if (m_atdCommand == null) DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error: unable to obtain atd command interface"); m_hostCommandExt = m_activeDevice.HostCommand as ICOMHostCommandExt; if (m_hostCommandExt == null) DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error: unable to obtain host command ext interface"); m_userPreference = m_sessionComManager.UserPreference; if (m_userPreference == null) DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error: unable to obtain user preference interface"); m_advanceSettings = m_activeDevice.HostCommand as ICOMAdvanceSettings; if (m_advanceSettings == null) DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error: unable to obtain advanced settings interface"); m_deviceSettings = m_activeDevice.HostCommand as ICOMDeviceSettings; if (m_deviceSettings == null) DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error: unable to obtain device settings interface"); m_deviceListener = m_activeDevice.DeviceListener; if (m_deviceListener == null) DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error: unable to obtain device listener interface"); m_deviceSettingsExt = m_activeDevice.HostCommand as ICOMDeviceSettingsExt; if (m_deviceSettingsExt == null) DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error: unable to obtain device settings ext interface"); UpdateOtherDeviceCapabilities(); // trigger user's event handler OnAttached(new AttachedArgs(m_activeDevice)); OnSerialNumber(new SerialNumberArgs(m_baseserial, SerialNumberTypes.Base)); // now poll for current state (proximity, mobile call status, donned status, mute status) GetInitialDeviceState(); DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: AttachedEventHandler to device"); } } /// /// Call this method to set your app as the default softphone in Plantronics /// (and therefore target of CallRequested events from Calisto P240) /// Optional: the name of the app/session to set. Pass "" empty string to use the current app session (your app). /// public void SetDefaultSoftphone(string sessionName = "") { if (sessionName == "") sessionName = m_sessionName; try { // let's register our app as softphone handler if (m_userPreference != null) { m_userPreference.DefaultSoftphone = sessionName; } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception caught setting session as default softphone: "+e.ToString()); } } /// /// Call this to get the name of previous default softphone app/session /// /// The previous app/session name public string GetDefaultSoftphone() { string retval = ""; try { // let's register our app as softphone handler if (m_userPreference != null) { retval = m_userPreference.DefaultSoftphone; } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception caught getting session as default softphone: " + e.ToString()); } return retval; } /// /// Set the Audio Sensing feature of the current primary device /// on or off. For example, set to off if you want to programatically /// control the audio state via ConnectAudioLinkToDevice method. /// /// A boolean to say if you want it on or off public void SetAudioSensing(bool enable) { try { if (m_advanceSettings != null) { m_advanceSettings.AudioSensing = enable; } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception caught setting audio sensing: " + e.ToString()); } } /// /// Gets the Audio Sensing setting value of the current primary device /// (on or off). For example, needs to be off if you want to programatically /// control the audio state via ConnectAudioLinkToDevice method. /// /// A boolean to say if you want it on or off public bool GetAudioSensing() { bool retval = false; try { if (m_advanceSettings != null) { retval = m_advanceSettings.AudioSensing; } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception caught getting audio sensing: " + e.ToString()); retval = false; } return retval; } public void SetMuteToneVolume(COMVolumeLevel level) { try { if (m_deviceSettings != null) { m_deviceSettings.MuteToneVolume = level; } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception caught setting mute tone volume: " + e.ToString()); } } public COMVolumeLevel GetMuteToneVolume() { COMVolumeLevel retval = COMVolumeLevel.VolumeLevel_Standard; try { if (m_deviceSettings != null) { retval = m_deviceSettings.MuteToneVolume; } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception caught getting mute tone volume: " + e.ToString()); } return retval; } public void SetMuteTone(COMRingTone tone) { try { if (m_deviceSettings != null) { m_deviceSettings.MuteTone = tone; } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception caught setting mute tone: " + e.ToString()); } } public COMRingTone GetMuteTone() { COMRingTone retval = COMRingTone.RingTone_Type1; try { if (m_deviceSettings != null) { retval = m_deviceSettings.MuteTone; } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception caught getting mute tone: " + e.ToString()); } return retval; } void m_deviceComEvents_onDataReceived(ref object report) { // LC 11-7-2013 TT: 23171 Cannot receive OLMP/Bladerunner responses from headset - need to expose Device.DataReceived event to COM // Try adding onDataReceived event handler //DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: onDataReceived report from device"); // return data to connected app via Spokes Wrapper event... byte[] reportbuf = (byte[])report; RawDataReceivedArgs args = new RawDataReceivedArgs(reportbuf, byteArrayToString(reportbuf)); OnRawDataReceived(args); // uncomment for debug: // Console.WriteLine(args.m_datareporthex); } //// Initialization code for Calisto devices //private void InitDisplayDevice() //{ // if (m_activeDevice != null) // { // try // { // IDisplayDeviceListener ddl = m_activeDevice.DeviceListener as IDisplayDeviceListener; // if (ddl != null) // { // DDSoftphoneID ddSP = DDSoftphoneID.DDSoftphoneID_Unknown; // bool bActive = true; // ddl.SetDefaultSoftphone(ddSP); // ddl.SetPresence(ddSP, bActive ? DDPresence.DDPresence_Available : DDPresence.DDPresence_Closed); // ddl.SetDateTimeFormat(Thread.CurrentThread.CurrentCulture.Name); // ddl.SetLocale(Thread.CurrentThread.CurrentCulture.LCID); // ddl.SetDateTime(DateTime.Now); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_AvayaIPAgent, "AvayaIPAgent"); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_AvayaIPSoftphone, "AvayaIPSoftphone"); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_AvayaOneXAgent, "AvayaOneXAgent"); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_AvayaOneXCommunicator, "AvayaOneXCommunicator"); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_CiscoIPCommunicator, "CiscoIPCommunicator"); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_CSF, "CiscoUCClientsCSF"); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_IBMSameTime, "IBMSameTime"); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_MSOfficeCommunicator, "MicrosoftOfficeCommunicator"); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_NECSP350, "NECSP350"); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_ShoreTel, "ShoreTelCallManager"); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_ShoreTelCommunicator, "ShoreTelCommunicator"); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_Skype, "Skype"); // ddl.SetSoftphoneName(DDSoftphoneID.DDSoftphoneID_Unknown, "Unknown"); // } // } // catch (Exception ex) // { // DebugPrint(MethodInfo.GetCurrentMethod().Name, "Exception in InitDisplayDevice(): " + ex.ToString()); // } // } //} private void RegisterForExtendedEvents() { // LC, Nemanja change, wire up serial number friendly events try { ICOMDeviceEventsExt_Event eex = m_deviceComEvents as ICOMDeviceEventsExt_Event; eex.onHeadsetStateChanged += eex_HeadsetStateChanged; } catch (Exception) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Failed to register for extended headsetstatechange events."); } try { if (m_activeDevice != null && m_activeDevice.ProductName.Contains("BT600")) return; // LC avoid base events for serials for BT600 ICOMBaseEvents_Event be = m_deviceComEvents as ICOMBaseEvents_Event; be.onBaseEventReceived += be_BaseEventReceived; } catch (Exception) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Failed to register for extended basestatechange events."); } } // NEW, Nemanja's code to get the serial ID's ! private void be_BaseEventReceived(COMBaseEventArgs e) { switch (e.EventType) { case BaseEventTypeExt.BaseEventTypeExt_Unknown: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: Unknown")); break; case BaseEventTypeExt.BaseEventTypeExt_PstnLinkEstablished: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: PstnLinkEstablished")); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; case BaseEventTypeExt.BaseEventTypeExt_PstnLinkDown: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: PstnLinkDown")); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; case BaseEventTypeExt.BaseEventTypeExt_VoipLinkEstablished: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: VoipLinkEstablished")); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; case BaseEventTypeExt.BaseEventTypeExt_VoipLinkDown: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: VoipLinkDown")); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; //case BaseEventTypeExt.BaseEventTypeExt_AudioMixer: // DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: AudioMixer")); // break; //case BaseEventTypeExt.BaseEventTypeExt_RFLinkType.BaseEventTypeExt_RFLinkWideBand: // DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: RFLinkWideBand")); // break; //case BaseEventTypeExt.BaseEventTypeExt_RFLinkNarrowBand: // DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: RFLinkNarrowBand")); // break; case BaseEventTypeExt.BaseEventTypeExt_MobileLinkEstablished: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: MobileLinkEstablished")); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; case BaseEventTypeExt.BaseEventTypeExt_MobileLinkDown: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: MobileLinkDown")); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; case BaseEventTypeExt.BaseEventTypeExt_InterfaceStateChange: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: InterfaceStateChanged")); GetHoldStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); break; //case BaseEventTypeExt.BaseEventTypeExt_AudioLocationChanged: // DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("BaseStateChanged: AudioLocationChanged")); // break; // LC add handler for basestate change serial number // We should be able to extract serial number at this point case BaseEventTypeExt.BaseEventTypeExt_SerialNumber: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("be_BaseEventReceived: SerialNumber")); string serialStr = byteArrayToString(e.SerialNumber); OnSerialNumber(new SerialNumberArgs(serialStr, SerialNumberTypes.Base)); break; } } // NEW, Nemanja's code to get the serial ID's ! private void eex_HeadsetStateChanged(COMHeadsetStateEventArgs e) { switch (e.HeadsetState) { // LC add handler for headsetstate change serial number // We should be able to extract serial number at this point case DeviceHeadsetState.HeadsetState_SerialNumber: DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("eex_HeadsetStateChanged: SerialNumber")); string serialStr = byteArrayToString(e.SerialNumber); OnSerialNumber(new SerialNumberArgs(serialStr, SerialNumberTypes.Headset)); break; // NEW CC events case DeviceHeadsetState.HeadsetState_QDConnected: OnConnected(new ConnectedStateArgs(true, false)); break; case DeviceHeadsetState.HeadsetState_QDDisconnected: OnDisconnected(new ConnectedStateArgs(false, false)); break; default: break; } } // NEW, Nemanja's code to get the serial ID's ! private static string byteArrayToString(byte[] p) { StringBuilder b = new StringBuilder(); foreach (byte x in p) b.Append(x.ToString("X2")); return b.ToString(); } // now poll for current state (proximity, mobile call status, donned status, mute status) private void GetInitialDeviceState() { try { if (m_activeDevice != null) { RegisterForProximity(true); GetInitialSoftphoneCallStatus(); // are we on a call? GetInitialMobileCallStatus(); // are we on a call? // LC 11-07-13 commented out, crashing spokes 3.0 sdk GetInitialDonnedStatus(); // are we donned? GetInitialMuteStatus(); RequestAllSerialNumbers(); GetLastDockedStatus(); GetLastConnectedStatus(); // new GetActiveAndHeldStates(); OnLineActiveChanged(new LineActiveChangedArgs(m_hostCommand.AudioState == DeviceAudioState.AudioState_MonoOn)); // is the line active? } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: No device is attached, cannot get initial device state."); } } catch(Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: exception was caught from Spokes SDK: "+e.GetType()); } } public void SetMuteAlert(COMMuteAlertValues muteAlert) { if (m_deviceSettingsExt != null) { m_deviceSettingsExt.MuteAlert = muteAlert; } } public COMMuteAlertValues GetMuteAlert() { COMMuteAlertValues retval = COMMuteAlertValues.MuteAlertValue_Disabled; if (m_deviceSettingsExt != null) { retval = m_deviceSettingsExt.MuteAlert; } return retval; } // new get last connected status of headset (QD connector) when app first runs private bool GetLastConnectedStatus() { bool connected = false; try { if (m_hostCommandExt != null) { m_lastconnected = true; // if settings interface is not available, assume connected if (m_deviceSettingsExt != null) { m_lastconnected = m_deviceSettingsExt.HeadsetConnectedState; } connected = m_lastconnected; if (connected) OnConnected(new ConnectedStateArgs(true, true)); else OnDisconnected(new ConnectedStateArgs(false, true)); } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: exception caught getting last QD connected state (does it have a QD?) " + e.ToString()); if ((m_devicename.ToUpper().Contains("DA80") || m_devicename.ToUpper().Contains("DA70") || m_devicename.ToUpper().Contains("DA90") ) && connectedRetries<5) { // todo - reschedule a check of connected state after time delay connectedTimer = new Timer(ConnectedRetryCallback, null, 1000, 0); connectedRetries++; } else { // probably we don't have QD connector, lets inform user... DeviceCapabilities.HasQDConnector = false; OnCapabilitiesChanged(EventArgs.Empty); } } return connected; } private void GetInitialSoftphoneCallStatus() { try { if (m_sessionComManager.CallManagerState.HasActiveCall) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: We have an ACTIVE call!"); OnOnCall(new OnCallArgs("", false, OnCallCallState.OnCall, 0)); // for now just say we are on a call // TODO Raise a TT - the ICallInfo interface is NOT exposed via Spokes SDK .NET API! //Collection calls = (Collection)m_sessionComManager.CallManagerState.GetCalls; //DebugPrint(MethodInfo.GetCurrentMethod().Name, "Got Calls"); } else { OnNotOnCall(new NotOnCallArgs(-1, "")); // we are not on a call } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception: "+e.ToString()); OnNotOnCall(new NotOnCallArgs(-1, "")); // we are not on a call } } private void GetActiveAndHeldStates() { try { GetHoldStates(); GetActiveStates(); OnMultiLineStateChanged(new MultiLineStateArgs(m_activeHeldFlags)); } catch (Exception) { // probably the attached device doesn't have multiline, lets inform user... DeviceCapabilities.HasMultiline = false; OnCapabilitiesChanged(EventArgs.Empty); } } private void DebugPrint(string methodname, string message) { if (m_debuglog!=null) m_debuglog.DebugPrint(methodname, message); } // hard coded other device caps, beside caller id private void UpdateOtherDeviceCapabilities() { // NEW if DeviceCapabilities.csv file exists in your app's current working directory with a list of device // features in the following format (one device per line): // ProductId,DeviceName,HasProximity,HasMobCallerId,HasMobCallState,HasDocking,HasWearingSensor,HasMultiline,IsWireless // Then use those capabilities for current active device // // Is the m_AllDeviceCapabilities vector populated? And is my device id in there? SpokesDeviceCaps myDeviceCapabilities = GetMyDeviceCapabilities(); if (myDeviceCapabilities.ProductId.Length > 0) { // we have found device in the DeviceCapabilities.csv file DeviceCapabilities.HasProximity = myDeviceCapabilities.HasProximity; DeviceCapabilities.HasMobCallerId = myDeviceCapabilities.HasMobCallerId; DeviceCapabilities.HasMobCallState = myDeviceCapabilities.HasMobCallState; DeviceCapabilities.HasDocking = myDeviceCapabilities.HasDocking; DeviceCapabilities.HasWearingSensor = myDeviceCapabilities.HasWearingSensor; DeviceCapabilities.HasMultiline = myDeviceCapabilities.HasMultiline; DeviceCapabilities.IsWireless = myDeviceCapabilities.IsWireless; DeviceCapabilities.HasQDConnector = myDeviceCapabilities.HasQDConnector; } else if (m_activeDevice!=null && m_activeDevice.ProductId == 126) { // special case - PLT Labs Concept 1 head tracking headset... DeviceCapabilities.HasProximity = true; DeviceCapabilities.HasMobCallerId = true; DeviceCapabilities.HasMobCallState = true; DeviceCapabilities.HasWearingSensor = true; DeviceCapabilities.HasDocking = true; // updated, legend does have docking DeviceCapabilities.IsWireless = true; DeviceCapabilities.HasQDConnector = false; } else { // OK, the Spokes Wrapper user maybe doesn't have the DeviceCapabilities.csv file // deployed in app's working directory. Falling back to old hard-coded capabilities // (which don't cover the whole product range DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Did not find product in DeviceCapabilities.csv or DeviceCapabilities.csv not present for device:"); DebugPrint(MethodInfo.GetCurrentMethod().Name, m_devicename); DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Will assume minimum capabilities, unless overridden by hard-coded capabilities in UpdateOtherDeviceCapabilities function."); // LC temporarily hard-code some device capabilities // e.g. fact that Blackwire C710/C720 do not support proximity, docking and is not wireless string devname = m_devicename; if (devname != null) { devname = devname.ToUpper(); // TODO: Overtime keep this code up-to-date with ALL our current products!!! // TODO: Future plan automatically work out device features based on HID Usages // (for now I think hard-coded works quite well) if (devname.Contains("BLACKWIRE")) { DeviceCapabilities.IsWireless = false; DeviceCapabilities.HasDocking = false; DeviceCapabilities.HasWearingSensor = false; } if (devname.Contains("C710") || devname.Contains("C720")) { DeviceCapabilities.HasProximity = false; DeviceCapabilities.HasMobCallerId = false; DeviceCapabilities.HasWearingSensor = true; DeviceCapabilities.HasDocking = false; DeviceCapabilities.IsWireless = false; } // LC new - if using vpro or vlegend then disable docking feature... if (devname.Contains("BT300")) { DeviceCapabilities.HasProximity = true; DeviceCapabilities.HasMobCallerId = true; DeviceCapabilities.HasMobCallState = true; DeviceCapabilities.HasWearingSensor = true; DeviceCapabilities.HasDocking = true; // updated, legend does have docking DeviceCapabilities.IsWireless = true; } if (devname.Contains("SAVI 7")) { DeviceCapabilities.HasWearingSensor = false; DeviceCapabilities.HasMultiline = true; DeviceCapabilities.HasMobCallState = true; DeviceCapabilities.HasDocking = true; DeviceCapabilities.IsWireless = true; } } } OnCapabilitiesChanged(EventArgs.Empty); } // detach from device events void DetachDevice() { if (m_activeDevice != null) { if (m_deviceComEvents != null) { // commented out - not using these any more (see AttachDevice comment) //// LC, new unregister the serial number events //IDeviceCOMEventsExt_Event eex = m_deviceComEvents as IDeviceCOMEventsExt_Event; //eex.HeadsetStateChanged -= eex_HeadsetStateChanged; //IBaseCOMEvents_Event be = m_deviceComEvents as IBaseCOMEvents_Event; //be.BaseEventReceived -= be_BaseEventReceived; // unregister device event handlers //m_deviceComEvents.onButtonPressed -= m_deviceComEvents_Handler; // not needed, instead rely on IDeviceListenerEvents.onHeadsetButtonPressed m_deviceComEvents.onAudioStateChanged -= m_deviceComEvents_Handler; m_deviceComEvents.onFlashButtonPressed -= m_deviceComEvents_Handler; m_deviceComEvents.onMuteStateChanged -= m_deviceComEvents_Handler; m_deviceComEvents.onSmartButtonPressed -= m_deviceComEvents_Handler; //m_deviceComEvents.onTalkButtonPressed -= m_deviceComEvents_Handler; // not needed, instead rely on IDeviceListenerEvents.onHeadsetButtonPressed //m_deviceComEvents.onDataReceived -= m_deviceComEvents_onDataReceived; // commenting out this as it locks up on exit Marshal.ReleaseComObject(m_deviceComEvents); m_deviceComEvents = null; } if (m_deviceListenerEvents != null) { // unregister device listener event handlers m_deviceListenerEvents.onATDStateChanged -= m_deviceListenerEvents_Handler; m_deviceListenerEvents.onBaseButtonPressed -= m_deviceListenerEvents_Handler; m_deviceListenerEvents.onBaseStateChanged -= m_deviceListenerEvents_Handler; m_deviceListenerEvents.onHeadsetButtonPressed -= m_deviceListenerEvents_Handler; m_deviceListenerEvents.onHeadsetStateChanged -= m_deviceListenerEvents_HandlerMethods; RegisterForProximity(false); Marshal.ReleaseComObject(m_deviceListenerEvents); m_deviceListenerEvents = null; } Marshal.ReleaseComObject(m_activeDevice); m_activeDevice = null; m_hostCommand = null; m_hostCommandExt = null; m_atdCommand = null; // LC Device was disconnected, clear down the GUI state... m_mobIncoming = false; // clear mobile call direction flag m_voipIncoming = false; // clear call direction flag OnNotOnCall(new NotOnCallArgs(0,"")); OnNotOnMobileCall(EventArgs.Empty); OnSerialNumber(new SerialNumberArgs("", SerialNumberTypes.Base)); OnSerialNumber(new SerialNumberArgs("", SerialNumberTypes.Headset)); // LC Device was disconnected, remove capability data DeviceCapabilities = new SpokesDeviceCaps(false, false, false, false, false, false, false); // no device = no capabilities! m_devicename = ""; m_baseserial = ""; OnCapabilitiesChanged(EventArgs.Empty); m_lastdocked = false; //m_battlevEventCount = 0; // trigger user's event handler OnDetached(EventArgs.Empty); DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: DetachedEventHandler from device"); } m_devicename = ""; m_baseserial = ""; } private void RegisterForProximity(bool register) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: About to register for proximity."); try { if (m_hostCommandExt != null) { m_hostCommandExt.EnableProximity(register); // enable proximity reporting for device if (register) m_hostCommandExt.RequestProximity(); // request to receive asyncrounous near/far proximity event to HeadsetStateChanged event handler. (note: will return it once. To get continuous updates of proximity you would need a to call GetProximity() repeatedly, e.g. in a worker thread). DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Completed request to register for proximity."); DeviceCapabilities.HasProximity = true; // Tweak availability of proximity per-device... string devname = m_devicename.ToUpper(); if (devname.Contains("C710") || devname.Contains("C720")) { DeviceCapabilities.HasProximity = false; } OnCapabilitiesChanged(EventArgs.Empty); } } catch (System.Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: INFO: proximity may not be supported on your device."); // note, remote auto smarts that used to turn off proximity feature, it won't work with BT600 // this will not matter, as long as our DeviceCapabilities.csv is up to date! //// uh-oh proximity may not be supported... disable it as option in GUI (only if we have not //// loaded all device capabilities database //if (m_AllDeviceCapabilities.Count==0) // DeviceCapabilities.HasProximity = false; //OnCapabilitiesChanged(EventArgs.Empty); } } /// /// Instruct Spokes to tell us the serial numbers of attached Plantronics device, i.e. headset and base/usb adaptor. /// public void RequestAllSerialNumbers() { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: About to request serial numbers."); RequestSingleSerialNumber(SerialNumberTypes.Base); RequestSingleSerialNumber(SerialNumberTypes.Headset); } // Some internal methods to get line active/held states of multi-line devices: private void GetHoldStates() { m_activeHeldFlags.DeskphoneHeld = GetHoldState(COMLineType.LineType_PSTN); m_activeHeldFlags.MobileHeld = GetHoldState(COMLineType.LineType_Mobile); m_activeHeldFlags.PCHeld = GetHoldState(COMLineType.LineType_VOIP); DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("Current Interface Hold States: PSTN: {0} Mobile: {1} VOIP: {2}", m_activeHeldFlags.DeskphoneHeld, m_activeHeldFlags.MobileHeld, m_activeHeldFlags.PCHeld)); } private void GetActiveStates() { m_activeHeldFlags.DeskphoneActive = GetActiveState(COMLineType.LineType_PSTN); m_activeHeldFlags.MobileActive = GetActiveState(COMLineType.LineType_Mobile); m_activeHeldFlags.PCActive = GetActiveState(COMLineType.LineType_VOIP); DebugPrint(MethodInfo.GetCurrentMethod().Name, String.Format("Current Interface Is Line Active States: PSTN: {0} Mobile: {1} VOIP: {2}", m_activeHeldFlags.DeskphoneActive, m_activeHeldFlags.MobileActive, m_activeHeldFlags.PCActive)); } private bool GetHoldState(COMLineType lineType) { bool state = false; // default - unknown state //Get the current hold state if (m_hostCommandExt!=null) { state = m_hostCommandExt.GetLinkHoldState(lineType); } return state; } private bool GetActiveState(COMLineType lineType) { bool state = false; // default - unknown state //Get the current active state if (m_hostCommandExt != null) { try { state = m_hostCommandExt.IsLineActive(lineType); } catch(COMException e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: cannot get line active state. Note: expected on D100 which does not support mutliline."); } } return state; } // new get last docked status of device when app first runs private bool GetLastDockedStatus() { bool docked = false; try { if (m_hostCommandExt != null) { m_lastdocked = m_hostCommandExt.HeadsetDocked; docked = m_lastdocked; if (docked) OnDocked(new DockedStateArgs(true, true)); else OnUnDocked(new DockedStateArgs(false, true)); } } catch (Exception) { // probably we don't support docking, lets inform user... if (m_activeDevice.ProductName.Contains("BT300")) { DeviceCapabilities.HasDocking = true; // updated, legend does have docking // TODO Raise TT for IsHeadsetDocked should be implemented for BT300! m_ignorenextundockedevent = true; //m_ignorenextbattlevevent = true; m_lastdocked = DetectLegendDockedState(true); docked = m_lastdocked; } //else if (m_activeDevice.ProductName.Contains("BT600")) //{ // DeviceCapabilities.HasDocking = true; // updated, focus uc does have docking // on second thought, it doesn't, it was a hack based on charging state // this is not reliable because when fully charged there is no transition from // docked to undocked (because although docked, it will not be charging). //} else { DeviceCapabilities.HasDocking = false; } OnCapabilitiesChanged(EventArgs.Empty); } return docked; } /// /// Instructs a mobile that is paired with Plantronics device to dial an outbound mobile call. /// /// The phone number you wish the mobile to call. public void DialMobileCall(string numbertodial) { if (m_atdCommand != null) { m_atdCommand.MakeMobileCall(numbertodial); } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error, unable to dial mobile call. atd command is null."); } } /// /// Instructs a mobile that is paired with Plantronics device to answer an inbound (ringing) mobile call /// public void AnswerMobileCall() { if (m_atdCommand != null) { m_atdCommand.AnswerMobileCall(); } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error, unable to answer mobile call. atd command is null."); } } /// /// Instructs a mobile that is paired with Plantronics device to end on ongoing mobile call /// public void EndMobileCall() { if (m_atdCommand != null) { m_atdCommand.EndMobileCall(); } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error, unable to end mobile call. atd command is null."); } } private void GetInitialMobileCallStatus() { if (m_atdCommand != null) { try { m_atdCommand.RequestMobileCallStatus(); // are we on a call? bool tmpHasCallerId = true; // device does support caller id feature // LC temporarily hard-code some device capabilities // e.g. fact that Blackwire C710/C720 do not support proximity, docking and is not wireless string devname = m_devicename; if (devname != null) { devname = devname.ToUpper(); if (devname.Contains("SAVI 7")) { tmpHasCallerId = false; // Savi 7xx does not support caller id feature } if (devname.Contains("BLACKWIRE")) { tmpHasCallerId = false; // Blackwire range does not support caller id feature } if (devname.Contains("C710") || devname.Contains("C720")) { tmpHasCallerId = false; // Blackwire 700 range does not support caller id feature } } DeviceCapabilities.HasMobCallerId = tmpHasCallerId; // set whether device supports caller id feature OnCapabilitiesChanged(EventArgs.Empty); } catch (System.Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: INFO: Exception occured getting mobile call status\r\nException = " + e.ToString()); DeviceCapabilities.HasMobCallerId = false; OnCapabilitiesChanged(EventArgs.Empty); } } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Error, unable to get mobile status. atd command is null."); DeviceCapabilities.HasMobCallerId = false; // device does not support caller id feature OnCapabilitiesChanged(EventArgs.Empty); } } // new get last donned status of device when app first runs private void GetInitialDonnedStatus() { try { if (m_hostCommandExt != null) { DeviceHeadsetState laststate = m_hostCommandExt.HeadsetState; switch (laststate) { case DeviceHeadsetState.HeadsetState_Doff: OnTakenOff(new WearingStateArgs(false, true)); break; case DeviceHeadsetState.HeadsetState_Don: OnPutOn(new WearingStateArgs(true, true)); break; } DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Last donned state was: "+laststate); } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Other Exception in GetInitialDonnedStatus(): " + e.ToString()); } } private void GetInitialMuteStatus() { try { if (m_hostCommand != null) { OnMuteChanged(new MuteChangedArgs(m_hostCommand.mute)); DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: Last mute state was: " + m_hostCommand.mute); } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Other Exception in GetInitialMuteStatus(): " + e.ToString()); } } /// /// Allows your softphone application to inform Plantronics device about an incoming call. The Plantronics device will then automatically ring. /// Note: will automatically open audio/rf link to wireless device. /// /// A unique numeric identifier for the call that your application and Spokes will use to identify it as. /// Optional caller's contact name that will display on Plantronics display devices, e.g. Calisto P800 and P240 devices. /// Boolean indicating if command was issued successfully or not. public bool IncomingCall(int callid, string contactname = "") { bool success = false; try { if (m_comSession != null) { ContactCOM contact = new ContactCOM(); contact.SetName(contactname); CallCOM call = new CallCOM(); call.SetId(callid); m_comSession.GetCallCommand().IncomingCall(call, contact, CallRingTone.RingTone_Unknown, CallAudioRoute.AudioRoute_ToHeadset); //ConnectAudioLinkToDevice(true); success = true; } } catch (Exception) { success = false; } return success; } /// /// Allows your softphone application to inform Plantronics device about an outgoing call. Note: will automatically open audio/rf link to wireless device. /// /// A unique numeric identifier for the call that your application and Spokes will use to identify it as. /// Optional caller's contact name that will display on Plantronics display devices, e.g. Calisto P800 and P240 devices. /// Boolean indicating if command was issued successfully or not. public bool OutgoingCall(int callid, string contactname = "") { bool success = false; try { if (m_comSession != null) { ContactCOM contact = new ContactCOM(); contact.SetName(contactname); CallCOM call = new CallCOM(); call.SetId(callid); m_comSession.GetCallCommand().OutgoingCall(call, contact, CallAudioRoute.AudioRoute_ToHeadset); ConnectAudioLinkToDevice(true); success = true; } } catch (Exception) { success = false; } return success; } /// /// Instructs Spokes to end an ongoing softphone call. /// /// The unique numeric id that defines which softphone call you want to end. /// Boolean indicating if the command was called succesfully or not. public bool EndCall(int callid) { bool success = false; try { if (m_comSession != null) { CallCOM call = new CallCOM(); // { Id = callid }; call.SetId(callid); m_comSession.GetCallCommand().TerminateCall(call); success = true; } } catch (Exception) { success = false; } return success; } /// /// Informs Spokes that user has answered an incoming (ringing) softphone call, /// for example with a softphone GUI. /// /// The unique numeric id that defines which softphone call you want to end. /// Boolean indicating if the command was called succesfully or not. public bool AnswerCall(int callid) { bool success = false; try { if (m_comSession != null) { CallCOM call = new CallCOM(); // { Id = callid }; call.SetId(callid); m_comSession.GetCallCommand().AnsweredCall(call); success = true; } } catch (Exception) { success = false; } return success; } /// /// Informs Spokes that user has placed a softphone call on hold, /// for example with a softphone GUI. /// /// The unique numeric id that defines which softphone call you want to end. /// Boolean indicating if the command was called succesfully or not. public bool HoldCall(int callid) { bool success = false; try { if (m_comSession != null) { CallCOM call = new CallCOM(); // { Id = callid }; call.SetId(callid); m_comSession.GetCallCommand().HoldCall(call); success = true; } } catch (Exception) { success = false; } return success; } /// /// Informs Spokes that user has resumed a softphone call that was previously on hold, /// for example with a softphone GUI. /// /// The unique numeric id that defines which softphone call you want to end. /// Boolean indicating if the command was called succesfully or not. public bool ResumeCall(int callid) { bool success = false; try { if (m_comSession != null) { CallCOM call = new CallCOM(); // { Id = callid }; call.SetId(callid); m_comSession.GetCallCommand().ResumeCall(call); success = true; } } catch (Exception) { success = false; } return success; } /// /// Instruct Spokes to tell us a serial number of the attached Plantronics device, i.e. headset or base/usb adaptor. /// /// Allows you to say if you would like the headset or base/usb adaptor serial number. public void RequestSingleSerialNumber(SerialNumberTypes serialNumberType) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: About to request serial number for: " + serialNumberType); try { if (m_hostCommandExt != null) { switch (serialNumberType) { case SerialNumberTypes.Headset: //m_hostCommandExt.RequestSerialNumber(COMDeviceType.DeviceType_Headset); // TT 34072, new way of requesting with madone since sdk 3.6 var value = m_hostCommandExt.GetSerialNumber(COMDeviceType.DeviceType_Headset); if (value.GetType().Name == "Byte[]") { string serialStr = byteArrayToString(value); OnSerialNumber(new SerialNumberArgs(serialStr, SerialNumberTypes.Headset)); } break; case SerialNumberTypes.Base: //m_hostCommandExt.RequestSerialNumber(COMDeviceType.DeviceType_Base); // TT 34072, new way of requesting with madone since sdk 3.6 var value2 = m_hostCommandExt.GetSerialNumber(COMDeviceType.DeviceType_Base); if (value2.GetType().Name == "Byte[]") { string serialStr = byteArrayToString(value2); OnSerialNumber(new SerialNumberArgs(serialStr, SerialNumberTypes.Base)); } break; } } } catch (System.Exception) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: INFO: serial number may not be supported on your device."); } } #region Call Control Helper Classes // Call abstraction public class CallCOM : Interop.Plantronics.COMCall { private int id = 0; int ICOMCall.ConferenceId { get { return 0; } set { } } int ICOMCall.Id { get { return id; } set { id = value; } } bool ICOMCall.InConference { get { return false; } set { } } internal void SetId(int callid) { id = callid; } } // Contact abstraction public class ContactCOM : Interop.Plantronics.COMContact { private string email; private string friendlyName; private string homePhone; private int id; private string mobPhone; private string name; private string phone; private string sipUri; private string workPhone; string ICOMContact.Email { get { return email; } set { email = value; } } string ICOMContact.FriendlyName { get { return friendlyName; } set { friendlyName = value; } } string ICOMContact.HomePhone { get { return homePhone; } set { homePhone = value; } } int ICOMContact.Id { get { return id; } set { id = value; } } string ICOMContact.MobilePhone { get { return mobPhone; } set { mobPhone = value; } } string ICOMContact.Name { get { return name; } set { name = value; } } string ICOMContact.Phone { get { return phone; } set { phone = value; } } string ICOMContact.SipUri { get { return sipUri; } set { sipUri = value; } } string ICOMContact.WorkPhone { get { return workPhone; } set { workPhone = value; } } internal void SetName(string contactname) { name = contactname; } } #endregion /// /// This function will establish or close the audio link between PC and the Plantronics audio device. /// It is required to be called where your app needs audio (i.e. when on a call) in order to support Plantronics wireless devices, because /// opening the audio link will also bring up the RF link. /// /// Tells Spokes whether to open or close the audio/rf link to device public void ConnectAudioLinkToDevice(bool connect) { try { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Setting audio link active = " + connect.ToString()); if (m_activeDevice != null) { m_activeDevice.HostCommand.AudioState = connect ? DeviceAudioState.AudioState_MonoOn : DeviceAudioState.AudioState_MonoOff; } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: INFO: cannot set audio link state, no device"); } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception caught setting audio link state: " + e.ToString()); } } /// /// This function return true or false to indicate whether there is an active audio/rf /// link between PC and the device. /// public bool IsAudioLinkToDeviceActive() { bool isActive = false; DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Querying audio link active."); if (m_activeDevice != null) { isActive = m_activeDevice.HostCommand.AudioState == DeviceAudioState.AudioState_MonoOn; } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: INFO: cannot get audio link state, no device"); } return isActive; } /// /// Set the microphone mute state of the attached Plantronics device. /// Note: For wireless devices mute only works when the audio/rf link is active (see also ConnectAudioLinkToDevice method). /// /// A boolean indicating if you want mute on or off public void SetMute(bool mute) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Setting mute = " + mute.ToString()); if (m_activeDevice != null && m_hostCommandExt != null) { //m_hostCommand.put.mute = mute; //m_hostCommandExt.MuteHeadset = mute; m_deviceListener.mute = mute; // LC now doing this with IDeviceListener interface //alternative way to mute call by id directly: //CallCOM call = new CallCOM(); //call.SetId(1); //m_comSession.GetCallCommand().MuteCall(call, mute); } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: INFO: cannot set mute, no device"); } } /// /// Get the microphone mute state of the attached Plantronics device. /// Note: For wireless devices mute only works when the audio/rf link is active (see also ConnectAudioLinkToDevice method). /// /// Boolean indicating if the headset is muted or not. public bool GetMute() { bool retval = false; DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Getting mute"); if (m_activeDevice != null && m_hostCommandExt != null) { //retval = m_hostCommand.mute; retval = m_deviceListener.mute; // LC now doing this with IDeviceListener interface } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "Spokes: INFO: cannot get mute, no device"); } return retval; } /// /// Instruct the Plantronics multiline device to activate or deactivate the specified phone line. /// /// The line to activate or deactive, PC, Mobile or Desk Phone /// Boolean indicating whether to activate or de-activate the line public void SetLineActive(Multiline_LineType multiline_LineType, bool activate) { if (m_hostCommandExt != null) { switch (multiline_LineType) { case Multiline_LineType.PC: m_hostCommandExt.SetActiveLink(COMLineType.LineType_VOIP, activate); break; case Multiline_LineType.Mobile: m_hostCommandExt.SetActiveLink(COMLineType.LineType_Mobile, activate); break; case Multiline_LineType.Deskphone: m_hostCommandExt.SetActiveLink(COMLineType.LineType_PSTN, activate); break; } } } /// /// Instruct the Plantronics multiline device to place on hold or remove from hold the specified phone line. /// /// The line to place on hold or remove from hold, PC, Mobile or Desk Phone /// Boolean indicating whether to hold or un-hold the line public void SetLineHold(Multiline_LineType multiline_LineType, bool hold) { if (m_hostCommandExt != null) { switch (multiline_LineType) { case Multiline_LineType.PC: m_hostCommandExt.SetLinkHoldState(COMLineType.LineType_VOIP, hold); break; case Multiline_LineType.Mobile: m_hostCommandExt.SetLinkHoldState(COMLineType.LineType_Mobile, hold); break; case Multiline_LineType.Deskphone: m_hostCommandExt.SetLinkHoldState(COMLineType.LineType_PSTN, hold); break; } } } /// /// Used to send a custom message to the attahched Plantronics device /// NOTE: currently no way to check the response from headset /// as IDevice.DataRecieved is not exposed by SDK. Should be in future. /// /// The custom message to send as a string public void SendCustomMessageToHeadset(string message) { try { if (m_activeDevice != null && m_hostCommand != null) { ICOMDeviceSettings devSet = m_hostCommand as ICOMDeviceSettings; byte[] outbuf = System.Text.Encoding.UTF8.GetBytes(message); DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Sending custom message to headset = " + message); devSet.SetData(COMCustomData.Custom_Data_Raw_HID_Pipe_Data, outbuf); } else { DebugPrint(MethodInfo.GetCurrentMethod().Name, "WARNING: Skipping sending custom message, no device connected!"); } } catch (Exception e) { DebugPrint(MethodInfo.GetCurrentMethod().Name, "INFO: Exception sending custom message to headset:\r\n" + e.ToString()); } } } }