--------------------坑-------------------------
DesfireCard Read项目总结
由于之前读卡器模块读卡速度过慢,换了读卡器模块。所以需要开发android端的程序。下面记录一下工作中出现的问题。
一、命名的问题。
函数名的大小写、格式(需要使用统一规则),以及英文的语法(注意例如时态、主谓宾的顺序)问题。
二、SDK的封装。
什么是需要封装进SDK中的以及需要传出的数据,这里是需要考虑的问题。
例如,所定义的函数select_applications的返回值。
这里不能返回数据长度一般为不太复杂的数据,需要判断的东西(即判断length的大小),要放在SDK中,不能写在外面,应该定义length >= 91的时候返回false。
注意数据的传出问题。
三、组包以及USB的通讯。
①首先,需要定义命令。
android端与读卡器模块的通讯需多次按一定的规律发送命令,因此需要组包。具体命令是通过PC端的软件解出来的,我需要发送的包,就是解出来的数据,并且保证在发送包之后能得到同样的数据。
组包的思路是首先定义一个String,例如private String cmd_add_0 = "11 10 00 4d 00 20 00";,然后定义一个函数public byte[] create_packet(byte cmd,String add_data),在函数内对String数据进行处理转换成byte[],然后拼接数据或改变某个位置的数据,create_packet的返回值byte[]并不是实际发送的数据(注意这里还有一个序列号需要修改,因为USB通讯序列号是会发生改变的,因此还需要定义一个函数private boolean send_cmd(byte cmd, String add_cmd_data, int ms, byte[] data),data用来传出返回的数据,即接收到的数据。在函数内改变序列号的位置,真正发送的数据的是通过send函数,发送的byte是packet这个byte[],global_seq是一个表示序列号的全局变量,在send_cmd函数内定义一个自加1操作,以控制序列号增长,同时,在发送命令之后,需要验证序列号是否相等,然后才会对data进行赋值)。
byte[] packet = create_packet(cmd, add_cmd_data);
packet[6] = global_seq;//组包完成
byte[] recv = send(packet, ms);//发送命令
.......................................................................
global_seq += 1;
这里注意要考虑函数返回值的问题以及recv为null的情况。当recv为null时(即发送命令之后收到的数据异常的情况,我们需要做一个判断),需要返回false,否则可能会出现bug。
②USB的通讯。
1.枚举USB设备。
USB设备的识别是通过PID和VID来识别的。因此,首先要知道读卡器设备的这两个数据并定义在res目录下的xml文件中,然后在manifest中的对应activity中注册。
android:resource="@xml/device_filter" />
同时,需要加上这一句feature。
<uses-featureandroid:name="android.hardware.usb.host"android:required="false"/>
USB主要相关类有:UsbManager、UsbDevice、UsbDeviceConnection、UsbEndpoint。
UsbManager :通过getSystemService实例化。
mUsbManager= (UsbManager) context.getSystemService(Context.USB_SERVICE);
UsbDevice:表示的是设备。通过调用UsbManager.getDeviceList,获取了deviceList,然后遍历deviceList,通过调用getVendorId可以获取连接到android的USB设备的VID,然后通过判断当设备的VID来找到设备即device,然后就可以对设备进行操作了(device.getVendorId() == 0x273a)。
UsbDeviceConnection:调用mUsbManager.openDevice(device)返回该类,用来表示打开device的状态。
UsbEndpoint:一个USB有3种EP。Input、Output、Interrupt。USB通讯是通过对EP进行操作来进行通讯的。实例化mDevice之后,通过调用getInterface获取UsbInterface,然后对UsbInterface进行操作即可获取EP。
在获取了EP之后,判断app是否具有USB通讯的权限(具体申请权限写在下面),有权限才能进行openDevice操作,否则会报错,openDevice返回UsbDeviceConnection。
mUsbManager.hasPermission(mDevice)
mUsbManager.openDevice(device)
//0、1、2分别对应了不同的EP。Input和Output均是指对android设备而言。
mEndpointIn= GetEndPoint( device,2);
mEndpointOut= GetEndPoint( device,1);
mInterruptEndpoint= GetEndPoint( device,0);
//定义函数
publicUsbEndpointGetEndPoint( UsbDevice device,intnEndpoint){
...........
UsbInterface intf = device.getInterface(0);
ep1 = intf.getEndpoint( nEndpoint );//intput和output的EP
if(ep1.getType() ==USB_ENDPOINT_XFER_INT){
ep1 = intf.getEndpoint( nEndpoint );//虽然这个判断感觉没有用。
...........
}
}
读到卡返回的命令为80 03(这里是十进制,对应十六进制是50) ,Interrupt返回的命令为80 02。(注意interrupt也算In的一种)。
IN 50 03
IN 50 03
2.申请通讯权限。android中USB通讯是需要申请通讯权限的。具体思路是动态定义一个broadcast,然后再注册。要确保申请权限之后才执行usb相关操作,否则容易出错,并且要考虑无device情况(即没有插入读卡器模块的情况),不然在未插入设备的情况下打开app会直接挂掉。
以上2步联系紧密。
..................................................
3.现在需要检测卡的状态(是否有卡),当检测到读到卡的时候,才能进行下一步操作。那么如何中读卡器中获取信息呢?主要是UsbRequest类。
........................................
//这个虽然之前实例化了,但是在这里还是要加上,不然会出现错误,具体原因我还没搞清//楚,待查证。
ByteBuffer buffer = ByteBuffer.allocate(16);
try{
mConnection= GetConnection(mDevice);
}catch(java.lang.SecurityException e) {
e.printStackTrace();
}
//这里需要用这个mInterruptEndpoint。
UsbRequest request =newUsbRequest();
request.initialize(mConnection,mInterruptEndpoint);
request.queue(buffer,16);
//这里大致就是UsbRequest将收到的数据,存进buffer中。
//queue boolean queue (ByteBuffer buffer)
//Queues the request to send or receive data on its endpoint.
//requestWait:Waits for the result of aqueue(ByteBuffer)operation
//UsbRequestrequestWait ()
//由此可知调用wait是为了让queue(buffer)做完,即将数据存进buffer中。
if(mConnection.requestWait() == request) {
Log.d(TAG,"after requestWait");
//然后判断
bytestatus0 = buffer.get(0);
bytestatus1 = buffer.get(1);
Log.d(TAG,"status get:"+ status0 +","+ status1);
if(status0 ==80&& status1 ==3) {
mOnCardListener.OnCardState(true);
reader_power_init();//这里检测到卡之后,需要进行初始化操作,这是Read Card之前的准备工作。
return1;
}
if(status0 ==80&& status1 ==2) {
mOnCardListener.OnCardState(false);
return0;
}
if(status0 ==0&& status1 ==0) {
return-1;
}
}
return-1;
这里我读到了三种状态:
读到卡80, 03 。
没有卡80,02 。
No change 0,0(这个待考证,我只是获取到了这个status的数据,然后进行了判断而已)。
80, 03这个数据是读卡器模块在检测到卡之后自动发送给android设备的,无需任何操作,我这里只是将EP收到的数据读出来进行判断,当收到80,03即表示有卡,那么我就可以进行下一步的操作。
这里需要单独开了一个线程去做detect_card的操作(因为需要一直检测状态)。
//根据detect_card函数的返回值来判断卡的状态。并且加上一个500ms的延时(每500ms调用一次detect_card函数,赋值给detect_card_ret),detect_card_ret应该是一个有三种状态的返回值,而不能直接通过detect_card的返回值赋值。
public voidrun() {
while(true) {
..............................
intret =mUSBReaderSDK.detect_card();
if(ret==1)// card inserted.detect_card_ret=1;
else if(ret==0)//card removed.detect_card_ret=0;
//else : -1: no changed, so ignored.try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
................
Time ret detect_card_ret status
0 1 1 insert card
500 -1 ignore insertcard(no changes)
1000 0 0 remove
1500 -1 ignore remove
//因为这里只需要对insert和remove状态进行监测,changes状态无需返回值。
detect_card_ret是个全局变量,下面需要通过对此变量进行判断,来决定是否开启ReadThread线程。这里的思路是定义一个ReadButton,每点击一次就new ReadThread,然后start。但是,开启线程之前,需要先做条件判断。首先判断是否有卡(即detect_card_ret是否为1),然后就是需要一个线程标志位(readthread_is_running,默认为false)。
当这两个条件满足后,才能真正开启线程。线程标志位需要在开启线程(即start thread)之前置true,执行完线程之后置false(即run函数的末尾处,以便下次开启)。
if(detect_card_ret==1&& !readthread_is_running){
readthread_is_running=true;
starThread();
}
private classReadThreadextendsThread{
@Override
public voidrun(){
....................
readthread_is_running=false;
}
}
ReadThread中,定义了如何Read Card。读卡的本质就是一系列数据的收发以及解析过程。
Desfire卡的读卡流程是
power_off
power_on
get_applications
select_applications
list_file
read_file
power_off
四、Util类的积累。
十六进制的byte与String之间的转换,以及小端的转换。
五、整个读卡的流程总结。