/** * 阿里云物联网客户端模块 * 使用 alibabacloud-iot-device-sdk 实现设备接入和数据上传 * 支持通过 API 动态获取设备三元组 */ const { device } = require('alibabacloud-iot-device-sdk'); const axios = require('axios'); const Qs = require('qs'); const config = require('./config.json'); const appLogger = require('./loggerConfig'); // 获取阿里云配置 const aliyunConfig = config.aliyunIoT; class AliyunIoTClient { constructor(deviceSerialNumber) { this.enabled = aliyunConfig.enabled; this.deviceSerialNumber = deviceSerialNumber; this.device = null; this.isConnected = false; this.deviceInfo = null; this.tripletInfo = null; if (!this.enabled) { return; } } /** * 通过 API 获取设备三元组 * @returns {Promise} 返回三元组信息 {deviceName, deviceSecret, productKey} */ async fetchTripletFromAPI() { try { if (!aliyunConfig.apiConfig || !aliyunConfig.apiConfig.baseUrl) { appLogger.logWarn(`API 配置缺失,无法获取三元组`, { module: 'aliyun', device: this.deviceSerialNumber, hasApiConfig: !!aliyunConfig.apiConfig, hasBaseUrl: !!aliyunConfig.apiConfig?.baseUrl }); console.warn(`⚠ API 配置不完整 [设备: ${this.deviceSerialNumber}]`); return null; } const apiConfig = aliyunConfig.apiConfig; const url = `${apiConfig.baseUrl}${apiConfig.secretUrl}`; const requestData = { isAutoRegister: 1, deviceName: this.deviceSerialNumber }; // 记录 API 请求详情 - 使用 INFO 级别以确保记录 appLogger.logInfo(`🔄 发起 API 请求获取三元组`, { module: 'aliyun', device: this.deviceSerialNumber, url: url, method: 'POST', params: requestData, timestamp: new Date().toISOString() }); appLogger.logAliyunFetchingTriplet(this.deviceSerialNumber); console.log(`🔄 正在从 API 获取设备三元组 [设备: ${this.deviceSerialNumber}]...`); console.log(` 📡 请求地址: ${url}`); console.log(` 📤 请求参数: ${JSON.stringify(requestData)}`); const response = await axios({ url: url, method: 'post', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, data: Qs.stringify(requestData), timeout: 10000 }); // 记录 API 响应详情 appLogger.logInfo(`✓ API 请求成功,收到响应`, { module: 'aliyun', device: this.deviceSerialNumber, statusCode: response.status, statusText: response.statusText, dataLength: JSON.stringify(response.data).length, timestamp: new Date().toISOString() }); console.log(`✓ API 请求成功 [状态码: ${response.status}]`); console.log(` 📥 响应数据大小: ${JSON.stringify(response.data).length} bytes`); if (response.data && response.data.data) { const tripletData = response.data.data; appLogger.logAliyunTripletFetched(this.deviceSerialNumber, { deviceName: tripletData.deviceName || this.deviceSerialNumber, deviceSecret: tripletData.deviceSecret, productKey: tripletData.productKey || aliyunConfig.productKey }); // 记录成功的三元组获取 appLogger.logInfo(`✓ 三元组获取成功`, { module: 'aliyun', device: this.deviceSerialNumber, deviceName: tripletData.deviceName || this.deviceSerialNumber, hasSecret: !!tripletData.deviceSecret, source: 'API', timestamp: new Date().toISOString() }); console.log(`✓ 设备三元组获取成功 [设备: ${this.deviceSerialNumber}]`); console.log(` ✔ 设备名称: ${tripletData.deviceName || this.deviceSerialNumber}`); console.log(` ✔ 密钥已获取: ${!!tripletData.deviceSecret}`); return { deviceName: tripletData.deviceName || this.deviceSerialNumber, deviceSecret: tripletData.deviceSecret, productKey: tripletData.productKey || aliyunConfig.productKey }; } else { console.error(`✗ API 返回数据格式错误:`, response.data); // 记录响应格式错误 appLogger.logError(`✗ API 返回数据格式错误`, new Error('Invalid API response format'), { module: 'aliyun', device: this.deviceSerialNumber, statusCode: response.status, response: JSON.stringify(response.data), timestamp: new Date().toISOString() }); appLogger.logAliyunTripletFetchError(this.deviceSerialNumber, new Error('Invalid API response format')); return null; } } catch (err) { console.error(`✗ 获取设备三元组失败 [${this.deviceSerialNumber}]: ${err.message}`); // 记录 API 请求错误详情 - 使用 ERROR 级别 appLogger.logError(`✗ API 请求失败`, err, { module: 'aliyun', device: this.deviceSerialNumber, errorType: err.code || err.name, errorMessage: err.message, requestUrl: aliyunConfig.apiConfig?.baseUrl + aliyunConfig.apiConfig?.secretUrl, timeout: err.code === 'ECONNABORTED' ? '连接超时' : undefined, connectionRefused: err.code === 'ECONNREFUSED' ? '连接被拒绝' : undefined, timestamp: new Date().toISOString() }); // 详细的错误日志输出 console.error(` ✗ 错误代码: ${err.code || err.name}`); console.error(` ✗ 错误信息: ${err.message}`); if (err.response) { console.error(` ✗ 响应状态: ${err.response.status}`); console.error(` ✗ 响应数据: ${JSON.stringify(err.response.data)}`); } appLogger.logAliyunTripletFetchError(this.deviceSerialNumber, err); return null; } } /** * 获取设备三元组(优先从 API 获取,回退到配置文件) */ async getTriplet() { // 首先尝试从 API 获取 if (aliyunConfig.apiConfig && aliyunConfig.apiConfig.baseUrl) { appLogger.logInfo(`尝试从 API 获取三元组`, { module: 'aliyun', device: this.deviceSerialNumber, api: aliyunConfig.apiConfig.baseUrl + aliyunConfig.apiConfig.secretUrl }); const triplet = await this.fetchTripletFromAPI(); if (triplet) { return triplet; } appLogger.logWarn(`API 获取三元组失败,尝试本地配置`, { module: 'aliyun', device: this.deviceSerialNumber }); console.warn(`⚠ API 获取失败,尝试从配置文件获取 [设备: ${this.deviceSerialNumber}]`); } // 回退到配置文件(如果有设备配置) if (aliyunConfig.devices && aliyunConfig.devices[this.deviceSerialNumber]) { this.deviceInfo = aliyunConfig.devices[this.deviceSerialNumber]; appLogger.logInfo(`三元组获取成功`, { module: 'aliyun', device: this.deviceSerialNumber, deviceName: this.deviceInfo.deviceName, source: 'config' }); return { deviceName: this.deviceInfo.deviceName, deviceSecret: this.deviceInfo.deviceSecret, productKey: aliyunConfig.productKey }; } // 三元组获取完全失败 appLogger.logError(`无法获取设备三元组`, new Error('Device not found'), { module: 'aliyun', device: this.deviceSerialNumber, apiEnabled: !!aliyunConfig.apiConfig?.baseUrl, hasLocalConfig: !!(aliyunConfig.devices && aliyunConfig.devices[this.deviceSerialNumber]) }); appLogger.logAliyunTripletFetchError(this.deviceSerialNumber, 'Device not found in config'); console.warn(`⚠ 设备 ${this.deviceSerialNumber} 未在阿里云配置中注册,且无法从 API 获取`); return null; } /** * 连接到阿里云 IoT */ async connect() { if (!this.enabled) { return Promise.resolve(); } return new Promise(async (resolve, reject) => { try { // 获取三元组信息 appLogger.logInfo(`开始获取设备三元组`, { module: 'aliyun', device: this.deviceSerialNumber, timestamp: new Date().toISOString() }); const triplet = await this.getTriplet(); if (!triplet || !triplet.deviceSecret) { appLogger.logError(`三元组验证失败`, new Error('Invalid triplet'), { module: 'aliyun', device: this.deviceSerialNumber, hasTriplet: !!triplet, hasSecret: !!(triplet && triplet.deviceSecret) }); console.error(`✗ 无法获取有效的设备三元组 [${this.deviceSerialNumber}]`); reject(new Error('Invalid triplet information')); return; } const productKey = triplet.productKey || aliyunConfig.productKey; const regionId = aliyunConfig.regionId; const deviceName = triplet.deviceName; const deviceSecret = triplet.deviceSecret; // 记录即将创建的设备实例 appLogger.logInfo(`准备创建阿里云设备实例`, { module: 'aliyun', device: this.deviceSerialNumber, deviceName: deviceName, productKey: productKey, regionId: regionId }); // 构建设备配置 const options = { productKey: productKey, deviceName: deviceName, deviceSecret: deviceSecret, regionId: regionId, keepalive: 60 }; console.log(`🔗 创建阿里云设备实例 [${deviceName}]...`); // 创建设备实例 this.device = device(options); // 设置连接超时 const connectTimeout = setTimeout(() => { appLogger.logError(`阿里云连接超时`, new Error('Connection timeout'), { module: 'aliyun', device: this.deviceSerialNumber, timeout: 15000, deviceName: deviceName }); console.error(`✗ 阿里云 IoT 连接超时 [设备: ${this.deviceSerialNumber}]`); reject(new Error('Connection timeout')); }, 15000); // 监听连接事件 this.device.on('connect', () => { clearTimeout(connectTimeout); this.isConnected = true; this.tripletInfo = triplet; appLogger.logInfo(`阿里云连接建立成功`, { module: 'aliyun', device: this.deviceSerialNumber, deviceName: deviceName, timestamp: new Date().toISOString() }); appLogger.logAliyunConnected(this.deviceSerialNumber, deviceName); console.log(`✓ 阿里云 IoT 连接成功 [设备: ${this.deviceSerialNumber}] [${deviceName}]`); // 订阅主题 this.subscribe(); resolve(); }); // 监听错误事件 this.device.on('error', (err) => { clearTimeout(connectTimeout); appLogger.logError(`阿里云连接错误`, err, { module: 'aliyun', device: this.deviceSerialNumber, deviceName: deviceName, errorType: err.code || err.name }); appLogger.logAliyunConnectionError(this.deviceSerialNumber, err); console.error(`✗ 阿里云 IoT 错误 [设备: ${this.deviceSerialNumber}]:`, err.message); this.isConnected = false; if (!this.isConnected) { reject(err); } }); // 监听离线事件 this.device.on('offline', () => { this.isConnected = false; appLogger.logAliyunOffline(this.deviceSerialNumber); console.warn(`⚠ 阿里云 IoT 离线 [设备: ${this.deviceSerialNumber}]`); }); // 监听重连事件 this.device.on('reconnect', () => { appLogger.logInfo(`阿里云 IoT 重新连接中 [设备: ${this.deviceSerialNumber}]`, { module: 'aliyun', device: this.deviceSerialNumber }); console.log(`⟳ 阿里云 IoT 重新连接中 [设备: ${this.deviceSerialNumber}]`); }); } catch (err) { appLogger.logError(`创建设备实例失败`, err, { device: this.deviceSerialNumber }); console.error(`创建设备实例失败:`, err); reject(err); } }); } /** * 订阅主题 */ subscribe() { if (!this.enabled || !this.device) return; // 订阅属性设置命令 this.device.subscribe('/a1/{productKey}/{deviceName}/thing/service/property/set'); // 监听消息 this.device.on('message', (topic, payload) => { console.log(`\n📩 收到阿里云下行消息 [${topic}]:`, payload); }); } /** * 发布医疗数据属性 * @param {object} keyData - 关键数据对象 */ publishProperties(keyData) { if (!this.enabled || !this.isConnected || !this.device) { if (this.enabled && !this.isConnected) { console.warn(`⚠ 设备 ${this.deviceSerialNumber} 未连接到阿里云 IoT`); } return; } // 构建属性数据 const properties = { // 基本信息 deviceModel: keyData.deviceModel, softwareVersion: keyData.softwareVersion, // 超滤参数 ultraFiltrateTarget: parseFloat(keyData.ultraFiltrateTarget) || 0, ultraFiltrateTotal: parseFloat(keyData.ultraFiltrateTotal) || 0, ultrafiltrateRateSet: parseFloat(keyData.ultrafiltrateRateSet) || 0, // 透析液参数 dialysisFluidFlow: parseFloat(keyData.dialysisFluidFlow) || 0, dialysisFluidActual: parseFloat(keyData.dialysisFluidActual) || 0, dialysisFluidTemp: parseFloat(keyData.dialysisFluidTemp) || 0, sodiumConc: parseFloat(keyData.sodiumConc) || 0, conductivity: parseFloat(keyData.conductivity) || 0, // 血液/血流参数 bloodFlow: parseFloat(keyData.bloodFlow) || 0, effectiveBloodFlow: parseFloat(keyData.effectiveBloodFlow) || 0, dialysisBloodVolume: parseFloat(keyData.dialysisBloodVolume) || 0, returnedBloodVolume: parseFloat(keyData.returnedBloodVolume) || 0, plasmaNA: parseFloat(keyData.plasmaNA) || 0, clearanceRate: parseFloat(keyData.clearanceRate) || 0, // 质量指标 instantKT: parseFloat(keyData.instantKT) || 0, ktvTarget: parseFloat(keyData.ktvTarget) || 0, // 电解质参数 bicarbonate: parseFloat(keyData.bicarbonate) || 0, // 压力参数 arterialPressure: parseFloat(keyData.arterialPressure) || 0, venousPressure: parseFloat(keyData.venousPressure) || 0, transmembranePressure: parseFloat(keyData.transmembranePressure) || 0, // 膜型参数 dialysisMode: keyData.dialysisMode, // 状态参数 runStatus: keyData.runStatus, alarmStatus: keyData.alarmStatus, treatmentDuration: parseFloat(keyData.treatmentDuration) || 0, // 置换参数 replacementFluidTarget: parseFloat(keyData.replacementFluidTarget) || 0, replacementRate: parseFloat(keyData.replacementRate) || 0, replacementVolume: parseFloat(keyData.replacementVolume) || 0 }; try { const propertyStr = JSON.stringify(properties); appLogger.logAliyunPublishing(this.deviceSerialNumber, 'properties', propertyStr.length); this.device.postProperty(properties, (err, data) => { if (err) { appLogger.logAliyunPublishError(this.deviceSerialNumber, 'properties', err); console.error(`发布属性失败 [${this.deviceSerialNumber}]:`, err.message); } }); } catch (err) { appLogger.logError(`发布属性异常`, err, { device: this.deviceSerialNumber, module: 'aliyun' }); console.error(`发布属性异常:`, err.message); } } /** * 发布事件 * @param {object} event - 事件对象 */ publishEvent(event) { if (!this.enabled || !this.isConnected || !this.device) { return; } const eventData = { eventType: event.type, description: event.description, severity: event.severity, timestamp: Date.now() }; try { const eventStr = JSON.stringify(eventData); appLogger.logAliyunPublishing(this.deviceSerialNumber, 'event', eventStr.length); // 发布自定义事件 this.device.postEvent('alert', eventData, (err, data) => { if (err) { appLogger.logAliyunPublishError(this.deviceSerialNumber, 'event', err); console.error(`发布事件失败:`, err.message); } else { appLogger.logInfo(`事件发布成功`, { module: 'aliyun', device: this.deviceSerialNumber, eventType: event.type, description: event.description }); console.log(`✓ 事件发布成功 [${this.deviceSerialNumber}] - ${event.description}`); } }); } catch (err) { appLogger.logError(`发布事件异常`, err, { device: this.deviceSerialNumber, module: 'aliyun' }); console.error(`发布事件异常:`, err.message); } } /** * 断开连接 */ disconnect() { if (!this.enabled || !this.device) return; try { this.device.disconnect(); this.isConnected = false; console.log(`✓ 阿里云 IoT 连接已关闭 [设备: ${this.deviceSerialNumber}]`); } catch (err) { console.error(`断开连接失败:`, err.message); } } /** * 获取连接状态 */ getConnectionStatus() { return { deviceSerialNumber: this.deviceSerialNumber, aliyunEnabled: this.enabled, isConnected: this.isConnected, deviceName: this.tripletInfo?.deviceName }; } } module.exports = AliyunIoTClient;