/** * 粮情服务模块 * 负责气体浓度数据获取和状态信息数据获取 */ import logger from "../../dxmodules/dxLogger.js" import config from "../../dxmodules/dxConfig.js" import http from "../../dxmodules/dxHttp.js" import bus from '../../dxmodules/dxEventBus.js' import std from "../../dxmodules/dxStd.js" import driver from "../driver.js" const grainService = {} // 从配置中获取业务编码定义 const functionId = { gasDetection: config.get('functionId.gasDetection') || "1000", safeInputControl: config.get('functionId.safeInputControl') || "2000", lightControl: config.get('functionId.lightControl') || "3000", doorStatus: config.get('functionId.doorStatus') || "4000" } // 从配置中获取接口返回编码 const respCode = { success: config.get('respCode.success') || "200", badRequest: config.get('respCode.badRequest') || "400", unauthorized: config.get('respCode.unauthorized') || "401", forbidden: config.get('respCode.forbidden') || "403", notFound: config.get('respCode.notFound') || "404", serverError: config.get('respCode.serverError') || "500" } // 错误码对应的错误信息 const errorMessages = { "400": "请求参数有误", "401": "未登录,用户不存在", "403": "用户无权限", "404": "请求功能不存在", "500": "请求执行异常,请联系管理员" } // 操作按钮对应的语音文件 const voiceFiles = { // 允许进仓模式 "11": "btn11.wav", // 允许进仓模式 "12": "btn12.wav", // 入仓 "13": "btn13.wav", // 出仓 // 冬季通风模式 "21": "btn21.wav", // 冬季通风模式 "22": "btn22.wav", // 启动 "23": "btn23.wav", // 关闭 // 禁止进仓模式 "31": "btn31.wav", // 禁止进仓模式 "32": "btn32.wav", // 紧急入仓 "33": "btn33.wav", // 出仓 // 照明控制 "light_open": "light_open.wav", // 开灯 "light_close": "light_close.wav" // 关灯 } /** * 根据错误码获取错误信息 * @param {string} code - 错误码 * @returns {string} 错误信息 */ function getErrorMessage(code) { return errorMessages[code] || "操作失败" } /** * 根据操作按钮和功能码获取语音文件 * @param {string} functionId - 功能码 * @param {object} params - 请求参数,包含mode和btn * @returns {string} 语音文件路径 */ function getVoiceFile(functionId, params) { // 照明控制功能 if (functionId === "3000") { // 开灯 if (String(params.btn) === "1") return voiceFiles["light_open"] // 关灯 if (String(params.btn) === "2") return voiceFiles["light_close"] } // 安全入仓控制功能 if (functionId === "2000" && params) { const { mode, btn } = params // 模式1: 允许进仓模式 if (mode === 1) { if (!btn) return voiceFiles["11"] // 允许进仓模式按钮 if (String(btn) === "1") return voiceFiles["12"] // 入仓按钮 if (String(btn) === "2") return voiceFiles["13"] // 出仓按钮 } // 模式2: 冬季通风模式 if (mode === 2) { if (!btn) return voiceFiles["21"] // 冬季通风模式按钮 if (String(btn) === "1") return voiceFiles["22"] // 启动按钮 if (String(btn) === "2") return voiceFiles["23"] // 关闭按钮 } // 模式3: 禁止进仓模式 if (mode === 3) { if (!btn) return voiceFiles["31"] // 禁止进仓模式按钮 if (String(btn) === "1") return voiceFiles["32"] // 紧急入仓按钮 if (String(btn) === "2") return voiceFiles["33"] // 出仓按钮 } } return null } // 从配置中获取HTTP接口路径 function getHttpUrl() { return config.get('http.safeInputAccess') || "http://192.168.1.199:80/cgi-bin/safeInputAccess" } // 存储气体浓度数据 let gasDataStorage = null /** * 检查气体浓度 * @param {function} callback - 回调函数,在气体浓度检测完成后调用 * @returns {boolean} true表示气体浓度合格,false表示气体浓度不合格 */ grainService.checkGasConcentration = function(callback) { // 使用setTimeout将HTTP请求放入后台执行,避免阻塞主线程 std.setTimeout(() => { try { // 确保URL格式正确,添加http://前缀 let url = getHttpUrl() if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'http://' + url } const timeout = 3000 // 3秒超时 logger.info(`[grain]: 正在获取气体浓度数据: ${url}`) // 构建POST请求数据 const postData = { sn: config.get("sys.sn") || " ", // 设备序列号,从配置中获取 houseId: "0000", // 仓廒编码,默认填充"0000" outId: "0000", // 自定义编码,默认填充"0000" functionId: functionId.gasDetection, // 气体浓度检测功能码 timestamp: Date.now().toString() // 时间戳 } logger.info(`[grain]: 发送POST请求数据: ${JSON.stringify(postData)}`) // 发送HTTP POST请求 let response = http.post(url, postData, timeout) // logger.info(`[grain]: 气体浓度数据响应: ${JSON.stringify(response)}`) // 解析响应数据 // 检查response是否为字符串,如果是则解析为对象 if (typeof response === 'string') { response = JSON.parse(response) } if (response && response.body) { // 解析响应体 let gasData try { gasData = JSON.parse(response.body) logger.info(`[grain]: 解析后的气体浓度数据: ${JSON.stringify(gasData)}`) // 根据接口返回编码进行日志输出 if (gasData.respCode === respCode.success) { logger.info(`[grain]: 气体浓度检测接口调用成功`) } else { logger.error(`[grain]: 气体浓度检测接口调用失败,返回编码: ${gasData.respCode}, 返回信息: ${gasData.respMsg}`) } } catch (parseError) { logger.error(`[grain]: 解析气体浓度数据失败: ${parseError.message}`) // 尝试清理响应体中的转义字符 try { // 移除多余的转义字符 const cleanedBody = response.body.replace(/\\r\\n/g, '').replace(/\\t/g, '').replace(/\"/g, '"') gasData = JSON.parse(cleanedBody) logger.info(`[grain]: 清理后解析的气体浓度数据: ${JSON.stringify(gasData)}`) // 根据接口返回编码进行日志输出 if (gasData.respCode === respCode.success) { logger.info(`[grain]: 气体浓度检测接口调用成功`) } else { logger.error(`[grain]: 气体浓度检测接口调用失败,返回编码: ${gasData.respCode}, 返回信息: ${gasData.respMsg}`) } } catch (cleanError) { logger.error(`[grain]: 清理后解析气体浓度数据仍失败: ${cleanError.message}`) // 调用回调函数 if (callback) { callback() } return } } // 存储气体数据 gasDataStorage = gasData // 检查气体浓度是否合格 // 新格式: {"value":[{"o2":"20.5","statusO2":"0","co2":"401","statusCo2":"0","ph3":"0","statusPh3":"0"}],"status":"1"} // statusO2/statusCo2/statusPh3: "0"表示合格,"1"表示不合格(仅用于UI显示) // data.status: "0"表示最终合格,"1"表示最终不合格 const gasValue = gasData.data && gasData.data.value ? gasData.data.value[0] : null const isO2Qualified = gasValue && gasValue.statusO2 === "0" const isCo2Qualified = gasValue && gasValue.statusCo2 === "0" const isPh3Qualified = gasValue && gasValue.statusPh3 === "0" // 触发事件更新UI bus.fire('gasConcentrationUpdated', gasData) // 最终合格判断只依赖status字段值 const isAllQualified = gasData.data && gasData.data.status === "0" if (isAllQualified) { logger.info("[grain]: 气体浓度检测合格") } else { logger.info("[grain]: 气体浓度检测不合格") } // 调用回调函数 if (callback) { callback() } } else { logger.error(`[grain]: 气体浓度数据获取失败: ${response ? "无响应体" : "无响应"}`) // 调用回调函数 if (callback) { callback() } } } catch (error) { logger.error(`[grain]: 气体浓度检测错误: ${error.message}`) // 调用回调函数 if (callback) { callback() } } }, 0) // 立即返回,避免阻塞主线程 return true } /** * 获取存储的气体浓度数据 * @returns {object|null} 存储的气体浓度数据,如果没有则返回null */ grainService.getGasData = function() { return gasDataStorage } /** * 检查设备状态信息 * @param {object} params - 请求参数 * @param {string} params.user1 - 人脸识别用户1 * @param {string} params.user2 - 人脸识别用户2 * @param {string} params.mode - 选择模式(1-允许入仓;2-冬季通风;3-禁止入仓) * @param {string} params.btn - 操作按钮(1-入仓/开启;2-出仓/关闭) * @returns {boolean} true表示状态信息获取成功,false表示失败 */ grainService.checkDevConcentration = function(params) { // 使用setTimeout将HTTP请求放入后台执行,避免阻塞主线程 std.setTimeout(() => { try { // 确保URL格式正确,添加http://前缀 let url = getHttpUrl() if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'http://' + url } const timeout = 3000 // 3秒超时 // logger.info(`[grain]: 正在获取状态信息数据: ${url}`) // 构建POST请求数据 const postData = { sn: config.get("sys.sn") || " ", // 设备序列号,从配置中获取 houseId: "0000", // 仓廒编码,默认填充"0000" outId: "0000", // 自定义编码,默认填充"0000" functionId: params && params.functionId ? params.functionId : functionId.safeInputControl, // 功能码,默认为安全入仓联动控制 timestamp: Date.now().toString(), // 时间戳 data: {} } // 添加可选参数 if (params) { if (params.user1) postData.data.user1 = params.user1 if (params.user2) postData.data.user2 = params.user2 if (params.mode) postData.data.mode = params.mode if (params.btn) postData.data.btn = params.btn } logger.info(`[grain]: 发送POST请求数据: ${JSON.stringify(postData)}`) // 发送HTTP POST请求 let response = http.post(url, postData, timeout) // logger.info(`[grain]: 状态信息数据响应: ${JSON.stringify(response)}`) // 解析响应数据 // 检查response是否为字符串,如果是则解析为对象 if (typeof response === 'string') { response = JSON.parse(response) } if (response && response.body) { // 解析响应体 let statusData try { statusData = JSON.parse(response.body) logger.info(`[grain]: 解析后的状态信息数据: ${JSON.stringify(statusData)}`) // 根据接口返回编码进行日志输出 if (statusData.respCode === respCode.success) { logger.info(`[grain]: 状态信息接口调用成功`) // 检查data是否为空,不为空才触发通知 if (Object.keys(postData.data).length > 0) { // 触发成功弹窗 bus.fire('showAccessResult', { faceAuth: true, gasConcentration: true, accessAllowed: true, message: '*执行成功*' }) // 播放成功语音提示 try { const voiceFile = getVoiceFile(postData.functionId, postData.data) if (voiceFile) { driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/${voiceFile}`) logger.info(`[grain]: 播放成功语音提示: ${voiceFile}`) } } catch (error) { logger.error(`[grain]: 播放语音提示失败: ${error.message}`) } } } else { logger.error(`[grain]: 状态信息接口调用失败,返回编码: ${statusData.respCode}, 返回信息: ${statusData.respMsg}`) // 检查data是否为空,不为空才触发通知 if (Object.keys(postData.data).length > 0) { // 触发失败弹窗 bus.fire('showAccessResult', { faceAuth: true, gasConcentration: true, accessAllowed: false, message: statusData.respMsg || getErrorMessage(statusData.respCode) }) // 播放失败语音提示 try { driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/failed.wav`) logger.info(`[grain]: 播放失败语音提示: failed.wav`) } catch (error) { logger.error(`[grain]: 播放语音提示失败: ${error.message}`) } } } } catch (parseError) { logger.error(`[grain]: 解析状态信息数据失败: ${parseError.message}`) // 尝试清理响应体中的转义字符 try { // 移除多余的转义字符 const cleanedBody = response.body.replace(/\\r\\n/g, '').replace(/\\t/g, '').replace(/\"/g, '"') statusData = JSON.parse(cleanedBody) logger.info(`[grain]: 清理后解析的状态信息数据: ${JSON.stringify(statusData)}`) // 根据接口返回编码进行日志输出 if (statusData.respCode === respCode.success) { logger.info(`[grain]: 状态信息接口调用成功`) // 检查data是否为空,不为空才触发通知 if (Object.keys(postData.data).length > 0) { // 触发成功弹窗 bus.fire('showAccessResult', { faceAuth: true, gasConcentration: true, accessAllowed: true, message: '*执行成功*' }) // 播放成功语音提示 try { const voiceFile = getVoiceFile(postData.functionId, postData.data) if (voiceFile) { driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/${voiceFile}`) logger.info(`[grain]: 播放成功语音提示: ${voiceFile}`) } } catch (error) { logger.error(`[grain]: 播放语音提示失败: ${error.message}`) } } } else { logger.error(`[grain]: 状态信息接口调用失败,返回编码: ${statusData.respCode}, 返回信息: ${statusData.respMsg}`) // 检查data是否为空,不为空才触发通知 if (Object.keys(postData.data).length > 0) { // 触发失败弹窗 bus.fire('showAccessResult', { faceAuth: true, gasConcentration: true, accessAllowed: false, message: statusData.respMsg || getErrorMessage(statusData.respCode) }) // 播放失败语音提示 try { driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/failed.wav`) logger.info(`[grain]: 播放失败语音提示: failed.wav`) } catch (error) { logger.error(`[grain]: 播放语音提示失败: ${error.message}`) } } } } catch (cleanError) { logger.error(`[grain]: 清理后解析状态信息数据仍失败: ${cleanError.message}`) return false } } // 触发事件更新UI bus.fire('statusInfoUpdated', statusData) return true } else { logger.error(`[grain]: 状态信息数据获取失败: ${response ? "无响应体" : "无响应"}`) // 网络请求失败时,默认返回false(安全起见) return false } } catch (error) { logger.error(`[grain]: 状态信息检测错误: ${error.message}`) // 发生错误时,默认返回false(安全起见) return false } }, 0) // 立即返回,避免阻塞主线程 return true } /** * 仓内照明联动控制 * @param {object} params - 请求参数 * @param {string} params.user1 - 人脸识别用户1 * @param {string} params.user2 - 人脸识别用户2 * @param {string} params.btn - 操作按钮(1-开灯;2-关灯) * @returns {boolean} true表示控制成功,false表示控制失败 */ grainService.controlLight = function(params) { // 使用setTimeout将HTTP请求放入后台执行,避免阻塞主线程 std.setTimeout(() => { try { // 确保URL格式正确,添加http://前缀 let url = getHttpUrl() if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'http://' + url } const timeout = 3000 // 3秒超时 // 构建POST请求数据 const postData = { sn: config.get("sys.sn") || " ", // 设备序列号,从配置中获取 houseId: "0000", // 仓廒编码,默认填充"0000" outId: "0000", // 自定义编码,默认填充"0000" functionId: functionId.lightControl, // 仓内照明联动控制功能码 timestamp: Date.now().toString(), // 时间戳 data: {} } // 添加可选参数 if (params) { if (params.user1) postData.data.user1 = params.user1 if (params.user2) postData.data.user2 = params.user2 if (params.btn) postData.data.btn = params.btn } logger.info(`[grain]: 发送仓内照明联动控制请求数据: ${JSON.stringify(postData)}`) // 发送HTTP POST请求 let response = http.post(url, postData, timeout) // 解析响应数据 // 检查response是否为字符串,如果是则解析为对象 if (typeof response === 'string') { response = JSON.parse(response) } if (response && response.body) { // 解析响应体 let statusData try { statusData = JSON.parse(response.body) logger.info(`[grain]: 解析后的仓内照明联动控制响应数据: ${JSON.stringify(statusData)}`) // 根据接口返回编码进行日志输出 if (statusData.respCode === respCode.success) { logger.info(`[grain]: 仓内照明联动控制接口调用成功`) // 触发成功弹窗 bus.fire('showAccessResult', { faceAuth: true, gasConcentration: true, accessAllowed: true, message: '*执行成功*' }) // 播放成功语音提示 try { const voiceFile = getVoiceFile(postData.functionId, postData.data) if (voiceFile) { driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/${voiceFile}`) logger.info(`[grain]: 播放成功语音提示: ${voiceFile}`) } } catch (error) { logger.error(`[grain]: 播放语音提示失败: ${error.message}`) } } else { logger.error(`[grain]: 仓内照明联动控制接口调用失败,返回编码: ${statusData.respCode}, 返回信息: ${statusData.respMsg}`) // 触发失败弹窗 bus.fire('showAccessResult', { faceAuth: true, gasConcentration: true, accessAllowed: false, message: statusData.respMsg || getErrorMessage(statusData.respCode) }) // 播放失败语音提示 try { driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/failed.wav`) logger.info(`[grain]: 播放失败语音提示: failed.wav`) } catch (error) { logger.error(`[grain]: 播放语音提示失败: ${error.message}`) } } } catch (parseError) { logger.error(`[grain]: 解析仓内照明联动控制响应数据失败: ${parseError.message}`) // 尝试清理响应体中的转义字符 try { // 移除多余的转义字符 const cleanedBody = response.body.replace(/\\r\\n/g, '').replace(/\\t/g, '').replace(/\"/g, '"') statusData = JSON.parse(cleanedBody) logger.info(`[grain]: 清理后解析的仓内照明联动控制响应数据: ${JSON.stringify(statusData)}`) // 根据接口返回编码进行日志输出 if (statusData.respCode === respCode.success) { logger.info(`[grain]: 仓内照明联动控制接口调用成功`) // 触发成功弹窗 bus.fire('showAccessResult', { faceAuth: true, gasConcentration: true, accessAllowed: true, message: '*执行成功*' }) // 播放成功语音提示 try { const voiceFile = getVoiceFile(postData.functionId, postData.data) if (voiceFile) { driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/${voiceFile}`) logger.info(`[grain]: 播放成功语音提示: ${voiceFile}`) } } catch (error) { logger.error(`[grain]: 播放语音提示失败: ${error.message}`) } } else { logger.error(`[grain]: 仓内照明联动控制接口调用失败,返回编码: ${statusData.respCode}, 返回信息: ${statusData.respMsg}`) // 触发失败弹窗 bus.fire('showAccessResult', { faceAuth: true, gasConcentration: true, accessAllowed: false, message: statusData.respMsg || getErrorMessage(statusData.respCode) }) // 播放失败语音提示 try { driver.alsa.play(`/app/code/resource/${config.get("base.language") == "CN" ? "CN" : "EN"}/wav/failed.wav`) logger.info(`[grain]: 播放失败语音提示: failed.wav`) } catch (error) { logger.error(`[grain]: 播放语音提示失败: ${error.message}`) } } } catch (cleanError) { logger.error(`[grain]: 清理后解析仓内照明联动控制响应数据仍失败: ${cleanError.message}`) return false } } // 触发事件更新UI bus.fire('statusInfoUpdated', statusData) return true } else { logger.error(`[grain]: 仓内照明联动控制请求失败: ${response ? "无响应体" : "无响应"}`) // 网络请求失败时,默认返回false(安全起见) return false } } catch (error) { logger.error(`[grain]: 仓内照明联动控制错误: ${error.message}`) // 发生错误时,默认返回false(安全起见) return false } }, 0) // 立即返回,避免阻塞主线程 return true } /** * 发送门磁状态消息 * @param {object} doorStatusData - 门磁状态数据 * @param {number} doorStatusData.status - 门磁状态,0表示门关,1表示门开 * @param {string} doorStatusData.statusText - 门磁状态文本 * @param {number} doorStatusData.time - 时间戳 */ grainService.sendDoorStatusMessage = function(doorStatusData) { // 使用setTimeout将HTTP请求放入后台执行,避免阻塞主线程 std.setTimeout(() => { try { // 确保URL格式正确,添加http://前缀 let url = getHttpUrl() if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'http://' + url } const timeout = 3000 // 3秒超时 logger.info(`[grain]: 正在发送门磁状态消息: ${url}`) // 构建POST请求数据 const postData = { sn: config.get("sys.sn") || " ", // 设备序列号,从配置中获取 houseId: "0000", // 仓廒编码,默认填充"0000" outId: "0000", // 自定义编码,默认填充"0000" functionId: functionId.doorStatus, // 门磁状态功能码 timestamp: Date.now().toString(), // 时间戳 data: { doorStatus: doorStatusData.status // 门磁状态 } } logger.info(`[grain]: 发送门磁状态消息数据: ${JSON.stringify(postData)}`) // 发送HTTP POST请求 let response = http.post(url, postData, timeout) // 解析响应数据 if (typeof response === 'string') { response = JSON.parse(response) } if (response && response.body) { let respData try { respData = JSON.parse(response.body) logger.info(`[grain]: 门磁状态消息响应: ${JSON.stringify(respData)}`) } catch (error) { logger.error(`[grain]: 解析门磁状态消息响应失败: ${error.message}`) } } else { logger.info(`[grain]: 门磁状态消息响应为空`) } } catch (error) { logger.error(`[grain]: 发送门磁状态消息失败: ${error.message}`) } }, 0) } // 监听门磁状态变化事件 bus.on('doorStatusChanged', (doorStatusData) => { logger.info(`[grain]: 接收到门磁状态变化事件: ${JSON.stringify(doorStatusData)}`) // 调用发送门磁状态消息的函数 try { if (typeof grainService.sendDoorStatusMessage === 'function') { grainService.sendDoorStatusMessage(doorStatusData) } else { logger.error('[grain]: sendDoorStatusMessage 不是一个函数') logger.error('[grain]: grainService.sendDoorStatusMessage =', grainService.sendDoorStatusMessage) } } catch (error) { logger.error('[grain]: 调用sendDoorStatusMessage出错:', error.message) } }) export default grainService