3.非標准的NDEF格式數據解析--IsoDep


1.使用目的:正常開發是針對NDEF格式數據進行開發,但實際情況並非如此,以廈門公交卡為例,廈門公交卡保存的是非NDEF格式數據。其類型是IsoDep類型。

2.非標准的NDEF格式數據流程:當廈門公交卡放到NFC上時,手機會捕獲該廈門公交卡標簽信息,自動獲得該tag能支持的技術支持,其中標簽的數據將封裝到Intent中,並啟動相關的Activity處理該標簽信息,判斷該標簽類型為IsoDep類型后,使用該IsoDep類對標簽進行操作。

You can use the getTechList() method to determine the technologies supported by the tag and create the corresponding TagTechnology object with one of classes provided by android.nfc.tech

你可以使用getTechList()方法來確定該tag能支持的技術,創建由android.nfc.tech包中提供的能與之匹配的的TagTechnology對象類。

NFC針對非標准的NDEF格式數據使用步驟:

1.獲取NFC權限/添加Intent過濾器

2.獲取NFC適配器

3.捕獲NFC Intent

4.處理該Intent(獲取信息Tag)--> 判斷標簽類型為IsoDep類型,並執行相關操作

5.IsoDep類的核心函數: transceive(byte[]) 函數。通過該函數發送相關指令,得到返回值。-- 需要了解底層的指令

例如:

底層的指令:

通過NFC讀取公交卡的余額和交易記錄

讀取分四個步驟:
1.select PSF (1PAY.SYS.DDF01)
選擇支付系統文件,它的名字是1PAY.SYS.DDF01。

byte[] DFN_PSE = { (byte) '1', (byte) 'P',
(byte) 'A', (byte) 'Y', (byte) '.', (byte) 'S', (byte) 'Y',
(byte) 'S', (byte) '.', (byte) 'D', (byte) 'D', (byte) 'F',
(byte) '0', (byte) '1', };

2.選擇公交卡應用的名字或者ID

長安通:
byte[] DFN_SRV = { (byte) 0xA0, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x86, (byte) 0x98,
(byte) 0x07, (byte) 0x01, };

3.讀取余額 
發送命令讀取電子錢包的余額: 

final byte[] cmd = { (byte) 0x80, // CLA Class 
(byte) 0x5C, // INS Instruction 
(byte) 0x00, // P1 Parameter 1 
(byte) 0x02, // P2 Parameter 2 
(byte) 0x04, // Le 
}; 

獲取到的余額數據是byte[] data, 前4字節合並成int,再除以100(兩個小數點),得到的結果就是余額。

4.讀取交易記錄 

一次性讀取命令,在不知道有多少條記錄的時候,用這個命令: 

byte[] cmd = { (byte) 0x00, // CLA Class 

(byte) 0xB2, // INS Instruction 

(byte) 0x01, // P1 Parameter 1 

(byte) 0xC5, // P2 Parameter 2 

(byte) 0x00, // Le 

}; 

返回所有的記錄byte[] data,每23個字節代表一條記錄 

也可以一條一條的讀取: 

cmd = { (byte) 0x00, // CLA Class 

(byte) 0xB2, // INS Instruction 

(byte) index, // P1 Parameter 1 

(byte) 0xC4, // P2 Parameter 2 

(byte) 0x00, // Le 

}; 

一條記錄是23個字節byte[] data,對其解碼如下 

data[0]-data[1]:index 

data[2]-data[4]:over,金額溢出? 

data[5]-data[8]:交易金額 

data[9]:如果等於0x06或者0x09,表示刷卡;否則是充值 

data[10]-data[15]:刷卡機或充值機編號 

data[16]-data[22]:日期String.format("%02X%02X.%02X.%02X %02X:%02X:%02X",data[16], data[17], data[18], data[19], data[20], data[21], data[22]); 

實例解析:

1.獲取NFC權限

    <uses-permission android:name="android.permission.NFC" />

添加Intent過濾器

<activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.nfc.action.TECH_DISCOVERED" />
            </intent-filter>
            <meta-data android:name="android.nfc.action.TECH_DISCOVERED"
                android:resource="@xml/nfc_tech_filter" />
            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
View Code

nfc_tech_filter.xml

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">

    <tech-list>
        <tech>android.nfc.tech.IsoDep</tech>
    </tech-list>
    <!-- 以下只是顯示怎么添加多個nfc支持類 -->
    <tech-list>
        <tech>android.nfc.tech.NfcV</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.NfcF</tech>
    </tech-list>

</resources>
View Code

2.獲取NFC適配器

private NfcAdapter nfcAdapter; // NFC適配器
...
......
        // 獲取默認的NFC控制器,並進行判斷
        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (nfcAdapter == null) {
            Log.d("h_bl", "設備不支持NFC!");
            return;
        }
        if (!nfcAdapter.isEnabled()) {
            Toast.makeText(getApplicationContext(), "請在系統設置中先啟用NFC功能!", Toast.LENGTH_SHORT).show();
            Log.d("h_bl", "請在系統設置中先啟用NFC功能!");
            return;
        }

3.捕獲NFC Intent

        Intent intent = this.getIntent(); // 捕獲NFC Intent
        String nfcAction = intent.getAction(); // 解析該Intent的Action

4.處理該Intent(獲取信息Tag) -- 廈門公交卡為IsoDep類,詳見IsoDep類的使用。

        if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(nfcAction)) {
            Log.d("h_bl", "ACTION_TECH_DISCOVERED");
            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);  // 獲取Tag標簽,既可以處理相關信息
            for (String tech : tag.getTechList()) {
                Log.d("h_bl", "tech=" + tech);
            }
            IsoDep isoDep = IsoDep.get(tag);
            String str = "";
            try {
                isoDep.connect(); // 連接
                if (isoDep.isConnected()) {
                    Log.d("h_bl", "isoDep.isConnected"); // 判斷是否連接上
                    // 1.select PSF (1PAY.SYS.DDF01)
                    // 選擇支付系統文件,它的名字是1PAY.SYS.DDF01。
                    byte[] DFN_PSE = { (byte) '1', (byte) 'P', (byte) 'A', (byte) 'Y', (byte) '.', (byte) 'S', (byte) 'Y', (byte) 'S', (byte) '.', (byte) 'D', (byte) 'D', (byte) 'F', (byte) '0', (byte) '1', };
                    isoDep.transceive(getSelectCommand(DFN_PSE));
                    // 2.選擇公交卡應用的名稱
                    byte[] DFN_SRV = { (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x86, (byte) 0x98, (byte) 0x07, (byte) 0x01, };
                    isoDep.transceive(getSelectCommand(DFN_SRV));
                    // 3.讀取余額
                    byte[] ReadMoney = { (byte) 0x80, // CLA Class
                            (byte) 0x5C, // INS Instruction
                            (byte) 0x00, // P1 Parameter 1
                            (byte) 0x02, // P2 Parameter 2
                            (byte) 0x04, // Le
                    };
                    byte[] Money = isoDep.transceive(ReadMoney);
                    if (Money != null && Money.length > 4) {
                        int cash = byteToInt(Money, 4);
                        float ba = cash / 100.0f;
                        show_msg.setText("余額:" + ba);
                    }
                    // 4.讀取所有交易記錄
                    byte[] ReadRecord = { (byte) 0x00, // CLA Class
                            (byte) 0xB2, // INS Instruction
                            (byte) 0x01, // P1 Parameter 1
                            (byte) 0xC5, // P2 Parameter 2
                            (byte) 0x00, // Le
                    };
                    byte[] Records = isoDep.transceive(ReadRecord);
                    // 處理Record
                    Log.d("h_bl", "總消費記錄" + Records);
                    ArrayList<byte[]> ret = parseRecords(Records);
                    List<String> retList = parseRecordsToStrings(ret);
                    show_msg.append("\n" + "消費記錄如下:");
                    for (String string : retList) {
                        Log.d("h_bl", "消費記錄" + string);
                        show_msg.append("\n" + string);
                    }

                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (isoDep != null) {
                    try {
                        isoDep.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

完整代碼:

package com.example.nfcdemo;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import android.support.v7.app.ActionBarActivity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.NfcF;
import android.nfc.tech.NfcV;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import com.example.nfcdemo2.R;

public class MainActivity extends ActionBarActivity {

    private NfcAdapter nfcAdapter; // NFC適配器
    // NFC前台調度系統
    private PendingIntent pendingIntent = null;
    private TextView show_msg; // 顯示數據
    protected final static byte TRANS_CSU = 6; // 如果等於0x06或者0x09,表示刷卡;否則是充值
    protected final static byte TRANS_CSU_CPX = 9; // 如果等於0x06或者0x09,表示刷卡;否則是充值
    public String[][] tenchlists;
    public IntentFilter[] filters;

    @SuppressWarnings("unchecked")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        show_msg = (TextView) findViewById(R.id.show_msg);
        tenchlists = new String[][] { { IsoDep.class.getName() }, { NfcV.class.getName() }, { NfcF.class.getName() }, };

        try {
            filters = new IntentFilter[] { new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED, "*/*") };
        } catch (MalformedMimeTypeException e1) {
            e1.printStackTrace();
        }
        // 初始化PendingIntent,當有NFC設備連接上的時候,就交給當前Activity處理
        pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        // 獲取默認的NFC控制器,並進行判斷
        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (nfcAdapter == null) {
            Log.d("h_bl", "設備不支持NFC!");
            finish();
            return;
        }
        if (!nfcAdapter.isEnabled()) {
            Toast.makeText(getApplicationContext(), "請在系統設置中先啟用NFC功能!", Toast.LENGTH_SHORT).show();
            Log.d("h_bl", "請在系統設置中先啟用NFC功能!");
            return;
        }
        Intent intent = this.getIntent(); // 捕獲NFC Intent
        praseIntent(intent);
    }

    private void praseIntent(Intent intent) {

        String nfcAction = intent.getAction(); // 解析該Intent的Action
        if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(nfcAction)) {
            Log.d("h_bl", "ACTION_TECH_DISCOVERED");
            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); // 獲取Tag標簽,既可以處理相關信息
            for (String tech : tag.getTechList()) {
                Log.d("h_bl", "tech=" + tech);
            }
            IsoDep isoDep = IsoDep.get(tag);
            String str = "";
            try {
                isoDep.connect(); // 連接
                if (isoDep.isConnected()) {
                    Log.d("h_bl", "isoDep.isConnected"); // 判斷是否連接上
                    // 1.select PSF (1PAY.SYS.DDF01)
                    // 選擇支付系統文件,它的名字是1PAY.SYS.DDF01。
                    byte[] DFN_PSE = { (byte) '1', (byte) 'P', (byte) 'A', (byte) 'Y', (byte) '.', (byte) 'S', (byte) 'Y', (byte) 'S', (byte) '.', (byte) 'D', (byte) 'D', (byte) 'F', (byte) '0', (byte) '1', };
                    isoDep.transceive(getSelectCommand(DFN_PSE));
                    // 2.選擇公交卡應用的名稱
                    byte[] DFN_SRV = { (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x86, (byte) 0x98, (byte) 0x07, (byte) 0x01, };
                    isoDep.transceive(getSelectCommand(DFN_SRV));
                    // 3.讀取余額
                    byte[] ReadMoney = { (byte) 0x80, // CLA Class
                            (byte) 0x5C, // INS Instruction
                            (byte) 0x00, // P1 Parameter 1
                            (byte) 0x02, // P2 Parameter 2
                            (byte) 0x04, // Le
                    };
                    byte[] Money = isoDep.transceive(ReadMoney);
                    if (Money != null && Money.length > 4) {
                        int cash = byteToInt(Money, 4);
                        float ba = cash / 100.0f;
                        show_msg.setText("余額:" + ba);
                    }
                    // 4.讀取所有交易記錄
                    byte[] ReadRecord = { (byte) 0x00, // CLA Class
                            (byte) 0xB2, // INS Instruction
                            (byte) 0x01, // P1 Parameter 1
                            (byte) 0xC5, // P2 Parameter 2
                            (byte) 0x00, // Le
                    };
                    byte[] Records = isoDep.transceive(ReadRecord);
                    // 處理Record
                    Log.d("h_bl", "總消費記錄" + Records);
                    ArrayList<byte[]> ret = parseRecords(Records);
                    List<String> retList = parseRecordsToStrings(ret);
                    show_msg.append("\n" + "消費記錄如下:");
                    for (String string : retList) {
                        Log.d("h_bl", "消費記錄" + string);
                        show_msg.append("\n" + string);
                    }

                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (isoDep != null) {
                    try {
                        isoDep.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    @Override
    protected void onPause() {
        if (nfcAdapter != null)
            nfcAdapter.disableForegroundDispatch(this);
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        nfcAdapter.enableForegroundDispatch(this, pendingIntent, filters, tenchlists);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        // 當前app正在前端界面運行,這個時候有intent發送過來,那么系統就會調用onNewIntent回調方法,將intent傳送過來
        // 我們只需要在這里檢驗這個intent是否是NFC相關的intent,如果是,就調用處理方法
        if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {
            Log.d("h_bl", "onNewIntent");
            praseIntent(intent);
        }

    }

    public static byte byteToHex(byte arg) {
        byte hex = 0;
        if (arg >= 48 && arg <= 57) {
            hex = (byte) (arg - 48);
        } else if (arg >= 65 && arg <= 70) {
            hex = (byte) (arg - 55);
        } else if (arg >= 97 && arg <= 102) {
            hex = (byte) (arg - 87);
        }
        return hex;
    }

    private byte[] getSelectCommand(byte[] aid) {
        final ByteBuffer cmd_pse = ByteBuffer.allocate(aid.length + 6);
        cmd_pse.put((byte) 0x00) // CLA Class
                .put((byte) 0xA4) // INS Instruction
                .put((byte) 0x04) // P1 Parameter 1
                .put((byte) 0x00) // P2 Parameter 2
                .put((byte) aid.length) // Lc
                .put(aid).put((byte) 0x00); // Le
        return cmd_pse.array();
    }

    /**
     * 整條Records解析成ArrayList<byte[]>
     * 
     * @param Records
     * @return
     */
    private ArrayList<byte[]> parseRecords(byte[] Records) {
        int max = Records.length / 23;
        Log.d("h_bl", "消費記錄有" + max + "條");
        ArrayList<byte[]> ret = new ArrayList<byte[]>();
        for (int i = 0; i < max; i++) {
            byte[] aRecord = new byte[23];
            for (int j = 23 * i, k = 0; j < 23 * (i + 1); j++, k++) {
                aRecord[k] = Records[j];
            }
            ret.add(aRecord);
        }
        for (byte[] bs : ret) {
            Log.d("h_bl", "消費記錄有byte[]" + bs); // 有數據。解析正確。
        }
        return ret;
    }

    /**
     * ArrayList<byte[]>記錄分析List<String> 一條記錄是23個字節byte[] data,對其解碼如下
     * data[0]-data[1]:index data[2]-data[4]:over,金額溢出??? data[5]-data[8]:交易金額
     * ??代碼應該是(5,4) data[9]:如果等於0x06或者0x09,表示刷卡;否則是充值
     * data[10]-data[15]:刷卡機或充值機編號
     * data[16]-data[22]:日期String.format("%02X%02X.%02X.%02X %02X:%02X:%02X"
     * ,data[16], data[17], data[18], data[19], data[20], data[21], data[22]);
     * 
     * @param logs
     * @return
     */
    private List<String> parseRecordsToStrings(ArrayList<byte[]>... Records) {
        List<String> recordsList = new ArrayList<String>();
        for (ArrayList<byte[]> record : Records) {
            if (record == null)
                continue;
            for (byte[] v : record) {
                StringBuilder r = new StringBuilder();
                int cash = Util.toInt(v, 5, 4);
                char t = (v[9] == TRANS_CSU || v[9] == TRANS_CSU_CPX) ? '-' : '+';
                r.append(String.format("%02X%02X.%02X.%02X %02X:%02X ", v[16], v[17], v[18], v[19], v[20], v[21], v[22]));
                r.append("   " + t).append(Util.toAmountString(cash / 100.0f));
                String aLog = r.toString();
                recordsList.add(aLog);
            }
        }
        return recordsList;
    }

    // byteArray轉化為int
    private int byteToInt(byte[] b, int n) {
        int ret = 0;
        for (int i = 0; i < n; i++) {
            ret = ret << 8;
            ret |= b[i] & 0x00FF;
        }
        if (ret > 100000 || ret < -100000)
            ret -= 0x80000000;
        return ret;
    }

}
View Code

 

Util:

public final class Util {
    private final static char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

    private Util() {
    }

    public static byte[] toBytes(int a) {
        return new byte[] { (byte) (0x000000ff & (a >>> 24)), (byte) (0x000000ff & (a >>> 16)), (byte) (0x000000ff & (a >>> 8)), (byte) (0x000000ff & (a)) };
    }

    public static int toInt(byte[] b, int s, int n) {
        int ret = 0;

        final int e = s + n;
        for (int i = s; i < e; ++i) {
            ret <<= 8;
            ret |= b[i] & 0xFF;
        }
        return ret;
    }

    public static int toIntR(byte[] b, int s, int n) {
        int ret = 0;

        for (int i = s; (i >= 0 && n > 0); --i, --n) {
            ret <<= 8;
            ret |= b[i] & 0xFF;
        }
        return ret;
    }

    public static int toInt(byte... b) {
        int ret = 0;
        for (final byte a : b) {
            ret <<= 8;
            ret |= a & 0xFF;
        }
        return ret;
    }

    public static String toHexString(byte[] d, int s, int n) {
        final char[] ret = new char[n * 2];
        final int e = s + n;

        int x = 0;
        for (int i = s; i < e; ++i) {
            final byte v = d[i];
            ret[x++] = HEX[0x0F & (v >> 4)];
            ret[x++] = HEX[0x0F & v];
        }
        return new String(ret);
    }

    public static String toHexStringR(byte[] d, int s, int n) {
        final char[] ret = new char[n * 2];

        int x = 0;
        for (int i = s + n - 1; i >= s; --i) {
            final byte v = d[i];
            ret[x++] = HEX[0x0F & (v >> 4)];
            ret[x++] = HEX[0x0F & v];
        }
        return new String(ret);
    }

    public static int parseInt(String txt, int radix, int def) {
        int ret;
        try {
            ret = Integer.valueOf(txt, radix);
        } catch (Exception e) {
            ret = def;
        }

        return ret;
    }

    public static String toAmountString(float value) {
        return String.format("%.2f", value);
    }
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM