@jiabaida/tools 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/cjs/core/BleApiManager.js +1 -1
  2. package/dist/cjs/core/BleCmdAnalysis/BaseParamProtocol.js +1 -0
  3. package/dist/cjs/core/BleCmdAnalysis/BleCmdAnalysis.js +1 -0
  4. package/dist/cjs/core/BleCmdAnalysis/BleCmdDD.js +1 -0
  5. package/dist/cjs/core/BleCmdAnalysis/BleCmdFFAA.js +1 -0
  6. package/dist/cjs/core/BleCmdAnalysis/BleCmdHVES.js +1 -0
  7. package/dist/cjs/core/BleCmdAnalysis/ESHostProtocol.js +1 -0
  8. package/dist/cjs/core/BleCmdAnalysis/readAndSetParam.js +1 -0
  9. package/dist/cjs/core/BleCmdAnalysis.js +1 -0
  10. package/dist/cjs/core/BleDataProcess.js +1 -0
  11. package/dist/cjs/core/OtaUpgrade.js +1 -0
  12. package/dist/cjs/core/TelinkApi.js +1 -0
  13. package/dist/cjs/core/Transfer.js +1 -0
  14. package/dist/cjs/core/commonfun.js +1 -1
  15. package/dist/cjs/core/dataJson/baseParamsJson.js +1 -0
  16. package/dist/cjs/core/keyAndPwdManager.js +1 -0
  17. package/dist/cjs/index.js +1 -1
  18. package/dist/esm/core/BleApiManager.js +1 -1
  19. package/dist/esm/core/BleCmdAnalysis/BaseParamProtocol.js +1 -0
  20. package/dist/esm/core/BleCmdAnalysis/BleCmdAnalysis.js +1 -0
  21. package/dist/esm/core/BleCmdAnalysis/BleCmdDD.js +1 -0
  22. package/dist/esm/core/BleCmdAnalysis/BleCmdFFAA.js +1 -0
  23. package/dist/esm/core/BleCmdAnalysis/BleCmdHVES.js +1 -0
  24. package/dist/esm/core/BleCmdAnalysis/ESHostProtocol.js +1 -0
  25. package/dist/esm/core/BleCmdAnalysis/readAndSetParam.js +1 -0
  26. package/dist/esm/core/BleCmdAnalysis.js +1 -0
  27. package/dist/esm/core/BleDataProcess.js +1 -0
  28. package/dist/esm/core/OtaUpgrade.js +1 -0
  29. package/dist/esm/core/TelinkApi.js +1 -0
  30. package/dist/esm/core/Transfer.js +1 -0
  31. package/dist/esm/core/commonfun.js +1 -1
  32. package/dist/esm/core/dataJson/baseParamsJson.js +1 -0
  33. package/dist/esm/core/keyAndPwdManager.js +1 -0
  34. package/dist/esm/index.js +1 -1
  35. package/package.json +13 -3
  36. package/src/core/BleApiManager.js +487 -0
  37. package/src/core/BleCmdAnalysis/BaseParamProtocol.js +651 -0
  38. package/src/core/BleCmdAnalysis/BleCmdAnalysis.js +220 -0
  39. package/src/core/BleCmdAnalysis/BleCmdDD.js +1214 -0
  40. package/src/core/BleCmdAnalysis/BleCmdDDA4.js +762 -0
  41. package/src/core/BleCmdAnalysis/BleCmdFFAA.js +407 -0
  42. package/src/core/BleCmdAnalysis/BleCmdHVES.js +1222 -0
  43. package/src/core/BleCmdAnalysis/ESHostProtocol.js +829 -0
  44. package/src/core/BleCmdAnalysis/index.js +7 -0
  45. package/src/core/BleCmdAnalysis/readAndSetParam.js +288 -0
  46. package/src/core/BleDataProcess.js +355 -0
  47. package/src/core/OtaUpgrade.js +338 -0
  48. package/src/core/TelinkApi.js +73 -0
  49. package/src/core/Transfer.js +516 -0
  50. package/src/core/array.js +10 -0
  51. package/src/core/commonfun.js +84 -0
  52. package/src/core/dataJson/baseParamsJson.js +754 -0
  53. package/src/core/dataJson/index.js +1 -0
  54. package/src/core/keyAndPwdManager.js +346 -0
  55. package/src/core/mqttServer.js +296 -0
  56. package/src/core/rsaEncrypt.js +45 -0
  57. package/src/index.js +11 -0
@@ -0,0 +1 @@
1
+ export * from './baseParamsJson'
@@ -0,0 +1,346 @@
1
+ import { getFFAAKeyAsync, getFFAA17Async, resolveFFAAKey, getBroadcastDataCmd } from "./BleCmdAnalysis/BleCmdFFAA";
2
+ import { string2hexArr, decimalToHex0x } from './BleDataProcess';
3
+
4
+ /** 旧密码功能只有外挂蓝牙版及WiFi蓝牙一体才有,需要验证appkey */
5
+ export const ALLOWED_MAC_PREFIX = "A5:C2"; //内置旧版本 不支持密码功能
6
+ export const APP_KEY_STATUS = {
7
+ /** 00 未启用key 未启用密码 */
8
+ NOT_ENABLED_NO_PASSWORD: "00",
9
+ /** 02 未启用key 已启用密码 */
10
+ NOT_ENABLED_WITH_PASSWORD: "02",
11
+ /** 01 已启用key 未启用密码 */
12
+ ENABLED_NO_PASSWORD: "01",
13
+ /** 03 已启用key 已启用密码 */
14
+ ENABLED_WITH_PASSWORD: "03",
15
+ };
16
+ //#region 加密mac地址和appkey
17
+ /**
18
+ * 使用随机数对 MAC 地址和 appkey(key值或者密码) 进行加密,生成密码指令数组。
19
+ * @param {string} verifyMacAddr - 设备的 MAC 地址,格式如 "XX:XX:XX:XX:XX:XX"。
20
+ * @param {string} key - 用于验证的 appkey 字符串。
21
+ * @param {number} randomNum - 1 到 99 之间的随机整数,用于加密计算。
22
+ * @returns {string[]} - 包含加密后十六进制值的密码指令数组。
23
+ */
24
+ export const verifyMacAddrAndKey = (verifyMacAddr, key, randomNum) => {
25
+ // 密码做如下验证:
26
+ // XX为随机数
27
+ // Z1 = MAC1^密码第一位+XX
28
+ // Z2 = MAC2^密码第二位+XX
29
+ // Z3 = MAC3^密码第三位+XX
30
+ // Z4 = MAC4^密码第四位+XX
31
+ // Z5 = MAC5^密码第五位+XX
32
+ // Z6 = MAC6^密码第六位+XX
33
+ let psdCode = []; // 密码指令数组(计算后的结果)
34
+ // Mac地址分隔成数组
35
+ let macArr = verifyMacAddr.split(":");
36
+ // 密码ASC码转化为十六进制数组
37
+ let psdHex = string2hexArr(key);
38
+ // 遍历 MAC 地址数组,进行加密计算
39
+ macArr.forEach((el, index) => {
40
+ // 将 MAC 地址的当前项转换为十六进制格式
41
+ let macDec = "0x" + el;
42
+ // 获取密码十六进制数组中对应位置的值
43
+ let psdDec = psdHex[index];
44
+ // 将 MAC 地址的当前项与密码对应项进行异或运算,再加上随机数
45
+ // 注意:这里的 macDec 和 psdDec 是字符串,需要转换为数字进行运算
46
+ let codeDec = (parseInt(macDec, 16) ^ parseInt(psdDec, 16)) + randomNum;
47
+ // 转为带0x的十六进制
48
+ let codeHex = decimalToHex0x(codeDec);
49
+ // 取低8位,先将 codeHex 转换为数字再进行位运算
50
+ codeHex = parseInt(codeHex, 16) & 0xff;
51
+ // 转为十六进制 即为指令值
52
+ psdCode.push(decimalToHex0x(codeHex));
53
+ });
54
+ return psdCode;
55
+ };
56
+
57
+ //#region 新版key 启用或验证appkey
58
+ /**
59
+ * 异步发送启用或验证 appkey 的指令。
60
+ * @param {Object} deviceData - 包含设备信息的对象,应包含 deviceId 和 macAddr 属性。
61
+ * @param {string} key - 用于验证或启用的 appkey 字符串。
62
+ * @param {string} code - 指令代码,'0x22' 表示启用,'0x21' 表示验证。
63
+ * @returns - 调用 getFFAAKeyAsync 方法返回的十六进制数据。
64
+ */
65
+ export const sendEnableOrVerifyKey = async (deviceData, key, code) => {
66
+ //发送启用验证appkey指令
67
+ console.warn("发送code appkey指令", code, key);
68
+ const randomNum = Math.floor(Math.random() * 99) + 1; // 生成1 - 99之间的随机整数
69
+ console.warn("randomNum: 随机数", randomNum);
70
+ // 使用随机数与macAddr加密 appkey
71
+ const psdCode = verifyMacAddrAndKey(deviceData.macAddr, key, randomNum);
72
+ console.log("psdCode: ", psdCode);
73
+ // 指令内容加上随机数
74
+ psdCode.push(decimalToHex0x(randomNum));
75
+ // '22'启用 '21'验证
76
+ const hex = await getFFAAKeyAsync(deviceData.deviceId, code, psdCode, `验证或启用key${code}`);
77
+ return hex;
78
+ };
79
+
80
+ //#region 验证旧版appkey
81
+ /**
82
+ * 异步发送旧版 appkey 验证指令。
83
+ * @param {Object} deviceData - 包含设备信息的对象,应包含 deviceId 和 macAddr 属性。
84
+ * @param {string} key - 用于验证的旧版 appkey 字符串。
85
+ * @returns - 一个 Promise,解析为调用 getFFAAKeyAsync 方法返回的十六进制数据。
86
+ */
87
+ export const sendOldAppKey = async (deviceData, key) => {
88
+ // 密码ASC码转化为十六进制 数组
89
+ let psdHex = string2hexArr(key);
90
+ const hex = await getFFAAKeyAsync(deviceData.deviceId, "0x15", psdHex, `验证旧appkey`);
91
+ return hex;
92
+ };
93
+
94
+ //#region 写入一级密码
95
+ /**
96
+ * 异步发送一级密码写入指令。
97
+ * @param {Object} deviceData - 包含设备信息的对象,应包含 deviceId 和 macAddr 属性。
98
+ * @param {string} pwd - 用于写入的一级密码字符串。
99
+ * @returns {*} - 写入指令返回的十六进制数据。
100
+ */
101
+ export const sendWrite1LevelPwd = async (deviceData, pwd) => {
102
+ if(deviceData.isNewAppKey) {
103
+ const randomNum = await getRandomNum(deviceData);
104
+ const psdCode = verifyMacAddrAndKey(deviceData.macAddr, pwd, randomNum);
105
+ psdCode.push(decimalToHex0x(randomNum));
106
+ const hex = await getFFAAKeyAsync(deviceData.deviceId, "0x16", psdCode, `写入一级密码`);
107
+ console.log('hex:写入一级密码 ', hex);
108
+ return hex;
109
+ }
110
+ // 密码ASC码转化为十六进制 数组
111
+ let psdHex = string2hexArr(pwd);
112
+ const hex = await getFFAAKeyAsync(deviceData.deviceId, "0x16", psdHex, `写入一级密码`);
113
+ return hex;
114
+ };
115
+
116
+ //#region 验证一、二级密码
117
+ /**
118
+ * 异步发送一、二级密码验证指令。
119
+ * @param {Object} deviceData - 包含设备信息的对象,应包含 deviceId 和 macAddr 属性。
120
+ * @param {string} pwd - 用于验证的旧版密码字符串。
121
+ * @param {string} code - 指令代码,'0x1B'-验证二级密码 '0x18'-验证一级密码。
122
+ * @returns {Promise} - 一个 Promise,解析为调用 getFFAAKeyAsync 方法返回的十六进制数据。
123
+ */
124
+ export const sendVerifyPwd = async (deviceData, pwd, code) => {
125
+ console.warn("deviceData: -----------", deviceData);
126
+ const randomNum = await getRandomNum(deviceData);
127
+ const psdCode = verifyMacAddrAndKey(deviceData.macAddr, pwd, randomNum);
128
+ if (deviceData.isNewAppKey) {
129
+ psdCode.push(decimalToHex0x(randomNum));
130
+ }
131
+ const hex = await getFFAAKeyAsync(deviceData.deviceId, code, psdCode, `验证密码`);
132
+ console.log("hex: 验证密码", hex);
133
+ return hex;
134
+ };
135
+
136
+ //#region 获取随机数
137
+ /**
138
+ * 获取随机数。
139
+ * @returns {Promise<number>} - 1 到 99 之间的随机整数。
140
+ */
141
+ export const getRandomNum = async (deviceData) => {
142
+ if (deviceData.isNewAppKey) {
143
+ return Math.floor(Math.random() * 99) + 1; // 生成1 - 99之间的随机整数
144
+ } else {
145
+ const hex = await getFFAA17Async(deviceData.deviceId);
146
+ const data = resolveFFAAKey(hex);
147
+ return data?.response;
148
+ }
149
+ };
150
+
151
+ //#region 验证三级密码
152
+ /** 验证三级密码
153
+ * @param {Object} deviceData - 包含设备信息的对象,应包含 deviceId 和 macAddr 属性。
154
+ * @returns {Promise} - 一个 Promise,解析为调用 getFFAAKeyAsync 方法返回的十六进制数据。
155
+ */
156
+ export const verify3LevelPassword = async (deviceData, ciphertext, randomNum) => {
157
+ return new Promise(async (resolve, reject) => {
158
+ const regex = /(\w{2})/g;
159
+ // 两位分隔为数组 去掉空格
160
+ const psdCode = ciphertext
161
+ .split(regex)
162
+ .filter((item) => item !== "")
163
+ .map((item) => `0x${item}`); // 密文十六进制数组
164
+ console.log(psdCode, "psdCode");
165
+ let code = "";
166
+ if (deviceData.isNewAppKey) {
167
+ psdCode.push(decimalToHex0x(randomNum));
168
+ code = "0x24";
169
+ } else {
170
+ code = "0x1D";
171
+ }
172
+ const hex = await getFFAAKeyAsync(deviceData.deviceId, code, psdCode, `验证三级密码`);
173
+ resolve(hex);
174
+ });
175
+ };
176
+
177
+ //#region 提取广播数据
178
+ /**
179
+ * 从广播数据中提取模块类型、产品类型、协议版本和 appkey 状态。
180
+ * @param {string} hexString - 包含广播数据的十六进制字符串。
181
+ * @returns {Object} - 包含模块类型、产品类型、协议版本和 appkey 状态的对象。
182
+ */
183
+ export const extractModuleInfo = (hexString) => {
184
+ // FFAA2505000400010130
185
+ const moduleType = hexString.slice(8, 12);
186
+ const productType = hexString.slice(12, 14);
187
+ const protocolVersion = hexString.slice(14, 16);
188
+ const appkeyStatus = hexString.slice(16, 18);
189
+ return {
190
+ moduleType,
191
+ productType,
192
+ protocolVersion,
193
+ appkeyStatus,
194
+ };
195
+ };
196
+
197
+ //#region 获取模块版本等信息 更新设备信息
198
+ /**
199
+ * 异步根据设备指令获取设备类型相关信息并更新设备数据。
200
+ * @param {Object} deviceData - 包含设备信息的对象,至少应包含 deviceId 属性。
201
+ * @returns {Promise<Object>} - 更新后的设备信息对象。
202
+ */
203
+ export const getTypeBuyCmd = async (deviceData) => {
204
+ // 异步获取设备的广播数据(十六进制字符串)
205
+ const hex = await getBroadcastDataCmd(deviceData.deviceId);
206
+ const data = resolveFFAAKey(hex);
207
+ let moduleInfo = null;
208
+ if (data && data.dataStr.length >= 20) {
209
+ // 从数据字符串中提取模块信息
210
+ moduleInfo = extractModuleInfo(data?.dataStr);
211
+ }
212
+ console.log("moduleInfo: 广播数据", moduleInfo);
213
+ if (moduleInfo) {
214
+ const { moduleType, productType, protocolVersion, appkeyStatus } = moduleInfo;
215
+ // 合并原设备数据和新获取的模块信息,同时添加 isNewAppKey 和 isConnect 属性
216
+ deviceData.isNewAppKey = true;
217
+ deviceData.isConnect = true;
218
+ deviceData.appkeyStatus = appkeyStatus;
219
+ deviceData.productType = productType;
220
+ deviceData.protocolVersion = protocolVersion;
221
+ deviceData.moduleType = moduleType;
222
+ }
223
+ console.warn("data: 获取模块产品类型等信息", moduleInfo);
224
+ // 返回更新后的设备信息对象
225
+ return deviceData;
226
+ };
227
+
228
+ //#region key及密码处理逻辑
229
+ /**
230
+ * 异步发送 appkey 并验证,根据设备类型和状态执行不同的验证逻辑。
231
+ * @param {Object} deviceData - 包含设备信息的对象,应包含 deviceId、macAddr、isNewAppKey、appkeyStatus 等属性。
232
+ * @param {string} appkeyValue - 用于新密码验证或启用的 appkey 字符串。
233
+ * @param {string} oldKey - 用于旧版密码验证的 appkey 字符串。
234
+ * @param {boolean} canEnable - 是否可以启用 key,默认为 false(客户端app和小程序都不启用key,检测才需启用)。
235
+
236
+ * @returns {Promise<Object>} - 解析为包含验证结果的对象,包含 verifyAppkey(是否验证成功)、hasPwd(是否有一级密码)、isEnableKey(是否是启用 key 指令)、isBlueKey(是否有密码功能)、enableSuccess(是否启用成功)。
237
+ */
238
+ export const sendAppKey = async (deviceData, appkeyValue, oldKey, canEnable = false) => {
239
+ console.log("deviceData: ------------------", deviceData);
240
+ try {
241
+ let isAppKeyVerified = true; //验证key结果(是否验证成功)
242
+ let hasPassword = false; //是否有一级密码
243
+ let isEnableKey = false; //是否是启用key指令
244
+ let supportsBlueKey = false; //是否有密码功能
245
+ let enableSuccess = false; //是否启用成功
246
+
247
+ if (deviceData.isNewAppKey) {
248
+ ({ isAppKeyVerified, hasPassword, isEnableKey, supportsBlueKey, enableSuccess } = await handleNewAppKey(
249
+ deviceData,
250
+ appkeyValue,
251
+ canEnable,
252
+ ));
253
+ } else {
254
+ ({ isAppKeyVerified, hasPassword, supportsBlueKey } = await handleOldAppKey(deviceData, oldKey));
255
+ }
256
+
257
+ return {
258
+ verifyAppkey: isAppKeyVerified,
259
+ hasPwd: hasPassword,
260
+ isEnableKey,
261
+ isBlueKey: supportsBlueKey,
262
+ enableSuccess,
263
+ };
264
+ } catch (error) {
265
+ console.error("Error in sendAppKey:", error);
266
+ throw error;
267
+ }
268
+ };
269
+
270
+ //#region 新密码处理逻辑
271
+ export const handleNewAppKey = async (deviceData, appkeyValue, canEnable) => {
272
+ const { appkeyStatus } = deviceData;
273
+ let isAppKeyVerified = true;
274
+ let hasPassword = false;
275
+ let isEnableKey = false;
276
+ let supportsBlueKey = true;
277
+ let enableSuccess = false;
278
+
279
+ switch (appkeyStatus) {
280
+ case APP_KEY_STATUS.NOT_ENABLED_NO_PASSWORD:
281
+ if (canEnable) {
282
+ const enableHex = await sendEnableOrVerifyKey(deviceData, appkeyValue, "0x22");
283
+ if (!enableHex) {
284
+ console.log("启用key失败: ------------------", enableHex);
285
+ isAppKeyVerified = false;
286
+ } else {
287
+ enableSuccess = enableHex[enableHex.length - 2] === 0;
288
+ }
289
+ console.log(enableSuccess ? "启用key成功" : "启用key失败");
290
+ }
291
+ isEnableKey = true;
292
+ break;
293
+
294
+ case APP_KEY_STATUS.NOT_ENABLED_WITH_PASSWORD:
295
+ hasPassword = true;
296
+ break;
297
+
298
+ case APP_KEY_STATUS.ENABLED_NO_PASSWORD:
299
+ break;
300
+
301
+ case APP_KEY_STATUS.ENABLED_WITH_PASSWORD:
302
+ hasPassword = true;
303
+ break;
304
+
305
+ default:
306
+ console.log("无appkeyStatus-----------------");
307
+ }
308
+
309
+ if ([APP_KEY_STATUS.ENABLED_NO_PASSWORD, APP_KEY_STATUS.ENABLED_WITH_PASSWORD].includes(appkeyStatus)) {
310
+ const hex = await sendEnableOrVerifyKey(deviceData, appkeyValue, "0x21");
311
+ if (!hex) {
312
+ isAppKeyVerified = false;
313
+ } else {
314
+ isAppKeyVerified = hex[hex.length - 2] === 0;
315
+ }
316
+ }
317
+
318
+ return { isAppKeyVerified, hasPassword, isEnableKey, supportsBlueKey, enableSuccess };
319
+ };
320
+
321
+ //#region 旧密码处理逻辑
322
+ export const handleOldAppKey = async (deviceData, oldKey) => {
323
+ const prefix = deviceData.macAddr.slice(0, 5).toUpperCase();
324
+ if (prefix === ALLOWED_MAC_PREFIX) {
325
+ return { isAppKeyVerified: true, hasPassword: false, supportsBlueKey: false };
326
+ }
327
+
328
+ const oldHex = await sendOldAppKey(deviceData, oldKey);
329
+ console.log("oldHex: 验证旧key", oldHex);
330
+
331
+ if (!oldHex) {
332
+ console.log("验证旧key无响应: 无密码功能");
333
+ return { isAppKeyVerified: true, hasPassword: false, supportsBlueKey: false };
334
+ }
335
+
336
+ switch (oldHex[oldHex.length - 2]) {
337
+ case 0:
338
+ return { isAppKeyVerified: true, hasPassword: true, supportsBlueKey: true };
339
+ case 1: // 旧密码验证失败允许读
340
+ return { isAppKeyVerified: true, hasPassword: false, supportsBlueKey: false };
341
+ case 2:
342
+ return { isAppKeyVerified: true, hasPassword: false, supportsBlueKey: true };
343
+ default:
344
+ return { isAppKeyVerified: true, hasPassword: false, supportsBlueKey: false };
345
+ }
346
+ };
@@ -0,0 +1,296 @@
1
+ import { sleep } from './BleDataProcess.js';
2
+ import { decrypt } from './rsaEncrypt.js';
3
+ import CryptoJS from 'crypto-js/crypto-js.js';
4
+ import mqtt from 'mqtt/dist/mqtt.js';
5
+ import { set_PlanCMD_DD } from './BleCmdAnalysis/BleCmdDD';
6
+ import { getFFAA80Async, resolveFFAA80, set_PlanCMD_FFAA } from './BleCmdAnalysis/BleCmdFFAA';
7
+ import { Transfer } from './Transfer.js';
8
+ import { getCMDESInfoAsync } from './BleCmdAnalysis/BleCmdHVES.js';
9
+
10
+ // 新的MQTT服务类
11
+ class MqttServer {
12
+ constructor({ mqttClient = {}, MQTT_HOST = 'wx://mqtt.jiabaida.com:8083/mqtt', DELAY_TIME = 300, networkModelUrl = 'https://cloud.jiabaida.com/bluetooth/server/device/networkModel', request }) {
13
+ this.timer = null;
14
+ this.productKey = 'bms_wifi';
15
+ this.is0f = false;
16
+ this.publishObj = {};
17
+ this.cmdCode = '';
18
+ this.callbackId = '';
19
+ this.mqttCode = '';
20
+ this.mqttPublish = false;
21
+ this.mqttClient = mqttClient;
22
+ this.MQTT_HOST = MQTT_HOST;
23
+ this.DELAY_TIME = DELAY_TIME;
24
+ this.networkModelUrl = networkModelUrl;
25
+ this.request = request; // 必须传入的http请求方法
26
+ // ...existing code...
27
+ }
28
+
29
+ // 获取参数方法
30
+ async getParams(item) {
31
+ try {
32
+ return await this.request({
33
+ url: `${this.networkModelUrl}?macAddr=${item.macAddr}`,
34
+ method: 'get',
35
+ });
36
+ } catch (error) {
37
+ console.error('getnetworkModel-error', error);
38
+ throw error;
39
+ }
40
+ }
41
+
42
+ // 封装 MQTT 连接逻辑
43
+ async connectMqtt(deviceObj) {
44
+ try {
45
+ const modalData = await this.getParams(deviceObj);
46
+ if (!modalData || modalData.data.code !== 200) {
47
+ return;
48
+ }
49
+ const mqttParam = modalData.data.data;
50
+ const clientId = decrypt(mqttParam.clientId);
51
+ const username = mqttParam.mqttUsername;
52
+ const password = decrypt(mqttParam.mqttSecret);
53
+ console.log('连接 MQTT...');
54
+ const options = {
55
+ clean: true,
56
+ connectTimeout: 4000,
57
+ clientId,
58
+ username,
59
+ password: CryptoJS.SHA256(password).toString(),
60
+ deviceId: deviceObj.deviceId,
61
+ };
62
+ const client = mqtt.connect(this.MQTT_HOST, options);
63
+ console.log('MQTT 客户端创建成功:', client);
64
+ client.on('connect', (res) => {
65
+ console.log('res连接成功', res);
66
+ client.productKey = this.productKey;
67
+ this.handleMqttConnect(client, deviceObj);
68
+ });
69
+ client.on('message', (topic, message) => {
70
+ this.handleMqttMessage(topic, message, client);
71
+ });
72
+ client.on('reconnect', (res) => console.log('MQTT 重连中...', res, client));
73
+ client.on('close', (res) => this.handleMqttClose(client, res));
74
+ client.on('offline', (res) => console.log('MQTT 脱机状态', res, client));
75
+ client.on('error', (res) => console.error('MQTT 连接错误', res, client));
76
+ client.on('disconnect', (res) => console.log('MQTT 断开连接', res, client));
77
+ } catch (e) {
78
+ console.error('MQTT 连接异常:', e);
79
+ }
80
+ }
81
+
82
+ handleMqttConnect(client, deviceObj) {
83
+ console.log('MQTT 连接成功 client', client);
84
+ console.log('MQTT 连接成功 deviceObj', deviceObj);
85
+ this.storeMqttClient(client);
86
+ this.publishInOrder(client, deviceObj).then(() => this.subscribeInOrder(client));
87
+ }
88
+
89
+ storeMqttClient(client) {
90
+ this.mqttClient[`${client.options.deviceId}_mqtt`] = client;
91
+ console.log('存储的 MQTT 客户端:', this.mqttClient);
92
+ }
93
+
94
+ getTopicsAndMessages(client, deviceObj) {
95
+ const { productType, soc, moduleType, macAddr } = deviceObj;
96
+ const message = JSON.stringify({ productKey: productType, soc, moduleTypeKey: moduleType, macAddr });
97
+ return [
98
+ {
99
+ topic: `/${client.productKey}/${client.options.clientId}/device/login`,
100
+ message,
101
+ },
102
+ ];
103
+ }
104
+
105
+ async publishInOrder(client, deviceObj) {
106
+ const topicsAndMessages = this.getTopicsAndMessages(client, deviceObj);
107
+ try {
108
+ for (const item of topicsAndMessages) {
109
+ await new Promise((resolve) => {
110
+ client.publish(item.topic, item.message, () => {
111
+ resolve();
112
+ });
113
+ });
114
+ }
115
+ } catch (error) {
116
+ console.error(`处理发布时出现错误: ${error}`);
117
+ }
118
+ }
119
+
120
+ getSubscribeTopics(client) {
121
+ const { productKey } = client;
122
+ const clientId = client.options.clientId;
123
+ return [`/${productKey}/${clientId}/device/login/reply`, `/${productKey}/${clientId}/device/passthrough/reply`, `/${productKey}/${clientId}/plat/cmd`, `/${productKey}/${clientId}/plat/passthrough`];
124
+ }
125
+
126
+ async subscribeInOrder(client) {
127
+ const subscribeTopics = this.getSubscribeTopics(client);
128
+ try {
129
+ for (const topic of subscribeTopics) {
130
+ await new Promise((resolve, reject) => {
131
+ client.subscribe(topic, (err) => {
132
+ if (!err) {
133
+ resolve();
134
+ } else {
135
+ reject(err);
136
+ }
137
+ });
138
+ });
139
+ }
140
+ } catch (error) {
141
+ console.error(`订阅主题时出现错误: ${error}`);
142
+ }
143
+ }
144
+
145
+ handleMqttMessage(topic, message, client) {
146
+ const arr = topic.split('/');
147
+ const clientId = arr[2];
148
+ client = Object.values(this.mqttClient).find((c) => c?.options?.clientId === clientId) || client;
149
+ if (topic === `/${client.productKey}/${client.options.clientId}/plat/passthrough` || topic === `/${client.productKey}/${client.options.clientId}/plat/cmd`) {
150
+ this.processMessageAsync(client, message.toString());
151
+ }
152
+ }
153
+
154
+ async processMessageAsync(client, message) {
155
+ const msg = JSON.parse(message);
156
+ this.callbackId = msg.callbackId;
157
+ this.cmdCode = msg.cmdCode;
158
+ let resultArray = [];
159
+ let codeValue = [];
160
+ if (this.cmdCode === 'jbdPWD') {
161
+ const cmdMap = {
162
+ 2: 'FFAA23010125',
163
+ 3: 'FFAA1F010121',
164
+ 4: 'FFAA20010122',
165
+ 5: 'FFAA23010226',
166
+ };
167
+ const cmdContent = cmdMap[msg.value];
168
+ codeValue = cmdContent ? cmdContent.split(',') : [];
169
+ } else {
170
+ codeValue = msg.value ? msg.value.split(',') : [];
171
+ this.is0f = codeValue.length === 2;
172
+ }
173
+ resultArray = codeValue[0] ? codeValue[0].match(/.{1,2}/g) : [];
174
+ try {
175
+ if (resultArray[0]?.toUpperCase() === 'FF' && resultArray[1]?.toUpperCase() === 'AA') {
176
+ } else if (resultArray[1]?.toUpperCase() === '78' || resultArray[1]?.toUpperCase() === '50') {
177
+ } else {
178
+ }
179
+ const publishObj = {
180
+ deviceId: client.options.deviceId,
181
+ clientId: client.options.clientId,
182
+ productKey: client.productKey,
183
+ callbackId: this.callbackId,
184
+ cmdCode: this.cmdCode,
185
+ is0f: this.is0f,
186
+ isLast: msg.isLast,
187
+ };
188
+ this.publishObj = publishObj;
189
+ if (this.cmdCode.toLowerCase().includes('wifi_')) {
190
+ await this.publishATHandle(msg.value);
191
+ } else {
192
+ this.writePublishInfoCmd(resultArray);
193
+ await sleep(this.DELAY_TIME);
194
+ }
195
+ } catch (error) {
196
+ console.error('error: 处理后台发指令错误', error);
197
+ }
198
+ }
199
+
200
+ async publishATHandle(ATvalue) {
201
+ const { deviceId, cmdCode, callbackId, productKey, clientId, isLast } = this.publishObj;
202
+ const client = this.mqttClient[`${deviceId}_mqtt`];
203
+ try {
204
+ if (client) {
205
+ const hex = await getFFAA80Async(deviceId, ATvalue);
206
+ const res = resolveFFAA80(hex);
207
+ if (res && res.moduleVersion) {
208
+ const value = res.moduleVersion;
209
+ client.publish(`/${productKey}/${clientId}/plat/cmd/reply`, JSON.stringify({ value, cmdCode, callbackId }), () => {});
210
+ }
211
+ }
212
+ if (isLast) {
213
+ await sleep(300);
214
+ this.mqttPublish = false;
215
+ uni.removeStorageSync && uni.removeStorageSync(`${deviceId}_mqttPublish`);
216
+ }
217
+ } catch (error) {
218
+ console.error(error);
219
+ }
220
+ }
221
+
222
+ handleMqttClose(client, res) {
223
+ console.log('MQTT 连接关闭', res, client);
224
+ client.end(() => {
225
+ this.mqttClient[`${client.options.deviceId}_mqtt`] = null;
226
+ });
227
+ }
228
+
229
+ // baseValue/voltageValue 作为参数传入
230
+ async publishHandle(value, baseValue, voltageValue) {
231
+ const client = this.mqttClient[`${this.publishObj.deviceId}_mqtt`];
232
+ if (this.publishObj.is0f) {
233
+ value = baseValue + ',' + voltageValue;
234
+ }
235
+ const msg = JSON.stringify({
236
+ value,
237
+ cmdCode: this.publishObj.cmdCode,
238
+ callbackId: this.publishObj.callbackId,
239
+ });
240
+ if (client) {
241
+ client.publish(`/${this.publishObj.productKey}/${this.publishObj.clientId}/plat/passthrough/reply`, msg, () => {});
242
+ }
243
+ if (this.publishObj.isLast) {
244
+ await sleep(300);
245
+ this.mqttPublish = false;
246
+ uni.removeStorageSync && uni.removeStorageSync(`${this.publishObj.deviceId}_mqttPublish`);
247
+ }
248
+ }
249
+
250
+ publishPassthroughHandle({ value, cmdCode }, deviceId, timeOffset = 30000) {
251
+ const client = this.mqttClient[`${deviceId}_mqtt`];
252
+ if (client) {
253
+ const clientTime = new Date().getTime();
254
+ const { laseCmdCode, lastPassthroughTime, clientId } = client?.options;
255
+ if (laseCmdCode != cmdCode || !(lastPassthroughTime && clientTime - lastPassthroughTime < timeOffset)) {
256
+ const topic = `/${client?.productKey}/${clientId}/device/passthrough`;
257
+ const payload = JSON.stringify({ value, cmdCode, clientTime });
258
+ client.publish(topic, payload, () => {
259
+ client.options['lastPassthroughTime'] = clientTime;
260
+ client.options['laseCmdCode'] = cmdCode;
261
+ });
262
+ }
263
+ }
264
+ }
265
+
266
+ async writePublishInfoCmd(values) {
267
+ clearTimeout(this.timer);
268
+ this.mqttPublish = true;
269
+ this.mqttCode = values[2];
270
+ let res = null;
271
+ // 下发指令逻辑
272
+ if (values[0]?.toUpperCase() === 'FF' && values[1]?.toUpperCase() === 'AA') {
273
+ res = await set_PlanCMD_FFAA(this.publishObj.deviceId, values.join(''));
274
+ } else if (values[1]?.toUpperCase() === '78' || values[1]?.toUpperCase() === '50') {
275
+ res = await getCMDESInfoAsync(this.publishObj.deviceId, values.join(''));
276
+ } else {
277
+ // DD
278
+ res = await set_PlanCMD_DD(this.publishObj.deviceId, values.join(''));
279
+ }
280
+ const content = res?.data || []
281
+ let code = Transfer.decimalToHex(content[1]);
282
+ if (content[0] == 0xff && content[1] == 0xaa) {
283
+ code = Transfer.decimalToHex(content[2]);
284
+ }
285
+ console.log('code: 回复的code', code);
286
+ // mqtt连接正常且是平台下发的指令 需上报数据到后端
287
+ if (code.toUpperCase() == this.mqttCode.toUpperCase()) {
288
+ this.publishHandle(res?.dataStr);
289
+ }
290
+ this.timer = setTimeout(() => {
291
+ this.mqttPublish = false;
292
+ }, 30000);
293
+ }
294
+ }
295
+
296
+ export default MqttServer;
@@ -0,0 +1,45 @@
1
+ import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
2
+
3
+ // 密钥对生成 http://web.chacuo.net/netrsakeypair
4
+
5
+ const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD\n' +
6
+ '2iRe41HdTNF8RUhNnHit5NpMNtGL0NPTSSpPjjI1kJfVorRvaQerUgkCAwEAAQ=='
7
+
8
+ const privateKey = 'MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEA4cWDRXldfRIqc2Mg\n' +
9
+ 'JSAiKqvRGa0v7a4fC0j3YjzpxcIArm1Ui5d9DN8Ly7vGdUnltPuy7yC9nVQuW89+\n' +
10
+ 'J6PnowIDAQABAkEApgP1/l07KJ/2BTkrxwD0/smvDaFzL+QswcCa4GsIP7iBvPna\n' +
11
+ '003yp3UGSXfEZWiQzHfehW2UHB5DIh5YbghaqQIhAPkB8LWgJlsaC8WP90ztg0nl\n' +
12
+ '+6VBLiunF6dV0Q7eJzMlAiEA6ByIpg2ZvT+X8Zzmkw32Si/Aw5VZ/ulJ5D6/MKuS\n' +
13
+ 'mScCIAjcRNBxrmu3dYvGH6qhGPbcNCQhOZ9cBr9xkkrRJNvxAiEAvVapUWs+wdWi\n' +
14
+ 'SIFIxSRah+G0SNcH9pyunfVhWH5cs3kCIQDGArpiAGQ4WOO+tFDqUVaDXwhQnx2H\n' +
15
+ '5OGt/FZJpmDnUw=='
16
+
17
+ // 加密
18
+ export function encrypt(txt) {
19
+ const encryptor = new JSEncrypt()
20
+ encryptor.setPublicKey(publicKey) // 设置公钥
21
+ return encryptor.encrypt(txt) // 对需要加密的数据进行加密
22
+ }
23
+
24
+ // 解密
25
+ export function decrypt(txt) {
26
+ const encryptor = new JSEncrypt()
27
+ encryptor.setPrivateKey(privateKey)
28
+ return encryptor.decrypt(txt)
29
+ }
30
+
31
+ export function decrypt2(txt) {
32
+ const encryptor = new JSEncrypt()
33
+ encryptor.setPrivateKey(publicKey)
34
+ return encryptor.decrypt(txt)
35
+ }
36
+
37
+ export const decryptUsername = (txt) => {
38
+ const text = decrypt(txt);
39
+ if (!text) {
40
+ return;
41
+ }
42
+ return text.substring(0, text.length - 13);
43
+ }
44
+
45
+