Monitoring Local Presence Changes in Lync Client SDK
Thursday, February 18, 2016Overview
I’m giving a talk remotely today to the DFW Unified Communications Users Group on Skype for Business development and will be showing some of the code related to my simple Raspberry Pi LED Status tool. The code behind the project was thrown together in under an hour and really isn’t a great example of best practices, however, it’s a good place to get an idea about how to do some basic development with the Skype for Business Client.
In this post, I’ll go through some of the code used for that project. This post is based on the initial commit, so make sure you’re referencing that in case I’ve got the bug and decided to update it.
The Lync Client SDK
Yes, Lync. Unfortunately, it wasn’t updated for the Skype for Business release, however, it works fine with the latest client. You’ll need to be running the latest Lync 2013 client or Skype for Business client in order to use solutions developed with Lync Client SDK, however, you require no other components to actually run the program—it’s all included with the client.
References
Download the Lync Client SDK (search, I won’t provide a link due to Link Rot regularly wiping them out) and install it.
You’ll need to reference Microsoft.Lync.Model.dll and Microsoft.Lync.Utilities.dll. They’ll be located in the Office15 folder…somewhere.
A Note about Multi-threaded Code
The Lync Client SDK provides a set of APIs for interacting with the client using multi-threaded code. This means that you’re going to have to be cautious with certain operations. Many things you’ll want to work with are fired on events in a background thread and all of the perils of multi-threaded programming will apply. You’ll see a few things I’ve done to combat this, but the code in the initial commit is by no means fully audited to ensure thread-safety and a lot can go wrong in this area!
Interacting With the Lync Client
In the constructor for the Monitor class, you’ll see everything you need in order to connect to and interact with the Lync client.
// Connect to the current Lync Client m_client = LyncClient.GetClient(); var contact = m_client.Self.Contact; if (contact == null) // There's better ways to do this, but this works in a dirty implementation { SimpleLogger.Log("Client is not logged in - Setting Offline", m_gpioController.AllOff()); throw new InvalidOperationException("Client must be logged in before starting the monitor"); } // Create a subscription and subscribe to our own contact object contact.ContactInformationChanged += ContactOnContactInformationChanged; var contactSubscription = m_client.ContactManager.CreateSubscription(); contactSubscription.Contacts.Add(contact);
The connection occurs at LyncClient.GetClient(). This method will throw if the client is not launched. I chose not to catch that exception since it effectively renders the application dead.
From there, I subscribe to the ContactInformationChanged event and add a subscription to my local contact (m_client.Self.Contact). This ensures that when any property of my contact changes, the method “ContactOnContactInformationChanged” will fire (on a background thread).
When Contact Information Changes
I have a pretty simple event handler defined for that:
private void ContactOnContactInformationChanged(object sender, ContactInformationChangedEventArgs e) { if (e.ChangedContactInformation.Contains(ContactInformationType.Availability)) SetLedState(); }The "e" property lets us know what specific modification caused the event to fire. It's common for more than one thing to change at a time, but since I only care about the Availability component, I check for it and fire off "SetLedState". The changed information is not included, just the component that changed, so we have to look that up. This is done via the following in SetLedState();
var contact = m_client.Self.Contact; if (contact == null) return; object availabilityId = contact.GetContactInformation(ContactInformationType.Availability); var availability = (ContactAvailability) availabilityId;In this application, I'm only subscribing to the local contact's presence so I simply grab that contact's Contact object. Availability is an int boxed in an object, not a ContactAvailability enum as one might expect, however it's simple to just cast that to ContactAvailability for easier decoding. From there, I "switch" on the ContactAvailability and set the LEDs using the GpioController class.
What’s up With All The Interlocked Stuff in GpioController
Remember all of that multi-threaded nonsense I mentioned earlier? I implemented Blinking using a Timer, which is a class that lets you fire off a method on a background thread at a given interval. Because of this, and because our status changes come on an event handler that fires on a background thread, there’s a few members of our GpioController that could be modified by more than one thread.
Normally I’d use a lock, and that would be fine here, as well, but the requirements for this application were simply to ensure that the variable being read is the latest copy in memory. Lighter weight patterns work fine in this scenario and I use them enough that I simply go there when the kind of variable fits well.
In addition, the GpioController is disposable because that timer needs to be cleaned up. The Dispose pattern that’s commonly used is not thread-safe. I’ve included a class in the Patterns class that handles it in a thread-safe manner. There’s not going to be a case in the application, as it’s currently written, where the GpioController will be disposed on anything but the main UI thread, however, I anticipate that future changes will introduce this and I’d rather not fix that later. For the most part, you can ignore that class. If you’ve not done a lot with the Interlocked static methods, you’ll find it to be confusing.
The GpioController keeps a “current LED state” in a flags enum. Enums are effectively syntactical sugar over ints with constant fields. I developed a NuGet package that contains a number of helpers for flags enums and includes a wrapper to provide “safe” access to an enum from multiple threads, providing guarantees that when the “Value” member is set, any getters on another thread will always receive the latest value instead of what happens to be in the cache for the core your code is executing on.
The only other place that needed protection was around the blinking feature. To protect that, I used an int variable in place of a bool and Interlocked to ensure it’s updated and read properly. Let’s look at that more closely:
// Check that the light is actually blinking and set it to NOT blinking in a threadsafe manner if (Interlocked.CompareExchange(ref m_isBlinking, 0, 1) == 1) { m_timer.Change(Timeout.Infinite, -1); Thread.Sleep(m_currentBlinkInterval.Milliseconds * 2); }
The Interlocked bit serves two purposes. First, it ensures that the value of m_isBlinking is both read and written to in a manner that guarantees the latest value will be retrieved. It also ensures that if two threads hit this code at nearly exactly the same time, only one will execute the statements in the if block, modifying our timer and incurring the penalty from sleep.
What the code is actually doing is saying "read what is actually stored in m_isBlinking" and if it's set to "1", set it to "0". Then, check the value that was previously stored in m_isBlinking and continue into the body of the "if" only if the previous value of m_isBlinking was "1".
Summary
The code is pretty thoroughly commented, so I’d encourage you to look at each of the methods in each of the classes for more information. You’ll see I’ve put an event handler in for detecting power events, which is a good idea when your application is going to run on a laptop/tablet. It’ll catch the “Suspend” event and let you do anything you need to do to clean up before suspending. Beware that this code should be simple, since the machine can hit suspend before your code is finished executing. I use it to turn the LEDs off and catch the Resume to ensure the application doesn’t crash when coming back up (the Lync Client API will fire events before you can actually get at the data contained within the Contact class, but it’s easy to detect and prevent while resuming).
No comments :
Post a Comment