Click here to Skip to main content
15,886,199 members
Articles / Desktop Programming / WPF
Article

The Dew Review - Plantronics Voyager Legend and the Spokes SDK

28 Jan 2014CPOL5 min read 20.9K   30   2  
Looking at the Plantronics Voyager Legend, and using the Spokes SDK .NET Interop for COM Service API with C# while building a WPF sample application.

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

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

 Image 1

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.

 Image 2

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.

 Image 3

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.

 Image 4

The code to initialize the ICOMSession, ISessionCOMManager and IDevice is pretty straightforward. This routine is called from the constructor of my MainViewModel.

C#
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)
        {
            // show session details
            PrintSession(comSession);

            // attach to session call events
            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.

C#
private void AttachDevice()
{
    activeDevice = comSession.ActiveDevice;
    IsDeviceActive = activeDevice != null;

    if (activeDevice != null)
    {
        deviceComEvents = activeDevice.DeviceEvents as IDeviceCOMEvents_Event;
                
        if (deviceComEvents != null)
        {
            // Attach to device events
            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)
        {
            // Attach to device listener events
            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.

C#
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.

Image 5

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." 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead Allscripts
United States United States
Alvin has over 24 years of programming experience in the health care, financial and manufacturing industries. He is currently a Principal Software Engineer at Allscripts in the Philadelphia area. He has been recognized as a Microsoft Windows Dev MVP (current - formerly MVP in C#/Visual Studio), OzCode Magician, Friend of Redgate and Xamarin Certified Professional. Alvin has tech reviewed several books, including titles on ASP.NET Core, Entity Framework Core, and WPF. He is also one of the founding organizers of the TechBash developer conference held each fall in Pocono Manor, PA.

Comments and Discussions

 
-- There are no messages in this forum --