/* Original copyright 2018 Benjamin Vedder benjamin@vedder.se and the VESC Tool project ( https://github.com/vedderb/vesc_tool ) Forked to: Copyright 2018 Danny Bokma github@diebie.nl (https://github.com/DieBieEngineering/DieBieMS-Tool) Now forked to: Copyright 2019 - 2020 Kevin Dionne kevin.dionne@ennoid.me (https://github.com/EnnoidMe/ENNOID-BMS-Tool) This file is part of ENNOID-BMS Tool. ENNOID-BMS Tool is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ENNOID-BMS Tool is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "commands.h" #include #include Commands::Commands(QObject *parent) : QObject(parent) { mSendCan = QSettings().value("send_can", false).toBool(); mCanId = QSettings().value("can_id", 0).toInt(); mIsLimitedMode = false; // Firmware state mFirmwareIsUploading = false; mFirmwareState = 0; mFimwarePtr = 0; mFirmwareTimer = 0; mFirmwareRetries = 0; mFirmwareUploadStatus = "FW Upload Status"; mCheckNextbmsConfig = false; mTimer = new QTimer(this); mTimer->setInterval(10); mTimer->start(); mbmsConfig = 0; mTimeoutCount = 50; mTimeoutFwVer = 0; mTimeoutBMSconf = 0; mTimeoutValues = 0; mTimeoutCells = 0; mTimeoutAux = 0; mTimeoutExp = 0; mTimeoutPingCan = 0; connect(mTimer, SIGNAL(timeout()), this, SLOT(timerSlot())); } void Commands::setLimitedMode(bool is_limited) { mIsLimitedMode = is_limited; } bool Commands::isLimitedMode() { return mIsLimitedMode; } void Commands::setSendCan(bool sendCan, int id) { mSendCan = sendCan; QSettings().setValue("send_can", sendCan); if (id >= 0) { setCanSendId(id); } } bool Commands::getSendCan() { return mSendCan; } void Commands::setCanSendId(unsigned int id) { mCanId = id; QSettings().setValue("can_id", id); } int Commands::getCanSendId() { return mCanId; } void Commands::processPacket(QByteArray data) { VByteArray vb(data); COMM_PACKET_ID id = (COMM_PACKET_ID)vb.vbPopFrontUint8(); switch (id) { case COMM_FW_VERSION: { mTimeoutFwVer = 0; int fw_major; int fw_minor; QString hw; QByteArray uuid; if (vb.size() >= 2) { fw_major = vb.vbPopFrontInt8(); fw_minor = vb.vbPopFrontInt8(); hw = vb.vbPopFrontString(); if (vb.size() >= 12) { uuid.append(vb.left(12)); vb.remove(0, 12); } } else { fw_major = -1; fw_minor = -1; } emit fwVersionReceived(fw_major, fw_minor, hw, uuid); } break; case COMM_ERASE_NEW_APP: case COMM_WRITE_NEW_APP_DATA: firmwareUploadUpdate(!vb.at(0)); break; case COMM_GET_VALUES: { mTimeoutValues = 0; BMS_VALUES values; values.packVoltage = vb.vbPopFrontDouble32(1e3); values.packCurrent = vb.vbPopFrontDouble32(1e3); values.soC = vb.vbPopFrontUint8(); values.cVHigh = vb.vbPopFrontDouble32(1e3); values.cVAverage = vb.vbPopFrontDouble32(1e3); values.cVLow = vb.vbPopFrontDouble32(1e3); values.cVMisMatch = vb.vbPopFrontDouble32(1e3); values.loadLCVoltage = vb.vbPopFrontDouble16(1e1); values.loadLCCurrent = vb.vbPopFrontDouble16(1e1); values.chargerVoltage = vb.vbPopFrontDouble16(1e1); values.tempBattHigh = vb.vbPopFrontDouble16(1e1); values.tempBattAverage = vb.vbPopFrontDouble16(1e1); values.tempBattLow = vb.vbPopFrontDouble16(1e1); values.tempBMSHigh = vb.vbPopFrontDouble16(1e1); values.tempBMSAverage = vb.vbPopFrontDouble16(1e1); values.tempBMSLow = vb.vbPopFrontDouble16(1e1); values.humidity = vb.vbPopFrontDouble16(1e1); values.opState = opStateToStr((OperationalStateTypedef)vb.vbPopFrontUint8()); values.balanceActive = vb.vbPopFrontUint8(); values.faultState = faultStateToStr((bms_fault_code)vb.vbPopFrontUint8()); emit valuesReceived(values); } break; case COMM_GET_BMS_CELLS:{ mTimeoutCells = 0; int mCellAmount; QVector mCellVoltages; mCellVoltages.clear(); mCellAmount = vb.vbPopFrontUint8(); for(int cellValuePointer = 0; cellValuePointer < mCellAmount; cellValuePointer++){ mCellVoltages.append(vb.vbPopFrontDouble16(1e3)); } emit cellsReceived(mCellAmount,mCellVoltages); } break; case COMM_GET_BMS_AUX:{ mTimeoutAux = 0; int mAuxAmount; QVector mAuxVoltages; mAuxVoltages.clear(); mAuxAmount = vb.vbPopFrontUint8(); for(int auxValuePointer = 0; auxValuePointer < mAuxAmount; auxValuePointer++){ mAuxVoltages.append(vb.vbPopFrontDouble16(1e1)); } emit auxReceived(mAuxAmount,mAuxVoltages); } break; case COMM_GET_BMS_EXP_TEMP:{ mTimeoutExp = 0; int mExpTempAmount; QVector mExpTempVoltages; mExpTempVoltages.clear(); mExpTempAmount = vb.vbPopFrontUint8(); for(int expTempValuePointer = 0; expTempValuePointer < mExpTempAmount; expTempValuePointer++){ mExpTempVoltages.append(vb.vbPopFrontDouble16(1e1)); } emit expTempReceived(mExpTempAmount,mExpTempVoltages); } break; case COMM_PRINT: emit printReceived(QString::fromLatin1(vb)); break; case COMM_ROTOR_POSITION: emit rotorPosReceived(vb.vbPopFrontDouble32(1e5)); break; case COMM_GET_MCCONF: case COMM_GET_MCCONF_DEFAULT: mTimeoutBMSconf = 0; if (mbmsConfig) { mbmsConfig->deSerialize(vb); mbmsConfig->updateDone(); if (mCheckNextbmsConfig) { mCheckNextbmsConfig = false; emit bmsConfigCheckResult(mbmsConfig->checkDifference(&mbmsConfigLast)); } } break; case COMM_SET_MCCONF: emit ackReceived("BMS Write OK"); break; case COMM_STORE_BMS_CONF: if (mbmsConfig) { mbmsConfig->storingDone(); } break; case COMM_PING_CAN: { mTimeoutPingCan = 0; QVector devs; while(vb.size() > 0) { devs.append(vb.vbPopFrontUint8()); } emit pingCanRx(devs, false); } break; default: break; } } void Commands::getFwVersion() { if (mTimeoutFwVer > 0) { return; } mTimeoutFwVer = mTimeoutCount; VByteArray vb; vb.vbAppendInt8(COMM_FW_VERSION); emitData(vb); } void Commands::getValues() { if (mTimeoutValues > 0) { return; } mTimeoutValues = mTimeoutCount; VByteArray vb; vb.vbAppendInt8(COMM_GET_VALUES); emitData(vb); } void Commands::getCells() { if (mTimeoutCells > 0) { return; } mTimeoutCells = mTimeoutCount; VByteArray vb; vb.vbAppendInt8(COMM_GET_BMS_CELLS); emitData(vb); } void Commands::getAux() { if (mTimeoutAux > 0) { return; } mTimeoutAux = mTimeoutCount; VByteArray vb; vb.vbAppendInt8(COMM_GET_BMS_AUX); emitData(vb); } void Commands::getExpansionTemp() { if (mTimeoutExp > 0) { return; } mTimeoutExp = mTimeoutCount; VByteArray vb; vb.vbAppendInt8(COMM_GET_BMS_EXP_TEMP); emitData(vb); } void Commands::sendTerminalCmd(QString cmd) { VByteArray vb; vb.vbAppendInt8(COMM_TERMINAL_CMD); vb.append(cmd.toLatin1()); emitData(vb); } void Commands::setDetect(disp_pos_mode mode) { VByteArray vb; vb.vbAppendInt8(COMM_SET_DETECT); vb.vbAppendInt8(mode); emitData(vb); } void Commands::samplePrint(debug_sampling_mode mode, int sample_len, int dec) { VByteArray vb; vb.vbAppendInt8(COMM_SAMPLE_PRINT); vb.vbAppendInt8(mode); vb.vbAppendUint16(sample_len); vb.vbAppendUint8(dec); emitData(vb); } void Commands::getBMSconf() { if (mTimeoutBMSconf > 0) { return; } mTimeoutBMSconf = mTimeoutCount; mCheckNextbmsConfig = false; VByteArray vb; vb.vbAppendInt8(COMM_GET_MCCONF); emitData(vb); } void Commands::getBMSconfDefault() { if (mTimeoutBMSconf > 0) { return; } mTimeoutBMSconf = mTimeoutCount; mCheckNextbmsConfig = false; VByteArray vb; vb.vbAppendInt8(COMM_GET_MCCONF_DEFAULT); emitData(vb); } void Commands::setBMSconf(bool check) { if (mbmsConfig) { mbmsConfigLast = *mbmsConfig; VByteArray vb; vb.vbAppendInt8(COMM_SET_MCCONF); mbmsConfig->serialize(vb); emitData(vb); if (check) { checkbmsConfig(); } } } void Commands::reboot() { VByteArray vb; vb.vbAppendInt8(COMM_REBOOT); emitData(vb); } void Commands::sendAlive() { VByteArray vb; vb.vbAppendInt8(COMM_ALIVE); emitData(vb); } void Commands::pairNrf(int ms) { VByteArray vb; vb.vbAppendInt8(COMM_NRF_START_PAIRING); vb.vbAppendInt32(ms); emitData(vb); } void Commands::timerSlot() { if (mFirmwareIsUploading) { if (mFirmwareTimer) { mFirmwareTimer--; } else { firmwareUploadUpdate(true); } } if (mTimeoutFwVer > 0) mTimeoutFwVer--; if (mTimeoutBMSconf > 0) mTimeoutBMSconf--; if (mTimeoutValues > 0) mTimeoutValues--; if (mTimeoutCells > 0) mTimeoutCells--; if (mTimeoutAux > 0) mTimeoutAux--; if (mTimeoutExp > 0) mTimeoutExp--; } void Commands::emitData(QByteArray data) { // Only allow firmware commands in limited mode if (mIsLimitedMode && data.at(0) > COMM_WRITE_NEW_APP_DATA) { return; } if (mSendCan) { data.prepend((char)mCanId); data.prepend((char)COMM_FORWARD_CAN); } emit dataToSend(data); } void Commands::firmwareUploadUpdate(bool isTimeout) { if (!mFirmwareIsUploading) { return; } const int app_packet_size = 200; const int retries = 5; const int timeout = 350; if (mFirmwareState == 0) { mFirmwareUploadStatus = "Buffer Erase"; if (isTimeout) { // Erase timed out, abort. mFirmwareIsUploading = false; mFimwarePtr = 0; mFirmwareUploadStatus = "Buffer Erase Timeout"; } else { mFirmwareState++; mFirmwareRetries = retries; mFirmwareTimer = timeout; firmwareUploadUpdate(true); } } else if (mFirmwareState == 1) { mFirmwareUploadStatus = "CRC/Size Write"; if (isTimeout) { if (mFirmwareRetries > 0) { mFirmwareRetries--; mFirmwareTimer = timeout; } else { mFirmwareIsUploading = false; mFimwarePtr = 0; mFirmwareState = 0; mFirmwareUploadStatus = "CRC/Size Write Timeout"; return; } quint16 crc = Packet::crc16((const unsigned char*)mNewFirmware.constData(), mNewFirmware.size()); VByteArray vb; vb.append((char)COMM_WRITE_NEW_APP_DATA); vb.vbAppendUint32(0); vb.vbAppendUint32(mNewFirmware.size()); vb.vbAppendUint16(crc); emitData(vb); } else { mFirmwareState++; mFirmwareRetries = retries; mFirmwareTimer = timeout; firmwareUploadUpdate(true); } } else if (mFirmwareState == 2) { mFirmwareUploadStatus = "FW Data Write"; if (isTimeout) { if (mFirmwareRetries > 0) { mFirmwareRetries--; mFirmwareTimer = timeout; } else { mFirmwareIsUploading = false; mFimwarePtr = 0; mFirmwareState = 0; mFirmwareUploadStatus = "FW Data Write Timeout"; return; } int fw_size_left = mNewFirmware.size() - mFimwarePtr; int send_size = fw_size_left > app_packet_size ? app_packet_size : fw_size_left; VByteArray vb; vb.append((char)COMM_WRITE_NEW_APP_DATA); if (mFirmwareIsBootloader) { vb.vbAppendUint32(mFimwarePtr + (1024 * 2 * 50)); } else { vb.vbAppendUint32(mFimwarePtr + 6); } vb.append(mNewFirmware.mid(mFimwarePtr, send_size)); emitData(vb); } else { mFirmwareRetries = retries; mFirmwareTimer = timeout; mFimwarePtr += app_packet_size; if (mFimwarePtr >= mNewFirmware.size()) { mFirmwareIsUploading = false; mFimwarePtr = 0; mFirmwareState = 0; mFirmwareUploadStatus = "FW Upload Done"; // Upload done. Enter bootloader! if (!mFirmwareIsBootloader) { QByteArray buffer; buffer.append((char)COMM_JUMP_TO_BOOTLOADER); emitData(buffer); } } else { firmwareUploadUpdate(true); } } } } QString Commands::opStateToStr(OperationalStateTypedef fault) { switch (fault) { case OP_STATE_INIT: return "Init"; case OP_STATE_CHARGING: return "Charging"; case OP_STATE_PRE_CHARGE: return "PreCharging"; case OP_STATE_LOAD_ENABLED: return "Load enabled"; case OP_STATE_BATTERY_DEAD: return "Battery dead"; case OP_STATE_POWER_DOWN: return "Power down"; case OP_STATE_EXTERNAL: return "External"; case OP_STATE_ERROR: return "Error"; case OP_STATE_ERROR_PRECHARGE: return "ErrPreCharge"; case OP_STATE_BALANCING: return "Balancing"; case OP_STATE_CHARGED: return "Charged"; case OP_STATE_FORCEON: return "ForceOn"; default: return "Unknown fault"; } } QString Commands::faultStateToStr(bms_fault_code fault) { switch (fault) { case FAULT_CODE_NONE: return "System OK"; case FAULT_CODE_PACK_OVER_VOLTAGE: return "Pack overvoltage"; case FAULT_CODE_PACK_UNDER_VOLTAGE: return "Pack undervoltage"; case FAULT_CODE_LOAD_OVER_VOLTAGE: return "Load overvoltage"; case FAULT_CODE_LOAD_UNDER_VOLTAGE: return "Load undervoltage"; case FAULT_CODE_CHARGER_OVER_VOLTAGE: return "Charger overvoltage"; case FAULT_CODE_CHARGER_UNDER_VOLTAGE: return "Charger undervoltgae"; case FAULT_CODE_CELL_HARD_OVER_VOLTAGE: return "Cell hard overvoltage"; case FAULT_CODE_CELL_HARD_UNDER_VOLTAGE: return "Cell hard undervoltage"; case FAULT_CODE_CELL_SOFT_OVER_VOLTAGE: return "Cell soft overvoltage"; case FAULT_CODE_CELL_SOFT_UNDER_VOLTAGE: return "Cell soft undervoltage"; case FAULT_CODE_MAX_UVP_OVP_ERRORS: return "MAX OVP/UVP errors"; case FAULT_CODE_MAX_UVT_OVT_ERRORS: return "MAX OVT/UVT errors"; case FAULT_CODE_OVER_CURRENT: return "Over current"; case FAULT_CODE_OVER_TEMP_BMS: return "Over temp BMS"; case FAULT_CODE_UNDER_TEMP_BMS: return "Under temp BMS"; case FAULT_CODE_DISCHARGE_OVER_TEMP_CELLS: return "Discharge over temp cell"; case FAULT_CODE_DISCHARGE_UNDER_TEMP_CELLS: return "Discharge under temp cell"; case FAULT_CODE_CHARGE_OVER_TEMP_CELLS: return "Charge over temp cell"; case FAULT_CODE_CHARGE_UNDER_TEMP_CELLS: return "Charge under temp cell"; case FAULT_CODE_PRECHARGE_TIMEOUT: return "Precharge timeout"; case FAULT_CODE_DISCHARGE_RETRY: return "Discharge retry"; case FAULT_CODE_CHARGE_RETRY: return "Charge retry"; case FAULT_CODE_CHARGER_DISCONNECT: return "Charge retry"; default: return "Unknown fault"; } } void Commands::setbmsConfig(ConfigParams *bmsConfig) { mbmsConfig = bmsConfig; connect(mbmsConfig, SIGNAL(updateRequested()), this, SLOT(getBMSconf())); connect(mbmsConfig, SIGNAL(updateRequestDefault()), this, SLOT(getBMSconfDefault())); } void Commands::startFirmwareUpload(QByteArray &newFirmware, bool isBootloader) { mFirmwareIsBootloader = isBootloader; mFirmwareIsUploading = true; mFirmwareState = mFirmwareIsBootloader ? 2 : 0; mFimwarePtr = 0; mFirmwareTimer = 500; mFirmwareRetries = 5; mNewFirmware.clear(); mNewFirmware.append(newFirmware); mFirmwareUploadStatus = "Buffer Erase"; if (mFirmwareIsBootloader) { firmwareUploadUpdate(true); } else { VByteArray vb; vb.vbAppendInt8(COMM_ERASE_NEW_APP); vb.vbAppendUint32(mNewFirmware.size()); emitData(vb); } } double Commands::getFirmwareUploadProgress() { if (mFirmwareIsUploading) { return (double)mFimwarePtr / (double)mNewFirmware.size(); } else { return -1.0; } } QString Commands::getFirmwareUploadStatus() { return mFirmwareUploadStatus; } void Commands::cancelFirmwareUpload() { if (mFirmwareIsUploading) { mFirmwareIsUploading = false; mFimwarePtr = 0; mFirmwareState = 0; mFirmwareUploadStatus = "Cancelled"; } } void Commands::checkbmsConfig() { mCheckNextbmsConfig = true; VByteArray vb; vb.vbAppendInt8(COMM_GET_MCCONF); emitData(vb); } void Commands::storeBMSConfig() { VByteArray vb; vb.vbAppendInt8(COMM_STORE_BMS_CONF); emitData(vb); } void Commands::emitEmptyValues() { BMS_VALUES values; values.packVoltage = 0.0; values.packCurrent = 0.0; values.soC = 0.0; values.cVHigh = 0.0; values.cVAverage = 0.0; values.cVLow = 0.0; values.cVMisMatch = 0.0; values.loadLCVoltage = 0.0; values.loadLCCurrent = 0.0; values.loadHCVoltage = 0.0; values.loadHCCurrent = 0.0; values.chargerVoltage = 0.0; values.auxVoltage = 0.0; values.auxCurrent = 0.0; values.tempBattHigh = 0.0; values.tempBattAverage = 0.0; values.tempBattLow = 0.0; values.tempBMSHigh = 0.0; values.tempBMSAverage = 0.0; values.tempBMSLow = 0.0; values.humidity = 0.0; values.opState = "Unknown"; values.balanceActive = 0.0; values.faultState = "Unknown"; emit valuesReceived(values); } void Commands::emitEmptySetupValues() { BMS_VALUES values; values.packVoltage = 0.0; values.packCurrent = 0.0; values.soC = 0.0; values.cVHigh = 0.0; values.cVAverage = 0.0; values.cVLow = 0.0; values.cVMisMatch = 0.0; values.loadLCVoltage = 0.0; values.loadLCCurrent = 0.0; values.loadHCVoltage = 0.0; values.loadHCCurrent = 0.0; values.chargerVoltage = 0.0; values.auxVoltage = 0.0; values.auxCurrent = 0.0; values.tempBattHigh = 0.0; values.tempBattAverage = 0.0; values.tempBattLow = 0.0; values.tempBMSHigh = 0.0; values.tempBMSAverage = 0.0; values.tempBMSLow = 0.0; values.opState = OP_STATE_INIT; values.balanceActive = 1; values.faultState = FAULT_CODE_NONE; emit valuesSetupReceived(values); } void Commands::pingCan() { if (mTimeoutPingCan > 0) { return; } mTimeoutPingCan = 500; VByteArray vb; vb.vbAppendInt8(COMM_PING_CAN); emitData(vb); }