// aliyun-iot.js
|
const IotDevice = require('alibabacloud-iot-device-sdk');
|
const logger = require('./logger');
|
const { getAliyunDeviceSecret, getAliyunConfig } = require('./api');
|
|
// 固定设备信息(可被配置覆盖)
|
const PRODUCT_KEY = 'k08fzZOZdxN';
|
// 可选的默认设备密钥(仅用于兼容单设备调试),实际应按设备序列号获取专属密钥
|
const DEFAULT_DEVICE_SECRET = null;
|
|
// 存储设备实例(长连接复用)
|
const devices = new Map();
|
|
/**
|
* 获取或创建设备实例(长连接)
|
* @param {string} deviceId
|
* @return {Object|null} 设备实例 或 null(创建失败)
|
*/
|
// 统一封装:按设备号从你的服务获取三元组
|
const aliyunCfg = getAliyunConfig();
|
let configLogged = false;
|
|
async function fetchDeviceTriple(deviceId, log) {
|
try {
|
(log || logger).info(`${deviceId} 请求三元组,设备号: ${deviceId}`);
|
const resp = await getAliyunDeviceSecret(aliyunCfg.secretApiPath || 'device/info/getAliyunDeviceSecret', deviceId);
|
const body = resp && resp.data ? resp.data : resp;
|
// 兼容不同返回结构:{code, data:{productKey, deviceName, deviceSecret}} 或 {productKey, deviceName, deviceSecret}
|
const payload = body && body.data ? body.data : body;
|
const pk = payload?.productKey || aliyunCfg.productKeyOverride || PRODUCT_KEY;
|
const dn = payload?.deviceName || deviceId;
|
const ds = payload?.deviceSecret || null;
|
if (!ds) {
|
throw new Error('未获取到 deviceSecret');
|
}
|
return { productKey: pk, deviceName: dn, deviceSecret: ds };
|
} catch (err) {
|
(log || logger).error(`${deviceId} 获取三元组失败: ${err.message}`);
|
return null;
|
}
|
}
|
|
async function getOrCreateDevice(deviceId, ctx) {
|
const log = logger.forDevice(deviceId, ctx?.ip, ctx?.clientId);
|
if (!deviceId || typeof deviceId !== 'string') {
|
log.error('无效的 deviceId', { deviceId });
|
return null;
|
}
|
|
const existingDevice = devices.get(deviceId);
|
if (existingDevice) {
|
return existingDevice;
|
}
|
|
try {
|
// 尝试为该设备ID获取独立的 deviceSecret
|
const triple = await fetchDeviceTriple(deviceId, log);
|
if (!triple) return null;
|
|
const { productKey, deviceName, deviceSecret } = triple;
|
const region = aliyunCfg.region || 'cn-shanghai';
|
const host = `${productKey}.iot-as-mqtt.${region}.aliyuncs.com`;
|
|
if (!configLogged) {
|
configLogged = true;
|
log.info('aliyun.config', {
|
source: aliyunCfg.__meta?.source,
|
path: aliyunCfg.__meta?.path,
|
region,
|
productKeyOverride: aliyunCfg.productKeyOverride || '',
|
apiBaseUrl: aliyunCfg.apiBaseUrl,
|
secretApiPath: aliyunCfg.secretApiPath,
|
});
|
}
|
|
const device = IotDevice.device({
|
productKey,
|
deviceName,
|
deviceSecret,
|
host,
|
port: 1883,
|
keepalive: 60,
|
clean: true,
|
reconnectPeriod: 5000 // 自动重连间隔
|
});
|
|
device.on('connect', () => {
|
log.info(`✅ 设备 ${deviceId} 已连接到阿里云 IoT`);
|
});
|
|
device.on('reconnect', () => {
|
log.warn(`🔁 设备 ${deviceId} 正在重连...`);
|
});
|
|
device.on('error', (err) => {
|
log.error(`❌ 设备 ${deviceId} 发生错误`, {
|
错误: err.message,
|
堆栈: err.stack
|
});
|
});
|
|
device.on('offline', () => {
|
log.warn(`⚠️ 设备 ${deviceId} 已离线`);
|
});
|
|
// 缓存实例
|
devices.set(deviceId, device);
|
|
return device;
|
|
} catch (err) {
|
log.error('创建设备实例失败', {
|
错误: err.message,
|
堆栈: err.stack,
|
deviceId
|
});
|
return null;
|
}
|
}
|
|
/**
|
* 发送数据到阿里云 IoT(高频上报,长连接)
|
* @param {string} deviceId
|
* @param {Object} data
|
*/
|
async function sendDataToAliyun(deviceId, data, ctx) {
|
const log = logger.forDevice(deviceId, ctx?.ip, ctx?.clientId);
|
// 参数校验
|
if (!deviceId || typeof deviceId !== 'string' || !data || typeof data !== 'object') {
|
log.error('sendDataToAliyun 参数错误', { deviceId, data: typeof data });
|
return;
|
}
|
|
let device;
|
try {
|
device = await getOrCreateDevice(deviceId, ctx);
|
if (!device) {
|
log.error('无法创建设备实例', { deviceId });
|
return;
|
}
|
|
// 如果未连接,不阻塞,直接返回(可选:记录日志)
|
if (!device.connected) {
|
log.warn(`设备 ${deviceId} 当前未连接,跳过发送`);
|
return;
|
}
|
|
// 发送数据(可配置是否打印 payload)
|
if (aliyunCfg.printPayload) {
|
console.log(`\n🚀 Aliyun IoT postProps 发送 [${deviceId}] JSON:`);
|
try {
|
console.log(JSON.stringify(data, null, 2));
|
} catch (_) {
|
console.log(data);
|
}
|
}
|
device.postProps(data, (res) => {
|
console.log('Aliyun IoT postProps 回调:', res);
|
if (res?.message === 'success') {
|
// 成功:无需日志(太频繁),可注释
|
log.debug(`📡 数据已发送`, { deviceId });
|
} else {
|
const errorMsg = res?.message || '未知错误';
|
log.error(`❌ 发送失败`, { deviceId, 错误: errorMsg, data });
|
}
|
});
|
|
} catch (err) {
|
log.error('sendDataToAliyun 执行异常', {
|
错误: err.message,
|
堆栈: err.stack,
|
deviceId,
|
data
|
});
|
}
|
}
|
|
/**
|
* 关闭所有设备连接(用于进程退出)
|
*/
|
function closeAllConnections() {
|
devices.forEach((device, id) => {
|
try {
|
device.end();
|
logger.forDevice(id).info(`已关闭设备连接: ${id}`);
|
} catch (err) {
|
logger.forDevice(id).error(`关闭设备 ${id} 失败`, { 错误: err.message });
|
}
|
});
|
devices.clear();
|
}
|
|
// 优雅退出
|
process.on('SIGINT', closeAllConnections);
|
process.on('SIGTERM', closeAllConnections);
|
|
module.exports = {
|
sendDataToAliyun,
|
closeAllConnections // 可选导出,用于主程序调用
|
};
|