Steve Spencer's Blog

Blogging on Azure Stuff

Proximity Communication with Windows Phone

In my previous post I talked about using NFC to communicate between devices and hopefully gave you an introduction to the capabilities of NFC. This post will cover the Proximity API in more detail and show you how to set up a persistent connection between two Windows Phones which will allow you to communicate without needing to keep tapping phones together to transfer data. Windows Phone and Windows 8 apps can utilise the .NET Proximity APIs to connect together apps without needing to be connected to the same network. Proximity uses BlueTooth or WiFi Direct (Not on Windows Phone 8) to make a persistent connection between the two devices and utilising StreamSockets to communicate between the two devices. The proximity API uses a PeerFinder to browse for proximate devices or to sit and wait for a Tap to occur.

The first scenario is to set your app in a mode where it can accept incoming Tap requests, then set up a connection between the two devices.

The second scenario is to start your app and then browse for other devices that are running your app and which are within range of your device.

Both scenarios use the PeerFinder classes to initiate the communication.

Scenario 1: TAP to send

PeerFinder has a number of static methods and events that allow you to wire up your app to respond to peer requests. In the Tap to send scenario your app needs to wait for a tap to occur and then initiate opening up the communications channel. PeerFinder has an event called TriggeredConnectionStateChanged which is triggered upon a Tap initiation and is fired for each event state change that occurs. Upon successful connection an open socket is returned which can then be used for communication between the two devices. Once the event is wired up the PeerFinder needs to be started. This is required for both scenarios. You will also need to ensure that the Proximity and Network capabilities are added to you app manifest.

// Wire up the event and be notified when a device is tapped
PeerFinder.TriggeredConnectionStateChanged += PeerFinder_TriggeredConnectionStateChanged; 
// Start finding peer apps, while making this app discoverable by peers
PeerFinder.Start();

The event handler is as follows:

<Insert Code>

PeerFinder.TriggeredConnectionStateChanged += PeerFinder_TriggeredConnectionStateChanged;
.
.
.
private void PeerFinder_TriggeredConnectionStateChanged(object sender, 
                                            TriggeredConnectionStateChangedEventArgs args)
{
    switch (args.State)
    {
        case TriggeredConnectState.Listening:
            // Connecting as host
            WriteStatusMessage("Listening");

            break;
        case TriggeredConnectState.PeerFound:
            // Proximity gesture is complete and user can pull their devices away. Remaining work is to 
            // establish the connection using a different transport, like TCP/IP or Bluetooth
            WriteStatusMessage("Peer Found");

            break;
        case TriggeredConnectState.Connecting:
            // Connecting as a client
            WriteStatusMessage("Connecting");
            break;
        case TriggeredConnectState.Completed:
            // Connection completed, retrieve the socket over which to communicate
            if (!StreamSockets.ContainsKey(args.Socket.Information.RemoteHostName.CanonicalName))
            {
                StreamSockets.Add(args.Socket.Information.RemoteHostName.CanonicalName, args.Socket);
                _currentConnectionID = args.Socket.Information.RemoteHostName.CanonicalName; 
            }

            WriteStatusMessage("Connected");

            // start the listener off on a new thread
            _Receivethread = new System.Threading.Thread(new ThreadStart(ReceiveData));
            _Receivethread.Start();

            break;
        case TriggeredConnectState.Canceled:
            WriteStatusMessage("Canceled");

            break;
        case TriggeredConnectState.Failed:
            // Connection was unsuccessful
            WriteStatusMessage("Failed");
            break;
    }

}

The code above covers the whole connection process and the important part is the TriggeredConnectState.Completed state. It is in this state that we are presented with a valid socket and in the case of Windows phone 8 a BlueTooth connection between the two phones has been created with an open StreamSocket to enable the communication. We also setup a thread that listens on the socket for incoming data to allow two way communication to occur. Both apps need to go through this process and setup a mechanism for sending and receiving data. PeerFinder will find all apps with the same id but it is possible to find other apps that are compatible with your application by adding alternate application identities by adding the information to the AlternateIdentities collection. However, when I tried to add an alternate Windows Phone 8 identity, the key "WindowsPhone" was not accepted and it looks like only Windows can be used.

Using this method the connection is established when the Tap is initiated but this can get tedious if you want to connect to a number of devices. In this case Scenario 2 would be better.

Scenario 2: Browser for devices

It is also possible to connect to compatible applications directly without the need to Tap devices together. The act of starting a compatible app will allow the device to be discoverable to other devices running the same app. PeerFinder is again used but this time we need to wire up a different event, ConnectionRequested. This is fired when a device attempts to connect and allows the connection to be made. When ConnectionRequested fires, the connection information of the device trying to connect is passed through with the EventArgs and PeerFinder.ConnectAsync is called. On successful connection then the same methods for sending and listening are used as in scenario 1.

PeerFinder.ConnectionRequested += PeerFinder_ConnectionRequested;
// Start finding peer apps, while making this app discoverable by peers
PeerFinder.Start();
.
.
.
async void PeerFinder_ConnectionRequested(object sender, ConnectionRequestedEventArgs args)
{
    WriteStatusMessage("ConnectionRequested");

    StreamSocket socket = await PeerFinder.ConnectAsync(pi);
    PeerConnected(socket);
}

We've wired up the app so that it is capable of receiving a connection from a remote device, we now need to initiate the connection.

Again this is done using PeerFinder and the method FindAllPeersAsync. Will return a list of applications that are compatible which can then be connected to individually. Only one Peer connection can be made at any one time so you will need to connect, send data and then disconnect before moving on to the next peer. Iterating through the list of PeerInformation that is passed.

IReadOnlyList<PeerInformation> readOnlyList = await PeerFinder.FindAllPeersAsync();
PeersFound(readOnlyList);
.
.
.
private async void PeersFound( IReadOnlyList<PeerInformation> readOnlyList)
{
    foreach (PeerInformation pi in readOnlyList)
    {
        StreamSocket socket = await PeerFinder.ConnectAsync(pi);
        PeerConnected(socket);
        return;//for now lets just deal with the first connection
    }
            
}

private void PeerConnected(StreamSocket socket)
{
    OnConnectionStatusChanged("Connected");

    // Do send/receive stuff here

}

For a full worked example see here and for more details of messages

You will need to look into the capabilities of each device as some will only connect via BlueTooth and others only using WiFi Direct and you can use the properties of PeerFinder to control which mechanism you would like to use

PeerFinder.AllowBluetooth = true;
PeerFinder.AllowInfrastructure = false; 
PeerFinder.AllowWiFiDirect = false;

The two posts should now give you information about how to connect phone apps together and provide some insight into how you can connect other devices and provide a rich set of functionality to you apps that previously required backend services to communicate.