/** * 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;