"use strict";
|
|
const { requestTuple } = require("./tuple-api");
|
|
class AliyunUploader {
|
constructor({ config, logger }) {
|
this.config = config;
|
this.logger = logger;
|
this._devices = new Map(); // deviceNo → { iotDevice, tuple, lastTupleFailedAt }
|
this._aliyunIot = null;
|
}
|
|
start() {
|
if (!this.config.enabled) {
|
this.logger.upload("aliyun", "init", "", "阿里云未启用,跳过");
|
return;
|
}
|
try {
|
this._aliyunIot = require("alibabacloud-iot-device-sdk");
|
this.logger.upload("aliyun", "init", "", "阿里云 IoT SDK 已加载");
|
} catch (err) {
|
this.logger.upload("aliyun", "init", "", `阿里云 IoT SDK 加载失败: ${err.message}`);
|
}
|
}
|
|
async upload(deviceNo, parsedData) {
|
if (!this.config.enabled) {
|
return { enabled: false, ok: true, tuple: { ok: true, code: "ALIYUN_DISABLED" }, props: { ok: true, code: "ALIYUN_DISABLED" } };
|
}
|
|
if (!deviceNo || String(deviceNo).trim() === "") {
|
return { enabled: true, ok: false, tuple: { ok: false, code: "ALIYUN_DEVICE_NO_MISSING" }, props: { ok: false, code: "ALIYUN_PROPS_SKIP", reason: "设备号为空" } };
|
}
|
|
const tupleResult = await this._ensureDevice(deviceNo);
|
|
if (!tupleResult.ok) {
|
return { enabled: true, ok: false, tuple: tupleResult, props: { ok: false, code: "ALIYUN_PROPS_SKIP", reason: "tuple not ready" } };
|
}
|
|
const propsResult = await this._postProps(deviceNo, parsedData);
|
|
return { enabled: true, ok: propsResult.ok, tuple: tupleResult, props: propsResult };
|
}
|
|
async _ensureDevice(deviceNo) {
|
const cached = this._devices.get(deviceNo);
|
if (cached && cached.iotDevice) {
|
return { ok: true, code: "ALIYUN_TUPLE_OK", reason: "device already registered" };
|
}
|
|
// 三元组冷却检查
|
if (cached && cached.lastTupleFailedAt) {
|
const cooldown = this.config.tupleRetryCooldownMs || 60000;
|
if (Date.now() - cached.lastTupleFailedAt < cooldown) {
|
return { ok: false, code: "ALIYUN_TUPLE_RETRY_COOLDOWN", reason: cached.lastTupleFailedReason || "冷却中" };
|
}
|
}
|
|
if (!this._aliyunIot) {
|
return { ok: false, code: "ALIYUN_DEVICE_CREATE_FAIL", reason: "阿里云 SDK 未加载" };
|
}
|
|
const result = await requestTuple({
|
baseUrl: this.config.tupleApiBaseUrl,
|
path: this.config.tupleApiPath,
|
autoRegister: this.config.autoRegister !== false,
|
deviceNo,
|
logger: this.logger,
|
});
|
|
if (!result.ok) {
|
this._devices.set(deviceNo, {
|
lastTupleFailedAt: Date.now(),
|
lastTupleFailedReason: result.reason,
|
});
|
return result;
|
}
|
|
const tuple = result.data;
|
let iotDevice;
|
try {
|
iotDevice = this._aliyunIot.device({
|
ProductKey: tuple.productKey,
|
DeviceName: tuple.deviceName,
|
DeviceSecret: tuple.deviceSecret,
|
});
|
} catch (err) {
|
this.logger.upload("aliyun", "device", deviceNo, `创建 SDK 实例失败: ${err.message}`);
|
return { ok: false, code: "ALIYUN_DEVICE_CREATE_FAIL", reason: err.message };
|
}
|
|
iotDevice.on("connect", () => {
|
this.logger.upload("aliyun", "connect", deviceNo, "阿里云 SDK 已连接");
|
});
|
|
iotDevice.on("error", (err) => {
|
this.logger.upload("aliyun", "error", deviceNo, `阿里云 SDK 错误: ${err.message || err}`);
|
});
|
|
this._devices.set(deviceNo, { iotDevice, tuple, lastTupleFailedAt: null, lastTupleFailedReason: null });
|
|
this.logger.upload("aliyun", "device", deviceNo, "阿里云设备实例创建成功");
|
return { ok: true, code: "ALIYUN_TUPLE_OK", reason: "success" };
|
}
|
|
_postProps(deviceNo, parsedData) {
|
const POSTPROPS_TIMEOUT_MS = 10000;
|
return new Promise((resolve) => {
|
const cached = this._devices.get(deviceNo);
|
if (!cached || !cached.iotDevice) {
|
resolve({ ok: false, code: "ALIYUN_PROPS_SKIP", reason: "没有可用 SDK 实例" });
|
return;
|
}
|
|
let done = false;
|
const timer = setTimeout(() => {
|
if (done) return;
|
done = true;
|
this.logger.upload("aliyun", "postProps", deviceNo, "属性上报超时");
|
resolve({ ok: false, code: "ALIYUN_PROPS_TIMEOUT", reason: "postProps timeout" });
|
}, POSTPROPS_TIMEOUT_MS);
|
|
cached.iotDevice.postProps(parsedData, (res) => {
|
if (done) return;
|
done = true;
|
clearTimeout(timer);
|
if (res && res.message === "success") {
|
this.logger.upload("aliyun", "postProps", deviceNo, "属性上报成功");
|
resolve({ ok: true, code: "ALIYUN_PROPS_OK", reason: "success" });
|
} else {
|
const reason = res ? JSON.stringify(res) : "无响应";
|
this.logger.upload("aliyun", "postProps", deviceNo, `属性上报失败: ${reason}`);
|
resolve({ ok: false, code: "ALIYUN_PROPS_FAIL", reason });
|
}
|
});
|
});
|
}
|
|
getStatus() {
|
let connectedCount = 0;
|
for (const [, dev] of this._devices) {
|
if (dev.iotDevice && dev.iotDevice._mqttClient && dev.iotDevice._mqttClient.connected) {
|
connectedCount++;
|
}
|
}
|
return {
|
enabled: !!this.config.enabled,
|
deviceCount: this._devices.size,
|
connectedCount,
|
};
|
}
|
|
stop() {
|
this._devices.clear();
|
}
|
}
|
|
module.exports = { AliyunUploader };
|