身份证读取设备开发解决方案:1、Windows下开发Qt程序demo读取身份证信息
文章目录
- 身份证读取设备开发解决方案:1、Windows下开发Qt程序demo读取身份证信息
- 1. 前言
- 2. 身份证读取模块
- 3. Qt5开发简单上位机读取身份证信息
- 1. 注意的点
- 2. 部分源码
- 3. 结果展示
- 4. 碎片代码
- 4. 问题记录
- 发送指令没有回复
- 读取照片返回-22错误
1. 前言
我们的门禁设备有一个新的需求,需要不但可以刷小区制作的门禁卡,还支持刷身份证进小区,开始时只有一个身份证的认证读取模块,没有开发案例和经验,摸索了一段时间后还是不行,最后找到一个封装模块,可以很方便的进行二次开发,适用于Windows、Linux、Android、单片机(但是要获取身份证照片的话目前建议的是使用Android或Windows,对应有解码库,Linux需要特殊处理,单片机暂时没提,要处理的话可能比较麻烦)。
2. 身份证读取模块
这个是京东的链接地址:https://item.jd.com/29630924722.html#crumb-wrap
由于身份证读取模块的特殊性,所以是比价贵的,基本都是在¥1000+,包括一个公安部授权的模块和一系列公安部授权解码照片等信息的软件等。
如果不需要二次开发的话可以直接购买现成的设备,需要二次开发的话根据设备外设情况确认,我们是使用的ttl串口方式,这样可以多平台使用,Windows、Android、单片机都可以通过串口协议直接读取身份证信息。
下面我将使用Qt5开发一个简单的上位机进行身份证信息读取,相关的串口协议在买了模块后厂家回提供。
3. Qt5开发简单上位机读取身份证信息
这里只开发一个简单的demo,根据协议选择并设置串口,之后根据串口协议寻卡、选卡、读卡,读卡成功后控制蜂鸣器发声。
1. 注意的点
- 1、读取身份证信息时由于长度较长,大致在1000+个字节,所以如果全部获取完整的串口信息需要注意一下,我这里是根据报头和固定长度来确定的,发现报头后判断报文类型,确认是读取身份证的回复后一直到读到固定大小为止,期间的报文全部缓存追加;
- 2、高低字节互换并转unicode才是结果,这个也比较麻烦,思路是将根据偶数序号分割读取到的内容后高低字节转换并将两个字节合并为16位的ushort数组存储,之后直接转换ushort数组为utf16即可;
- 3、照片解码需要license、相关软件和库,目前支持Linux、Android、Windows,所以单片机开发的需要注意一下,解码照片需要上位机配合。
- 4、最好使用最新的解码动态库,license软件、动态库dll都放在exe运行目录下,且运行时需要使用管理员权限,运行完成后就会在exe目录下生成bmp图片,由于其没有头文件只有动态库,所以需要了解一下Qt直接调用动态库不需要头文件的方式。
2. 部分源码
由于涉及协议,所以无法上传工程文件,只展示部分内容,提供解析字节码到unicode再到字符串这个过程便于部分同学爬坑。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QLibrary>QSerialPort *serialPort;
QByteArray readBuff;MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);serialPort = new QSerialPort;searchSerialPort();
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::on_connectBtn_clicked()
{QString portName = ui->portBox->currentText();qInfo()<<"FILE:"<<__FILE__ << "LINE:" << __LINE__ <<"portName:"<<portName;serialPort->setPortName(ui->portBox->currentText()); //串口名serialPort->setBaudRate(ui->baudRateLineEdit->text().toInt()); //波特率serialPort->setDataBits(QSerialPort::Data8); //数据位serialPort->setStopBits(QSerialPort::OneStop); //停止位serialPort->setParity(QSerialPort::NoParity); //奇偶校验serialPort->setFlowControl(QSerialPort::NoFlowControl);if (serialPort->open(QIODevice::ReadWrite)){qWarning()<<"file:"<<__FILE__<<"line:"<<__LINE__<<"Port have been opened";ui->statusBar->showMessage("串口连接成功!");connect(serialPort, SIGNAL(readyRead()), this, SLOT(parseSerialData()));}else{qCritical()<<"file:"<<__FILE__<<"line:"<<__LINE__<<"open com failed";return;}connect(serialPort, static_cast<void (QSerialPort::*)(QSerialPort::SerialPortError)>(&QSerialPort::error),this, handleSerialError);ui->connectBtn->setEnabled(false);ui->closeBtn->setEnabled(true);
}void MainWindow::handleSerialError(QSerialPort::SerialPortError error) {if (error == QSerialPort::ResourceError) {QMessageBox::critical(this, tr("error"), "串口连接中断,请检查是否正确连接!");} else {ui->statusBar->showMessage(serialPort->errorString());}
}static QByteArray g_IDNumCardInfo;
static bool readIDNumCardFlag = false;static QString getName() {ushort name[15];for(int i = 0; i < 15; i++) {name[i] = g_IDNumCardInfo[8+2*i+1] << 8;name[i] = name[i] + (g_IDNumCardInfo[8+2*i] & 0x00ff);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<QString().sprintf("%02x",name[i]);}QString nameRes = QString::fromUtf16(name, 15);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<< nameRes;return nameRes;
}static QString getSex() {ushort sex[1];for(int i = 0; i < 1; i++){sex[i] = g_IDNumCardInfo[8+30+2*i+1] << 8;sex[i] = sex[i] + (g_IDNumCardInfo[8+30+2*i] & 0x00ff);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<QString().sprintf("%02x",sex[i]);}QString sexRes = QString::fromUtf16(sex, 1);if(sexRes == "1") {sexRes = "男";} else if(sexRes == "2") {sexRes = "女";} else if(sexRes == "9") {sexRes = "其他";}qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<< sexRes;return sexRes;
}static QString getNation() {ushort nation[2];for(int i = 0; i < 2; i++) {nation[i] = g_IDNumCardInfo[8+30+2+2*i+1] << 8;nation[i] = nation[i] + (g_IDNumCardInfo[8+30+2+2*i] & 0x00ff);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<QString().sprintf("%02x",nation[i]);}int nationTmp = QString::fromUtf16(nation, 2).toInt();qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<nationTmp;QString nationRes;switch (nationTmp) {case 1:nationRes = "汉";break;case 2:nationRes = "蒙古";break;case 3:nationRes = "回";break;case 4:nationRes = "藏";break;case 5:nationRes = "维吾尔";break;case 6:nationRes = "苗";break;case 7:nationRes = "彝"; //yizu彝族break;case 8:nationRes = "壮";break;case 9:nationRes = "布依";break;case 10:nationRes = "朝鲜";break;case 11:nationRes = "满";break;case 12:nationRes = "侗"; //侗族dongzubreak;case 13:nationRes = "瑶";break;case 14:nationRes = "白";break;case 15:nationRes = "土家";break;case 16:nationRes = "哈尼";break;case 17:nationRes = "哈萨克";break;case 18:nationRes = "傣"; //傣族daizubreak;case 19:nationRes = "黎";break;case 20:nationRes = "傈僳"; //傈僳族lisuzubreak;case 21:nationRes = "佤"; //佤族wazubreak;case 22:nationRes = "畲"; //畲族shezubreak;case 23:nationRes = "高山";break;case 24:nationRes = "拉祜"; //拉祜lahubreak;case 25:nationRes = "水";break;case 26:nationRes = "东乡";break;case 27:nationRes = "纳西";break;case 28:nationRes = "景颇";break;case 29:nationRes = "柯尔克孜";break;case 30:nationRes = "土";break;case 31:nationRes = "达斡尔"; //dawoerbreak;case 32:nationRes = "仫佬族"; //mulaozubreak;case 33:nationRes = "羌"; //qiangbreak;case 34:nationRes = "布朗";break;case 35:nationRes = "撒拉";break;case 36:nationRes = "毛南";break;case 37:nationRes = "仡佬"; //仡佬族gelaozubreak;case 38:nationRes = "锡伯";break;case 39:nationRes = "阿昌";break;case 40:nationRes = "普米";break;case 41:nationRes = "塔吉克";break;case 42:nationRes = "怒";break;case 43:nationRes = "乌孜别克";break;case 44:nationRes = "俄罗斯";break;case 45:nationRes = "鄂温克";break;case 46:nationRes = "德昂";break;case 47:nationRes = "独龙";break;case 48:nationRes = "裕固";break;case 49:nationRes = "京";break;case 50:nationRes = "塔塔尔";break;case 51:nationRes = "独龙";break;case 52:nationRes = "鄂伦春";break;case 53:nationRes = "赫哲";break;case 54:nationRes = "门巴";break;case 55:nationRes = "珞巴";break;case 56:nationRes = "基诺";break;default:break;}qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__ << nationRes;return nationRes;
}static QString getBirth() {ushort birth[8];for(int i = 0; i < 8; i++) {birth[i] = g_IDNumCardInfo[8+30+2+4+2*i+1] << 8;birth[i] = birth[i] + (g_IDNumCardInfo[8+30+2+4+2*i] & 0x00ff);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<QString().sprintf("%02x",birth[i]);}QString birthRes = QString::fromUtf16(birth, 8);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<birthRes;return birthRes;
}static QString getAddress() {ushort address[35];for(int i = 0; i < 35; i++) {address[i] = g_IDNumCardInfo[8+30+2+4+16+2*i+1] << 8;address[i] = address[i] + (g_IDNumCardInfo[8+30+2+4+16+2*i] & 0x00ff);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<QString().sprintf("%02x",address[i]);}QString addressRes = QString::fromUtf16(address, 35);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<addressRes;return addressRes;
}static QString getIDCardNum() {ushort idCardNum[18];for(int i = 0; i < 18; i++) {idCardNum[i] = g_IDNumCardInfo[8+30+2+4+16+70+2*i+1] << 8;idCardNum[i] = idCardNum[i] + (g_IDNumCardInfo[8+30+2+4+16+70+2*i] & 0x00ff);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<QString().sprintf("%02x",idCardNum[i]);}QString idCardNumRes = QString::fromUtf16(idCardNum, 18);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<idCardNumRes;return idCardNumRes;
}static QString getIssuingAuthority() {ushort issuingAuthority[15];for(int i = 0; i < 15; i++) {issuingAuthority[i] = g_IDNumCardInfo[8+30+2+4+16+70+36+2*i+1] << 8;issuingAuthority[i] = issuingAuthority[i] + (g_IDNumCardInfo[8+30+2+4+16+70+36+2*i] & 0x00ff);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<QString().sprintf("%02x", issuingAuthority[i]);}QString issuingAuthorityRes = QString::fromUtf16(issuingAuthority, 15);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<issuingAuthorityRes;return issuingAuthorityRes;
}static QString getEffectiveStartDate() {ushort effectiveStartDate[8];for(int i = 0; i < 8; i++) {effectiveStartDate[i] = g_IDNumCardInfo[8+30+2+4+16+70+36+30+2*i+1] << 8;effectiveStartDate[i] = effectiveStartDate[i] + (g_IDNumCardInfo[8+30+2+4+16+70+36+30+2*i] & 0x00ff);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<QString().sprintf("%02x", effectiveStartDate[i]);}QString effectiveStartDateRes = QString::fromUtf16(effectiveStartDate, 8);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<effectiveStartDateRes;return effectiveStartDateRes;
}static QString getEffectiveEndDate() {ushort effectiveEndDate[8];for(int i = 0; i < 8; i++) {effectiveEndDate[i] = g_IDNumCardInfo[8+30+2+4+16+70+36+30+16+2*i+1] << 8;effectiveEndDate[i] = effectiveEndDate[i] + (g_IDNumCardInfo[8+30+2+4+16+70+36+30+16+2*i] & 0x00ff);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<QString().sprintf("%02x", effectiveEndDate[i]);}QString effectiveEndDateRes = QString::fromUtf16(effectiveEndDate, 8);qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<effectiveEndDateRes;return effectiveEndDateRes;
}typedef int (*pcom_open)(char* , char* , int );
static int getPhoto() {uchar photo[1024];char dst[38556];for(int i = 0; i < 1024; i++) {photo[i] = g_IDNumCardInfo[8+30+2+4+16+70+36+30+16+16+36+i] & 0xff;// qDebug()<<"file:"<<__FILE__<<"line:"<<__LINE__<<QString().sprintf("%02x", photo[i]);}//Qt中只调用动态库不需要头文件的方式QLibrary mylib("DLL_File.dll");int res;if(mylib.load()) {pcom_open open = (pcom_open)mylib.resolve("unpack");if(open) {res = open((char*)photo, dst, 1);}qDebug()<<"res:"<<res;return res;}return -1;
}void MainWindow::parseSerialData() {readBuff.clear();readBuff = serialPort->readAll();for(int i = 0; i < readBuff.size(); i++) {if(readBuff[i].operator == (0xEA) &&readBuff[i+1].operator == (0xEB) &&readBuff[i+2].operator == (0xEC) &&readBuff[i+3].operator == (0xED)) {if(readBuff[7].operator == (0xA4)) {ui->statusBar->showMessage(QString("蜂鸣器:").append(readBuff.toHex().toUpper()));}else if(readBuff[7].operator == (0xB0)) {ui->statusBar->showMessage(QString("身份证寻卡:").append(readBuff.toHex().toUpper()));}else if(readBuff[7].operator == (0xB1)) {ui->statusBar->showMessage(QString("身份证选卡:").append(readBuff.toHex().toUpper()));}else if (readBuff[7].operator == (0xB4)) {ui->statusBar->showMessage(QString("身份证读取:").append(readBuff.toHex().toUpper()));qDebug()<<readBuff.size();//触发读到身份证的标志,直到读取完所有的身份证信息readIDNumCardFlag = true;}}}qDebug() << "FILE:" << __FILE__ << "LINE:" << __LINE__ << "读取到的报文:" << readBuff.toHex();if(readIDNumCardFlag) {qDebug()<<g_IDNumCardInfo.size();if(g_IDNumCardInfo.size() < 1290) {qDebug()<<g_IDNumCardInfo.size();g_IDNumCardInfo = g_IDNumCardInfo.append(readBuff);if(g_IDNumCardInfo.size() == 1290) {qDebug()<<g_IDNumCardInfo.toHex();QString IDCardInfo;//30字节姓名IDCardInfo = getName().append("\n");//2字节性别IDCardInfo.append(getSex()).append("\n");//4字节民族IDCardInfo.append(getNation()).append("\n");//16字节出生IDCardInfo.append(getBirth()).append("\n");//70字节住址IDCardInfo.append(getAddress()).append("\n");//36字节身份证号IDCardInfo.append(getIDCardNum()).append("\n");//30字节签发机关IDCardInfo.append(getIssuingAuthority()).append("\n");//16字节有效起始日期IDCardInfo.append(getEffectiveStartDate()).append("\n");//16字节有效截至日期IDCardInfo.append(getEffectiveEndDate()).append("\n");//36字节备用信息,暂为空//1024字节照片信息if(getPhoto() == 1) {QPixmap img;img.load("zp.bmp");ui->imgLabel->clear();ui->imgLabel->setPixmap(img);ui->IDCard2InfoLabel->setText(IDCardInfo);}g_IDNumCardInfo.clear();readIDNumCardFlag = false;} else if(g_IDNumCardInfo.size() > 1290) {readIDNumCardFlag = false;g_IDNumCardInfo.clear();}}}
}void MainWindow::on_closeBtn_clicked()
{serialPort->clearError();serialPort->clear();serialPort->close();ui->closeBtn->setEnabled(false);ui->connectBtn->setEnabled(true);ui->statusBar->showMessage("串口断开完成!");
}void MainWindow::searchSerialPort()
{ui->portBox->clear();foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {QSerialPort serialTmp;serialTmp.setPort(info);ui->portBox->addItem(info.portName());if(serialTmp.open(QIODevice::ReadWrite)){serialTmp.close();}else{qCritical()<<"file:"<<__FILE__<<"line:"<<__LINE__<<"串口打开失败,或串口已打开";QMessageBox::warning(this, "提示信息", QString("串口打开失败,串口已打开:%1").arg(info.portName()));}}ui->portBox->setCurrentIndex(ui->portBox->count() - 1);
}
3. 结果展示
4. 碎片代码
通过Qlabel显示图片:
QPixmap img;
img.load("zp.bmp");
ui->imgLabel->clear();
ui->imgLabel->setPixmap(img);ui->IDCard2InfoLabel->setText(IDCardInfo);
不需要头文件直接加载dll动态库:
//定义函数指针
typedef int (*pcom_open)(char* , char* , int );//Qt中只调用动态库不需要头文件的方式
QLibrary mylib("DLL_File.dll");
int res;
if(mylib.load()) {pcom_open open = (pcom_open)mylib.resolve("unpack");if(open) {res = open((char*)photo, dst, 1);}qDebug()<<"res:"<<res;return res;
}
还有比较有趣的就是直到了56个民族的发音。
4. 问题记录
发送指令没有回复
在最初使用串口工具进行测试时发现发送寻卡、选卡和读卡指令时一直没有回复报文:
然后一边找客服解答一边写了串口代码测试发现有回复,然后尝试勾选了Hex发送、Hex显示、加时间戳和分包显示后正常了,这个就尴尬了,当指令为16进制时需要注意你的串口测试工具是否开启了16进制,否则发送的指令很有可能没有回复:
读取照片返回-22错误
编译后在当前目录运行即可,使用Qt creator运行时没有找到解码软件。给到的身份证解码模组的软件、动态库等都是放到exe的当前目录下的。