WP8 - Proximity與NFC - 3

Windows Phone 8–Proximity與NFC–3

在<Windows Phone 8–Proximity與NFC–1>介紹了相關Proximity APIs的說明,並且在<Windows Phone 8–Proximity與NFC–2>

介紹如何在Device之間互相溝通,接下來這一篇,主要說明如何透過WP 8與NFC tag互相溝通的部分,在這一篇的內容裡會

提到不少與NDEF相關的說明,由於NFC tag的溝通比較特別,因此,我獨立了一篇加以說明。

 

〉NFC

    其標準涵蓋了通訊協定與資料交換格式,並基於RFID的標準發展而成。其標準包括:ISO/IEC 18902,這些經由NFC Forum

所負責定義,其Forum是由Nokia、Philips、Sony…等超過160成員所定義。採用RFID passive chipset,有以下幾類型:

Name Available memory URL length Text length NFC class WP 8 support
Ultralight C (ULC) 148 bytes 132 characters 130 characters Type 2 yes
Ultralight (UL) 48 bytes 41 characters 39 characters Type 2 yes
Topaz 512 bytes     Type 1 yes
Sony FeliCa Lite 224 bytes     Type 3 yes
Sony FeliCa 4K 4096 bytes     Type 3 yes
NTAG203 144 bytes 132 characters 130 characters Type 2 yes
MiFare 1K 1024 bytes 256 characters 709 characters Class(1) yes
Fudan F08 1024 bytes       ?
DESFire EV1 8k 8192 bytes     Type 4 yes
DESFire EV1 4k 4096 bytes     Type 4 yes
DESFire EV1 2k 2048 bytes     Type 4 yes

參考來源:<Use NFC tags with Windows Phone 8>,更多相關NFC tag資訊可參考<Nfc Tag>。

Windows Phone 8 僅支援 NDEF 格式的tag。NDEF是NFC Data Exchange Format(NDEF)訊息。相關的資訊可參考:

<Understanding NFC Data Exchange Format (NDEF) messages>。

 

[注意]

1. 如果您手邊有NFC tag,記得先對tag進行格式化

   由於目前WP8的SDK並不支援格式化的功能,建議透過其他NFC Writer或Android手機且有支持NFC

   可搭配[NFC TagWriter by NXP]軟體來使用。

 

 

2. WP8 在支援NFC上的限制

    Proximity API 只給了high level 連接NFC協定與WP在中間加了保護的機制,進一步限制了一些功能;

    ‧無法對tag有寫入lock的功能,<How can I lock / write protect NFC Tags?>;

    ‧無法格式化tag僅支援tag是符合NDEF的格式

    ‧tag只能包括NDEF message

    ‧無法使用所有tag中的memory。

        在NDEF格式化資料與私人標籤資料於記憶體中。由於事實上一個tag只有一些bytes的記憶體,資料的大小就非常重要。

    ‧Proximity API沒有工具或API直接維護NDEF message;需搭配<NDEF Library for Proximity APIs (NFC)>;

    ‧當程式處於background時,無法接收或寫入tag;

 

 

〉利用NFC來交換資訊 - device與tag之間的交易

   往下透過實例來說明WP 8設備透過Proximity API察覺有可寫入tag進行範圍、讀取其內容,以及寫入資料至該tag,

使用於寫入的協定包括:Windows、WindowsUri、WindowsMime、LaunchAapp與NDEF。

 

1.  建立一個專案,在WMAppManifest.xml加入使用Proximity API所需的<capabilities />:

     =>加入ID_CAP_PROXIMITY,如下:

<Capabilities>
  <Capability Name="ID_CAP_PROXIMITY" />
</Capabilities>

  如果不加入則在取得ProximityDevice時將會有Exception。

2. 如果應用程式對於NFC硬體需求是必要的,可至WMAppManifest.xml中的<Requirements />加上NFC的需求:

   =>加入了<Requirements />,將影響沒有支持NFC的設備不能在Store裡找到這App,也無法進行NFC功能。

<Requirements>
  <Requirement Name="ID_REQ_NFC" />
</Requirements>

3. 測試是否目前設備是否有支持Proximity API:

   主要是因為NFC非Windows Phone必要的硬件需求,因此,需透過試著取得ProximityDevice物件來判別,如下:

ProximityDevice gProximityDevice = null;
 
private void Init()
{
    gProximityDevice = ProximityDevice.GetDefault();
    if (gProximityDevice == null)
        MessageBox.Show("沒有可用Proximity硬體!");
    else
    {
        MessageBox.Show("Proximity硬體存在!");
        // 註冊監控是否有tag或device進入或離開監測的範圍
        gProximityDevice.DeviceArrived += ProximityDevice_DeviceArrived;
        gProximityDevice.DeviceDeparted += ProximityDevice_DeviceDeparted;
    }
}
 
void ProximityDevice_DeviceDeparted(ProximityDevice sender)
{
    // 代表Proximity失去監測的device/tag
}
 
void ProximityDevice_DeviceArrived(ProximityDevice sender)
{            
    // 代表Proximity發現device/tag進入監測
}

       可搭配DeviceArrivedDeviceDeparted二個事件來準確捕捉是否有device/tag進入該Proximity監測的範圍。

 

 

4. Subscribe to read a NFC tag

    要接收Device/Tag中的資料,需要使用SubscribeForMessage()並且指定一個callback handler來處理收到的訊息。

在訂閱訊息時,protocol 與 handler是非常重要的二個元素,對的protocol才能訂閱到正確的對象,handler才能有效果處理,

因為handler會收到一個ProximityMessage參數,其訊息內容均在這裡。

利用SubscribeForMessage()訂閱成功之後會回傳一個ID,要記得保存,因為之後StopSubscribeForMessage需要用來註銷訂閱

 

[注意]

‧當收到一個訊息後,程式的處理邏輯需要在ProximityDevice.DeviceArrived被解雇之前就完成這個訂閱的邏輯。

   或者是用戶需要移開這個tag,再進行一次觸發DeviceArrived。

 

在本案例裡我們訂閱幾個特定的Protocol來擷取Tag中的訊息:

Protocol Description
Windows.{sub type} 訊息包含binary data。需指定Sub type。
WindowsUri 訊息包含URI (UTF-16LE encoded string)。不需指定Sub Type。
WindowMime 訊息資料是指定的MIME type。舉例來說:
訊息資料是JPEG image,其message type值為WindowsMime.image/jpe。
訊息資料是vCard,其message type值為WindowsMime.text/x-vcard。
WriteableTag 當訂閱該協定,如果一個writeable tag進入proximity範圍,proximity message將收到一個包含
int32 (little endian) 的值,以告知該tag可寫入的最大size。
該協定只適用於subscriptions。
NDEF 訊息內容是NDEF recoard格式屬性。如果發佈者是使用NDEF為message type,則內容需為NDEF records。
一個訂閱NDEF協定,可以取得所有相關NDEF formatted messages的訊息。

 

4-0. 註冊要訂閱的協定類型

/// <summary>
/// 初始化取得的ProximityDevice
/// </summary>
private void Initialization()
{
    if (gDevice == null)
    {
       // 取得ProximityDevice類別
        gDevice = ProximityDevice.GetDefault();
        gDevice.DeviceArrived += ProximityDevice_DeviceArrived;
        gDevice.DeviceDeparted += ProximityDevice_DeviceDeparted;
 
        // 訂閱訊息
        gDevice.SubscribeForMessage("WriteableTag", WriteableTagHandler);
        gDevice.SubscribeForMessage("Windows.MySubType", mySubTypeHandler);
        gDevice.SubscribeForMessage("WindowsUri", WindowsUriHandler);
        gDevice.SubscribeForMessage("WindowsMime", WindowsMimeHandler);
 
        gDevice.SubscribeForMessage("NDEF", NDEFHandler);
    }
}

 

4-1. 處理訂閱 Windows.MySubType 的訊息

private void mySubTypeHandler(ProximityDevice sender, ProximityMessage message)
{
    // 取得binary data
    var buffer = message.Data.ToArray();
 
    // 將資料用UTF-8編輯取得.
    var data = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
 
    string s = "Windows.MySubType : \n" + data + "\n\n";
    Dispatcher.BeginInvoke(() =>
    {
        logReadNfc.Text += s;
    });
}
 
// 如遇到沒有辦法toArray()或Encoding的話,記得加入以下的參考
//extend type : ByteArray.AsBuffer()
using System.Runtime.InteropServices.WindowsRuntime;
//encoding/decoding string to ByteArray
using System.Text;

 

4-2. 處理訂閱 WindowsUri 的訊息

private void WindowsUriHandler(ProximityDevice sender, ProximityMessage message)
{
    try
    {
        var buffer = message.Data.ToArray();
        // 由於是使用UTF-16LE編輯的URI,先用unicode取回原始字串
        var sUri = Encoding.Unicode.GetString(buffer, 0, buffer.Length);
 
        // 移除null字元
        if (sUri[sUri.Length - 1] == '\0')
            sUri = sUri.Remove(sUri.Length - 1);
 
        var uri = new Uri(sUri);
        string s = "WindowsUri : \n" + uri + "\n\n";
        Dispatcher.BeginInvoke(() =>
        {
            logReadNfc.Text += s;
        });
    }
    catch (Exception e)
    {
        Dispatcher.BeginInvoke(() =>
        {
            MessageBox.Show(e.Message);
        });
    }
}

 

4-3. 處理訂閱 WindowsMime 的訊息

private void WindowsMimeHandler(ProximityDevice sender, ProximityMessage message)
{
    var buffer = message.Data.ToArray();
    int mimesize = 0;
    // search first '\0' charactere
    for (mimesize = 0;
        mimesize < 256 && buffer[mimesize] != 0;
        ++mimesize)
    {
    };
 
    //extract mimetype
    var mimeType = Encoding.UTF8.GetString(buffer, 0, mimesize);
 
    //convert data to string. This traitement depend on mimetype value.
    var data = Encoding.UTF8.GetString(buffer, 256, buffer.Length - 256);
 
    string s = "WindowsMime : " + mimeType + "\n";
    s += data + "\n\n";
 
    Dispatcher.BeginInvoke(() =>
    {
        logReadNfc.Text += s;
    });
}

      取得buffer時由於內容中包括MIME type的標籤,因此,需要先找到MIME type的總長度,再將它取出。

      另外,內容由256位元開始擷取內容。

 

4-4. 處理訂閱 WriteableTag 的訊息

private void WriteableTagHandler(ProximityDevice sender, ProximityMessage message)
{
    //convert binary to int32
    var WritableSize = System.BitConverter.ToInt32(message.Data.ToArray(), 0);
 
    Dispatcher.BeginInvoke(() =>
    {
        TagSize.Text = WritableSize.ToString() + " Bytes";
    });
}

 

4-5. 處理訂閱 NDEF 的訊息

        要處理NDEF的內容,請先至<NDEF Library for Proximity APIs (NFC)>下載專用的Library。

//NDEF message received.
private void NDEFHandler(ProximityDevice sender, ProximityMessage message)
{
    string s = "NDEF  \n";
    // 轉換 NdefMessage
    var ndefMessage = NdefMessage.FromByteArray(message.Data.ToArray());
    foreach (NdefRecord record in ndefMessage)
    {
        // 取得序列化類型,進一步識別為何種NDEF類型
        var specializedType = record.CheckSpecializedType(false);
        if (specializedType == typeof(NdefSpRecord))
        {
            // Convert and extract Smart Poster info
            var spRecord = new NdefSpRecord(record);
            s += "Type: " + spRecord.Type.ToString() + "\n";
            s += "Content: " + spRecord.Uri + "\n";
 
        }
        else if (specializedType == typeof(NdefUriRecord))
        {
            // Convert and extract Smart Poster info
            var uriRecord = new NdefUriRecord(record);
            s += "Type: " + uriRecord.Type.ToString() + "\n";
            s += "Content: " + uriRecord.Uri + "\n";
        }
        else if (specializedType == typeof(NdefTextRecord))
        {
            // Convert and extract Smart Poster info
            var textRecord = new NdefTextRecord(record);
            s += "Type: " + textRecord.Type.ToString() + " " + textRecord.LanguageCode + "\n";
            s += "Content: " + textRecord.Text + "\n";                    
        }
        else
        {
            s += "Type: " + specializedType.ToString() + "\n";
        }
        s += "\n\n";
    }
    Dispatcher.BeginInvoke(() =>
    {
        logReadNfc.Text += s;
    });
}

      Supports fully standardized basic record types:

      ‧Smart Poster

      ‧URI

      ‧Text

 

      Convenience classes for:

      ‧LaunchApp tags - launch a Windows (Phone) app just by tapping a tag

      ‧Nokia Accessories tags - let the user choose an app to launch on his Nokia Lumia Windows Phone 8 device

      ‧WpSettings tags - launch a settings page on Windows Phone 8 (e.g., Bluetooth settings, flight mode).

          Actually modifying these settings is not allowed by the security model of Windows Phone

      ‧Android Application Record (AAR) tags - launch an Android app by tapping a tag

      ‧Geo tags - longitude & latitude of a place, using different Geo URI schemes (more details)

      ‧Social tags - linking to social networks like Twitter, Facebook, Foursquare or Skype

      ‧SMS tags - defining number and body of the message

      ‧Mailto tags - sending email messages with recipient address and optional subject and body

      ‧Telephone call tags - defining the number to call

      ‧NearSpeak tags - store voice messages on NFC tags,

          using the custom URI scheme as defined by the NearSpeak app: http://www.nearspeak.at/

 

      上述這些NDEF的功能,我覺得讓Proximity API變得更豐富,可以找機會玩看看。

 

 

5. Publish to write a NFC tag

    要寫資料至tag,必需使用PublishBinaryMessage(),才能發佈資料至Tag之中;其中它有三個參數要注意一下:

‧protocol:選擇要寫入的NFC message格式;

‧data:binary data;

‧handler (optional):如果資料被寫入device/tag時會觸發,具有二個參數:ProximityDevice、Publisher ID(由PushlishbinaryMessage產生);

它跟SubscribeForMessage一樣會回傳一個ID,要記得保存,因為之後StopPublishingMessage需要用來停止發佈。

 

要發佈訊息至Tag前,先了解範例用到的有那些Protocol:

Protocol Description
Windows:WriteTag.{sub type} 與Windows協定相同,不同的是目的為寫入內容至靜態tag。
該協定其發佈的內容不會發送給devices,除了可寫入的靜態tag。
要記得加上Sub type的定義不然會有錯誤。
該協定只適用於Publication。
WindowsUri:WriteTag 與WindowsUri協定相同,不同的是目的為寫入內容至靜態tag。
該協定其發佈的內容不會發送給devices,除了可寫入的靜態tag。
不可以加上Sub type的定義不然會有錯誤。
該協定只適用於Publication。
LaunchApp:WriteTag 寫一個Tag用於啟動特定的應用程式與夾帶參數。
如果發布LaunchApp:WriteTag消息標籤當該標籤被電腦擷取時,結果和調用PeerFinder.Start的方法是一樣的,並且可以通過啟動參數。
WirteMime:WriteTag.{mime}  

更多詳細的定義可以參考<Protocol>。

 

5-0. 註冊要發佈訊息的事件邏輯

       根據不同的內容分成5-1~5-7,逐一說明各個Protocol使用的方式。

 

5-1. 使用 Windows:WriteTag.MySubType 發佈內容

// 識別用戶選擇的是那一種發佈message type。
if ("Windows.MySubType".Equals(writeMethod.SelectedItem))
{
    // 利用UTF-8進行編碼
    var buffer = Encoding.UTF8.GetBytes(NfcMessage.Text);
    publishId = gDevice.PublishBinaryMessage("Windows:WriteTag.MySubType",
                                                buffer.AsBuffer(), publishHandler);
}

        利用UTF-8編碼要發送的內容,透過PushBinaryMessage()發佈。

 

5-2. 使用 WindowsUri:WriteTag 發佈內容

if ("WindowsUri".Equals(writeMethod.SelectedItem))
{
    try
    {
        // 將輸入的文字轉成URI
        var uri = new Uri(NfcMessage.Text);
        // 對URI encoding為UTF16LE格式
   var buffer = Encoding.Unicode.GetBytes(uri.ToString());
        publishId = gDevice.PublishBinaryMessage("WindowsUri:WriteTag",
                                                    buffer.AsBuffer(), publishHandler);
    }
    catch (Exception exp)
    {
        MessageBox.Show(exp.Message);
        return;
    }
 
}

        要記得透過Encoding.Unicode.GetBytes()將字串轉成符合UTF-16LE的格式。

        對於可操作的URI類型非常多,如下表:

Action URI Format
Open a web page URI is an URL. For example http://www.nokia.com/
URI associations Custom URI can launch a specific application. You can read more information here URI associations for Windows Phone 8.
Geo tag

http://m.ovi.me/?c=<latitude>,<longitude>
[How to Store Geo Coordinates on an NFC Tag]

Launch a driving to a position

ms-drive-to:?destination.latitude=<latitude>&destination.longitude=<longitude>
How to request driving or walking directions

Dial a phone number tel:+33600000
Send a mail mailto:doctor@tardis.time
可以增加mailto後夾帶的資訊,例如:主旨、內文…等。更多詳細資訊可參考<here>。
Use Nokia music Nokia Music app-to-app protocol for Windows Phone 8
Launching built-in apps and WP8 settings URI schemes for launching built-in apps for Windows Phone 8
Dynamic Tag 產生動態的Tag,可使用HTTP URI去完成一個URI Redirection。該Redirection可能回傳一個新的URI或檔案,類似vCard、Image…等。可參考<Web Redirection Script>。

 

5-3. 使用 WindowsMime:WriteTag.text/plain 發佈內容

if ("WindowsMime.text/plain".Equals(writeMethod.SelectedItem))
{
    // 利用UTF-8進行編輯
    var buffer = Encoding.UTF8.GetBytes(NfcMessage.Text);
    publishId = gDevice.PublishBinaryMessage("WindowsMime:WriteTag.text/plain",
                                                buffer.AsBuffer(), publishHandler);
}

        需注意指定的MIME類型,如果是其他的MIME類型可參考各自MIME轉成Byte[]的做法。

 

5-4. 使用 LaunchApp:WriteTag 發佈內容

if ("LaunchApp".Equals(writeMethod.SelectedItem))
{
    // 建立符合LauchApp需要的格式:{params}\t{Platform}\t{App ID}
 
    string command = "text=" + NfcMessage.Text + "\t";
    command += "WindowsPhone" + "\t";
    command += "{" + Windows.ApplicationModel.Store.CurrentApp.AppId.ToString() + "}";
 
    var byteArray = Encoding.Unicode.GetBytes(command);
    publishId = gDevice.PublishBinaryMessage("LaunchApp:WriteTag",
                                                byteArray.AsBuffer(), publishHandler);
}

      需注意要建立符合LauchApp需要的格式:{params}\t{Platform}\t{App ID}

 

5-5. 使用 NDEF:WriteTag 寫入NdefTextRecord

// 利用NDEF Library建立NDEF message,以TextRecord為例
    var ndefRecord = new NdefTextRecord
    {
        Text = NfcMessage.Text,
        LanguageCode = CultureInfo.CurrentCulture.TwoLetterISOLanguageName
    };
    var ndefMessage = new NdefMessage { ndefRecord };
 
    //publish binary NDEF message.
    publishId = gDevice.PublishBinaryMessage("NDEF:WriteTag",
                                            ndefMessage.ToByteArray().AsBuffer(), publishHandler);

 

5-6. 實作負責處理PublishBinaryMessage()的callback handler

private void publishHandler(ProximityDevice sender, long messageId)
{
    // 標籤已寫入資訊,停止發佈。
    gDevice.StopPublishingMessage(publishId);
    publishId = -1;
    Dispatcher.BeginInvoke(() =>
    {
        progressWrite.IsVisible = false;
        MessageBox.Show("Write tag succed");
    });
}

 

5-7. 註冊要處理如果是LaunchApp:WriteTag的參數所開啟程式的功能

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
 
    // 處理NFC Tag帶進來的參數,關鍵字:ms_nfp_launchargs。
    string parameter = string.Empty;
    if (NavigationContext.QueryString.TryGetValue("ms_nfp_launchargs", out parameter))
    {
        MessageBox.Show("Congratulation\nYou launch application with a NFC tag.\nParamaters : " + parameter);
        NavigationContext.QueryString.Remove("ms_nfp_launchargs");
    }
}

        需注意關鍵參數:ms_nfp_launchargs。才能取得我們傳遞的內容。

 

[範例程式]

下載後,請切換WMAppManifest.xml中的DefaultTask為NfcPage.xaml。程式範例內容以擷錄<Media:NFCTag.zip>的內容進行改寫而成。

 

[補充]

‧NDEF Message – Social Record

private void PublishSocialNDEF()
{
   var ndefsRecord = new NdefSocialRecord
   {
        SocialType= NdefSocialRecord.NfcSocialType.Facebook,
        SocialUserName = "pou629"                             
   };
   ndefMessage = new NdefMessage { ndefsRecord }; 
   publishId = gDevice.PublishBinaryMessage("NDEF:WriteTag",
                                ndefMessage.ToByteArray().AsBuffer(), publishHandler);
}

 

‧NDEF Message – Mailto Record

pprivate void PublishMailTo()
{
    var record = new NdefMailtoRecord
    {
        Address = "poumason@live.com",
        Subject = "Feedback for the NDEF Library",
        Body = NfcMessage.Text
    };
    ndefMessage = new NdefMessage { record };
    publishId = gDevice.PublishBinaryMessage("NDEF:WriteTag",
                    ndefMessage.ToByteArray().AsBuffer(), publishHandler);
}

======

以上是分享操作Proximity API去修改NFC Tag的方法,並且補充了操作NDEF Library來增加寫入資訊的豐富度。

希望對大家在了解NFC與Proximity API上有一定的幫助。謝謝。

 

References:

Use NFC tags with Windows Phone 8 (重要)

Using NFC to exchange information (重要)

Understanding NFC Data Exchange Format (NDEF) messages (重要)

NDEF Library for Proximity APIs (NFC) (重要)

How to Store Application Data on NFC Tags

vCard Class Library & vCard Library for Windows Phone

Trouble with MemoryStream and StreamWriter & How to use C# Textreader Class & TextWriter Class
    & StreamWriter Class

File-NFC WP8 sample app.zip

Media:NFCTag.zip

 

Dotblogs Tags: