WP8 - Proximity與NFC - 2

Windows Phone 8–Proximity與NFC–2

Windows Phone 8支持Proximity communication採用的是NFC技術。透過該技術讓二個設備之間透過tapping後建立連線。

在前一篇<Windows Phone 8–Proximity與NFC–1>說明了WP8在Proximity APIs後,該篇主要針對實作部分加以說明如何

操作Proximity APIs來交易與建立連線。

 

 

〉利用NFC來交換資訊 - device之間互相的溝通:

    根據在前一篇討論的部分,在操作Proximity APIs最重要的類別:ProximityDevice是往下說明的重要部分,

包括操作設備是否具有Proximity provider、處理publish與subscribe運作、指定message type與儲存資訊,

另外在與Device互相溝通時使用的Protocol與callback handler也需要注意使用。往下便實際透過範例程式加以說明。

 

[事件準備]

1. 請先準備Windows Phone 8設備如果是要採用Emulator的話,請先參考<Proximity tapper>安裝相關功能。

2. 專案中的WMAppManifest.xml加上對應的<Capabilities />:ID_CAP_PROXIMITY

 

 

針對要實作devices之間的互相溝通,首先從Publish / Subscribe使用的Message type與適用對象來找出實作的對象:

Protocol Targets Operations Description
Windows.{Sub type} Between devices Publish and subscribe 訊息包括binary data。{Sub type}要記得指定。訊息代表被傳遞至另一個設備。
WindowsUri Between devices Publish and subscribe 訊息包括一個UTF-16LE編碼的URI字串。不需指定{Sub type}。
WindowsMime.{MIME} Between devices Publish and subscribe 訊息是指定的MIME類型。{MIME}要記得指定。
NDEF Between devices, between device and a tag Publish and subscribe 訊息內容是標準的NDEF record。使用NDEF為message type則取得的訊息使用NDEF record。

主要在負責devices之間溝通有上述四個Protocol,以下範例將分別說明這四個Protocol的應用。

 

(1) 發佈/訂閱 Windows.MySubType 訊息

1-1.  發佈訊息

  使用方法為:(此範例使用Message為主,如果要傳送BinaryMessage或UriMessage可自行轉換)

  public long PublishMessage(string messageType, string message, MessageTransmittedHandler messageTransmittedHandler)

private void WindowsSubTypePublish()
{
    // 回傳一個id做為建立subscriptione與stoppublishingmessage()
    gPublishId = gDevice.PublishMessage(
        "Windows.MySubType",
        txtMsg.Text.Trim(),
        // 增加該handler負責處理當發佈的訊息已抵達至另一個設備。
        (ProximityDevice device, long messageId) =>
        {
            Dispatcher.BeginInvoke(() =>
            {
                txtLog.Text = string.Format("已傳送訊息:id = {0}\r\n", messageId);
            });
            CancelPublication();
        });
}

  搭配參數與實作對應的Handler:

  public delegate void MessageTransmittedHandler(ProximityDevice sender, long messageId)

  負責處理當發佈的訊息送達另一個設備所觸發的事件。至於在發送時的Message Type請參考<>或<>的說明。

1-2. 訂閱訊息

  使用方法為:

  public long SubscribeForMessage(string messageType, MessageReceivedHandler messageReceivedHandler)

private void WindowsSubTypeSubsrcibe()
{
    // 指定ProximityDevice訂閱某個訊息類型的訊息
    // 並指定收到訊息後要處理的handler
    gSubscribeId = gDevice.SubscribeForMessage(
        "Windows.MySubType",
        (ProximityDevice device, ProximityMessage message) =>
        {
            Dispatcher.BeginInvoke(() =>
            {
                // 因為收到的訊息為字串
                txtLog.Text = string.Format("已收到訊息:\r\nmsg type:{0}\r\ncontent:{1}\r\nid:{2}",
                                        message.MessageType,
                                        message.DataAsString,
                                        message.SubscriptionId);
            });
            CancelSubscritption();
        });
}

  搭配參數與實作對應的Handler:

  public delegate void MessageReceivedHandler(ProximityDevice sender, ProximityMessage message)

  負責處理設備收到了指定訂閱的訊息所觸發的事件

 

 

(2). 發佈/訂閱 WindowsUri 訊息

2-1. 發佈URI messages

         發佈的訊息內容,例如:web links、telephone links、application specific URI schema…等,類似發佈文字訊息。

         使用:PublishUriMessage()的方法,將訊息發佈給其他的devices;使用PublishBinaryMessage()的方法將訊息發佈至tag之中。

         需注意使用的是:WindowsUri 協定,並且URI需使用UTF-16LE編碼;

private void PublishWindowsUri()
{
    // 採用一個.PublishUriMessage發送指定的uri
    gPublishId = gDevice.PublishUriMessage(
            new Uri(txtMsg.Text.Replace("msg:",string.Empty)),
            // 增加該handler負責處理當發佈的訊息已抵達至另一個設備。
            (ProximityDevice sender, long messageId) =>
            {
                Dispatcher.BeginInvoke(() =>
                {
                    txtLog.Text = string.Format("已傳送訊息:id = {0}\r\n", messageId);
                });
                CancelPublication();
            });
}

 

2-2. 訂閱URI messages

private void SubscribeWindowsUri()
{
    gSubscribeId = gDevice.SubscribeForMessage(
                    "WindowsUri",
                    (ProximityDevice device, ProximityMessage message) =>
                    {
                        // 處理取得message的內容
                        byte[] array = new byte[message.Data.Length];
                        using (DataReader dataReader = DataReader.FromBuffer(message.Data))
                        {
                            dataReader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf16LE;
                            dataReader.ReadBytes(array);
 
                            Uri uri = new Uri(System.Text.Encoding.Unicode.GetString(
                                                array, 0, array.Length - 2));
 
                            txtLog.Text = String.Format("Received URL \"{0}\"\n", uri.ToString());                                   
                            CancelSubscritption();
                        }
                    });
}

 

 

(3) 發佈/訂閱 WindowsMime.{MIME} 訊息

3-1. 發佈 WindowsMime.{MIME} 訊息

       以圖像png為例,發佈給另一個設備。在發佈時需注意只適用於小size的圖示、contact、行事曆事件…等。另外,

       也需要注意ProximityDevice的MaxMessageBytes屬性,它限制了有多少bytes可以被發佈出來。建議在發送時先了解一下大小。

       在處理MIME時,需根據發送的類型不同將它們轉成byte[],才能進行發佈。

private void WindowsMimePublish()
{
    // 顯示目前ProximityDevice支持最大發佈的Size
    MessageBox.Show(string.Format("Max sizx: {0} KB", gDevice.MaxMessageBytes / 1024));
    // 指定要傳遞的檔案
    Uri tUri = new Uri("Assets/ApplicationIcon.png", UriKind.Relative);
    using (Stream tStream = System.Windows.Application.GetResourceStream(tUri).Stream)
    {
        byte[] tBytes = new byte[tStream.Length];
        tStream.Read(tBytes, 0, (int)tStream.Length);
        using (DataWriter tWriter = new DataWriter())
        {
            // PNG specification says that data must be in MSB order
            tWriter.ByteOrder = ByteOrder.BigEndian;
            tWriter.WriteBytes(tBytes);
 
            // to publish to a tag you would use message type "WindowsMime:WriteTag.image/png"
            gPublishId = gDevice.PublishBinaryMessage(
                            "WindowsMime.image/png",
                            tWriter.DetachBuffer(),
                            (ProximityDevice device, long messageId) =>
                            {
                                Dispatcher.BeginInvoke(() =>
                                {
                                    txtLog.Text = string.Format("已傳送訊息:id = {0}\r\n", messageId);
                                });
                            });
        }
    }
}

 

3-2. 訂閱 WindowsMime.{MIME} 訊息

private void WindowsMimeSubscribe()
{
    gSubscribeId = 
        gDevice.SubscribeForMessage(
            "WindowsMime.image/png",
            (ProximityDevice device, ProximityMessage message) =>
            {
                byte[] array = new byte[message.Data.Length];
                using (DataReader dataReader = DataReader.FromBuffer(message.Data))
                {
                    // PNG specification 
                    dataReader.ByteOrder = ByteOrder.BigEndian;
                    dataReader.ReadBytes(array);
                    Dispatcher.BeginInvoke(() =>
                    {
                        using (MemoryStream stream = new MemoryStream(array))
                        {
                            var bitmap = new System.Windows.Media.Imaging.BitmapImage();
                            bitmap.SetSource(stream);
 
                            // you could have the following image declared in XAML
                            imgObj.Source = bitmap;
                        }
                    });
                }
            });
}    

相關WindowsMime的資訊可以參考:<WindowsMime Protocol>。

另外,如果您希望透過NFC實現devices之間檔案的傳遞,那麼您可用 PeerFinder 類別來加以實現,概念是:

透過NFC找到new peer(使用TriggeredConnectionStateChanged 事件或subscribe 一個app-to-app的NFC message),

接著透過PeerFinder建立一個StreamSocket物件來實現二個devices之間資料的傳遞。(ProximityDevice)

 

 

4. 發佈/訂閱 NDEF 訊息:       

     NFC Forum已定義了NFC交易內容專用的binary structure,命名為:NDEF (NFC Data Exchange Format)。

 

4-1. 發佈 NDEF 訊息

       由於這邊使用的是devices之間的交易,所以用「NDEF」為message type;針對device與tag則要改成「NDEF:WriteTag」。

       由於NDEF record不容易編輯,因此有了<NDEF Library for Proximity APIs (NFC)>,透過它來往下操作。

private void PublishNDEFMessage()
{
    // 使用NDEF Library元件來發送個 TEXT訊息
    var ndefRecord = new NdefTextRecord
    {
        Text = "hello, NFC",
        LanguageCode = CultureInfo.CurrentCulture.TwoLetterISOLanguageName
    };
    var ndefMessage = new NdefMessage { ndefRecord };
    gPublishId = gDevice.PublishBinaryMessage(
                "NDEF",
                ndefMessage.ToByteArray().AsBuffer(),
                (ProximityDevice device, long messageId) =>
                {
                    // 標籤已寫入資訊,停止發佈。
                    CancelPublication();
                    Dispatcher.BeginInvoke(() =>
                    {
                        MessageBox.Show("transmitting device succed");
                    });
                });
}

       利用NdefTextRecord類別為例, 簡單寫入一個訊息來操作訊息。

 

4-2. 訂閱 NDEF 訊息

private void SubscribeNDEFMessage()
{
    gSubscribeId = gDevice.SubscribeForMessage(
                "NDEF",
                (ProximityDevice device, ProximityMessage message) =>
                {
                    string tMsg = string.Empty;
                    var ndefMessage = NdefMessage.FromByteArray(message.Data.ToArray());
                    foreach (NdefRecord record in ndefMessage)
                    {
                        // 取得序列化類型,進一步識別為何種NDEF類型
                        var specializedType = record.CheckSpecializedType(false);
                        if (specializedType == typeof(NdefTextRecord))
                        {
                            // Convert and extract Smart Poster info
                            var textRecord = new NdefTextRecord(record);
                            tMsg += "Type: " + textRecord.Type.ToString() + " " + textRecord.LanguageCode + "\n";
                            tMsg += "Content: " + textRecord.Text + "\n";
                        }
                    }
                    Dispatcher.BeginInvoke(() =>
                    {
                        txtLog.Text = tMsg;
                    });
                });
}

 

 

5. 發佈/訂閱 Custom 的訊息

     要透過自訂的類別可以做為Proximity的交易,首先需要先定義一個可被序列化的類別,接著在透過序列化與反序列化的方式,

定義好的客製類別進行傳遞與交易。

 

5-1. 定義自訂類別

[System.Runtime.Serialization.DataContract]
public class MyStoreInfo
{
    [System.Runtime.Serialization.DataMember]
    public string Name { get; set; }
 
    [System.Runtime.Serialization.DataMember]
    public string Address { get; set; }
 
    [System.Runtime.Serialization.DataMember]
    public string URI { get; set; }
}

     在操作定義類別時,如果遇到類別無法定義,記得加入 System.Runtime.Serialization.dll 的參考。

 

5-2. 發佈自訂類別

private void PublishCustomData()
{
    Custom.MyStoreInfo tInfo = new Custom.MyStoreInfo
    {
        Name = "吃到飽",
        Address = "台北市火車站",
        URI = "http://www.google.com"
    };
 
    // 將Custom Data的類別序列化
    MemoryStream stream = new MemoryStream();
    var serializer = new System.Runtime.Serialization.DataContractSerializer(typeof(Custom.MyStoreInfo));
    serializer.WriteObject(stream, tInfo);
 
    // 將取得的Stream轉成byte[],最後再透過datawriter寫入proximity
    byte[] array = new byte[stream.Length];
    stream.Position = 0;
    stream.Read(array, 0, (int)stream.Length);
    using (DataWriter tWriter = new DataWriter())
    {
        tWriter.WriteBytes(array);
 
        // 發佈自訂的SubType:Windows.MyCustom:ProximityProj.Custom
        gPublishId = gDevice.PublishBinaryMessage(
                        "Windows.MyCustom:ProximityProj.Custom",
                        tWriter.DetachBuffer(),
                        (ProximityDevice device, long messageId) =>
                        {
                            // 標籤已寫入資訊,停止發佈。
                            CancelPublication();
                            Dispatcher.BeginInvoke(() =>
                            {
                                MessageBox.Show("Write tag succed");
                            });
                        });
    }
}

 

5-3. 訂閱自訂類別

private void SubscribeCustomData()
{
   gSubscribeId = gDevice.SubscribeForMessage(
                   "Windows.MyCustom:ProximityProj.Custom",
                   (ProximityDevice device, ProximityMessage message) =>
                   {
                       byte[] array = new byte[message.Data.Length];
                       using (DataReader dataReader = DataReader.FromBuffer(message.Data))
                       {
                           dataReader.ByteOrder = ByteOrder.BigEndian;
                           dataReader.ReadBytes(array);
 
                           using (MemoryStream memory = new MemoryStream(array))
                           {
                               var serializer = new System.Runtime.Serialization.DataContractSerializer(typeof(Custom.MyStoreInfo));
 
                               Custom.MyStoreInfo item = (Custom.MyStoreInfo)serializer.ReadObject(memory);
 
                               Dispatcher.BeginInvoke(() =>
                               {
                                   string tMsg = string.Format("name: {0}\naddress: {1}\nuri: {2}",
                                                       item.Name, item.Address, item.URI);
                                   MessageBox.Show(tMsg);
                               });
                           }
                       }
                   });
}

        操作時DataReader、DataWriter是最重要的類別,另外要注意Stream的操作也是一個學問。

 

 

[範例程式]

測試方法:

‧請將WMAppManifest.xml中的<DefaultTask />調整成MainPage.xaml即可以進行測試。

‧測試Windows.{Sub type}:在文字框輸入:「msg:」

‧測試WindowsUri:在文字框輸入:「uri:」

‧測試WindowsMime.{mime type}:在文字框輸入:「mime:」

‧測試NDEF:在文字框輸入:「ndef:」

‧測試Custom Data:在文字框輸入:「custom:」

======

"Proximity"並非只有NFC技術而已,只是WP 8與其他系統之間近期以支持NFC這higher level的技術。

以上該篇主要說明devices之間的溝通,相關於device與tag之間的操作,則參考下一篇<Windows Phone 8–Proximity與NFC–3>的內容。

希望有助於大家了解Proximity APIs的功能,謝謝。

 

References

Proximity and Near Field Communication & NFC (重點文章,其內容包括很多相關的範例)

Using NFC to exchange information (重要)

Using NFC to establish a persistent connection

How to Launch Apps via Proximity APIs (NFC)

New NFC and Proximity documentation on Windows Phone 8

Windows Phone 8: Networking, Bluetooth, and NFC Proximity for Developers & PixPresenter Sample (重要)

WP8 NFC Tutorial: Voice Messages on NFC Tags

How to Store Application Data on NFC Tags

Use NFC tags with Windows Phone 8 (重要)

http://ndef.codeplex.com/ (存取NDEF必要) & PublishBinaryMessage()

Windows 8 学习笔记(八)--各种流之间的转换

Windows.Storage.Streams namespace

Calling the API PublishBinaryMessage will fail when handling NFC touch case

What is the best way to update an XAML image element with a new JPEG image?

Connecting Android and Windows 8 via NFC

10 Great Windows Phone 8 Code Samples (No, Wait… 30)

WP 8 NFC File Transfer

 

Dotblogs Tags: