'use strict'; /** * H102 recovery script. * Sends RFM_SET_GET_READMODE to switch back to RFID mode. * Also tries factory-reset (RFM_REBOOT) if needed. * Run: node recover.js */ const { SerialPort } = require('serialport'); const PORT = process.env.RFID_PORT || '/dev/cu.usbserial-4130'; const BAUDS = [115200, 9600, 19200, 38400, 57600]; 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), addr = 0xFF) { const hdr = Buffer.from([0xCF, addr, cmd[0], cmd[1], data.length]); const body = Buffer.concat([hdr, data]); const chk = crc16(body); return Buffer.concat([body, Buffer.from([(chk >> 8) & 0xFF, chk & 0xFF])]); } const hex = b => Buffer.from(b).toString('hex').replace(/../g, '$& ').trim().toUpperCase(); // Commands const CMD_GET_PARAM = [0x00, 0x72]; const CMD_REBOOT = [0x00, 0x52]; const CMD_READMODE = [0x00, 0x8E]; // RFM_SET_GET_READMODE: set RFID mode (0x00 = RFID, 0x01 = barcode/QR) // SET format: OPTION(0x01=set) + READMODE(1) + RECEV(7 reserved zeros) const SET_RFID_MODE = frame(CMD_READMODE, Buffer.from([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])); const GET_READMODE = frame(CMD_READMODE, Buffer.from([0x02])); // OPTION=0x02 = read const FACTORY_RESET = frame(CMD_REBOOT, Buffer.alloc(0)); const GET_PARAM = frame(CMD_GET_PARAM, Buffer.alloc(0)); function tryAtBaud(baud, targetPort) { return new Promise(resolve => { console.log(`\n── Trying ${baud} baud on ${targetPort} ──`); const port = new SerialPort({ path: targetPort, baudRate: baud, autoOpen: false }); let resolved = false; port.open(err => { if (err) { console.log(` open failed: ${err.message}`); return resolve(false); } let rxBuf = Buffer.alloc(0); port.on('data', chunk => { rxBuf = Buffer.concat([rxBuf, chunk]); console.log(` RX: ${hex(chunk)}`); }); const done = ok => { if (resolved) return; resolved = true; setTimeout(() => port.close(() => resolve(ok)), 200); }; // Step 1: GET_ALL_PARAM — checks if device responds to serial at all console.log(` TX GET_PARAM: ${hex(GET_PARAM)}`); port.write(GET_PARAM); setTimeout(() => { if (rxBuf.length === 0) { console.log(` No response at ${baud} baud — trying next`); return done(false); } console.log(`\n ✓ Device responding at ${baud} baud!`); console.log(` Full RX so far: ${hex(rxBuf)}`); rxBuf = Buffer.alloc(0); // Step 2: GET current READMODE console.log(`\n TX GET_READMODE: ${hex(GET_READMODE)}`); port.write(GET_READMODE); setTimeout(() => { console.log(` READMODE response: ${hex(rxBuf)}`); // rxBuf[5]=STATUS, rxBuf[6]=Option, rxBuf[7]=READMODE value const readModeVal = rxBuf[7]; if (readModeVal === 0x01) { console.log('\n !! Device is in BARCODE/QR mode (READMODE=0x01).'); console.log(' Switching back to RFID mode...'); } else { console.log(`\n READMODE = 0x${(readModeVal||0).toString(16)} — may already be RFID mode, sending SET anyway`); } rxBuf = Buffer.alloc(0); // Step 3: SET RFID mode console.log(`\n TX SET_RFID_MODE: ${hex(SET_RFID_MODE)}`); port.write(SET_RFID_MODE); setTimeout(() => { console.log(` SET_RFID_MODE response: ${hex(rxBuf)}`); const setStatus = rxBuf[5]; if (setStatus === 0x00) { console.log('\n ✓ RFID mode restored! Unplug and replug the H102, then restart the daemon.'); } else { console.log(`\n SET_RFID_MODE returned status 0x${(setStatus||0).toString(16)}`); console.log(' Trying factory reset...'); rxBuf = Buffer.alloc(0); console.log(`\n TX FACTORY_RESET: ${hex(FACTORY_RESET)}`); port.write(FACTORY_RESET); setTimeout(() => { console.log(` FACTORY_RESET response: ${hex(rxBuf)}`); console.log('\n Factory reset sent. Unplug and replug the H102.'); done(true); }, 2000); return; } done(true); }, 2000); }, 1500); }, 2000); }); port.on('error', err => { console.log(` port error: ${err.message}`); if (!resolved) done(false); }); }); } async function main() { // Check if port exists const ports = await SerialPort.list(); console.log('Available ports:'); ports.forEach(p => console.log(` ${p.path} (${p.manufacturer || 'unknown'})`)); const usbPort = ports.find(p => /usbserial|usbmodem|ttyUSB|ttyACM/i.test(p.path)); const targetPort = process.env.RFID_PORT || (usbPort ? usbPort.path.replace('/dev/tty.', '/dev/cu.') : PORT); console.log(`\nTargeting: ${targetPort}`); for (const baud of BAUDS) { const ok = await tryAtBaud(baud, targetPort); if (ok) return; } console.log('\n━━━ No serial response at any baud rate ━━━'); console.log('The device may have switched to USB-HID keyboard mode entirely.'); console.log('\nHardware factory reset options for H102:'); console.log(' 1. Hold the scan TRIGGER button for 8-10 seconds while powered on'); console.log(' 2. Hold POWER + TRIGGER simultaneously for 5 seconds'); console.log(' 3. Check the back/bottom for a reset pinhole (use a paperclip)'); console.log(' 4. Connect via BLE: power cycle the device, then within 30 seconds'); console.log(' open the CfTech app → "BLE" → scan — the device only advertises'); console.log(' for ~30 seconds after power-on'); } main().catch(console.error);