@waiting/idcard-reader-bp8903
Version:
224 lines (201 loc) • 7.33 kB
JavaScript
/**
* @waiting/idcard-reader-bp8903
* 南天 BP8903 二代机具读卡
*
* @version 2.3.2
* @author waiting
* @license MIT
* @link https://github.com/waitingsong/node-idcard-reader-bp8903#readme
*/
import { join, tmpdir, dirname, normalize, isFileExists, fileExists } from '@waiting/shared-core';
import { DTypes } from 'win32-def';
import { parseDeviceOpts, parseCompositeOpts, validateDllFile, testWrite, initialIDData, composite, initialDataBase, formatBaseData } from '@waiting/idcard-reader-base';
export { initialCompositeOpts, initialOpts, nationMap } from '@waiting/idcard-reader-base';
import { info } from '@waiting/log';
import { Library } from 'ffi';
import { of } from 'rxjs';
import { timeout, retry, map, delay, tap, mergeMap, catchError } from 'rxjs/operators';
import { decode } from 'iconv-lite';
const config = {
appDir: '',
tmpDir: join(tmpdir(), 'idcard-reader'),
};
const dllFuncs = {
OpenCom: [DTypes.INT32, [DTypes.INT32, DTypes.POINT, DTypes.INT32, DTypes.INT32]],
CloseCom: [DTypes.INT32, []],
IDCard_GetInformation: [DTypes.INT32, [DTypes.INT32, DTypes.INT32, DTypes.POINT, DTypes.INT32, DTypes.POINT, DTypes.POINT, DTypes.POINT]],
};
function connectDevice(device, port) {
if (device && device.inUse) {
device.deviceOpts.debug && info('Cautiton: connectDevice() device in use');
return 0
}
const openRet = device.apib.OpenCom(port, Buffer.from(''), 9600, 1);
device.deviceOpts.debug && info(`open com ret: ${openRet}`);
return openRet === 0 ? port : 0
}
function disconnectDevice(device) {
const ret = device.apib.CloseCom();
device.deviceOpts.debug && info(`disconnectDevice at port: ${device.openPort}, ret: ${ret} `);
device.inUse = false;
return true
}
function findDeviceList(deviceOpts, compositeOpts, apib) {
const arr = [];
if (deviceOpts.port > 0) {
const device = findDevice(deviceOpts.port, deviceOpts, compositeOpts, apib);
if (device.openPort > 0) {
arr.push(device);
}
}
else {
// 检测串口. bp8903 为串口接口
for (let i = 1; i <= 16; i++) {
const device = findDevice(i, deviceOpts, compositeOpts, apib);
if (device.openPort > 0) {
arr.push(device);
if (!deviceOpts.searchAll) {
break
}
}
}
}
return arr
}
function findDevice(openPort, deviceOpts, compositeOpts, apib) {
const device = {
apib,
deviceOpts,
compositeOpts,
inUse: false,
openPort: 0,
};
const port = connectDevice(device, openPort);
if (port > 0) {
device.inUse = true;
device.openPort = port;
deviceOpts.debug && info(`Found device at serial/usb port: ${port}`);
disconnectDevice(device);
}
return device
}
/** 读取二代证基础信息 */
function readAll(device) {
const path = dirname(device.deviceOpts.dllTxt);
process.env.PATH = `${process.env.PATH};${path}`;
const buf = Buffer.alloc(10240);
const srcDir = path.replace(/\\/g, '/') + '/';
const targetPath = normalize(device.deviceOpts.imgSaveDir + '/').replace(/\\/g, '/');
if (device.deviceOpts.debug) {
info('starting reading readCard ret');
info('IDCard_GetInformation() src path:' + srcDir);
info('IDCard_GetInformation() target path:' + targetPath);
}
const code = device.apib.IDCard_GetInformation(device.openPort, 9600, Buffer.from(''), 3, buf, Buffer.from(srcDir), Buffer.from(targetPath));
const ret = decode(buf, 'gbk');
if (device.deviceOpts.debug) {
info(`readDataBase code: ${code}`);
info(`readDataBase bufLen: ${buf.byteLength}`);
info(`readDataBase ret: ${ret}`);
// info(buf.slice(80))
}
return ret.replace(/\0/g, '')
}
async function init(options) {
const deviceOpts = parseDeviceOpts(options);
const compositeOpts = parseCompositeOpts(options);
// 先清空 解决头像图片生成可能失败问题
// compositeOpts.fontHwxhei = ''
// compositeOpts.fontOcrb = ''
// compositeOpts.fontSimhei = ''
const { debug } = deviceOpts;
if (debug) {
info(compositeOpts);
info(deviceOpts);
}
await validateDllFile(deviceOpts.dllTxt);
// 允许 未指定照片解码dll
if (deviceOpts.dllImage && compositeOpts.useComposite && !await isFileExists(deviceOpts.dllImage)) {
throw new Error('File not exists: ' + deviceOpts.dllImage)
}
await testWrite(deviceOpts.imgSaveDir);
const apib = Library(deviceOpts.dllTxt, dllFuncs);
const devices = findDeviceList(deviceOpts, compositeOpts, apib);
if (devices && devices.length) {
return devices
}
else {
throw new Error('未找到读卡设备')
}
}
/** Read card data */
function read(device) {
if (device.openPort) {
try {
disconnectDevice(device);
}
catch (ex) {
throw ex
}
const text$ = of(readAll(device)).pipe(timeout(2000));
const iddata$ = text$.pipe(retry(device.deviceOpts.findCardRetryTimes), map(text => {
const base = pickFields(text);
const imagePath = device.compositeOpts.useComposite
? genAvatarPath(device.deviceOpts.imgSaveDir, base.idc)
: '';
const ret = Object.assign({}, initialIDData, { base,
imagePath });
return ret
}));
const ret$ = iddata$.pipe(delay(device.compositeOpts.useComposite ? 300 : 0), tap(() => {
disconnectDevice(device);
}), mergeMap(data => {
return fileExists(data.imagePath).pipe(map(path => {
data.imagePath = path;
return data
}))
}), mergeMap(data => {
return !device.compositeOpts.useComposite || !data.imagePath
? of(data)
: composite(data.imagePath, data.base, device.compositeOpts).pipe(map(imgPath => {
data.compositePath = imgPath;
return data
}))
}), timeout(20000), catchError((err) => {
disconnectDevice(device);
throw err
}));
return ret$.toPromise()
}
else {
throw new Error('设备端口未指定')
}
}
/** Pick fields from origin text */
function pickFields(text) {
const base = Object.assign({}, initialDataBase);
if (!text || !text.length) {
return base
}
/** 姓名|身份证号|性别|民族|出生年月|住址|签发机关|起始日期|截至日期 */
const arr = text.split('|');
if (arr.length === 9) {
base.name = arr[0].trim();
base.idc = arr[1];
base.genderName = arr[2];
base.nationName = arr[3];
base.birth = arr[4];
base.address = arr[5].trim();
base.regorg = arr[6].trim();
base.startdate = arr[7];
base.enddate = arr[8];
}
return formatBaseData(base)
}
function genAvatarPath(imgSaveDir, idc) {
const path = join(imgSaveDir, `${idc}.bmp`);
return path
}
// base directory of this module
config.appDir = __dirname + '/..';
export { init, read, genAvatarPath };