Using NFC to establish a persistent connection

This article describes how to open a connection between devices either by tapping the devices together or by searching for nearby devices over Bluetooth. A simple peer-to-peer chat application – NFC Talk – is used as an example.

Windows Phone 8 Proximity API

Windows Phone 8 Proximity API consists of classes in Windows.Networking.Proximity namespace. For details on the classes, methods, and other programming elements, see the MSDN Proximity API reference.

In addition to this also other networking related classes – like StreamSocket from Windows.Networking namespace – are very closely related to proximity operations and are often used in conjunction with it. For details on the networking namespace see the MSDN Networking API reference.

Also notice that the application capabilities that are needed in the WMAppManifest.xml for the above to work are ID_CAP_PROXIMITY and ID_CAP_NETWORKING.

Example overview

NFC Talk is a simple example application that demonstrates how a connection between two devices is initiated by either tapping the devices together or by searching for nearby devices utilising Bluetooth.

Windows Phone 8 Proximity API is used in NFC Talk to detect a nearby device and to acquire a socket connection to it. Further communication after the initial connection setup is done through the socket over Bluetooth or WLAN, whichever socket was automatically opened on by the platform.

In order to have an understanding of the whole picture, let's take a look at the NFC Talk application architecture.

Figure 1. NFC Talk application architecture

The application basically consists of five Windows Phone application pages, a singleton DataContext for owning the application widely used objects like an instance of Settings, and an instance of Communication, which is where all the peer-to-peer communication that is interesting for this article has been encapsulated.

Starting proximity communication and connecting to other devices

Looking for nearby devices is started by attaching delegates to Windows.Networking.Proximity.PeerFinder.TriggeredConnectionStateChanged and/or Windows.Networking.Proximity.PeerFinder.ConnectionRequested properties and calling the static void PeerFinder.Start() function.

using Windows.Networking.Proximity; // PeerFinder
using Windows.Networking.Sockets; // StreamSocket
using Windows.Storage.Streams; // DataWriter, DataReader

...

class Communication : INotifyPropertyChanged
{
    ...
    
    public string PeerName
    {
        ...
    }

    public IReadOnlyList<PeerInformation> Peers
    {
        ...
    }  

    private enum ConnectionStatusValue
    {
        Idle = 0,
        Searching,

        ...

        Connected
    };

    private ConnectionStatusValue _status;
    private StreamSocket _socket;
    private DataWriter _writer;
    private DataReader _reader;

    public void Start()
    {
        if (_status == ConnectionStatusValue.Idle)
        {
            _status = ConnectionStatusValue.Searching;

            ...

            PeerFinder.TriggeredConnectionStateChanged += TriggeredConnectionStateChanged;
            PeerFinder.ConnectionRequested += ConnectionRequested;
            PeerFinder.Start();
        }
        else
        {
            ...
        }
    }

    ...
}

When a very nearby device is detected by the platform – that is, when the devices are properly tapped together – the delegate that was attached to the TriggeredConnectionStateChanged is invoked and the TriggeredConnectionStateChangedEventArgs with the StreamSocket available right there is delivered as an argument. At this point it may make sense to stop listening for further tap events, as has been done in NFC Talk. In order to read and write the socket DataReader and DataWriter objects are created, listening to incoming data transfers is started, and the application user's chat nickname is sent to the application instance in the other device.

...

class Communication : INotifyPropertyChanged
{
    ...

    private void TriggeredConnectionStateChanged(object sender,
                                                 TriggeredConnectionStateChangedEventArgs e)
    {
        switch (e.State)
        {
            ...

            case TriggeredConnectState.Completed:
                {
                    PeerFinder.TriggeredConnectionStateChanged -= TriggeredConnectionStateChanged;
                    PeerFinder.ConnectionRequested -= ConnectionRequested;

                    _socket = e.Socket;

                    _writer = new DataWriter(e.Socket.OutputStream);
                    _writer.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;
                    _writer.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;

                    _reader = new DataReader(e.Socket.InputStream);
                    _reader.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;
                    _reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;

                    _status = ConnectionStatusValue.Connected;

                    ListenAsync();

                    SendNameAsync(NFCTalk.DataContext.Singleton.Settings.Name);

                    ...
                 }
                 break;

             ...
        }

        ...
    }

    ...
}

Another possiblity to create the connection – in addition to the tap gesture – is to search for nearby devices using Bluetooth (Wi-Fi direct is not supported on Windows Phone 8). Searching for devices is started by calling the static async IReadOnlyList<PeerInformation> PeerFinder.FindAllPeersAsync() function that returns a list of detected nearby devices. In order for a device to be detectable it needs to be running the same application in foreground and it needs to have Bluetooth turned on.

...

class Communication : INotifyPropertyChanged
{
    ...

    public async void Search()
    {
        if (_status != ConnectionStatusValue.Idle)
        {
            ...

            try
            {
                ...

                Peers = await PeerFinder.FindAllPeersAsync();

                ...
            }
            catch (Exception)
            {
                ...
            }
        }
        else
        {
            ...
        }
    }

    ...
}  

When search has completed and if nearby devices were found, a connection to a device can be attempted by calling static async StreamSocket PeerFinder.ConnectAsync(PeerInformation) function. Calling this function causes the ConnectionRequested delegate to be invoked in the other device, giving the other device also a possiblity to attempt opening a connection in the other direction, i.e. back to the connection initiator, thus creating a two-way socket connection.

...

class Communication : INotifyPropertyChanged
{
    ...

    public async void Connect(PeerInformation peer)
    {
        ...
        
        try
        {
            ...

            _socket = await PeerFinder.ConnectAsync(peer);

            if (_socket != null)
            {
                PeerFinder.TriggeredConnectionStateChanged -= TriggeredConnectionStateChanged;
                PeerFinder.ConnectionRequested -= ConnectionRequested;

                _writer = new DataWriter(_socket.OutputStream);
                _writer.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
                _writer.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;

                _reader = new DataReader(_socket.InputStream);
                _reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
                _reader.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;

                _status = ConnectionStatusValue.Connected;

                ListenAsync();

                SendNameAsync(NFCTalk.DataContext.Singleton.Settings.Name);

                ...
            }

            ...
        }
        catch (Exception)
        {
            ...
        }
    }

    private async void ConnectionRequested(object sender, ConnectionRequestedEventArgs e)
    {
        try
        {

            ...

            _socket = await PeerFinder.ConnectAsync(e.PeerInformation);

            PeerFinder.TriggeredConnectionStateChanged -= TriggeredConnectionStateChanged;
            PeerFinder.ConnectionRequested -= ConnectionRequested;

            _writer = new DataWriter(_socket.OutputStream);
            _writer.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
            _writer.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;

            _reader = new DataReader(_socket.InputStream);
            _reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
            _reader.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;

            _status = ConnectionStatusValue.Connected;

            ListenAsync();

            SendNameAsync(NFCTalk.DataContext.Singleton.Settings.Name);

            ...
        }
        catch (Exception)
        {
            ...
        }
    }

    ...
}  

Alright, now that we have taken a look at couple of ways to open a two-way socket connection between devices, you might wonder what are ListenAsync() and SendNameAsync(NFCTalk.DataContext.Singleton.Settings.Name)? Read further to learn how to transmit data between the connected devices.

Listening for incoming transmissions

Let's pause for a minute here to investigate the protocol that has been defined for the communication in NFC Talk, as it makes understanding the following code clips easier. The protocol is briefly just that the first message sent over the socket must be the chat name, all encoding must be little endian, text as UTF-8, and the message format as in the following table.

Message Version Identifier Content
Send chat name Uint32 0 Uint32 0 Uint32 Name length in bytes string Name
Send message Uint32 0 Uint32 1 Uint32 Text length in bytes string Text

NFCTalk.DataContext.Singleton.Settings.Name is just how we're accessing the string Name property – the chat nickname for the user operating this device – in the settings object.

Listening to incoming communication is an asynchronously run loop that reads data from the socket and checks the protocol version number and incoming message type, and then either reads in the chat nickname of the peer or the chat message from the peer. A simple helper method for reading the socket has been written here, as the data we want to read may not be available at once but may require multiple asynchronous load operations.

...

class Communication : INotifyPropertyChanged
{
    ...

    private async Task ListenAsync()
    {
        try
        {
            while (true)
            {
                await GuaranteedLoadAsync(sizeof(UInt32));
                uint version = _reader.ReadUInt32();

                if (version == 0)
                {
                    await GuaranteedLoadAsync(sizeof(UInt32));
                    uint code = _reader.ReadUInt32();

                    switch (code)
                    {
                        case 0: // name
                            {
                                await GuaranteedLoadAsync(sizeof(UInt32));
                                uint length = _reader.ReadUInt32();

                                await GuaranteedLoadAsync(length);
                                string name = _reader.ReadString(length);

                                PeerName = name;
                            }
                            break;

                        case 1: // message
                            {
                                await GuaranteedLoadAsync(sizeof(UInt32));
                                uint length = _reader.ReadUInt32();

                                await GuaranteedLoadAsync(length);
                                string text = _reader.ReadString(length);

                                Message m = new Message()
                                    {
                                        Name = PeerName,
                                        Text = text,
                                        Direction = Message.DirectionValue.In
                                    };
 
                               ...
                            }
                            break;
                    }
                }
                else
                {
                    throw new Exception("Protocol version mismatch");
                }
            }
        }
        catch (Exception)
        {
            ...
        }
    }

    private async Task GuaranteedLoadAsync(uint length)
    {
        DataReaderLoadOperation op;

        while (length != _reader.UnconsumedBufferLength)
        {
            op = _reader.LoadAsync(length - _reader.UnconsumedBufferLength);

            if (await op.AsTask<uint>() == 0)
            {
                throw new Exception();
            }
        }
    }

    ...
}

Transmitting information

Transmitting data over the socket is easy. To conform with the NFC Talk example application's protocol, we are first sending the protocol version, then the operation identifier and finally the operation specific content. uint DataWriter.MeasureString(string) is used to determine the amount of bytes the string we are sending is going to take.

...

class Communication : INotifyPropertyChanged
{
    ...

    private async Task SendNameAsync(string name)
    {
        try
        {
            _writer.WriteUInt32(0); // protocol version
            _writer.WriteUInt32(0); // operation identifier

            uint length = _writer.MeasureString(name);
            _writer.WriteUInt32(length);
            _writer.WriteString(name);

            await _writer.StoreAsync();
        }
        catch (Exception)
        {
            ...
        }
    }

    public async Task SendMessageAsync(Message m)
    {
        try
        {
            if (m.Text.Length > 0)
            {
                _writer.WriteUInt32(0); // protocol version
                _writer.WriteUInt32(1); // operation identifier

                uint length = _writer.MeasureString(m.Text);
                _writer.WriteUInt32(length);
                _writer.WriteString(m.Text);

                await _writer.StoreAsync();
            }
        }
        catch (Exception)
        {
            ...
        }
    }

    ...
}

Closing a connection and stopping proximity communication

When no more communication is going to be done to a connected remote device we need to disconnect the socket. This can be done by simply disposing the socket. Notice that in the NFC Talk application we are reconnecting the connection delegates in the disconnection method in order to be able to make connections to other devices.

...

class Communication : INotifyPropertyChanged
{
    ...

    public void Disconnect()
    {
        if (_status == ConnectionStatusValue.Connected)
        {
            if (_socket != null)
            {
                _socket.Dispose();
                _socket = null;
            }

            PeerFinder.TriggeredConnectionStateChanged += TriggeredConnectionStateChanged;
            PeerFinder.ConnectionRequested += ConnectionRequested;

            _status = ConnectionStatusValue.Searching;
        }
    }

    ...
}

When there is no more need to initiate or receive connections, we need to call static void PeerFinder.Stop() function.

...

class Communication : INotifyPropertyChanged
{
    ...

    public void Stop()
    {
        PeerFinder.Stop();

        switch (_status)
        {
            ...

            case ConnectionStatusValue.Searching:
                {
                    PeerFinder.TriggeredConnectionStateChanged -= TriggeredConnectionStateChanged;
                    PeerFinder.ConnectionRequested -= ConnectionRequested;
                }
                break;

            case ConnectionStatusValue.Connected:
                {
                    if (_socket != null)
                    {
                        _socket.Dispose();
                        _socket = null;
                    }
                }
                break;
        }

        _status = ConnectionStatusValue.Idle;
    }

    ...
}

Further reading

Opening a socket connection between devices is just one of the possibilities provided by the Proximity API. Head over to MSDN Proximity API reference to learn more.


Last updated 22 May 2013

Back to top

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×