Microsoft Lync and Skype for Business have a rich set of .NET APIs which make it easy to extend the platform and integrate it with other applications. This blog helps explain how to use those APIs.

Subscribing to presence in UCMA 2.0

Posted: March 11th, 2009 | Author: | Filed under: OCS Development, UCMA 2.0 | No Comments »

HPIM0061.JPGLet’s say, for purposes of illustration, that you have a little problem.

Streamlining your Unified Communications development with the concise magic of UCMA 2.0 has suddenly made you rich and famous, and freed up most of your development time. Before, you spent hours hunched over an ancient 640×480 CRT monitor, debugging uncooperative server applications; now you write a few lines of code on your 30″ wall-mounted flat-panel monitor while toying with the allocation of your latest $10 million in investment funds on your other 30″ wall-mounted flat-panel monitor, and within minutes your application is ready for production.

The trouble: you don’t want your boss to think that all this wealth and fame has gone to your head. When she’s signed into Communicator and available, you want to make sure she sees that you’re slaving away, hard at work. When she’s away from the desk and can’t keep tabs on your Communicator presence, you can relax a little.

Thankfully, UCMA 2.0 allows you to manage presence subscriptions and receive notifications whenever there is a change in the presence of one of your subscription targets.

We worked through the code to get a platform and a UserEndpoint established in my last post, so this time I’ll skip ahead a bit and just give you a class with all of that code written. If you want to dig into the details of how the setup and teardown works,you can check out my previous post (on publishing presence).

This class is pretty generic,so you can use it for trying out other UCMA 2.0 features as well. The only caveat is that it hasn’t really got any error handling, so it’s obviously not suitable for production code or even for anything at all complicated.

public class PresenceMonitor
{
    private const string STATE_XML_FORMAT =
        "<state xmlns="http://schemas.microsoft.com/2006/09/sip/state"" +
        " xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" manual=" +
        ""true" xsi:type="userState">{0}" +
        "";

    private const string NOTE_XML_FORMAT =
        "" +
        "{0}";

    private const int AVAILABLE = 3500;
    private const int BUSY = 6500;

    private const string APP_USER_AGENT = ...
    private const string LOCALHOST = ...
    private const int PORT = ...
    private const string GRUU = ...
    private const string OCS_SERVER = ...
    private const int TLS_PORT = 5061;

    private string _mySipUri;
    private string _yourSipUri;

    private CollaborationPlatform _platform;
    private UserEndpoint _endpoint;

    public void Run()
    {
        Console.Write("Enter your SIP URI: ");
        _mySipUri = Console.ReadLine();

        Console.Write("Enter the SIP URI to monitor: ");
        _yourSipUri = Console.ReadLine();

        IAsyncResult startupAsyncResult = StartUp();

        Console.WriteLine("-----Press enter to shut down.-----");
        Console.ReadLine();

        startupAsyncResult.AsyncWaitHandle.WaitOne();

        Console.WriteLine("Shutting down...");
        IAsyncResult shutdownAsyncResult = Stop();

        Console.WriteLine("-----Press enter to exit.-----");
        Console.ReadLine();

        shutdownAsyncResult.AsyncWaitHandle.WaitOne();
    }

    #region Startup
    private IAsyncResult StartUp()
    {
        ServerPlatformSettings settings =
            new ServerPlatformSettings(
                APP_USER_AGENT,
                LOCALHOST,
                PORT,
                GRUU,
                GetLocalCertificate()
            );

        _platform = new CollaborationPlatform(settings);
        return _platform.BeginStartup(StartupCompleted, null);
    }

    private void StartupCompleted(IAsyncResult result)
    {
        _platform.EndStartup(result);

        Console.WriteLine("Started up platform.");
        UserEndpointSettings endpointSettings =
            new UserEndpointSettings(
                _mySipUri,
                OCS_SERVER,
                TLS_PORT
            );

        _endpoint = new UserEndpoint(_platform, endpointSettings);
        _endpoint.BeginEstablish(EstablishCompleted, null);
    }

    private void EstablishCompleted(IAsyncResult result)
    {
        _endpoint.EndEstablish(result);

        Console.WriteLine("Established endpoint.");

        SubscribeToPresence();
    }
    #endregion

    private void SubscribeToPresence()
    {
        // Code to do something vaguely useful goes here.
    }
    #region Shutdown

    private IAsyncResult Stop()
    {
        return _endpoint.BeginTerminate(TerminateCompleted, null);
    }

    private void TerminateCompleted(IAsyncResult result)
    {
        _endpoint.EndTerminate(result);

        Console.WriteLine("Terminated endpoint.");

        _platform.BeginShutdown(ShutdownCompleted, null);
    }

    private void ShutdownCompleted(IAsyncResult result)
    {
        _platform.EndShutdown(result);

        Console.WriteLine("Shut down platform.");
    }

    private static X509Certificate2 GetLocalCertificate()
    {
        X509Store store =
            new X509Store(StoreLocation.LocalMachine);

        store.Open(OpenFlags.ReadOnly);

        X509Certificate2Collection certificates =
            store.Certificates;

        foreach (X509Certificate2 certificate in certificates)
        {
            if (certificate.SubjectName.Name.Contains(
                Dns.GetHostEntry("localhost").HostName)
                && certificate.HasPrivateKey)
            {
                return certificate;
            }
        }

        return null;
    }
    #endregion

}

Our code is going to go in the SubscribeToPresence method. The first thing we need to do is choose the presence categories that we want to subscribe to. The only one we need for now is the state category, which is the one that contains availability information.

_endpoint.RemotePresence.PresenceSubscriptionCategories =
    new string[] { "state" };

The method to add subscription targets takes a collection of RemotePresentitySubscriptionTargets. We only have one target, but we still need to put it into a list.

RemotePresentitySubscriptionTarget target =
    new RemotePresentitySubscriptionTarget(_yourSipUri, null);

List targets =
    new List() { target };

And, last but not least, we need to call the BeginAddTargets method. We’ll create the AddTargetsCompleted callback method in a second.

_endpoint.RemotePresence.BeginAddTargets(
    targets, AddTargetsCompleted, null
);

The callback method is mind-numbingly simple. We just call the end method to finish the asynchronous operation, and then write a line to the console saying that we added the presence target. I’ll take a minute to note here that if you were adding proper error-handling, you’d want to put that end method call in a try-catch block, because any exception that occurs during the asynchronous execution of the operation will get thrown in your code when you call the end method.

private void AddTargetsCompleted(IAsyncResult result)
{
    _endpoint.RemotePresence.EndAddTargets(result);
    Console.WriteLine("Added presence target.");
}

Things are looking balmy so far. If you ran the application now, it would successfully subscribe to the presence of the user whose SIP URI you enter. The trouble is that it wouldn’t do anything helpful with the presence information. We need to add an event handler for the PresenceNotificationReceived event.

This can go in the SubscribeToPresence method, right before you instantiate the RemotePresentitySubscriptionTarget.

_endpoint.RemotePresence.PresenceNotificationReceived +=
    new EventHandler(
        RemotePresence_PresenceNotificationReceived
    );

In the event handler itself, things get a little dodgy. This is usually the case when XML parsing gets involved in the business. Bear with me.

void RemotePresence_PresenceNotificationReceived(object sender, RemotePresenceNotificationEventArgs e)
{
    foreach (RemotePresentityNotificationData
        notification in e.Notifications)
    {
        foreach (PresenceCategoryWithMetaData
            category in notification.Categories)
        {
            string xml = category.CreateInnerDataXml();

            if (xml != null && xml.Length > 0)
            {
                XmlDocument stateCategory = new XmlDocument();
                StringReader reader = new StringReader(xml);
                stateCategory.Load(reader);
                XmlNodeList availabilityNodeList =
                    stateCategory.GetElementsByTagName("availability");

                if (availabilityNodeList != null && availabilityNodeList.Count > 0)
                {
                    long availability =
                        Convert.ToInt64(availabilityNodeList[0].InnerText);

                    Console.WriteLine(
                        string.Format("Monitored user's presence changed to {0}",
                        availability)
                    );

                    ProcessAvailability(availability);
                }
            }
        }
    }
}

Here’s the story: in the event arguments for the PresenceNotificationReceived event, we get a collection of RemotePresentityNotificationData objects. Each of these has one set of presence categories for one Communicator user. Because we only subscribed to the state category, we’re assuming that all of the PresenceCategoryWithMetaData objects we get in the presence notifications represent state information. By calling CreateInnerDataXml on the PresenceCategoryWithMetaData object, we get the raw presence XML, which we can then rummage through to find the availability element. Once we’ve got our hands on that, it’s a simple matter of grabbing the number inside and doing something with it.

Here’s what we do with it:

private void ProcessAvailability(long busyness)
{
    if (busyness >= BUSY)
    {
        PublishAvailabilityAndNote(AVAILABLE, "Lounging on the beach");
    }
    else
    {
        PublishAvailabilityAndNote(BUSY, "Working incredibly hard");
    }
}

The availability values increase as availability decreases, so if the boss has an availability value of busy or greater, she’s probably not keeping tabs on us.

I covered the gritty details of publishing presence in my previous post, so again I’ll just give you the code here. The only important difference is that we’re publishing TWO categories here instead of just the one that we published before. The note category controls that bit of text that you can type in under your name in Communicator. The template for it is stored in a constant that I snuck into the code for the class that I gave you at the beginning of this post. Take a closer look if you’re curious about the format.

private void PublishAvailabilityAndNote(long availability, string note)
{
    string stateXml = String.Format(
        STATE_XML_FORMAT,
        availability
    );

    string noteXml = string.Format(
        NOTE_XML_FORMAT,
        note
    );

    CustomPresenceCategory customCategory =
        new CustomPresenceCategory("state", stateXml);

    CustomPresenceCategory noteCategory =
        new CustomPresenceCategory("note", noteXml);

    PresenceCategory[] categoriesToPublish =
        new PresenceCategory[] { customCategory, noteCategory };

    string presenceDescription = string.Format(
        "availability: {0}, note: {1}", availability, note
    );

    _endpoint.LocalOwnerPresence.BeginPublishPresence(
        categoriesToPublish,
        PublishPresenceCompleted,
        presenceDescription
    );
}

private void PublishPresenceCompleted(IAsyncResult result)
{
    _endpoint.LocalOwnerPresence.EndPublishPresence(result);

    Console.WriteLine(string.Format(
        "Changed my presence to ({0}).", result.AsyncState.ToString())
    );
}

With this final crowning touch, we’re all set. If you create a PresenceMonitor object and call its Run method in a console app, you can enter your SIP URI and the SIP URI to monitor (don’t forget to prefix both with sip:) and the application will do its work. Change the presence of the monitored user, and straight away your own presence will automatically respond, as you, meanwhile, watch YouTube videos while being fanned with palm fronds.

I’ll leave the question of where to prop up those 30″ flat-panel monitors on the beach to a future post.



Leave a Reply

  • Note: Comment moderation is in use because of excessive spam. Your comment may not appear immediately.

  •