npm package: @samlab-corp/sfm-node
Description: SFM Protocol V3.6.0 based fingerprint recognition library for Node.js
Author: Samlab Corp
Latest version: 0.6.0

# @samlab-corp/sfm-node

Node.js library for Suprema SFM (Suprema Fingerprint Module) devices based on SFM Protocol Manual V3.6.0.

Supports fingerprint enrollment, verification (1:1), identification (1:N), template management, firmware upgrade, and continuous scanning via Serial (RS232), RS422/485, or TCP/IP.

## Installation

```bash
npm install @samlab-corp/sfm-node
# or
yarn add @samlab-corp/sfm-node
```

### Requirements

- Node.js 18+
- A Suprema SFM fingerprint module connected via serial port or TCP/IP

## Quick Start

```typescript
import { SfmCommands } from "@samlab-corp/sfm-node";

// Create and connect in one step
const sfm = await SfmCommands.create({
  portPath: "/dev/ttyUSB0",
  baudRate: 115200,
});

// Enroll fingerprint (userId: 1)
await sfm.fingerprint.enroll(1);

// 1:1 Verification
await sfm.fingerprint.verify(1);

// 1:N Identification
await sfm.fingerprint.identify();

await sfm.disconnect();
```

## Constructor Options

```typescript
const sfm = new SfmCommands({
  portPath: "/dev/ttyUSB0", // Serial port path (used by create() / connect())
  baudRate: 115200, // Serial baud rate (default: 115200)
  debug: false, // Enable debug logging
  commandTimeout: 20000, // Command response timeout in ms
  readTimeout: 3000, // Data read timeout in ms
  secureMode: false, // AES256 encryption mode
  encryptionKey: Buffer, // AES256 encryption key (32 bytes)
  iv: Buffer, // AES256 IV (16 bytes)
  secureCode: Buffer, // Secure code
  encryptionMode: "AES256_CBC", // Encryption mode ('AES256_CBC' | 'AES256_ECB')
  networkMode: false, // RS422/485 network mode
  terminalId: 0, // Network terminal ID
  hexAsciiMode: false, // Hex-ASCII serial bridge mode
  autoReconnect: {
    // Auto-reconnect options
    enabled: true,
    interval: 3000,
    maxRetries: 10,
  },
});
```

## API Reference

### Connection Management

| Method                                     | Description                                   |
| ------------------------------------------ | --------------------------------------------- |
| `SfmCommands.create(options)`              | Create instance and connect in one step       |
| `SfmCommands.listPorts()`                  | List available serial ports                   |
| `sfm.connect(portPath?)`                   | Connect to device (uses portPath from options if omitted) |
| `sfm.disconnect()`                         | Disconnect from device                        |
| `sfm.setSecureMode(enabled, options?)`     | Toggle AES256 encryption mode                 |
| `sfm.setNetworkMode(enabled, terminalId?)` | Toggle RS422/485 network mode                 |
| `sfm.setOnDisconnect(callback)`            | Register disconnect callback                  |
| `sfm.setAutoReconnect(options)`            | Configure auto-reconnect                      |
| `sfm.setAutoReconnectCallbacks(callbacks)` | Register reconnect status callbacks           |
| `sfm.isReconnecting`                       | Whether reconnection is in progress (boolean) |

---

### sfm.fingerprint — Enrollment / Verification / Identification

| Method                                                      | Description                                                               | Return Type                        |
| ----------------------------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------- |
| `enroll(userId, flag?, options?)`                           | Enroll fingerprint (auto-adapts to device ENROLL_MODE: 1-scan or 2-scan)  | `EnrollResult`                     |
| `enrollScan1(userId, flag?, options?)`                      | First scan enrollment                                                     | `EnrollResult`                     |
| `enrollScan2(options?)`                                     | Second scan enrollment (for SCAN2 modes)                                  | `EnrollResult`                     |
| `verify(userId, options?)`                                  | 1:1 Verification — match against a specific user                          | `VerifyResult`                     |
| `identify(options?)`                                        | 1:N Identification — match against all enrolled fingerprints              | `IdentifyResult`                   |
| `deleteTemplate(userId)`                                    | Delete a specific user's fingerprint template                             | `CommandResult`                    |
| `deleteAllTemplates()`                                      | Delete all fingerprint templates                                          | `CommandResult`                    |
| `listUserIds(options?)`                                     | List enrolled user IDs                                                    | `ListUserIdsResult`                |
| `readTemplate(userId, options?)`                            | Read fingerprint template data from device (RTX protocol)                 | `{ success, templateData, error }` |
| `writeTemplate(userId, templateData, flag?, options?)`      | Write fingerprint template to device (ET protocol, 384B chunks)           | `{ success, userId, error }`       |
| `writeTemplateMulti(userId, templateData, flag?, options?)` | Write fingerprint template to device (ETX extended protocol, multi-chunk) | `{ success, userId, error }`       |
| `captureImage(options?)`                                    | Capture fingerprint image (SI command)                                    | `CaptureImageResult`               |
| `cancel()`                                                  | Cancel in-progress command (CA: 0x60). Sends packet only, no response wait | `CommandResult`                    |

#### Enrollment Flags

Flags used with `enroll`, `writeTemplate`, etc.:

| Flag                    | Value  | Description                     |
| ----------------------- | ------ | ------------------------------- |
| `SFM_FLAG.NONE`         | `0x00` | No flag (default)               |
| `SFM_FLAG.CHECK_ID`     | `0x70` | Check for duplicate ID          |
| `SFM_FLAG.ADD_NEW`      | `0x71` | Add fingerprint to existing ID  |
| `SFM_FLAG.AUTO_ID`      | `0x79` | Auto-assign ID                  |
| `SFM_FLAG.CHECK_FINGER` | `0x84` | Check for duplicate fingerprint |

#### Examples

```typescript
// Basic enrollment
const result = await sfm.fingerprint.enroll(1);

// Enroll with duplicate fingerprint check
const result = await sfm.fingerprint.enroll(1, SFM_FLAG.CHECK_FINGER);

// With log callback
const result = await sfm.fingerprint.verify(1, {
  onLog: (msg) => console.log(msg),
});

// Template backup / restore
const { templateData } = await sfm.fingerprint.readTemplate(1);
await sfm.fingerprint.writeTemplate(2, templateData, SFM_FLAG.ADD_NEW);

// List enrolled users
const list = await sfm.fingerprint.listUserIds();
// { success: true, userIds: [1, 2, 3], count: 3, error: null }

// Capture fingerprint image
const image = await sfm.fingerprint.captureImage();
// { success: true, imageData: Buffer, width: 256, height: 288, error: null }

// Cancel in-progress scan (e.g. user navigated away)
const enrollPromise = sfm.fingerprint.enrollScan1(userId);
await sfm.fingerprint.cancel();
const result = await enrollPromise;
// { success: false, cancelled: true, step: 1, error: '스캔 1 실패: 0x81' }
```

#### Return Types

```typescript
interface EnrollResult {
  success: boolean;
  cancelled?: boolean; // true if cancelled by CA command (0x81)
  step: number; // Current scan step (1 or 2)
  userId?: number;
  error?: string;
}

interface VerifyResult {
  success: boolean;
  userId?: number;
  error: string | null;
}

interface IdentifyResult {
  success: boolean;
  userId: number;
  error: string | null;
}

interface ListUserIdsResult {
  success: boolean;
  userIds: number[];
  count: number; // Number of enrolled templates
  error: string | null;
}

interface CaptureImageResult {
  success: boolean;
  imageData: Buffer | null;
  width: number;
  height: number;
  error: string | null;
}
```

---

### sfm.system — System Configuration / Diagnostics

| Method                          | Description                                                   | Return Type                                             |
| ------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------- |
| `getSystemStatus()`             | Get module and sensor status                                  | `SystemStatusResult`                                    |
| `getDeviceInfo()`               | Get firmware version, build number, serial number             | `{ firmwareVersion, buildNumber, serialNumber, error }` |
| `getParam(key)`                 | Read system parameter                                         | `ParamResult`                                           |
| `setParam(key, value)`          | Write system parameter (RAM only)                             | `CommandResult`                                         |
| `writeConfig(key, value)`       | Write system parameter + save to flash (setParam + saveParam) | `CommandResult`                                         |
| `saveParam()`                   | Save current parameters to flash                              | `CommandResult`                                         |
| `reset()`                       | Reset module                                                  | `CommandResult`                                         |
| `selfTest(command?, dataSize?)` | Communication self-test (data round-trip verification)        | `SelfTestResult`                                        |

#### Examples

```typescript
// Device info
const info = await sfm.system.getDeviceInfo();
// { firmwareVersion: '3.10', buildNumber: '19.7.9.32', serialNumber: 12345, error: null }

// System status
const status = await sfm.system.getSystemStatus();
// { success: true, moduleStatusLabel: 'ALIVE', sensorStatusLabel: 'ALIVE', ... }

// Read parameter
const param = await sfm.system.getParam("SECURITY_LEVEL");
// { value: 0x33, error: null }

// Write parameter (string alias or numeric value)
await sfm.system.setParam("SECURITY_LEVEL", "HIGH");
await sfm.system.saveParam();

// Or use writeConfig to write + save in one call
await sfm.system.writeConfig("ENROLL_MODE", "SCAN2_MERGE");

// Self-test
const test = await sfm.system.selfTest();
// { success: true, sent: 65536, received: 65536, elapsed: 1200, error: null }
```

#### Return Types

```typescript
interface SystemStatusResult {
  success: boolean;
  moduleStatus?: number; // 0x30=ALIVE, 0x34=BUSY
  moduleStatusLabel?: string;
  sensorStatus?: number; // 0x30=ALIVE, 0x31=ERROR
  sensorStatusLabel?: string;
  error: string | null;
}

interface ParamResult {
  value: number;
  error: string | null;
}

interface CommandResult {
  success: boolean;
  error: string | null;
}

interface SelfTestResult {
  success: boolean;
  sent: number;
  received: number;
  elapsed: number; // ms
  error: string | null;
}
```

---

### sfm.freeScan — Continuous Fingerprint Scanning Mode

The device automatically scans fingerprints and delivers identification results via callback.

| Method                     | Description                                                | Return Type            |
| -------------------------- | ---------------------------------------------------------- | ---------------------- |
| `start()`                  | Start FreeScan (auto-configures AUTO_RESPONSE + FREE_SCAN) | `{ success, message }` |
| `stop()`                   | Stop FreeScan                                              | `{ success, message }` |
| `isRunning`                | Whether FreeScan is active (boolean)                       | —                      |
| `isDeviceFreeScanActive()` | Check if device FREE_SCAN parameter is ON                  | `boolean`              |
| `onDataHandler(handler)`   | Register external data handler (`FreeScanDataHandler`)     | —                      |
| `offDataHandler()`         | Remove external data handler                               | —                      |
| `onErrorHandler(handler)`  | Register external error handler (`FreeScanErrorHandler`)   | —                      |
| `offErrorHandler()`        | Remove external error handler                              | —                      |

#### Examples

```typescript
// Register data handler
sfm.freeScan.onDataHandler((result) => {
  if (result.success) {
    console.log(`Identified user: ${result.userId}`);
  } else {
    console.log("Unregistered fingerprint");
  }
});

// Register error handler (optional)
sfm.freeScan.onErrorHandler((error) => {
  console.error("FreeScan error:", error);
});

// Start FreeScan
await sfm.freeScan.start();

// Check status
console.log(sfm.freeScan.isRunning); // true

// Stop FreeScan
await sfm.freeScan.stop();

// Remove handlers when no longer needed
sfm.freeScan.offDataHandler();
sfm.freeScan.offErrorHandler();
```

#### Callback Types

```typescript
interface FreeScanResult {
  success: boolean;
  userId?: number; // User ID on successful identification
  flag?: number; // Error flag on failure
}

type FreeScanDataHandler = (result: FreeScanResult) => void;
type FreeScanErrorHandler = (error?: Error) => void;
```

---

### sfm.firmware — Firmware Upgrade

| Method                                               | Description                              |
| ---------------------------------------------------- | ---------------------------------------- |
| `upgrade(filePath, options?)`                        | Firmware upgrade via serial handshake    |
| `upgradeViaProtocol(filePath, chunkSize?, options?)` | Firmware upgrade via SFM packet protocol |
| `loadFirmware(filePath)`                             | Load firmware file and build packet      |

#### Examples

```typescript
// Serial handshake method (typical)
await sfm.firmware.upgrade("/path/to/firmware.bin", {
  onProgress: (percent, sent, total) => console.log(`${percent}%`),
  onStatus: (msg) => console.log(msg),
});

// Protocol method
await sfm.firmware.upgradeViaProtocol("/path/to/firmware.bin");
```

#### Options Type

```typescript
interface FirmwareOptions {
  onProgress?: (percent: number, sent: number, total: number) => void;
  onStatus?: (message: string) => void;
}
```

---

### sfm.data — Multi-Packet Data Transfer

Low-level multi-packet data transfer. Handles checksum verification and AES256 encryption automatically.

| Method                                      | Description               |
| ------------------------------------------- | ------------------------- |
| `send(command, data, chunkSize?, options?)` | Send multi-packet data    |
| `recv(command, totalSize, options?)`        | Receive multi-packet data |

```typescript
await sfm.data.send(command, buffer, 0x4000, {
  onProgress: (packetIndex, totalPackets) => { ... },
});

const data = await sfm.data.recv(command, totalSize);
```

---

## System Parameter Key Reference

Keys and available values for `sfm.system.getParam(key)`, `sfm.system.setParam(key, value)`, and `sfm.system.writeConfig(key, value)`.

### Timeout / Timing

| Key                | Description                            | Values                                                                          |
| ------------------ | -------------------------------------- | ------------------------------------------------------------------------------- |
| `TIMEOUT`          | Fingerprint input wait time            | `'INFINITE'`, `'1S'`, `'2S'`, `'3S'`, `'4S'`, `'5S'`, `'10S'`, `'15S'`, `'20S'` |
| `MATCHING_TIMEOUT` | Matching timeout                       | `'INFINITE'`, `'1S'`, `'3S'`, `'5S'`, `'10S'`, `'20S'`                          |
| `RESPONSE_DELAY`   | Response delay                         | `'NONE'`, `'20MS'`, `'40MS'`, `'60MS'`, `'80MS'`, `'100MS'`, `'200MS'`          |
| `FREE_SCAN_DELAY`  | FreeScan consecutive recognition delay | `'NONE'`, `'1S'`, `'2S'`, `'3S'`, `'5S'`, `'10S'`                               |

### Enrollment / Authentication

| Key                   | Description                            | Values                                                                                                |
| --------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `ENROLL_MODE`         | Enrollment mode                        | `'SCAN1'` (single scan), `'SCAN2_MERGE'` (2 scans merged), `'SCAN2_DUAL'` (2 scans stored separately) |
| `SECURITY_LEVEL`      | Security level (FAR)                   | `'LOW'`, `'NORMAL'`, `'HIGH'`, `'HIGHEST'`, `'AUTO_NORMAL'`, `'AUTO_SECURE'`, `'AUTO_MORE_SECURE'`    |
| `ENROLL_DISPLACEMENT` | Finger displacement during enrollment  | `'NONE'`, `'1MM'`, `'3MM'`, `'5MM'`, `'10MM'`                                                         |
| `PROVISIONAL_ENROLL`  | Provisional enrollment mode            | `'PERMANENT'`, `'TEMPORARY'`                                                                          |
| `PASS_WHEN_EMPTY`     | Authentication result when DB is empty | `'FAIL'`, `'PASS'`                                                                                    |

### Sensor / Image

| Key                  | Description                 | Values                                                           |
| -------------------- | --------------------------- | ---------------------------------------------------------------- |
| `SENSOR_TYPE`        | Sensor type (read-only)     | `'FC'`, `'OPTICAL'`, `'TC'`, `'OC'`, `'OL'`, `'FOH02'`, `'TS4'`  |
| `SENSITIVITY`        | Sensor sensitivity          | `'LV0'` ~ `'LV7'`                                                |
| `IMAGE_FORMAT`       | Image transfer format       | `'RAW_GRAY'`, `'BINARY'`, `'GRAY_4BIT'`, `'WSQ'`                 |
| `IMAGE_QUALITY`      | Image quality threshold     | `'WEAK'`, `'MODERATE'`, `'STRONG'`, `'STRONGEST'`                |
| `ROTATE_IMAGE`       | Sensor image rotation       | `'NORMAL'`, `'FLIP'`                                             |
| `ROTATION`           | Matching rotation tolerance | `'DEG15'`, `'DEG30'`, `'DEG45'`, `'DEG60'`, `'DEG75'`, `'DEG90'` |
| `LIGHTING_CONDITION` | Lighting environment        | `'OUTDOOR'`, `'INDOOR'`                                          |

### Communication

| Key                 | Description                | Values                                                                                    |
| ------------------- | -------------------------- | ----------------------------------------------------------------------------------------- |
| `BAUDRATE`          | Baud rate                  | `'9600'`, `'19200'`, `'38400'`, `'57600'`, `'115200'`, `'230400'`, `'460800'`, `'921600'` |
| `ENCRYPTION_MODE`   | Encryption mode            | `'OFF'`, `'ON'`                                                                           |
| `ASCII_PACKET`      | Packet exchange format     | `'HEX'`, `'ASCII'`                                                                        |
| `NETWORK_MODE`      | Network communication mode | `'SINGLE'`, `'NETWORK'`                                                                   |
| `SEND_SCAN_SUCCESS` | Send SCAN_SUCCESS response | `'OFF'`, `'ON'`                                                                           |

### Operation Mode

| Key             | Description                 | Values                                            |
| --------------- | --------------------------- | ------------------------------------------------- |
| `FREE_SCAN`     | Idle-state auto scan        | `'OFF'`, `'ON'`                                   |
| `AUTO_RESPONSE` | GPIO/FreeScan auto response | `'OFF'`, `'HOST'`, `'AUX'`, `'BOTH'`              |
| `FAST_MODE`     | 1:N matching speed boost    | `'OFF'`, `'LV1'` ~ `'LV4'`, `'FASTEST'`, `'AUTO'` |
| `WATCHDOG`      | Watchdog timer              | `'OFF'`, `'ON'`                                   |

### Template / Security

| Key                        | Description                  | Values                                    |
| -------------------------- | ---------------------------- | ----------------------------------------- |
| `TEMPLATE_TYPE`            | Template type                | `'SUPREMA'`, `'ISO'`, `'ANSI'`            |
| `ENHANCED_PRIVACY`         | Enhanced privacy mode        | `'OFF'`, `'ON'`                           |
| `FAKE_FINGER_DETECTION`    | Fake finger detection        | `'OFF'`, `'WEAK'`, `'NORMAL'`, `'STRONG'` |
| `CHECK_LATENT_FINGERPRINT` | Latent fingerprint detection | `'ENROLL_ONLY'`, `'NEVER'`, `'ALWAYS'`    |

### Read-Only Parameters

| Key                | Description                           |
| ------------------ | ------------------------------------- |
| `TEMPLATE_SIZE`    | Template size                         |
| `MODULE_ID`        | Module ID                             |
| `FIRMWARE_VERSION` | Firmware version                      |
| `SERIAL_NUMBER`    | Serial number                         |
| `BUILD_NUMBER`     | Build number                          |
| `ENROLLED_FINGER`  | Number of enrolled fingerprints       |
| `AVAILABLE_FINGER` | Number of available fingerprint slots |
| `BAUDRATE2`        | Secondary baud rate                   |
| `VOLTAGE_WARNING`  | Voltage warning threshold             |
| `POWER_OFF`        | Auto power-off timeout                |

---

## Error Codes

| Code                     | Value  | Description                          |
| ------------------------ | ------ | ------------------------------------ |
| `SFM_ERROR.SUCCESS`      | `0x61` | Success                              |
| `SFM_ERROR.SCAN_SUCCESS` | `0x62` | Scan success (intermediate response) |
| `SFM_ERROR.SCAN_FAIL`    | `0x63` | Scan failed                          |
| `SFM_ERROR.NOT_FOUND`    | `0x69` | Data not found                       |
| `SFM_ERROR.NOT_MATCH`    | `0x6a` | Mismatch                             |
| `SFM_ERROR.TRY_AGAIN`    | `0x6b` | Retry needed (poor image quality)    |
| `SFM_ERROR.TIME_OUT`     | `0x6c` | Timeout                              |
| `SFM_ERROR.MEM_FULL`     | `0x6d` | Memory full                          |
| `SFM_ERROR.EXIST_ID`     | `0x6e` | ID already exists                    |
| `SFM_ERROR.CONTINUE`     | `0x74` | Continue data transfer               |
| `SFM_ERROR.UNSUPPORTED`  | `0x75` | Unsupported command                  |

## TypeScript

All types are exported:

```typescript
import {
  SfmCommands,
  SFM_ERROR,
  SFM_FLAG,
  ENCRYPTION_MODE,
} from "@samlab-corp/sfm-node";

import type {
  SfmClientOptions,
  SfmParamKey,
  SfmParamAliasMap,
  EncryptionMode,
  SerialPortInfo,
  EnrollResult,
  VerifyResult,
  IdentifyResult,
  CommandResult,
  ListUserIdsResult,
  CaptureImageResult,
  SystemStatusResult,
  ParamResult,
  SelfTestResult,
  FreeScanResult,
  FreeScanDataHandler,
  FreeScanErrorHandler,
  FirmwareOptions,
} from "@samlab-corp/sfm-node";
```

## License

[MIT](LICENSE)