iOS 11 NFC技術

MobService發表於2019-05-21

前言

NFC這個詞相信大家現在都已經不陌生了,各大城市的地鐵、商場等等支援NFC支付一度成為頭條的熱點。其實很早之前就已經有二維碼和NFC的誕生了,但是由於二維碼成本低廉,技術門檻相對較低,因此,二維碼迅速搶佔了移動支付的市場,但是,與此同時NFC的發展並未因此停止。


其實在安卓端的NFC發展已經非常迅猛了,只是我們的蘋果爸爸遲遲不肯帶我們一起玩NFC。終於,在去年的WWDC上,蘋果宣佈“開放”其NFC介面,這為以後NFC的應用開發提供了更多的可能。


關於NFC

NFC(Near Field Communication)近場通訊,當兩個裝置相互靠近時能進行資訊交流的一個技術。


使用了NFC技術的裝置(比如手機)可以在彼此靠近的情況下進行資料交換,是由非接觸式射頻識別(RFID)及互連互通技術整合演變而來,通過在單一晶片上整合感應式讀卡器、感應式卡片和點對點通訊的功能,利用移動終端實現移動支付、電子票務、門禁、移動身份識別、防偽等應用。目前,蘋果的CoreNFC對NFC的格式支援有限,暫時僅支援NDEF格式。


關於NDEF

NDEF(NFC Data Exchange Format)是一種能夠在NFC裝置或者標籤之間進行資訊交換的資料格式。NDEF格式由各種 NDEF Messages 和 NDEF Records 組成。NDEF格式使用了一種容易理解的格式來儲存和交換資訊,如:URI、純文字等等。


NFC標籤,像Mifare Classic卡片可以配置為NDEF標籤,通過一個NFC裝置寫入的資料可以被其他NDEF相容的裝置訪問。NDEF訊息還可以用於兩個活躍的NFC裝置之間“點對點”模式交換資料。

iOS 11 NFC技術


NDEF Messages

NDEF Messages是NDEF Records交換機制的基礎,每一個message包含一個或多個records。


NDEF Records

NDEF Records包含一個特定的payload,並且有以下結構來標識內容和記錄大小:

iOS 11 NFC技術

Record Header(記錄頭)

記錄頭包含了很多重要的資訊,它佔用3個位來標識遵循TNF協議的記錄的型別,講人話就是這3個位用來表示這條記錄的型別的。


TNF: Type Name Format 欄位

一條NDEF記錄的型別名稱是一個3個位的數值,用來描述這條記錄的型別,並且可以用來設定對該記錄中其它的結構和內容的期望。簡單的說就是這3個位不僅可以表示該條記錄的型別,也可以在一定程度上決定了該條記錄接下來的資料結構。可能的記錄名稱如下表:

TNF Value


Record Type

0x00

Empty Record 表明這條記錄沒有型別、id或有效payload。這個記錄型別一般用於新格式化的NDEF卡上,因為NDEF標籤必須有至少一個NDEF紀錄。

0x01

Well-Known Record 表明記錄型別欄位使用RTD型別名稱格式。這種型別名稱用一個Record Type Definition (RTD)來儲存任何指定的型別,例如:儲存RTD文字、RTD URIs等等。同時,這是一種比較常用的也比較有用的記錄型別。

0x02

MIME Media Record 表明payload是這條NDEF記錄分塊的中間或者最後一塊。

0x03

Absolute URI Record 表明這條記錄的型別欄位一定包含一個URI欄位。

0x04

External Record 表明這條記錄的型別欄位包含一個RTD格式的外部欄位。

0x05

Unknown Record 表明payload的型別未知。

0x06

Unchanged Record 未發生變化的記錄型別,釋同MIME Media Record。


IL:ID Length 欄位

IL是ID長度的標誌位,用來表示下面的ID Length欄位是否省略。如果這個位設定為0,則該條記錄中省略ID Length。


SR:Short Record 位

短記錄標誌位,如果下面的PAYLOAD LENGTH欄位小於等於一個位元組,則該位設定為1 。


CF:Chunk Flag

塊標識位,用於標識當前塊是第一個記錄塊還是中間的記錄塊。


ME:Message End

結束標誌位,用於標識當前記錄是否是當前Message的最後一條記錄。


MB:Message Begin

起始標誌位,用於標識當前記錄是否是當前Message的第一條記錄。


Type Length

表示型別欄位的長度。對於上面TNF欄位描述中的某些值,該欄位一直為0 。


Payload Length

表示該條記錄的payload欄位長度。如果上面的SR欄位設定為1,則該欄位佔用1個位元組的長度,但是如果SR設定為0,則該欄位將有32個位,佔用4個位元組的長度。


ID Length

表示該條記錄的ID的長度。只有當上面的IL位置1時該欄位才會被省略。


Record Type

表示記錄的型別,這個欄位的值必須根據TNF位的設定確定。


Record ID

表示該條記錄的ID。當IL位為0時,該欄位省略。


Payload

表示該條記錄的payload,該欄位的長度務必與上面的Payload Length欄位值一致。


關於 Well-Known Records 和 URI Records


首先要說的就是這兩個概念的區別,Well-Known Records(TNF Record Type 0x01) 是最常用也是最有用的NFC記錄型別,它是寫在上面說到的TNF欄位的三個位裡的,它描述的是當前這條NDEF記錄的整體型別,相當於一個總的架構決策。


URI Records(0x55/‘U’)是比較有用的資料型別,它是寫在上面 Recode Type 欄位的一個位元組(8個位)裡的,它描述的是這條NDEF記錄攜帶的資料資訊型別,簡單的說就是這條記錄攜帶了什麼樣的資訊。


這裡,關於這個URI Records我要多說幾句,這個型別可以用來儲存例如電話號碼、網站地址以及各種協議的連結等等很多有用的資訊,它的結構定義如下:

iOS 11 NFC技術

第一個位元組表示該型別的識別碼,這個識別碼的主要是用於縮短URI的長度,它的有效值詳見下表:

iOS 11 NFC技術

後面的N個位元組就是用來表示一個URI去掉前面識別碼之後剩餘的部分,舉個例子:例如我們要將 https://www.mob.com 寫入,則在第一個位元組裡我們要寫入的是 0x02,表示 https://www.,接下來要連續寫入的就是 0x6D 0x6F 0x62 0x2E 0x63 0x6F 0x6D (詳細請參考:ASCII碼對照表http://ascii.911cha.com/)


以上所有內容就是關於NDEF資料格式的詳細說明了,那麼這裡也很不幸的告訴大家,我們平時直接從某寶、某東或者某某某上買來的NFC卡片(俗稱:白卡)都不會是NDEF格式的,所以。。。


將 Mifare Classic Cards 用作 NDEF 標籤


Mifare Classic 1K和4K的卡可以被初始化為NFC的NDEF格式標籤。關於Mifare的詳細介紹請各位老闆參見:Mifare維基百科 Mifare Classic 可以配置為NFC論壇相容的NDEF標籤,但必須以某種特定的方式組織它裡面的資料才可以。具體要求可以參考如下資料:


AN1304 - NFC Type MIFARE Classic Tag Operation

https://www.nxp.com/docs/en/application-note/AN1304.pdf


上面的是使用手冊的權威來源,下面將快速的介紹一下將 Mifare Classic Cards 用作 NDEF 標籤所涉及的關鍵概念。


1. Mifare Application Directory (MAD)

Mifare應用程式的目錄,為了在Mifare Classic卡片的扇區記憶體與單個NDEF記錄之間建立關係而存在。MAD表明了哪個扇區包含著哪個NDEF記錄。關於Mifare應用程式目錄的權威資訊來源如下:


AN10787 - MIFARE Application Directory (MAD)

https://www.nxp.com/docs/en/application-note/AN10787.pdf


為了相容性考慮,官方根據卡片記憶體的大小定義了兩種不同型別的MAD,簡單說明一下區別,MAD1可以用在任何卡片上,而MAD2只能用在記憶體大於1K位元組的卡片上。


2. 儲存 NDEF Messages

為了在Mifare Classic卡上儲存NDEF訊息,訊息需要被封裝在一個叫做 TLV 塊 的東西里面。關於 TLV 塊 的基本結構描述如下:

TLV 是三個不同維度的縮寫:T:Tag Field 標籤L:Length Field 長度V:Value Field 數值


一個 TLV 塊 由一個或多個位元組組成,這取決於上面三個維度的存在情況,但至少有一個位元組,因為 T 欄位在每種情況下都是強制性的。


Tag Field

標籤欄位是唯一必填的欄位,使用單個位元組來標識 TLV 塊 的型別,有效值如下:


Length Field

長度欄位包含了數值欄位的長度(位元組),它可以使用一個或三個位元組兩種不同的方式表示:一個位元組格式就是簡單0x00~0xFF的一個位元組數值;三個位元組格式的組成如下:

iOS 11 NFC技術

Value Field

數值欄位僅在長度欄位存在且不等於0x00時才存在,這個欄位就是 payload 的儲存位置。


Terminator TLV

終止符,是資料區域中的最後一個TLV 塊,固定的單個位元組:0xFE,這個TLV 塊也是是強制性的。


3. 一個帶有NDEF記錄的Mifare Classic 卡片的記憶體示例

iOS 11 NFC技術

上圖示例中在扇區1包含了兩個NDEF記錄:


第一個記錄在扇區1塊4的前兩個位元組:根據上面的描述,每個記錄都以TLV 塊開頭,並且TLV 塊的第一個位元組(值0x00)指示這是一個空塊型別,第二個位元組是長度欄位,並且也是0x00,因此該記錄沒有payload,TLV 塊的值欄位不存在。當Mifare Classic卡首次格式化以確保至少有一條記錄存在時,一般會插入此記錄。


第二個記錄從扇區1塊4的第3個位元組開始,到塊5的第6個位元組結束:

iOS 11 NFC技術

同樣,對於前兩個位元組,根據TLV 塊的描述,第一個位元組 0x03 表示這是一個NDEF Message型別,第二個位元組 0x11 表示該塊的資料長度是17個位元組。對於接下來17個位元組的分析如下表:


Byte(s)

Value

Description

04:04

0xD1

NDEF記錄的記錄頭,詳細解釋參考上面的NDEF記錄描述部分。位分配如下:

TNF = 0x01 表示這是一個Well-Known型別的記錄

IL = 0 表示沒有ID欄位

SR = 1 表示這是一個短記錄

CF = 0 表示這個塊不是第一塊

ME = 1 表示當前記錄為最後一條記錄

MB = 1 表示Message的開始

04:05

0x01

NDEF記錄型別的長度,1個位元組,因為下面的記錄型別值是0x55

04:06

0x0D

表示payload的長度,13個位元組

04:07

0x55

NDEF記錄的型別,0x55表示URI型別

04:08

0x01

表示payload開始,後面的將是payload中的內容

04:09..05:04

...

payload內容的16進位制資料

最後一個位元組,0xFE 是TLV 塊的終止符,表示這個塊的結束,這個沒什麼好說的了~~


相關參考連結:

•NXP官方網站

https://www.nxp.com/

•NFC論壇

https://nfc-forum.org/

•Mifare Classic S50 技術詳解

http://www.cnblogs.com/SCPlatform/p/5116180.html


iOS CoreNFC

下面將通過一個簡單的示例來演示怎麼使用蘋果爸爸推出的CoreNFC,該示例僅可以用來讀取儲存在卡片上的NDEF格式的資訊。


為此,我使用了STM32F407微控制器與Adafruit PN532 ShieldNFC讀寫模組配對,將資訊寫入NDEF格式的卡片上。本文中,我不會記錄如何將普通的Mifare Classic卡片格式化成NDEF格式的卡片以及如何將資料寫入到NDEF卡片中(iOS的CoreNFC暫時不支援寫入)。


我們的app要使用NFC必須要進行應用授權:首先要建立一個支援NFC的證書,並且開啟NFC Tag Reading,如下圖:

iOS 11 NFC技術

匯入證書之後,我們需要進行Info.plist配置Privacy - NFC Scan Usage Description許可權,如下圖:

iOS 11 NFC技術

要實現NFC功能,我們得先匯入CoreNFC.framework,並匯入其標頭檔案#import目前為止,iOS模擬器還不支援CoreNFC,只能使用真機除錯。

1. 初始化Session

與二維碼掃描等類似,NFC 也具備一個用於資訊互動的Session,並且這個Session要在使用期間一直持有,所以初始化Session程式碼如下:

@interface NFCTableViewController ()<NFCNDEFReaderSessionDelegate>/** NFC Session */@property (nonatomic, strong) NFCNDEFReaderSession *nfcSession;/** founded NFC Messages */@property (nonatomic, strong) NSMutableArray<NSArray<NFCNDEFMessage *> *> *nfcMessages;@end@implementation NFCTableViewController#pragma mark - initializeNFC

- (void)initializeNFCSession {    // 建立 Session
    self.nfcSession = [[NFCNDEFReaderSession alloc] initWithDelegate:self queue:dispatch_get_main_queue() invalidateAfterFirstRead:NO];    // 設定 Session 的提示資訊
    self.nfcSession.alertMessage = @"You can scan NFC-tags by holding them behind the top of your iPhone.";
}@end複製程式碼

2. 啟動Session

經過上面的初始化之後,有了Session就可以開啟監聽了,啟動監聽很簡單,示例程式碼如下:

- (IBAction)startSearchBtnClick:(UIBarButtonItem *)sender {    NSLog(@"%s", __func__);    // 啟動Session
    [self.nfcSession beginSession];
}複製程式碼

Session啟動時會自動調出系統的掃描皮膚,如下圖:

3. 監聽代理回撥

通過上面的方式把Session啟動之後,裝置就會自動開始掃描NDEF格式的標籤資訊,當掃描到標籤資訊,或者發生任何異常時都會通過代理方法回撥,所以我們需要監聽Session的回撥資訊,示例如下:

#pragma mark - NFCNDEFReaderSessionDelegate/*! * @method readerSession:didInvalidateWithError: * * @param session   The session object that is invalidated. * @param error     The error indicates the invalidation reason. * * @discussion      Gets called when a session becomes invalid.  At this point the client is expected to discard *                  the returned session object. *//** NFC 讀取的session發生錯誤或session過期時回撥,此時客戶端應當丟棄返回的session,即此session不可重用。 @param session 發生錯誤的session @param error 錯誤資訊 */- (void)readerSession:(NFCNDEFReaderSession *)session didInvalidateWithError:(NSError *)error {    if (error)
    {        NSLog(@"NFC-Session invalidated: %@", error);
    }    if (self.nfcSession)
    {        // 關閉當前session
        [self.nfcSession invalidateSession];
        self.nfcSession = nil;
    }    // 重新初始化一個新的session
    [self initializeNFCSession];
}/*! * @method readerSession:didDetectNDEFs: * * @param session   The session object used for tag detection. * @param messages  Array of @link NFCNDEFMessage @link/ objects. The order of the discovery on the tag is maintained. * * @discussion      Gets called when the reader detects NFC tag(s) with NDEF messages in the polling sequence.  Polling *                  is automatically restarted once the detected tag is removed from the reader's read range. *//** 當NFC Session在輪詢佇列中讀取到NDEF資訊時回撥,此時輪詢會自動重啟一次再次檢測NFC標籤是否離開了讀取範圍。(原理類似於機械按鍵的防抖動) @param session 讀取Session @param messages 讀取到的資訊 */- (void)readerSession:(NFCNDEFReaderSession *)session didDetectNDEFs:(NSArray<NFCNDEFMessage *> *)messages {    NSLog(@"New NFC Messages %zd detected:", messages.count);    for (NFCNDEFMessage *message in messages) {        NSLog(@"- %zd Records:", message.records.count);        for (NFCNDEFPayload *record in message.records) {            NSLog(@"\t- TNF(TypeNameFormat): %@", [self formattedTypeNameFormat:record.typeNameFormat]);            NSLog(@"\t- Payload: %@", [[NSString alloc] initWithData:record.payload encoding:NSUTF8StringEncoding]);            NSLog(@"\t- Type: %@", [[NSString alloc] initWithData:record.type encoding:NSUTF8StringEncoding]);            NSLog(@"\t- Identifier: %@", [[NSString alloc] initWithData:record.identifier encoding:NSUTF8StringEncoding]);
        }
    }
    
    [self.nfcMessages addObject:messages];    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.tableView reloadData];
    });
}複製程式碼


相關文章