When I was asked to consider checking out a new Plantronics
headset, I immediately thought of my time working as a phone representative,
answering customer support requests. My team would answer the phone using a
telephone headset that made us look like we were landing planes for the
military. All day we would sit at our desks in front of a screen that would
show us information about the customers who were calling in. We could search
for information in an online knowledge base, but for the most part, we spent
our days staring at that screen.
Our call center had an automated time clock based on the
time you were available to receive customer’s phone calls. When you arrived,
you would clock in by keying a pin into your telephone keypad. Once your pin
was keyed in, you would be online to receive calls. As long as you were
authenticated to your phone, calls would be forwarded to you. When you would
get up from your desk for a break, lunch, or for the end of the day you would
log off your phone. This process would stop the calls from coming to your
extension and clock out of the time clock.
In this example, I am going to recreate this scenario using
the Plantronics Legend UC headset. While this is not a headset you’ll find in a call center it is a great platform for a technology demonstration today, anticipating sensor-equipped call center headsets tomorrow. This headset is Bluetooth capable and
connects to many different soft-phones available including Lync, Skype, and
Avaya. Additionally, there are sensors on-board to detect proximity to the
Bluetooth receiver. There are also sensors to detect if the headset is on your
head. This allows the headset to automatically answer calls when you put the
headset over your ear. I think these two sensors are
very interesting when considering the scope of this article, and I will focus
on using them to automate our clock-in / clock-out process.
Initial Development Setup
In order for a workstation to interact with all of the
sensors of a Plantronics headset, a client software package called Spokes must
be installed. For developers, there is a software development kit that you can
download and use in place of the client software. Through the rest of this
sample, I am going to assume that you have downloaded, installed, and have the
Spokes software service running.
I am going to start developing with Visual Studio 2012 and
create a blank web forms application with ASP.Net 4.5. We want notifications
to be presented to the call center representative and I am going to put
together a simple timesheet grid. For these user-interface elements, I am
going to add a trial copy of Telerik’s ASP.Net AJAX controls. Once the
controls are installed, I will right-click the web project in the solution
explorer and convert it to a RadControls Web Project with the Windows 7 theme.
To connect to the headset from the browser, I need to add a
JavaScript file from the Plantronics SDK called spokes.js You can find this file, after you install the
SDK, at: c:\Program Files (x86)\
Plantronics\Plantronics SDK\Samples\RestJsClient I placed my copy of
this file in the Scripts folder in my web project. I want isolate all of the
logic for connecting to the headset and time clock operations into a single
JavaScript file that will be referenced by all pages in the application. For
this, I created a JavaScript file called callcenter.js and added it to my
Scripts folder. In order to achieve this common use of the headset throughout
the application, I have added references to the spokes.js and callcenter.js to
my site.master file that will be used by all pages in the application.
<%--Site scripts--%>
<asp:ScriptReference Path="~/Scripts/spokes.js" />
<asp:ScriptReference Path="~/Scripts/callcenter.js" />
</Scripts>
</asp:ScriptManager>
Listing 1 – Script
References added to Site.Master that will control the headset
The notifications will be presented inside of a Telerik
Notification control. This control will present itself as a sliding window in
the bottom corner of the screen, similar to other “toast notifications” you
receive from system services from the Windows taskbar. The markup for this
control is as follows:
<telerik:RadNotification ID="statusNotification" runat="server" Animation="Slide" Title="Headset Status" OffsetX="-10" OffsetY="-10" ShowCloseButton="False" ViewStateMode="Disabled" Height="125" Width="250">
<ContentTemplate>
<table style="width: 250px; height: 75px;">
<tr>
<td valign="center">
<img id="Img1" runat="server" src="~/Images/headset_40.png" />
</td>
<td valign="center">
<span id="statusText"></span>
</td>
</tr>
</table>
</ContentTemplate>
</telerik:RadNotification>
Listing 2 –
RadNotification control markup
Building out the Headset Call Center
The callcenter.js file is a standalone component that
isolates all of the negotiations with the headset and posting of notifications
about the headset to the user. I have started the construction of this script
with a self-executing function, to isolate private methods and expose only
those methods needed for the rest of the application.
var CallCenter = (function() {
var statusCtl;
var spokes;
var initFunction = function () {
spokes = new Spokes("http://127.0.0.1:32001/Spokes");
statusCtl = $find(controls.statusNotification.ClientID);
ConnectSpokes();
};
return {
Init: initFunction,
Spokes : function() {
return spokes;
},
};
})();
$().ready(function() {
CallCenter.Init();
});
Listing 3 –
JavaScript foundation for the CallCenter JavaScript object
In this code listing, I have allocated a private variable to
store the reference to the Spokes service. Additionally, I have captured a
reference to the Notification control object so that we may publish messages of
our user later. The spokes object is constructed from a service that runs on
the local machine and answers on port 32001. Once we have our reference to the
spokes object, we need to connect to the headset with the connectSpokes()
method. This method
will allow us to start listening for state change events that the Spokes
service will publish for us using a queue with a pub/sub mechanism.
var ConnectSpokes = function() {
spokes.Device.deviceList(function (result) {
if (result.isError) {
ShowMessage("Unable to connect to headset");
} else if (result.Result[0] == null) {
ShowMessage("Error - Is there a headset connected?");
} else {
spokes.Device.attach(result.Result[0].Uid, ControlInterface);
PollDeviceEvents();
}
});
};
function ControlInterface(session) {
if (session.isError || !spokes.Device.isAttached) {
ShowMessage("Session Registration Error");
}
else {
spokes.Device.deviceInfo(function (result) {
if (result.Result.RemoteFirmwareVersion == null) {
ShowMessage("No headset attached to adapter");
clockedInState = false;
} else {
ShowMessage("Connected to headset successfully");
}
console.log(result);
});
}
}
Listing 4 –
Connecting to the Spokes service with REST
There are several tests to note in these methods:
-
First, we are checking that the request for a list of devices did
not return an error. If an error was returned from a list of devices, then the
Plantronics adapters and headset are not properly connected and powered.
-
If the device list is empty, then there is an error as well. We
will display a message indicating the user should check headset connections
-
In the
controlInterface
method there is a test for the RemoteFirmwareVersion
.
This is the only property exposed by the headset device itself that we can
inspect. All other stateful properties that are exposed are those of the USB
adapter. We must check for the presence of a value on this property in order
to verify that a headset is properly connected to our workstation.
Figure
1 – Notification of a successful connection to the headset service
If these tests pass, we notify the user, attach to the
headset and start listening for events in the pollDeviceEvents
method. For reference, here is the body of the sendMessage
method, which
utilizes the notification control:
function ShowMessage(msg) {
$("#statusText").html(msg);
statusCtl.show();
}
Listing 5 – The ShowMessage
method that notifies our browser user
The pollDeviceEvents
method is a batch of checks and pivots
to determine how to best respond to events raised by the headset. Its source
is below:
function PollDeviceEvents() {
setInterval(function() {
spokes.Device.events(function (result) {
if (!result.isError) {
for (var i = 0; i < result.Result.length; i++) {
var eventName = result.Result[i].Event_Name;
if (eventName == "Doff" || eventName == "Don" || eventName == "OutofRange" || eventName=="InRange") {
if (eventName == "Doff" || eventName == "OutofRange") {
BeginClockout();
}
else if (eventName == "Don" || eventName == "InRange") {
window.clearInterval(clockOutInterval);
statusCtl.set_autoCloseDelay(3000);
LogTimeEvent(true);
ShowMessage("Going on the clock");
}
}
console.log(result.Result[i].Event_Name);
}
}
})
}, 500)
};
Listing 6 –
PollDeviceEvents method – Handling the events raised by the headset
This method is only interested in four events that the
headset raises: Don
, Doff
, InRange
and OutofRange
. The first two
events describe the device either being placed on or removed from the user’s
head. The latter two events describe the relative proximity of the headset
device to the workstation. In my scenario, when the user takes off their
headset or they walk away from their workstation, we will use the BeginClockout
method to allocate 30
seconds for them to come back or be clocked off.
var secondsDelay = 30;
var secondsUntilClockOut;
function BeginClockout() {
if (secondsUntilClockOut > 0)
return;
secondsUntilClockOut = secondsDelay;
statusCtl.set_autoCloseDelay(secondsDelay * 1000);
ShowMessage("You have removed your headset and will be clocked out in <span id='countDown'>" + secondsDelay + "</span> seconds");
clockOutInterval = window.setInterval(function () {
if (secondsUntilClockOut == 0) {
window.clearInterval(clockOutInterval);
LogTimeEvent(false);
}
secondsUntilClockOut--;
$("#countDown").html(secondsUntilClockOut);
}, 1000);
}
Listing 7 – The
BeginClockout method – indicate that a user will be automatically clocked out
In the BeginClockout
method, we set a timer to count every
second until the specified secondsDelay
value runs out. During this time, we will use our notification control through
the ShowMessage
method to illustrate how much time is left
until the time clock action takes place.
Figure 2 –
Notification of pending time clock action
The time clock will be acted upon in the LogTimeEvent
method, passing in an
indicator of the new state. I will extend the CallCenter
object to provide a
way to identify the employee and location of the time clock service, so that in
the LogTimeEvent
method I can
make a standard jQuery-based ajax call to a WebAPI service with all of the
information needed to effect the time clock.
var timeClockUrl = "";
var employeeId = -1;
return {
Init: initFunction,
Spokes : function() {
return spokes;
},
set_TimeClockUrl: function(url) {
timeClockUrl = url;
return this;
},
set_EmployeeId: function(id) {
employeeId = id;
return this;
}
};
function LogTimeEvent(beginWork) {
clockInTimeout = window.setTimeout(function () {
$.ajax(timeClockUrl, {
async: false,
cache: false,
type: "POST",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({
EmployeeId: employeeId,
StartClock: beginWork
})
});
clockedInState = beginWork;
scheduledClockIn = false;
}, 1000);
scheduledClockIn = true;
}
Listing 8 – The
LogTimeEvent method – The transport that connects a headset event to a time clock
event
The payload for this jQuery post contains the employeeId and
the indicator of new time clock state. This method is configured to delay for
one second the transmission of this information. This short delay allows us to
trap repeated events from the headset and only transmit one call to the time
clock service.
Configuring the Time Clock Service
For this sample, I have elected to use ASP.Net WebAPI as the
service framework for our time clock. I have created an api folder in my project, and in this
folder I will use the ‘Add New Item’ menu to add a new Web API Controller class
named ClockController
. Inside of this class, I’m going to build out the Post
method to accept the payload from our jQuery ajax call inside of the LogTimeEvent
method.
public static readonly List<TimeClockEvent> TimeClockEvents = new List<TimeClockEvent>();
public HttpResponseMessage Post([FromBody]ClockModel model)
{
if (model.StartClock)
{
var evt = new TimeClockEvent()
{
Id = TimeClockEvents.Count + 1,
EmployeeId = model.EmployeeId,
ClockIn = DateTime.Now
};
TimeClockEvents.Add(evt);
}
else
{
var evt = TimeClockEvents.OrderByDescending(e => e.ClockIn).First(e => e.EmployeeId == model.EmployeeId);
evt.ClockOut = DateTime.Now;
}
return new HttpResponseMessage(HttpStatusCode.Created);
}
public class ClockModel
{
public int EmployeeId;
public bool StartClock;
}
public class TimeClockEvent
{
public int Id { get; set; }
public int EmployeeId { get; set; }
public DateTime ClockIn { get; set; }
public DateTime ClockOut { get; set; }
public TimeSpan Elapsed
{
get { return ClockOut - ClockIn; }
}
}
Listing 9 – The ClockController and supporting models
I am using a static list as an in-memory store of time clock
events. In a real application, you will want to use some other form of
persisted storage like a database to store your time records. This POST method
receives our payload as a ClockModel
object. Based on the desired state of the
time clock record, the method will create or update a record in the
TimeClockEvents
list.
With this data in place, we can create a simple timesheet
with a grid control that is bound to the data source to demonstrate that we are
in fact logging time records.
Figure 3 – A
timesheet showing two previously entered records and a third record generated
from a headset
Summary
In this article, we put together a JavaScript object that
connected to a RESTful service on the local machine to communicate with a
Plantronics headset. Our object would listen for events triggered from the
headset’s internal sensors and send appropriate messages to a web service that
we constructed with WebAPI. The sample code for this project can be downloaded
here. Please note, the sample
code contains a more robust connection and error handling for the headset
events. The Plantronics Developer forum and SDK can be accessed at http://developer.plantronics.com
The Telerik controls can be downloaded from http://www.telerik.com/products/aspnet-ajax/download.aspx