@samlab-corp/sfm-node 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/commands/data.d.ts +4 -4
  2. package/dist/commands/data.js +12 -12
  3. package/dist/commands/data.js.map +1 -1
  4. package/dist/commands/fingerprint.d.ts +23 -23
  5. package/dist/commands/fingerprint.d.ts.map +1 -1
  6. package/dist/commands/fingerprint.js +114 -114
  7. package/dist/commands/fingerprint.js.map +1 -1
  8. package/dist/commands/firmware.d.ts +5 -5
  9. package/dist/commands/firmware.d.ts.map +1 -1
  10. package/dist/commands/firmware.js +32 -32
  11. package/dist/commands/firmware.js.map +1 -1
  12. package/dist/commands/freescan.d.ts +16 -16
  13. package/dist/commands/freescan.d.ts.map +1 -1
  14. package/dist/commands/freescan.js +41 -41
  15. package/dist/commands/freescan.js.map +1 -1
  16. package/dist/commands/index.d.ts +12 -12
  17. package/dist/commands/index.d.ts.map +1 -1
  18. package/dist/commands/index.js +14 -14
  19. package/dist/commands/index.js.map +1 -1
  20. package/dist/commands/system.d.ts +12 -12
  21. package/dist/commands/system.d.ts.map +1 -1
  22. package/dist/commands/system.js +14 -14
  23. package/dist/commands/system.js.map +1 -1
  24. package/dist/constants/protocol.d.ts +25 -0
  25. package/dist/constants/protocol.d.ts.map +1 -1
  26. package/dist/constants/protocol.js +26 -1
  27. package/dist/constants/protocol.js.map +1 -1
  28. package/dist/core/client.d.ts +33 -21
  29. package/dist/core/client.d.ts.map +1 -1
  30. package/dist/core/client.js +72 -52
  31. package/dist/core/client.js.map +1 -1
  32. package/dist/core/crypto.d.ts +19 -19
  33. package/dist/core/crypto.js +26 -26
  34. package/dist/core/crypto.js.map +1 -1
  35. package/dist/core/errors.d.ts +6 -6
  36. package/dist/core/errors.d.ts.map +1 -1
  37. package/dist/core/errors.js +46 -32
  38. package/dist/core/errors.js.map +1 -1
  39. package/dist/core/index.d.ts +3 -3
  40. package/dist/core/index.d.ts.map +1 -1
  41. package/dist/core/index.js +5 -2
  42. package/dist/core/index.js.map +1 -1
  43. package/dist/core/packet.d.ts +81 -41
  44. package/dist/core/packet.d.ts.map +1 -1
  45. package/dist/core/packet.js +256 -60
  46. package/dist/core/packet.js.map +1 -1
  47. package/dist/core/serial.d.ts +37 -27
  48. package/dist/core/serial.d.ts.map +1 -1
  49. package/dist/core/serial.js +91 -62
  50. package/dist/core/serial.js.map +1 -1
  51. package/dist/core/tcp.d.ts +14 -14
  52. package/dist/core/tcp.js +17 -17
  53. package/dist/core/tcp.js.map +1 -1
  54. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Fingerprint enroll/verify/identify/capture commands
3
+ * 지문 등록/검증/식별/캡처 명령
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.FingerprintCommands = void 0;
@@ -11,12 +11,12 @@ class FingerprintCommands {
11
11
  this.client = client;
12
12
  this.system = null;
13
13
  }
14
- /** @internal Set SystemCommands reference */
14
+ /** @internal SystemCommands 참조 설정 */
15
15
  _setSystemCommands(system) {
16
16
  this.system = system;
17
17
  }
18
18
  /**
19
- * Enroll scan 1 - send ES command + read 0x62 (scan success) + 0x61 (template 1 saved)
19
+ * 등록 스캔 1 - ES 명령 전송 + 0x62 (스캔 성공) + 0x61 (템플릿 1 저장) 수신
20
20
  */
21
21
  async enrollScan1(userId, flag = index_js_1.SFM_FLAG.NONE, { onLog } = {}) {
22
22
  if (userId == null) {
@@ -25,9 +25,9 @@ class FingerprintCommands {
25
25
  const log = (msg) => {
26
26
  onLog?.(msg);
27
27
  };
28
- log(`[ENROLL] Scan 1 started - userId: ${userId}`);
28
+ log(`[ENROLL] 스캔 1 시작 - userId: ${userId}`);
29
29
  const response = await this.client.sendCommand(index_js_1.SFM_CMD.ES, userId, 0, flag);
30
- log(`[ENROLL] Response: flag=0x${response.flag?.toString(16)}, param=${response.param}, valid=${response.valid}`);
30
+ log(`[ENROLL] 응답: flag=0x${response.flag?.toString(16)}, param=${response.param}, valid=${response.valid}`);
31
31
  if (!response.valid) {
32
32
  return { success: false, step: 1, error: response.error || "unknown error" };
33
33
  }
@@ -35,33 +35,33 @@ class FingerprintCommands {
35
35
  return {
36
36
  success: false,
37
37
  step: 1,
38
- error: `Scan 1 failed: 0x${response.flag?.toString(16) ?? "??"}`,
38
+ error: `스캔 1 실패: 0x${response.flag?.toString(16) ?? "??"}`,
39
39
  };
40
40
  }
41
- log(`[ENROLL] Scan 1 success (0x62), waiting for template save...`);
41
+ log(`[ENROLL] 스캔 1 성공 (0x62), 템플릿 저장 대기 중...`);
42
42
  const saveResponse = await this.client.receiveResponse(this.client.commandTimeout);
43
- log(`[ENROLL] Save 1 response: flag=0x${saveResponse.flag?.toString(16)}, valid=${saveResponse.valid}`);
43
+ log(`[ENROLL] 저장 1 응답: flag=0x${saveResponse.flag?.toString(16)}, valid=${saveResponse.valid}`);
44
44
  if (!saveResponse.valid || saveResponse.flag !== index_js_1.SFM_ERROR.SUCCESS) {
45
45
  return {
46
46
  success: false,
47
47
  step: 1,
48
48
  error: saveResponse.error ||
49
- `Save 1 failed: 0x${saveResponse.flag?.toString(16)}`,
49
+ `저장 1 실패: 0x${saveResponse.flag?.toString(16)}`,
50
50
  };
51
51
  }
52
- log(`[ENROLL] Template 1 saved`);
52
+ log(`[ENROLL] 템플릿 1 저장 완료`);
53
53
  return { success: true, step: 1, userId: saveResponse.param ?? 0 };
54
54
  }
55
55
  /**
56
- * Enroll scan 2 - read 0x62 (scan success) + 0x61 (template 2 saved)
56
+ * 등록 스캔 2 - 0x62 (스캔 성공) + 0x61 (템플릿 2 저장) 수신
57
57
  */
58
58
  async enrollScan2({ onLog } = {}) {
59
59
  const log = (msg) => {
60
60
  onLog?.(msg);
61
61
  };
62
- log(`[ENROLL] Waiting for scan 2...`);
62
+ log(`[ENROLL] 스캔 2 대기 중...`);
63
63
  const scanResponse = await this.client.receiveResponse(this.client.commandTimeout);
64
- log(`[ENROLL] Scan 2 response: flag=0x${scanResponse.flag?.toString(16)}, valid=${scanResponse.valid}`);
64
+ log(`[ENROLL] 스캔 2 응답: flag=0x${scanResponse.flag?.toString(16)}, valid=${scanResponse.valid}`);
65
65
  if (!scanResponse.valid) {
66
66
  return { success: false, step: 2, error: scanResponse.error || "unknown error" };
67
67
  }
@@ -69,35 +69,35 @@ class FingerprintCommands {
69
69
  return {
70
70
  success: false,
71
71
  step: 2,
72
- error: `Scan 2 failed: 0x${scanResponse.flag?.toString(16) ?? "??"}`,
72
+ error: `스캔 2 실패: 0x${scanResponse.flag?.toString(16) ?? "??"}`,
73
73
  };
74
74
  }
75
- log(`[ENROLL] Scan 2 success (0x62), waiting for template save...`);
75
+ log(`[ENROLL] 스캔 2 성공 (0x62), 템플릿 저장 대기 중...`);
76
76
  const saveResponse = await this.client.receiveResponse(this.client.commandTimeout);
77
- log(`[ENROLL] Save 2 response: flag=0x${saveResponse.flag?.toString(16)}, valid=${saveResponse.valid}`);
77
+ log(`[ENROLL] 저장 2 응답: flag=0x${saveResponse.flag?.toString(16)}, valid=${saveResponse.valid}`);
78
78
  if (!saveResponse.valid || saveResponse.flag !== index_js_1.SFM_ERROR.SUCCESS) {
79
79
  return {
80
80
  success: false,
81
81
  step: 2,
82
82
  error: saveResponse.error ||
83
- `Save 2 failed: 0x${saveResponse.flag?.toString(16)}`,
83
+ `저장 2 실패: 0x${saveResponse.flag?.toString(16)}`,
84
84
  };
85
85
  }
86
- log(`[ENROLL] Template 2 saved - enrollment complete`);
86
+ log(`[ENROLL] 템플릿 2 저장 완료 - 등록 완료`);
87
87
  return { success: true, step: 2, userId: saveResponse.param ?? 0 };
88
88
  }
89
89
  /**
90
- * Enroll (automatically adapts to current ENROLL_MODE setting)
90
+ * 등록 (현재 ENROLL_MODE 설정에 자동 적응)
91
91
  *
92
- * - SCAN1 (0x30): 1 scan only
93
- * - SCAN2_MERGE (0x31) / SCAN2_DUAL (0x41): scan 1 + scan 2
92
+ * - SCAN1 (0x30): 1 스캔만
93
+ * - SCAN2_MERGE (0x31) / SCAN2_DUAL (0x41): 스캔 1 + 스캔 2
94
94
  */
95
95
  async enroll(userId, flag = index_js_1.SFM_FLAG.NONE, { onLog } = {}) {
96
96
  const log = (msg) => {
97
97
  onLog?.(msg);
98
98
  };
99
- // Read current ENROLL_MODE from device
100
- let enrollMode = 0x31; // default: SCAN2_MERGE
99
+ // 디바이스에서 현재 ENROLL_MODE 읽기
100
+ let enrollMode = 0x31; // 기본값: SCAN2_MERGE
101
101
  if (this.system) {
102
102
  const param = await this.system.getParam("ENROLL_MODE");
103
103
  if (!param.error && param.value != null) {
@@ -108,12 +108,12 @@ class FingerprintCommands {
108
108
  const result1 = await this.enrollScan1(userId, flag, { onLog });
109
109
  if (!result1.success)
110
110
  return result1;
111
- // SCAN1 mode: single scan is enough
111
+ // SCAN1 모드: 1회 스캔으로 충분
112
112
  if (enrollMode === 0x30) {
113
- log("[ENROLL] SCAN1 modeenrollment complete");
113
+ log("[ENROLL] SCAN1 모드등록 완료");
114
114
  return result1;
115
115
  }
116
- // SCAN2_MERGE / SCAN2_DUAL: need second scan
116
+ // SCAN2_MERGE / SCAN2_DUAL: 2차 스캔 필요
117
117
  return this.enrollScan2({ onLog });
118
118
  }
119
119
  /**
@@ -126,19 +126,19 @@ class FingerprintCommands {
126
126
  const log = (msg) => {
127
127
  onLog?.(msg);
128
128
  };
129
- log(`[VERIFY] 1:1 verification started - userId: ${userId}`);
129
+ log(`[VERIFY] 1:1 검증 시작 - userId: ${userId}`);
130
130
  const response = await this.client.sendCommand(index_js_1.SFM_CMD.VS, userId, 0, 0);
131
- log(`[VERIFY] Response: flag=0x${response.flag?.toString(16)}, param=${response.param}, valid=${response.valid}`);
131
+ log(`[VERIFY] 응답: flag=0x${response.flag?.toString(16)}, param=${response.param}, valid=${response.valid}`);
132
132
  if (!response.valid) {
133
133
  return { success: false, error: response.error || "unknown error" };
134
134
  }
135
135
  let lastResponse = response;
136
136
  let scanCount = 1;
137
137
  while (lastResponse.flag === index_js_1.SFM_ERROR.SCAN_SUCCESS) {
138
- log(`[VERIFY] Scan #${scanCount} success (0x62), waiting for match...`);
138
+ log(`[VERIFY] 스캔 #${scanCount} 성공 (0x62), 매칭 대기 중...`);
139
139
  lastResponse = await this.client.receiveResponse(this.client.commandTimeout);
140
140
  scanCount++;
141
- log(`[VERIFY] Response #${scanCount}: flag=0x${lastResponse.flag?.toString(16)}, param=${lastResponse.param}, valid=${lastResponse.valid}`);
141
+ log(`[VERIFY] 응답 #${scanCount}: flag=0x${lastResponse.flag?.toString(16)}, param=${lastResponse.param}, valid=${lastResponse.valid}`);
142
142
  if (!lastResponse.valid) {
143
143
  return { success: false, error: lastResponse.error || "unknown error" };
144
144
  }
@@ -147,7 +147,7 @@ class FingerprintCommands {
147
147
  success: lastResponse.flag === index_js_1.SFM_ERROR.SUCCESS,
148
148
  userId: lastResponse.param,
149
149
  error: lastResponse.flag !== index_js_1.SFM_ERROR.SUCCESS
150
- ? `Verification failed: 0x${lastResponse.flag?.toString(16) ?? "??"}`
150
+ ? `검증 실패: 0x${lastResponse.flag?.toString(16) ?? "??"}`
151
151
  : null,
152
152
  };
153
153
  }
@@ -158,19 +158,19 @@ class FingerprintCommands {
158
158
  const log = (msg) => {
159
159
  onLog?.(msg);
160
160
  };
161
- log(`[IDENTIFY] 1:N identification started`);
161
+ log(`[IDENTIFY] 1:N 식별 시작`);
162
162
  const response = await this.client.sendCommand(index_js_1.SFM_CMD.IS, 0, 0, 0);
163
- log(`[IDENTIFY] Response: flag=0x${response.flag?.toString(16)}, param=${response.param}, valid=${response.valid}`);
163
+ log(`[IDENTIFY] 응답: flag=0x${response.flag?.toString(16)}, param=${response.param}, valid=${response.valid}`);
164
164
  if (!response.valid) {
165
165
  return { success: false, userId: 0, error: response.error || "unknown error" };
166
166
  }
167
167
  let lastResponse = response;
168
168
  let scanCount = 1;
169
169
  while (lastResponse.flag === index_js_1.SFM_ERROR.SCAN_SUCCESS) {
170
- log(`[IDENTIFY] Scan #${scanCount} success (0x62), waiting for match...`);
170
+ log(`[IDENTIFY] 스캔 #${scanCount} 성공 (0x62), 매칭 대기 중...`);
171
171
  lastResponse = await this.client.receiveResponse(this.client.commandTimeout);
172
172
  scanCount++;
173
- log(`[IDENTIFY] Response #${scanCount}: flag=0x${lastResponse.flag?.toString(16)}, param=${lastResponse.param}, valid=${lastResponse.valid}`);
173
+ log(`[IDENTIFY] 응답 #${scanCount}: flag=0x${lastResponse.flag?.toString(16)}, param=${lastResponse.param}, valid=${lastResponse.valid}`);
174
174
  if (!lastResponse.valid) {
175
175
  return { success: false, userId: 0, error: lastResponse.error || "unknown error" };
176
176
  }
@@ -179,12 +179,12 @@ class FingerprintCommands {
179
179
  success: lastResponse.flag === index_js_1.SFM_ERROR.SUCCESS,
180
180
  userId: lastResponse.param ?? 0,
181
181
  error: lastResponse.flag !== index_js_1.SFM_ERROR.SUCCESS
182
- ? `Identification failed: 0x${lastResponse.flag?.toString(16) ?? "??"}`
182
+ ? `식별 실패: 0x${lastResponse.flag?.toString(16) ?? "??"}`
183
183
  : null,
184
184
  };
185
185
  }
186
186
  /**
187
- * Delete specific template (DT: 0x16)
187
+ * 특정 템플릿 삭제 (DT: 0x16)
188
188
  */
189
189
  async deleteTemplate(userId) {
190
190
  if (userId == null) {
@@ -197,12 +197,12 @@ class FingerprintCommands {
197
197
  return {
198
198
  success: response.flag === index_js_1.SFM_ERROR.SUCCESS,
199
199
  error: response.flag !== index_js_1.SFM_ERROR.SUCCESS
200
- ? `Delete failed: 0x${response.flag?.toString(16) ?? "??"}`
200
+ ? `삭제 실패: 0x${response.flag?.toString(16) ?? "??"}`
201
201
  : null,
202
202
  };
203
203
  }
204
204
  /**
205
- * Delete all templates (DA: 0x17)
205
+ * 전체 템플릿 삭제 (DA: 0x17)
206
206
  */
207
207
  async deleteAllTemplates() {
208
208
  const response = await this.client.sendCommand(index_js_1.SFM_CMD.DA, 0, 0, 0);
@@ -211,17 +211,17 @@ class FingerprintCommands {
211
211
  }
212
212
  return {
213
213
  success: response.flag === index_js_1.SFM_ERROR.SUCCESS,
214
- error: response.flag !== index_js_1.SFM_ERROR.SUCCESS ? "Delete all failed" : null,
214
+ error: response.flag !== index_js_1.SFM_ERROR.SUCCESS ? "전체 삭제 실패" : null,
215
215
  };
216
216
  }
217
217
  /**
218
- * List enrolled user IDs (LT: 0x18)
218
+ * 등록된 사용자 ID 목록 조회 (LT: 0x18)
219
219
  */
220
220
  async listUserIds({ onLog } = {}) {
221
221
  const log = (msg) => {
222
222
  onLog?.(msg);
223
223
  };
224
- log("[LIST] Fetching user ID list");
224
+ log("[LIST] 사용자 ID 목록 조회 중");
225
225
  const response = await this.client.sendCommand(index_js_1.SFM_CMD.LT, 0, 0, 0);
226
226
  if (!response.valid) {
227
227
  return { success: false, userIds: [], count: 0, error: response.error || "unknown error" };
@@ -231,36 +231,36 @@ class FingerprintCommands {
231
231
  success: false,
232
232
  userIds: [],
233
233
  count: 0,
234
- error: `List failed: 0x${response.flag?.toString(16) ?? "??"}`,
234
+ error: `목록 조회 실패: 0x${response.flag?.toString(16) ?? "??"}`,
235
235
  };
236
236
  }
237
237
  const count = response.param ?? 0;
238
238
  const dataSize = response.size ?? 0;
239
- log(`[LIST] Enrolled users: ${count}, data size: ${dataSize}`);
239
+ log(`[LIST] 등록 사용자: ${count}, 데이터 크기: ${dataSize}`);
240
240
  if (count === 0 || dataSize === 0) {
241
241
  return { success: true, userIds: [], count: 0, error: null };
242
242
  }
243
243
  const rawData = await this.client.serial.read(dataSize, this.client.commandTimeout);
244
244
  if (rawData.length < dataSize) {
245
- return { success: false, userIds: [], count: 0, error: `Incomplete data: received ${rawData.length}/${dataSize} bytes` };
245
+ return { success: false, userIds: [], count: 0, error: `불완전한 데이터: ${rawData.length}/${dataSize} 바이트 수신` };
246
246
  }
247
- log(`[LIST] Raw data (hex): ${Buffer.from(rawData).toString("hex").match(/../g)?.join(" ") ?? ""}`);
247
+ log(`[LIST] 원시 데이터 (hex): ${Buffer.from(rawData).toString("hex").match(/../g)?.join(" ") ?? ""}`);
248
248
  const allIds = [];
249
249
  for (let i = 0; i + 4 <= rawData.length; i += 4) {
250
250
  allIds.push(rawData.readUInt32LE(i));
251
251
  }
252
252
  const userIds = [...new Set(allIds)];
253
- log(`[LIST] Templates: ${count}, unique users: ${userIds.length}, IDs: [${userIds.join(", ")}]`);
253
+ log(`[LIST] 템플릿: ${count}, 고유 사용자: ${userIds.length}, ID: [${userIds.join(", ")}]`);
254
254
  return { success: true, userIds, count, error: null };
255
255
  }
256
256
  /**
257
- * Read template from device (RTX: 0x89, ping-pong protocol)
257
+ * 디바이스에서 템플릿 읽기 (RTX: 0x89, 핑퐁 프로토콜)
258
258
  *
259
- * RTX response Size field: (templateCount << 16) | templateSize
260
- * e.g. 131456 = 0x00020180 → count=2, size=384
259
+ * RTX 응답 Size 필드: (templateCount << 16) | templateSize
260
+ * 예: 131456 = 0x00020180 → count=2, size=384
261
261
  *
262
- * Flow: send RTX → read 384B datasend ACK (RTX) → read 384B data → ...
263
- * Device flag: 0x74 (CONTINUE) = more chunks, 0x61 (SUCCESS) = last chunk.
262
+ * 흐름: RTX 전송 → 384B 데이터 수신 → ACK (RTX) 전송 → 384B 데이터 수신 → ...
263
+ * 디바이스 flag: 0x74 (CONTINUE) = 추가 청크 있음, 0x61 (SUCCESS) = 마지막 청크.
264
264
  */
265
265
  async readTemplate(userId, { onLog } = {}) {
266
266
  if (userId == null) {
@@ -269,10 +269,10 @@ class FingerprintCommands {
269
269
  const log = (msg) => {
270
270
  onLog?.(msg);
271
271
  };
272
- // Flag=0: read all templates for this userId
273
- log(`[RTX] Reading template for userId: ${userId}`);
272
+ // Flag=0: 해당 userId의 모든 템플릿 읽기
273
+ log(`[RTX] userId: ${userId} 템플릿 읽기`);
274
274
  const response = await this.client.sendCommand(index_js_1.SFM_CMD.RTX, userId, 384, 0);
275
- log(`[RTX] Response: flag=0x${response.flag?.toString(16)}, param=${response.param}, size=${response.size}, valid=${response.valid}`);
275
+ log(`[RTX] 응답: flag=0x${response.flag?.toString(16)}, param=${response.param}, size=${response.size}, valid=${response.valid}`);
276
276
  if (!response.valid) {
277
277
  return { success: false, templateData: null, error: response.error || "unknown error" };
278
278
  }
@@ -280,39 +280,39 @@ class FingerprintCommands {
280
280
  return {
281
281
  success: false,
282
282
  templateData: null,
283
- error: `Read template failed: 0x${response.flag?.toString(16) ?? "??"}`,
283
+ error: `템플릿 읽기 실패: 0x${response.flag?.toString(16) ?? "??"}`,
284
284
  };
285
285
  }
286
- // Parse RTX response size: (templateCount << 16) | templateSize
286
+ // RTX 응답 size 파싱: (templateCount << 16) | templateSize
287
287
  const rawSize = response.size ?? 0;
288
288
  const templateCount = (rawSize >>> 16) & 0xffff;
289
289
  const templateSize = rawSize & 0xffff;
290
290
  log(`[RTX] templateCount=${templateCount}, templateSize=${templateSize}B`);
291
291
  if (templateSize === 0) {
292
- return { success: false, templateData: null, error: "No template data (size=0)" };
292
+ return { success: false, templateData: null, error: "템플릿 데이터 없음 (size=0)" };
293
293
  }
294
294
  const chunks = [];
295
295
  const MAX_TEMPLATE_CHUNKS = 100;
296
296
  const totalChunks = templateCount || 1;
297
297
  if (totalChunks > MAX_TEMPLATE_CHUNKS) {
298
- return { success: false, templateData: null, error: `Unreasonable chunk count: ${totalChunks} (max ${MAX_TEMPLATE_CHUNKS})` };
298
+ return { success: false, templateData: null, error: `비정상적인 청크 수: ${totalChunks} (최대 ${MAX_TEMPLATE_CHUNKS})` };
299
299
  }
300
300
  for (let i = 0; i < totalChunks; i++) {
301
- log(`[RTX] Receiving chunk ${i + 1}/${totalChunks}...`);
301
+ log(`[RTX] 청크 ${i + 1}/${totalChunks} 수신 중...`);
302
302
  const readTimeout = Math.max(this.client.commandTimeout, Math.ceil(templateSize / 100) + 5000);
303
- // Extended Data Transfer Protocol:
304
- // Data Packet Header(13B) + Data Body(templateSize) + Data Checksum(4B)
303
+ // 확장 데이터 전송 프로토콜:
304
+ // 데이터 패킷 헤더(13B) + 데이터 본문(templateSize) + 데이터 체크섬(4B)
305
305
  const dataPacketHeader = await this.client.serial.read(13, readTimeout);
306
- log(`[RTX] Data packet header: ${Buffer.from(dataPacketHeader).toString("hex")}`);
306
+ log(`[RTX] 데이터 패킷 헤더: ${Buffer.from(dataPacketHeader).toString("hex")}`);
307
307
  const chunkData = await this.client.serial.read(templateSize, readTimeout);
308
308
  if (!chunkData || chunkData.length < templateSize) {
309
309
  return {
310
310
  success: false,
311
311
  templateData: null,
312
- error: `Incomplete read: ${chunkData?.length ?? 0}/${templateSize}B`,
312
+ error: `불완전한 읽기: ${chunkData?.length ?? 0}/${templateSize}B`,
313
313
  };
314
314
  }
315
- // Read and validate 4-byte checksum
315
+ // 4바이트 체크섬 읽기 검증
316
316
  const checksumData = await this.client.serial.read(4, readTimeout);
317
317
  const receivedChecksum = checksumData.readUInt32LE(0);
318
318
  let calculatedChecksum = 0;
@@ -320,30 +320,30 @@ class FingerprintCommands {
320
320
  calculatedChecksum += chunkData[j];
321
321
  }
322
322
  calculatedChecksum = calculatedChecksum >>> 0;
323
- log(`[RTX] Chunk ${i + 1} received: ${chunkData.length}B, checksum: received=0x${receivedChecksum.toString(16)}, calculated=0x${calculatedChecksum.toString(16)}`);
323
+ log(`[RTX] 청크 ${i + 1} 수신: ${chunkData.length}B, 체크섬: received=0x${receivedChecksum.toString(16)}, calculated=0x${calculatedChecksum.toString(16)}`);
324
324
  if (receivedChecksum !== calculatedChecksum) {
325
325
  return {
326
326
  success: false,
327
327
  templateData: null,
328
- error: `Checksum mismatch at chunk ${i + 1}: received=0x${receivedChecksum.toString(16)}, calculated=0x${calculatedChecksum.toString(16)}`,
328
+ error: `청크 ${i + 1} 체크섬 불일치: received=0x${receivedChecksum.toString(16)}, calculated=0x${calculatedChecksum.toString(16)}`,
329
329
  };
330
330
  }
331
331
  chunks.push(chunkData.subarray(0, templateSize));
332
- // Send DATA_OK(0x83) ACK to trigger next data packet
332
+ // DATA_OK(0x83) ACK 전송하여 다음 데이터 패킷 트리거
333
333
  const DATA_OK = 0x83;
334
- log(`[RTX] Sending DATA_OK ACK...`);
334
+ log(`[RTX] DATA_OK ACK 전송 중...`);
335
335
  const ackPacket = (0, packet_js_1.buildNormalPacket)(index_js_1.SFM_CMD.RTX, 0, 0, DATA_OK);
336
336
  await this.client._writePacket(ackPacket);
337
- log(`[RTX] ACK sent: ${Buffer.from(ackPacket).toString("hex")}`);
337
+ log(`[RTX] ACK 전송: ${Buffer.from(ackPacket).toString("hex")}`);
338
338
  }
339
339
  const templateData = Buffer.concat(chunks);
340
- log(`[RTX] Template received: ${templateData.length}B (${chunks.length} chunks)`);
340
+ log(`[RTX] 템플릿 수신 완료: ${templateData.length}B (${chunks.length} 청크)`);
341
341
  return { success: true, templateData, error: null };
342
342
  }
343
343
  /**
344
- * Write template to device (ET: 0x07 — Simple Template Enrollment)
344
+ * 디바이스에 템플릿 쓰기 (ET: 0x07 — 단순 템플릿 등록)
345
345
  *
346
- * Kiosk 호환 프로토콜 (rs232/fingerPrinter.js enrollByTemplate):
346
+ * 키오스크 호환 프로토콜 (rs232/fingerPrinter.js enrollByTemplate):
347
347
  * 1. [Command Packet 13B] + [Template Data] + [END 0x0A] 를 한 번에 전송
348
348
  * 2. 디바이스로부터 2개 응답 수신 (스캔 처리 + 최종 결과)
349
349
  *
@@ -360,55 +360,55 @@ class FingerprintCommands {
360
360
  const CHUNK = 384;
361
361
  const totalSize = templateData.length;
362
362
  const chunkCount = Math.ceil(totalSize / CHUNK);
363
- log(`[ET] Writing template for userId: ${userId}, totalSize: ${totalSize}B, chunks: ${chunkCount}`);
363
+ log(`[ET] userId: ${userId} 템플릿 쓰기, totalSize: ${totalSize}B, chunks: ${chunkCount}`);
364
364
  let lastResponseParam = userId;
365
365
  for (let i = 0; i < chunkCount; i++) {
366
366
  const offset = i * CHUNK;
367
367
  const currentChunkSize = Math.min(CHUNK, totalSize - offset);
368
368
  const chunkData = templateData.subarray(offset, offset + currentChunkSize);
369
- log(`[ET] Sending chunk ${i + 1}/${chunkCount}: ${currentChunkSize}B`);
370
- // Build command packet (13B)
369
+ log(`[ET] 청크 ${i + 1}/${chunkCount} 전송: ${currentChunkSize}B`);
370
+ // 명령 패킷 생성 (13B)
371
371
  const cmdPacket = (0, packet_js_1.buildNormalPacket)(index_js_1.SFM_CMD.ET, userId, currentChunkSize, flag);
372
372
  // ET 프로토콜: [Command 13B] + [Template Data] + [END 0x0A]
373
373
  const endByte = Buffer.from([0x0a]);
374
374
  const combined = Buffer.concat([cmdPacket, chunkData, endByte]);
375
- log(`[ET] Packet: ${Buffer.from(combined).toString("hex")}`);
375
+ log(`[ET] 패킷: ${Buffer.from(combined).toString("hex")}`);
376
376
  await this.client.serial.write(combined);
377
377
  // ET는 2개 응답을 수신
378
378
  // 응답 1: 처리 중간 응답
379
379
  const response1 = await this.client.receiveResponse(this.client.commandTimeout);
380
- log(`[ET] Response 1: flag=0x${response1.flag?.toString(16)}, param=${response1.param}, valid=${response1.valid}`);
380
+ log(`[ET] 응답 1: flag=0x${response1.flag?.toString(16)}, param=${response1.param}, valid=${response1.valid}`);
381
381
  if (!response1.valid) {
382
- return { success: false, error: `ET chunk ${i + 1} response 1 invalid: ${response1.error}` };
382
+ return { success: false, error: `ET 청크 ${i + 1} 응답 1 무효: ${response1.error}` };
383
383
  }
384
384
  // 응답 2: 최종 결과
385
385
  const response2 = await this.client.receiveResponse(this.client.commandTimeout);
386
- log(`[ET] Response 2: flag=0x${response2.flag?.toString(16)}, param=${response2.param}, valid=${response2.valid}`);
386
+ log(`[ET] 응답 2: flag=0x${response2.flag?.toString(16)}, param=${response2.param}, valid=${response2.valid}`);
387
387
  if (!response2.valid) {
388
- return { success: false, error: `ET chunk ${i + 1} response 2 invalid: ${response2.error}` };
388
+ return { success: false, error: `ET 청크 ${i + 1} 응답 2 무효: ${response2.error}` };
389
389
  }
390
390
  if (response2.flag !== index_js_1.SFM_ERROR.SUCCESS) {
391
391
  return {
392
392
  success: false,
393
- error: `ET chunk ${i + 1} failed: 0x${response2.flag?.toString(16) ?? "??"}`,
393
+ error: `ET 청크 ${i + 1} 실패: 0x${response2.flag?.toString(16) ?? "??"}`,
394
394
  };
395
395
  }
396
396
  lastResponseParam = response2.param ?? userId;
397
- log(`[ET] Chunk ${i + 1}/${chunkCount} enrolled successfully`);
397
+ log(`[ET] 청크 ${i + 1}/${chunkCount} 등록 성공`);
398
398
  }
399
- log(`[ET] Template written successfully (${chunkCount} chunks, ${totalSize}B)`);
399
+ log(`[ET] 템플릿 쓰기 성공 (${chunkCount} 청크, ${totalSize}B)`);
400
400
  return { success: true, userId: lastResponseParam, error: null };
401
401
  }
402
402
  /**
403
- * Write template to device (ETX: 0x87 — Extended Template Enrollment)
403
+ * 디바이스에 템플릿 쓰기 (ETX: 0x87 — 확장 템플릿 등록)
404
404
  *
405
- * Extended Data Transfer Protocol (Appendix B):
406
- * Supports multi-chunk: 768B → 2 chunks of 384B sent in one ETX session.
405
+ * 확장 데이터 전송 프로토콜 (Appendix B):
406
+ * 멀티 청크 지원: 768B → 384B 2개 청크를 하나의 ETX 세션에서 전송.
407
407
  *
408
408
  * 1. sendCommand(ETX, userId, sizeField, flag) → SUCCESS(0x61)
409
409
  * sizeField: (templateCount << 24) | (enrollCount << 16) | chunkSize
410
- * 2. For each chunk:
411
- * Data Packet Header(13B) + Data Body(384B) + Data Checksum(4B)
410
+ * 2. 청크별:
411
+ * 데이터 패킷 헤더(13B) + 데이터 본문(384B) + 데이터 체크섬(4B)
412
412
  * → DATA_OK(0x83) 응답 수신
413
413
  */
414
414
  async writeTemplateMulti(userId, templateData, flag = index_js_1.SFM_FLAG.NONE, { onLog } = {}) {
@@ -423,34 +423,34 @@ class FingerprintCommands {
423
423
  const templateCount = Math.ceil(totalSize / CHUNK);
424
424
  const chunkSize = Math.min(totalSize, CHUNK);
425
425
  if (templateCount > 255) {
426
- return { success: false, error: `Template too large: ${templateCount} chunks exceeds 255 max` };
426
+ return { success: false, error: `템플릿이 너무 큼: ${templateCount} 청크가 최대 255 초과` };
427
427
  }
428
428
  // ETX Size: (templateCount << 24) | (enrollCount << 16) | chunkSize
429
429
  // enrollCount=1: 각 템플릿을 1번씩만 저장
430
430
  const sizeField = (templateCount << 24) | (1 << 16) | chunkSize;
431
- log(`[ETX] Writing template for userId: ${userId}, totalSize: ${totalSize}B, chunks: ${templateCount}, sizeField=0x${sizeField.toString(16)}`);
432
- // Step 1: ETX 명령 패킷 전송 + SUCCESS(0x61) 응답 대기
431
+ log(`[ETX] userId: ${userId} 템플릿 쓰기, totalSize: ${totalSize}B, chunks: ${templateCount}, sizeField=0x${sizeField.toString(16)}`);
432
+ // 1단계: ETX 명령 패킷 전송 + SUCCESS(0x61) 응답 대기
433
433
  const cmdResponse = await this.client.sendCommand(index_js_1.SFM_CMD.ETX, userId, sizeField, flag);
434
- log(`[ETX] Command response: flag=0x${cmdResponse.flag?.toString(16)}, param=${cmdResponse.param}, valid=${cmdResponse.valid}`);
434
+ log(`[ETX] 명령 응답: flag=0x${cmdResponse.flag?.toString(16)}, param=${cmdResponse.param}, valid=${cmdResponse.valid}`);
435
435
  if (!cmdResponse.valid) {
436
436
  return { success: false, error: cmdResponse.error || "unknown error" };
437
437
  }
438
438
  if (cmdResponse.flag !== index_js_1.SFM_ERROR.SUCCESS) {
439
439
  return {
440
440
  success: false,
441
- error: `ETX command failed: 0x${cmdResponse.flag?.toString(16) ?? "??"}`,
441
+ error: `ETX 명령 실패: 0x${cmdResponse.flag?.toString(16) ?? "??"}`,
442
442
  };
443
443
  }
444
- // Step 2: Send each chunk with Extended Data Transfer Protocol
444
+ // 2단계: 확장 데이터 전송 프로토콜로 청크 전송
445
445
  const DATA_OK = 0x83;
446
446
  for (let i = 0; i < templateCount; i++) {
447
447
  const offset = i * CHUNK;
448
448
  const currentChunkSize = Math.min(CHUNK, totalSize - offset);
449
449
  const chunkData = templateData.subarray(offset, offset + currentChunkSize);
450
- // Data Packet Header param: (totalPackets & 0xFFFF) | ((packetIndex & 0xFFFF) << 16)
450
+ // 데이터 패킷 헤더 param: (totalPackets & 0xFFFF) | ((packetIndex & 0xFFFF) << 16)
451
451
  const dataPacketParam = (templateCount & 0xFFFF) | ((i & 0xFFFF) << 16);
452
452
  const dataHeader = (0, packet_js_1.buildNormalPacket)(index_js_1.SFM_CMD.ETX, dataPacketParam, currentChunkSize, 0);
453
- // Data Checksum: full byte sum (NOT & 0xFF)
453
+ // 데이터 체크섬: 전체 바이트 (& 0xFF 아님)
454
454
  let checksum = 0;
455
455
  for (let j = 0; j < currentChunkSize; j++) {
456
456
  checksum += chunkData[j];
@@ -458,35 +458,35 @@ class FingerprintCommands {
458
458
  const checksumBuf = Buffer.alloc(4);
459
459
  checksumBuf.writeUInt32LE(checksum >>> 0, 0);
460
460
  const combined = Buffer.concat([dataHeader, chunkData, checksumBuf]);
461
- log(`[ETX] Sending chunk ${i + 1}/${templateCount}: ${combined.length}B (header:13 + data:${currentChunkSize} + checksum:4, checksum=${checksum})`);
461
+ log(`[ETX] 청크 ${i + 1}/${templateCount} 전송: ${combined.length}B (header:13 + data:${currentChunkSize} + checksum:4, checksum=${checksum})`);
462
462
  await this.client.serial.write(combined);
463
- // Wait for DATA_OK(0x83) response
463
+ // DATA_OK(0x83) 응답 대기
464
464
  const ackResponse = await this.client.receiveResponse(this.client.commandTimeout);
465
- log(`[ETX] Chunk ${i + 1} ACK: flag=0x${ackResponse.flag?.toString(16)}, param=${ackResponse.param}, valid=${ackResponse.valid}`);
465
+ log(`[ETX] 청크 ${i + 1} ACK: flag=0x${ackResponse.flag?.toString(16)}, param=${ackResponse.param}, valid=${ackResponse.valid}`);
466
466
  if (!ackResponse.valid) {
467
- return { success: false, error: `Chunk ${i + 1} ACK invalid: ${ackResponse.error}` };
467
+ return { success: false, error: `청크 ${i + 1} ACK 무효: ${ackResponse.error}` };
468
468
  }
469
469
  if (ackResponse.flag !== index_js_1.SFM_ERROR.SUCCESS && ackResponse.flag !== DATA_OK) {
470
470
  return {
471
471
  success: false,
472
- error: `ETX chunk ${i + 1} failed: 0x${ackResponse.flag?.toString(16) ?? "??"}`,
472
+ error: `ETX 청크 ${i + 1} 실패: 0x${ackResponse.flag?.toString(16) ?? "??"}`,
473
473
  };
474
474
  }
475
475
  }
476
- log(`[ETX] Template written successfully (${templateCount} chunks, ${totalSize}B)`);
477
- // Drain residual responses from device (e.g. SCAN_SUCCESS after write)
476
+ log(`[ETX] 템플릿 쓰기 성공 (${templateCount} 청크, ${totalSize}B)`);
477
+ // 디바이스의 잔여 응답 소진 (예: 쓰기 SCAN_SUCCESS)
478
478
  await new Promise((r) => setTimeout(r, 100));
479
479
  await this.client.serial.purgeRX();
480
480
  return { success: true, userId: cmdResponse.param, error: null };
481
481
  }
482
482
  /**
483
- * Capture fingerprint image (SI: 0x15)
483
+ * 지문 이미지 캡처 (SI: 0x15)
484
484
  */
485
485
  async captureImage({ onLog } = {}) {
486
486
  const log = (msg) => {
487
487
  onLog?.(msg);
488
488
  };
489
- log("[CAPTURE] Fingerprint scan started (SI: 0x15)");
489
+ log("[CAPTURE] 지문 스캔 시작 (SI: 0x15)");
490
490
  const siResponse = await this.client.sendCommand(index_js_1.SFM_CMD.SI, 0, 0, 0);
491
491
  log(`[CAPTURE] SI 응답: flag=0x${siResponse.flag?.toString(16)}, param=${siResponse.param}, size=${siResponse.size}, valid=${siResponse.valid}`);
492
492
  if (!siResponse.valid) {
@@ -500,9 +500,9 @@ class FingerprintCommands {
500
500
  }
501
501
  let lastResponse = siResponse;
502
502
  while (lastResponse.flag === index_js_1.SFM_ERROR.SCAN_SUCCESS) {
503
- log("[CAPTURE] Scan success (0x62), waiting for next response...");
503
+ log("[CAPTURE] 스캔 성공 (0x62), 다음 응답 대기 중...");
504
504
  lastResponse = await this.client.receiveResponse(this.client.commandTimeout);
505
- log(`[CAPTURE] Response: flag=0x${lastResponse.flag?.toString(16)}, param=${lastResponse.param}, size=${lastResponse.size}, valid=${lastResponse.valid}`);
505
+ log(`[CAPTURE] 응답: flag=0x${lastResponse.flag?.toString(16)}, param=${lastResponse.param}, size=${lastResponse.size}, valid=${lastResponse.valid}`);
506
506
  if (!lastResponse.valid) {
507
507
  return {
508
508
  success: false,
@@ -519,7 +519,7 @@ class FingerprintCommands {
519
519
  imageData: null,
520
520
  width: 0,
521
521
  height: 0,
522
- error: `Scan failed: 0x${lastResponse.flag?.toString(16) ?? "??"}`,
522
+ error: `스캔 실패: 0x${lastResponse.flag?.toString(16) ?? "??"}`,
523
523
  };
524
524
  }
525
525
  const imageSize = lastResponse.size ?? 0;
@@ -530,10 +530,10 @@ class FingerprintCommands {
530
530
  imageData: null,
531
531
  width: 0,
532
532
  height: 0,
533
- error: "No image data (size=0)",
533
+ error: "이미지 데이터 없음 (size=0)",
534
534
  };
535
535
  }
536
- log(`[CAPTURE] Receiving image data: ${imageSize}B`);
536
+ log(`[CAPTURE] 이미지 데이터 수신: ${imageSize}B`);
537
537
  const readTimeout = Math.max(this.client.commandTimeout, Math.ceil(imageSize / 100) + 5000);
538
538
  const imageData = await this.client.serial.read(imageSize, readTimeout);
539
539
  if (!imageData || imageData.length < imageSize) {
@@ -542,10 +542,10 @@ class FingerprintCommands {
542
542
  imageData: null,
543
543
  width: 0,
544
544
  height: 0,
545
- error: `Incomplete read: ${imageData?.length ?? 0}/${imageSize}B`,
545
+ error: `불완전한 읽기: ${imageData?.length ?? 0}/${imageSize}B`,
546
546
  };
547
547
  }
548
- log(`[CAPTURE] Image received: ${imageData.length}B`);
548
+ log(`[CAPTURE] 이미지 수신 완료: ${imageData.length}B`);
549
549
  const sqrt = Math.sqrt(imageSize);
550
550
  let width, height;
551
551
  if (Number.isInteger(sqrt)) {
@@ -576,7 +576,7 @@ class FingerprintCommands {
576
576
  }
577
577
  }
578
578
  }
579
- log(`[CAPTURE] Image resolution: ${width}x${height} (${width * height} vs ${imageSize})`);
579
+ log(`[CAPTURE] 이미지 해상도: ${width}x${height} (${width * height} vs ${imageSize})`);
580
580
  return { success: true, imageData, width, height, error: null };
581
581
  }
582
582
  }