Sending a call to a specific endpoint in UCMA

One of the cool things about Lync that people often take for granted is that you can sign in from multiple computers (or devices), and any call that is sent to you will ring at all of those various locations. There's quite a bit of complex routing logic that goes into this, but it mostly takes place under the covers, and even when you're developing a UCMA application, you generally don't need to worry about it. If you create a new AudioVideoCall object and call AudioVideoCall.BeginEstablish with a SIP URI, Lync automatically "forks" that call to all of the registered endpoints for that user. Basically when the call hits the Lync Front End Server, the Front End Server sends a branch to each of the endpoints where that user is signed in. The first one to answer gets to take the call. While it's very courteous of Lync Server to do all of this stuff on its own without bothering us, the branching behaviour can sometimes get in the way. You might want to send an IM or a call to a user at one specific location. Maybe you want a call to go only to the user's IP desk phone, but not to the Lync client on the PC. What do you do in a case like this?

If you read my previous post on GRUUs, and you were wondering what possible use anyone could get out of one of these creatures, the time has now come. Lync has a particular type of GRUU that it uses to identify a single endpoint -- that is to say, not just a Lync user, but a specific location (IP address, port) where that user is signed in. Here's what one looks like:

sip:michael@domain.local;opaque=user:epid:tA_uNyaIa52myaQiU_MRzAAA;gruu

If you use this GRUU as the destination URI when establishing a call, it will go only to that single endpoint, bypassing the fancy forking logic applied by Lync!

Now, how do you find out an endpoint URI like this to use for calls? Well, anywhere you have access to a ParticipantEndpoint object in UCMA, you can check the ParticipantEndpoint.Uri property to get the GRUU for that endpoint. For example, in a two-party call, you can check Call.RemoteEndpoint.Uri. In a conference, you can get the collection of remote participants using the GetRemoteParticipantEndpoints method, and get endpoint URIs from the collection of ParticipantEndpoint objects you get back.

To give you a brief and very simple demonstration, I've written up a quick sample app. This is what it does: when you place an audio call to the application, it accepts the call and then sends you an IM at the specific endpoint where you called from. Sign in from two or more computers and give it a try. (Please forgive the slightly messy state of the code and the lack of error handling.)

[csharp] using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Rtc.Collaboration; using Microsoft.Rtc.Collaboration.AudioVideo; using System.Threading; using Microsoft.Rtc.Signaling;

namespace EndpointUriSample { class Tester { CollaborationPlatform _platform; ApplicationEndpoint _endpoint; AudioVideoCall _avCall;

public void Start() { ProvisionedApplicationPlatformSettings settings = new ProvisionedApplicationPlatformSettings("tester", "urn:application:mgreenlee.test");

_platform = new CollaborationPlatform(settings);

_platform.EndStartup(_platform.BeginStartup(null, null));

ApplicationEndpointSettings endpointSettings = new ApplicationEndpointSettings("sip:mgreenlee.test@ccdev.claritycon.com", "ccdev-lync-001.ccdev.clarityinternal.net", 5061);

_endpoint = new ApplicationEndpoint(_platform, endpointSettings);

_endpoint.EndEstablish(_endpoint.BeginEstablish(null, null));

_endpoint.RegisterForIncomingCall<AudioVideoCall>(OnAudioVideoCallReceived); }

void OnAudioVideoCallReceived(object sender, CallReceivedEventArgs<AudioVideoCall> e) { e.Call.BeginAccept( ar => { try { e.Call.EndAccept(ar);

PlaceInstantMessageCallToSender(e.Call.RemoteEndpoint.Uri); } catch (RealTimeException ex) { Console.WriteLine(ex); } }, null); }

private void PlaceInstantMessageCallToSender(string senderEndpointUri) { Conversation conversation = new Conversation(_endpoint); InstantMessagingCall imCall = new InstantMessagingCall(conversation);

imCall.BeginEstablish(senderEndpointUri, null, ar => { try { imCall.EndEstablish(ar);

SendInstantMessage(imCall); } catch (RealTimeException ex) { Console.WriteLine(ex); } }, null); }

private static void SendInstantMessage(InstantMessagingCall imCall) { imCall.Flow.BeginSendInstantMessage("You are the endpoint that called me.", ar2 => { try { imCall.Flow.EndSendInstantMessage(ar2); } catch (RealTimeException ex) { Console.WriteLine(ex); } }, null); }

public void Stop() { _endpoint.EndTerminate(_endpoint.BeginTerminate(null, null));

_platform.EndShutdown(_platform.BeginShutdown(null, null)); }

} } [/csharp]

You'll just need to add a Program.cs with a Main method that instantiates the class and calls the Start method. Feel free to comment or email me if you have any questions!