糖尿病康复,内容丰富有趣,生活中的好帮手!
糖尿病康复 > 身份证读取设备开发解决方案:2 Android下通过usb转串口读取身份证信息

身份证读取设备开发解决方案:2 Android下通过usb转串口读取身份证信息

时间:2018-12-22 20:34:27

相关推荐

身份证读取设备开发解决方案:2 Android下通过usb转串口读取身份证信息

身份证读取设备开发解决方案:2、Android下通过usb转串口读取身份证信息

文章目录

身份证读取设备开发解决方案:2、Android下通过usb转串口读取身份证信息1. 前言2. 准备3. android下读取身份证信息的demo3.1 部分源码3.2 碎片代码3.3 结果展示3.4 注意点3.4.1 添加串口库3.4.2 SDK最小版本错误3.4.3 获取解码后的图片4. 最后

1. 前言

之前我们已经在Windows下通过Qt5开发了一个简单的demo测试了通过封装的身份证模块读取身份证信息是可用的,接下来我们将在Android手机上开发一个简单的demo进行测试,仍然使用串口方式。(这个是上一篇:/weixin_39510813/article/details/118579865?spm=1001..3001.5501)

2. 准备

usb转ttl串口的模块,这个我们在Windows下测试的时候也需要的,默认厂家ttl串口的只引出了排线,没有这个转接模块,需要自己接一下线

如果是直接在手机上测试的话还需要一个usb的转接头,具体看你的手机是type c的还是Micro USB接口,大多数Android现在都使用type c了,我这里测试的手机也是type c的接口

当然,如果是定制的Android板子的话则也可以根据自己的外设接口选择不同的身份证模块,一般Android可以直接买usb接口的。

组合后的样式:

androidStudio开发环境,不知道的可以看这里:/weixin_39510813/article/details/87438291

一台Android设备

Android下的串口处理jar包,可以在GitHub上寻找(比如:/mik3y/usb-serial-for-android,当然也可以使用Google官方很多年前提供的:/cepr/android-serialport-api),当然也可以通过ndk的方式通过c++直接操作串口之后提供接口给到Java层,网上的jar>包是已经封装好的,如果有特殊需求也可以自行操作底层处理

ok,万事具备,开始coding

3. android下读取身份证信息的demo

3.1 部分源码

这里没有做过多的封装,只是一个demo,而且为了看代码比较方便,所以目前只放了两个文件,由于协议原因,部分内容暂时不展示:

MainActivity.java部分内容:

package com.xiaoyaoyou.xyz;import androidx.annotation.RequiresApi;import androidx.appcompat.app.AppCompatActivity;import android.content.Context;import android.graphics.Bitmap;import android.hardware.usb.UsbDeviceConnection;import android.hardware.usb.UsbManager;import android.os.Build;import android.os.Bundle;import android.provider.Settings;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.ImageView;import android.widget.TextView;import android.widget.Toast;import com.hoho.android.usbserial.driver.UsbSerialDriver;import com.hoho.android.usbserial.driver.UsbSerialPort;import com.hoho.android.usbserial.driver.UsbSerialProber;import com.hoho.android.usbserial.util.SerialInputOutputManager;import com.zkteco.android.IDReader.WLTService;import java.io.IOException;import java.nio.charset.StandardCharsets;import java.util.List;import java.util.concurrent.atomic.AtomicBoolean;import java.util.concurrent.atomic.AtomicInteger;public class MainActivity extends AppCompatActivity {private static final String TAG = "ReadIDCardMainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button buttonConnect = (Button) findViewById(R.id.buttonConnect);Button buttonDisconnect = (Button) findViewById(R.id.buttonDisConnect);Button buttonBuzzer = (Button) findViewById(R.id.buttonBuzzer);Button buttonFindIDCard = (Button) findViewById(R.id.buttonFindIdCard);Button buttonSelectIDCard = (Button) findViewById(R.id.buttonSelectIDCard);Button buttonReadIDCard = (Button) findViewById(R.id.buttonReadIDCard);Button buttonClear = (Button) findViewById(R.id.buttonClear);final UsbSerialPort[] port = new UsbSerialPort[1];TextView ReadSerialRes = (TextView) findViewById(R.id.textViewSerialRead);TextView IDCardInfoRes = (TextView) findViewById(R.id.IDCardInfotextView);AtomicBoolean g_readIDCardInfoFlag = new AtomicBoolean(false);byte[] IDCardInfo = new byte[1290];AtomicInteger IDCardInfoLen = new AtomicInteger();//连接串口buttonConnect.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// Find all available drivers from attached devices.UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager);if (availableDrivers.isEmpty()) {Toast.makeText(getApplicationContext(), "未查询到串口设备", Toast.LENGTH_SHORT).show();return;}// Open a connection to the first available driver.UsbSerialDriver driver = availableDrivers.get(0);UsbDeviceConnection connection = manager.openDevice(driver.getDevice());if (connection == null) {Toast.makeText(getApplicationContext(), "连接串口失败,请确认是否开启usb权限", Toast.LENGTH_SHORT).show();return;}port[0] = driver.getPorts().get(0); //Most devices have just one port (port 0)try {port[0].open(connection);port[0].setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);Toast.makeText(getApplicationContext(), "打开串口成功", Toast.LENGTH_SHORT).show();//串口读取监听SerialInputOutputManager.Listener listener = new SerialInputOutputManager.Listener() {@RequiresApi(api = Build.VERSION_CODES.KITKAT)@Overridepublic void onNewData(byte[] data) {runOnUiThread(() -> {ReadSerialRes.append(bytesToHex(data) + "\n");for (int i = 0; i < data.length; i++) {switch (data[7]) {case (byte) 0xA0:ReadSerialRes.setText("蜂鸣器");ReadSerialRes.append(bytesToHex(data), 12, 14);break;case (byte) 0xB0:ReadSerialRes.setText("寻卡");ReadSerialRes.append(bytesToHex(data), 12, 14);break;case (byte) 0xB1:ReadSerialRes.setText("选卡");ReadSerialRes.append(bytesToHex(data), 12, 14);break;case (byte) 0xB4:ReadSerialRes.setText("读卡");ReadSerialRes.append(bytesToHex(data), 12, 14);ReadSerialRes.append("\n");//触发读卡g_readIDCardInfoFlag.set(true);break;default:break;}}if (g_readIDCardInfoFlag.get()) {Log.d(TAG, "readData:" + bytesToHex(data));if (IDCardInfoLen.get() < 1290) {System.arraycopy(data, 0, IDCardInfo, IDCardInfoLen.get(), data.length);IDCardInfoLen.addAndGet(data.length);Log.d(TAG, "IDCardInfoLen:" + IDCardInfoLen);}if (IDCardInfoLen.get() == 1290) {Log.d(TAG, "IDCardInfo:" + bytesToHex(IDCardInfo));//头8字节不用理会,30字节姓名byte[] name = new byte[30];System.arraycopy(IDCardInfo, 8, name, 0, 30);Log.d(TAG, "nameByte:" + bytesToHex(name));String nameStr = UnicodeByteToStr(name);IDCardInfoRes.append(nameStr + "\n");Log.i(TAG, "name:" + nameStr);//2字节性别byte[] sex = new byte[2];System.arraycopy(IDCardInfo, 8 + 30, sex, 0, 2);Log.d(TAG, "sexByte:" + bytesToHex(sex));String sexStr = UnicodeByteToStr(sex);switch (sexStr) {case "1":sexStr = "男";break;case "2":sexStr = "女";break;case "9":sexStr = "其他";break;default:Log.e(TAG, "Unknown sex:" + sexStr);break;}IDCardInfoRes.append(sexStr + "\n");Log.i(TAG, "sex:" + sexStr);//4字节民族byte[] nation = new byte[4];System.arraycopy(IDCardInfo, 8 + 30 + 2, nation, 0, 4);Log.d(TAG, "nationByte:" + bytesToHex(nation));String nationStr = UnicodeByteToStr(nation);switch (Integer.parseInt(nationStr)) {case 1:nationStr = "汉";break;case 2:nationStr = "蒙古";break;case 3:nationStr = "回";break;case 4:nationStr = "藏";break;case 5:nationStr = "维吾尔";break;case 6:nationStr = "苗";break;case 7:nationStr = "彝"; //yizu彝族break;case 8:nationStr = "壮";break;case 9:nationStr = "布依";break;case 10:nationStr = "朝鲜";break;case 11:nationStr = "满";break;case 12:nationStr = "侗"; //侗族dongzubreak;case 13:nationStr = "瑶";break;case 14:nationStr = "白";break;case 15:nationStr = "土家";break;case 16:nationStr = "哈尼";break;case 17:nationStr = "哈萨克";break;case 18:nationStr = "傣"; //傣族daizubreak;case 19:nationStr = "黎";break;case 20:nationStr = "傈僳"; //傈僳族lisuzubreak;case 21:nationStr = "佤"; //佤族wazubreak;case 22:nationStr = "畲"; //畲族shezubreak;case 23:nationStr = "高山";break;case 24:nationStr = "拉祜"; //拉祜lahubreak;case 25:nationStr = "水";break;case 26:nationStr = "东乡";break;case 27:nationStr = "纳西";break;case 28:nationStr = "景颇";break;case 29:nationStr = "柯尔克孜";break;case 30:nationStr = "土";break;case 31:nationStr = "达斡尔"; //dawoerbreak;case 32:nationStr = "仫佬族"; //mulaozubreak;case 33:nationStr = "羌"; //qiangbreak;case 34:nationStr = "布朗";break;case 35:nationStr = "撒拉";break;case 36:nationStr = "毛南";break;case 37:nationStr = "仡佬"; //仡佬族gelaozubreak;case 38:nationStr = "锡伯";break;case 39:nationStr = "阿昌";break;case 40:nationStr = "普米";break;case 41:nationStr = "塔吉克";break;case 42:nationStr = "怒";break;case 43:nationStr = "乌孜别克";break;case 44:nationStr = "俄罗斯";break;case 45:nationStr = "鄂温克";break;case 46:nationStr = "德昂";break;case 47:nationStr = "独龙";break;case 48:nationStr = "裕固";break;case 49:nationStr = "京";break;case 50:nationStr = "塔塔尔";break;case 51:nationStr = "独龙";break;case 52:nationStr = "鄂伦春";break;case 53:nationStr = "赫哲";break;case 54:nationStr = "门巴";break;case 55:nationStr = "珞巴";break;case 56:nationStr = "基诺";break;default:break;}IDCardInfoRes.append(nationStr + "\n");Log.i(TAG, "nation:" + nationStr);//16字节出生byte[] birth = new byte[16];System.arraycopy(IDCardInfo, 8 + 30 + 2 + 4, birth, 0, 16);Log.d(TAG, "birthByte:" + bytesToHex(birth));String birthStr = UnicodeByteToStr(birth);IDCardInfoRes.append(birthStr + "\n");Log.i(TAG, "birth:" + birthStr);//70字节住址byte[] address = new byte[70];System.arraycopy(IDCardInfo, 8 + 30 + 2 + 4 + 16, address, 0, 70);Log.d(TAG, "addressByte:" + bytesToHex(address));String addressStr = UnicodeByteToStr(address);IDCardInfoRes.append(addressStr + "\n");Log.i(TAG, "address:" + addressStr);//36字节公民身份证号码byte[] IDNum = new byte[36];System.arraycopy(IDCardInfo, 8 + 30 + 2 + 4 + 16 + 70, IDNum, 0, 36);Log.d(TAG, "IDNumByte:" + bytesToHex(IDNum));String IDNumStr = UnicodeByteToStr(IDNum);IDCardInfoRes.append(IDNumStr + "\n");Log.i(TAG, "IDNum:" + IDNumStr);//30字节签发机关byte[] issuingAuthority = new byte[30];System.arraycopy(IDCardInfo, 8 + 30 + 2 + 4 + 16 + 70 + 36, issuingAuthority, 0, 30);Log.d(TAG, "issuingAuthority:" + bytesToHex(issuingAuthority));String issuingAuthorityStr = UnicodeByteToStr(issuingAuthority);IDCardInfoRes.append(issuingAuthorityStr + "\n");Log.i(TAG, "issuingAuthority:" + issuingAuthorityStr);//16字节有效起始日期byte[] effectiveStartDate = new byte[16];System.arraycopy(IDCardInfo, 8 + 30 + 2 + 4 + 16 + 70 + 36 + 30, effectiveStartDate, 0, 16);Log.d(TAG, "effectiveStartDate:" + bytesToHex(effectiveStartDate));String effectiveStartDateStr = UnicodeByteToStr(effectiveStartDate);IDCardInfoRes.append(effectiveStartDateStr + "\n");Log.i(TAG, "effectiveStartDateStr:" + effectiveStartDateStr);//16字节有效截至日期byte[] effectiveEndDate = new byte[16];System.arraycopy(IDCardInfo, 8 + 30 + 2 + 4 + 16 + 70 + 36 + 30 + 16, effectiveEndDate, 0, 16);Log.d(TAG, "effectiveEndDate:" + bytesToHex(effectiveEndDate));String effectiveEndDateStr = UnicodeByteToStr(effectiveEndDate);IDCardInfoRes.append(effectiveEndDateStr + "\n");Log.i(TAG, "effectiveEndDateStr:" + effectiveEndDateStr);//36字节备用//1024字节照片信息byte[] photo = new byte[1024];System.arraycopy(IDCardInfo, 8 + 30 + 2 + 4 + 16 + 70 + 36 + 30 + 16 + 16 + 36, photo, 0, 1024);Log.d(TAG, "photo:" + bytesToHex(photo));Bitmap photoRes = Tool.getBitmap(photo);ImageView image = (ImageView)findViewById(R.id.imageView);image.setImageBitmap(photoRes);}if (IDCardInfoLen.get() >= 1290) {//处理完毕初始化g_readIDCardInfoFlag.set(false);IDCardInfoLen.set(0);}}});}@Overridepublic void onRunError(Exception e) {}};SerialInputOutputManager usbIoManager = new SerialInputOutputManager(port[0], listener);usbIoManager.start();} catch (IOException e) {Toast.makeText(getApplicationContext(), "打开串口失败", Toast.LENGTH_SHORT).show();e.printStackTrace();}}});//蜂鸣器buttonBuzzer.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {}});//寻卡buttonFindIDCard.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {}});//选卡buttonSelectIDCard.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {}});//读卡buttonReadIDCard.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {}});buttonClear.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {ReadSerialRes.setText("");IDCardInfoRes.setText("");}});}final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();public static String bytesToHex(byte[] bytes) {char[] hexChars = new char[bytes.length * 2];for (int j = 0; j < bytes.length; j++) {int v = bytes[j] & 0xFF;hexChars[j * 2] = hexArray[v >>> 4];hexChars[j * 2 + 1] = hexArray[v & 0x0F];}return new String(hexChars);}@RequiresApi(api = Build.VERSION_CODES.KITKAT)public static String UnicodeByteToStr(byte[] bBuf) {return new String(bBuf, StandardCharsets.UTF_16LE); // 这种不会处理字符串结束符 \0}}

Tool.java:

package com.xiaoyaoyou.xyz;import android.graphics.Bitmap;import android.util.Log;import com.zkteco.android.IDReader.WLTService;public class Tool{private static final String TAG = "ReadIDCardMainActivity";public static Bitmap getBitmap(byte []data) {byte[] arrayOfByte1 = new byte[40960];try {if(data==null) return null;int k = WLTService.wlt2Bmp(data, arrayOfByte1);Log.i(TAG, "getBitmap: 解码结果:"+k);if (k != 1) {return null;}byte[] arrayOfByte2 = new byte[38556];System.arraycopy(arrayOfByte1, 0, arrayOfByte2, 0, 38556);return Tool.createRgbBitmap(arrayOfByte2, 102, 126);} catch (Exception localException) {localException.printStackTrace();}return null;}private static int convertByteToInt(byte paramByte){int i = paramByte >> 4 & 0xF;int j = 0xF & paramByte;return i * 16 + j;}private static int[] convertByteToColor(byte[] paramArrayOfByte, int paramInt1, int paramInt2){int i = paramArrayOfByte.length;if (i == 0) {return null;}int[] arrayOfInt = new int[i / 3];for (int n = 0; n < paramInt2; n++){int i1 = n * paramInt1;for (int i2 = 0; i2 < paramInt1; i2++){int i3 = (i1 + i2) * 3;int j = convertByteToInt(paramArrayOfByte[i3]);int k = convertByteToInt(paramArrayOfByte[(i3 + 1)]);int m = convertByteToInt(paramArrayOfByte[(i3 + 2)]);arrayOfInt[((paramInt2 - n - 1) * paramInt1 + i2)] = (j << 16 | k << 8 | m | 0xFF000000);}}return arrayOfInt;}private static Bitmap createRgbBitmap(byte[] paramArrayOfByte, int paramInt1, int paramInt2){int[] arrayOfInt = convertByteToColor(paramArrayOfByte, paramInt1, paramInt2);if (arrayOfInt == null) {return null;}Bitmap localBitmap;localBitmap = Bitmap.createBitmap(arrayOfInt, 0, paramInt1, paramInt1, paramInt2, Bitmap.Config.ARGB_8888);return localBitmap;}}

3.2 碎片代码

串口读取这个库通过监听的方式来读取,后续读取结果也都在这个读取到的线程中处理:

SerialInputOutputManager.Listener listener = new SerialInputOutputManager.Listener() {@RequiresApi(api = Build.VERSION_CODES.KITKAT)@Overridepublic void onNewData(byte[] data) {runOnUiThread(() -> {...}}}

关于读取到信息后的处理结果和之前的类似,由于长度为1290,所以需要根据表示缓存多帧数据后进一步处理:

//触发读卡g_readIDCardInfoFlag.set(true);if (g_readIDCardInfoFlag.get()) {if (IDCardInfoLen.get() < 1290) {...}if (IDCardInfoLen.get() == 1290) {}if (IDCardInfoLen.get() >= 1290) {//处理完毕初始化g_readIDCardInfoFlag.set(false);IDCardInfoLen.set(0);}}

将byte[]转换为16进制字符串:

final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();public static String bytesToHex(byte[] bytes) {char[] hexChars = new char[bytes.length * 2];for (int j = 0; j < bytes.length; j++) {int v = bytes[j] & 0xFF;hexChars[j * 2] = hexArray[v >>> 4];hexChars[j * 2 + 1] = hexArray[v & 0x0F];}return new String(hexChars);}

处理byte[]转unicode:

@RequiresApi(api = Build.VERSION_CODES.KITKAT)public static String UnicodeByteToStr(byte[] bBuf) {return new String(bBuf, StandardCharsets.UTF_16LE); // 这种不会处理字符串结束符 \0}

拿到1024个字节的图片信息后,我们通过Tool.java中提供的接口调用相关库进行解码处理,这个库厂家会提供,我们只要拿到对应的库后以jni的方式添加到我们项目中即可调用。

3.3 结果展示

3.4 注意点

3.4.1 添加串口库

我是使用的:/mik3y/usb-serial-for-android这个串口库,按照GitHub上添加扩展:

dependencies {implementation 'com.github.mik3y:usb-serial-for-android:3.4.1'}

出现错误:could not find method implementtation

最终发现这个有点被误导:

这里显示是添加到根目录下的build.gradle,实际上只是第一部分url添加到项目根目录下的build.gradle,而下面依赖项的内容是添加到android/app下的build.gradle的(根据这里找到了答案:/questions/45615474/gradle-error-could-not-find-method-implementation-for-arguments-com-android,我是使用的最新4.x的AndroidStudio)。

此外,需要注意添加device_filter.xml

创建资源文件xml后复制内容进来即可。

3.4.2 SDK最小版本错误

串口库要求的sdk最低版本是17,而默认一般是16:

Manifest merger failed : uses-sdk:minSdkVersion 16 cannot be smaller than version 17 declared in library [com.github.mik3y:usb-serial-for-android:3.4.1] C:\Users\admin\.gradle\caches\transforms-2\files-2.1\f58431aa42060dd0c91c5352a5ce0898\usb-serial-for-android-3.4.1\AndroidManifest.xml as the library might be using APIs not available in 16Suggestion: use a compatible library with a minSdk of at most 16,or increase this project's minSdk version to at least 17,or use tools:overrideLibrary="com.hoho.android.usbserial" to force usage (may lead to runtime failures)

修改一下android/app下面的build.gradle,修改minSdkVersion为17即可,我们基本不会用到16版本的什么内容,所以16和17基本没有什么影响。

3.4.3 获取解码后的图片

由于Android终端机基本上我们都是使用一个usb口,所以串口基本就是搜索到的一个,波特率我们也直接写死了。拿到1024个字节的图片信息后可以通过这个Android库直接进行处理;es-100厂家给的Android demo封装的太狠了,串口处理这块的内容全部封装了。

如果有需要单独处理串口的可以看一下这个库:/Sabirjan/WLT2Bmp_Android (主要是看其怎么使用wlt2bmp这个库的,我们通过串口已经拿到了1024字节的图片byte数组,只需要再通过这个库解码成bmp图片后展示即可,Tool.java就是我从这块拿出来的,然后结合厂家给的解码库做了一点点修改)。

当然,如果是Android上直接通过usb使用的话不需要按照我上面这样来处理,官方的demo给了jar包和so,按照jni的方式添加后,根据demo直接读取即可,封装的接口已经足够简便了,我这里仍然操作串口处理主要是为了熟悉串口协议确认这种方式也可行。

4. 最后

接下来我们将在单片机上进行身份证信息的读取,说实话我是不推荐这么做的,身份证照片的加密压缩信息需要公安部研究所提供的解码库才能解码,而单片机上是没有对应库的,所以最终要获取照片信息还是得发送到上位机处理,所以如果可以的话最好直接使用Android或Windows(Linux下也可以,但是需要单独找厂家获取SDK),不然加个单片机最后获取照片还是需要上位机,那何必浪费这个单片机,还无故多个单片机和上位机的通信过程。

如果觉得《身份证读取设备开发解决方案:2 Android下通过usb转串口读取身份证信息》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。