import logger from "../../dxmodules/dxLogger.js" import std from "../../dxmodules/dxStd.js" import config from "../../dxmodules/dxConfig.js" import dxCommonUtils from "../../dxmodules/dxCommonUtils.js" import dxos from "../../dxmodules/dxOs.js" import map from "../../dxmodules/dxMap.js" import bus from "../../dxmodules/dxEventBus.js" import driver from "../driver.js" import mqttService from "./mqttService.js" import sqliteService from "./sqliteService.js" import utils from '../common/utils/utils.js' import grainService from './grainService.js' const accessService = {} const mqtt_map = map.get("MQTT") // 双人认证相关变量 let dualAuthTimeout = null const MAX_QUEUE_SIZE = 2 // 最大队列长度,用于双人认证 // 使用dxMap存储认证队列和超时定时器,确保所有worker线程都能访问同一个队列和定时器 const AUTH_QUEUE_TOPIC = "AUTH_QUEUE" const AUTH_QUEUE_KEY = "queue" const AUTH_TIMEOUT_KEY = "timeout" const getAuthQueue = function() { try { // 获取AUTH_QUEUE主题的map实例 const authMap = map.get(AUTH_QUEUE_TOPIC) // 从map中获取队列 let queue = authMap.get(AUTH_QUEUE_KEY) // 确保返回的是一个数组 if (!queue || typeof queue !== 'object' || !Array.isArray(queue)) { queue = [] // 保存到map中 authMap.put(AUTH_QUEUE_KEY, queue) } return queue } catch (error) { logger.error("获取认证队列失败: " + error.message) // 出错时返回空数组 return [] } } const updateAuthQueue = function(queue) { try { // 获取AUTH_QUEUE主题的map实例 const authMap = map.get(AUTH_QUEUE_TOPIC) // 确保存储的是一个数组 if (Array.isArray(queue)) { authMap.put(AUTH_QUEUE_KEY, queue) } else { logger.error("更新认证队列失败: 队列必须是数组") } } catch (error) { logger.error("更新认证队列失败: " + error.message) } } const clearAuthQueue = function() { try { // 获取AUTH_QUEUE主题的map实例 const authMap = map.get(AUTH_QUEUE_TOPIC) // 清空队列 authMap.put(AUTH_QUEUE_KEY, []) } catch (error) { logger.error("清空认证队列失败: " + error.message) } } const getAuthTimeout = function() { try { // 获取AUTH_QUEUE主题的map实例 const authMap = map.get(AUTH_QUEUE_TOPIC) // 从map中获取超时定时器 return authMap.get(AUTH_TIMEOUT_KEY) } catch (error) { logger.error("获取认证超时定时器失败: " + error.message) // 出错时返回null return null } } const setAuthTimeout = function(timeoutId) { try { // 获取AUTH_QUEUE主题的map实例 const authMap = map.get(AUTH_QUEUE_TOPIC) // 保存超时定时器 authMap.put(AUTH_TIMEOUT_KEY, timeoutId) } catch (error) { logger.error("设置认证超时定时器失败: " + error.message) } } const clearAuthTimeout = function() { try { // 获取AUTH_QUEUE主题的map实例 const authMap = map.get(AUTH_QUEUE_TOPIC) // 清空超时定时器 authMap.put(AUTH_TIMEOUT_KEY, null) } catch (error) { logger.error("清空认证超时定时器失败: " + error.message) } } /** * 人脸/密码白名单校验 * @param {object} data {type:码制(string),code:码内容(string)} * @returns number:-1(参数错误),0(通行成功),1(在线验证),string:校验失败的原因 */ accessService.access = function (data, fileName, similarity) { let language = config.get("base.language") || "CN"; // 打印认证队列状态 logger.info("[access]: access函数开始执行,当前认证队列状态: " + JSON.stringify(getAuthQueue())) logger.info("[access]: 当前认证用户: " + JSON.stringify(data)) // 通行加锁 let lockMap = map.get("access_lock") if (lockMap.get("access_lock")) { logger.error("[access]: 通行加锁,请稍后再试") return false } lockMap.put("access_lock", true) try { data.timeStamp = Math.floor(Date.parse(new Date()) / 1000) data.message = "" // 先根据code查询凭证 let res // 确保 type 是字符串类型 const typeStr = data.type.toString() if (typeStr == "300") { res = sqliteService.d1_voucher.findByUserIdAndType(data.code, typeStr) } else { res = sqliteService.d1_voucher.findByCodeAndType(data.code, typeStr) } // 认证结果 let ret = true // 是否是陌生人 let isStranger = false if (similarity === false) { // 如果相似度验证失败,则不进行认证 ret = false isStranger = true } else { if (res.length == 0) { logger.error("[access]: 通行失败,没查询到凭证!") ret = false isStranger = true data.message = "Voucher verification failed" } else { data.userId = res[0].userId data.keyId = res[0].keyId // 根据userId查人员 res = sqliteService.d1_person.findByUserId(data.userId) if (res.length == 0) { logger.error("[access]: 通行失败,没查询到人员!") ret = false isStranger = true data.message = "Personnel verification failed" } else { let idCard let userType = 0 try { idCard = JSON.parse(res[0].extra).idCard userType = JSON.parse(res[0].extra).type || 0 } catch (error) { logger.error("无身份证号或类型") } data.extra = { name: res[0].name, idCard: idCard, type: userType } data.permissionIds = res[0].permissionIds } } // 处理双人认证信息 if (data.dualAuthInfo) { logger.info("[access]: 处理双人认证信息: " + JSON.stringify(data.dualAuthInfo)) // 存储第一用户信息 let firstUserId = data.dualAuthInfo.firstUserId // 查询第一用户的详细信息 let res1 = sqliteService.d1_person.findByUserId(firstUserId) if (res1.length > 0) { // 获取第一用户的姓名、身份证号和身份类型 let idCard1 let firstUserType = 0 try { idCard1 = JSON.parse(res1[0].extra).idCard firstUserType = JSON.parse(res1[0].extra).type || 0 } catch (error) { logger.error("无第一用户身份证号或类型") } data.userId = firstUserId data.extra = { name: res1[0].name, idCard: idCard1, type: firstUserType } } else { // 如果没有查询到第一用户信息,使用默认值 data.userId = firstUserId data.extra = { name: data.dualAuthInfo.firstUserName, idCard: "", type: 0 } } // 存储第二用户信息 data.userId2 = data.dualAuthInfo.secondUserId // 查询第二用户的详细信息 let res2 = sqliteService.d1_person.findByUserId(data.dualAuthInfo.secondUserId) if (res2.length > 0) { // 获取第二用户的姓名和身份证号 let idCard2 let secondUserType = 0 try { idCard2 = JSON.parse(res2[0].extra).idCard secondUserType = JSON.parse(res2[0].extra).type || 0 } catch (error) { logger.error("无第二用户身份证号或类型") } data.extra2 = { name: res2[0].name, idCard: idCard2 } // 存储第二用户的权限ID(身份类型) data.permissionId2 = secondUserType.toString() } else { // 如果没有查询到第二用户信息,使用默认值 data.extra2 = { name: data.dualAuthInfo.secondUserName, idCard: "" } data.permissionId2 = "" } // 处理第一用户的人脸图片 if (data.firstUserFileName) { let firstUserSrc = `/data/passRecord/${firstUserId}_${data.timeStamp}_1.jpg` std.ensurePathExists(firstUserSrc) // 确保目录存在 if (std.exist(data.firstUserFileName)) { // 移动图片到指定目录 dxos.systemBrief(`mv ${data.firstUserFileName} ${firstUserSrc}`) // 更新data中的code为第一用户的图片路径 data.code = firstUserSrc } } // 处理第二用户的人脸图片 if (fileName) { let secondUserSrc = `/data/passRecord/${data.userId2}_${data.timeStamp}_2.jpg` std.ensurePathExists(secondUserSrc) // 确保目录存在 if (std.exist(fileName)) { // 移动图片到指定目录 dxos.systemBrief(`mv ${fileName} ${secondUserSrc}`) // 更新data中的code2为第二用户的图片路径 data.code2 = secondUserSrc } } } if (ret) { // 根据userId查询权限 let permissionIds = data.permissionIds.split(",") let permissions = [] for (let i = 0; i < permissionIds.length; i++) { const element = permissionIds[i]; let res = sqliteService.d1_permission.findByPermissionId(element) permissions.push(...res) } let permissionId = judgmentPermission(permissions) if (permissions && permissions.length > 0 && permissionId) { logger.info("[access]: 权限认证通过") // 存储用户身份类型作为权限ID(本系统中身份即权限) let userType = 0 try { if (data.extra) { userType = data.extra.type || 0 } else { // 从数据库查询用户信息获取身份类型 let userRes = sqliteService.d1_person.findByUserId(data.userId) if (userRes && userRes.length > 0) { userType = JSON.parse(userRes[0].extra).type || 0 } } } catch (error) { logger.error("解析用户类型失败") } data.permissionId = userType.toString() // 检查是否是双人认证模式 if (!data.dualAuthInfo) { // 单人通行:科长(type=1)权限直接通过 if (userType === 1) { logger.info("[access]: 科长权限,单人通行认证通过") ret = true } else if (userType === 0) { // 保管员(type=0)需要双人认证 let authQueue = getAuthQueue() // 检查认证队列长度 if (authQueue.length >= MAX_QUEUE_SIZE) { // 队列已满,清除队列,重新开始认证 logger.info("[access]: 认证队列已满,重置认证流程") clearAuthQueue() authQueue = getAuthQueue() } // 检查当前用户是否已经在队列中(避免重复认证) const isUserInQueue = authQueue.some(item => item.userId === data.userId) if (isUserInQueue) { logger.info("[access]: 用户已在认证队列中,跳过重复认证") // 解锁 lockMap.put("access_lock", false) logger.error("[access]: 解锁成功") driver.finger.controlLed("BLUE") return } // 打印当前队列状态 logger.info("[access]: 当前认证队列状态: " + JSON.stringify(authQueue)) // 将当前用户添加到认证队列 authQueue.push({ userId: data.userId, name: data.extra.name, type: userType, fileName: fileName, data: data }) // 更新队列到dxMap updateAuthQueue(authQueue) logger.info("[access]: 用户添加到认证队列,当前队列长度: " + authQueue.length) if (authQueue.length === 1) { // 第一用户认证 logger.info("[access]: 保管员权限,需要双人认证") // 触发第一用户认证成功事件,更新UI bus.fire("accessSuccess", { data: { userId: data.userId, extra: data.extra, dualAuthInfo: { firstUserId: data.userId, firstUserName: data.extra.name } }, fileName: fileName }) // 语音提示第二用户进行认证 driver.audio.play(`/app/code/resource/${language}/wav/user2.wav`) // 等待第二用户认证,设置60秒超时 let newTimeout = std.setTimeout(() => { // 检查队列是否为空,如果为空则不触发超时错误 let authQueue = getAuthQueue() if (authQueue.length === 0) { logger.info("[access]: 认证队列为空,跳过超时处理") return } logger.info("[access]: 双人认证超时,认证失败") // 保存超时失败的通行记录 if (authQueue.length > 0) { let firstUser = authQueue[0] let timeoutData = { type: firstUser.data.type, code: firstUser.data.code, timeStamp: Math.floor(Date.now() / 1000), userId: firstUser.userId, extra: { name: firstUser.name }, message: "双人认证超时" } savePassPic(timeoutData, firstUser.fileName) reply(timeoutData, false) } // 清空认证队列 clearAuthQueue() // 清空超时定时器 clearAuthTimeout() // 触发失败弹窗 bus.fire("showAccessResult", { faceAuth: true, gasConcentration: false, accessAllowed: false, message: "*双人认证超时,禁止通行*" }) driver.audio.play(`/app/code/resource/${language}/wav/recg_f.wav`) }, 60000) // 保存超时定时器 setAuthTimeout(newTimeout) // 解锁 lockMap.put("access_lock", false) logger.error("[access]: 解锁成功") driver.finger.controlLed("BLUE") // 第一用户认证成功,等待第二用户,不设置ret,直接返回 return } else if (authQueue.length === 2) { // 第二用户认证,进行双人认证 logger.info("[access]: 第二用户认证,进行双人认证") // 打印队列中的用户信息 logger.info("[access]: 认证队列中的用户: " + JSON.stringify(authQueue)) const firstUser = authQueue[0] const secondUser = authQueue[1] // 合并第一用户和第二用户信息 data.dualAuthInfo = { firstUserId: firstUser.userId, firstUserName: firstUser.name, secondUserId: secondUser.userId, secondUserName: secondUser.name, secondUserExtra: secondUser.data.extra } // 确保第二用户的信息包含身份证号 data.extra2 = secondUser.data.extra // 将 data.userId 和 data.extra 设置为第一用户的信息 data.userId = firstUser.userId // 尝试从数据库获取第一用户的完整信息 try { let userRes = sqliteService.d1_person.findByUserId(firstUser.userId) if (userRes && userRes.length > 0) { // 获取第一用户的姓名、身份证号和身份类型 let idCard1 let firstUserType = 0 try { idCard1 = JSON.parse(userRes[0].extra).idCard firstUserType = JSON.parse(userRes[0].extra).type || 0 } catch (error) { logger.error("无第一用户身份证号或类型") } data.extra = { name: userRes[0].name, idCard: idCard1, type: firstUserType } } else { data.extra = { name: firstUser.name, idCard: '', type: firstUser.type } } } catch (error) { logger.error("解析第一用户信息失败") data.extra = { name: firstUser.name, idCard: '', type: firstUser.type } } // 存储第二用户的人脸图片 if (secondUser.fileName) { let secondUserSrc = `/data/passRecord/${secondUser.userId}_${data.timeStamp}_2.jpg` std.ensurePathExists(secondUserSrc) // 确保目录存在 if (std.exist(secondUser.fileName)) { // 移动图片到指定目录 dxos.systemBrief(`mv ${secondUser.fileName} ${secondUserSrc}`) // 更新data中的code2为第二用户的图片路径 data.code2 = secondUserSrc } } // 存储第一用户的人脸图片 if (firstUser.fileName) { let firstUserSrc = `/data/passRecord/${firstUser.userId}_${data.timeStamp}_1.jpg` std.ensurePathExists(firstUserSrc) // 确保目录存在 if (std.exist(firstUser.fileName)) { // 移动图片到指定目录 dxos.systemBrief(`mv ${firstUser.fileName} ${firstUserSrc}`) // 更新data中的code为第一用户的图片路径 data.code = firstUserSrc data.firstUserFileName = firstUserSrc } } // 双人通行:两个用户都有基本权限即可 logger.info("[access]: 双人认证通过") ret = true // 清除双人认证超时 clearAuthTimeout() // 清空认证队列 clearAuthQueue() } } else { logger.info("[access]: 无权限通行") ret = false } } else { // 双人通行:两个用户都有基本权限即可 logger.info("[access]: 双人认证通过") ret = true // 清除双人认证超时 clearAuthTimeout() // 清空认证队列 clearAuthQueue() } if (ret) { // 暂停人脸认证功能 driver.face.status(false) logger.info("[access]: 暂停人脸认证功能") } } else { logger.info("[access]: 无权限") ret = false data.message = "Permission verification failed" } } if (!ret && config.get('mqtt.onlinecheck') == 1 && mqtt_map.get("MQTT_STATUS") == "connected") { logger.info("[access]: 无权限,走在线验证") let serialNo = std.genRandomStr(10) driver.mqtt.send("access_device/v2/event/access_online", JSON.stringify(mqttService.mqttReply(serialNo, data, mqttService.CODE.S_000))) driver.audio.play(`/app/code/resource/${language}/wav/recg.wav`) // 等待在线验证结果 let payload = getOnlinecheck() if (payload && payload.serialNo == serialNo && payload.code == '000000') { ret = true // 暂停人脸认证功能 driver.face.status(false) logger.info("[access]: 暂停人脸认证功能") } else { logger.info("[access]: 在线验证失败") ret = false } } } if (ret == true) { if (data.type == 500) { driver.finger.controlLed("GREEN") } else if (data.type == 600) { bleReply(data, true) } // 验证气体浓度 grainService.checkGasConcentration(function() { // 从存储的气体数据中获取验证结果 const gasData = grainService.getGasData() if(gasData && gasData.data && gasData.data.status === "0") { logger.info("[access]: 气体浓度验证合格") // 通行成功处理 driver.screen.accessSuccess() logger.info("[access]: 通行成功") // 显示通行成功结果 bus.fire("showAccessResult", { faceAuth: true, gasConcentration: true, accessAllowed: true, message: "*仓内气体浓度合格,允许通行*" }) // 触发通行成功事件,通知UI更新 bus.fire("accessSuccess", { data: data, fileName: fileName }) driver.audio.play(`/app/code/resource/${language}/wav/access_s.wav`) // 播放语音 driver.gpio.open() // 开门 savePassPic(data, fileName) // 保存通行图片 reply(data, true) // 上报通行记录 // 60秒后重置用户UI std.setTimeout(() => { bus.fire("accessUnlockComplete") }, 60000) } else { logger.info("[access]: 气体浓度验证不合格") // 通行失败处理 driver.screen.accessFail() logger.error("[access]: 通行失败") // 触发失败弹窗 bus.fire("showAccessResult", { faceAuth: true, gasConcentration: false, accessAllowed: false, message: "*仓内气体浓度不合格,禁止通行*" }) // 触发通行成功事件,更新用户UI bus.fire("accessSuccess", { data: data, fileName: fileName }) if (utils.isEmpty(similarity)) { driver.audio.play(`/app/code/resource/${language}/wav/access_f.wav`) } // 60秒后重置用户UI std.setTimeout(() => { bus.fire("accessUnlockComplete") }, 60000) savePassPic(data, fileName) // 添加气体浓度失败信息 data.message = "气体浓度不合格" reply(data, false) // 上报通行记录 } }) } else { if (data.type == 500) { driver.finger.controlLed("RED") } else if (data.type == 600) { bleReply(data, false) } driver.screen.accessFail() logger.error("[access]: 通行失败") // 触发失败弹窗 bus.fire("showAccessResult", { faceAuth: true, gasConcentration: false, accessAllowed: false, message: "*权限认证失败,禁止通行*" }) if (similarity !== false) { driver.audio.play(`/app/code/resource/${language}/wav/access_f.wav`) } // 60秒后重置用户UI std.setTimeout(() => { bus.fire("accessUnlockComplete") }, 60000) if (isStranger && !config.get("sys.strangerImage")) { // 陌生人不保存照片 } else { savePassPic(data, fileName) } // 添加权限认证失败信息 data.message = "权限认证失败" reply(data, false) } } catch (error) { logger.error(error) } // 语音播报需要时间,所以延迟1秒解锁 std.sleep(1000) lockMap.put("access_lock", false) logger.error("[access]: 解锁成功") // 触发通行解锁完成事件,通知UI重置 // bus.fire("accessUnlockComplete") driver.finger.controlLed("BLUE") } // 保存通行图片 function savePassPic(data, fileName) { // 处理单人认证的人脸图片 if (data.type == "300") { // 仅处理人脸类型 // 如果是双人认证,已经在处理双人认证信息时保存了图片,这里跳过 if (data.dualAuthInfo) { return } let src = `/data/passRecord/${data.userId}_${data.timeStamp}.jpg` std.ensurePathExists(src) // 确保目录存在 if (std.exist(fileName)) { // 移动图片到指定目录 dxos.systemBrief(`mv ${fileName} ${src}`) // 只清理原始临时图片文件(以时间戳命名的文件),保留调整后的图片文件 dxos.systemBrief(`rm -rf /data/user/temp/[0-9]*.jpg`) // 更新data中的code为图片路径 data.code = src } else { logger.error("[access]: 通行失败,图片不存在!!!!!!!!!!!!!!!" + fileName) } } // 处理双人认证的人脸图片 if (data.dualAuthInfo) { // 保存第一用户的人脸图片 if (fileName) { let src = `/data/passRecord/${data.userId}_${data.timeStamp}_1.jpg` std.ensurePathExists(src) // 确保目录存在 if (std.exist(fileName)) { // 移动图片到指定目录 dxos.systemBrief(`mv ${fileName} ${src}`) // 只清理原始临时图片文件(以时间戳命名的文件),保留调整后的图片文件 dxos.systemBrief(`rm -rf /data/user/temp/[0-9]*.jpg`) // 更新data中的code为图片路径 data.code = src data.firstUserFileName = src } else { logger.error("[access]: 通行失败,图片不存在!!!!!!!!!!!!!!!" + fileName) } } } } function getOnlinecheck() { let timeout = config.get("mqtt.timeout") timeout = utils.isEmpty(timeout) ? 2000 : timeout * 1000 return driver.sync.request("mqtt.getOnlinecheck", timeout) } /** * 校验权限时间是否可以通行 * @param {array} permissions 权限记录数组 * @returns permissionId成功,false失败 */ function judgmentPermission(permissions) { let currentTime = Math.floor(Date.now() / 1000) for (let permission of permissions) { if (permission.timeType == 0) { // 永久权限 return permission.permissionId } else if (permission.beginTime <= currentTime && currentTime <= permission.endTime) { if (permission.timeType == 1) { // 时间段权限 return permission.permissionId } if (permission.timeType == 2 && permission.period) { // 每日权限 let dayTimes = permission.period if (dayTimes && dayTimes.split("|").some((dayTime) => isCurrentTimeInTimeRange(dayTime))) { return permission.permissionId } } if (permission.timeType == 3 && permission.period) { // 周重复权限 let dayTimes = JSON.parse(permission.period)[new Date().getDay() == 0 ? 7 : new Date().getDay()] if (dayTimes && dayTimes.split("|").some((dayTime) => isCurrentTimeInTimeRange(dayTime))) { return permission.permissionId } } } } return false } /** * 判断当前时间是否在时间段内 * @param {string} time eg:15:00-19:00 * @returns */ function isCurrentTimeInTimeRange(time) { // 分割开始时间和结束时间 let [startTime, endTime] = time.split('-'); // 解析开始时间的小时和分钟 let [startHour, startMinute] = startTime.split(':'); // 解析结束时间的小时和分钟 let [endHour, endMinute] = endTime.split(':'); // 获取当前时间 let currentTime = new Date(); // 创建开始时间的日期对象 let startDate = new Date(); startDate.setHours(parseInt(startHour, 10)); startDate.setMinutes(parseInt(startMinute, 10)); // 创建结束时间的日期对象 let endDate = new Date(); endDate.setHours(parseInt(endHour, 10)); endDate.setMinutes(parseInt(endMinute, 10)); // 检查当前时间是否在时间范围内 return currentTime >= startDate && currentTime < endDate; } // access通行上报 function reply(data, result) { delete data.permissionIds let record = { id: std.genRandomStr(10), keyId: data.keyId || "", permissionId: data.permissionId || "", userId: data.userId || "", type: data.type || "", code: data.code || "", door: data.door || "", timeStamp: data.timeStamp || Math.floor(Date.now() / 1000), result: result ? 0 : 1, extra: JSON.stringify(data.extra || {}), message: data.message || "", } // 处理双人认证信息 if (data.dualAuthInfo && data.dualAuthInfo.secondUserId) { record.userId2 = data.dualAuthInfo.secondUserId // 保存第二用户的完整信息 if (data.dualAuthInfo.secondUserExtra) { record.extra2 = JSON.stringify(data.dualAuthInfo.secondUserExtra) } else if (data.extra2) { record.extra2 = JSON.stringify(data.extra2) } else { record.extra2 = JSON.stringify({ name: data.dualAuthInfo.secondUserName }) } if (data.code2) { record.code2 = data.code2 } } // 存储通行记录,判断上限 let count = sqliteService.d1_pass_record.count() let configNum = config.get("access.offlineAccessNum"); configNum = configNum ? configNum : 2000; if (configNum > 0) { if (count >= configNum) { // 达到最大存储数量 // 删除最远的那条 let lastRecord = sqliteService.d1_pass_record.findAllOrderBytimeStampAsc({ page: 0, size: 1 }) if (lastRecord && lastRecord.length == 1) { //判断下如果是人脸 去删除一下人脸照片 if (lastRecord[0].type == 300) { dxos.systemBrief(`rm -rf ${lastRecord[0].code}`) } sqliteService.d1_pass_record.deleteByid(lastRecord[0].id) } } sqliteService.d1_pass_record.save(record) } let accessRecord = { userId: record.userId, type: record.type, result: record.result, name: data.extra && data.extra.name ? data.extra.name : "", timeStamp: record.timeStamp, extra: {}, error: record.message } let serialNo = record.id if (record.type == 300) { if (config.get('sys.strangerImage') && config.get('access.uploadToCloud')) { accessRecord.code = dxCommonUtils.fs.fileToBase64(record.code) } else { accessRecord.code = "" } } let payload = mqttService.mqttReply(serialNo, [accessRecord], mqttService.CODE.S_000) driver.mqtt.send("access_device/v2/event/access", JSON.stringify(payload)) } // 蓝牙回复 function bleReply (data, result) { let replyData = JSON.stringify({ serialNo: data.serialNo, code: result ? "000000" : "100000", time: Math.floor(Date.parse(new Date()) / 1000) }) driver.uartBle.send("0101" + dxCommonUtils.codec.strToUtf8Hex(replyData)) } export default accessService