1.背景
最近在研究二代證讀卡器,手頭上的設備是新中新DKQ-A16D,在官網(https://www.onecardok.com.cn/download)逛了一圈,發現Win下的示例,瀏覽器插件很多,Linux下的就少的可憐了,只有“新中新身份證讀卡器開發包Linux版V1.2.1”,“新中新讀卡器麒麟ArmV8火狐擴展程序安裝及使用說明V1.1”,意味着瀏覽器插件還只能在ARM的平台下才能使用。Linux的也只有C++和Java版本。
2.開搞C++
大概看了下C++的Demo,這個Demo主要就是調用CppDemo目錄下的那些libx86,libx64等目錄下的libSynReader.so文件,具體調用的函數如下:
//打開USB通信 int OpenUsbComm(); //讀身份證文字和照片信息 int getIDcard(St_IDCardDataUTF8 *pIDCardDataUTF8); //解碼照片函數,wltBuffer原始照片1024字節wlt數據,bmpPath 保存照片位置 int saveWlt2Bmp( char* wltBuffer,const char* bmpPath); int saveWlt2BmpUseFork( char* wltBuffer,const char* bmpPath); //關閉USB通信 int CloseComm();
流程:先打開USB通信(OpenUsbComm) ---> 讀取身份證文字和照片信息(getIDcard) ---> 解碼照片(saveWlt2Bmp或者saveWlt2BmpUseFork) ---> 關閉USB通信(CloseComm)
至於saveWlt2Bmp和saveWlt2BmpUseFork這兩個函數到底什么區別,不懂。兩個函數調用結果好像都是一樣。
這里需要注意的是getIDcard這個函數,它的參數是一個結構體指針 ,根據CppDemo中的頭文件定義,這個結構體是這樣的:
typedef struct IDCardDataUTF8 { char CardType[10]; //I為外國人居住證,J 為港澳台居住證,空格(0x20)為普通身份證 char Name[40]; //姓名 char EngName[130]; //英文名(外國人居住證) char Sex[10]; //性別 char Nation[100]; //民族或國籍(外國人居住證) char Birthday[18]; //出生日期 char Address[80]; //住址 char IDCardNo[40]; //身份證號或外國人居住證號(外國人居住證) char GrantDept[40]; //發證機關 char UserLifeBegin[30]; //有效開始日期 char UserLifeEnd[30]; //有效截止日期 char PassID[30]; //通行證號碼(港澳台) char IssuesTimes[10]; //簽發次數(港澳台) char CertVol[10]; //證件版本號(外國人居住證) char wlt[1024]; //照片數據 int isSavePhotoOK; //照片是否解碼保存 0=no 1=yes char fp[1024]; //指紋數據 int isFpRead; //是否讀取了證內指紋 0=no 1=yes } St_IDCardDataUTF8, *PSt_IDCardDataUTF8;
3.開搞Python
知道了調用哪些函數,知道了流程,接下來就是Python的事了。
在Python中,要調用.so文件,主要是依靠ctypes這個庫。
具體加載so庫的代碼也簡單,
import ctypes from ctypes import * synR = ctypes.cdll.LoadLibrary("/usr/lib/libSynReader64.so")
加載完庫,調用庫中的函數也簡單,
nRet = synR.OpenUsbComm()
nRet是代表打開USB通信時是否成功,等於0代表開啟成功,還有其他的值,具體參見官網下載的那個Linux包里有個word文檔。
接下來就是本文的難點了,在Python中是沒有結構體這個概念的,那getIDcard又要傳一個結構體指針,該怎么辦?
好在ctypes已經幫我們想好了,ctype內置了一個Structure這個類,只要我們創建一個類,繼承自這個類,在類中定義一個_fields_的list,C++那邊就會當作結構體了。具體實現如下:
class IDCardDataUTF8(Structure): _fields_ = [ ("CardType", c_char * 10), # I為外國人居住證,J 為港澳台居住證,空格(0x20)為普通身份證 ("Name", c_char * 40), # 姓名 ("EngName", c_char * 130), # 英文名(外國人居住證) ("Sex", c_char * 10), # 性別 ("Nation", c_char*100), # 民族或國籍(外國人居住證) ("Birthday", c_char*18), # 出生日期 ("Address", c_char*80), # 住址 ("IDCardNo", c_char*40), # 身份證號或外國人居住證號(外國人居住證) ("GrantDept", c_char*40), # 發證機關 ("UserLifeBegin", c_char*30), # 有效開始日期 ("UserLifeEnd", c_char*30), # 有效截止日期 ("PassID", c_char*30), # 通行證號碼(港澳台) ("IssuesTimes", c_char*10), # 簽發次數(港澳台) ("CertVol", c_char * 10), # 證件版本號(外國人居住證) ("wlt", c_byte*1024), # 照片數據 ("isSavePhotoOK", c_int), # 照片是否解碼保存 0=no 1=yes ("fp", c_char*1024), # 指紋數據 ("isFpRead", c_int) # 是否讀取了指紋 ]
對比下這個結構體和上面C++的結構體,是不是很像?這里有個坑,在下不才,在坑了蹲了2天才跳出來。注意看wlt這項,wlt是存儲照片數據的,C++是定義成char,剛開始我也是跟着用c_char,結果死活解碼不了照片,后面經過2天的各種搜索,各種嘗試,發現這個wlt是一個字節數組,所以需要定義成c_byte,一下子照片就解碼出來了。
至於指紋數據,沒去研究。
OK,結構體准備好了,實例化一個結構體對象,接着就可以調用getIDcard了。具體代碼如下:
data = IDCardDataUTF8()
readRet = synR.getIDcard(byref(data))
這個有個byref代表的就是把data參數變成一個指針,也就是上面C++說的結構體指針。
執行完這句,如果readRet等於0,代表讀取成功,接着就可以從data中拿到身份證上的基本信息了。
print(data.Name.decode('utf-8')) #身份證上的姓名
接着就是要解碼身份證上的照片信息了。
synR.saveWlt2BmpUseFork(data.wlt, b'photo.bmp')
這行代碼執行后,會在.py同級目錄下生成一個名為photo.bmp的照片文件,照片解碼成功。
最后,就是把USB通信關閉就可以了。
synR.CloseComm()
4.結束
有一點要注意的是,需要把CppDemo目錄下對應你系統的libx86或者libx64目錄下的所有so文件拷貝到/usr/lib和/lib目錄下