[NFC] 读羊城通卡片信息
學(xué)習(xí)開(kāi)源項(xiàng)目NFCard,最新版源碼以及幾年前代碼相比較,發(fā)現(xiàn)之前的版本可以支持讀羊城通,而現(xiàn)在不再支持讀羊城通卡信息,那定一個(gè)小目標(biāo)。通過(guò)NFC讀取羊城通卡片信息之余額和交易記錄。
實(shí)現(xiàn)的效果如圖:
目錄
1.建立工程,編寫(xiě)NFC相關(guān)代碼;
2.根據(jù)開(kāi)源項(xiàng)目中的指令,讀取余額;
3.根據(jù)開(kāi)源項(xiàng)目中的指令,讀取交易記錄;
4.根據(jù)卡片原始信息解析數(shù)據(jù);
一、編寫(xiě)NFC相關(guān)代碼
import android.app.PendingIntent;import android.content.Intent;import android.nfc.NfcAdapter;import android.nfc.Tag;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;public class MainActivity extends AppCompatActivity {private NfcAdapter mNfcAdapter;private PendingIntent mPendingIntent;private Intent mIntent;private final int READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK;private NfcAdapter.ReaderCallback mReaderCallback = new NfcAdapter.ReaderCallback() {@Overridepublic void onTagDiscovered(Tag tag) {System.out.println(tag.toString());}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}@Overrideprotected void onStart() {super.onStart();initNfc();}@Overrideprotected void onResume() {super.onResume();registerNfc();}@Overrideprotected void onPause() {super.onPause();unRegisterNfc();}private void initNfc() {mNfcAdapter = NfcAdapter.getDefaultAdapter(this);mIntent = new Intent(NfcAdapter.ACTION_TECH_DISCOVERED);mIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);mPendingIntent = PendingIntent.getActivity(this, 0, mIntent, 0);}private void registerNfc() {Bundle bundle = new Bundle();mNfcAdapter.enableReaderMode(this, mReaderCallback, READER_FLAGS, bundle);}private void unRegisterNfc() {mNfcAdapter.disableReaderMode(this);}}運(yùn)行上述代碼的效果:(羊城通緊貼在手機(jī)NFC感應(yīng)處)
I/System.out: TAG: Tech [android.nfc.tech.IsoDep, android.nfc.tech.NfcA]看源碼解釋一下:
注冊(cè)NFC調(diào)用了NfcAdapter的enableReaderMode方法,先看看源碼:
三行代碼三大段注釋,nice。enableReaderMode()摳腳翻譯如下:
限制NFC的模式為讀卡器模式
在這種模式中,就僅僅是可以用NFC讀寫(xiě)帶有NFC芯片的標(biāo)簽(卡片、貼紙等),不允許點(diǎn)對(duì)點(diǎn)模式和卡模擬模式。
可以通過(guò)FLAG_READER_SKIP_NDEF_CHECK這個(gè)標(biāo)志過(guò)濾NDEF標(biāo)簽,這個(gè)就是標(biāo)志就是第三個(gè)參數(shù)啦,NDEF(NFC Data Exchange Format,NFC數(shù)據(jù)交換格式)是Android SDK API主要支持NFC論壇標(biāo)準(zhǔn)。
如果是準(zhǔn)備與另一臺(tái)Android卡模擬設(shè)備交互,那么建議設(shè)置的標(biāo)志就是FLAG_READER_NFC_A和FLAG_READER_SKIP_NDEF_CHECK
參數(shù)callback:發(fā)現(xiàn)符合的標(biāo)簽就回調(diào)到callback
參數(shù)extras : 對(duì)讀卡器模式進(jìn)行一些配置(先晾它一會(huì),目前只是傳了一個(gè)空bundle進(jìn)去)
參數(shù)flags:標(biāo)志
上述參數(shù)中有一個(gè)flags,我們順便也看看有哪些flag以及flag的作用是什么,看源碼然后摳腳解釋下:
/*** Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.* <p>* Setting this flag enables polling for Nfc-A technology.*/public static final int FLAG_READER_NFC_A = 0x1;/*** Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.* <p>* Setting this flag enables polling for Nfc-B technology.*/public static final int FLAG_READER_NFC_B = 0x2;/*** Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.* <p>* Setting this flag enables polling for Nfc-F technology.*/public static final int FLAG_READER_NFC_F = 0x4;/*** Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.* <p>* Setting this flag enables polling for Nfc-V (ISO15693) technology.*/public static final int FLAG_READER_NFC_V = 0x8;/*** Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.* <p>* Setting this flag enables polling for NfcBarcode technology.*/public static final int FLAG_READER_NFC_BARCODE = 0x10;/*** Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.* <p>* Setting this flag allows the caller to prevent the* platform from performing an NDEF check on the tags it* finds.*/public static final int FLAG_READER_SKIP_NDEF_CHECK = 0x80;/*** Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.* <p>* Setting this flag allows the caller to prevent the* platform from playing sounds when it discovers a tag.*/public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 0x100;| FLAG_READER_NFC_A | 0x1 | 支持NFCA數(shù)據(jù)格式 |
| FLAG_READER_NFC_B | 0x2 | 支持NFCB數(shù)據(jù)格式 |
| FLAG_READER_NFC_F | 0x4 | 支持NFCF數(shù)據(jù)格式 |
| FLAG_READER_NFC_V | 0x8 | 支持NFCV數(shù)據(jù)格式 |
| FLAG_READER_NFC_BARCODE | 0x10 | 支持NFCBARCODE數(shù)據(jù)格式 |
| FLAG_READER_SKIP_NDEF_CHECK | 0x80 | 過(guò)濾NDEF數(shù)據(jù)格式 |
| FLAG_READER_NO_PLATFORM_SOUNDS | 0x100 | 關(guān)閉發(fā)現(xiàn)TAG時(shí)的聲音 |
看完上述,估計(jì)有點(diǎn)蒙,好像也有點(diǎn)跑偏,趕緊回到注冊(cè)NFC的這個(gè)方法中,我們?cè)趏nResume中調(diào)用了enableReaderMode,此方法在卡片(此處指羊城通)貼到手機(jī)NFC感應(yīng)處時(shí)會(huì)回調(diào)到ReaderCallback中,所以我們在onTagDiscovered這個(gè)回調(diào)中即可與卡片進(jìn)行交互。
二、根據(jù)指令讀取羊城通余額
首先我們可以去交通信息中心下載一份《城市公共交通IC卡技術(shù)規(guī)范》卡片的部分,認(rèn)真去閱讀(一頭扎進(jìn)去估計(jì)難看懂),我們知道選擇目錄的指令為:00A40400+lc+文件名+00;讀取余額的指令為:805C000204(指令為7816報(bào)文格式)
其次我們可以去閱讀NFCard這個(gè)開(kāi)源項(xiàng)目,從源碼中知道,選擇的文件名為:”P(pán)AY.TICL”,lc為:08
到此,我們整理下所需指令:
| 00A40400085041592E5449434C00 | 選擇PAY.TICL目錄(P的十六進(jìn)制ASCII碼為50) |
| 805C000204 | 讀取余額 |
上述拼接指令過(guò)程中,需要把字符換成對(duì)應(yīng)的十六進(jìn)制ASCII碼,好在Google的Sample給我們提供了這些轉(zhuǎn)換方法,恩,又可以抄一波(具體Sample路徑:”sdk根目錄”\samples\android-“version”\connectivity\CardReader..\LoyaltyCardReader.java):
/*** Utility class to convert a byte array to a hexadecimal string.** @param bytes Bytes to convert* @return String, containing hexadecimal representation.*/public static String ByteArrayToHexString(byte[] bytes) {final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};char[] hexChars = new char[bytes.length * 2];int v;for ( int j = 0; j < bytes.length; j++ ) {v = bytes[j] & 0xFF;hexChars[j * 2] = hexArray[v >>> 4];hexChars[j * 2 + 1] = hexArray[v & 0x0F];}return new String(hexChars);}/*** Utility class to convert a hexadecimal string to a byte string.** <p>Behavior with input strings containing non-hexadecimal characters is undefined.** @param s String containing hexadecimal characters to convert* @return Byte array generated from input*/public static byte[] HexStringToByteArray(String s) {int len = s.length();byte[] data = new byte[len / 2];for (int i = 0; i < len; i += 2) {data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)+ Character.digit(s.charAt(i+1), 16));}return data;}有了卡片,有了卡片指令,我們就開(kāi)始通過(guò)NFC進(jìn)行交互
回到我們NFC的回調(diào)方法中,我們可以從回調(diào)方法onTagDiscovered(Tag tag)拿到一個(gè)TAG,這個(gè)TAG從輸出的日志看,有IsoDep,通過(guò)IsoDep類(lèi)的transceive方法即可發(fā)送指令數(shù)據(jù)到卡片并且返回響應(yīng)數(shù)據(jù):
具體數(shù)據(jù)為:
I/System.out: 指令報(bào)文:00A40400085041592E5449434C00 響應(yīng)報(bào)文:6F3484085041592E5449434CA5289F0801029F0C21FFFFFFFFFFFFFFFF000000000000000000000000000000002016122400000186A09000 I/System.out: 指令報(bào)文:805C000204 響應(yīng)報(bào)文:00000E479000根據(jù)7816報(bào)文格式,響應(yīng)報(bào)文格式為DATA+SW1+SW2,SW1和SW2為狀態(tài)字,分別占一個(gè)字節(jié),由此DATA=00000E47,SW1=90,SW2=00。00000E47轉(zhuǎn)十進(jìn)制則是3655,這個(gè)時(shí)候我們用QQ來(lái)讀一下羊城通對(duì)比一下余額是否正確,QQ讀出余額如下圖:
我們使用的transceive方法,將原始數(shù)據(jù)發(fā)送至卡片標(biāo)簽,并且得到響應(yīng),如果中途移開(kāi)卡片,則會(huì)拋出TagLostException(也是繼承IOException),如果中途讀寫(xiě)失敗或者取消,則拋出IOException。
/*** Send raw ISO-DEP data to the tag and receive the response.** <p>Applications must only send the INF payload, and not the start of frame and* end of frame indicators. Applications do not need to fragment the payload, it* will be automatically fragmented and defragmented by {@link #transceive} if* it exceeds FSD/FSC limits.** <p>Use {@link #getMaxTransceiveLength} to retrieve the maximum number of bytes* that can be sent with {@link #transceive}.** <p>This is an I/O operation and will block until complete. It must* not be called from the main application thread. A blocked call will be canceled with* {@link IOException} if {@link #close} is called from another thread.** <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.** @param data command bytes to send, must not be null* @return response bytes received, will not be null* @throws TagLostException if the tag leaves the field* @throws IOException if there is an I/O failure, or this operation is canceled*/public byte[] transceive(byte[] data) throws IOException { return transceive(data, true);}小結(jié):
我們通過(guò)開(kāi)源項(xiàng)目NFCard、Google的Sample之CardReader、交通信息中心的《城市公共交通IC卡技術(shù)規(guī)范》文檔,成功讀出了羊城通余額。
這里需要提醒的是,最新版的NFCard源碼讀不出我手中的羊城通,顯示為未知卡片,反而找到2013年的版本才讀出來(lái),本人手中的卡有效期也是2013年-2018年。這樣估計(jì)是各版本的卡有所不同。
接下來(lái)就是讀取卡片的卡號(hào)、有效期、交易記錄這些信息,并且解析數(shù)據(jù),顯示在界面上。
總結(jié)
以上是生活随笔為你收集整理的[NFC] 读羊城通卡片信息的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 初识beego
- 下一篇: 用C语言实现简单的一字棋游戏