Introduction
Did you know that Plantronics,
who most developers know is a leading maker of Bluetooth headsets, also has an SDK to enable the
automation of and interaction with their devices? The Spokes SDK and tons of
documentation and other resources are available at developer.plantronics.com
(registration is required to access the downloads and docs).
The Device
I have been working with a Plantronics
Voyager Legend headset and a BT300 USB adapter. The Legend
comes in a nice little case that can also charge the headset (with the included
Micro USB cable) while it is inside. It can pair with two devices
simultaneously, which is really handy. I have been using it with my Intel Ultrabook and Lumia 920 Windows Phone. I can
listen to music and test my application on the PC while connected to my phone
to answer calls and texts.
The SDK
The Spokes SDK offers a few
different APIs that developers can use.
- COM Service API – For
C++ developers
- .NET Interop for COM
Service API – For .NET managed language developers
- REST API – For
developers using JavaScript, Java and other languages
I have been using the .NET
Interop for COM Service API with C# while building a WPF sample application. I
have taken code from a couple of the sample applications to build a WPF
application using MVVM. I used Laurent Bugnion’s MVVM Light framework as well.
The COM
Interop API
To use the interop API from
a .NET application, add a reference to the Interop.Plantronics.dll included
with the Spokes SDK.
HINT:
I am using Visual Studio 2013 and had to change the properties of the reference
to not embed the interop type in my assembly. Otherwise, my application would
not compile after adding the code to instantiate the types in the API. Change
"Embed Interop Types"to False and "Copy Local"to True. See the screenshot
below.
My application is a simple
one. It is a WPF app using the MVVM
pattern with a single view and one ViewModel. The view allows you to view the
current active device, set a mobile number to call, interact with the connected
device with a series of checkboxes and buttons, and a log viewer at the bottom
of the screen. There are also buttons to clear and save the log. The save
function is hard coded to save to a file on the current user’s Desktop.
To dive deeper into the
functions that act on the Plantronics Spokes API, the application enables users
to perform a variety of operations on the attached device. There is the ability
to make and end mobile calls if the device supports simultaneous pairing with a
PC and a mobile device. There is also a Mute Mobile checkbox, but my headset
does not seem to support this part of the API. There are also a series of
functions that can be performed when the device is working with a softphone
application in Windows (such as Skype). Some of the actions are attaching to
outgoing and incoming calls, placing the call on hold and resuming it, or
turning on/off the mute, audio and ring functions.
The code to initialize the
ICOMSession
, ISessionCOMManager
and IDevice
is pretty straightforward. This
routine is called from the constructor of my MainViewModel
.
private void InitializeHeadset()
{
try
{
sessionComManager = new SessionComManagerClass();
LogContents += "Session Manager created" + Environment.NewLine;
sessionManagerEvents = sessionComManager as ISessionCOMManagerEvents_Event;
if (sessionManagerEvents != null)
{
sessionManagerEvents.CallStateChanged += sessionComManager_CallStateChanged;
sessionManagerEvents.DeviceStateChanged += sessionComManager_DeviceStateChanged;
LogContents += "Attached to session manager events: Call State Changed + Device State Changed." + Environment.NewLine;
}
else
LogContents += "Error: Unable to attach to session manager events" + Environment.NewLine;
comSession = sessionComManager.Register("COM Session");
if (comSession != null)
{
PrintSession(comSession);
sessionEvents = comSession.CallEvents as ICOMCallEvents_Event;
if (sessionEvents != null)
{
sessionEvents.CallRequested += sessionEvents_CallRequested;
sessionEvents.CallStateChanged += sessionEvents_CallStateChanged;
LogContents += "Attached to session call events: Call Requested + Call State Changed." + Environment.NewLine;
}
else
LogContents += "Error: Unable to attach to session call events" + Environment.NewLine;
activeDevice = comSession.ActiveDevice;
IsDeviceActive = activeDevice != null;
AttachDevice();
PrintDevice(activeDevice);
}
else
LogContents += "Error: Unable to register session" + Environment.NewLine;
}
catch (Exception ex)
{
LogContents += "Error: " + ex.Message + Environment.NewLine;
}
}
The one other relevant
method called here is the AttachDevice()
method.
private void AttachDevice()
{
activeDevice = comSession.ActiveDevice;
IsDeviceActive = activeDevice != null;
if (activeDevice != null)
{
deviceComEvents = activeDevice.DeviceEvents as IDeviceCOMEvents_Event;
if (deviceComEvents != null)
{
deviceComEvents.ButtonPressed += deviceComEvents_Handler;
deviceComEvents.AudioStateChanged += deviceComEvents_Handler;
deviceComEvents.FlashPressed += deviceComEvents_Handler;
deviceComEvents.MuteStateChanged += deviceComEvents_Handler;
deviceComEvents.SmartPressed += deviceComEvents_Handler;
deviceComEvents.TalkPressed += deviceComEvents_Handler;
LogContents += "Attached to device events" + Environment.NewLine;
}
else
LogContents += "Error: unable to attach to device events" + Environment.NewLine;
deviceListenerEvents = activeDevice.DeviceListener as IDeviceListenerCOMEvents_Event;
if (deviceListenerEvents != null)
{
deviceListenerEvents.ATDStateChanged += deviceListenerEvents_Handler;
deviceListenerEvents.BaseButtonPressed += deviceListenerEvents_Handler;
deviceListenerEvents.BaseStateChanged += deviceListenerEvents_Handler;
deviceListenerEvents.HeadsetButtonPressed += deviceListenerEvents_Handler;
deviceListenerEvents.HeadsetStateChanged += deviceListenerEvents_Handler;
LogContents += "Attach to device listener events" + Environment.NewLine;
}
else
LogContents += "Error: unable to attach to device listener events" + Environment.NewLine;
LogContents += "Attached to device" + Environment.NewLine;
}
else
{
LogContents += "No active device detected" + Environment.NewLine;
}
}
These methods initialize the
needed objects and wire up all the events needed to log and react to activity
on the headset. There is corresponding code to detach from a device and cleanup
all of the COM objects on shutdown.
For actions from the UI via
checkbox or button, I have a switch statement to determine which action was
invoked and call the appropriate method in the Spokes API.
private void CommandExecute(SpokesCommandType command)
{
try
{
IATDCommand atdc;
switch (command)
{
case SpokesCommandType.RingOn:
if (_activeDevice != null) _activeDevice.HostCommand.Ring(true);
break;
case SpokesCommandType.RingOff:
if (_activeDevice != null) _activeDevice.HostCommand.Ring(false);
break;
case SpokesCommandType.AudioOn:
if (_activeDevice != null) _activeDevice.HostCommand.AudioState = AudioType.AudioType_MonoOn;
break;
case SpokesCommandType.AudioOff:
if (_activeDevice != null) _activeDevice.HostCommand.AudioState = AudioType.AudioType_MonoOff;
break;
case SpokesCommandType.MuteOn:
if (_activeDevice != null) _activeDevice.DeviceListener.Mute = true;
break;
case SpokesCommandType.MuteOff:
if (_activeDevice != null) _activeDevice.DeviceListener.Mute = false;
break;
case SpokesCommandType.MobileMuteOn:
if (_activeDevice != null)
{
atdc = (IATDCommand)_activeDevice.HostCommand;
atdc.MuteMobileCall(true);
}
break;
case SpokesCommandType.MobileMuteOff:
if (_activeDevice != null)
{
atdc = (IATDCommand)_activeDevice.HostCommand;
atdc.MuteMobileCall(false);
}
break;
case SpokesCommandType.MakeCall:
SendMakeMobileCall(PhoneNumber);
break;
case SpokesCommandType.Incoming:
_comSession.CallCommand.IncomingCall(new CallCOM { Id = CallId }, new ContactCOM { Name = "Bob Smith",
Phone = PhoneNumber }, RingTone.RingTone_Unknown, AudioRoute.AudioRoute_ToHeadset);
break;
case SpokesCommandType.Outgoing:
_comSession.CallCommand.OutgoingCall(new CallCOM { Id = CallId }, new ContactCOM { Name = "Bob Smith",
Phone = PhoneNumber }, AudioRoute.AudioRoute_ToHeadset);
break;
case SpokesCommandType.HoldCall:
_comSession.CallCommand.HoldCall(new CallCOM { Id = CallId });
break;
case SpokesCommandType.EndMobile:
atdc = (IATDCommand)_activeDevice.HostCommand;
atdc.EndMobileCall();
CallId++;
break;
case SpokesCommandType.EndCall:
_comSession.CallCommand.TerminateCall(new CallCOM { Id = CallId });
break;
case SpokesCommandType.AnsweredCall:
_comSession.CallCommand.AnsweredCall(new CallCOM { Id = CallId });
break;
case SpokesCommandType.ResumeCall:
_comSession.CallCommand.ResumeCall(new CallCOM { Id = CallId });
break;
case SpokesCommandType.SetConferenceId:
_comSession.CallCommand.SetConferenceId(new CallCOM { Id = CallId });
break;
}
}
catch (COMException comEx)
{
LogContents += "COM Exception encountered: " + comEx.Message + LineBreak;
}
catch (Exception ex)
{
LogContents += "Exception encountered: " + ex.Message + LineBreak;
}
}
All of the On/Off commands
come from CheckBox state on the UI and invoke commands on the active device.
The commands from the buttons on the UI invoke various call-related commands on
the active COMSession object.
The Running
Application
Here is a shot of the
application running on my Windows 8.1 desktop while attached to an outgoing
Skype call to my home voicemail box. You can see that all activity is logged
while interacting with the API. This is a very useful way to get familiar with
the API while planning your real production application concept.
You can download the source
code for the project here.
Future Applications
This initial foray into the
Spokes API has been really enjoyable. It is very intuitive. I referenced the
sample applications, but did not have to dive into the abundant online SDK
documentation at all. The SDK even includes an emulator to test against if you
don’t have a real device, or not enough devices for your entire team to use at
once.
I already have a few ideas
for little apps to automate some tasks on my headset. I am looking forward to
finding the time to work on those projects… perhaps over the holidays.
I definitely recommend
checking out the Spokes SDK if you have a need in this area. Go check out the
Plantronics developer site. There is so much great information there to
kick-start your project. You can also find a bunch of useful articles by
Plantronics on CodeProject.
Disclosure
of Material Connection: I received one or more of
the products or services mentioned above for free in the hope that I would
mention it on my blog. Regardless, I only recommend products or services I use
personally and believe my readers will enjoy. I am disclosing this in accordance
with the Federal Trade
Commission’s 16 CFR, Part 255: "Guides Concerning the Use of Endorsements and
Testimonials in Advertising."