Steps to integrate to Plantronics:

  1. Install the SDK
  2. Cut and Paste code from below!
[New!] Amazon Connect Sample Code – to enable Plantronics Headset Call Control:
See Articles section: Plantronics Headset Call Control with Amazon Connect.


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;
}