Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Windows Phone 8: Peer Connection (Bluetooth and Wi-Fi Direct)

DZone's Guide to

Windows Phone 8: Peer Connection (Bluetooth and Wi-Fi Direct)

· Mobile Zone
Free Resource

Discover how to focus on operators for Reactive Programming and how they are essential to react to data in your application.  Brought to you in partnership with Wakanda

One of the features I got excited about with the Windows Phone 8 SDK is the Proximity APIs. These APIs give your application access Bluetooth connectivity and Wi-Fi Direct/NFC to share anything you want from 2 compatible phones running an application that is expecting for a connection. My example is pretty simplistic and it won’t fit in a real life case but it is good for starters.

In this example we will use heavily the PeerFinder class that exposes functions to accept and wait for Connections and to asynchronously search for peers to connect to. Once a Connection established we can read and write using Binary readers over sockets. Pretty easy!

The project can be downloaded here.

MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Windows.Foundation;
using Windows.Networking.Proximity;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

namespace PhoneApp1
{
    public partial class MainPage : PhoneApplicationPage
    {
        private IReadOnlyList<PeerInformation> _peerInformationList;
        private PeerInformation _requestingPeer;
        private StreamSocket _socket = null;
        private bool _socketClosed = true;
        private DataWriter _dataWriter;
        private DataReader _dataReader;
        private bool _triggeredConnectSupported = false;

        bool _isLaunchedByTap = false;
        private bool _browseConnectSupported = false;
        // Constructor
        public MainPage()
        {
            InitializeComponent();
            Loaded += MainPage_Loaded;
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            _triggeredConnectSupported = (PeerFinder.SupportedDiscoveryTypes & PeerDiscoveryTypes.Triggered) ==
                             PeerDiscoveryTypes.Triggered;
            _browseConnectSupported = (PeerFinder.SupportedDiscoveryTypes & PeerDiscoveryTypes.Browse) ==
                                      PeerDiscoveryTypes.Browse;
            if (_triggeredConnectSupported || _browseConnectSupported)
            {
                // This scenario demonstrates "PeerFinder" to browse for peers to connect to using a StreamSocket
                PeerFinder_StartFindingPeersButton.Click += new RoutedEventHandler(PeerFinder_StartFindingPeers);
                PeerFinder_BrowsePeersButton.Click += new RoutedEventHandler(PeerFinder_BrowsePeers);
                PeerFinder_ConnectButton.Click += new RoutedEventHandler(PeerFinder_Connect);
                PeerFinder_AcceptButton.Click += new RoutedEventHandler(PeerFinder_Accept);
                PeerFinder_SendButton.Click += new RoutedEventHandler(PeerFinder_Send);
                PeerFinder_StartFindingPeersButton.Visibility = Visibility.Visible;
            }
        }
        string[] rgConnectState = {"PeerFound", 
                                   "Listening",
                                   "Connecting",
                                   "Completed",
                                   "Canceled",
                                   "Failed"};
        public void NotifyUser(string strMessage, NotifyType type)
        {
            Dispatcher.BeginInvoke( () =>
            {
                switch (type)
                {
                    // Use the status message style.
                    case NotifyType.StatusMessage:
                        MessageBox.Show(strMessage,"Status",MessageBoxButton.OK);
                        break;
                    // Use the error message style.
                    case NotifyType.ErrorMessage:
                        MessageBox.Show(strMessage,"Error",MessageBoxButton.OK);
                        break;
                }
            });
        }
         private void TriggeredConnectionStateChangedEventHandler(object sender, TriggeredConnectionStateChangedEventArgs eventArgs)
        {
            
            if (eventArgs.State == TriggeredConnectState.PeerFound)
            {
                // Use this state to indicate to users that the tap is complete and
                // they can pull there devices away.
                NotifyUser("Tap complete, socket connection starting!", NotifyType.StatusMessage);
            }

            if (eventArgs.State == TriggeredConnectState.Completed)
            {
                NotifyUser("Socket connect success!", NotifyType.StatusMessage);
                // Grab the socket that just connected.
                _socket = eventArgs.Socket;
                Dispatcher.BeginInvoke(() =>
                {
                    this.PeerFinder_StartSendReceive();
                });

            }

            if (eventArgs.State == TriggeredConnectState.Failed)
            {
                NotifyUser("Socket connect failed!", NotifyType.ErrorMessage);
            }
        }

        private bool _peerFinderStarted = false;

        private void SocketError(String errMessage)
        {
            NotifyUser(errMessage, NotifyType.ErrorMessage);
            PeerFinder_StartFindingPeersButton.Visibility = Visibility.Visible;
            if (_browseConnectSupported)
            {
                PeerFinder_BrowsePeersButton.Visibility = Visibility.Visible;
            }
            PeerFinder_SendButton.Visibility = Visibility.Collapsed;
            PeerFinder_MessageBox.Visibility = Visibility.Collapsed;
            if (!_socketClosed)
            {
                _socketClosed = true;
                _socket.Dispose();

                _socket = null;
            }
        }

        async private void PeerFinder_Send(object sender, RoutedEventArgs e)
        {
            NotifyUser("", NotifyType.ErrorMessage);
            String message = PeerFinder_MessageBox.Text;
            PeerFinder_MessageBox.Text = ""; // clear the input now that the message is being sent.
            if (!_socketClosed)
            {
                if (message.Length > 0)
                {
                    try
                    {
                        uint strLength = _dataWriter.MeasureString(message);
                        _dataWriter.WriteUInt32(strLength);
                        _dataWriter.WriteString(message);
                        uint numBytesWritten = await _dataWriter.StoreAsync();
                        if (numBytesWritten > 0)
                        {
                            NotifyUser("Sent message: " + message + ", number of bytes written: " + numBytesWritten, NotifyType.StatusMessage);

                        }
                        else
                        {
                            SocketError("The remote side closed the socket");
                        }
                    }
                    catch (Exception err)
                    {
                        if (!_socketClosed)
                        {
                            SocketError("Failed to send message with error: " + err.Message);
                        }
                    }
                }
                else
                {
                    NotifyUser("Please type a message", NotifyType.ErrorMessage);
                }
            }
            else
            {
                SocketError("The remote side closed the socket");
            }
        }

        async private void PeerFinder_Accept(object sender, RoutedEventArgs e)
        {
            NotifyUser("Connecting to " + _requestingPeer.DisplayName + "....", NotifyType.StatusMessage);
            PeerFinder_AcceptButton.Visibility = Visibility.Collapsed;
            try
            {
                _socket = await PeerFinder.ConnectAsync(_requestingPeer);
                NotifyUser("Connection succeeded", NotifyType.StatusMessage);
                PeerFinder_StartSendReceive();
            }
            catch (Exception err)
            {
                NotifyUser("Connection to " + _requestingPeer.DisplayName + " failed: " + err.Message, NotifyType.ErrorMessage);
            }
        }

        private void PeerConnectionRequested(object sender, ConnectionRequestedEventArgs args)
        {
            _requestingPeer = args.PeerInformation;
             Dispatcher.BeginInvoke( () =>
            {
                NotifyUser("Connection requested from peer " + args.PeerInformation.DisplayName, NotifyType.StatusMessage);

                this.PeerFinder_AcceptButton.Visibility = Visibility.Visible;
                this.PeerFinder_SendButton.Visibility = Visibility.Collapsed;
                this.PeerFinder_MessageBox.Visibility = Visibility.Collapsed;
            });
        }

        async void PeerFinder_StartReader()
        {
            try
            {
                uint bytesRead = await _dataReader.LoadAsync(sizeof(uint));
                if (bytesRead > 0)
                {
                    uint strLength = (uint)_dataReader.ReadUInt32();
                    bytesRead = await _dataReader.LoadAsync(strLength);
                    if (bytesRead > 0)
                    {
                        String message = _dataReader.ReadString(strLength);
                        NotifyUser("Got message: " + message, NotifyType.StatusMessage);
                        PeerFinder_StartReader(); // Start another reader
                    }
                    else
                    {
                        SocketError("The remote side closed the socket");
                    }
                }
                else
                {
                    SocketError("The remote side closed the socket");
                }
            }
            catch (Exception e)
            {
                if (!_socketClosed)
                {
                    SocketError("Reading from socket failed: " + e.Message);
                }
            }
        }

        // Start the send receive operations
        void PeerFinder_StartSendReceive()
        {
            PeerFinder_SendButton.Visibility = Visibility.Visible;
            PeerFinder_MessageBox.Visibility = Visibility.Visible;

            // Hide the controls related to setting up a connection
            PeerFinder_ConnectButton.Visibility = Visibility.Collapsed;
            PeerFinder_AcceptButton.Visibility = Visibility.Collapsed;
            PeerFinder_FoundPeersList.Visibility = Visibility.Collapsed;
            PeerFinder_BrowsePeersButton.Visibility = Visibility.Collapsed;
            PeerFinder_StartFindingPeersButton.Visibility = Visibility.Collapsed;
            _dataReader = new DataReader(_socket.InputStream);
            _dataWriter = new DataWriter(_socket.OutputStream);
            _socketClosed = false;
            PeerFinder_StartReader();
        }

        async void PeerFinder_Connect(object sender, RoutedEventArgs e)
        {
            NotifyUser("", NotifyType.ErrorMessage);
            PeerInformation peerToConnect = null;
            try
            {
                // If nothing is selected, select the first peer
                if (PeerFinder_FoundPeersList.SelectedIndex == -1)
                {
                    peerToConnect = _peerInformationList[0];
                }
                else
                {
                    peerToConnect = _peerInformationList[PeerFinder_FoundPeersList.SelectedIndex];
                }

               NotifyUser("Connecting to " + peerToConnect.DisplayName + "....", NotifyType.StatusMessage);
                _socket = await PeerFinder.ConnectAsync(peerToConnect);
                NotifyUser("Connection succeeded", NotifyType.StatusMessage);
                PeerFinder_StartSendReceive();
            }
            catch (Exception err)
            {
                NotifyUser("Connection to " + peerToConnect.DisplayName + " failed: " + err.Message, NotifyType.ErrorMessage);
            }
        }

        async void PeerFinder_BrowsePeers(object sender, RoutedEventArgs e)
        {
            NotifyUser("Finding Peers...", NotifyType.StatusMessage);
            try
            {
                _peerInformationList = await PeerFinder.FindAllPeersAsync();
            }
            catch (Exception ex)
            {
                Debug.WriteLine("FindAll throws exception" + ex.Message);
            }
            Debug.WriteLine("Async operation completed");
            NotifyUser("No peers found", NotifyType.StatusMessage);
            try{
            if (_peerInformationList.Count > 0)
            {
                PeerFinder_FoundPeersList.Items.Clear();
                for (int i = 0; i < _peerInformationList.Count; i++)
                {
                    ListBoxItem item = new ListBoxItem();
                    item.Content = _peerInformationList[i].DisplayName;
                    PeerFinder_FoundPeersList.Items.Add(item);
                }
                PeerFinder_ConnectButton.Visibility = Visibility.Visible;
                PeerFinder_FoundPeersList.Visibility = Visibility.Visible;
                NotifyUser("Finding Peers Done", NotifyType.StatusMessage);
            }}
            catch
            {
                NotifyUser("No peers found", NotifyType.StatusMessage);
                PeerFinder_ConnectButton.Visibility = Visibility.Collapsed;
                PeerFinder_FoundPeersList.Visibility = Visibility.Collapsed;
            }
        }

        void PeerFinder_StartFindingPeers(object sender, RoutedEventArgs e)
        {
            NotifyUser("", NotifyType.ErrorMessage);
            if (!_peerFinderStarted)
            {
                // attach the callback handler (there can only be one PeerConnectProgress handler).
                PeerFinder.TriggeredConnectionStateChanged += new TypedEventHandler<object, TriggeredConnectionStateChangedEventArgs>(TriggeredConnectionStateChangedEventHandler);
                // attach the incoming connection request event handler
                PeerFinder.ConnectionRequested += new TypedEventHandler<object, ConnectionRequestedEventArgs>(PeerConnectionRequested);
                // start listening for proximate peers
                PeerFinder.Start();
                _peerFinderStarted = true;
                if (_browseConnectSupported && _triggeredConnectSupported)
                {
                    NotifyUser("Tap another device to connect to a peer or click Browse for Peers button.", NotifyType.StatusMessage);
                    PeerFinder_BrowsePeersButton.Visibility = Visibility.Visible;
                }
                else if (_triggeredConnectSupported)
                {
                    NotifyUser("Tap another device to connect to a peer.", NotifyType.StatusMessage);
                }
                else if (_browseConnectSupported)
                {
                    NotifyUser("Click Browse for Peers button.", NotifyType.StatusMessage);
                }
            }
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (_triggeredConnectSupported || _browseConnectSupported)
            {
                // Initially only the advertise button should be visible.
                PeerFinder_StartFindingPeersButton.Visibility = Visibility.Visible;
                PeerFinder_BrowsePeersButton.Visibility = Visibility.Collapsed;
                PeerFinder_ConnectButton.Visibility = Visibility.Collapsed;
                PeerFinder_FoundPeersList.Visibility = Visibility.Collapsed;
                PeerFinder_SendButton.Visibility = Visibility.Collapsed;
                PeerFinder_AcceptButton.Visibility = Visibility.Collapsed;
                PeerFinder_MessageBox.Visibility = Visibility.Collapsed;
                PeerFinder_MessageBox.Text = "Hello World";
                if (IsLaunchedByTap())
                {
                    NotifyUser("Launched by tap", NotifyType.StatusMessage);
                    PeerFinder_StartFindingPeers(null, null);
                }
                else
                {
                    if (!_triggeredConnectSupported)
                    {
                        NotifyUser("Tap based discovery of peers not supported", NotifyType.ErrorMessage);
                    }
                    else if (!_browseConnectSupported)
                    {
                        NotifyUser("Browsing for peers not supported", NotifyType.ErrorMessage);
                    }
                }
            }
            else
            {
                NotifyUser("Tap based discovery of peers not supported \nBrowsing for peers not supported", NotifyType.ErrorMessage);
            }
        }

        // Invoked when the main page navigates to a different scenario
        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            if (_peerFinderStarted)
            {
                // detach the callback handler (there can only be one PeerConnectProgress handler).
PeerFinder.TriggeredConnectionStateChanged -= new TypedEventHandler<object, TriggeredConnectionStateChangedEventArgs>(TriggeredConnectionStateChangedEventHandler);
                // detach the incoming connection request event handler
                PeerFinder.ConnectionRequested -= new TypedEventHandler<object, ConnectionRequestedEventArgs>(PeerConnectionRequested);
                PeerFinder.Stop();
                if (_socket != null)
                {
                    _socketClosed = true;
                    _socket.Dispose();

                    _socket = null;
                }
                _peerFinderStarted = false;
            }
        }
        public bool IsLaunchedByTap()
        {
            bool isLaunchedByTap = _isLaunchedByTap;
            _isLaunchedByTap = false;
            return isLaunchedByTap;
        }
        public enum NotifyType
        {
            StatusMessage,
            ErrorMessage
        };
    }
}

MainPage.xaml
    <phone:PhoneApplicationPage
    x:Class="PhoneApp1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock Text="{Binding Path=LocalizedResources.PageTitle, Source={StaticResource LocalizedStrings}}" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <StackPanel Orientation="Vertical" Margin="0,10,0,0" Grid.Row="0">
                <Button x:Name="PeerFinder_StartFindingPeersButton" Content="Advertise" Visibility="Collapsed" Margin="0,0,10,0"/>
                <Button x:Name="PeerFinder_BrowsePeersButton" Content="Browse for Peers" Visibility="Collapsed" Margin="0,0,10,0"/>
                <Button x:Name="PeerFinder_ConnectButton" Content="Connect To a Peer" Visibility="Collapsed" Margin="0,0,10,0"/>
                <ListBox x:Name="PeerFinder_FoundPeersList" Visibility="Collapsed">
                </ListBox>
            </StackPanel>
            <StackPanel Orientation="Vertical" Margin="0,10,0,0" Grid.Row="1">
                <Button x:Name="PeerFinder_AcceptButton" Content="Accept Connection Request" Visibility="Collapsed" Margin="0,0,10,0"/>
                <Button x:Name="PeerFinder_SendButton" Content="Send Message" Visibility="Collapsed" Margin="0,0,10,0"/>
                <TextBox x:Name="PeerFinder_MessageBox" Visibility="Collapsed" Width="400" Margin="0,0,10,0"/>
            </StackPanel>
        </Grid>

        <Grid x:Name="Output" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="1">
            <TextBlock x:Name="PeerFinderOutputText" TextWrapping="Wrap" />

        </Grid>


        <!--Uncomment to see an alignment grid to help ensure your controls are
            aligned on common boundaries.  Remove or comment out before shipping
            your application.-->
        <!--<Image Margin="0" Source="/Assets/AlignmentGrid.png" Stretch="None" IsHitTestVisible="False" />-->
    </Grid>

</phone:PhoneApplicationPage>

Of course you need to tweak the Proprieties file a bit (I just checked all the authorization). And there you have it folks an easy way to send messages between WP8 smartphone. I couldn’t try this because the VM can’t access to Bluetooth but I am pretty sure once I get my hands on real machines it will.

Also I need to mention that this code is a simple port of the Proximity Lab of Windows RT and one can only hope that this will allow bumping data from a Windows Phone to a Windows Tablet and vice versa.

Learn how divergent branches can appear in your repository and how to better understand why they are called “branches".  Brought to you in partnership with Wakanda

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}