/**
|
* 阿里云物联网客户端模块
|
* 使用 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<object>} 返回三元组信息 {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;
|