@jiabaida/tools 1.0.0 → 1.0.2

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 +222 -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 +87 -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,1214 @@
1
+ import { getData } from './BleCmdAnalysis.js'
2
+ import { generateCrcCheckSum, decimalToHex, hex2string, hexArr2string, hexToDecimal, stringToTwoHexArray, hexArr2Assic, generateCrc16modbusCheck } from '../BleDataProcess';
3
+ import { getFFAA80Async, resolveFFAA80 } from './BleCmdFFAA.js';
4
+ // #region 进出工厂
5
+ /**进工厂 */
6
+ export const enterFactory = async (deviceId) => {
7
+ return set_PlanCMD_DD(deviceId, 'DD5A00025678FF3077');
8
+ }
9
+ /**写参数退工厂 */
10
+ export const existFactory = async (deviceId) => {
11
+ return set_PlanCMD_DD(deviceId, 'DD5A01022828ffad77');
12
+ }
13
+ /**读参数退工厂 */
14
+ export const readExistFactory = async (deviceId) => {
15
+ return set_PlanCMD_DD(deviceId, 'DD5A01020000FFFD77');
16
+ }
17
+ // #region DD通用指令
18
+ /**DD通用指令
19
+ * @param {*} deviceId
20
+ * @param {*} command eg: 'DD5A01020000FFFD77'
21
+ * @returns
22
+ */
23
+ export const set_PlanCMD_DD = async (deviceId, command) => {
24
+ if (typeof command == 'string') {
25
+ command = command.match(/(.{2})/g).map((o) => `0x${o}`);
26
+ }
27
+ let result = null;
28
+ try {
29
+ const data = await getData(
30
+ deviceId,
31
+ {
32
+ command,
33
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd, pkgLen: hexArr[3] + 7 }),
34
+ pkgVerifyHandler: (pkg) => ({ verified: true }),
35
+ },
36
+ 'PlanCMD_DD'
37
+ );
38
+ if (data) {
39
+ const dataStr = hexArr2string(data);
40
+ const content = data.slice(4, data.length - 3);
41
+ result = {
42
+ data,
43
+ dataStr,
44
+ status: hex2string(data[2]),
45
+ len: data[3],
46
+ content,
47
+ };
48
+ } else {
49
+ result = {
50
+ status: 0,
51
+ content: [],
52
+ };
53
+ }
54
+ } catch (error) {
55
+ console.error(error);
56
+ }
57
+ return result;
58
+ }
59
+
60
+ // #region 3B3C指令
61
+ export const set_PlanCMD_3B3C = async (deviceId, command) => {
62
+ if (typeof command == 'string') {
63
+ command = command.match(/(.{2})/g).map((o) => `0x${o}`);
64
+ }
65
+ let result = null;
66
+ try {
67
+ const data = await getData(
68
+ deviceId,
69
+ {
70
+ command,
71
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0x3b && hexArr[1] == 0x3c, pkgLen: hexArr[11] + 15 }),
72
+ pkgVerifyHandler: (pkg) => ({ verified: true }),
73
+ },
74
+ 'PlanCMD_3B3C'
75
+ );
76
+ if (data) {
77
+ const dataStr = hexArr2string(data);
78
+ const content = data.slice(12, data.length - 3);
79
+ result = {
80
+ dataStr,
81
+ status: data[10] == 0x80 ? 1 : 0, // 0x80表示失败
82
+ addr: hexArr2string(data.slice(2, 10)),
83
+ content,
84
+ };
85
+ }
86
+ } catch (error) {
87
+ console.error(error);
88
+ }
89
+ return result;
90
+ }
91
+
92
+ // #region 获取设备3B3C信息包
93
+ /** 获取设备3B3C信息包
94
+ *
95
+ * @param {*} deviceId
96
+ * @returns
97
+ */
98
+ export const get3B3CAsync = async (deviceId) => {
99
+ const /*帧头*/ header = ['0x3b', '0x3c'];
100
+ const /*地址码*/ addressCode = ['0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00'];
101
+ const /*命令码*/ commandCode = ['0x01'];
102
+ const /*数据内容*/ data = [];
103
+ const /*数据内容长度*/ dataLength = [`0x${decimalToHex(data.length, 2)}`];
104
+ const /*校验位(地址码~数据内容)*/ check = generateCrc16modbusCheck([...addressCode, ...commandCode, ...dataLength, ...data], false);
105
+ const /*帧尾*/ footer = ['0x0d'];
106
+
107
+ // 帧头-地址码-命令码-数据内容长度-数据内容(没有时为空)-校验位-帧尾 3B3C-0000000000000000-01-00-NULL-9771-0D
108
+
109
+ const command = [...header, ...addressCode, ...commandCode, ...dataLength, ...data, ...check, ...footer];
110
+ const response = '3B3C010000000000000001470104070235000208555031365330313503154A42442D4C313653323030412E31302E30322E3031041E4A4244343831303030303100000000000000000000000000000000000000327A0D';
111
+ return getData(
112
+ deviceId,
113
+ {
114
+ command,
115
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0x3b && hexArr[1] == 0x3c, pkgLen: hexArr[11] + 15 }),
116
+ pkgVerifyHandler: (pkg) => {
117
+ const len = pkg.length;
118
+ const [c1, c2] = generateCrc16modbusCheck(pkg.slice(2, len - 3));
119
+ const [_c1, _c2] = [pkg[len - 3], pkg[len - 2]];
120
+ return { verified: c1 == _c1 && c2 == _c2 };
121
+ },
122
+ },
123
+ '3B3C'
124
+ );
125
+ }
126
+
127
+ // #region 解析3B3C信息包
128
+ /** 解析3B3C信息包
129
+ *
130
+ * @param {number[]} data 3b3c十六进制数据数组
131
+ * @returns
132
+ */
133
+ export const resolve3B3C = (data) => {
134
+ if (!data) return null;
135
+ // [59, 60, 1, 0, 0, 0, 0, 0, 0, 0, 1, 71, 1, 4, 7, 2, 53, 0, 2, 8, 85, 80, 49, 54, 83, 48, 49, 53, 3, 21, 74, 66, 68, 45, 76, 49, 54, 83, 50, 48, 48, 65, 46, 49, 48, 46, 48, 50, 46, 48, 49, 4, 30, 74, 66, 68, 52, 56, 49, 48, 48, 48, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 122, 13]
136
+ // 3B3C010000000000000001470104070235000208555031365330313503154A42442D4C313653323030412E31302E30322E3031041E4A4244343831303030303100000000000000000000000000000000000000327A0D
137
+
138
+ // [59, 60, 0, 0, 0, 0, 0, 0, 0, 0, 1, 22, 1, 4, 0, 2, 1, 0, 2, 0, 3, 10, 68, 72, 48, 52, 46, 49, 46, 52, 46, 55, 4, 0, 119, 90, 13];
139
+ // 3B3C000000000000000001160104000201000200030A444830342E312E342E370400775A0D
140
+ const dataStr = hexArr2string(data);
141
+ let result = {
142
+ dataStr,
143
+ status: hex2string(data[10]),
144
+ addr: hexArr2string(data.slice(2, 10)),
145
+ };
146
+ const content = data.slice(12, data.length - 3);
147
+ const CONTENT = [/*产品类型-TYPE*/ '01', /*产品类型标识长度*/ '04', /*产品类型表示*/ '00', /*前端类型标识*/ '02', /*协议版本标识*/ '01', /*层级关系架构*/ '00', /*硬件版本号-TYPE*/ '02', /*硬件版本号长度*/ '00', /*软件版本号-TYPE*/ '03', /*软件版本号长度*/ '0a', /*软件版本号内容ASCII*/ '44', /*软件版本号内容ASCII*/ '48', /*软件版本号内容ASCII*/ '30', /*软件版本号内容ASCII*/ '34', /*软件版本号内容ASCII*/ '2e', /*软件版本号内容ASCII*/ '31', /*软件版本号内容ASCII*/ '2e', /*软件版本号内容ASCII*/ '34', /*软件版本号内容ASCII*/ '2e', /*软件版本号内容ASCII*/ '37', /*PCB-TYPE*/ '04', /*PCB内容长度*/ '00'];
148
+ while (content.length > 0) {
149
+ const [v1, v2, ...v] = content.splice(0, content[1] + 2);
150
+ if (v1 == 0x01) {
151
+ result['productFlag'] = hex2string(v1);
152
+ result['productLen'] = v2;
153
+ if (v) {
154
+ const [productType, frontType, protocolV, level] = v.map((o) => hex2string(o));
155
+ Object.assign(result, { productType, frontType, protocolV, level });
156
+ }
157
+ }
158
+ if (v1 == 0x02) {
159
+ result['hardwareFlag'] = hex2string(v1);
160
+ result['hardwareLen'] = v2;
161
+ result['hardwareV'] = hexArr2Assic(v);
162
+ }
163
+ if (v1 == 0x03) {
164
+ result['sofewareFlag'] = hex2string(v1);
165
+ result['sofewareLen'] = v2;
166
+ result['sofewareV'] = hexArr2Assic(v);
167
+ }
168
+ if (v1 == 0x04) {
169
+ result['pcbFlag'] = hex2string(v1);
170
+ result['pcbLen'] = v2;
171
+ result['pcbContent'] = hexArr2Assic(v);
172
+ }
173
+ }
174
+ return result;
175
+ }
176
+
177
+ // #region 获取设备基本信息03指令
178
+ /** 获取设备基本信息03指令
179
+ *
180
+ * @param {*} deviceId
181
+ * @returns
182
+ */
183
+ export const getDDA503Async = async (deviceId) => {
184
+ const command = ['0xdd', '0xa5', '0x03', '0x00', '0xff', '0xfd', '0x77'];
185
+ const response = 'DD03002313E10000199D27100000310F00000000000014410310060BB60BC60BC10BBA0BBD0BC1F89777';
186
+
187
+ const hex = await getData(
188
+ deviceId,
189
+ {
190
+ command,
191
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0x03, pkgLen: hexArr[3] + 7 }),
192
+ pkgVerifyHandler: (pkg) => {
193
+ const len = pkg.length;
194
+ const [c1, c2] = generateCrcCheckSum(pkg.slice(2, len - 3));
195
+ const [_c1, _c2] = [pkg[len - 3], pkg[len - 2]];
196
+ return { verified: c1 == _c1 && c2 == _c2 };
197
+ },
198
+ },
199
+ 'DDA5_03'
200
+ );
201
+ if (!hex) return null;
202
+ return resolveDDA503(hex);
203
+ }
204
+
205
+ // #region 解析基本信息03指令
206
+ /** 解析基本信息03指令
207
+ *
208
+ * @param {number[]} data DDA503十六进制数据数组
209
+ * @returns {Object} 解析后的设备信息对象
210
+ * @returns {string} dataStr 十六进制数据数组转换后的字符串
211
+ * @returns {string} status 状态信息的字符串表示
212
+ * @returns {number} len 数据长度
213
+ * @returns {string} softwareV 软件版本信息的字符串表示
214
+ * @returns {boolean} chargeSwitch 充电开关状态,true 表示打开,false 表示关闭
215
+ * @returns {boolean} dischargeSwitch 放电开关状态,true 表示打开,false 表示关闭
216
+ * @returns {string} BMSVersion BMS 软件版本号
217
+ * @returns {string} totalVoltage 总电压,单位伏特,保留两位小数
218
+ * @returns {number} electricity 总电流,单位安培,保留两位小数
219
+ * @returns {string} power 功率,单位瓦特,保留两位小数
220
+ * @returns {number} soc 电池剩余电量百分比
221
+ * @returns {number} surplusCapacity 剩余容量,单位 Ah,保留两位小数
222
+ * @returns {number} normCap 标称容量,单位 Ah,保留两位小数
223
+ * @returns {number} humidity 湿度值
224
+ * @returns {number} cycleIndex 循环次数
225
+ * @returns {number} fullChargeCapacity 满充容量,单位 Ah
226
+ * @returns {number} SOH 电池健康状态百分比
227
+ * @returns {boolean} heatingState 是否正在加热,true 表示正在加热,false 表示未加热
228
+ * @returns {number} heatingCurrent 加热电流,单位 mA
229
+ * @returns {boolean} isFactoryMode 是否处于工厂模式,true 表示是,false 表示否
230
+ * @returns {boolean} equilibriumStatus 均衡状态,true 表示正在均衡,false 表示未均衡
231
+ * @returns {boolean} protectStatus 保护状态,true 表示存在保护状态,false 表示正常
232
+ * @returns {string} protectStatusIndex 发生保护的状态索引,以逗号分隔的字符串
233
+ * @returns {number} ntcNums NTC 传感器数量
234
+ * @returns {string[]} temperaturesList 温度列表,单位摄氏度,保留一位小数
235
+ * @returns {string[]} fahTempList 华氏度温度列表,保留一位小数
236
+ * @returns {number} fet FET 状态值
237
+ * balances 单串均衡数组
238
+ */
239
+ export const resolveDDA503 = (data) => {
240
+ if (!data) return null;
241
+ const cmdResp03 = hexArr2string(data);
242
+ const content = data.slice(4, data.length - 3);
243
+
244
+ const binArr = data[24].toString(2).padStart(8, '0').split('').reverse();
245
+
246
+ const balances = data.slice(16, 20).flatMap((byte) =>
247
+ byte.toString(2).padStart(8, "0").split("").reverse()
248
+ );
249
+ console.warn("BC: ---------------", balances);
250
+ const fet = data[24];
251
+ /**充电开关 */
252
+ const chargeSwitch = !!Number(binArr[0]);
253
+ const dischargeSwitch = !!Number(binArr[1]);
254
+
255
+ /** 软件版本 */
256
+ const BMSVersion = decimalToHex(data[22]);
257
+
258
+ const humidityIndex = data[26] * 2 + 27;
259
+ /**湿度 */
260
+ const humidity = data[humidityIndex];
261
+ // 判断电流及容量单位,默认10mAh, 当fet控制状态bit7=1或者湿度值为88(十六进制,对应十进制136)时,单位为100mAh
262
+ const n = !!Number(binArr[7]) || humidity == 136 ? 10 : 100;
263
+ /**总电压 */
264
+ const totalVoltage = Number((((data[4] << 8) + (data[5] & 0x0ff)) / 100).toFixed(2))
265
+ let current = (data[6] << 8) + (data[7] & 0x0ff);
266
+ current = current > 32768 ? (current - 65536) / n : current / n;
267
+ /**总电流 */
268
+ const electricity = Number(current.toFixed(2));
269
+ /**功率 */
270
+ const power = Number((totalVoltage * electricity).toFixed(2));
271
+ /** soc % */
272
+ const soc = data[23] ?? 0;
273
+ /** 串数 */
274
+ const seriesNum = data[25] ?? 0;
275
+
276
+ const volumeHex = decimalToHex(data[8]) + decimalToHex(data[9]); // 10mAh
277
+
278
+ /** 剩余容量 */
279
+ const surplusCapacity = Number((hexToDecimal(volumeHex) / n).toFixed(2));
280
+
281
+ const normCapHex = decimalToHex(data[10]) + decimalToHex(data[11]);
282
+ /** 标称容量 */
283
+ const normCap = Number((hexToDecimal(normCapHex) / n).toFixed(2));
284
+
285
+ const cycleHex = decimalToHex(data[12]) + decimalToHex(data[13]);
286
+ /** 循环次数 */
287
+ const cycleIndex = hexToDecimal(cycleHex);
288
+
289
+ const fccHex = decimalToHex(data[humidityIndex + 3]) + decimalToHex(data[humidityIndex + 4]);
290
+ /** 满充容量 */
291
+ const fullChargeCapacity = Number((hexToDecimal(fccHex) / n).toFixed(2));
292
+ const SOH = cycleIndex <= 100 ? 100 : Math.min(100, (fullChargeCapacity / normCap) * 100);
293
+ /**是否正在加热 当FET bit3=1 OR bit4=1(任意一个为1),则加热状态=开*/
294
+ const heatingState = !!Number(binArr[4]) || !!Number(binArr[3])
295
+ /**加热电流 */
296
+ let heatingCurrent = null;
297
+ // 判断模组回复长度(内容长度加起止位命令码等)是否大于计算出的加热电流的索引加上校验码停止位(由于是索引,长度需加1)
298
+ const hasHeatingCurrent = data[3] + 7 >= humidityIndex + 9 + 4 + 1;
299
+ const hasEquilCurrent = data[3] + 7 >= humidityIndex + 7 + 4 + 1;
300
+ // 如果处于加热状态且存在加热电流数据
301
+ if (heatingState && hasHeatingCurrent) {
302
+ const i = humidityIndex + 9;
303
+ heatingCurrent = heatingCurrent = (
304
+ ((data[i] << 8) | data[i + 1]) /
305
+ 100
306
+ ).toFixed(2);
307
+ }
308
+ let equilibriumCurrent = null;
309
+ // 均衡电流
310
+ if (hasEquilCurrent) {
311
+ const i = humidityIndex + 7;
312
+ let equilCurrent = (data[i] << 8) + (data[i + 1] & 0x0ff);
313
+ equilCurrent = equilCurrent > 32768 ? (equilCurrent - 65536) / 1000 : equilCurrent / 1000;
314
+ equilibriumCurrent = Number(equilCurrent.toFixed(2));
315
+ }
316
+ /**是否是工厂模式 */
317
+ const isFactoryMode = !!Number(binArr[6]);
318
+ /**均衡状态 */
319
+ const equilibriumStatus = !(data[16] === 0 && data[17] === 0);
320
+ /**保护状态 */
321
+ const protectStatus = !(data[20] === 0 && data[21] === 0);
322
+ // 解析保护状态
323
+ const protectStateHex = decimalToHex(data[20]) + decimalToHex(data[21]);
324
+ const protVal = hexToDecimal(protectStateHex);
325
+
326
+ // 告警状态下标
327
+ let alarmIndex = data[26] * 2 + 28;
328
+ let alarmStateHex = decimalToHex(data[alarmIndex]) + decimalToHex(data[alarmIndex + 1]);
329
+ let alarmsState = hexToDecimal(alarmStateHex);
330
+
331
+ const ntcNums = data[26] & 0xff;
332
+ /**温度 */
333
+ const temperaturesList = Array.from({ length: ntcNums }, (_, i) => {
334
+ const tempH = data[26 + i * 2 + 1] & 0xff;
335
+ const tempL = data[26 + i * 2 + 2] & 0xff;
336
+ return ((tempH * 256 + tempL - 2731) / 10).toFixed(1);
337
+ });
338
+ /** 华氏度 */
339
+ const fahTempList = []
340
+ temperaturesList.forEach((el) => {
341
+ const value = Number(el) * (9 / 5) + 32;
342
+ fahTempList.push(value.toFixed(1))
343
+ })
344
+ const temperatures = temperaturesList.map((item, index) => ({
345
+ name: index + 1,
346
+ value: item,
347
+ }));
348
+ return {
349
+ cmdResp03,
350
+ status: hex2string(data[2]),
351
+ len: data[3],
352
+ softwareV: hex2string(content[18]),
353
+ balances,
354
+ BMSVersion,
355
+ soc,
356
+ normCap,
357
+ surplusCapacity,
358
+ totalVoltage,
359
+ electricity,
360
+ power,
361
+ cycleIndex,
362
+ equilibriumStatus,
363
+ chargeSwitch,
364
+ dischargeSwitch,
365
+ temperatures,
366
+ humidity,
367
+ protectStatus,
368
+ fullChargeCapacity,
369
+ SOH,
370
+ heatingState,
371
+ heatingCurrent,
372
+ equilibriumCurrent,
373
+ isFactoryMode,
374
+ protVal,
375
+ alarmsState,
376
+ ntcNums,
377
+ temperaturesList,
378
+ fahTempList,
379
+ fet,
380
+ seriesNum,
381
+ };
382
+ }
383
+
384
+ // #region 获取DDA504数据
385
+ /** 获取DDA504数据
386
+ * 电压数据
387
+ * @param {*} deviceId
388
+ * @returns
389
+ */
390
+ export const getDDA504Async = async (deviceId, dataScope) => {
391
+ const command = ['0xdd', '0xa5', '0x04', '0x00', '0xff', '0xfc', '0x77'];
392
+ const response = '';
393
+
394
+ const hex = await getData(
395
+ deviceId,
396
+ {
397
+ command,
398
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0x04, pkgLen: hexArr[3] + 7 }),
399
+ pkgVerifyHandler: (pkg) => {
400
+ const len = pkg.length;
401
+ const [c1, c2] = generateCrcCheckSum(pkg.slice(2, len - 3));
402
+ const [_c1, _c2] = [pkg[len - 3], pkg[len - 2]];
403
+ return { verified: c1 == _c1 && c2 == _c2 };
404
+ },
405
+ },
406
+ 'DDA5_04'
407
+ );
408
+ if (!hex) return null;
409
+ return resolveDDA504(hex, dataScope);
410
+ }
411
+
412
+ // #region 解析DDA504数据
413
+ /**
414
+ * 解析硬件电压数据 04 指令
415
+ *
416
+ * @param {number[]} data DDA504十六进制数据数组
417
+ * @param {boolean} [dataScope=false] 是否启用高精度数据解析,默认值为 false
418
+ * @returns {Object} 解析后的电压数据对象
419
+ * @returns {string} dataStr 十六进制数据数组转换后的字符串
420
+ * @returns {number[]} voltageList 电压列表,根据 dataScope 保留不同精度
421
+ * @returns {number|null} highestVoltage 最高电压,根据 dataScope 保留不同精度,若电压列表为空则为 null
422
+ * @returns {number|null} lowestVoltage 最低电压,根据 dataScope 保留不同精度,若电压列表为空则为 null
423
+ * @returns {number|null} averageVoltage 平均电压,根据 dataScope 保留不同精度,若电压列表为空则为 null
424
+ * @returns {number|null} dropoutVoltage 电压差,根据 dataScope 保留不同精度,若电压列表为空则为 null
425
+ */
426
+ export const resolveDDA504 = (data, dataScope) => {
427
+ console.log('data: ', data);
428
+ if (!data) return null;
429
+ const cmdResp04 = hexArr2string(data);
430
+ const voltageList = [];
431
+ for (let index = 4; index < parseInt(data[3]) + 4; index += 2) {
432
+
433
+ let voltageHex = decimalToHex(data[index]) + decimalToHex(data[index + 1]);
434
+ let voltage = hexToDecimal(voltageHex);
435
+ voltageList.push(Number(voltage));
436
+ }
437
+ let highestVoltage = null
438
+ let lowestVoltage = null
439
+ let averageVoltage = null
440
+ let dropoutVoltage = null
441
+ if (voltageList.length > 0) {
442
+ // 最高、最低电压
443
+ highestVoltage = Math.max(...voltageList);
444
+ lowestVoltage = Math.min(...voltageList);
445
+ // 计算平均电压
446
+ const sum = voltageList.reduce((acc, cur) => acc + cur, 0);
447
+ averageVoltage = sum / voltageList.length;
448
+ // 压差
449
+ const dropout = highestVoltage - lowestVoltage;
450
+
451
+ // 精度处理
452
+ const precision = dataScope ? 3 : 2;
453
+ highestVoltage = Number((highestVoltage / 1000).toFixed(precision));
454
+ lowestVoltage = Number((lowestVoltage / 1000).toFixed(precision));
455
+ averageVoltage = Number((averageVoltage / 1000).toFixed(precision));
456
+ dropoutVoltage = Number((dropout / 1000).toFixed(precision));
457
+ }
458
+ voltageList.forEach((v, i) => {
459
+ let newV = v / 1000
460
+ voltageList[i] = Number(newV.toFixed(dataScope ? 3 : 2));
461
+ });
462
+ const voltageSeries = voltageList.map((item, index) => ({
463
+ name: index + 1,
464
+ value: item,
465
+ }));
466
+ return {
467
+ cmdResp04,
468
+ voltageList,
469
+ voltageSeries,
470
+ highestVoltage,
471
+ lowestVoltage,
472
+ averageVoltage,
473
+ dropoutVoltage
474
+ };
475
+ }
476
+
477
+ // #region 获取芯片类型
478
+ /** 获取芯片类型
479
+ *
480
+ * @param {*} deviceId
481
+ * @returns
482
+ */
483
+ export const getChipTypeAsync = async (deviceId) => {
484
+ const command = ['0xdd', '0xa5', '0x00', '0x00', '0x00', '0x00', '0x77'];
485
+ const hex = await getData(
486
+ deviceId,
487
+ {
488
+ command,
489
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0x00, pkgLen: hexArr[3] + 7 }),
490
+ pkgVerifyHandler: (pkg) => {
491
+ const len = pkg.length;
492
+ const [c1, c2] = generateCrcCheckSum(pkg.slice(2, len - 3));
493
+ const [_c1, _c2] = [pkg[len - 3], pkg[len - 2]];
494
+ return { verified: c1 == _c1 && c2 == _c2 };
495
+ },
496
+ },
497
+ 'DDA5_00'
498
+ );
499
+ if (!hex) return null;
500
+ return resolveDDA500(hex);
501
+ }
502
+
503
+ // #region 解析芯片类型
504
+ /** 解析芯片类型
505
+ *
506
+ * @param {number[]} data DDA500十六进制数据数组
507
+ * @returns {Object} 解析后的芯片类型对象
508
+ * @returns {string} dataStr 十六进制数据数组转换后的字符串
509
+ * @returns {string} status 状态码
510
+ * @returns {number} len 数据长度
511
+ * @returns {string} type 芯片类型
512
+ */
513
+ export const resolveDDA500 = (data) => {
514
+ // 'dd00 0002 0005 fff9 77';
515
+ // 当XX = 0或者发送该指令不回复时,默认BMS方案为TI方案,
516
+ // 当XX = 1时 为凹凸方案
517
+ // 当XX = 2时 为新塘松下方案
518
+ // 当XX = 3时 为中颖309方案
519
+ // 当XX = 4时 为中颖303方案,
520
+ // 当XX = 5时 为集澈芯片
521
+ if (!data) return null;
522
+ const dataStr = hexArr2string(data);
523
+ return {
524
+ dataStr,
525
+ status: hex2string(data[2]),
526
+ len: data[3],
527
+ type: data[5],
528
+ };
529
+ }
530
+
531
+ // #region 设置MOS开关 E1指令
532
+ /**设置MOS开关
533
+ * value:
534
+ * 0x00 - 充电MOS打开 放电MOS打开
535
+ * 0x01 - 充电MOS关闭 放电MOS打开
536
+ * 0x02 - 充电MOS打开 放电MOS关闭
537
+ * 0x03 - 充电MOS关闭 放电MOS关闭
538
+ */
539
+ export const getDD5AE1Async = async (deviceId, value) => {
540
+ const _command = ['0xE1', '0x02', '0x00' /*0x00用户级别控制 0xaa运营级别控制*/, value];
541
+ const checks = generateCrcCheckSum(_command);
542
+ const command = ['0xDD', '0x5A', ..._command, ...checks, '0x77'];
543
+
544
+ // T: DD 5A E1 02 00 02 FF 1B 77
545
+ // R: DD E1 00000000 77
546
+ return getData(
547
+ deviceId,
548
+ {
549
+ command,
550
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0xe1, pkgLen: hexArr[3] + 7 }),
551
+ pkgVerifyHandler: (pkg) => {
552
+ return { verified: true };
553
+ },
554
+ },
555
+ 'DD5A_E1'
556
+ );
557
+ }
558
+
559
+ // #region 设置MOS开关 FB指令
560
+ /**设置MOS开关 FB
561
+ * type:
562
+ * 0x00 - 放电MOS
563
+ * 0x01 - 充电MOS
564
+ * 0x03 - 预放电MOS
565
+ * 0x05 - 电池激活
566
+ * 0x06 - 加热控制
567
+ *
568
+ * value:
569
+ * 0x00 - 打开
570
+ * 0x01 - 关闭
571
+ *
572
+ * 电池激活 value:
573
+ * 0x00 - 解除激活
574
+ * 0x01 - 激活
575
+ * 0x02 - 试用30分钟激活
576
+ * 0x03 - 试用一小时激活
577
+ * 0x04 - 试用两小时激活
578
+ * 0x05 - 试用三小时激活
579
+ * * 加热控制 value:
580
+ * 0x00 - 关闭加热
581
+ * 0x01 - 开启加热
582
+ */
583
+ export const getDD5AFBAsync = async (deviceId, type, value) => {
584
+ const _command = ['0xFB', '0x02', type, value];
585
+ const checks = generateCrcCheckSum(_command);
586
+ const command = ['0xDD', '0x5A', ..._command, ...checks, '0x77'];
587
+ console.log('command: ', command);
588
+ return getData(
589
+ deviceId,
590
+ {
591
+ command,
592
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0xfb, pkgLen: hexArr[3] + 7 }),
593
+ pkgVerifyHandler: (pkg) => {
594
+ return { verified: true };
595
+ },
596
+ },
597
+ 'DD5A_FB'
598
+ );
599
+ }
600
+ /** 解析DD指令响应
601
+ *
602
+ * @param {*} data
603
+ * @returns
604
+ */
605
+ export const resolveBaseDD = (data) => {
606
+ if (!data) return null;
607
+ const dataStr = hexArr2string(data);
608
+ const content = data.slice(4, data.length - 3);
609
+ return {
610
+ dataStr,
611
+ status: hex2string(data[2]),
612
+ len: data[3],
613
+ content,
614
+ };
615
+ }
616
+
617
+ // #region 0A 控制指令
618
+ /**
619
+ * 0A 控制指令
620
+ * @param {*} deviceId 设备id
621
+ * @param {*} values 指令内容 十六进制字符串 eg: '0100'
622
+ *
623
+ * @param 0100 - 重置容量 重置容量按照电压参数重新估算容量
624
+ * @param 0200 - 清除历史保护次数
625
+ * @param 0300 - 复位一次单片机
626
+ * @param 0400 - 清除保护状态和告警状态
627
+ * @param 0500 - 保护板进入到休眠状态
628
+ * @param 0600 - 进入超低功耗,需要充电唤醒。
629
+ * @param 0700 - 自动均衡模式,发送该指令自动开启均衡
630
+ * @param 0800 - 进入储运模式
631
+ * @param 0900 - 设置SOC-20%开关功能 双聚版本,设置为1之后则低于20%会关闭放电
632
+ * @param 0A00 - 低于20%强制打开指令 当SOC_20%功能有效,并且SOC<20%,可发送此指令强制打开
633
+ * @param 0B00 - 强制启动-启动模式 启动电池模式时强启启动输出,强制输出1小时
634
+ * @param 0C00 - 强制加热 加热到15℃自动停止
635
+ * @param 0D00 - 低电量蜂鸣器报警功能 蜂鸣器 放电时 每30S响一次 放电结束延时5分钟
636
+ * @param 0E00 - 打开低电量闪断功能 闪断功能为当从10%或者5%时触发,触发后控制MOS打嗝,开1S断1S,循环3次
637
+ * @param 0F00 - 关闭低电量闪断功能
638
+ * @param 1881 - 恢复出厂设置参数
639
+ * @param FFFF - 心跳包
640
+ * @returns hex 数组
641
+ */
642
+ export const getDD5A0AAsync = async (deviceId, value) => {
643
+ const values = stringToTwoHexArray(value);
644
+ const _command = ['0x0A', '0x02', ...values];
645
+ const checks = generateCrcCheckSum(_command);
646
+ const command = ['0xDD', '0x5A', ..._command, ...checks, '0x77'];
647
+ console.log('command: ', command);
648
+ return getData(
649
+ deviceId,
650
+ {
651
+ command,
652
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0x0A, pkgLen: hexArr[3] + 7 }),
653
+ pkgVerifyHandler: (pkg) => {
654
+ return { verified: true };
655
+ },
656
+ },
657
+ `DD5A_0A_${value}`
658
+ );
659
+ }
660
+
661
+ // #region 设置满充容量
662
+ /**设置满充容量
663
+ *
664
+ * @param {*} deviceId
665
+ * @param {*} value AH
666
+ * @returns
667
+ */
668
+ export const getDD5AFAAsync = async (deviceId, value) => {
669
+ const hex = value * 100; /*AH => 10mAh*/
670
+ const hexStr = decimalToHex(hex, 4);
671
+ const _values = [`0x${hexStr.slice(0, 2)}`, `0x${hexStr.slice(2, 4)}`];
672
+ const _command = ['0xFA', '0x05', '0x00', '0x70', '0x01', ..._values];
673
+ const checks = generateCrcCheckSum(_command);
674
+ const command = ['0xDD', '0x5A', ..._command, ...checks, '0x77'];
675
+ return getData(
676
+ deviceId,
677
+ {
678
+ command,
679
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0xfa, pkgLen: hexArr[3] + 7 }),
680
+ pkgVerifyHandler: (pkg) => {
681
+ return { verified: true };
682
+ },
683
+ },
684
+ 'DD5A_FA'
685
+ );
686
+ }
687
+
688
+ // #region 获取设备硬件版本号05指令
689
+ /** 获取设备硬件版本号05指令
690
+ *
691
+ * @param {*} deviceId
692
+ * @returns
693
+ */
694
+ export const getDDA505Async = async (deviceId) => {
695
+ const command = ['0xdd', '0xa5', '0x05', '0x00', '0xff', '0xfb', '0x77'];
696
+ const response = 'DD 05 00 0A 30 31 32 33 34 35 36 37 38 39 FD E9 77';
697
+
698
+ return getData(
699
+ deviceId,
700
+ {
701
+ command,
702
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0x05, pkgLen: hexArr[3] + 7 }),
703
+ pkgVerifyHandler: (pkg) => {
704
+ const len = pkg.length;
705
+ const [c1, c2] = generateCrcCheckSum(pkg.slice(2, len - 3));
706
+ const [_c1, _c2] = [pkg[len - 3], pkg[len - 2]];
707
+ return { verified: c1 == _c1 && c2 == _c2 };
708
+ },
709
+ },
710
+ 'DDA5_05'
711
+ );
712
+ }
713
+ /** 解析硬件版本号05指令
714
+ *
715
+ * @param {number[]} data DDA505十六进制数据数组
716
+ * @returns
717
+ */
718
+ export const resolveDDA505 = (data) => {
719
+ if (!data) return null;
720
+ const dataStr = hexArr2string(data);
721
+ return {
722
+ dataStr,
723
+ status: hex2string(data[2]),
724
+ len: data[3],
725
+ hardwareV: hexArr2Assic(data.slice(4, data.length - 3)),
726
+ };
727
+ }
728
+
729
+ // #region 新协议读参数
730
+ /**DDA5FA 读参数FA指令 新协议
731
+ * @param {string[]} paramNo 参数号 十六进制字符串数组
732
+ * @param {string} length 长度 十六进制字符串数组
733
+ * @returns
734
+ */
735
+ export const getDDA5FAAsync = async (deviceId, paramNo, length) => {
736
+ const header = ['0xDD', '0xA5']
737
+ const _values = [...paramNo, length];
738
+ const lengthHex = decimalToHex(_values.length)
739
+ const _command = ['0xFA', lengthHex, ..._values];
740
+ const checks = generateCrcCheckSum(_command);
741
+ const command = [...header, ..._command, ...checks, '0x77'];
742
+ return getData(
743
+ deviceId,
744
+ {
745
+ command,
746
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0xFA, pkgLen: hexArr[3] + 7 }),
747
+ pkgVerifyHandler: (pkg) => {
748
+ return { verified: true };
749
+ },
750
+ },
751
+ 'READ_DDA5_FA'
752
+ );
753
+ }
754
+
755
+ // #region 写参数FA指令 新协议
756
+ /**DDA5FA 写参数FA指令 新协议
757
+ * @param {string[]} paramNo 参数号 十六进制字符串数组
758
+ * @param {string} valueLength 写入值的长度(写入几个值) 十六进制字符串数组
759
+ * @param {string[]} content 写入值内容(按对应序号的长度字节) 十六进制字符串数组
760
+ * @returns
761
+ */
762
+ export const setDDA5FAAsync = async (deviceId, paramNo, valueLength, content) => {
763
+ const header = ['0xDD', '0x5A']
764
+ const _values = [...paramNo, valueLength, ...content];
765
+ const lengthHex = decimalToHex(_values.length)
766
+ const _command = ['0xFA', lengthHex, ..._values];
767
+ const checks = generateCrcCheckSum(_command);
768
+ const command = [...header, ..._command, ...checks, '0x77'];
769
+ return getData(
770
+ deviceId,
771
+ {
772
+ command,
773
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0xFA, pkgLen: hexArr[3] + 7 }),
774
+ pkgVerifyHandler: (pkg) => {
775
+ return { verified: true };
776
+ },
777
+ },
778
+ 'SET_DDA5_FA'
779
+ );
780
+ }
781
+ /**
782
+ * 解析DDA5FA数据
783
+ * @param {*} data
784
+ * @returns
785
+ */
786
+ export const resolveDDA5FA = (data) => {
787
+ if (!data) return null;
788
+ const dataStr = hexArr2string(data);
789
+ return {
790
+ dataStr,
791
+ status: hex2string(data[2]),
792
+ len: data[3],
793
+ type: data[5],
794
+ value: data.slice(7, -3),
795
+ };
796
+ }
797
+
798
+ // #region 读参数旧协议
799
+ /** 获取读参数FA指令 旧协议
800
+ *
801
+ * @param {*} deviceId
802
+ * @returns
803
+ */
804
+ export const getDDA5OldAsync = async (deviceId, path) => {
805
+ const values = [path, '0x00']
806
+ const checks = generateCrcCheckSum(values);
807
+ const command = ['0xDD', '0xa5', ...values, ...checks, '0x77'];
808
+
809
+ return getData(
810
+ deviceId,
811
+ {
812
+ command,
813
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == path, pkgLen: hexArr[3] + 7 }),
814
+ pkgVerifyHandler: (pkg) => {
815
+ const len = pkg.length;
816
+ const [c1, c2] = generateCrcCheckSum(pkg.slice(2, len - 3));
817
+ const [_c1, _c2] = [pkg[len - 3], pkg[len - 2]];
818
+ return { verified: c1 == _c1 && c2 == _c2 };
819
+ },
820
+ },
821
+ 'DDA5_读旧协议读参'
822
+ );
823
+ }
824
+
825
+ // #region 写参数旧协议
826
+ /**写入参数 旧协议
827
+ *
828
+ * @param {*} deviceId
829
+ * @param {*} path "0x17"
830
+ * @param {string[]} value 写入值内容 十六进制字符串数组 ["0x00,0x00"]
831
+ * @returns
832
+ */
833
+ export const setDDA5OldAsync = async (deviceId, path, value, type = '') => {
834
+
835
+ const values = [path, '0x02', ...value]
836
+ const checks = generateCrcCheckSum(values);
837
+ const command = ['0xDD', '0x5A', ...values, ...checks, '0x77'];
838
+
839
+ return getData(
840
+ deviceId,
841
+ {
842
+ command,
843
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == path, pkgLen: hexArr[3] + 7 }),
844
+ pkgVerifyHandler: (pkg) => ({ verified: true }),
845
+ },
846
+ 'DDA5_写旧协议读参' + type
847
+ );
848
+ }
849
+
850
+ // #region 设置剩余容量
851
+ /**设置剩余容量 */
852
+ export const getDD5AE0Async = async (deviceId, value) => {
853
+ const hex = value * 100; /*AH => 10mAh*/
854
+ const hexStr = decimalToHex(hex, 4);
855
+ const _values = [`0x${hexStr.slice(0, 2)}`, `0x${hexStr.slice(2, 4)}`];
856
+ const _command = ['0xE0', '0x02', ..._values];
857
+ const checks = generateCrcCheckSum(_command);
858
+ // DD5AE0 02 XXXXCCCC77
859
+ const command = ['0xDD', '0x5A', ..._command, ...checks, '0x77'];
860
+ console.warn('=======CCCCCCC=SET===', { value, hex, hexStr, _values, _command, checks, command });
861
+ const response = 'DDE0 0000 0000 77';
862
+ return getData(
863
+ deviceId,
864
+ {
865
+ command,
866
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0xe0, pkgLen: hexArr[3] + 7 }),
867
+ pkgVerifyHandler: (pkg) => {
868
+ return { verified: true };
869
+ },
870
+ },
871
+ 'DD5A_E0'
872
+ );
873
+ }
874
+
875
+ // #region BMS复位
876
+ /** BMS复位 */
877
+ export const getDD5A0EAsync = async (deviceId) => {
878
+ const command = ['0xDD', '0x5A', '0x0E', '0x02', '0x81', '0x18', '0xFF', '0x57', '0x77'];
879
+ return getData(
880
+ deviceId,
881
+ {
882
+ command,
883
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0x0e, pkgLen: hexArr[3] + 7 }),
884
+ pkgVerifyHandler: (pkg) => {
885
+ return { verified: true };
886
+ },
887
+ },
888
+ 'DD5A_0E_复位'
889
+ );
890
+ }
891
+
892
+ // #region 加热测试
893
+ /** 加热测试 */
894
+ export const getDD5AE4Async = async (deviceId) => {
895
+ const command = ['0xdd', '0x5a', '0xe4', '0x02', '0x18', '0x81', '0xfe', '0x81', '0x77'];
896
+ return getData(
897
+ deviceId,
898
+ {
899
+ command,
900
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0xe4, pkgLen: hexArr[3] + 7 }),
901
+ pkgVerifyHandler: (pkg) => {
902
+ return { verified: true };
903
+ },
904
+ },
905
+ 'DD5A_E4'
906
+ );
907
+ }
908
+
909
+
910
+ // #region 5A通用写入指令
911
+ /**发送5A写入指令
912
+ *
913
+ * @param {*} deviceId
914
+ * @param {*} path 指令路径
915
+ * @param path 0xB0 电压校准
916
+ * @param path 0x0B 清除保护次数
917
+ * @param {*} lengthHex 内容长度
918
+ * @param {*} values 指令值
919
+ * @returns
920
+ */
921
+ export const getDD5AAllAsync = async (deviceId, path, lengthHex, values, type) => {
922
+ const _command = [path, lengthHex, ...values];
923
+ const checks = generateCrcCheckSum(_command);
924
+ const command = ['0xDD', '0x5A', ..._command, ...checks, '0x77'];
925
+ return getData(
926
+ deviceId,
927
+ {
928
+ command,
929
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == path, pkgLen: hexArr[3] + 7 }),
930
+ pkgVerifyHandler: (pkg) => {
931
+ return { verified: true };
932
+ },
933
+ },
934
+ `DD5A_${path}_${type}`
935
+ );
936
+ }
937
+
938
+ // #region 发送BMS指令 原始指令
939
+ /**发送BMS指令 原始指令
940
+ *
941
+ * @param {*} deviceId
942
+ * @param {*} values 原始指令值字符串
943
+ * @returns
944
+ */
945
+ export const sendBMSAsync = async (deviceId, values) => {
946
+ // 将 values 字符串按两两字符分隔,并在每一项前添加 '0x'
947
+ const command = values.match(/[0-9a-fA-F]{2}/g)?.map(item => `0x${item}`) || [];
948
+ return getData(
949
+ deviceId,
950
+ {
951
+ command,
952
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == command[2], pkgLen: hexArr[3] + 7 }),
953
+ pkgVerifyHandler: (pkg) => {
954
+ return { verified: true };
955
+ },
956
+ },
957
+ `DD5A_${command[2]}`
958
+ );
959
+ }
960
+ // #region 解析BMS指令 原始指令
961
+ /** 解析BMS指令 原始指令
962
+ *
963
+ * @param {number[]} data DD 十六进制数据数组
964
+ * @returns {object} 解析后的数据 十六进制数据字符串
965
+ */
966
+ export const resolveBMSCmd = (data) => {
967
+ if (!data) return null;
968
+ const dataStr = hexArr2string(data);
969
+ return {
970
+ dataStr,
971
+ status: hex2string(data[2]),
972
+ len: data[3],
973
+ };
974
+ }
975
+ // #region 保护次数
976
+ /**
977
+ * 保护次数
978
+ * @param {*} deviceId
979
+ * @returns 保护次数数组
980
+ */
981
+ export const getProtectCountCmd = async (deviceId) => {
982
+ const command = ['0xdd', '0xa5', '0xaa', '0x00', '0xff', '0x56', '0x77'];
983
+ const data = await getData(
984
+ deviceId,
985
+ {
986
+ command,
987
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0xaa, pkgLen: hexArr[3] + 7 }),
988
+ pkgVerifyHandler: (pkg) => {
989
+ return { verified: true };
990
+ },
991
+ },
992
+ 'DDA5_AA保护次数'
993
+ );
994
+ const arr = [];
995
+ // 使用循环来简化索引操作
996
+ for (let i = 4; i < data.length - 4; i += 2) {
997
+ const offset = i;
998
+ console.log('offset', offset);
999
+ // 优化位运算,确保位移和加法的优先级正确
1000
+ let value = ((data[offset] & 0xff) << 8) + (data[offset + 1] & 0xff)
1001
+ arr.push(value);
1002
+ }
1003
+ return arr;
1004
+ }
1005
+ // #region 清除电池循环次数
1006
+ /**清除电池循环次数
1007
+ * - 需要进/退工厂
1008
+ */
1009
+ export const getDD5A17Async = async (deviceId) => {
1010
+ // DD5A17020001FFE677
1011
+ const command = ['0xDD', '0x5A', '0x17', '0x02', '0x00', '0x01', '0xFF', '0xE6', '0x77'];
1012
+ return getData(
1013
+ deviceId,
1014
+ {
1015
+ command,
1016
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0x17, pkgLen: hexArr[3] + 7 }),
1017
+ pkgVerifyHandler: (pkg) => {
1018
+ return { verified: true };
1019
+ },
1020
+ },
1021
+ 'DD5A_17-清除电池循环次数'
1022
+ );
1023
+ }
1024
+
1025
+ // #region 历史运行履历
1026
+ /** 获取历史运行履历总数 */
1027
+ export const getDDA507Async = async (deviceId) => {
1028
+ // DD A5 07 00 FF F9 77
1029
+ // DD 07 00 04 00 02 00 02 FF F8 77
1030
+ // DD07000400020002FFF877
1031
+ const command = ['0xDD', '0xA5', '0x07', '0x00', '0xFF', '0xF9', '0x77'];
1032
+ return getData(
1033
+ deviceId,
1034
+ {
1035
+ command,
1036
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0x07, pkgLen: hexArr[3] + 7 }),
1037
+ pkgVerifyHandler: (pkg) => {
1038
+ const len = pkg.length;
1039
+ const [c1, c2] = generateCrcCheckSum(pkg.slice(2, len - 3));
1040
+ const [_c1, _c2] = [pkg[len - 3], pkg[len - 2]];
1041
+ return { verified: c1 == _c1 && c2 == _c2 };
1042
+ },
1043
+ },
1044
+ 'DDA5_07历史运行履历总数'
1045
+ );
1046
+ }
1047
+ /**读取履历详情
1048
+ *
1049
+ * @param {*} deviceId
1050
+ * @param {*} i 当前条数
1051
+ * @returns
1052
+ */
1053
+ export const getDDA508Async = async (deviceId, i = 0) => {
1054
+ // DD A5 08 00 FF F8 77
1055
+
1056
+ // DD08004400020000042F215B0009400500000000
1057
+ // 983A0A000000990B980B0000000037022802020F
1058
+ // 0100350237023602350233023502330233023402
1059
+ // 32023202320233022F022802F90E77
1060
+
1061
+ const _command = ['0x08', `0x${i.toString(16).padStart(2, 0)}`];
1062
+ const checks = generateCrcCheckSum(_command);
1063
+
1064
+ const command = ['0xDD', '0xA5', ..._command, ...checks, '0x77'];
1065
+ return getData(
1066
+ deviceId,
1067
+ {
1068
+ command,
1069
+ commandVerifyHandler: (hexArr) => ({ verified: hexArr[0] == 0xdd && hexArr[1] == 0x08, pkgLen: hexArr[3] + 7 }),
1070
+ pkgVerifyHandler: (pkg) => {
1071
+ const len = pkg.length;
1072
+ const [c1, c2] = generateCrcCheckSum(pkg.slice(2, len - 3));
1073
+ const [_c1, _c2] = [pkg[len - 3], pkg[len - 2]];
1074
+ return { verified: c1 == _c1 && c2 == _c2 };
1075
+ },
1076
+ },
1077
+ 'DDA5_08读取履历详情'
1078
+ );
1079
+ }
1080
+
1081
+ // #region 解析保护状态
1082
+ /** 解析保护状态 */
1083
+ export const resolveProtections = (binary) => {
1084
+ const arr = binary.split('').reverse();
1085
+ // "device.0041": "单体过压保护",
1086
+ // "device.0042": "单体欠压保护",
1087
+ // "device.0043": "整组过压保护",
1088
+ // "device.0044": "整组欠压保护",
1089
+ // "device.0045": "充电过温保护",
1090
+ // "device.0046": "充电低温保护",
1091
+ // "device.0047": "放电过温保护",
1092
+ // "device.0048": "放电低温保护",
1093
+ // "device.0049": "充电过流保护",
1094
+ // "device.0050": "放电过流保护",
1095
+ // "device.0051": "短路保护",
1096
+ // "device.0052": "前端检测IC错误",
1097
+ // "device.0053": "软件锁定MOS",
1098
+ // "device.0054": "充电MOS击穿",
1099
+ // "device.0055": "放电MOS击穿",
1100
+ // "device.00551": "PCB高温",
1101
+ const map = ['device.0041', 'device.0042', 'device.0043', 'device.0044', 'device.0045', 'device.0046', 'device.0047', 'device.0048', 'device.0049', 'device.0050', 'device.0051', 'device.0052', 'device.0053', 'device.0054', 'device.0055', 'device.00551'];
1102
+ let _arr = [];
1103
+ let _indexs = [];
1104
+ for (let i = 0; i < arr.length; i++) {
1105
+ if (arr[i] == 1) {
1106
+ _arr.push(map[i])
1107
+ _indexs.push(i)
1108
+ };
1109
+ }
1110
+ return { _arr, _indexs };
1111
+ }
1112
+
1113
+ // #region 解析履历详情
1114
+ /** 解析履历详情 */
1115
+ export const resolveDDA508 = (data) => {
1116
+ if (!data) return null;
1117
+ // DD 08 00 44 0002 0000 04 2F21 5B0009 4005 0000 0000
1118
+ // 983A0A000000990B980B0000000037022802020F
1119
+ // 0100350237023602350233023502330233023402
1120
+ // 32023202320233022F022802F90E77
1121
+ if (!data) return null;
1122
+ const hexArr = data.map((o) => o.toString(16).padStart(2, '0').toUpperCase());
1123
+ const f = (s) => parseInt(s, 16);
1124
+ const fet = data[40].toString(2).padStart(8, '0').split('').reverse();
1125
+ const protection = f(hexArr[23] + hexArr[22])
1126
+ .toString(2)
1127
+ .padStart(16, '0');
1128
+ const n = !!Number(fet[7]) ? 10 : 100; // bit7(1: 100mAh 0:10mAh)
1129
+ let current = (hexArr[16] << 8) + (hexArr[17] & 0x0ff);
1130
+ current = current > 32768 ? (current - 65536) / n : current / n;
1131
+ const electricity = Number(current.toFixed(2));
1132
+ const sysTime = (data[30] * 65536) + (data[31] * 256) + data[32]; // 系统时间 分钟数
1133
+ console.log('hexArr[30], hexArr[31], hexArr[32]', hexArr[30], hexArr[31], hexArr[32]);
1134
+
1135
+ console.warn('sysTime: ', sysTime);
1136
+ const reportTime = (data[11] * 65536) + (data[12] * 256) + data[13]; // 上报时间 分钟数
1137
+ console.log('data[11], data[12], data[13]', data[11], data[12], data[13]);
1138
+
1139
+ console.warn('reportTime: ', reportTime);
1140
+ const time = Math.abs(sysTime - reportTime) * 60 * 1000; // 时间差 毫秒数 相差的时间戳
1141
+ console.warn('time: ', time);
1142
+ const timestamp = new Date().getTime() - time; // 当前系统时间减去时间差,得到历史时间戳
1143
+ console.warn('new Date().getTime(): ', new Date().getTime());
1144
+ return {
1145
+ dataStr: hexArr.join(''),
1146
+ status: hex2string(data[2]),
1147
+ len: data[3],
1148
+ total: f(hexArr[4] + hexArr[5]), // 总页数
1149
+ page: f(hexArr[6] + hexArr[7]), // 当前页(从0开始)
1150
+ type: data[8], // 1放电开始 2充电开始 3电流停止 4保护 5告警 6定时
1151
+ sysTime: sysTime,
1152
+ timestamp, // 历史时间戳
1153
+ sumV: (f(hexArr[15] + hexArr[14]) / 100).toFixed(2), // 总电压,单位10mV=>V
1154
+ sumE: electricity, // 总电流,单位A
1155
+ restC: (f(hexArr[19] + hexArr[18]) / n).toFixed(2), // 剩余容量,单位Ah
1156
+ designC: (f(hexArr[21] + hexArr[20]) / n).toFixed(2), // 剩余容量,单位Ah
1157
+ protections: resolveProtections(protection)._arr, // 保护标志
1158
+ protectionIndex: resolveProtections(protection)._indexs, // 保护标志
1159
+ tH: ((f(hexArr[27] + hexArr[26]) - 2731) / 10).toFixed(2), // 最高温度
1160
+ tL: ((f(hexArr[29] + hexArr[28]) - 2731) / 10).toFixed(2), // 最低温度
1161
+ vH: (f(hexArr[35] + hexArr[34]) / 1000).toFixed(3), // 最高单体电压 mv=>V
1162
+ vL: (f(hexArr[37] + hexArr[36]) / 1000).toFixed(3), // 最低单体电压 mv=>V
1163
+ nH: f(hexArr[38]), // 最高单体编号
1164
+ nL: f(hexArr[39]), // 最低单体编号
1165
+ fet: fet.join(''),
1166
+ };
1167
+ }
1168
+ // #region 3B3C指令
1169
+ export const get3B3CInfo = ({ deviceId, macAddr, moduleType, productType }) => {
1170
+ return new Promise(async (resolve, reject) => {
1171
+ let reportData = { macAddr, moduleTypeKey: moduleType, productKey: productType };
1172
+ let _3b3c = null;
1173
+ let _dda5_03 = null;
1174
+ let _dda5_05 = null;
1175
+ let _ffaa_80 = null;
1176
+ try {
1177
+ if (!deviceId) throw new Error('deviceId required');
1178
+ // 发送FFAA_80
1179
+ const _ffaa_80_hex = await getFFAA80Async(deviceId, 'AT^VERSION?');
1180
+ _ffaa_80 = resolveFFAA80(_ffaa_80_hex);
1181
+ console.warn('Report-3B3C _ffaa_80 ', { _ffaa_80_hex, _ffaa_80 });
1182
+ if (_ffaa_80) reportData['moduleVersion'] = _ffaa_80?.moduleVersion;
1183
+
1184
+ // 发送3B3C
1185
+ const _3b3c_hex = await get3B3CAsync(deviceId);
1186
+ _3b3c = resolve3B3C(_3b3c_hex);
1187
+ console.warn('Report-3B3C _3b3c ', { _3b3c_hex, _3b3c });
1188
+ if (_3b3c) {
1189
+ reportData['cmdContent'] = _3b3c?.dataStr;
1190
+ } /*3B3C超时 or 不支持3B3C*/ else {
1191
+ // 发送DDA5_03
1192
+ const _dda5_03_hex = await getDDA503Async(deviceId);
1193
+ _dda5_03 = resolveDDA503(_dda5_03_hex);
1194
+ console.warn('Report-3B3C _dda5_03 ', { _dda5_03_hex, _dda5_03 });
1195
+ if (_dda5_03) reportData['bmsVersion'] = _dda5_03?.softwareV;
1196
+
1197
+ // 发送DDA5_05
1198
+ const _dda5_05_hex = await getDDA505Async(deviceId);
1199
+ _dda5_05 = resolveDDA505(_dda5_05_hex);
1200
+ console.warn('Report-3B3C _dda5_05 ', { _dda5_05_hex, _dda5_05 });
1201
+ if (_dda5_05) reportData['bmsSn'] = _dda5_05?.hardwareV;
1202
+ }
1203
+
1204
+ // try {
1205
+ // // 直接上报
1206
+ // const res = await serverApi.request({ url: serverApi.urls['report3B3C'], method: 'post', data: reportData }, 'json');
1207
+ // console.warn('Report-3B3C Report: ', { reportData, res });
1208
+ // } catch (error) {}
1209
+ resolve({ reportData, _3b3c, _dda5_03, _dda5_05, _ffaa_80 });
1210
+ } catch (error) {
1211
+ reject(error);
1212
+ }
1213
+ });
1214
+ }