class ReaderClient { constructor(options = {}) { this.wsUrl = options.wsUrl || "ws://127.0.0.1:6689"; this.driveType = options.driveType || "D5200"; this.portName = options.portName || "USB"; this.hidSN = options.hidSN || ""; this.driverIp = options.driverIp || ""; this.driverPort = options.driverPort || "6688"; // ISO14443A 参数 this.keyType = options.keyType || "0"; this.key = options.key || "FFFFFFFFFFFF"; this.blockAddress = options.blockAddress || "1"; this.numberOfBlocksRead = options.numberOfBlocksRead || "1"; // ISO15693 参数 this.readDataType = options.readDataType || "0"; this.readSecurityStatus = options.readSecurityStatus || "0"; this.iso15693BlockAddress = options.iso15693BlockAddress || "0"; this.iso15693NumberOfBlocks = options.iso15693NumberOfBlocks || "7"; this.ws = null; this.connected = false; this.handle = null; this.requestId = 1; this.pending = new Map(); } async init() { if (typeof WebSocket === "undefined") { throw new Error("当前浏览器不支持 WebSocket"); } return true; } async ensureWsConnected() { if (this.ws && this.ws.readyState === WebSocket.OPEN) { return; } if (this.ws && this.ws.readyState === WebSocket.CONNECTING) { await new Promise((resolve, reject) => { const timeoutRef = setTimeout( () => reject(new Error("读卡器服务连接超时")), 5000 ); const onOpen = () => { clearTimeout(timeoutRef); this.ws.removeEventListener("open", onOpen); this.ws.removeEventListener("error", onError); resolve(); }; const onError = () => { clearTimeout(timeoutRef); this.ws.removeEventListener("open", onOpen); this.ws.removeEventListener("error", onError); reject(new Error("读卡器服务连接失败")); }; this.ws.addEventListener("open", onOpen); this.ws.addEventListener("error", onError); }); return; } this.ws = new WebSocket(this.wsUrl); this.ws.onmessage = (event) => { this.handleMessage(event.data); }; this.ws.onclose = () => { this.connected = false; this.handle = null; for (const [, item] of this.pending) { clearTimeout(item.timeoutRef); item.reject(new Error("读卡器服务连接已断开")); } this.pending.clear(); }; this.ws.onerror = () => { // 具体错误在请求超时/close时抛出 }; await new Promise((resolve, reject) => { const timeoutRef = setTimeout( () => reject(new Error("读卡器服务连接超时")), 5000 ); this.ws.onopen = () => { clearTimeout(timeoutRef); resolve(); }; this.ws.onerror = () => { clearTimeout(timeoutRef); reject(new Error("读卡器服务连接失败,请先启动厂家读卡器服务程序")); }; }); } handleMessage(rawData) { let payload; try { payload = JSON.parse(rawData); } catch { for (const [, item] of this.pending) { clearTimeout(item.timeoutRef); item.resolve(rawData); } this.pending.clear(); return; } const traceId = payload.traceId; if (traceId && this.pending.has(traceId)) { const item = this.pending.get(traceId); this.pending.delete(traceId); clearTimeout(item.timeoutRef); item.resolve(payload); return; } if (this.pending.size === 1) { const firstKey = this.pending.keys().next().value; const item = this.pending.get(firstKey); this.pending.delete(firstKey); clearTimeout(item.timeoutRef); item.resolve(payload); } } async sendCommand(command, param = {}, handleOverride, timeoutMs = 6000) { await this.ensureWsConnected(); const traceId = `r${Date.now()}_${this.requestId++}`; const message = { command, param, traceId, }; const finalHandle = handleOverride ?? this.handle; if (finalHandle !== null && finalHandle !== undefined) { message.handle = String(finalHandle); } const promise = new Promise((resolve, reject) => { const timeoutRef = setTimeout(() => { this.pending.delete(traceId); reject(new Error(`${command} 超时`)); }, timeoutMs); this.pending.set(traceId, { resolve, reject, timeoutRef }); }); this.ws.send(JSON.stringify(message)); return promise; } getPayloadCode(payload) { if (!payload || typeof payload !== "object") { return null; } if (payload.code != null) { return Number(payload.code); } if (payload.status != null) { return Number(payload.status); } if (payload.result?.code != null) { return Number(payload.result.code); } return null; } getPayloadMessage(payload) { if (!payload || typeof payload !== "object") { return ""; } return String( payload.msg ?? payload.message ?? payload.error ?? payload.result?.msg ?? payload.result?.message ?? "" ); } extractHandle(payload) { if (!payload || typeof payload !== "object") { return null; } const h = payload.handle ?? payload.result?.handle ?? payload.data?.handle; if (h === null || h === undefined || h === "") { return null; } return String(h); } extractTagDataList(payload) { if (!payload || typeof payload !== "object") { return []; } if (Array.isArray(payload.tagDataList)) { return payload.tagDataList; } if (Array.isArray(payload.data)) { return payload.data; } if (payload.data && Array.isArray(payload.data.tagDataList)) { return payload.data.tagDataList; } if (payload.result && Array.isArray(payload.result.tagDataList)) { return payload.result.tagDataList; } if (payload.result && Array.isArray(payload.result.data)) { return payload.result.data; } return []; } parseUidFromTagRow(row) { if (!row) { return ""; } if (Array.isArray(row)) { return String(row[1] || "").trim(); } if (typeof row === "object") { if (Array.isArray(row.value)) { return String(row.value[1] || "").trim(); } if (Array.isArray(row.data)) { return String(row.data[1] || "").trim(); } if (Array.isArray(row.tag)) { return String(row.tag[1] || "").trim(); } for (const value of Object.values(row)) { if (Array.isArray(value)) { return String(value[1] || "").trim(); } } } return ""; } async selectDevice() { await this.init(); try { await this.sendCommand("CloseDriver", {}, -1, 3000); } catch { // 忽略 } const openResp = await this.sendCommand( "OpenDriver", { driveType: this.driveType, portName: this.portName, hidSN: this.hidSN, driverIp: this.driverIp, driverPort: this.driverPort, driverId: "", userName: "", passWord: "", }, undefined, 8000 ); const code = this.getPayloadCode(openResp); if (code != null && code < 0) { const msg = this.getPayloadMessage(openResp) || "OpenDriver 失败"; throw new Error(msg); } const openedHandle = this.extractHandle(openResp); if (!openedHandle) { throw new Error("OpenDriver 未返回 handle"); } this.handle = openedHandle; this.connected = true; return true; } // ISO14443A 读取 UID async readUidISO14443A() { await this.init(); if (!this.connected || !this.handle) { throw new Error("读卡器未连接,请先连接读卡器"); } const readResp = await this.sendCommand( "ISO14443A_BatchReadMultiBlocks", { antennaCollection: [1], readDataType: "0", keyType: this.keyType, key: this.key, blockAddress: this.blockAddress, numberOfBlocksRead: this.numberOfBlocksRead, }, this.handle, 6000 ); const code = this.getPayloadCode(readResp); if (code != null && code < 0) { const msg = this.getPayloadMessage(readResp) || "ISO14443A 读卡失败"; throw new Error(msg); } const tagDataList = this.extractTagDataList(readResp); if (!Array.isArray(tagDataList) || tagDataList.length === 0) { throw new Error("未读取到 UID,请确认 ISO14443A 卡片已到读卡位"); } const firstRow = tagDataList[0]; const uid = this.parseUidFromTagRow(firstRow); if (!uid) { throw new Error("未读取到 UID,请确认 ISO14443A 卡片已到读卡位"); } return uid.toUpperCase(); } // ISO15693 读取 UID async readUidISO15693() { await this.init(); if (!this.connected || !this.handle) { throw new Error("读卡器未连接,请先连接读卡器"); } const readResp = await this.sendCommand( "ISO15693_BatchReadMultiBlocks", { antennaCollection: [1], readDataType: this.readDataType, readSecurityStatus: this.readSecurityStatus, blockAddress: this.iso15693BlockAddress, numberOfBlocksRead: this.iso15693NumberOfBlocks, }, this.handle, 6000 ); const code = this.getPayloadCode(readResp); if (code != null && code < 0) { const msg = this.getPayloadMessage(readResp) || "ISO15693 读卡失败"; throw new Error(msg); } const tagDataList = this.extractTagDataList(readResp); if (!Array.isArray(tagDataList) || tagDataList.length === 0) { throw new Error("未读取到 UID,请确认 ISO15693 标签已到读卡位"); } const firstRow = tagDataList[0]; const uid = this.parseUidFromTagRow(firstRow); if (!uid) { throw new Error("未读取到 UID,请确认 ISO15693 标签已到读卡位"); } return uid.toUpperCase(); } // 根据协议类型读取 UID async readUid(protocol = "ISO14443A") { if (protocol === "ISO15693") { return await this.readUidISO15693(); } return await this.readUidISO14443A(); } } window.ReaderClient = ReaderClient;