chenyc
2026-03-22 7885cede659f3255be56f77c1eef2ada7387d6f1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
const aliyunIot = require("aliyun-iot-device-sdk");
const logger = require("./logger");
const config = require("./config");
const { getAliyunDeviceSecret } = require("./api");
 
// 阿里云物联网对接管理器:按设备号获取三元组、建立 iotDevice,并在每次收到数据时上报属性
function createAliyunManager({ propertyMapper }) {
  if (!config.aliyun || !config.aliyun.enabled) {
    logger.info("Aliyun IoT is disabled by config");
    return {
      reportDeviceData: () => {},
      closeAll: () => {}
    };
  }
 
  // 设备号 -> { iotDevice, productKey, deviceName }
  const deviceMap = new Map();
 
  async function ensureIotDevice(deviceNumber) {
    let entry = deviceMap.get(deviceNumber);
    if (entry && entry.iotDevice) {
      return entry;
    }
 
    try {
      logger.info("Request Aliyun device secret", { deviceNumber });
      // 复用旧项目的 api.js 约定:第一个参数是相对路径
      const resp = await getAliyunDeviceSecret(
        "device/info/getAliyunDeviceSecret",
        deviceNumber
      );
      const body = resp && resp.data ? resp.data : null;
      if (!body || !body.data) {
        logger.warn("Aliyun device secret response invalid", {
          deviceNumber,
          body
        });
        return null;
      }
      const data = body.data;
      if (!data.productKey || !data.deviceName || !data.deviceSecret) {
        logger.warn("Aliyun device secret missing fields", {
          deviceNumber,
          data
        });
        return null;
      }
 
      const productKey = data.productKey;
      const deviceName = data.deviceName;
      const deviceSecret = data.deviceSecret;
 
      logger.info("Creating Aliyun IoT device", {
        deviceNumber,
        productKey,
        deviceName
      });
 
      const iotDevice = aliyunIot.device({
        ProductKey: productKey,
        DeviceName: deviceName,
        DeviceSecret: deviceSecret
      });
 
      iotDevice.on("connect", () => {
        logger.info("Aliyun IoT connected", { deviceNumber, productKey, deviceName });
      });
 
      iotDevice.on("error", (err) => {
        logger.error("Aliyun IoT error", {
          deviceNumber,
          error: err.message || err
        });
      });
 
      iotDevice.on("close", () => {
        logger.warn("Aliyun IoT connection closed", { deviceNumber });
      });
 
      entry = { iotDevice, productKey, deviceName };
      deviceMap.set(deviceNumber, entry);
      return entry;
    } catch (err) {
      logger.error("ensureIotDevice error", {
        deviceNumber,
        error: err.message || err
      });
      return null;
    }
  }
 
  async function reportDeviceData(deviceNumber, rawData) {
    try {
      const entry = await ensureIotDevice(deviceNumber);
      if (!entry || !entry.iotDevice) {
        return;
      }
 
      // 使用 PropertyMapper 将内部字段转换为阿里云物模型属性对象
      let props = rawData;
      if (propertyMapper && typeof propertyMapper.transformForAliyun === "function") {
        try {
          props = propertyMapper.transformForAliyun(rawData);
        } catch (e) {
          logger.error("transformForAliyun error", {
            deviceNumber,
            error: e.message || e
          });
          props = rawData;
        }
      }
 
      // 记录准备上报到阿里云的物模型数据,便于和设备原始数据对照
      logger.info("Aliyun postProps payload", {
        deviceNumber,
        productKey: entry.productKey,
        deviceName: entry.deviceName,
        props
      });
 
      entry.iotDevice.postProps(props, (res) => {
        if (res && res.message === "success") {
          logger.info("Aliyun postProps success", { deviceNumber });
        } else {
          logger.error("Aliyun postProps failed", { deviceNumber, res });
        }
      });
    } catch (err) {
      logger.error("reportDeviceData error", {
        deviceNumber,
        error: err.message || err
      });
    }
  }
 
  function closeAll() {
    for (const [deviceNumber, entry] of deviceMap.entries()) {
      try {
        if (entry.iotDevice && typeof entry.iotDevice.end === "function") {
          entry.iotDevice.end();
        }
      } catch (err) {
        logger.error("Close Aliyun device error", {
          deviceNumber,
          error: err.message || err
        });
      }
      deviceMap.delete(deviceNumber);
    }
  }
 
  return {
    reportDeviceData,
    closeAll
  };
}
 
module.exports = createAliyunManager;