/**
|
* Fingerprint MZ Module
|
*
|
* This module provides an interface for communicating with the MZ fingerprint module via UART.
|
* It supports operations like image capture, fingerprint registration, matching, and storage management.
|
*
|
* Features:
|
* - `Image Capture`: Capture fingerprint images for enrollment or verification.
|
* - `Registration`: Generate and store fingerprint templates.
|
* - `Matching`: One-to-one (1:1) and one-to-many (1:N) fingerprint matching.
|
* - `Storage`: Manage fingerprint templates in the module's flash storage.
|
* - `System`: Configure module parameters and read system status.
|
*
|
* Usage:
|
* import dxFingerMz from './dxFingerMz.js';
|
* dxFingerMz.init({ id: 'fingerUart', path: '/dev/ttySLB0' });
|
* const result = dxFingerMz.getImage();
|
*/
|
import std from '../dxmodules/dxStd.js'
|
import dxUart from '../dxmodules/dxUart.js'
|
import common from '../dxmodules/dxCommon.js'
|
import log from '../dxmodules/dxLogger.js'
|
import * as os from "os"
|
|
const dxFingerMz = {}
|
|
// Packet Header
|
const HEADER = new Uint8Array([0xEF, 0x01])
|
// Device Address
|
let ADDRESS = new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF])
|
|
// Packet Flags
|
const FLAG = {
|
"COMMAND": 0x01, // Command packet
|
"DATA": 0x02, // Data packet
|
"DATA_END": 0x08, // End of data packet
|
"COMMAND_END": 0x07, // Response packet
|
}
|
|
// LED Color
|
dxFingerMz.LED_COLOR = {
|
"NONE": 0,
|
"BLUE": 1,
|
"GREEN": 2,
|
"RED": 4,
|
}
|
|
// Function Code
|
dxFingerMz.LED_FUNCTION_CODE = {
|
"NORMAL_BREATH": 1, // Common Breathing Light
|
"NORMAL_FLASH": 2, // Common Flashing Light
|
"CONSTANT_ON": 3, // Constant On Light
|
"CONSTANT_OFF": 4, // Constant Off Light
|
"GRADUAL_ON": 5, // Gradual On Light
|
"GRADUAL_OFF": 6, // Gradual Off Light
|
}
|
// Command Set
|
const CMD_MAP = {
|
/* General Commands */
|
"GET_IMAGE": 0x01, // Get image for verification
|
"GET_CHAR": 0x02, // Generate character file from image
|
"MATCH": 0x03, // Precise match
|
"SEARCH": 0x04, // Search fingerprint
|
"REG_MODEL": 0x05, // Combine character files (generate template)
|
"STORD_CHAR": 0x06, // Store template
|
"LOAD_CHAR": 0x07, // Load template
|
"UP_CHAR": 0x08, // Upload template
|
"DOWN_CHAR": 0x09, // Download template
|
"DEL_CHAR": 0x0C, // Delete template
|
"CLEAR_CHAR": 0x0D, // Clear fingerprint library
|
"WRITE_REG": 0x0E, // Write system register
|
"READ_SYS_PARA": 0x0F, // Read system parameters
|
"READ_INF_PAGE": 0x16, // Read information page
|
"BURN_CODE": 0x1a, // Burn code (Erase code)
|
"VALID_TEMPLETE_NUM": 0x1d, // Read valid template count
|
"READ_INDEX_TABLE": 0x1f, // Read index table
|
"GET_ENROLL_IMAGE": 0x29, // Get image for enrollment
|
"SLEEP": 0x33, // Sleep command
|
|
/* Module Commands */
|
"CANCEL": 0x30, // Cancel command
|
"AUTO_ENROLL": 0x31, // Auto enroll template
|
"AUTO_IDENTIFY": 0x32, // Auto identify fingerprint
|
|
/* Maintenance Commands */
|
"UP_IMAGE": 0x0A, // Upload image
|
"DOWN_IMAGE": 0x0B, // Download image
|
"GET_CHIP_SN": 0x34, // Get chip serial number
|
"HAND_SHAKE": 0x35, // Handshake
|
"CHECK_SENSOR": 0x36, // Check sensor
|
"REST_SETTING": 0x3B, // Restore factory settings
|
|
/* Custom Commands */
|
"SET_PWD": 0x12, // Set password
|
"VFY_PWD": 0x13, // Verify password
|
"GET_RANDOM_CODE": 0x14, // Get random code
|
"SET_CHIP_ADDR": 0x15, // Set chip address
|
"WRITE_NOTEPAD": 0x18, // Write notepad
|
"READ_NOTEPAD": 0x19, // Read notepad
|
"CONTROL_BLN": 0x3C, // Control LED light
|
"GET_IMAGE_INFO": 0x3D, // Get image info
|
"SEARCH_NOW": 0x3E, // Search current fingerprint
|
}
|
|
let options = {
|
id: 'fingerUart',
|
total: 5000,
|
timeout: 500,
|
type: '3',
|
path: '/dev/ttySLB0',
|
baudrate: '57600-8-N-2'
|
}
|
|
/**
|
* Get Image (Verification) - Detects finger and stores the fingerprint image in the image buffer.
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.getImage = function () {
|
let resp = execCmd(CMD_MAP.GET_IMAGE)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Generate Character File - Generates a fingerprint character file from the image in the image buffer and stores it in the character buffer.
|
* @param {number} bufferId - Buffer ID (1 or 2).
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.genChar = function (bufferId) {
|
let resp = execCmd(CMD_MAP.GET_CHAR, [bufferId])
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Precise Match - Compares the character file or template in the buffer.
|
* @returns {object|null} An object containing the result code and match score, or null on failure.
|
* - code: Confirmation code.
|
* - score: Match score.
|
*/
|
dxFingerMz.match = function () {
|
let resp = execCmd(CMD_MAP.MATCH)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return { code: resp.data[0], score: fromEndianHexExtended(resp.data.subarray(1, 3), 2, false) }
|
} else {
|
return null
|
}
|
}
|
|
/**
|
* Search Fingerprint - Searches the fingerprint library (whole or partial) using the character file in the buffer.
|
* If found, returns the page number.
|
* @param {number} bufferId - Character buffer ID (default is 1).
|
* @param {number} startPage - Start page number.
|
* @param {number} pageNum - Number of pages to search.
|
* @returns {object|null} An object containing the result code, page index, and score, or null on failure.
|
* - code: Confirmation code.
|
* - pageIndex: Index of the found page.
|
* - score: Match score.
|
*/
|
dxFingerMz.search = function (bufferId, startPage, pageNum) {
|
let data = new Uint8Array([bufferId, ...toEndianHexExtended(startPage, 2, false), ...toEndianHexExtended(pageNum, 2, false)])
|
let resp = execCmd(CMD_MAP.SEARCH, data)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return { code: resp.data[0], pageIndex: fromEndianHexExtended(resp.data.subarray(1, 3), 2, false), score: fromEndianHexExtended(resp.data.subarray(3, 5), 2, false) }
|
} else {
|
return null
|
}
|
}
|
|
/**
|
* Register Model (Combine Characters) - Combines character files to generate a template and stores it in the character buffer.
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.regModel = function () {
|
let resp = execCmd(CMD_MAP.REG_MODEL)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Store Template - Stores the template file from the buffer to the flash database at the specified page index.
|
* @param {number} bufferId - Character buffer ID (default is 1).
|
* @param {number} pageIndex - Fingerprint library location index.
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.storeChar = function (bufferId, pageIndex) {
|
let data = new Uint8Array([bufferId, ...toEndianHexExtended(pageIndex, 2, false)])
|
let resp = execCmd(CMD_MAP.STORD_CHAR, data)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Load Template - Reads the fingerprint template from the flash database at the specified page index into the buffer.
|
* @param {number} bufferId - Character buffer ID (default is 2).
|
* @param {number} pageIndex - Fingerprint library location index.
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.loadChar = function (bufferId, pageIndex) {
|
let data = new Uint8Array([bufferId, ...toEndianHexExtended(pageIndex, 2, false)])
|
let resp = execCmd(CMD_MAP.LOAD_CHAR, data)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Upload Template - Uploads the template file from the buffer to the Host.
|
* @param {number} bufferId - Character buffer ID (default is 2).
|
* @returns {ArrayBuffer|null} Template data, or null on failure.
|
*/
|
dxFingerMz.upChar = function (bufferId) {
|
let resp = execCmd(CMD_MAP.UP_CHAR, [bufferId])
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
let data = recvDataPkt({ header: HEADER, addr: ADDRESS, flag: FLAG.DATA })
|
return data
|
} else {
|
return null
|
}
|
}
|
|
/**
|
* Download Template - Downloads a template from the Host to the module's buffer.
|
* @param {number} bufferId - Character buffer ID (default is 1).
|
* @param {ArrayBuffer} char - Template data.
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.downChar = function (bufferId, char) {
|
let resp = execCmd(CMD_MAP.DOWN_CHAR, [bufferId])
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
sendData({
|
header: HEADER, addr: ADDRESS, flag: new Uint8Array([FLAG.DATA]), len: new Uint8Array(toEndianHexExtended(2, 2, false)), cmd: new Uint8Array([CMD_MAP.DOWN_CHAR]),
|
data: char
|
})
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Delete Template - Deletes N fingerprint templates starting from the specified page index in the flash database.
|
* @param {number} pageIndex - Starting index in the fingerprint library.
|
* @param {number} num - Number of templates to delete.
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.deletChar = function (pageIndex, num) {
|
let data = new Uint8Array([...toEndianHexExtended(pageIndex, 2, false), ...toEndianHexExtended(num, 2, false)])
|
let resp = execCmd(CMD_MAP.DEL_CHAR, data)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Clear Library (PS_Empty) - Deletes all fingerprint templates in the flash database.
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.clearChar = function () {
|
let resp = execCmd(CMD_MAP.CLEAR_CHAR)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Write System Register - Writes to a module register.
|
* @param {number} regId - Register ID.
|
* @param {number} context - Content to write.
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.writeReg = function (regId, context) {
|
let resp = execCmd(CMD_MAP.WRITE_REG, [regId, context])
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Read System Parameters - Reads the module's basic parameters (baud rate, packet size, etc.).
|
* @returns {object|null} An object containing the result code and parameter data, or null on failure.
|
* - code: Confirmation code.
|
* - data: System parameters.
|
*/
|
dxFingerMz.readSysPara = function () {
|
let resp = execCmd(CMD_MAP.READ_SYS_PARA)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return { code: resp.data[resp.data.length - 1], data: resp.data.subarray(0, resp.data.length - 1) }
|
} else {
|
return null
|
}
|
}
|
|
/**
|
* Get Valid Template Count - Reads the number of valid templates.
|
* @returns {object|null} An object containing the result code and valid count, or null on failure.
|
* - code: Confirmation code.
|
* - validNum: Number of valid templates.
|
*/
|
dxFingerMz.getValidTemplateNum = function () {
|
let resp = execCmd(CMD_MAP.VALID_TEMPLETE_NUM)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return { code: resp.data[0], validNum: resp.data[1] }
|
} else {
|
return null
|
}
|
}
|
|
/**
|
* Read Index Table - Reads the index table of enrolled templates.
|
* @param {number} indexPage - Index table page number (0, 1, 2, 3...). Each page corresponds to a range of templates (0-255, 256-511, etc.).
|
* Each bit represents a template: 1 means enrolled, 0 means not enrolled.
|
* @returns {object|null} An object containing the result code and index table, or null on failure.
|
* - code: Confirmation code.
|
* - indexTable: The index table data.
|
*/
|
dxFingerMz.readIndexTable = function (indexPage) {
|
let resp = execCmd(CMD_MAP.READ_INDEX_TABLE, [indexPage])
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return { code: resp.data[0], indexTable: resp.data.subarray(1) }
|
} else {
|
return null
|
}
|
}
|
|
/**
|
* Get Enroll Image - Detects finger and stores the fingerprint image in the buffer (for registration).
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.getEnrollImage = function () {
|
let resp = execCmd(CMD_MAP.GET_ENROLL_IMAGE)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Sleep - Sets the sensor to sleep mode.
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.sleep = function () {
|
let resp = execCmd(CMD_MAP.SLEEP)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Cancel - Cancels auto-registration or auto-verification operations.
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.cancel = function () {
|
let resp = execCmd(CMD_MAP.CANCEL)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Auto Register - One-stop fingerprint registration. Includes capture, feature generation, template merging, and storage.
|
* @param {number} pageIndex - Page index.
|
* @param {number} count - Number of finger presses required.
|
* @param {number} [timeout=60] - Timeout in seconds.
|
* @param {number} [config=0] - Configuration flags.
|
* @returns {ArrayBuffer|null} Result data, or null on failure/timeout.
|
*/
|
dxFingerMz.autoRegister = function (pageIndex, count, timeout = 60, config = 0) {
|
let param = {
|
header: HEADER, addr: ADDRESS, flag: new Uint8Array([FLAG.COMMAND]), len: new Uint8Array(toEndianHexExtended(8, 2, false)), cmd: new Uint8Array([CMD_MAP.AUTO_ENROLL])
|
, data: new Uint8Array([...toEndianHexExtended(pageIndex, 2, false), count, ...toEndianHexExtended(config, 2, false)])
|
}
|
// Send command
|
send(param)
|
// For each press, there will be multiple responses (status, merge result, duplicate check, store result).
|
let startTime = Date.now()
|
while (Date.now() - startTime < timeout * 1000) {
|
let resp = recvCmdPkt({ addr: ADDRESS })
|
if (resp && resp.flag[0] == FLAG.COMMAND_END && resp.data[0] == 0x00 && resp.data[1] == 0x06 && resp.data[2] == 0xf2) {
|
return resp.data
|
}
|
os.sleep(50)
|
}
|
return null
|
}
|
|
/**
|
* Auto Compare - Automatic fingerprint verification. Includes image capture, feature generation, and search.
|
* @param {number} pageIndex - Template index for 1:1 match, or 0xFFFF for 1:N search.
|
* @param {number} scoreLevel - Security level (1-5, default is 3).
|
* @param {number} config - Configuration flags.
|
* @returns {object|null} An object containing the result code, index, and score, or null on failure.
|
* - code: Confirmation code.
|
* - index: Matched index.
|
* - score: Match score.
|
*/
|
dxFingerMz.autoCompare = function (pageIndex, scoreLevel, config) {
|
let param = {
|
header: HEADER, addr: ADDRESS, flag: new Uint8Array([FLAG.COMMAND]), len: new Uint8Array(toEndianHexExtended(8, 2, false)), cmd: new Uint8Array([CMD_MAP.AUTO_IDENTIFY])
|
, data: new Uint8Array([scoreLevel, ...toEndianHexExtended(pageIndex, 2, false), ...toEndianHexExtended(config, 2, false)])
|
}
|
// Send command
|
send(param)
|
// Expect 3 responses: 1. Command validity, 2. Image result, 3. Search result.
|
for (let i = 0; i < 3; i++) {
|
let resp = recvCmdPkt({ addr: ADDRESS })
|
if (resp && resp.flag[0] == FLAG.COMMAND_END && resp.data[0] == 0 && resp.data[1] == 0x05) {
|
return { code: resp.data[0], index: fromEndianHexExtended(resp.data.subarray(2, 4), 2, false), score: fromEndianHexExtended(resp.data.subarray(4, 6), 2, false) }
|
}
|
}
|
return null
|
}
|
|
/**
|
* Get Chip SN - Retrieves the unique serial number of the chip.
|
* @returns {object|null} An object containing the result code and serial number, or null on failure.
|
* - code: Confirmation code.
|
* - sn: Serial number string/buffer.
|
*/
|
dxFingerMz.getChipSN = function () {
|
let resp = execCmd(CMD_MAP.GET_CHIP_SN, [0x00])
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return { code: resp.data[0], sn: resp.data.subarray(1) }
|
} else {
|
return null
|
}
|
}
|
|
/**
|
* Search Now - Searches the library using the most recently extracted feature in the character buffer.
|
* @param {number} startPage - Start page index.
|
* @param {number} pageNum - Number of pages to search.
|
* @returns {object|null} An object containing the result code, page index, and score, or null on failure.
|
* - code: Confirmation code.
|
* - pageIndex: Found page index.
|
* - score: Match score.
|
*/
|
dxFingerMz.searchNow = function (startPage, pageNum) {
|
let data = new Uint8Array([...toEndianHexExtended(startPage, 2, false), ...toEndianHexExtended(pageNum, 2, false)])
|
let resp = execCmd(CMD_MAP.SEARCH_NOW, data)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return { code: resp.data[0], pageIndex: fromEndianHexExtended(resp.data.subarray(1, 3), 2, false), score: fromEndianHexExtended(resp.data.subarray(3, 5), 2, false) }
|
} else {
|
return null
|
}
|
}
|
|
/**
|
* Restore Factory Settings - Clears internal data (if enrolled) and deletes internal keys.
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.restSetting = function () {
|
let resp = execCmd(CMD_MAP.REST_SETTING)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Control LED - Controls the LED color and brightness.
|
* @param {number} startColor - Start color(dxFingerMz.LED_COLOR.BLUE, dxFingerMz.LED_COLOR.GREEN, dxFingerMz.LED_COLOR.RED).
|
* @param {number} functionCode - Function code.
|
* @param {number} endColor - End color (default is dxFingerMz.LED_COLOR.RED).
|
* @param {number} loopCount - Loop count (default is 0).
|
* @returns {number} Confirmation code (0 for success, others for failure).
|
*/
|
dxFingerMz.controlLed = function (startColor, functionCode = dxFingerMz.LED_FUNCTION_CODE.CONSTANT_ON, endColor = dxFingerMz.LED_COLOR.RED, loopCount = 0) {
|
let data = new Uint8Array([functionCode, startColor, endColor, loopCount])
|
let resp = execCmd(CMD_MAP.CONTROL_BLN, data)
|
if (resp && resp.flag[0] == FLAG.COMMAND_END) {
|
return resp.data
|
} else {
|
return -1
|
}
|
}
|
|
/**
|
* Retrieve the first unused index from the fingerprint database
|
* @returns {number} Return the first unused index, returning -1 indicates retrieval failure
|
*/
|
dxFingerMz.getIndex = function () {
|
// The fingerprint database has four page numbers, 0-3. Traverse and search these four page numbers
|
for(let i = 0; i < 4; i++){
|
let inx = dxFingerMz.readIndexTable(i)
|
if(inx && inx.code == 0){
|
let ret = findFreeFingerIndex(inx.indexTable, i)
|
if(ret != -1){
|
return ret
|
}
|
}
|
}
|
return -1
|
}
|
|
/**
|
* Set Device Address - Sets the device address (default is 0xFFFFFFFF).
|
* @param {number} startId - Start ID.
|
* @param {number} endId - End ID.
|
* @returns {number} The available fingerprint ID, or false on failure.
|
*/
|
dxFingerMz.setChipAddr = function (startId, endId) {
|
}
|
|
/**
|
* Initialize Fingerprint Module.
|
* @param {Object} params - Initialization parameters.
|
* @param {string} [params.type='3'] - UART type.
|
* @param {string} [params.path='/dev/ttySLB1'] - UART path.
|
* @param {string} [params.baudrate='115200-8-N-1'] - Baud rate configuration.
|
* @param {string} [params.id='fingerUart'] - Connection ID.
|
* @param {number} [params.total=5000] - Total fingerprint capacity.
|
* @param {number} [params.timeout=500] - Timeout in milliseconds.
|
*/
|
dxFingerMz.init = function (params) {
|
options.id = params.id ? params.id : options.id
|
options.timeout = params.timeout ? params.timeout : options.timeout
|
options.total = params.total ? params.total : options.total
|
options.type = params.type ? params.type : options.type
|
options.path = params.path ? params.path : options.path
|
options.baudrate = params.baudrate ? params.baudrate : options.baudrate
|
|
dxUart.open(options.type, options.path, options.id)
|
dxUart.ioctl(6, options.baudrate, options.id)
|
}
|
|
/** *****************************private******************************* */
|
|
/**
|
* Execute Command - Sends a command and receives the response.
|
* @private
|
* @param {number} cmd - The command to execute.
|
* @param {Uint8Array|Array} [data] - Optional data payload.
|
* @returns {Object|null} The response packet, or null on failure.
|
*/
|
function execCmd(cmd, data) {
|
let len = 3 // cmd(1) + checksum(2)
|
let cmdData = data
|
if (data) {
|
if (Array.isArray(data)) {
|
cmdData = new Uint8Array(data)
|
}
|
len += cmdData.length
|
}
|
|
let param = {
|
header: HEADER,
|
addr: ADDRESS,
|
flag: new Uint8Array([FLAG.COMMAND]),
|
len: new Uint8Array(toEndianHexExtended(len, 2, false)),
|
cmd: new Uint8Array([cmd])
|
}
|
|
if (cmdData) {
|
param.data = cmdData
|
}
|
|
// Send command
|
send(param)
|
return recvCmdPkt({ addr: ADDRESS })
|
}
|
|
/**
|
* Send Packet
|
* @private
|
* @param {Object} params - Packet parameters.
|
* @param {number} params.header - Packet header.
|
* @param {number} params.addr - Device address.
|
* @param {number} params.flag - Packet flag.
|
* @param {number} params.len - Packet length.
|
* @param {number} params.cmd - Command byte (optional).
|
* @param {string} params.data - Data payload (optional).
|
*/
|
function send(params) {
|
if (!params || typeof params !== 'object') {
|
throw new Error("Parameters should be an object containing prefix, cmd and data");
|
}
|
|
const { header, addr, flag, len, cmd, data } = params;
|
// Build packet
|
let packet = concatUint8Arrays(header, addr, flag, len, cmd, data, new Uint8Array([0x00, 0x00]));
|
|
// Calculate checksum
|
let checksum = 0;
|
for (let i = 6; i < packet.length; i++) {
|
checksum += packet[i];
|
}
|
checksum = Math.min(checksum, 0xFFFF);
|
// Set checksum
|
packet.set(new Uint8Array(toEndianHexExtended(checksum, 2, false)), packet.length - 2);
|
|
// Send packet via UART
|
let ret = dxUart.send(packet.buffer, options.id);
|
if (!ret) {
|
throw new Error("dxFingerMz.send fail")
|
}
|
}
|
|
/**
|
* Send Data Packet - Splits data into chunks and sends them.
|
* @private
|
* @param {Object} params - Parameters.
|
* @param {string|Uint8Array} params.data - Data to send.
|
*/
|
function sendData(param) {
|
let dataLen = param.data.length
|
let transNum = Math.ceil(dataLen / 128)
|
for (let i = 0; i < transNum; i++) {
|
let data = param.data.subarray(i * 128, (i + 1) * 128 > dataLen ? dataLen : (i + 1) * 128)
|
let childParam = {
|
header: HEADER, addr: ADDRESS, flag: new Uint8Array([FLAG.DATA]), len: new Uint8Array(toEndianHexExtended(2, 2, false))
|
, data: data
|
}
|
childParam.len = new Uint8Array(toEndianHexExtended(data.length + 2, 2, false))
|
if (i == transNum - 1) {
|
childParam.flag = new Uint8Array([FLAG.DATA_END])
|
} else {
|
childParam.flag = new Uint8Array([FLAG.DATA])
|
}
|
send(childParam)
|
os.sleep(100)
|
}
|
}
|
|
/**
|
* Receive Packet - Reads a single packet from UART.
|
* @private
|
* @param {Object} param - Parameters.
|
* @param {Uint8Array} param.addr - Expected address.
|
* @returns {Object|null} Received packet object, or null if invalid/timeout.
|
*/
|
function receive(param) {
|
const header1 = dxUart.receive(1, options.timeout, options.id);
|
if (!header1 || header1[0] !== 0xEF) {
|
std.sleep(50)
|
return null
|
}
|
const header2 = dxUart.receive(1, options.timeout, options.id);
|
if (!header2 || header2[0] !== 0x01) {
|
std.sleep(50)
|
return null
|
}
|
|
// Receive addr
|
const addr = dxUart.receive(4, options.timeout, options.id);
|
if (common.arrayBufferToHexString(addr) != common.arrayBufferToHexString(param.addr)) {
|
return null
|
}
|
|
// Receive flag
|
const flag = dxUart.receive(1, options.timeout, options.id);
|
|
// Receive len
|
const len = dxUart.receive(2, options.timeout, options.id);
|
|
// Receive data
|
const data = dxUart.receive((fromEndianHexExtended(len, 2, false)) - 2, options.timeout, options.id);
|
|
// Receive checksum
|
const checksumPkt = dxUart.receive(2, options.timeout, options.id);
|
|
// Verify checksum
|
let checksum = 0;
|
let packet = concatUint8Arrays(flag, len, data);
|
for (let i = 0; i < packet.length; i++) {
|
checksum += packet[i];
|
}
|
checksum = Math.min(checksum, 0xFFFF);
|
if (checksum != fromEndianHexExtended(checksumPkt, 2, false)) {
|
return null
|
}
|
|
let allPack = concatUint8Arrays(new Uint8Array([header1, header2]), addr, flag, len, data, checksumPkt);
|
|
return {
|
header: new Uint8Array([header1, header2]),
|
addr: addr,
|
flag: flag,
|
len: len,
|
data: data,
|
checksum: checksum
|
};
|
}
|
|
/**
|
* Receive Data Packet - Receives a multi-packet data stream.
|
* @private
|
* @param {Object} param - Parameters.
|
* @returns {Uint8Array} The complete data buffer.
|
*/
|
function recvDataPkt(param) {
|
let chunks = []; // Collect all chunks
|
let totalLength = 0;
|
let deadlineTime = Date.now() + 2000;
|
|
while (Date.now() < deadlineTime) {
|
const data = receive(param);
|
if (data) {
|
chunks.push(data.data);
|
totalLength += data.data.length;
|
} else {
|
break;
|
}
|
os.sleep(10);
|
}
|
|
// Merge all chunks
|
const packet = new Uint8Array(totalLength);
|
let offset = 0;
|
for (const chunk of chunks) {
|
packet.set(chunk, offset);
|
offset += chunk.length;
|
}
|
|
return packet;
|
}
|
|
/**
|
* Receive Command Packet - Attempts to receive a command response multiple times.
|
* @private
|
* @param {Object} param - Parameters.
|
* @returns {Object|null} Response packet, or null on failure.
|
*/
|
function recvCmdPkt(param) {
|
for (let i = 0; i < 5; i++) {
|
const packet = receive(param);
|
if (packet) {
|
return packet
|
}
|
os.sleep(10)
|
}
|
return null
|
}
|
|
/**
|
* Convert number to little-endian (default) hex byte array.
|
* @private
|
* @param {number} number - The number to convert.
|
* @param {number} bytes - Number of bytes.
|
* @param {boolean} isLittleEndian - Little endian flag.
|
* @returns {Uint8Array} The byte array.
|
*/
|
function toEndianHexExtended(number, bytes, isLittleEndian = true) {
|
const buffer = new Uint8Array(bytes);
|
for (let i = 0; i < bytes; i++) {
|
// Bitwise operation, taking 8 bits at a time
|
buffer[i] = (number >> (i * 8)) & 0xFF;
|
}
|
// If big endian, reverse the array
|
if (!isLittleEndian) {
|
buffer.reverse();
|
}
|
return buffer;
|
}
|
|
/**
|
* Convert byte array to number (supports little/big endian).
|
* @private
|
* @param {Uint8Array|number[]} bytesArray - The byte array.
|
* @param {number} bytes - Number of bytes.
|
* @param {boolean} isLittleEndian - Little endian flag.
|
* @returns {number} The converted number.
|
*/
|
function fromEndianHexExtended(bytesArray, bytes, isLittleEndian = true) {
|
// Parameter validation
|
if (!bytesArray || bytesArray.length !== bytes) {
|
throw new Error(`Invalid bytes array. Expected length: ${bytes}`);
|
}
|
|
let result = 0;
|
if (isLittleEndian) {
|
// Little endian: least significant byte first
|
for (let i = 0; i < bytes; i++) {
|
result |= (bytesArray[i] << (i * 8));
|
}
|
} else {
|
// Big endian: most significant byte first
|
for (let i = 0; i < bytes; i++) {
|
result = (result << 8) | bytesArray[i];
|
}
|
}
|
|
// Return as unsigned integer
|
return result >>> 0;
|
}
|
|
/**
|
* Concatenate multiple Uint8Arrays.
|
* @private
|
* @param {...Uint8Array} arrays - Arrays to concatenate.
|
* @returns {Uint8Array} The concatenated array.
|
*/
|
function concatUint8Arrays(...arrays) {
|
const validArrays = arrays.filter(arr => arr && arr.length > 0);
|
const totalLength = validArrays.reduce((acc, arr) => acc + arr.length, 0);
|
|
const result = new Uint8Array(totalLength);
|
let offset = 0;
|
validArrays.forEach(arr => {
|
result.set(arr, offset);
|
offset += arr.length;
|
});
|
return result
|
}
|
|
/**
|
* Find the earliest (smallest) unused template index in the fingerprint module
|
*
|
* @param {Uint8Array} buf The element is a 32-byte Uint8Array
|
* @returns {number} Return the available template number (0~1023). Returning -1 indicates no empty space.
|
*/
|
function findFreeFingerIndex(buf, page) {
|
|
// Traverse 32 bytes → 256 bits
|
for (let byteIndex = 0; byteIndex < 32; byteIndex++) {
|
let byte = buf[byteIndex];
|
|
// byte == 0xFF means all 8 bits are occupied, skip
|
if (byte === 0xFF) continue;
|
|
// Find which bit in byte is 0
|
for (let bit = 0; bit < 8; bit++) {
|
let used = (byte >> bit) & 1;
|
|
if (used === 0) {
|
// Global index = page offset + bit position
|
let globalIndex = page * 256 + byteIndex * 8 + bit;
|
return globalIndex;
|
}
|
}
|
}
|
|
return -1; // No empty space
|
}
|
|
export default dxFingerMz;
|