const readServiceID = '0000FFE0-0000-1000-8000-00805F9B34FB'; const readID = '0000FFE4-0000-1000-8000-00805F9B34FB'; const writeServiceID = '0000FFE5-0000-1000-8000-00805F9B34FB'; const writeID = '0000FFE9-0000-1000-8000-00805F9B34FB'; var readPart = ''; function acceptDevice(device) { return device.btid ? true : false; } function isDevice(device, data) { const advertisData = new Uint8Array(data.advertisData); const mac = device.btid .split('') .map((p, i) => parseInt(p + device.btid[i + 1], 16)) .filter((p, i) => i % 2 == 0); if (advertisData.slice(0, 2).toString() == [0x52, 0x53].toString() && advertisData.slice(4, 10).toString() == mac.toString()) { return true; } return false; } function readDataBP00(value, data) { if (value.split('HSO').length == 2) { data.imei = value.split('HSO')[0]; value = value.split('HSO')[1]; while (value != '') { switch (value[0]) { case 'P': data.quantity = parseInt(value.slice(1, 3)); value = value.slice(3, value.length); break; case 'S': data.gpscount = parseInt(value.slice(1, 3), 16); value = value.slice(3, value.length); break; case 'G': data.gsmlevel = parseInt(value.slice(1, 3), 16); value = value.slice(3, value.length); break; case 'T': data.temp = parseInt(value.slice(1, 5), 16) < 0x8000 ? parseInt(value.slice(1, 5), 16) : parseInt(value.slice(1, 5), 16) - 0x10000; value = value.slice(5, value.length); break; case 'C': data.cycle = parseInt(value.slice(1, 5), 16); value = value.slice(5, value.length); break; case 'B': var state = parseInt(value.slice(1, 3), 16); data.lowTempProtectState = (state >> 0) & 1; data.highTempProtectState = (state >> 1) & 1; data.dischargeProtectState = (state >> 2) & 1; data.chargeProtectState = (state >> 3) & 1; data.lowVoltageState = (state >> 4) & 1; data.chargeState = (state >> 5) & 3; data.oilState = (state >> 7) & (1 == 0) ? 1 : 0; value = value.slice(3, value.length); break; case 'X': var state = parseInt(value.slice(1, 3), 16); data.autoProtectState = (state >> 0) & 1; data.elecState = (state >> 1) & (1 == 0) ? 1 : 0; data.factoryType = state >> 2; value = value.slice(3, value.length); break; default: if (['P', 'S', 'G', 'T', 'C', 'B', 'X'].indexOf(value[3]) == -1) { //data.voltage = parseInt(value.slice(0, 4), 16) / 100 value = value.slice(4, value.length); } else { //data.voltage = parseInt(value.slice(0, 3), 16) / 100 value = value.slice(3, value.length); } } } return data; } return false; } function readDataBS50(value, data) { const length = parseInt(value.slice(0, 4), 16); value = value.slice(4, value.length); if (value.length == length * 2) { return BMSReply( value .split('') .map((p, i) => parseInt(p + value[i + 1], 16)) .filter((p, i) => i % 2 == 0), data ); } return false; } function BMSReply(value, data) { if (value[0] == 0x4e && value[1] == 0x57) { //value = value.slice(2, value.length) const length = value[2] * 0x100 + value[3]; if (value.length == length + 2) { data.bmsid = value .slice(4, 8) .map((p) => ('00' + p.toString(16)).substr(p.toString(16).length)) .join(''); if ( value[8] == 0x06 && value[value.length - 5] == 0x68 && value.slice(0, value.length - 4).reduce((p, c) => p + c) == value[value.length - 2] * 0x100 + value[value.length - 1] ) { value = value.slice(11, value.length - 9); while (value.length != 0) { switch (value[0]) { case 0x79: const voltageLength = parseInt(value[1] / 3); data.voltageList = Array(voltageLength); let maxVoltage = -1; let minVoltage = 99999; let sumVoltage = 0; for (var i = 0; i < voltageLength; i++) { let _v = Math.round(value[i * 3 + 3] * 0x100 + value[i * 3 + 4]) / 1000; data.voltageList[value[i * 3 + 2] - 1] = _v; sumVoltage += _v; if (_v > maxVoltage) maxVoltage = _v; if (_v < minVoltage) minVoltage = _v; } data.maxVoltage = maxVoltage; data.minVoltage = minVoltage; data.sumVoltage = sumVoltage; value = value.slice(voltageLength * 3 + 2, value.length); break; case 0x80: data.powerMOSTemp = value[1] * 0x100 + value[2]; if (data.powerMOSTemp > 100) data.powerMOSTemp = 100 - data.powerMOSTemp; value = value.slice(3, value.length); break; case 0x81: data.batteryChamberTemp = value[1] * 0x100 + value[2]; if (data.batteryChamberTemp > 100) data.batteryChamberTemp = 100 - data.batteryChamberTemp; value = value.slice(3, value.length); break; case 0x82: data.temp = value[1] * 0x100 + value[2]; if (data.temp > 100) data.temp = 100 - data.temp; value = value.slice(3, value.length); break; case 0x83: data.voltage = Math.round(value[1] * 0x100 + value[2]) / 100; value = value.slice(3, value.length); break; case 0x84: data.chargeState = 0; _value = value[1] * 0x100 + value[2]; if (_value > 10000) { data.current = (_value - 10000) * 0.01 * -1; data.chargeState = 2; } else if (_value < 10000) { data.current = (10000 - _value) * 0.01; data.chargeState = 1; } value = value.slice(3, value.length); break; case 0x85: data.quantity = value[1]; data.soc = value[1]; value = value.slice(2, value.length); break; case 0x86: data.tempCount = value[1]; value = value.slice(2, value.length); break; case 0x87: data.cycle = value[1] * 0x100 + value[2]; value = value.slice(3, value.length); break; case 0x89: data.totalCirculatingCapacity = value[1] * 0x1000000 + value[2] * 0x10000 + value[3] * 0x100 + value[4]; value = value.slice(5, value.length); break; case 0x8a: data.count = value[1] * 0x100 + value[2]; value = value.slice(3, value.length); break; case 0x8b: data.alarmState = []; if (((value[2] >> 0) & 1) == 1) data.alarmState.push('低容量报警'); if (((value[2] >> 1) & 1) == 1) data.alarmState.push('MOS管超温报警'); if (((value[2] >> 2) & 1) == 1) data.alarmState.push('充电过压报警'); if (((value[2] >> 3) & 1) == 1) data.alarmState.push('放电欠压报警'); if (((value[2] >> 4) & 1) == 1) data.alarmState.push('电池超温报警'); if (((value[2] >> 5) & 1) == 1) data.alarmState.push('充电过流报警'); if (((value[2] >> 6) & 1) == 1) data.alarmState.push('放电过流报警'); if (((value[2] >> 7) & 1) == 1) data.alarmState.push('电芯压差报警'); if (((value[1] >> 0) & 1) == 1) data.alarmState.push('电池箱内超温报警'); if (((value[1] >> 1) & 1) == 1) data.alarmState.push('电池低温报警'); if (((value[1] >> 2) & 1) == 1) data.alarmState.push('单体过压报警'); if (((value[1] >> 3) & 1) == 1) data.alarmState.push('单体欠压报警'); if (((value[1] >> 4) & 1) == 1) data.alarmState.push('309_A保护'); if (((value[1] >> 5) & 1) == 1) data.alarmState.push('309_B保护'); value = value.slice(3, value.length); break; case 0x8c: data.chargeProtectState = (value[2] >> 0) & 1; data.dischargeProtectState = (value[2] >> 1) & 1; data.equilibrium = (value[2] >> 2) & 1; data.disconnectState = (value[2] >> 3) & 1; value = value.slice(3, value.length); break; case 0x8e: value = value.slice(3, value.length); break; case 0x8f: value = value.slice(3, value.length); break; case 0x90: value = value.slice(3, value.length); break; case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: value = value.slice(3, value.length); break; case 0x9d: value = value.slice(2, value.length); break; case 0x9e: case 0x9f: case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7: case 0xa8: value = value.slice(3, value.length); break; case 0xa9: value = value.slice(2, value.length); break; case 0xaa: // 电池容量 data.capacity = (value[1] << 24) | (value[2] << 16) | (value[3] << 8) | value[4]; value = value.slice(5, value.length); break; case 0xab: case 0xac: value = value.slice(2, value.length); break; case 0xad: value = value.slice(3, value.length); break; case 0xae: case 0xaf: value = value.slice(2, value.length); break; case 0xb0: value = value.slice(3, value.length); break; case 0xb1: value = value.slice(2, value.length); break; case 0xb2: value = value.slice(11, value.length); break; case 0xb3: value = value.slice(2, value.length); break; case 0xb4: value = value.slice(9, value.length); break; case 0xb5: value = value.slice(5, value.length); break; case 0xb6: value = value.slice(5, value.length); break; case 0xb7: // 软件版本号 15bit data.firmware = ''; for (let i = 1; i <= 15; i++) { data.firmware += String.fromCharCode(value[i]); } value = value.slice(16, value.length); break; case 0xb8: value = value.slice(2, value.length); break; case 0xb9: value = value.slice(5, value.length); break; case 0xba: value = value.slice(25, value.length); break; default: value = []; } } } return data; } } return false; } function readData(device, value, data) { value = Array.from(new Int8Array(value)) .map((p) => String.fromCharCode(p)) .join(''); if (value[value.length - 1] == ')') { value = readPart.concat(value); readPart = ''; } else if (value[0] == '(') { readPart = value; } else { readPart = readPart.concat(value); } if (value[0] == '(' && value[value.length - 1] == ')') { value = value.slice(1, value.length - 1); if (value.slice(0, 12) == device.mac_id) { value = value.slice(12, value.length); switch (value.slice(0, 4)) { case 'BP00': return readDataBP00(value.slice(4, value.length), data); case 'BS50': return readDataBS50(value.slice(4, value.length), data); } } } return false; } /** * cmd: 命令字 02:写 */ function BMSCommand(data, cmd, type = 0) { // 帧来源: 01 // 传输类型: 00 const length = data.length + 18; var data = [0x4e, 0x57, parseInt(length / 0x100), parseInt(length % 0x100), 0x00, 0x00, 0x00, 0x00, cmd, 0x01, type].concat(data).concat([0x00, 0x00, 0x00, 0x00, 0x68]); const sum = data.reduce((p, c) => p + c); data.push(parseInt(sum / 0x1000000)); data.push(parseInt((sum % 0x1000000) / 0x10000)); data.push(parseInt((sum % 0x10000) / 0x100)); data.push(parseInt(sum % 0x100)); console.log(data); return data; } function BMSRead() { return BMSCommand([0x00], 0x06, 0); } function BMSTurnOn() { return BMSCommand([0xac, 1], 0x02, 1); } function BMSTurnOff() { return BMSCommand([0xac, 0], 0x02, 1); } function bmsChargingMOS(value) { return BMSCommand([0xab, value], 0x02, 0); } function bmsDischargeMOS(value) { return BMSCommand([0xac, value], 0x02, 0); } function sendData(device, cmd, data = []) { return ( '(' + device.mac_id + cmd + data .map((p) => ('00' + p.toString(16)).substr(p.toString(16).length)) .join('') .toUpperCase() + ')' ) .split('') .map((p) => p.charCodeAt(0)); } function sendDataAE00(device, data) { return sendData(device, 'AE00', [data.length % 0x100, parseInt(data.length / 0x100)].concat(data)); } function stateUpdate(device, deviceId) { return [sendData(device, 'AU20'), sendDataAE00(device, BMSRead())]; } function turnOn(device, deviceId) { return [sendDataAE00(device, BMSTurnOn())]; } function turnOff(device, deviceId) { return [sendDataAE00(device, BMSTurnOff())]; } function bmsInfo(device, deviceId, info) { return false; } function bmsSet(device, deviceId, name, vars) { return false; } module.exports = { readServiceID: readServiceID, readID: readID, writeServiceID: writeServiceID, writeID: writeID, acceptDevice: acceptDevice, isDevice: isDevice, readData: readData, stateUpdate: stateUpdate, turnOn: turnOn, turnOff: turnOff, bmsInfo: bmsInfo, bmsSet: bmsSet, BMSCommand: BMSCommand, BMSRead: BMSRead, BMSTurnOn: BMSTurnOn, BMSTurnOff: BMSTurnOff, BMSReply: BMSReply, bmsChargingMOS: bmsChargingMOS, bmsDischargeMOS: bmsDischargeMOS };