Steps to integrate to Plantronics:

  1. Install the SDK
  2. Cut and Paste code from below!

Questions? Browse the Articles and use the forum.

// Program.cs : C# sample demonstrating basic call control.
// NOTE: add a reference to: C:\Program Files (x86)\Plantronics\Spokes3G SDK\Interop.Plantronics.dll
using System;
using System.Runtime.InteropServices;
using Interop.Plantronics;

namespace PLTCSharpSample
{
    class Program
    {
        // Plantronics API objects and interfaces required
        private static COMSessionManager _sessionManager;
        private static COMSession _session;
        private static ICOMSessionManagerEvents_Event _sessionManagerEvents;
        private static COMDevice _device;
        private static ICOMDeviceListenerEvents_Event _deviceListenerEvents;
        private static COMCallCommand _callCommand;
        private static COMDeviceListener _deviceListener;

        private static int _callid; // variable to track call id between my app and Plantronics

        static void Main()
        {
            Console.WriteLine("C# Plantronics COM API Sample");
            bool quit = false;

            // Connect to the Plantronics COM API
            _sessionManager = new COMSessionManager();
            _sessionManager.Register("My App", out _session);

            _sessionManagerEvents = (ICOMSessionManagerEvents_Event)_sessionManager; // obtain session manager events interface
            _sessionManagerEvents.onDeviceStateChanged += _sessionManagerEvents_onDeviceStateChanged; // register for device state changed events (device arrival / removal)

            _session.onCallStateChanged += _session_onCallStateChanged; // register for call state changed events (headset call control)

            _callCommand = _session.GetCallCommand(); // obtain Plantronics object for controlling call state of Plantronics device
            AttachDevice(); // register for device events and obtain device listener

            while (!quit)
            {
                ShowMenu();
                string cmd = Console.ReadLine();
                switch (cmd)
                {
                    case "1":
                        _callid++;
                        DoIncomingCall(_callid, "Bob%20Smith"); // inform Plantronics my app has an incoming (ringing) call
                        break;
                    case "2":
                        _callid++;
                        DoOutgoingCall(_callid, "Bob%20Smith"); // inform Plantronics my app has an outgoing call
                        break;
                    case "3":
                        DoAnswerCall(_callid); // inform Plantronics my app has now answered an incoming (ringing) call
                        break;
                    case "4":
                        DoHoldCall(_callid); // place call on hold
                        break;
                    case "5":
                        DoResumeCall(_callid); // resume the call
                        break;
                    case "6":
                        DoMuteCall(true); // mute the headset (note for wireless products, audio link must be active)
                        break;
                    case "7":
                        DoMuteCall(false); // unmute the headset (note for wireless products, audio link must be active)
                        break;
                    case "8":
                        DoTerminateCall(_callid); // inform Plantronics my app has now terminated the call
                        break;
                    case "0":
                        quit = true;
                        break;
                    default:
                        Console.WriteLine("Unrecognised menu choice.");
                        break;
                }
            }

            // Cleanup the Plantronics COM API
            DetachDevice();
            _session.onCallStateChanged -= _session_onCallStateChanged;
            _sessionManagerEvents.onDeviceStateChanged -= _sessionManagerEvents_onDeviceStateChanged;
            _sessionManager.Unregister(_session);
            Marshal.ReleaseComObject(_session);
            Marshal.ReleaseComObject(_sessionManager);
        }

        private static void ShowMenu()
        {
            Console.WriteLine();
            Console.WriteLine("plt sample menu");
            Console.WriteLine("--");
            Console.WriteLine("1 - ring/incoming call");
            Console.WriteLine("2 - outgoing call");
            Console.WriteLine("3 - answer call");
            Console.WriteLine("4 - hold call");
            Console.WriteLine("5 - resume call");
            Console.WriteLine("6 - mute call");
            Console.WriteLine("7 - unmute call");
            Console.WriteLine("8 - end call");
            Console.WriteLine("0 - quit");
            Console.WriteLine();
            Console.Write("> ");
        }

        private static void DoIncomingCall(int callid, string contactname)
        {
            COMCall call = new COMCall() { Id = callid };
            Contact contact = new Contact() { Name = contactname };
            _callCommand.IncomingCall(call, contact, CallRingTone.RingTone_Unknown,
                CallAudioRoute.AudioRoute_ToHeadset);
            Console.WriteLine("Performing incoming call, id = " + callid);
        }

        private static void DoOutgoingCall(int callid, string contactname)
        {
            Console.WriteLine("Performing outgoing call, id = " + callid);
            COMCall call = new COMCall() { Id = callid };
            Contact contact = new Contact() { Name = contactname };
            _callCommand.OutgoingCall(call, contact, CallAudioRoute.AudioRoute_ToHeadset);
        }

        private static void DoAnswerCall(int callid)
        {
            Console.WriteLine("Performing outgoing call, id = " + callid);
            COMCall call = new COMCall() { Id = callid };
            _callCommand.AnsweredCall(call);
        }

        private static void DoHoldCall(int callid)
        {
            Console.WriteLine("Performing outgoing call, id = " + callid);
            COMCall call = new COMCall() { Id = callid };
            _callCommand.HoldCall(call);
        }

        private static void DoResumeCall(int callid)
        {
            Console.WriteLine("Performing outgoing call, id = " + callid);
            COMCall call = new COMCall() { Id = callid };
            _callCommand.ResumeCall(call);
        }

        private static void DoMuteCall(bool mute)
        {
            Console.WriteLine("Setting headset mute = " + mute);
            _deviceListener.mute = mute;
        }

        private static void DoTerminateCall(int callid)
        {
            Console.WriteLine("Performing outgoing call, id = " + callid);
            COMCall call = new COMCall() { Id = callid };
            _callCommand.TerminateCall(call);
        }

        private static void _session_onCallStateChanged(COMCallEventArgs callEventArgs)
        {
            // informs us the calling state has changed, for example user as answered/terminated a call
            // using headset buttons - this event should be used in my app to actually connect/terminate the call!
            Console.WriteLine("Call State Changed: callid=" + callEventArgs.call.Id + " new state=" + callEventArgs.CallState);
        }

        private static void _sessionManagerEvents_onDeviceStateChanged(COMStateDeviceEventArgs deviceEventArgs)
        {
            // device may have arrived or been removed. Either way detach device event handlers, then try to re-attach them
            DetachDevice();
            AttachDevice();
        }

        private static void AttachDevice()
        {
            // Lets get the primary Plantronics device (if any) and then register
            // for the device event handlers
            try
            {
                _device = _session.GetActiveDevice();
                if (_device != null)
                {
                    // display device information:
                    Console.WriteLine("Device attached: " + _device.ProductName + ", Product ID = " + _device.ProductId.ToString("X"));

                    _deviceListenerEvents = (ICOMDeviceListenerEvents_Event)_device.DeviceListener;
                    if (_deviceListenerEvents != null)
                    {
                        _deviceListenerEvents.onHeadsetStateChanged += _deviceListenerEvents_onHeadsetStateChanged;

                        Console.WriteLine("Successfully hooked to device listener events");
                    }
                    else
                        Console.WriteLine("Unable to hook to device listener events");

                    _deviceListener = _device.DeviceListener; // Obtain a DeviceListener object for later use

                    Console.WriteLine("Device mute state: muted = " + _deviceListener.mute); // Obtain initial device microphone mute state
                }
                else
                    Console.WriteLine("Unable to retrieve active device");
            }
            catch (Exception)
            {
                Console.WriteLine("Unable to retrieve/hook to active device");
            }
        }

        private static void _deviceListenerEvents_onHeadsetStateChanged(COMDeviceListenerEventArgs args)
        {
            // informs us of a variety of Plantronics device state changes
            Console.Write(args.DeviceEventType + " - ");
            if (args.DeviceEventType == COMDeviceEventType.DeviceEventType_HeadsetStateChanged)
            {
                Console.Write(args.HeadsetStateChange);
            }
            Console.WriteLine();
        }

        private static void DetachDevice()
        {
            // Lets un-register the Plantronics device event handlers
            if (_device != null)
            {
                Marshal.ReleaseComObject(_device);
                _device = null;
            }
            if (_deviceListenerEvents != null)
            {
                _deviceListenerEvents.onHeadsetStateChanged -= _deviceListenerEvents_onHeadsetStateChanged;
                Marshal.ReleaseComObject(_deviceListenerEvents);
                _deviceListenerEvents = null;
            }
        }

        // Convenience class for passing a COMContact to CallCommand IncomingCall/OutgoingCall methods
        class Contact : COMContact
        {
            public string Email { get; set; }
            public string FriendlyName { get; set; }
            public string HomePhone { get; set; }
            public int Id { get; set; }
            public string MobilePhone { get; set; }
            public string Name { get; set; }
            public string Phone { get; set; }
            public string SipUri { get; set; }
            public string WorkPhone { get; set; }
        }
    }
}
        
// PLTCPlusPlusSample.cpp : C++ COM sample demonstrating basic call control.
// NOTE: Spokes 3.0 COM SDK is distributed as tlb file. 
// C++ user can use #import directive (see below) that will create all proper C++ types, wrappers and interfaces for 
// communicating with running PLTHub.exe COM server

#include <stdio.h>
#include <tchar.h>
#include <atlbase.h>
#include <atlstr.h>
#include <fstream>
#include <istream>
#include <iostream>
#include <sstream>
#include <string>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include "atlcom.h"
#include <SDKDDKVer.h>

#import "Plantronics.tlb" no_namespace, named_guids, raw_interfaces_only

#ifdef _MSC_VER

#if _MSC_VER < 1900
// Version of the compiler before 19 (VS2015) did not properly handle this
#define __func__ __FUNCTION__
#define noexcept
#endif

#endif

#define _ATL_DEBUG_QI
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS      // some CString constructors will be explicit

using namespace std;
using namespace ATL;

// We need this as project is created as Win console app, and we are instantiating ATL COM objects that are used as Event sink's
CComModule _Module;
extern __declspec(selectany) CAtlModule* _pAtlModule=&_Module;

#define PRINT_ERROR(str, res) std::cout << str << "Error Code: " << std::hex << res << std::endl

ICOMSessionManagerPtr sessionManager;
ICOMSessionPtr session;

// Helper functions to print meaningful string representations of enums
std::string EnumToString(CallState val)
{
	std::string ret;
	switch (val)
	{
	case CallState_Unknown: ret = "CallState_Unknown"; break;
	case CallState_AcceptCall: ret = "CallState_AcceptCall"; break;
	case CallState_TerminateCall: ret = "CallState_TerminateCall"; break;
	case CallState_HoldCall: ret = "CallState_HoldCall"; break;
	case CallState_Resumecall: ret = "CallState_Resumecall"; break;
	case CallState_Flash: ret = "CallState_Flash"; break;
	case CallState_CallInProgress: ret = "CallState_CallInProgress"; break;
	case CallState_CallRinging: ret = "CallState_CallRinging"; break;
	case CallState_CallEnded: ret = "CallState_CallEnded"; break;
	case CallState_TransferToHeadSet: ret = "CallState_TransferToHeadSet"; break;
	case CallState_TransferToSpeaker: ret = "CallState_TransferToSpeaker"; break;
	case CallState_MuteON: ret = "CallState_MuteON"; break;
	case CallState_MuteOFF: ret = "CallState_MuteOFF"; break;
	case CallState_MobileCallRinging: ret = "CallState_MobileCallRinging"; break;
	case CallState_MobileCallInProgress: ret = "CallState_MobileCallInProgress"; break;
	case CallState_MobileCallEnded: ret = "CallState_MobileCallEnded"; break;
	case CallState_Don: ret = "CallState_Don"; break;
	case CallState_Doff: ret = "CallState_Doff"; break;
	case CallState_CallIdle: ret = "CallState_CallIdle"; break;
	case CallState_Play: ret = "CallState_Play"; break;
	case CallState_Pause: ret = "CallState_Pause"; break;
	case CallState_Stop: ret = "CallState_Stop"; break;
	case CallState_DTMFKey: ret = "CallState_DTMFKey"; break;
	case CallState_RejectCall: ret = "CallState_RejectCall"; break;
	}
	return ret;
}
std::string EnumToString(DeviceHeadsetStateChange val)
{
	std::string ret;
	switch (val)
	{
	case HeadsetStateChange_Unknown: ret = "HeadsetStateChange_Unknown"; break;
	case HeadsetStateChange_MonoON: ret = "HeadsetStateChange_MonoON"; break;
	case HeadsetStateChange_MonoOFF: ret = "HeadsetStateChange_MonoOFF"; break;
	case HeadsetStateChange_StereoON: ret = "HeadsetStateChange_StereoON"; break;
	case HeadsetStateChange_StereoOFF: ret = "HeadsetStateChange_StereoOFF"; break;
	case HeadsetStateChange_MuteON: ret = "HeadsetStateChange_MuteON"; break;
	case HeadsetStateChange_MuteOFF: ret = "HeadsetStateChange_MuteOFF"; break;
	case HeadsetStateChange_BatteryLevel: ret = "HeadsetStateChange_BatteryLevel"; break;
	case HeadsetStateChange_InRange: ret = "HeadsetStateChange_InRange"; break;
	case HeadsetStateChange_OutofRange: ret = "HeadsetStateChange_OutofRange"; break;
	case HeadsetStateChange_Docked: ret = "HeadsetStateChange_Docked"; break;
	case HeadsetStateChange_UnDocked: ret = "HeadsetStateChange_UnDocked"; break;
	case HeadsetStateChange_InConference: ret = "HeadsetStateChange_InConference"; break;
	case HeadsetStateChange_Don: ret = "HeadsetStateChange_Don"; break;
	case HeadsetStateChange_Doff: ret = "HeadsetStateChange_Doff"; break;
	case HeadsetStateChange_SerialNumber: ret = "HeadsetStateChange_SerialNumber"; break;
	case HeadsetStateChange_Near: ret = "HeadsetStateChange_Near"; break;
	case HeadsetStateChange_Far: ret = "HeadsetStateChange_Far"; break;
	case HeadsetStateChange_DockedCharging: ret = "HeadsetStateChange_DockedCharging"; break;
	case HeadsetStateChange_ProximityUnknown: ret = "HeadsetStateChange_ProximityUnknown"; break;
	case HeadsetStateChange_ProximityEnabled: ret = "HeadsetStateChange_ProximityEnabled"; break;
	case HeadsetStateChange_ProximityDisabled: ret = "HeadsetStateChange_ProximityDisabled"; break;
	}
	return ret;
}
std::string EnumToString(DeviceChangeState val)
{
	std::string ret;
	switch (val)
	{
	case DeviceState_Unknown: ret = "DeviceState_Unknown"; break;
	case DeviceState_Added: ret = "DeviceState_Added"; break;
	case DeviceState_Removed: ret = "DeviceState_Removed"; break;
	}
	return ret;
}
std::string EnumToString(COMDeviceEventType val)
{
	std::string ret;
	switch (val)
	{
	case DeviceEventType_Unknown: ret = "DeviceEventType_Unknown"; break;
	case DeviceEventType_HeadsetButtonPressed: ret = "DeviceEventType_HeadsetButtonPressed"; break;
	case DeviceEventType_HeadsetStateChanged: ret = "DeviceEventType_HeadsetStateChanged"; break;
	case DeviceEventType_BaseButtonPressed: ret = "DeviceEventType_BaseButtonPressed"; break;
	case DeviceEventType_BaseStateChanged: ret = "DeviceEventType_BaseStateChanged"; break;
	case DeviceEventType_ATDButtonPressed: ret = "DeviceEventType_ATDButtonPressed"; break;
	case DeviceEventType_ATDStateChanged: ret = "DeviceEventType_ATDStateChanged"; break;
	}
	return ret;
}

// CallObject
class ATL_NO_VTABLE CallObject :
	public CComObjectRootEx<CComSingleThreadModel>,
	public IDispatchImpl<ICOMCall, &__uuidof(ICOMCall), &LIBID_Spokes3GCOMServerLib, /*wMajor =*/ 3, /*wMinor =*/ 0 >
{
public:

	CallObject() { }
	
	CallObject(long id) { put_Id(id); }

	static CComObject<CallObject>* GetCallObject()
	{
		CComObject<CallObject>* pCall;
		CComObject<CallObject>::CreateInstance(&pCall);
		pCall->AddRef(); // this object is created with ref count 0;
		std::string strCallId;
		std::cout << "Enter Call Id: ";
		std::getline(std::cin, strCallId);

		long id;
		std::stringstream myStream(strCallId);
		myStream >> id;

		pCall->put_Id(id);
		return pCall;
	}

	// Constructor that takes the call id as a parameter
	static CComObject<CallObject>* GetCallObject(long id)
	{
		CComObject<CallObject>* pCall;
		CComObject<CallObject>::CreateInstance(&pCall);
		pCall->AddRef(); // this object is created with ref count 0;
		pCall->put_Id(id);
		return pCall;
	}
	
	BEGIN_COM_MAP(CallObject)
		COM_INTERFACE_ENTRY2(IDispatch, ICOMCall)
		COM_INTERFACE_ENTRY(ICOMCall)
	END_COM_MAP()
	
	DECLARE_PROTECT_FINAL_CONSTRUCT()

	HRESULT FinalConstruct() { return S_OK;	}

	void FinalRelease()	{ }

public:

private:
	long m_id;

// ICOMCall Methods
public:
	STDMETHOD(get_Id)(long * pVal)
	{
		*pVal = m_id;
		return S_OK;
	}
	STDMETHOD(put_Id)(long pVal)
	{
		m_id = pVal;
		return S_OK;
	}
	STDMETHOD(get_ConferenceId)(long * pVal) { return E_NOTIMPL; }
	STDMETHOD(put_ConferenceId)(long pVal) { return E_NOTIMPL; }
	STDMETHOD(get_InConference)(VARIANT_BOOL * pVal) { return E_NOTIMPL; }
	STDMETHOD(put_InConference)(VARIANT_BOOL pVal) { return E_NOTIMPL;}
};

// CallContact
class ATL_NO_VTABLE CallContact :
	public CComObjectRootEx<CComSingleThreadModel>,
	public IDispatchImpl<ICOMContact, &__uuidof(ICOMContact), &LIBID_Spokes3GCOMServerLib, /*wMajor =*/ 3, /*wMinor =*/ 0 >
{
private:
	_bstr_t m_name;
	_bstr_t m_friendName;
	LONG m_id;
	_bstr_t m_sipUri;
	_bstr_t m_phone;
	_bstr_t m_email;
	_bstr_t m_workPhone;
	_bstr_t m_mobilePhone;
	_bstr_t m_homePhone;
public:
	CallContact()
	{
		static LONG id = 0;
		m_id = ++id;
	}

	~CallContact() { }

	static CComObject<CallContact>* GetContactObject()
	{
		CComObject<CallContact>* pContact;
		CComObject<CallContact>::CreateInstance(&pContact);
		pContact->AddRef(); // this object is created with ref count 0;
		std::string name;
		std::cout << "Enter Contact Name: ";
		std::getline(std::cin, name);
		pContact->put_Name(_bstr_t(name.c_str()));
		return pContact;
	}

	// Added a function override taking contact name as a parameter
	static CComObject<CallContact>* GetContactObject(std::string name)
	{
		CComObject<CallContact>* pContact;
		CComObject<CallContact>::CreateInstance(&pContact);
		pContact->AddRef(); // this object is created with ref count 0;
		pContact->put_Name(_bstr_t(name.c_str()));
		return pContact;
	}

	BEGIN_COM_MAP(CallContact)
		COM_INTERFACE_ENTRY2(IDispatch, ICOMContact)
		COM_INTERFACE_ENTRY(ICOMContact)
	END_COM_MAP()
	
	DECLARE_PROTECT_FINAL_CONSTRUCT()

	HRESULT FinalConstruct() { return S_OK;	}

	void FinalRelease()	{ }

public:
// ICOMContact Methods
public:
	STDMETHOD(get_Name)(BSTR * pVal)
	{
		*pVal = SysAllocString(m_name);
		return S_OK;
	}
	STDMETHOD(put_Name)(BSTR pVal)
	{
		m_name = pVal;
		return S_OK;
	}
	STDMETHOD(get_FriendlyName)(BSTR * pVal)
	{
		*pVal = SysAllocString(m_friendName);
		return S_OK;
	}
	STDMETHOD(put_FriendlyName)(BSTR pVal)
	{
		m_friendName = pVal;
		return S_OK;
	}
	STDMETHOD(get_Id)(LONG * pVal)
	{
		*pVal = m_id;
		return S_OK;
	}
	STDMETHOD(put_Id)(LONG pVal)
	{
		m_id = pVal;
		return S_OK;
	}
	STDMETHOD(get_SipUri)(BSTR * pVal)
	{
		*pVal = SysAllocString(m_sipUri);
		return S_OK;
	}
	STDMETHOD(put_SipUri)(BSTR pVal)
	{
		m_sipUri = pVal;
		return S_OK;
	}
	STDMETHOD(get_Phone)(BSTR * pVal)
	{
		*pVal = SysAllocString(m_phone);
		return S_OK;
	}
	STDMETHOD(put_Phone)(BSTR pVal)
	{
		m_phone = pVal;
		return S_OK;
	}
	STDMETHOD(get_Email)(BSTR * pVal)
	{
		*pVal = SysAllocString(m_email);
		return S_OK;
	}
	STDMETHOD(put_Email)(BSTR pVal)
	{
		m_email = pVal;
		return S_OK;
	}
	STDMETHOD(get_WorkPhone)(BSTR * pVal)
	{
		*pVal = SysAllocString(m_workPhone);
		return S_OK;
	}
	STDMETHOD(put_WorkPhone)(BSTR pVal)
	{
		m_workPhone = pVal;
		return S_OK;
	}
	STDMETHOD(get_MobilePhone)(BSTR * pVal)
	{
		*pVal = SysAllocString(m_mobilePhone);
		return S_OK;
	}
	STDMETHOD(put_MobilePhone)(BSTR pVal)
	{
		m_mobilePhone = pVal;
		return S_OK;
	}
	STDMETHOD(get_HomePhone)(BSTR * pVal)
	{
		*pVal = SysAllocString(m_homePhone);
		return S_OK;
	}
	STDMETHOD(put_HomePhone)(BSTR pVal)
	{
		m_homePhone = pVal;
		return S_OK;
	}
};

_ATL_FUNC_INFO OnDeviceEvents = { CC_STDCALL, VT_EMPTY, 1,{ VT_DISPATCH } };

// DeviceListenerEventSink() : COM class : Sink for DeviceListener Events
class DeviceListenerEventSink :
	public CComObjectRootEx<CComSingleThreadModel>,
	public IDispatchImpl<ICOMDeviceListenerEvents, &__uuidof(ICOMDeviceListenerEvents), &LIBID_Spokes3GCOMServerLib, /*wMajor =*/ 3, /*wMinor =*/ 0>
{
public:

	BEGIN_COM_MAP(DeviceListenerEventSink)
		COM_INTERFACE_ENTRY(IDispatch)
		COM_INTERFACE_ENTRY_IID(__uuidof(ICOMDeviceListenerEvents), IDispatch)
	END_COM_MAP()

	// ICOMDeviceListenerEvents Methods
	STDMETHOD(onHeadsetStateChanged)(struct ICOMDeviceListenerEventArgs *args);
	STDMETHOD(onHeadsetButtonPressed)(struct ICOMDeviceListenerEventArgs *args);
	STDMETHOD(onBaseButtonPressed)(struct ICOMDeviceListenerEventArgs *args);
	STDMETHOD(onBaseStateChanged)(struct ICOMDeviceListenerEventArgs *args);
	STDMETHOD(onATDStateChanged)(struct ICOMDeviceListenerEventArgs *args);
	
	DECLARE_PROTECT_FINAL_CONSTRUCT()
	HRESULT FinalConstruct() { return S_OK;	}
};

bool gGotDevice = false;

// DeviceListenerSink() : user defined class : wrapper to Advise/Unadvise on Device Listener events
class DeviceListenerSink
{
public:
	DeviceListenerSink(ICOMSessionPtr session)
	{
        if (session != nullptr)
		{
			ICOMDevicePtr device;
			if (SUCCEEDED(session->GetActiveDevice(&device)) && device != nullptr)
			{
				if (SUCCEEDED(device->get_DeviceListener(&m_deviceListener)) && m_deviceListener != nullptr)
				{
					if (SUCCEEDED(CComObject<DeviceListenerEventSink>::CreateInstance(&m_sink)) && m_sink != nullptr)
					{
						m_sink->AddRef();
						HRESULT hr = AtlAdvise(m_deviceListener, m_sink, __uuidof(ICOMDeviceListenerEvents), &m_cookie);
						if (FAILED(hr)) PRINT_ERROR("Advise Device Listener event Error: ", hr);
                        else
                        {
                            gGotDevice = true;
                        }
					}
				}
			}
		}
	}
	~DeviceListenerSink()
	{
        if (m_deviceListener != nullptr)
        {
            HRESULT hr = AtlUnadvise(m_deviceListener, __uuidof(ICOMDeviceListenerEvents), m_cookie);
            if (FAILED(hr)) PRINT_ERROR("Unadvise Device Listener event Error: ", hr);
        }
        if (m_sink != nullptr)
        {
            m_sink->Release();
        }
        gGotDevice = false;
	}
private:
	CComObject<DeviceListenerEventSink> *m_sink = nullptr;
	ICOMDeviceListenerPtr m_deviceListener = nullptr;
	DWORD m_cookie;
};

// SessionEventSink() : COM class : Sink for Session Call Events
class SessionEventSink :
	public CComObjectRootEx<CComSingleThreadModel>,
	public IDispatchImpl<ICOMCallEvents, &__uuidof(ICOMCallEvents), &LIBID_Spokes3GCOMServerLib, /*wMajor =*/ 3, /*wMinor =*/ 0 >
{
public:

	BEGIN_COM_MAP(SessionEventSink)
		COM_INTERFACE_ENTRY(IDispatch)
		COM_INTERFACE_ENTRY_IID(__uuidof(ICOMCallEvents), IDispatch)
	END_COM_MAP()

	// ICOMCallEvents Methods
	STDMETHOD(onCallRequested)(struct ICOMCallRequestEventArgs *args);
	STDMETHOD(onCallStateChanged)(struct ICOMCallEventArgs *args);
	
	DECLARE_PROTECT_FINAL_CONSTRUCT()
	HRESULT FinalConstruct() { return S_OK;	}
};

// SessionManagerEventSink() : COM class : Sink for Session Manager events
class SessionManagerEventSink :
	public CComObjectRootEx<CComSingleThreadModel>,
	public IDispatchImpl<ICOMSessionManagerEvents, &__uuidof(ICOMSessionManagerEvents), &LIBID_Spokes3GCOMServerLib, /*wMajor =*/ 3, /*wMinor =*/ 0 >
{
public:

	BEGIN_COM_MAP(SessionManagerEventSink)
		COM_INTERFACE_ENTRY(IDispatch)
		COM_INTERFACE_ENTRY_IID(__uuidof(ICOMSessionManagerEvents), IDispatch)
	END_COM_MAP()

	// ICOMSessionManagerEvents Methods
	STDMETHOD(onDeviceStateChanged)(struct ICOMStateDeviceEventArgs *args);
	STDMETHOD(onCallStateChanged)(struct ICOMCallEventArgs *args);
	
	DECLARE_PROTECT_FINAL_CONSTRUCT()
	HRESULT FinalConstruct() { return S_OK;	}
};

// SessionSink() : user defined class : wrapper to Advise/Unadvise on Session Call events
class SessionSink
{
public:
	SessionSink(ICOMSessionPtr session)
	{
		if (session != nullptr)
		{
			m_session = session;
			if (SUCCEEDED(CComObject<SessionEventSink>::CreateInstance(&m_sink)) && m_sink != nullptr)
			{
				m_sink->AddRef();
				HRESULT hr = AtlAdvise(m_session, m_sink, __uuidof(ICOMCallEvents), &m_cookie);
				if (FAILED(hr)) PRINT_ERROR("Advise Session Call event Error", hr);
			}
		}
	}
	~SessionSink()
	{
		if (m_session != nullptr)
        {
            HRESULT hr = AtlUnadvise(m_session, __uuidof(ICOMCallEvents), m_cookie);
            if (FAILED(hr)) PRINT_ERROR("Unadvise Session Call event Error", hr);
        }
        if (m_sink != nullptr)
        {
            m_sink->Release();
        }
	}
private:
	CComObject<SessionEventSink> *m_sink = nullptr;
	ICOMSessionPtr m_session = nullptr;
	DWORD m_cookie;
};

// SessionManagerSink() : user defined class : wrapper to Advise/Unadvise on Session Manager events
class SessionManagerSink
{
public:
	SessionManagerSink(ICOMSessionManagerPtr sessionManager)
	{
		m_sessionManager = sessionManager;
		CComObject<SessionManagerEventSink>::CreateInstance(&m_sink);
		if (m_sessionManager != nullptr && m_sink != nullptr)
		{
			m_sink->AddRef();
			HRESULT hr = AtlAdvise(m_sessionManager, m_sink, __uuidof(ICOMSessionManagerEvents), &m_cookie);
			if (FAILED(hr)) PRINT_ERROR("Advise Session Manager events Error", hr);
		}
	}
	~SessionManagerSink()
	{
		if (m_sessionManager != nullptr)
        {
            HRESULT hr = AtlUnadvise(m_sessionManager, __uuidof(ICOMSessionManagerEvents), m_cookie);
            if (FAILED(hr)) PRINT_ERROR("Unadvise Session Manager events Error", hr);
        }
        if (m_sink != nullptr)
        {
            m_sink->Release();
        }
	}
private:
	CComObject<SessionManagerEventSink> *m_sink = nullptr;
	ICOMSessionManagerPtr m_sessionManager = nullptr;
	DWORD m_cookie;
};

// DeviceSink() : user defined class : wrapper to Advise/Unadvise on Device events
class DeviceSink
{
public:
	DeviceSink(ICOMSessionPtr session)
	{
		if (session != nullptr)
		{
			if (SUCCEEDED(session->GetActiveDevice(&m_device)) && m_device != nullptr)
			{
				// hook to device listener event
				m_devListSink = new DeviceListenerSink(session);
			}
		}
	}
	~DeviceSink()
	{
		delete m_devListSink;
	}
private:
	DeviceListenerSink* m_devListSink = nullptr;
	ICOMDevicePtr m_device = nullptr;
};

STDMETHODIMP SessionEventSink::onCallRequested(struct ICOMCallRequestEventArgs *requestArgs)
{
	std::string contactName;
	ICOMContact* callContact = nullptr;
	requestArgs->get_contact(&callContact);
	if (callContact != nullptr)
	{
		BSTR name;
		callContact->get_Name(&name);
		contactName = (name != nullptr ? _bstr_t(name) : "none");
	}
	std::cout << std::endl << "(Session) SessionEventSink onCallRequested contact: " << contactName;
	return S_OK;
}
STDMETHODIMP SessionEventSink::onCallStateChanged(struct ICOMCallEventArgs *callEventArgs)
{
	CallState callState;
	callEventArgs->get_CallState(&callState);

	long callId(0);
	ICOMCall* call = nullptr;
	callEventArgs->get_call(&call);
	call->get_Id(&callId);

	std::cout << "Call State Changed: callid=" << callId << " new state=" << EnumToString(callState) << std::endl;
	return S_OK;
}

STDMETHODIMP SessionManagerEventSink::onDeviceStateChanged(struct ICOMStateDeviceEventArgs *args)
{
	DeviceChangeState devState;
	args->get_DeviceState(&devState);
	std::cout << "onDeviceStateChanged: " << EnumToString(devState) << std::endl;

    gGotDevice = false; // schedule a device re-attach attempt next time around the main menu loop

    if (devState == DeviceState_Added)
    {
        std::cout << "Press Enter to attach this device." << std::endl;
    }

	return S_OK;
}
STDMETHODIMP SessionManagerEventSink::onCallStateChanged(struct ICOMCallEventArgs *callEventArgs) { return S_OK; }

STDMETHODIMP DeviceListenerEventSink::onHeadsetStateChanged(struct ICOMDeviceListenerEventArgs *args)
{
	DeviceHeadsetStateChange state;
	COMDeviceEventType evType;
	args->get_HeadsetStateChange(&state);
	args->get_DeviceEventType(&evType);

	if (evType == COMDeviceEventType::DeviceEventType_HeadsetStateChanged)
	{
		std::cout << EnumToString(evType) << ", state = " << EnumToString(state) << std::endl;
	}


	return S_OK;
}
STDMETHODIMP DeviceListenerEventSink::onHeadsetButtonPressed(struct ICOMDeviceListenerEventArgs *args)
{
	DeviceHeadsetButton btn;
	COMDeviceEventType evType;
	args->get_HeadsetButton(&btn);
	args->get_DeviceEventType(&evType);

	if ((evType == COMDeviceEventType::DeviceEventType_HeadsetButtonPressed) && (btn == DeviceHeadsetButton::HeadsetButton_Mute))
	{
		ICOMDevicePtr device;
		ICOMDeviceListenerPtr deviceListener;
		VARIANT_BOOL valBool;
		session->GetActiveDevice(&device);
		device->get_DeviceListener(&deviceListener);
		deviceListener->get_mute(&valBool);
		std::cout << "DeviceEventType_HeadsetStateChanged - ";
		(valBool == VARIANT_TRUE) ? std::cout << EnumToString(DeviceHeadsetStateChange::HeadsetStateChange_MuteON) : std::cout << EnumToString(DeviceHeadsetStateChange::HeadsetStateChange_MuteOFF); std::cout << std::endl;
	}
	return S_OK;
}
STDMETHODIMP DeviceListenerEventSink::onBaseButtonPressed(struct ICOMDeviceListenerEventArgs *args) { return S_OK; }
STDMETHODIMP DeviceListenerEventSink::onBaseStateChanged(struct ICOMDeviceListenerEventArgs *args) { return S_OK; }
STDMETHODIMP DeviceListenerEventSink::onATDStateChanged(struct ICOMDeviceListenerEventArgs *args) { return S_OK; }

// Implementation of C++ COM sample
DeviceSink* gDeviceSink = nullptr;

//  helper function that:
//	- gets the handle to the currently active device, 
//	- gets a handle to deviceListner object
//	- prints its ProductName, ProductID & mute status of the device
void PrintDeviceDetails()
{
	HRESULT res = S_OK;
	BSTR productName;
	USHORT productId;
	VARIANT_BOOL valBool;

	ICOMDevicePtr myDevice;
	ICOMDeviceListenerPtr myDeviceListener;
	
	// get current active device from active session
	if (FAILED(res = session->GetActiveDevice(&myDevice)))
	{
		std::cout << "Unable to retrieve/hook to active device" << std::endl;
		return; 
	}

	// get device listener interface
	if (FAILED(res = myDevice->get_DeviceListener(&myDeviceListener)))
	{
		std::cout << "Device is not attached"; 
		return;
	}
	std::cout << "Successfully hooked to device listener events" << std::endl;

	// get & display device information
	myDevice->get_ProductName(&productName);
	myDevice->get_ProductId(&productId);
	_bstr_t bproductName(productName);
	std::cout << "Device attached: " << bproductName;
	std::wcout << L", Product ID = " << std::uppercase << std::hex << std::setfill(L'0') << std::setw(4) << productId << std::endl;
	
	// Obtain initial device microphone mute state
	res = myDeviceListener->get_mute(&valBool);
	std::cout << "Device mute state: muted = "; 
	(valBool == VARIANT_TRUE) ? std::cout << "True" : std::cout << "False"; std::cout << std::endl;
	
	return;
}

void ShowMenu()
{
	std::cout << std::endl;
	std::cout << "plt sample menu" << std::endl;
	std::cout << "--" << std::endl;
	std::cout << "1 - ring/incoming call" << std::endl;
	std::cout << "2 - outgoing call" << std::endl;
	std::cout << "3 - answer call" << std::endl;
	std::cout << "4 - hold call" << std::endl;
	std::cout << "5 - resume call" << std::endl;
	std::cout << "6 - mute call" << std::endl;
	std::cout << "7 - unmute call" << std::endl;
	std::cout << "8 - end call" << std::endl;
	std::cout << "0 - quit" << std::endl;
	std::cout << std::endl;
	std::cout << "> ";
}

void DoIncomingCall(int callid, string contactname)
{
	std::cout << "Performing IncomingCall, id = " << callid << std::endl;
	HRESULT res = S_OK;
	ICOMCallCommandPtr callCommand;

	if (FAILED(res = session->GetCallCommand(&callCommand)))
	{
		PRINT_ERROR("Error gettting interface from Call Command", res); return;
	}

	res = callCommand->IncomingCall(CallObject::GetCallObject(callid), CallContact::GetContactObject(contactname), RingTone_Unknown, AudioRoute_ToHeadset);
}

void DoOutgoingCall(int callid, string contactname)
{
	std::cout << "Performing OutgoingCall, id = " << callid << std::endl;
	HRESULT res = S_OK;
	ICOMCallCommandPtr callCommand;

	if (FAILED(res = session->GetCallCommand(&callCommand)))
	{
		PRINT_ERROR("Error gettting interface from Call Command", res); return;
	}

	res = callCommand->OutgoingCall(CallObject::GetCallObject(callid), CallContact::GetContactObject(contactname), AudioRoute_ToHeadset);
}

void DoAnswerCall(int callid)
{
	std::cout << "Performing AnswerCall, id = " << callid << std::endl;
	HRESULT res = S_OK;
	ICOMCallCommandPtr callCommand;

	if (FAILED(res = session->GetCallCommand(&callCommand)))
	{
		PRINT_ERROR("Error gettting interface from Call Command", res); return;
	}

	res = callCommand->AnsweredCall(CallObject::GetCallObject(callid));
}

void DoTerminateCall(int callid)
{
	std::cout << "Performing TerminateCall, id = " << callid << std::endl;
	HRESULT res = S_OK;
	ICOMCallCommandPtr callCommand;

	if (FAILED(res = session->GetCallCommand(&callCommand)))
	{
		PRINT_ERROR("Error gettting interface from Call Command", res); return;
	}

	res = callCommand->TerminateCall(CallObject::GetCallObject(callid));
}

void DoHoldCall(int callid)
{
	std::cout << "Performing HoldCall, id = " << callid << std::endl;
	HRESULT res = S_OK;
	ICOMCallCommandPtr callCommand;

	if (FAILED(res = session->GetCallCommand(&callCommand)))
	{
		PRINT_ERROR("Error gettting interface from Call Command", res); return;
	}

	res = callCommand->HoldCall(CallObject::GetCallObject(callid));
}

void DoResumeCall(int callid)
{
	std::cout << "Performing ResumeCall, id = " << callid << std::endl;
	HRESULT res = S_OK;
	ICOMCallCommandPtr callCommand;

	if (FAILED(res = session->GetCallCommand(&callCommand)))
	{
		PRINT_ERROR("Error gettting interface from Call Command", res); return;
	}

	res = callCommand->ResumeCall(CallObject::GetCallObject(callid));
}

void DoMuteCall(int callid, bool mute)
{
	std::cout << "Setting headset mute = ";
	(mute == true) ? std::cout << "True" : std::cout << "False"; std::cout << std::endl;
	
	HRESULT res = S_OK;
	ICOMCallCommandPtr callCommand;

	if (FAILED(res = session->GetCallCommand(&callCommand)))
	{
		PRINT_ERROR("Error gettting interface from Call Command", res); return;
	}

	(mute == true) ? res = callCommand->MuteCall(CallObject::GetCallObject(callid), VARIANT_TRUE) : res = callCommand->MuteCall(CallObject::GetCallObject(callid), VARIANT_FALSE);
}

// sample entry point

int _tmain(int argc, _TCHAR* argv[])
{
	bool quit = false;
	std::string input;
	int cmd = -1;
	int _callid = 0; // variable to track call id between my app and Plantronics

	// Initialize COM
	{
		::CoInitializeEx(NULL, COINIT_MULTITHREADED);

		std::cout << "C++ Plantronics COM API Sample" << std::endl;

		// Create Spokes session manager object
		if (SUCCEEDED(sessionManager.CreateInstance(CLSID_COMSessionManager)))
		{
			// register Session Manager for device attached/detached events
			SessionManagerSink sessionManagerSink(sessionManager);

			// Register new session in Spokes.
			if (SUCCEEDED(sessionManager->Register(_bstr_t("COM Plugin"), &session)))
			{
				// register Session for call state change events
				SessionSink sessionSink(session);

				ICOMUserPreference *userPref;
				sessionManager->get_UserPreference(&userPref);
				userPref->put_DefaultSoftphone(_bstr_t("COM Plugin"));

				while (!quit)
				{
                    // If we have not attached to device (or device was removed/added), then attempt to attach to a device
                    if (!gGotDevice)
                    {
                        if (gDeviceSink != nullptr)
                        {
                            delete gDeviceSink;
                        }
                        std::cout << "Attempting device attach" << std::endl;
                        // Instantiate Device Sink object and hook to DeviceListner events
                        gDeviceSink = new DeviceSink(session);

                        // call to AttachDevice()
                        PrintDeviceDetails();
                    }

					ShowMenu();
					std::getline(std::cin, input);

					// Convert from string to number safely.
					std::stringstream inputStringStream(input);
					if (!(inputStringStream >> cmd))
					{
						cmd = -1;
						// check if device is attached and prompt error if it is
						if (gGotDevice == true)
							std::cout << "Invalid input, please try again" << std::endl;
					}

					switch (cmd)
					{
						case 1:
							_callid++;
							DoIncomingCall(_callid, "Bob%20Smith"); // inform Plantronics my app has an incoming (ringing) call
							break;
						case 2:
							_callid++;
							DoOutgoingCall(_callid, "Bob%20Smith"); // inform Plantronics my app has an outgoing call
							break;
						case 3:
							DoAnswerCall(_callid); // inform Plantronics my app has now answered an incoming (ringing) call
							break;
						case 4:
							DoHoldCall(_callid); // place call on hold
							break;
						case 5:
							DoResumeCall(_callid); // resume the call
							break;
						case 6:
							DoMuteCall(_callid, true); // mute the headset (note for wireless products, audio link must be active)
							break;
						case 7:
							DoMuteCall(_callid, false); // unmute the headset (note for wireless products, audio link must be active)
							break;
						case 8:
							DoTerminateCall(_callid); // inform Plantronics my app has now terminated the call
							break;
						case 0:
							quit = true;
							break;
						default:
							if (gGotDevice == true)
								std::cout << "Unrecognised menu choice, please try again." << std::endl;
							break;
					}
					Sleep(250);
				}
				// cleanup unregister session
                if (gDeviceSink != nullptr)
                {
                    delete gDeviceSink;
                }
                sessionManager->Unregister(session);
			}
		}
	}

	// uninitialize COM
	::CoUninitialize();

	return 0;
}
// PlantronicsRESTDemo.java : Java sample demonstrating basic call control.
package plantronicsrestdemo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

// Pre-requisite, install Plantronics Hub from: http://www.plantronics.com/software
public class PlantronicsRESTDemo {

    static Boolean quit = false;
    static String sessionid = "";
    static Boolean pluginRegistered = false;
    static int callid = 0;
    private static EventsListenerThread eventslistener = null;
    
    private static String BaseURL = "http://127.0.0.1:32017/";
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        System.out.println("Java Plantronics REST API Sample");
               
        DoAttachDevice(); // Connect to the Plantronics REST API
        DoShowDeviceInfo(); // get info about attached device (if any)
         
        // start polling the API for device/call events or to reconnect to API (in the event of connection failure)...
        DoStartEventsListener(sessionid);
        
        while (!quit)
        {
            ShowMenu();
            String cmd = ReadCommand();          
            switch (cmd)
            {
                case "1":
                    DoIncomingCall(++callid, "Bob%20Smith"); // inform Plantronics my app has an incoming (ringing) call
                    break;
                case "2":
                    DoOutgoingCall(++callid, "Bob%20Smith"); // inform Plantronics my app has an outgoing call
                    break;
                case "3":
                    DoAnswerCall(callid); // inform Plantronics my app has now answered an incoming (ringing) call
                    break;
                case "4":
                    DoHoldCall(callid); // place call on hold
                    break; 
                case "5":
                    DoResumeCall(callid); // resume the call
                    break;
                case "6":
                    DoMute(true); // mute the headset (note for legacy wireless products, audio link must be active)
                    break;
                case "7":
                    DoMute(false); // unmute the headset (note for legacy wireless products, audio link must be active)
                    break;
                 case "8":
                    DoTerminateCall(callid); // inform Plantronics my app has now terminated the call
                    break;
                 case "0":
                    quit = true;
                    break;
                 default:
                    System.out.println("Unrecognised menu choice.");
                    break;
            }
        }
        
        DoStopEventsListener(); // stop events polling
        DoReleaseDevice(); // Cleanup the Plantronics REST API
    }

    private static void ShowMenu() {
        System.out.println();
        System.out.println("plt sample menu");
        System.out.println("--");
        System.out.println("1 - ring/incoming call");
        System.out.println("2 - outgoing call");
        System.out.println("3 - answer call");
        System.out.println("4 - hold call");
        System.out.println("5 - resume call");
        System.out.println("6 - mute call");
        System.out.println("7 - unmute call");
        System.out.println("8 - end call");
        System.out.println("0 - quit");
        System.out.println();
        System.out.print("Type a command> ");
    }

    private static String ReadCommand() {
        BufferedReader buffer=new BufferedReader(new InputStreamReader(System.in));
        String line = "";
        try {
            line = buffer.readLine();
        } catch (IOException ex) {
            Logger.getLogger(PlantronicsRESTDemo.class.getName()).log(Level.SEVERE, null, ex);
        }
        return line;
    }

    public static void DoShowDeviceInfo() {
        // get info about attached device (if any)
        RESTConvenienceClass.SendRESTCommand(
            BaseURL + "Spokes/DeviceServices/Info"
        );
    }

    // Connect to the Plantronics REST API
    public static void DoAttachDevice() {
        // Connect to the Plantronics REST API (attach device session)
        String tmpResult = 
            RESTConvenienceClass.SendRESTCommand(
                BaseURL + "Spokes/DeviceServices/Attach?uid=0123456789"
            );
        int pos = tmpResult.indexOf("\"Result\":\"");
        if (pos>-1)
        {
            tmpResult = tmpResult.substring(pos+10);
            pos = tmpResult.indexOf("\"");
            if (pos>-1)
            {
                tmpResult = tmpResult.substring(0, pos);
                System.out.println("Session id is: " + tmpResult);
                sessionid = tmpResult;
            }
            try 
            {
                Thread.sleep(250);
            } 
            catch (InterruptedException ex) 
            {
                Logger.getLogger(PlantronicsRESTDemo.class.getName()).log(Level.SEVERE, null, ex);
            }                                  
                                        
            if (sessionid.length()>0)
            {
                // Register Spokes plugin (Plantronics API application session)
                RESTConvenienceClass.SendRESTCommand(
                    BaseURL + "Spokes/SessionManager/Register?name=My%20Java%20Plugin"
                );               
                pluginRegistered = true;
            }
        }
        else     
        {
            System.out.println("Error: Connecting to Device failed, no Result/session detected in response");
        }
    }

    // Cleanup the Plantronics REST API
    public static void DoReleaseDevice() {
        // Unregister Spokes plugin (Plantronics API application session)
        RESTConvenienceClass.SendRESTCommand(
            BaseURL + "Spokes/SessionManager/UnRegister?name=My%20Java%20Plugin"
        );                
        pluginRegistered = false;

        try 
        {
            Thread.sleep(250);
        } 
        catch (InterruptedException ex) 
        {
            Logger.getLogger(PlantronicsRESTDemo.class.getName()).log(Level.SEVERE, null, ex);
        }      
            
        if (sessionid.length()>0)
        {
            // Disconnect from the Plantronics REST API (release device session)
            RESTConvenienceClass.SendRESTCommand(
                BaseURL + "Spokes/DeviceServices/Release?sess="
                    + sessionid
            );
            sessionid = "";            
        }
    }

    private static void DoIncomingCall(int callid, String caller_name) {
        if (sessionid.length()>0 && pluginRegistered)
        {
            // inform Plantronics my app has an incoming (ringing) call
            RESTConvenienceClass.SendRESTCommand(
                BaseURL + "Spokes/CallServices/IncomingCall?name=My%20Java%20Plugin&callID=%7B%22Id%22%3A%22"
                    + callid 
                    + "%22%7D&contact=%7B%22Name%22%3A%22"
                    + caller_name 
                    + "%22%7D&tones=Unknown&route=ToHeadset"
            );
        }
        else
        {
            System.out.println("Error: You need to Attach device first");
        }
    }
    
    private static void DoOutgoingCall(int callid, String caller_name) {
        if (sessionid.length()>0 && pluginRegistered)
        {
            // inform Plantronics my app has an outgoing call
            RESTConvenienceClass.SendRESTCommand(
                BaseURL + "Spokes/CallServices/OutgoingCall?name=My%20Java%20Plugin&callID=%7B%22Id%22%3A%22"
                    + callid 
                    + "%22%7D&contact=%7B%22Name%22%3A%22"
                    + caller_name 
                    + "%22%7D&route=ToHeadset"
            );
        }
        else
        {
            System.out.println("Error: You need to Attach device first");
        }
    }

        private static void DoTerminateCall(int callid) {
        if (sessionid.length()>0 && pluginRegistered)
        {
            // inform Plantronics my app has now terminated the call
            RESTConvenienceClass.SendRESTCommand(
                BaseURL + "Spokes/CallServices/TerminateCall?name=My%20Java%20Plugin&callID=%7B%22Id%22%3A%22"
                    + callid 
                    + "%22%7D"
            );
        }
        else
        {
            System.out.println("Error: You need to Attach device first");
        }
    }

    private static void DoAnswerCall(int callid) {
        if (sessionid.length()>0 && pluginRegistered)
        {
            // inform Plantronics my app has now answered an incoming (ringing) call
            RESTConvenienceClass.SendRESTCommand(
                BaseURL + "Spokes/CallServices/AnswerCall?name=My%20Java%20Plugin&callID=%7B%22Id%22%3A%22"
                    + callid 
                    + "%22%7D"
            );
        }
        else
        {
            System.out.println("Error: You need to Attach device");
        }
    }

    private static void DoHoldCall(int callid) {
        if (sessionid.length()>0 && pluginRegistered)
        {
            // place call on hold
            RESTConvenienceClass.SendRESTCommand(
                BaseURL + "Spokes/CallServices/HoldCall?name=My%20Java%20Plugin&callID=%7B%22Id%22%3A%22"
                    + callid 
                    + "%22%7D"
            );
        }
        else
        {
            System.out.println("Error: You need to Attach device");
        }
    }

    private static void DoResumeCall(int callid) {
        if (sessionid.length()>0 && pluginRegistered)
        {
            // resume the call
            RESTConvenienceClass.SendRESTCommand(
                BaseURL + "Spokes/CallServices/ResumeCall?name=My%20Java%20Plugin&callID=%7B%22Id%22%3A%22"
                    + callid 
                    + "%22%7D"
            );
        }
        else
        {
            System.out.println("Error: You need to Attach device");
        }
    }

    private static void DoMute(boolean muted) {
        if (sessionid.length()>0 && pluginRegistered)
        {
            // mute/un-mute the headset (note for legacy wireless products, audio link must be active)
            RESTConvenienceClass.SendRESTCommand(
                BaseURL + "Spokes/CallServices/MuteCall?name=My%20Java%20Plugin&muted="
                    + muted 
            );
        }
        else
        {
            System.out.println("Error: You need to Attach device first");
        }
    }    

    private static void DoStartEventsListener(String sessid) {
        if (eventslistener==null)
        {
            eventslistener = new EventsListenerThread(sessid, "My%20Java%20Plugin");
            eventslistener.start();
        }
    }
    
    private static void DoStopEventsListener() {
        if (eventslistener!=null)
        {
            eventslistener.shutdown();
            eventslistener = null;
        }
    }

    private static void DoGetCallManagerState() {
        if (sessionid.length()>0 && pluginRegistered)
        {
            // query current Plantronics API call manager state
            RESTConvenienceClass.SendRESTCommand(
                BaseURL + "Spokes/CallServices/CallManagerState"
           );
        }
        else
        {
            System.out.println("Error: You need to Attach device first");
        }
    }
}
class EventsListenerThread extends Thread
{
    private Boolean quit = false;
    private Boolean deviceAttached = true;
    private String sessid = "";
    private String plugin_name;
    private static String BaseURL = "http://127.0.0.1:32017/";
    //private static String BaseURL = "https://127.0.0.1:32018/";
    
    public EventsListenerThread(String sessid, String plugin_name)
    {
        this.sessid = sessid;
        this.plugin_name = plugin_name;
        if (sessid.length()==0)
        {
            deviceAttached = false;
        }
    }
    
    public void run ()
    {
        System.out.println("Events Listener Starting");
        while (!quit) {
            Sleep(1800);
            if (!quit)
            {
                System.out.println();
                System.out.println();
                if (!deviceAttached)
                {
                    System.out.println("-- POLLING FOR DEVICE RE-ATTACH --");
                    PlantronicsRESTDemo.DoAttachDevice();
                    if (PlantronicsRESTDemo.sessionid.length()>0)
                    {
                        sessid = PlantronicsRESTDemo.sessionid;
                        deviceAttached = true;
                        PlantronicsRESTDemo.DoShowDeviceInfo();
                    }
                }
                else
                {
                    System.out.println("-- POLLING FOR EVENTS --");
                    // deviceEvents informs us of a variety of Plantronics device state changes
                    // SPECIAL CASE device removal - with the REST API, when device is removed
                    // polling for device events will return error Error of type Invalid session id
                    String deviceEvents = RESTConvenienceClass.SendRESTCommandWithDebugPrompt(
                        BaseURL + "Spokes/DeviceServices/Events?sess="
                            + sessid
                            + "&queue=0"
                        , "DEVICE EVENTS", true
                    );
                    if(deviceEvents.contains("Invalid session id") || deviceEvents.contains("Empty session id"))
                    {
                        System.out.println("-- ** DEVICE DETACHED / SESSION INVALID ** --");                    
                        deviceAttached = false;
                        PlantronicsRESTDemo.sessionid = "";
                        PlantronicsRESTDemo.DoReleaseDevice();
                    }
                    Sleep(200);            
                    if (!quit && deviceAttached)
                    {
                        // session Call Events informs us the calling state has changed, for example user as answered/terminated a call
                        // using headset buttons - this event should be used in my app to actually connect/terminate the call!
                        String callEvents = RESTConvenienceClass.SendRESTCommandWithDebugPrompt(
                            BaseURL + "Spokes/CallServices/CallEvents?name=My%20Java%20Plugin"
                            , "CALL EVENTS", true
                        );
                        ParseCallEvents(callEvents);
                    }
                }
            }
        }
        System.out.println("Events Listener Stopping");
    }
    
    private void ParseCallEvents(String callEvents)
    {
        if (callEvents.contains("[") || callEvents.contains("\"Action\":"))
        {
            callEvents = callEvents.substring(callEvents.indexOf("["));
            callEvents = callEvents.substring(0, callEvents.indexOf("]"));
            List<String> actions = Arrays.asList(callEvents.split("\"Action\":"));
            String callId;
            for(Iterator<String> i = actions.iterator(); i.hasNext(); ) {
                String item = i.next();
                int pos = item.indexOf(",");
                if (pos>-1)
                {
                    callId = item.substring(item.indexOf("\"Id\":") + 5);
                    callId = callId.substring(0, callId.indexOf(","));
                    System.out.println("CallState: " + RESTConvenienceClass.eCallState.get(item.substring(0, pos)) +
                            ", " + "Call ID: " + callId);
                }
            }
        }
    }
    
    public void shutdown()
    {
        quit = true;
    }

    private void Sleep(int millis) {
        try {        
            Thread.sleep(millis);
        } catch (InterruptedException ex) {
            Logger.getLogger(EventsListenerThread.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

class RESTConvenienceClass {
    public static String SendRESTCommand(String restcommand) {
        return SendRESTCommandWithDebugPrompt(restcommand, "RESULT", true);
    }
     
    public static String SendRESTCommandWithDebugPrompt(String restcommand, String debugprompt, Boolean showsent) {
        String result = "";
        if (showsent) System.out.println("Sending REST Command: " + restcommand);
        try {
            result = getHTML(restcommand);
            System.out.println(debugprompt + " = " + result);            
        } catch (Exception ex) {
            Logger.getLogger(PlantronicsRESTDemo.class.getName()).log(Level.SEVERE, null, ex);
        }
        return result;
    }
    
    // thanks: http://stackoverflow.com/questions/1485708/how-do-i-do-a-http-get-in-java
    public static String getHTML(String urlToRead) throws Exception {
        StringBuilder result = new StringBuilder();
        URL url = new URL(urlToRead);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line;
        while ((line = rd.readLine()) != null) {
            result.append(line);
        }
        rd.close();
        return result.toString();
    }    

    public static Map<String, String> eCallState = new HashMap<String, String>() {{
        put("0", "CALL_STATE_UNKNOWN");
        put("1", "CALL_STATE_ACCEPT_CALL");
        put("2", "CALL_STATE_TERMINATE_CALL");
        put("3", "CALL_STATE_HOLD_CALL");
        put("4", "CALL_STATE_RESUME_CALL");
        put("5", "CALL_STATE_FLASH");
        put("6", "CALL_STATE_CALL_IN_PROGRESS");
        put("7", "CALL_STATE_CALL_RINGING");
        put("8", "CALL_STATE_CALL_ENDED");
        put("9", "CALL_STATE_TRANSFER_TO_HEADSET");
        put("10", "CALL_STATE_TRANSFER_TO_SPEAKER");
        put("11", "CALL_STATE_MUTEON");
        put("12", "CALL_STATE_MUTEOFF");
        put("13", "CALL_STATE_MOBILE_CALL_RINGING");
        put("14", "CALL_STATE_MOBILE_CALL_IN_PROGRESS");
        put("15", "CALL_STATE_MOBILE_CALL_ENDED");
        put("16", "CALL_STATE_DON");
        put("17", "CALL_STATE_DOFF");
        put("18", "CALL_STATE_CALL_IDLE");
        put("19", "CALL_STATE_PLAY");
        put("20", "CALL_STATE_PAUSE");
        put("21", "CALL_STATE_STOP");
        put("22", "CALL_STATE_DTMF_KEY");
        put("23", "CALL_STATE_REJECT_CALL");
	put("24", "CALL_STATE_MAKE_CALL");
	put("25", "CALL_STATE_HOOK");
	put("26", "CALL_STATE_HOOK_IDLE");
	put("27", "CALL_STATE_HOOK_DOCKED");
	put("28", "CALL_STATE_HOOK_UNDOCKED");
	put("29", "CALL_STATE_BASE_EVENT");
        put("30", "CALL_STATE_CALL_ANSWERED_AND_ENDED");
        put("31", "CALL_STATE_CALL_UNANSWERED_AND_ENDED");
        put("32", "CALL_STATE_DEVICE_CHANGE");
        put("33", "CALL_STATE_DEVICE_ARRIVED");
	put("34", "CALL_STATE_DEVICE_REMOVED");        
    }};
}
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<!-- Plantronics JavaScript Sample.html : JavaScript sample demonstrating basic call control.
     Try a *live* demo now! Visit: https://pltdev.github.io/
     NOTE:
     This sample has a pre-requisite on spokes.js (part of the Plantronics SDK located
     here: C:\Program Files (x86)\Plantronics\Spokes3G SDK\Samples / https://pltdev.github.io/spokes.js)
     And jQuery version 1.7.2 (https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js)
     And a convenience function "print_r" for debug (located: https://pltdev.github.io/util.js) -->
<head>
    <meta charset="utf-8" />
    <title>Plantronics JavaScript Sample</title>
</head>
<!-- Connect / disconnect with Plantronics REST API -->
<body onload="connectToPlantronics()" onbeforeunload="disconnectFromPlantronics()">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script src="https://pltdev.github.io/spokes.js"></script> <!-- Plantronics API interface -->
    <script src="https://pltdev.github.io/util.js"></script>

    <!-- Main page -->
    <h1>Plantronics JavaScript Sample</h1>
    Pre-requisite, install Plantronics Hub from: <a href="http://www.plantronics.com/software" target="_new">www.plantronics.com/software</a>
    <p>Note Firefox users: If you get "Error connecting to Plantronics Hub." then visit this URL: <a href="https://127.0.0.1:32018/Spokes/DeviceServices/Info" target="_new">https://127.0.0.1:32018/Spokes/DeviceServices/Info</a> and click Advanced > Add Exception... to add a security exception to allow the connection.</p>

    <p>
        <b>plt sample menu<br />
        --</b><br />
        <button id="incoming_call">ring/incoming call</button><br />
        <button id="outgoing_call">outgoing call</button><br />
        <button id="answer_call">answer call</button><br />
        <button id="hold_call">hold call</button><br />
        <button id="resume_call">resume call</button><br />
        <button id="mute_call">mute call</button><br />
        <button id="unmute_call">unmute call</button><br />
        <button id="end_call">end call</button>
    </p>

    <button id="clear_log">Clear Log</button>

    <div id="log"><b>Log:</b></div>
    <script type="text/javascript">
        var spokes; // global Spokes object, initialised when this page is loaded (onload)
        var plugin_registered = false;
        var plugin_name = "Hello World JavaScript Plugin";
        var callid = 0;
        var deviceAttached = true;
        var pollRate = 2000;
        var previousPollRate = 2000;
        // start polling the API for device events to to reconnect to API (in the event of connection failure)...
        var run = setInterval(pollDeviceEventsOrReconnect, pollRate);

        // Sample menu command implementations:
        $('#clear_log').click(function () {
            log.innerHTML = "<b>Log:</b>";
        });

        // inform Plantronics my app has an incoming (ringing) call
        $('#incoming_call').click(function () {
            callid = callid + 1;
            call_id = callid;
            appendLog("<font color=\"#00FF00\">Initiating make call command, call id = " + callid.toString() + "</font>");

            spokes.Plugin.incomingCall(plugin_name, getCallId(callid), getContact("Dummy Contact"), "Unknown", "ToHeadset", function (result) {
                showCallStatus(result);
            });
        });

        // inform Plantronics my app has an outgoing call
        $('#outgoing_call').click(function () {
            callid = callid + 1;
            call_id = callid;
            appendLog("<font color=\"#00FF00\">Initiating make call command, call id = " + callid.toString() + "</font>");

            spokes.Plugin.outgoingCall(plugin_name, getCallId(callid), getContact("Dummy Contact"), "ToHeadset", function (result) {
                showCallStatus(result);
            });
        });

        // inform Plantronics my app has now answered an incoming (ringing) call
        $('#answer_call').click(function () {
            appendLog("<font color=\"#00FF00\">Answering call command, call id = " + callid.toString() + "</font>");

            spokes.Plugin.answerCall(plugin_name, getCallId(callid), function (result) {
                showCallStatus(result);
            });
        });

        // place call on hold
        $('#hold_call').click(function () {
            appendLog("<font color=\"#00FF00\">Hold call command, call id = " + callid.toString() + "</font>");

            spokes.Plugin.holdCall(plugin_name, getCallId(callid), function (result) {
                showCallStatus(result);
            });
        });

        // resume the call
        $('#resume_call').click(function () {
            appendLog("<font color=\"#00FF00\">Resume call command, call id = " + callid.toString() + "</font>");

            spokes.Plugin.resumeCall(plugin_name, getCallId(callid), function (result) {
                showCallStatus(result);
            });
        });

        // mute the headset (note for legacy wireless products, audio link must be active)
        $('#mute_call').click(function () {
            appendLog("<font color=\"#00FF00\">Muting call</font>");

            spokes.Plugin.muteCall(plugin_name, true, function (result) {
                showCallStatus(result);
            });
        });

        // unmute the headset (note for legacy wireless products, audio link must be active)
        $('#unmute_call').click(function () {
            appendLog("<font color=\"#00FF00\">Unmuting call</font>");

            spokes.Plugin.muteCall(plugin_name, false, function (result) {
                showCallStatus(result);
            });
        });

        // inform Plantronics my app has now terminated the call
        $('#end_call').click(function () {
            appendLog("<font color=\"#00FF00\">Ending call, call id = " + callid.toString() + "</font>");

            spokes.Plugin.terminateCall(plugin_name, getCallId(callid), function (result) {
                showCallStatus(result);
            });
        });

        // Convenience functions to create call and contact structures used for IncomingCall/OutgoingCall functions
        function getCallId(callid) {
            return new SpokesCallId({ Id: '' + callid } );
        }

        function getContact(contactname) {
            return new SpokesContact({ Name: contactname });
        }

        // displays status of call commands
        function showCallStatus(result, toJson) {
            if (result.isError) {
                appendLog("Error: " + result.Err.Description);
            } else {
                if (toJson) appendLog(JSON.stringify(result.Result))
                else appendLog("Success.");
            }
        };

        function appendLog(str) {
            log.innerHTML = log.innerHTML + "<br />" + str;
        }

        function connectToPlantronics() {
            // Connect to the Plantronics REST API
            spokes = new Spokes("https://127.0.0.1:32018/Spokes");

            // get info about attached device (if any)
            spokes.Device.deviceInfo(function (result) {
                if (!result.isError) {
                    if (result.Result /*[0]*/ != null) {
                        // Log Spokes active device found (first in list returned, index 0)
                        appendLog("Device found = " + result.Result /*[0]*/.ProductName + ", id = " + result.Result /*[0]*/.Uid);

                        deviceAttached = true;

                        // attach to the device, provide a callback function for the result
                        spokes.Device.attach(result.Result /*[0]*/.Uid, deviceAttachedCallback);
                    } else {
                        appendLog("Error: Device was null on connecting to Spokes. Is there a Plantronics device connected?");
                        deviceAttached = false;
                    }
                    pollRate = 2000; // waiting for device events now, force faster polling rate to start now (if applicable)
                    if (previousPollRate == 10000) {
                        var previousPollRate = 2000;
                        // start polling the device and call state events...
                        var run = setInterval(pollDeviceEventsOrReconnect, pollRate);
                    }
                } else {
                    if (result.Err.Description === "There are no supported devices") {
                        appendLog("Please attach a Plantronics headset to the PC.");
                    }
                    else
                    { 
                        appendLog("Error connecting to Plantronics Hub. (Have you installed and run Plantronics Hub from <a href=\"http://www.plantronics.com/software\" target=\"_new\">www.plantronics.com/software</a>, or " +
                            "are you Firefox user and getting \"Error connecting to Plantronics Hub.\"? If so visit this URL: <a href=\"https://127.0.0.1:32018/Spokes/DeviceServices/Info\" target=\"_new\">" +
                            "https://127.0.0.1:32018/Spokes/DeviceServices/Info</a> and click Advanced > Add Exception... to add a security exception to allow the connection.");
                        pollRate = 10000; // slow down polling rate while we are waiting for Hub to be running
                    }
                }
            });
        }

        //Callback to receive result of device attach. If successful register a plugin (Plantronics API application session)
        function deviceAttachedCallback(session) {
            if (session.isError || !spokes.Device.isAttached) {
                appendLog("Session Registration Error");
                deviceAttached = false;
                disconnectFromPlantronics();
            } else {
                appendLog("Session ID: " + session.Result);

                registerPlugin(); // register a plugin (Plantronics API application session)
            }
        }

        function setPluginActive()
        {
            //Set plugin active status to true
            spokes.Plugin.isActive(plugin_name, true, function (result) {
                if (!result.isError) {
                    // plugin registered and active. Show UI.
                    plugin_registered = true;
                    appendLog("Plugin \"" + plugin_name + "\" registered successfully.");
                } else {
                    appendLog("Error checking if plugin is active: " + result.Err.Description);
                }
            });
        }

        // Register a Spokes Plugin (Plantronics API application session) to get access to Call Services, Device and Call events
        function registerPlugin() {
            if (!plugin_registered) {
                spokes.Plugin.register(plugin_name, function (result) {
                    if (!result.isError) {
                        setPluginActive();
                    } else {
                        appendLog("Info: registering plugin: " + result.Err.Description);
                        if (result.Err.Description === "Plugin exists")
                        {
                            setPluginActive();
                        }
                        else
                        {
                            deviceAttached = false;
                            disconnectFromPlantronics();
                        }
                    }
                });
            }
        }

        // Unregister Spokes plugin (Plantronics API application session)
        function unregisterPlugin() {
            spokes.Plugin.unRegister(plugin_name);
            plugin_registered = false;
            appendLog("Plugin un-registered.");
        }

        // Cleanup the Plantronics REST API
        function disconnectFromPlantronics() {
            unregisterPlugin();
            spokes.Device.release(function (result) {
                if (!result.isError) {
                    appendLog("Released device");
                } else {
                    appendLog("Error releasing device");
                }
                appendLog("Disconnected from Spokes");
            });
        }

        // Function to perform device and call event polling if we are connected to Hub, or else attempt to reconnect to Hub
        function pollDeviceEventsOrReconnect() {
            // supports variable poll rate, 2000ms waiting for a device, 10000ms waiting for Hub to be running
            if (previousPollRate != pollRate) {
                clearInterval(run);
                previousPollRate = pollRate;
                run = setInterval(pollDeviceEventsOrReconnect, pollRate); 
            }
            if (spokes == null || !deviceAttached || !spokes.Device.isAttached) {
                appendLog("-- POLLING FOR HUB / DEVICE RE-ATTACH --");
                connectToPlantronics();
                return;
            }

            // Poll for device events
            // informs us of a variety of Plantronics device state changes
            spokes.Device.events(
                function (result) {
                    if (result.isError) {
                        appendLog("Error polling for device events: " + result.Err.Description);
                        if (result.Err.Description === "No response.  Server appears to be offline.") {
                            pollRate = 10000;
                            appendLog("changing POLL RATE to " + pollRate);
                        }
                        if (result.Err.Description === "Invalid session id" ||
                            result.Err.Description === "Empty session id" ||
                            result.Err.Description === "No response.  Server appears to be offline.") {
                            appendLog("-- ** DEVICE DETACHED / SESSION INVALID ** --");
                            deviceAttached = false;
                            disconnectFromPlantronics();
                        }
                    } else {
                        // display list of events collected from REST service
                        if (result.Result.length > 0) {
                            for (var i = 0; i < result.Result.length; i++) {
                                appendLog("<font color=\"#0000FF\">Device Event: " + result.Result[i].Event_Log_Type_Name + ", " + print_r(result.Result[i]) + "</font>");
                            }
                        }
                    }
                });

            // Poll for call state events (call control events)
            // informs us the calling state has changed, for example user as answered/terminated a call
            // using headset buttons - this event should be used in my app to actually connect/terminate the call!
            spokes.Plugin.callEvents(plugin_name,
                function (result) {
                    if (result.isError) {
                        appendLog("Error polling for call events: " + result.Err.Description);
                        if (result.Err.Description === "No response.  Server appears to be offline.") {
                            pollRate = 10000;
                            appendLog("changing POLL RATE to " + pollRate);
                        }
                        if (result.Err.Description === "Invalid session id" ||
                            result.Err.Description === "Empty session id" ||
                            result.Err.Description === "No response.  Server appears to be offline.") {
                            appendLog("-- ** DEVICE DETACHED / SESSION INVALID ** --");
                            deviceAttached = false;
                            disconnectFromPlantronics();
                        }
                    } else {
                        // display list of events collected from REST service
                        if (result.Result.length > 0) {
                            for (var i = 0; i < result.Result.length; i++) {
                                appendLog("<font color=\"#0000FF\">Call Event: " + result.Result[i].Event_Log_Type_Name + ", " + print_r(result.Result[i]) + "</font>");
                                // Workout the actual call state and call id in question
                                var callState = SessionCallState.Lookup[result.Result[i]["Action"]];
                                var callId = result.Result[i]["CallId"]["Id"];
                                appendLog("CallState: " + callState + ", Call ID: " + callId);
                            }
                        }
                    }
                });
        }
    </script>
</body>
</html>
// PLTCPlusPlusNativeSample.cpp : C++ Sample demonstrating basic call control. This implementation is linked to Plantronics native Spokes library
// Note: With Plantronics SDK installed, ensure the following project configuration: 
// 1. C/C++ -> General -> Additional Include direcories =>
//		C:/Program Files (x86)/Plantronics/Spokes3G SDK/Include;C:/Program Files/Plantronics/Spokes3G SDK/Include
// 2. Linker -> General -> Additional Library Directories =>
//		C:/Program Files (x86)/Plantronics/Spokes3G SDK;C:/Program Files/Plantronics/Spokes3G SDK
// 3. Linker -> Input -> Additional Dependancies =>
//		%(AdditionalDependencies);Spokes.lib
// **NOTE**: Native Library does not support Manager Pro, Multi-softphone or Multi-device configurations
// Please consider REST Service (PC and Mac) or COM Service (PC only) APIs
// For more information see: http://developer.plantronics.com/api-overview#second
#pragma once

#include <windows.h>
#include <iostream>
#include <sstream>
#include <string>
#include <iomanip>
#include <stdio.h>
#include <tchar.h>
#include <SDKDDKVer.h>
#include "Spokes3G.h"
#include "cpp\query_cast.h"

using namespace std;

bool gGotDevice = false;
	
std::string EnumToString(eCallState val)
{
	std::string str;
	switch (val)
	{

	case CALL_STATE_UNKNOWN: str = "CALL_STATE_UNKNOWN  "; break;
	case CALL_STATE_ACCEPT_CALL: str = "CALL_STATE_ACCEPT_CALL  "; break;
	case CALL_STATE_TERMINATE_CALL: str = "  CALL_STATE_TERMINATE_CALL"; break;
	case CALL_STATE_HOLD_CALL: str = "CALL_STATE_HOLD_CALL  "; break;
	case CALL_STATE_RESUME_CALL: str = " CALL_STATE_RESUME_CALL "; break;
	case CALL_STATE_FLASH: str = "CALL_STATE_FLASH  "; break;
	case CALL_STATE_CALL_IN_PROGRESS: str = "CALL_STATE_CALL_IN_PROGRESS  "; break;
	case CALL_STATE_CALL_RINGING: str = "CALL_STATE_CALL_RINGING  "; break;
	case CALL_STATE_CALL_ENDED: str = "CALL_STATE_CALL_ENDED  "; break;
	case CALL_STATE_TRANSFER_TO_HEADSET: str = " CALL_STATE_TRANSFER_TO_HEADSET "; break;
	case CALL_STATE_TRANSFER_TO_SPEAKER: str = "CALL_STATE_TRANSFER_TO_SPEAKER  "; break;
	case CALL_STATE_MUTEON: str = "CALL_STATE_MUTEON  "; break;
	case CALL_STATE_MUTEOFF: str = "CALL_STATE_MUTEOFF  "; break;
	case CALL_STATE_MOBILE_CALL_RINGING: str = " CALL_STATE_MOBILE_CALL_RINGING "; break;
	case CALL_STATE_MOBILE_CALL_IN_PROGRESS: str = "CALL_STATE_MOBILE_CALL_IN_PROGRESS  "; break;
	case CALL_STATE_MOBILE_CALL_ENDED: str = "  CALL_STATE_MOBILE_CALL_ENDED"; break;
	case CALL_STATE_DON: str = " CALL_STATE_DON "; break;
	case CALL_STATE_DOFF: str = "CALL_STATE_DOFF  "; break;
	case CALL_STATE_CALL_IDLE: str = "  CALL_STATE_CALL_IDLE"; break;
	case CALL_STATE_PLAY: str = "CALL_STATE_PLAY  "; break;
	case CALL_STATE_PAUSE: str = "  CALL_STATE_PAUSE"; break;
	case CALL_STATE_STOP: str = "CALL_STATE_STOP  "; break;
	case CALL_STATE_DTMF_KEY: str = " CALL_STATE_DTMF_KEY "; break;
	case CALL_STATE_REJECT_CALL: str = "CALL_STATE_REJECT_CALL  "; break;
	case CALL_STATE_MAKE_CALL: str = " CALL_STATE_MAKE_CALL "; break; // Information only.  
	case CALL_STATE_HOOK: str = "  CALL_STATE_HOOK"; break;
	case CALL_STATE_HOOK_IDLE: str = " CALL_STATE_HOOK_IDLE "; break;
	case CALL_STATE_HOOK_DOCKED: str = "  CALL_STATE_HOOK_DOCKED"; break;
	case CALL_STATE_HOOK_UNDOCKED: str = " CALL_STATE_HOOK_UNDOCKED"; break;
	case CALL_STATE_BASE_EVENT: str = "CALL_STATE_BASE_EVENT  "; break;
	case CALL_STATE_CALL_ANSWERED_AND_ENDED: str = "CALL_STATE_CALL_ANSWERED_AND_ENDED  "; break;
	case CALL_STATE_CALL_UNANSWERED_AND_ENDED: str = "CALL_STATE_CALL_UNANSWERED_AND_ENDED  "; break;
	case CALL_STATE_DEVICE_ARRIVED: str = "CALL_STATE_DEVICE_ARRIVED  "; break;
	case CALL_STATE_DEVICE_REMOVED: str = "CALL_STATE_DEVICE_REMOVED  "; break;
	case CALL_STATE_DEVICE_CHANGE: str = "CALL_STATE_DEVICE_CHANGE  "; break;
	default:return "CALL_STATE_UNKNOWN";
	};
	return str;
}

std::string EnumToString(eDeviceState val)
{
	std::string str;
	switch (val)
	{
	case DEV_STATE_ADDED: str = " DEV_STATE_ADDED"; break;

	case DEV_STATE_REMOVED: str = " DEV_STATE_REMOVED"; break;
	default: str = " DEV_STATE_UNKNOWN";
	}
	return str;
}
	
void PrintDeviceDetails(IDevice* device)
{
	bool muted;
	IDeviceListener* myDeviceListener = nullptr;

	DMResult res = device->getDeviceListener(&myDeviceListener);
	if (res != DMResult::DM_RESULT_SUCCESS)
	{
		std::cout << "Device is not attached / unable to hook up to device listener events" << std::endl;
		return;
	}
	std::cout << "Successfully hooked to device listener events" << std::endl;

	wchar_t buffer1[1024];
	device->getProductName(buffer1, 1024);
	int32_t pid = device->getProductID();

	std::wcout << "Device attached: " << buffer1;
	std::wcout << L", Product ID = " << std::uppercase << std::hex << std::setfill(L'0') << std::setw(4) << pid << std::endl;

	// Obtain initial device microphone mute state
	res = myDeviceListener->getMute(muted);
	std::cout << "Device mute state: muted = ";
	(muted == true) ? std::cout << "True" : std::cout << "False"; std::cout << std::endl;

	return;
}
	
// Call Object
class Call : public ICall
{
private:
	int32_t m_id = 0;
public:
	virtual int32_t getID() { return m_id; }
	virtual void setID(int32_t id) { m_id = id; }
	virtual int32_t getConferenceID() { return 0; }
	virtual void setConferenceID(int32_t id) { /*no op*/ }
	virtual bool getInConference() { return false; }
	virtual void setInConference(bool bConference) { /*no op*/ }

	void operator delete(void *p) { ::operator delete(p); }

	~Call() { }
};

ICall* GetCall(Call& call)
{
	std::string strCallId;
	std::cout << "Enter Call Id: ";
	std::getline(std::cin, strCallId);

	int32_t id;
	std::stringstream myStream(strCallId);
	myStream >> id;
	call.setID(id);
	return &call;
}

ICall* GetCall(Call& call, int id)
{
	call.setID(id);
	return &call;
}

// Contact Object
class Contact : public IContact
{
private:
	int32_t m_id = 0;
	wstring m_name;
	wstring m_friendlyName;
	wstring m_sipUri;
	wstring m_email;
	wstring m_phone;
	wstring m_workPhone;
	wstring m_mobilePhone;
	wstring m_homePhone;

public:
	virtual const wchar_t* getName() const { return m_name.c_str(); }
	virtual void setName(const wchar_t* pName) { m_name = pName; }
	virtual const wchar_t* getFriendlyName() const { return m_friendlyName.c_str(); }
	virtual void setFriendlyName(const wchar_t* pFName) { m_friendlyName = pFName; }
	virtual int32_t getID() const { return m_id; }
	virtual void setID(int32_t id) { m_id = id; }
	virtual const wchar_t* getSipUri() const { return m_sipUri.c_str(); }
	virtual void setSipUri(const wchar_t* pSip) { m_sipUri = pSip; }
	virtual const wchar_t* getPhone() const { return m_phone.c_str(); }
	virtual void setPhone(const wchar_t* pPhone) { m_phone = pPhone; }
	virtual const wchar_t* getEmail() const { return m_email.c_str(); }
	virtual void setEmail(const wchar_t* pEmail) { m_email = pEmail; }
	virtual const wchar_t* getWorkPhone() const { return m_workPhone.c_str(); }
	virtual void setWorkPhone(const wchar_t* pWPhone) { m_workPhone = pWPhone; }
	virtual const wchar_t* getMobilePhone() const { return m_mobilePhone.c_str(); }
	virtual void setMobilePhone(const wchar_t* pMPhone) { m_mobilePhone = pMPhone; }
	virtual const wchar_t* getHomePhone() const { return m_homePhone.c_str(); }
	virtual void setHomePhone(const wchar_t* pHPhone) { m_homePhone = pHPhone; }

	void operator delete(void *p) { ::operator delete(p); }

	~Contact() { }
};

IContact* GetContact()
{
	std::string strContactName;
	std::cout << "Enter contact name: ";
	std::getline(std::cin, strContactName);

	wstring wName(strContactName.begin(), strContactName.end());
	std::cout << "Name:" << strContactName << std::endl;
	IContact* contact = nullptr;
	//	getContact( &contact );
	//	contact->setName( wName.c_str() );
	return contact;
}

IContact* GetContact(Contact& contact, std::string name)
{
	std::wstring wsTmp(name.begin(), name.end());
	contact.setName(wsTmp.c_str());
	return &contact;
}

Call call;
Contact contact;

// create sink for receiving session events
class CallEventSink : public ICallEvents
{
private:
	ISession* m_session = nullptr;
public:
	CallEventSink() {}
	CallEventSink(ISession* session) : m_session(session) { m_session->registerCallback(this); }
	virtual ~CallEventSink() { m_session->unregisterCallback(this); }

	virtual bool OnCallStateChanged(CallStateEventArgs const& pcscArgs)
	{
		std::string state = EnumToString(pcscArgs.callState);
		//cout << "OnCallStateChanged Session event " << state.c_str() << endl; 
		return true;
	}
	virtual bool OnCallRequest(CallRequestEventArgs const& pcscArgs) { cout << "OnCallRequest Session event" << endl; return true; }
	void operator delete(void *p) { ::operator delete(p); }
};
// create sink for receiving session manager events
class SessionManagerEventSink : public ISessionManagerEvents
{
private:
	ISessionManager* m_sessionManager = nullptr;
public:
	SessionManagerEventSink() {}
	SessionManagerEventSink(ISessionManager* sessionManager) : m_sessionManager(sessionManager) { m_sessionManager->registerCallback(this); }
	virtual ~SessionManagerEventSink() { m_sessionManager->unregisterCallback(this); gGotDevice = false; }
	virtual bool OnCallStateChanged(CallStateEventArgs const& cseArgs)
	{
		int32_t callId = call.getID();
		std::string state = EnumToString(cseArgs.callState);
		std::cout << "Call State Changed:  callid=" << callId << " new state=" << state.c_str() << endl;
			
		return true;
	}
	virtual bool OnDeviceStateChanged(DeviceStateEventArgs const& devArgs)
	{
		eDeviceState devState = devArgs.deviceState;
		std::string state = EnumToString(devState);
		cout << "OnDeviceStateChanged: " << state.c_str() << endl;

		gGotDevice = false; // schedule a device re-attach attempt next time around the main menu loop

		if (devState == eDeviceState::DEV_STATE_ADDED)
		{
			std::cout << "Press Enter to attach this device." << std::endl;
		}

		return true;
	}
	void operator delete(void *p) { ::operator delete(p); }
};

void ShowMenu()
{
	std::cout << std::endl;
	std::cout << "plt sample menu" << std::endl;
	std::cout << "--" << std::endl;
	std::cout << "1 - ring/incoming call" << std::endl;
	std::cout << "2 - outgoing call" << std::endl;
	std::cout << "3 - answer call" << std::endl;
	std::cout << "4 - hold call" << std::endl;
	std::cout << "5 - resume call" << std::endl;
	std::cout << "6 - mute call" << std::endl;
	std::cout << "7 - unmute call" << std::endl;
	std::cout << "8 - end call" << std::endl;
	std::cout << "0 - quit" << std::endl;
	std::cout << std::endl;
	std::cout << "> ";
}

int _tmain(int argc, _TCHAR* argv[])
{
	bool quit = false;
	std::string input;
	int cmd = -1;
	int _callid = 0; // variable to track call id between my app and Plantronics

	InitSpokesRuntime();

	cout << "C++ Plantronics Native API Sample" << std::endl;
		
	// create session manager
	ISessionManager *sessionManager = nullptr;
	if (SM_RESULT_SUCCESS == getSessionManager(&sessionManager))
	{
		// create session
		ISession* session = nullptr;
		if (SM_RESULT_SUCCESS == sessionManager->registerSession(L"Sample client", &session))
		{
			IDevice* activeDevice = nullptr;
			SMResult res = session->getActiveDevice(&activeDevice); // always succeeds (since getActiveDevice() gets a proxy & retruns SM_RESULT_SUCCESS)
				
			IUserPreference* userPreference = nullptr;
			if (SM_RESULT_SUCCESS == sessionManager->getUserPreference(&userPreference))
			{
				// set this session as default one for receiving CallRequest events
				int32_t pluginId;
				session->getPluginId(&pluginId);
				userPreference->setDefaultSoftphone(pluginId);
			}

			// get call command
			ICallCommand* callCommand = nullptr;
			if (SM_RESULT_SUCCESS == session->getCallCommand(&callCommand))
			{
				// sink to all call events
				unique_ptr<CallEventSink> callEvents(new CallEventSink(session)); //auto callEvents = std::make_unique<CallEventSink>(session);
				unique_ptr<SessionManagerEventSink> sessionManagerEvents(new SessionManagerEventSink(sessionManager)); //auto sessionManagerEvents = std::make_unique<SessionManagerEventSink>(sessionManager);
					
				// enter in command loop
				while (!quit)
				{
					// If we have not attached to device (or device was removed/added), then attempt to attach to a device
					if (!gGotDevice)
					{
						std::cout << "Attempting device attach" << std::endl;
						if (true == activeDevice->isAttached())
						{
							gGotDevice = true;
							PrintDeviceDetails(activeDevice);
						}
						else
						{
							std::cout << "Unable to retrieve/hook to active device" << std::endl;
						}
					}
					ShowMenu();
					std::getline(std::cin, input);

					// Convert from string to number safely.
					std::stringstream inputStringStream(input);
					if (!(inputStringStream >> cmd))
					{
						cmd = -1;
						// check if device is attached and prompt error if it is
						if (gGotDevice == true)
							std::cout << "Invalid input, please try again" << std::endl;
					}

					switch (cmd)
					{
					case 1:
						_callid++;
						callCommand->incomingCall(GetCall(call, _callid), // inform Plantronics my app has an incoming (ringing) call
							GetContact(contact, "Bob%20Smith"), RING_TONE_UNKNOWN, eAudioRoute::AUDIO_ROUTE_TO_HEADSET);
						break;
					case 2:
						_callid++;
						callCommand->outgoingCall(GetCall(call, _callid),  // inform Plantronics my app has an outgoing call
							GetContact(contact, "Bob%20Smith"), eAudioRoute::AUDIO_ROUTE_TO_HEADSET);
						break;
					case 3:
						callCommand->answeredCall(GetCall(call, _callid)); // inform Plantronics my app has now answered an incoming (ringing) call
						break;
					case 4:
						callCommand->holdCall(GetCall(call, _callid)); // place call on hold
						break;
					case 5:
						callCommand->resumeCall(GetCall(call, _callid)); // resume the call
						break;
					case 6:
						callCommand->muteCall(true); // mute the headset (note for wireless products, audio link must be active)
						break;
					case 7:
						callCommand->muteCall(false); // unmute the headset (note for wireless products, audio link must be active)
						break;
					case 8:
						callCommand->terminateCall(GetCall(call, _callid)); // inform Plantronics my app has now terminated the call
						break;
					case 0:
						quit = true;
						break;
					default:
						if (gGotDevice == true)
							std::cout << "Unrecognised menu choice, please try again." << std::endl;
						break;
					}

					Sleep(250);
				}

				input = "";
				activeDevice->Release();
			}
		}
		sessionManager->unregisterSession(session);
		session->Release();
	}

	ShutDownSpokesRuntime();

	return 0;
}

Comments


Removed comment - horrendous formatting; will re-post

Hi,
Is Visual Studio running as Administrator while PLT SDK is not? (Or vica versa?)
they should both not be running as Administrator to use COM between them.
thanks,
Lewis

Thanks Lewis - yes, VS was running as Admin but not the SDK, I will re-run with both running under normal previleges.

Thanks Lewis - yes, VS was running as Admin but not the SDK, I will re-run with both running under normal previleges.

Yes, issue was with Admin privileges - when both, the SDK + VS executing with normal privileges, I could get past successfully executing the line that was throwing the exception, namely creating the COM session manager. THX !