Create Attendant Plugins

Introduction

This document is created to get people started with creating plugins for the Anywhere365 Social Attendant. It is created with the versions 3.1 and 3.2 of the Social Attendant in mind. With newer version the SDK could be updated and this document probably will be outdated.

First Chapters will give you some basic knowledge about the SDK , the API and what the Attendant CoreThe Core is the center of Anywhere365. It manages all the Dialogues. is. The different types of Plugins will be explained and you will get an insight in the Core and how plugins should communicate with it.

Next there is a little information about the demo application that is created with this document.

In the chapters after, there will be a step by step getting started guide that will help you to create your first plugin. The demo application explained before these chapters can be a helpful guideline in combination with the steps in this document for developing your company’s own plugin for the Anywhere365 Social Attendant.

Another important part after creating a plugin is how to debug the created plugin, this part will be explained in the chapter named Debugging plugin.

At least we will end with some tips about how you can make users of your plugin install the plugin. There is no single or right way yet, the way you choose is depending on how much time you like to spend in creating a way to install the plugin or on what kind of users will use your plugin.

When you have finished reading last part I hope you have enough information to create future plugins. If you haven’t of if you have any questions or tips for us about creating plugins, you can contact WORKSTREAMPEOPLE.

 

Plugin Types

There are 2 main types of plugins. One type that just contains an assembly without any elements in the graphical interface (GUI). The other type contains elements that are shown in the interface of the attendant.

Client Plugin

An example of a plugin that does not use the GUI is our Busy Light plugin. It listens to events from the Attendant Core and reacts to some of these events.

In the technical specification of the plugin the main class of this kind of plugin will implement the interface IWspClientPlugin.

GUI Plugin

Plugins that use the GUI can be placed at some different places. It is possible to add a new item to the menu, when using a new menu item it is possible to fill the left (Content) and the right (Search) part or both. We created another type for our address books, which is introduced at a very early stage of development. It is mainly the same as adding a menu-item, but an existing menu-item will be enabled.

More placeholders for GUI plugins will be added to the SDK later on. One is already added in the control bar at the top of the Attendant, on the left of the audio buttons.

In the technical specification of the plugin the main class of this kind of plugin will implement the interface IWspClientGuiPlugin. Next thing is to specify what location you like to use for the controls (PluginUserControl). Possible locations are displayed in the picture below. It is possible that the address book type will be fully replaced by menu items in the future.

 

Communications with Attendant Core

An important part of the SDK for plugins of the Social Attendant is communicating to the Attendant Core. The core of the attendant is a part that handles communications with a lot of external programs and factors. This means it handles all calls to LyncMicrosoft Lync (formerly Microsoft Office Communicator) is an instant messaging program designed for business use and is the successor of Windows Messenger. In order to use Lync, a Microsoft Lync Server is required., UCCUCC stands for Unified Contact Center and consists of a queue that can be handled by Agents Each Contact Center has its own settings, interactive voice response questions and Agent with specific skills. Agents can be member of, or sign up to, one or more Contact Centers.’s (Unified Contact Centers), Exchange, etc…

The Attendant was the first client application using this core but more and more of our client applications are starting to depend on it. So also for our plugins in the Attendant it is important to be able to contact the core and listen to what it is doing and receiving. The way we can communicate with the core in a plugin is by an API, this API reveals the most important parts of the Attendant Core.

The main parts in the Attendant Core that the API reveals is something we call Managers, or interfaces of managers. We are still expanding the amount of Managers available in the API but down here is a graphical view of parts that are available to the plugin through the API.

There are some other properties available. The items that are not fully colored are not managers but tools to help in the plugin or in the attendant. The Module part is so you can log trough the logging mechanism of the Attendant. PluginInfo contains all information entered in the XML file with the plugin, this information is about the plugin and its creator. The PluginInfo is mainly build to check in the attendant for showing and using information about the plugin.

There are still some features directly on the API, They will be explained here:

ServiceInfoList

A collection with information about changes in the states of managers. Not all managers are available by default, but you have to wait for the correct state. Every change is added to this collection and revealing it in the API will make it possible to wait for the needed state of a manager.

PluginId

A unique identifying number for the plugin that is also known by the Attendant. Also in the log this ID will be used.

IsLoggedOn

A property that contains a representation of the Contact that is currently logged in. If Lync is not started or logged in the value of this property will be null.

 

Sample Application

In the chapters after this one we will explain step by step how to create your first plugin for the Social Attendant of Anywhere365. This application is already made as a sample. This sample can be used if you are getting stuck at a point.

The Sample application still needs the references to dll’s of an installed attendant, you can find more information about this below. Also there is a reference to an assembly for MVVM which can be downloaded by using NuGet.

If you do not have the sample application and would like to use it for a jump start with creating plugins you should contact WORKSTREAMPEOPLE.

The chapters in this document are missing some explanation about basic WPF and C# stuff like creating UserControls and using Dependency Properties. If you are not familiar with this it would be helpful to check the demo application so you can see how we created these parts.

 

Getting Started

Let’s start with creating our first Plugin for the Anywhere365 Social Attendant. We will create a simple GUI plugin that will introduce a new menu item. When the item is clicked it the attendant show that it is possible to place control on the left side (MAIN AREA) and the right side (RIGHT_AREA).

To show the interaction with the core we will wait for Lync to get started. After Lync is started we will show who is logged in, if Lync is logged out it will say so. Further we will show some information about the last call that is made.

WspClientGuiPlugin

  1. Open Visual Studio (2013) and create a new project and a solution if needed. I will name the project “Wsp.Anywhere365.DemoPlugin”.

  2. Add some references needed. After installation of the attendant these DLL’s can be found in the installation directory of the attendant (default: C:\Program Files (x86)\Anywhere365 Attendant).
    Add the next dll’s as reference:

    1. Wsp.Anywhere365.Client.Adapter.dll

    2. Wsp.Anywhere365.Client.Configuration.dll

    3. Wsp.Anywhere365.Client.Core.dll

    4. Wsp.Anywhere365.Client.Generic.dll

    5. Wsp.Anywhere365.Client.GUI.Generic.dll

    6. Wsp.Anywhere365.Client.SDK.dll

    7. Wsp.Anywhere365.External.UccStatistics.dll

    8. Wsp.Anywhere365.Generic.dll

    Tip If you like to create an Address book you could also add WSP.Anywhere365.GenericAddressbook.dll, this will contain default controls and object for address books.

  3. Next we will add an XML file that describes our plugin. The file will be named the same as the assembly and contains all information about the plugin and its creators. In my case I will name it “Wsp.Anywhere365.DemoPlugin.xml”

  4. Fill the XML file with the next information. Most parts can be changed to your information. Only the PluginDLL must be filled with the name of the created DLL.

    <?xml version="1.0" encoding="utf-8" ?>

    <AttendantPlugin

    Name="Demo Plugin"

    Description="Demo for starting with plugins."

    Vendor="WORKSTREAMPEOPLE B.V."

    VendorLogo="WORKSTREAMPEOPLE.png"

    VendorVersion=""

    Author=""

    CompanyUrl="www.WORKSTREAMPEOPLE.com"

    HelpFile="Wsp.Anywhere365.DemoPlugin.chm"

    TestedWithVersion=""

    PluginDll="Wsp.Anywhere365.DemoPlugin.dll">

    </AttendantPlugin>

  5. Make the file available in the bin folder by setting the property “Copy to Output Directory” to “Copy if newer” of “Copy always”. Also make sure the Build Action is set to “Content”.

  6. Rename the created class or create a new one. You can name it whatever you like. I will call it “DemoPlugin.cs”.

  7. For the DemoPlugin class Implement the interface IWspClientGuiPlugin. Make sure you implement all members of this interface. The code will look like the sample below.

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Windows.Media.Imaging;

    using Wsp.Anywhere365.Client.Core.SDK.Interface;

    using Wsp.Anywhere365.Client.SDK;

    namespace Wsp.Anywhere365.CallHistory

    {

    public class DemoPlugin : IWspClientGuiPlugin

    {

    public List<PluginUserControl> GetUserControlList()

    {

    throw new NotImplementedException();

    }

    public Dictionary<TabIcon, BitmapImage> GetUserIcon()

    {

    throw new NotImplementedException();

    }

    public void Init(IWspClientApi WspClientApi)

    {

    throw new NotImplementedException();

    }

    }

    }

Note If you chose “Class Library” as project type you are missing a reference for BitMapImage, this reference can be found in the assembly
PresentationCore

The last part of code will become the hearth of your plugin. You can create a local variable for your List of PluginUserControls and define these in your constructor. The big difference of a PluginUserControl with a default UserControl is the Property Area. This Property is filled with the location you want to show your Application. Because we want a plugin which is shown in the menu we should use two or one of two controls, which are the NEWPLUGIN_MAIN_AREA and the NEWPLUGIN_RIGHT_AREA. We will continue here later on.

 

PluginUserControl

  1. Create PluginUserControls, do this by creating a UserControl (WPF). Add a reference in the xaml: xmlns:ASDK="clr-namespace:Wsp.Anywhere365.Client.SDK;assembly=Wsp.Anywhere365.Client.SDK"
    and rename the control to <ASDK:PluginUserControl >.
    Make sure you inherit from PluginUserControl instead of UserControl in the CodeBehind and reference the correct namespace.

  2. To show an example how the core can be used I will create two Dependency Objects in the PluginUserControl, a bool named IsLoggedIn and a string named LoggedInUsername, these will be filled by the class DemoPlugin.

Note In a more extended plugin everything is possible, usually I create a MVVM-structure and give the IWspClientApi in the constructor of the
ViewModel. Then all the core functions can be called from ViewModel.

 

Show User who is logged in

The method GetUserControlList() will return this list and the attendant will do the rest. But offcourse we want to do more than that, the Init(Client.Core.SDK.Interface.IWspClientApi wspClientApi) gives us a IWspCleintApi object. This object is the core of communicating with the attendant.

  1. Add come local variables to use later on, one for the api reference, one for the plugin control and one for a list to add the LeftControl.

    private IWspClientApi _wspClientApi;

    private PluginUserControl leftControl;

    private readonly List<PluginUserControl> userControlList;

  2. Create LeftControl in the constructor of the DemoPlugin class, set the erea to NEWPLUGIN_MAIN_AREA to place it on the right place in the attendant and add it to the userControlList.

    public DemoPlugin()

    {

    userControlList = new List<PluginUserControl>();

    leftControl = new LeftControl();

    leftControl.Area = Area.NEWPLUGIN_MAIN_AREA;

    userControlList.Add(leftControl);

    }

  3. Make sure the GetUserControlList method returns our created and filled list op PluginUserControls

    public List<PluginUserControl> GetUserControlList()

    {

    return userControlList;

    }

  4. On init set the _wspClientApi, earlier this will not be available. And try to add a handler for the CollectionChanged on the ServiceInfoList. This List contains all status-changes for the Core Adapters.

    public void Init(IWspClientApi wspClientApi)

    {

    _wspClientApi = wspClientApi;

    _wspClientApi.ServiceInfoList.CollectionChanged +=ServiceInfoList_CollectionChanged;

    }

    private void ServiceInfoList_CollectionChanged(object sender,System.Collections.Specialized.NotifyCollectionChangedEventArgs e)

    {

    ...

    }

  5. When lync is started and logged in we will get the username of the logged in user from the API and we will set our created Boolean to true. When the status of the adapter will change to “LoggedOff” the value of the Boolean will be changed back to false.

    private void ServiceInfoList_CollectionChanged(object sender, System.Collections.

    Specialized.NotifyCollectionChangedEventArgs e)

    {

    foreach (ServiceInfo info in e.NewItems)

    {

    if (info.Name.Equals("LyncClientAdapter"))

    {

    switch (e.Action)

    {

    case NotifyCollectionChangedAction.Replace:

    switch (info.Status)

    {

    case State.Started:

    Application.Current.Dispatcher.BeginInvoke(new Action(() =>

    {

    _leftControl.Logged-InUsername = _wspClientApi.IsLoggedOn.SipUri.FriendlyUri;

    _leftControl.IsLoggedIn = true;

    }));

    break;

    case State.LoggedOff:

    Application.Current.Dispatcher.BeginInvoke(new Action(() =>

    {

    _leftControl.IsLoggedIn = false;

    }));

    break;

    }

    break;

    default:

    break;

    }

    }

    }

    }

    Notice that the dispatcher of the current application is invoked. This is because the event is raised in a different thread.

  6. To make the plugin implement able by the attendant you have to implement GetUserIcon. A simple way to make it work is to return an empty Dictionary. The method will be extended later on.

    public Dictionary<TabIcon, BitmapImage> GetUserIcon()
    {

    return new Dictionary<TabIcon, BitmapImage>();

    }

 

Show Contact for last incoming call

At this point the first interaction should be working. How to debug/test this is described in the next chapter. Before I will go to this I will extend the sample by noticing that there is an incoming call and get the contact of this call.

  1. Create a Dependency Property for a ItemViewModel. (Wsp.Anywhere365.Client.GUI.Generic.ViewModel.ItemViewModel). To use this ViewModel we need a MVVM library, we normally use the one of galasoft. This can be found in NuGet (Visual Studio plugin, manager for external DLL’s.

    Note For now MVVM is needed to use our custom controls with our custom model. This will be changed in a future version. If you don’t use one of
    our controls, you don’t need the mvvm library.

  2. Add listener for the Actual Conversation List on the conversation manager of the API.

    if (_wspClientApi.IConversation != null)

    {

    _wspClientApi.IConversation.ActualConversationList.CollectionChanged+= IncomingConversations_CollectionChanged;

    }

  3. Create method IncomingConversations_CollectionChanged and implement it to get the contact from incoming conversations and set it on the Dependency Property of the PluginUserControl.

    private void IncomingConversations_CollectionChanged(object sender, NotifyCollectionChangedEventArgse)

    {

    switch (e.Action)

    {

    case NotifyCollectionChangedAction.Add:

    foreach (Conversation item in e.NewItems)

    {

    if (item.ModalityList.ContainsKey(ModalityTypes.AudioVideoModality))

    {

    (item.ModalityList[ModalityTypes.AudioVideoModality] as ModalityAudioVideo).ModalityStateChangedEvent += (s, args) =>

    {

    Application.Current.Dispatcher.BeginInvoke(new Action(() =>

    {

    _leftControl.Contact = new ContactViewModel(item.Contact);

    }));

    };

    }
    }

    break;

    default:

    break;

    }

    }

  4. There is a button style to show a simple contact with name and status in the SDK you can add it to your PluginUserControl by the style “SquareContactBig”

    <Button Width="95"

    Height="90"

    Margin="10,70,10,10"

    VerticalAlignment="Top"

    HorizontalAlignment="Left"

    Style="{StaticResource SquareContactBig}"

    DataContext="{Binding Contact, RelativeSource={RelativeSourceAncestorType=ASDK:PluginUserControl}}">

    </Button>

 

Implement Menu Icon

Next we like to see a nice icon in the menu and make it use a color of our choice. To do this we need to extend the method to get the User Icons and add a property that returns the color.

  1. Before we extend the method returning the icons we will create a local variable in the DemoPlugin Class that will contain the dictionary.

    private readonly Dictionary<TabIcon,
    System.Windows.Media.Imaging.BitmapImage> _icons;

  2. Add the declaration of the dictionary to the constructor and add an image to the project with the build action “Resource”. In the sample a map Images is created for this resource and a resource is copied from one of our plugins. In the constructor also add a value to the Dictionary, make sure the key is set to MENU_ICON (form enum: Wsp.Anywhere365.Client.SDK.TabIcon). The constructor will finally be complete and look like the next part of code.

    public DemoPlugin()

    {

    _userControlList = new List<PluginUserControl>();

    _leftControl = new LeftControl();

    _leftControl.Area = Area.NEWPLUGIN_MAIN_AREA;

    _userControlList.Add(_leftControl);

    _icons = new Dictionary<TabIcon,

    System.Windows.Media.Imaging.BitmapImage>();

    _icons.Add(TabIcon.MENU_ICON, new BitmapImage(new

    Uri("pack://application:,,,/Wsp.Anywhere365.DemoPlugin;component/Images/icon_demo_plugin.png", UriKind.Absolute)));

    }

  3. Make the GetUserIcon method return the dictionary we created.

    public Dictionary<TabIcon, BitmapImage> GetUserIcon()

    {

    return _icons;

    }

  4. Now create a property named MenuColor that will return the chosen color on the getter.

    public string MenuColor

    {

    get { return "#59A064"; }

    }

This will finish our code and show a nice icon in the Menu. When the menu is clicked our user control will be shown in the main part of the Attendant. When you don’t set an image and color for the menu a grey circle without image will be shown in the menu.

In the next chapter more will be explained about debugging/testing the plugin in the attendant.

 

Debugging plugin

Let’s get our plugin ready to get tested. Make sure you installed the attendant and keep the installation path in mind. The installation path normally is “C:\Program Files (x86)\Anywhere365 Attendant\”, at least for 64-bit machines. Plugins are placed in the folder named “Plugins”. If the folder does not exist create it. In the plugins folder create a folder with the same name as the main DLL (project name of project in VS). In case of the sample that would be “Wsp.Anywhere365.DemoPlugin”.

Build your project (hopefully you don’t need to get any errors fixed) and go to the folder where it is build. In the folder there are a lot of files but you only need the DLL’s that are not available in the attendant. In the case of the demo plugin this would be only the “Wsp.Anywhere365.DemoPlugin.dll” and off course we need to copy the XML containing all plugin information. If one of this files is not present the attendant will nog load the plugin. Another possible problem for the attendant could be that the name of the folder is nog the same as the DLL.

The folder should look like the one below.

Now you can run the application but the debugger will not be attached. Let’s make it possible to use the run button in Visual Studio. Open the properties of you project and open the “Debug” tab. Select the Start Action “Start external program” and browse to the executable of the attendant. Also to load al correct config files we need to change the Working directory to the folder where the executable of the attendant is.

Now we are ready to hit the debug button in Visual Studio. To check if debug is working it’s smart to enter a breakpoint at the constructor of the DemoPlugin class.

 

Installer

There is no default way to install a plugin yet. We normally create a stand-alone installer to place the files in the Plugin folder of the attendant. You can check if and where the attendant is installed by reading the registry of the computer. The location of our entry is {HKEY_LOCAL_MACHINE\ SOFTWARE\WORKSTREAMPEOPLE\Anywhere365 Attendant] in the key named “InstallPath” the location of the installation is defined when a social attendant is installed. This key will be removed when the attendant is uninstalled.

A nice free to use tool to create a setup is the WIX Toolset (http://wixtoolset.org/). It is also possible to create a Zip-file with a readme which explains what files need to be placed on what location. For internal use in a company these things are not always necessary.

In the future there will be introduced a new way to install Plugin from within our application but this is work in progress.