IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 身份证读取设备开发解决方案:1、Windows下开发Qt程序demo读取身份证信息 -> 正文阅读

[嵌入式]身份证读取设备开发解决方案:1、Windows下开发Qt程序demo读取身份证信息

身份证读取设备开发解决方案:1、Windows下开发Qt程序demo读取身份证信息


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 = "侗";  //侗族dongzu
        break;
    case 13:
        nationRes = "瑶";
        break;
    case 14:
        nationRes = "白";
        break;
    case 15:
        nationRes = "土家";
        break;
    case 16:
        nationRes = "哈尼";
        break;
    case 17:
        nationRes = "哈萨克";
        break;
    case 18:
        nationRes = "傣";  //傣族daizu
        break;
    case 19:
        nationRes = "黎";
        break;
    case 20:
        nationRes = "傈僳";  //傈僳族lisuzu
        break;
    case 21:
        nationRes = "佤";  //佤族wazu
        break;
    case 22:
        nationRes = "畲";  //畲族shezu
        break;
    case 23:
        nationRes = "高山";
        break;
    case 24:
        nationRes = "拉祜";  //拉祜lahu
        break;
    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 = "达斡尔";  //dawoer
        break;
    case 32:
        nationRes = "仫佬族";  //mulaozu
        break;
    case 33:
        nationRes = "羌";  //qiang
        break;
    case 34:
        nationRes = "布朗";
        break;
    case 35:
        nationRes = "撒拉";
        break;
    case 36:
        nationRes = "毛南";
        break;
    case 37:
        nationRes = "仡佬";  //仡佬族gelaozu
        break;
    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的当前目录下的。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-07-09 17:37:02  更:2021-07-09 17:37:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/5 3:17:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码