'use strict'; // Chafon H102 probe: scan baud rates, attempt GET_ALL_PARAM, attempt SET interface to CDC_COM const { SerialPort } = require('serialport'); const PORT = process.env.RFID_PORT || '/dev/cu.usbserial-5130'; const BAUDS = [115200, 9600, 38400, 57600, 19200]; function crc16(buf) { let crc = 0xFFFF; for (const b of buf) { crc ^= b; for (let i = 0; i < 8; i++) crc = (crc & 1) ? ((crc >> 1) ^ 0x8408) : (crc >> 1); } return crc; } function frame(cmd, data = Buffer.alloc(0)) { const hdr = Buffer.from([0xCF, 0x00, cmd[0], cmd[1], data.length]); const body = Buffer.concat([hdr, data]); const chk = crc16(body); const f = Buffer.concat([body, Buffer.from([(chk >> 8) & 0xFF, chk & 0xFF])]); console.log(` TX: ${f.toString('hex').replace(/../g,'$& ').trim().toUpperCase()}`); return f; } const CMD_INVENTORY = [0x00, 0x01]; const CMD_GET_PARAM = [0x00, 0x72]; const CMD_SET_PARAM = [0x00, 0x71]; // AllParamBean minimal structure to set interface to CDC_COM (0x04) // Based on SDK doc: mInterface byte is first param byte. // We send a minimal known-safe config: keep everything default, just flip interface. // Structure (from SDK AllParamBean): interface(1) baud(1) addr(1) power(1) ... // We'll use GET_PARAM first to read existing values then patch interface byte. function tryPort(baud) { return new Promise(resolve => { console.log(`\n── Testing ${baud} baud ──`); const port = new SerialPort({ path: PORT, baudRate: baud, autoOpen: false }); port.open(err => { if (err) { console.log(` open error: ${err.message}`); return resolve(null); } let buf = Buffer.alloc(0); let resolved = false; const done = (result) => { if (resolved) return; resolved = true; port.close(() => resolve(result)); }; port.on('data', chunk => { const hex = chunk.toString('hex').replace(/../g,'$& ').trim().toUpperCase(); const asc = chunk.toString().replace(/[^\x20-\x7E]/g, '.'); console.log(` RX: ${hex} "${asc}"`); buf = Buffer.concat([buf, chunk]); }); // Send inventory first (simplest command) port.write(frame(CMD_INVENTORY)); setTimeout(() => { if (buf.length > 0) { console.log(` ✓ Got ${buf.length} bytes at ${baud} baud`); done({ baud, buf }); } else { console.log(` ✗ No response at ${baud} baud`); done(null); } }, 2000); }); }); } async function getAndPatchConfig(baud) { return new Promise(resolve => { console.log(`\n── GET_ALL_PARAM at ${baud} baud ──`); const port = new SerialPort({ path: PORT, baudRate: baud, autoOpen: false }); port.open(err => { if (err) return resolve(false); let buf = Buffer.alloc(0); port.on('data', chunk => { buf = Buffer.concat([buf, chunk]); const hex = chunk.toString('hex').replace(/../g,'$& ').trim().toUpperCase(); console.log(` RX: ${hex}`); }); port.write(frame(CMD_GET_PARAM)); setTimeout(() => { if (buf.length < 7) { console.log(' No config response received'); port.close(() => resolve(false)); return; } // Response: CF ADDR CMD_H CMD_L LEN STATUS DATA... CRC_H CRC_L // DATA is the AllParamBean bytes — patch interface byte (index 0 of data = STATUS+0) // status = buf[5], param data starts at buf[6] const status = buf[5]; console.log(` GET_PARAM status: 0x${status.toString(16)}`); if (status !== 0x00) { port.close(() => resolve(false)); return; } const paramData = buf.slice(6, buf.length - 2); // strip CRC console.log(` Param bytes: ${paramData.toString('hex').replace(/../g,'$& ').trim().toUpperCase()}`); console.log(` Current interface byte: 0x${paramData[0]?.toString(16)}`); // Patch interface byte to 0x04 (CDC_COM serial mode) paramData[0] = 0x04; console.log(` Setting interface to 0x04 (CDC_COM)...`); port.write(frame(CMD_SET_PARAM, paramData)); setTimeout(() => { const hex2 = buf.slice(paramData.length + 9).toString('hex').replace(/../g,'$& ').trim().toUpperCase(); console.log(` SET_PARAM response raw: ${hex2 || '(none yet)'}`); port.close(() => resolve(true)); }, 2000); }, 2000); }); }); } async function main() { console.log(`Probing ${PORT}`); console.log('Place a tag on the reader if you have one handy.\n'); let workingBaud = null; for (const baud of BAUDS) { const result = await tryPort(baud); if (result) { workingBaud = result.baud; break; } } if (!workingBaud) { console.log('\n━━━ No response at any baud rate ━━━'); console.log('Device is likely in USB-HID keyboard wedge mode.'); console.log('Serial port (CH340) is present but firmware is not routing data to it.'); console.log('\nFix: use Chafon CfTech Android app over BLE to change Interface setting'); console.log(' to "CDC_COM" (0x04) or "USB" (0x01), then reconnect USB.'); console.log('\nSafer option: use one of the shop Android tablets (not your Pixel).'); return; } console.log(`\n✓ Device communicating at ${workingBaud} baud`); await getAndPatchConfig(workingBaud); console.log('\nDone. If SET_PARAM succeeded, unplug and replug the H102 USB.'); console.log(`Then restart the daemon with: RFID_BAUD=${workingBaud} node index.js`); console.log('(or update BAUD_RATE in index.js if it differs from 115200)'); } main().catch(console.error);