[
  {
    "path": "BMSModule.cpp",
    "content": "#include \"config.h\"\n#include \"BMSModule.h\"\n#include \"BMSUtil.h\"\n#include \"Logger.h\"\n\nextern EEPROMSettings settings;\n\nBMSModule::BMSModule()\n{\n    for (int i = 0; i < 6; i++)\n    {\n        cellVolt[i] = 0.0f;\n        lowestCellVolt[i] = 5.0f;\n        highestCellVolt[i] = 0.0f;\n        balanceState[i] = 0;\n    }\n    moduleVolt = 0.0f;\n    temperatures[0] = 0.0f;\n    temperatures[1] = 0.0f;\n    lowestTemperature = 200.0f;\n    highestTemperature = -100.0f;\n    lowestModuleVolt = 200.0f;\n    highestModuleVolt = 0.0f;\n    exists = false;\n    moduleAddress = 0;\n    goodPackets = 0;\n    badPackets = 0;\n}\n\n/*\nReading the status of the board to identify any flags, will be more useful when implementing a sleep cycle\n*/\nvoid BMSModule::readStatus()\n{\n  uint8_t payload[3];\n  uint8_t buff[8];\n  payload[0] = moduleAddress << 1; //adresss\n  payload[1] = REG_ALERT_STATUS;//Alert Status start\n  payload[2] = 0x04;\n  BMSUtil::sendDataWithReply(payload, 3, false, buff, 7);\n  alerts = buff[3];\n  faults = buff[4];\n  COVFaults = buff[5];\n  CUVFaults = buff[6];\n}\n\nuint8_t BMSModule::getFaults()\n{\n    return faults;\n}\n\nuint8_t BMSModule::getAlerts()\n{\n    return alerts;\n}\n\nuint8_t BMSModule::getCOVCells()\n{\n    return COVFaults;\n}\n\nuint8_t BMSModule::getCUVCells()\n{\n    return CUVFaults;\n}\n\n/*\nReading the setpoints, after a reset the default tesla setpoints are loaded\nDefault response : 0x10, 0x80, 0x31, 0x81, 0x08, 0x81, 0x66, 0xff\n*/\n/*\nvoid BMSModule::readSetpoint()\n{\n  uint8_t payload[3];\n  uint8_t buff[12];\n  payload[0] = moduleAddress << 1; //adresss\n  payload[1] = 0x40;//Alert Status start\n  payload[2] = 0x08;//two registers\n  sendData(payload, 3, false);\n  delay(2);\n  getReply(buff);\n\n  OVolt = 2.0+ (0.05* buff[5]);\n  UVolt = 0.7 + (0.1* buff[7]);\n  Tset = 35 + (5 * (buff[9] >> 4));\n} */\n\nbool BMSModule::readModuleValues()\n{\n    uint8_t payload[4];\n    uint8_t buff[50];\n    uint8_t calcCRC;\n    bool retVal = false;\n    int retLen;\n    float tempCalc;\n    float tempTemp;\n\n    payload[0] = moduleAddress << 1;\n\n    readStatus();\n    Logger::debug(\"Module %i   alerts=%X   faults=%X   COV=%X   CUV=%X\", moduleAddress, alerts, faults, COVFaults, CUVFaults);\n\n    payload[1] = REG_ADC_CTRL;\n    payload[2] = 0b00111101; //ADC Auto mode, read every ADC input we can (Both Temps, Pack, 6 cells)\n    BMSUtil::sendDataWithReply(payload, 3, true, buff, 3);\n\n    payload[1] = REG_IO_CTRL;\n    payload[2] = 0b00000011; //enable temperature measurement VSS pins\n    BMSUtil::sendDataWithReply(payload, 3, true, buff, 3);\n\n    payload[1] = REG_ADC_CONV; //start all ADC conversions\n    payload[2] = 1;\n    BMSUtil::sendDataWithReply(payload, 3, true, buff, 3);\n\n    payload[1] = REG_GPAI; //start reading registers at the module voltage registers\n    payload[2] = 0x12; //read 18 bytes (Each value takes 2 - ModuleV, CellV1-6, Temp1, Temp2)\n    retLen = BMSUtil::sendDataWithReply(payload, 3, false, buff, 22);\n\n    calcCRC = BMSUtil::genCRC(buff, retLen-1);\n    Logger::debug(\"Sent CRC: %x     Calculated CRC: %x\", buff[21], calcCRC);\n\n    //18 data bytes, address, command, length, and CRC = 22 bytes returned\n    //Also validate CRC to ensure we didn't get garbage data.\n    if ( (retLen == 22) && (buff[21] == calcCRC) )\n    {\n        if (buff[0] == (moduleAddress << 1) && buff[1] == REG_GPAI && buff[2] == 0x12) //Also ensure this is actually the reply to our intended query\n        {\n            //payload is 2 bytes gpai, 2 bytes for each of 6 cell voltages, 2 bytes for each of two temperatures (18 bytes of data)\n            moduleVolt = (buff[3] * 256 + buff[4]) * 0.002034609f;\n            if (moduleVolt > highestModuleVolt) highestModuleVolt = moduleVolt;\n            if (moduleVolt < lowestModuleVolt) lowestModuleVolt = moduleVolt;            \n            for (int i = 0; i < 6; i++) \n            {\n                cellVolt[i] = (buff[5 + (i * 2)] * 256 + buff[6 + (i * 2)]) * 0.000381493f;\n                if (lowestCellVolt[i] > cellVolt[i]) lowestCellVolt[i] = cellVolt[i];\n                if (highestCellVolt[i] < cellVolt[i]) highestCellVolt[i] = cellVolt[i];\n            }\n\n            //Now using steinhart/hart equation for temperatures. We'll see if it is better than old code.\n            tempTemp = (1.78f / ((buff[17] * 256 + buff[18] + 2) / 33046.0f) - 3.57f);\n            tempTemp *= 1000.0f;\n            tempCalc =  1.0f / (0.0007610373573f + (0.0002728524832 * logf(tempTemp)) + (powf(logf(tempTemp), 3) * 0.0000001022822735f));            \n\n            temperatures[0] = tempCalc - 273.15f;            \n\n            tempTemp = 1.78f / ((buff[19] * 256 + buff[20] + 9) / 33068.0f) - 3.57f;\n            tempTemp *= 1000.0f;\n            tempCalc = 1.0f / (0.0007610373573f + (0.0002728524832 * logf(tempTemp)) + (powf(logf(tempTemp), 3) * 0.0000001022822735f));\n            temperatures[1] = tempCalc - 273.15f;\n\n            if (getLowTemp() < lowestTemperature) lowestTemperature = getLowTemp();\n            if (getHighTemp() > highestTemperature) highestTemperature = getHighTemp();\n\n            Logger::debug(\"Got voltage and temperature readings\");\n            goodPackets++;\n            retVal = true;\n        }\n    }\n    else\n    {\n        Logger::error(\"Invalid module response received for module %i  len: %i   crc: %i   calc: %i\", \n                      moduleAddress, retLen, buff[21], calcCRC);\n        badPackets++;\n    }\n\n    Logger::debug(\"Good RX: %d       Bad RX: %d\", goodPackets, badPackets);\n\n     //turning the temperature wires off here seems to cause weird temperature glitches\n   // payload[1] = REG_IO_CTRL;\n   // payload[2] = 0b00000000; //turn off temperature measurement pins\n   // BMSUtil::sendData(payload, 3, true);\n   // delay(3);        \n   // BMSUtil::getReply(buff, 50);    //TODO: we're not validating the reply here. Perhaps check to see if a valid reply came back    \n\n    return retVal;\n}\n\nfloat BMSModule::getCellVoltage(int cell)\n{\n    if (cell < 0 || cell > 5) return 0.0f;\n    return cellVolt[cell];\n}\n\nfloat BMSModule::getLowCellV()\n{\n    float lowVal = 10.0f;\n    for (int i = 0; i < 6; i++) if (cellVolt[i] < lowVal) lowVal = cellVolt[i];\n    return lowVal;\n}\n\nfloat BMSModule::getHighCellV()\n{\n    float hiVal = 0.0f;\n    for (int i = 0; i < 6; i++) if (cellVolt[i] > hiVal) hiVal = cellVolt[i];\n    return hiVal;\n}\n\nfloat BMSModule::getAverageV()\n{\n    float avgVal = 0.0f;\n    for (int i = 0; i < 6; i++) avgVal += cellVolt[i];\n    avgVal /= 6.0f;\n    return avgVal;\n}\n\nfloat BMSModule::getHighestModuleVolt()\n{\n    return highestModuleVolt;\n}\n\nfloat BMSModule::getLowestModuleVolt()\n{\n    return lowestModuleVolt;\n}\n\nfloat BMSModule::getHighestCellVolt(int cell)\n{\n    if (cell < 0 || cell > 5) return 0.0f;\n    return highestCellVolt[cell];\n}\n\nfloat BMSModule::getLowestCellVolt(int cell)\n{\n    if (cell < 0 || cell > 5) return 0.0f;\n    return lowestCellVolt[cell];\n}\n\nfloat BMSModule::getHighestTemp()\n{\n    return highestTemperature;\n}\n\nfloat BMSModule::getLowestTemp()\n{\n    return lowestTemperature;\n}\n\nfloat BMSModule::getLowTemp()\n{\n   return (temperatures[0] < temperatures[1]) ? temperatures[0] : temperatures[1]; \n}\n\nfloat BMSModule::getHighTemp()\n{\n   return (temperatures[0] < temperatures[1]) ? temperatures[1] : temperatures[0];\n}\n\nfloat BMSModule::getAvgTemp()\n{\n    return (temperatures[0] + temperatures[1]) / 2.0f;\n}\n\nfloat BMSModule::getModuleVoltage()\n{\n    return moduleVolt;\n}\n\nfloat BMSModule::getTemperature(int temp)\n{\n    if (temp < 0 || temp > 1) return 0.0f;\n    return temperatures[temp];\n}\n\nvoid BMSModule::setAddress(int newAddr)\n{\n    if (newAddr < 0 || newAddr > MAX_MODULE_ADDR) return;\n    moduleAddress = newAddr;\n}\n\nint BMSModule::getAddress()\n{\n    return moduleAddress;\n}\n\nbool BMSModule::isExisting()\n{\n    return exists;\n}\n\n\nvoid BMSModule::setExists(bool ex)\n{\n    exists = ex;\n}\n\nvoid BMSModule::balanceCells()\n{\n    uint8_t payload[4];\n    uint8_t buff[30];\n    uint8_t balance = 0;//bit 0 - 5 are to activate cell balancing 1-6\n\n    payload[0] = moduleAddress << 1;\n    payload[1] = REG_BAL_CTRL;\n    payload[2] = 0; //writing zero to this register resets balance time and must be done before setting balance resistors again.\n    BMSUtil::sendData(payload, 3, true);\n    delay(2);\n    BMSUtil::getReply(buff, 30);\n\n    for (int i = 0; i < 6; i++)\n    {\n        if ( (balanceState[i] == 0) && (getCellVoltage(i) > settings.balanceVoltage) ) balanceState[i] = 1;\n\n        if ( /*(balanceState[i] == 1) &&*/ (getCellVoltage(i) < (settings.balanceVoltage - settings.balanceHyst)) ) balanceState[i] = 0;\n\n        if (balanceState[i] == 1) balance |= (1<<i);\n    }\n\n    if (balance != 0) //only send balance command when needed\n    {\n        payload[0] = moduleAddress << 1;\n        payload[1] = REG_BAL_TIME;\n        payload[2] = 0x82; //balance for two minutes if nobody says otherwise before then\n        BMSUtil::sendData(payload, 3, true);\n        delay(2);\n        BMSUtil::getReply(buff, 30);\n\n        payload[0] = moduleAddress << 1;\n        payload[1] = REG_BAL_CTRL;\n        payload[2] = balance; //write balance state to register\n        BMSUtil::sendData(payload, 3, true);\n        delay(2);\n        BMSUtil::getReply(buff, 30);\n\n        if (Logger::isDebug()) //read registers back out to check if everthing is good\n        {\n            Logger::debug(\"Reading back balancing registers:\");\n            delay(50);\n            payload[0] = moduleAddress << 1;\n            payload[1] = REG_BAL_TIME;\n            payload[2] = 1; //expecting only 1 byte back\n            BMSUtil::sendData(payload, 3, false);\n            delay(2);\n            BMSUtil::getReply(buff, 30);\n\n            payload[0] = moduleAddress << 1;\n            payload[1] = REG_BAL_CTRL;\n            payload[2] = 1; //also only gets one byte\n            BMSUtil::sendData(payload, 3, false);\n            delay(2);\n            BMSUtil::getReply(buff, 30);\n        }\n    }\n}\n\nuint8_t BMSModule::getBalancingState(int cell)\n{\n    if (cell < 0 || cell > 5) return 0;\n    return balanceState[cell];\n}\n"
  },
  {
    "path": "BMSModule.h",
    "content": "#pragma once\n\nclass BMSModule\n{\npublic:\n    BMSModule();\n    void readStatus();\n    bool readModuleValues();\n    float getCellVoltage(int cell);\n    float getLowCellV();\n    float getHighCellV();\n    float getAverageV();\n    float getLowTemp();\n    float getHighTemp();\n    float getHighestModuleVolt();\n    float getLowestModuleVolt();\n    float getHighestCellVolt(int cell);\n    float getLowestCellVolt(int cell);\n    float getHighestTemp();\n    float getLowestTemp();\n    float getAvgTemp();\n    float getModuleVoltage();\n    float getTemperature(int temp);\n    uint8_t getFaults();\n    uint8_t getAlerts();\n    uint8_t getCOVCells();\n    uint8_t getCUVCells();\n    void setAddress(int newAddr);\n    int getAddress();\n    bool isExisting();\n    void setExists(bool ex);\n    void balanceCells();\n    uint8_t getBalancingState(int cell);\n\nprivate:\n    float cellVolt[6];          // calculated as 16 bit value * 6.250 / 16383 = volts\n    float lowestCellVolt[6];\n    float highestCellVolt[6];\n    float moduleVolt;          // calculated as 16 bit value * 33.333 / 16383 = volts\n    float temperatures[2];     // Don't know the proper scaling at this point    \n    float lowestTemperature;\n    float highestTemperature;\n    float lowestModuleVolt;\n    float highestModuleVolt;\n    uint8_t balanceState[6]; //0 = balancing off for this cell, 1 = balancing currently on\n    bool exists;\n    int alerts;\n    int faults;\n    int COVFaults;\n    int CUVFaults;\n    int goodPackets;\n    int badPackets;\n\n    uint8_t moduleAddress;     //1 to 0x3E\n};\n"
  },
  {
    "path": "BMSModuleManager.cpp",
    "content": "#include \"config.h\"\n#include \"BMSModuleManager.h\"\n#include \"BMSUtil.h\"\n#include \"Logger.h\"\n\nextern EEPROMSettings settings;\n\nBMSModuleManager::BMSModuleManager()\n{\n    for (int i = 1; i <= MAX_MODULE_ADDR; i++) {\n        modules[i].setExists(false);\n        modules[i].setAddress(i);\n    }\n    lowestPackVolt = 1000.0f;\n    highestPackVolt = 0.0f;\n    lowestPackTemp = 200.0f;\n    highestPackTemp = -100.0f;\n    isFaulted = false;\n}\n\nvoid BMSModuleManager::balanceCells()\n{  \n    for (int address = 1; address <= MAX_MODULE_ADDR; address++)\n    {\n        if (modules[address].isExisting()) modules[address].balanceCells();\n    }\n}\n\n/*\n * Try to set up any unitialized boards. Send a command to address 0 and see if there is a response. If there is then there is\n * still at least one unitialized board. Go ahead and give it the first ID not registered as already taken.\n * If we send a command to address 0 and no one responds then every board is inialized and this routine stops.\n * Don't run this routine until after the boards have already been enumerated.\\\n * Note: The 0x80 conversion it is looking might in theory block the message from being forwarded so it might be required\n * To do all of this differently. Try with multiple boards. The alternative method would be to try to set the next unused\n * address and see if any boards respond back saying that they set the address. \n */\nvoid BMSModuleManager::setupBoards()\n{\n    uint8_t payload[3];\n    uint8_t buff[10];\n    int retLen;\n\n    payload[0] = 0;\n    payload[1] = 0;\n    payload[2] = 1;\n\n    while (1 == 1)\n    {\n        payload[0] = 0;\n        payload[1] = 0;\n        payload[2] = 1;\n        retLen = BMSUtil::sendDataWithReply(payload, 3, false, buff, 4);\n        if (retLen == 4)\n        {\n            if (buff[0] == 0x80 && buff[1] == 0 && buff[2] == 1)\n            {\n                Logger::debug(\"00 found\");\n                //look for a free address to use\n                for (int y = 1; y < 63; y++) \n                {\n                    if (!modules[y].isExisting())\n                    {\n                        payload[0] = 0;\n                        payload[1] = REG_ADDR_CTRL;\n                        payload[2] = y | 0x80;\n                        BMSUtil::sendData(payload, 3, true);\n                        delay(3);\n                        if (BMSUtil::getReply(buff, 10) > 2)\n                        {\n                            if (buff[0] == (0x81) && buff[1] == REG_ADDR_CTRL && buff[2] == (y + 0x80)) \n                            {\n                                modules[y].setExists(true);\n                                numFoundModules++;\n                                Logger::debug(\"Address assigned\");\n                            }\n                        }\n                        break; //quit the for loop\n                    }\n                }\n            }\n            else break; //nobody responded properly to the zero address so our work here is done.\n        }\n        else break;\n    }\n}\n\n/*\n * Iterate through all 62 possible board addresses (1-62) to see if they respond\n */\nvoid BMSModuleManager::findBoards()\n{\n    uint8_t payload[3];\n    uint8_t buff[8];\n\n    numFoundModules = 0;\n    payload[0] = 0;\n    payload[1] = 0; //read registers starting at 0\n    payload[2] = 1; //read one byte\n    for (int x = 1; x <= MAX_MODULE_ADDR; x++)\n    {\n        modules[x].setExists(false);\n        payload[0] = x << 1;\n        BMSUtil::sendData(payload, 3, false);\n        delay(20);\n        if (BMSUtil::getReply(buff, 8) > 4)\n        {\n            if (buff[0] == (x << 1) && buff[1] == 0 && buff[2] == 1 && buff[4] > 0) {\n                modules[x].setExists(true);\n                numFoundModules++;\n                Logger::debug(\"Found module with address: %X\", x); \n            }\n        }\n        delay(5);\n    }\n}\n\n\n/*\n * Force all modules to reset back to address 0 then set them all up in order so that the first module\n * in line from the master board is 1, the second one 2, and so on.\n*/\nvoid BMSModuleManager::renumberBoardIDs()\n{\n    uint8_t payload[3];\n    uint8_t buff[8];\n    int attempts = 1;\n\n    for (int y = 1; y < 63; y++) \n    {\n        modules[y].setExists(false);\n        numFoundModules = 0;\n    }\n\n    while (attempts < 3)\n    {\n        payload[0] = 0x3F << 1; //broadcast the reset command\n        payload[1] = 0x3C;//reset\n        payload[2] = 0xA5;//data to cause a reset\n        BMSUtil::sendData(payload, 3, true);\n        delay(100);\n        BMSUtil::getReply(buff, 8);\n        if (buff[0] == 0x7F && buff[1] == 0x3C && buff[2] == 0xA5 && buff[3] == 0x57) break;\n        attempts++;\n    }\n\n    setupBoards();\n}\n\n/*\nAfter a RESET boards have their faults written due to the hard restart or first time power up, this clears thier faults\n*/\nvoid BMSModuleManager::clearFaults()\n{\n    uint8_t payload[3];\n    uint8_t buff[8];\n    payload[0] = 0x7F; //broadcast\n    payload[1] = REG_ALERT_STATUS;//Alert Status\n    payload[2] = 0xFF;//data to cause a reset\n    BMSUtil::sendDataWithReply(payload, 3, true, buff, 4);\n\n    payload[0] = 0x7F; //broadcast\n    payload[2] = 0x00;//data to clear\n    BMSUtil::sendDataWithReply(payload, 3, true, buff, 4);\n\n    payload[0] = 0x7F; //broadcast\n    payload[1] = REG_FAULT_STATUS;//Fault Status\n    payload[2] = 0xFF;//data to cause a reset\n    BMSUtil::sendDataWithReply(payload, 3, true, buff, 4);\n\n    payload[0] = 0x7F; //broadcast\n    payload[2] = 0x00;//data to clear\n    BMSUtil::sendDataWithReply(payload, 3, true, buff, 4);\n\n    isFaulted = false;\n}\n\n/*\nPuts all boards on the bus into a Sleep state, very good to use when the vehicle is a rest state. \nPulling the boards out of sleep only to check voltage decay and temperature when the contactors are open.\n*/\n\nvoid BMSModuleManager::sleepBoards()\n{\n    uint8_t payload[3];\n    uint8_t buff[8];\n    payload[0] = 0x7F; //broadcast\n    payload[1] = REG_IO_CTRL;//IO ctrl start\n    payload[2] = 0x04;//write sleep bit\n    BMSUtil::sendData(payload, 3, true);\n    delay(2);\n    BMSUtil::getReply(buff, 8);\n}\n\n/*\nWakes all the boards up and clears thier SLEEP state bit in the Alert Status Registery\n*/\n\nvoid BMSModuleManager::wakeBoards()\n{\n    uint8_t payload[3];\n    uint8_t buff[8];\n    payload[0] = 0x7F; //broadcast\n    payload[1] = REG_IO_CTRL;//IO ctrl start\n    payload[2] = 0x00;//write sleep bit\n    BMSUtil::sendData(payload, 3, true);\n    delay(2);\n    BMSUtil::getReply(buff, 8);\n  \n    payload[0] = 0x7F; //broadcast\n    payload[1] = REG_ALERT_STATUS;//Fault Status\n    payload[2] = 0x04;//data to cause a reset\n    BMSUtil::sendData(payload, 3, true);\n    delay(2);\n    BMSUtil::getReply(buff, 8);\n    payload[0] = 0x7F; //broadcast\n    payload[2] = 0x00;//data to clear\n    BMSUtil::sendData(payload, 3, true);\n    delay(2);\n    BMSUtil::getReply(buff, 8);\n}\n\nvoid BMSModuleManager::getAllVoltTemp()\n{\n    packVolt = 0.0f;\n    for (int x = 1; x <= MAX_MODULE_ADDR; x++)\n    {\n        if (modules[x].isExisting()) \n        {\n            Logger::debug(\"\");\n            Logger::debug(\"Module %i exists. Reading voltage and temperature values\", x);\n            modules[x].readModuleValues();\n            Logger::debug(\"Module voltage: %f\", modules[x].getModuleVoltage());\n            Logger::debug(\"Lowest Cell V: %f     Highest Cell V: %f\", modules[x].getLowCellV(), modules[x].getHighCellV());\n            Logger::debug(\"Temp1: %f       Temp2: %f\", modules[x].getTemperature(0), modules[x].getTemperature(1));\n            packVolt += modules[x].getModuleVoltage();\n            if (modules[x].getLowTemp() < lowestPackTemp) lowestPackTemp = modules[x].getLowTemp();\n            if (modules[x].getHighTemp() > highestPackTemp) highestPackTemp = modules[x].getHighTemp();            \n        }\n    }\n\n    if (packVolt > highestPackVolt) highestPackVolt = packVolt;\n    if (packVolt < lowestPackVolt) lowestPackVolt = packVolt;\n\n    if (digitalRead(13) == LOW) {\n        if (!isFaulted) Logger::error(\"One or more BMS modules have entered the fault state!\");\n        isFaulted = true;\n    }\n    else\n    {\n        if (isFaulted) Logger::info(\"All modules have exited a faulted state\");\n        isFaulted = false;\n    }\n}\n\nfloat BMSModuleManager::getPackVoltage()\n{\n    return packVolt;\n}\n\nfloat BMSModuleManager::getAvgTemperature()\n{\n    float avg = 0.0f;    \n    for (int x = 1; x <= MAX_MODULE_ADDR; x++)\n    {\n        if (modules[x].isExisting()) avg += modules[x].getAvgTemp();\n    }\n    avg = avg / (float)numFoundModules;\n\n    return avg;\n}\n\nfloat BMSModuleManager::getAvgCellVolt()\n{\n    float avg = 0.0f;    \n    for (int x = 1; x <= MAX_MODULE_ADDR; x++)\n    {\n        if (modules[x].isExisting()) avg += modules[x].getAverageV();\n    }\n    avg = avg / (float)numFoundModules;\n\n    return avg;\n}\n\nvoid BMSModuleManager::printPackSummary()\n{\n    uint8_t faults;\n    uint8_t alerts;\n    uint8_t COV;\n    uint8_t CUV;\n\n    Logger::console(\"\");\n    Logger::console(\"\");\n    Logger::console(\"\");\n    Logger::console(\"                                     Pack Status:\");\n    if (isFaulted) Logger::console(\"                                       FAULTED!\");\n    else Logger::console(\"                                   All systems go!\");\n    Logger::console(\"Modules: %i    Voltage: %fV   Avg Cell Voltage: %fV     Avg Temp: %fC \", numFoundModules, \n                    getPackVoltage(),getAvgCellVolt(), getAvgTemperature());\n    Logger::console(\"\");\n    for (int y = 1; y < 63; y++)\n    {\n        if (modules[y].isExisting())\n        {\n            faults = modules[y].getFaults();\n            alerts = modules[y].getAlerts();\n            COV = modules[y].getCOVCells();\n            CUV = modules[y].getCUVCells();\n\n            Logger::console(\"                               Module #%i\", y);\n\n            Logger::console(\"  Voltage: %fV   (%fV-%fV)     Temperatures: (%fC-%fC)\", modules[y].getModuleVoltage(), \n                            modules[y].getLowCellV(), modules[y].getHighCellV(), modules[y].getLowTemp(), modules[y].getHighTemp());\n\n            SerialUSB.print(\"  Currently balancing cells: \");\n            for (int i = 0; i < 6; i++)\n            {                \n                if (modules[y].getBalancingState(i) == 1) \n                {                    \n                    SerialUSB.print(i);\n                    SerialUSB.print(\" \");\n                }\n            }\n            SerialUSB.println();\n\n            if (faults > 0)\n            {\n                Logger::console(\"  MODULE IS FAULTED:\");\n                if (faults & 1)\n                {\n                    SerialUSB.print(\"    Overvoltage Cell Numbers (1-6): \");\n                    for (int i = 0; i < 6; i++)\n                    {\n                        if (COV & (1 << i)) \n                        {\n                            SerialUSB.print(i+1);\n                            SerialUSB.print(\" \");\n                        }\n                    }\n                    SerialUSB.println();\n                }\n                if (faults & 2)\n                {\n                    SerialUSB.print(\"    Undervoltage Cell Numbers (1-6): \");\n                    for (int i = 0; i < 6; i++)\n                    {\n                        if (CUV & (1 << i)) \n                        {\n                            SerialUSB.print(i+1);\n                            SerialUSB.print(\" \");\n                        }\n                    }\n                    SerialUSB.println();\n                }\n                if (faults & 4)\n                {\n                    Logger::console(\"    CRC error in received packet\");\n                }\n                if (faults & 8)\n                {\n                    Logger::console(\"    Power on reset has occurred\");\n                }\n                if (faults & 0x10)\n                {\n                    Logger::console(\"    Test fault active\");\n                }\n                if (faults & 0x20)\n                {\n                    Logger::console(\"    Internal registers inconsistent\");\n                }\n            }\n            if (alerts > 0)\n            {\n                Logger::console(\"  MODULE HAS ALERTS:\");\n                if (alerts & 1)\n                {\n                    Logger::console(\"    Over temperature on TS1\");\n                }\n                if (alerts & 2)\n                {\n                    Logger::console(\"    Over temperature on TS2\");\n                }\n                if (alerts & 4)\n                {\n                    Logger::console(\"    Sleep mode active\");\n                }\n                if (alerts & 8)\n                {\n                    Logger::console(\"    Thermal shutdown active\");\n                }\n                if (alerts & 0x10)\n                {\n                    Logger::console(\"    Test Alert\");\n                }\n                if (alerts & 0x20)\n                {\n                    Logger::console(\"    OTP EPROM Uncorrectable Error\");\n                }\n                if (alerts & 0x40)\n                {\n                    Logger::console(\"    GROUP3 Regs Invalid\");\n                }\n                if (alerts & 0x80)\n                {\n                    Logger::console(\"    Address not registered\");\n                }\n            }\n            if (faults > 0 || alerts > 0) SerialUSB.println();\n        }\n    }\n}\n\nvoid BMSModuleManager::printPackDetails()\n{\n    uint8_t faults;\n    uint8_t alerts;\n    uint8_t COV;\n    uint8_t CUV;\n    int cellNum = 0;\n\n    Logger::console(\"\");\n    Logger::console(\"\");\n    Logger::console(\"\");\n    Logger::console(\"                                         Pack Status:\");\n    if (isFaulted) Logger::console(\"                                           FAULTED!\");\n    else Logger::console(\"                                      All systems go!\");\n    Logger::console(\"Modules: %i    Voltage: %fV   Avg Cell Voltage: %fV     Avg Temp: %fC \", numFoundModules, \n                    getPackVoltage(),getAvgCellVolt(), getAvgTemperature());\n    Logger::console(\"\");\n    for (int y = 1; y < 63; y++)\n    {\n        if (modules[y].isExisting())\n        {\n            faults = modules[y].getFaults();\n            alerts = modules[y].getAlerts();\n            COV = modules[y].getCOVCells();\n            CUV = modules[y].getCUVCells();\n\n            SerialUSB.print(\"Module #\");\n            SerialUSB.print(y);\n            if (y < 10) SerialUSB.print(\" \");\n            SerialUSB.print(\"  \");\n            SerialUSB.print(modules[y].getModuleVoltage());\n            SerialUSB.print(\"V\");\n            for (int i = 0; i < 6; i++)\n            {\n                if (cellNum < 10) SerialUSB.print(\" \");\n                SerialUSB.print(\"  Cell\");\n                SerialUSB.print(cellNum++);\n                SerialUSB.print(\": \");\n                SerialUSB.print(modules[y].getCellVoltage(i));\n                SerialUSB.print(\"V\");\n                if (modules[y].getBalancingState(i) == 1) SerialUSB.print(\"*\");\n                else SerialUSB.print(\" \");\n            }\n            SerialUSB.print(\"  Neg Term Temp: \");\n            SerialUSB.print(modules[y].getTemperature(0));\n            SerialUSB.print(\"C  Pos Term Temp: \");\n            SerialUSB.print(modules[y].getTemperature(1)); \n            SerialUSB.println(\"C\");\n        }\n    }\n}\n\nvoid BMSModuleManager::processCANMsg(CAN_FRAME &frame)\n{\n    uint8_t battId = (frame.id >> 16) & 0xF;\n    uint8_t moduleId = (frame.id >> 8) & 0xFF;\n    uint8_t cellId = (frame.id) & 0xFF;\n    \n    if (moduleId = 0xFF)  //every module\n    {\n        if (cellId == 0xFF) sendBatterySummary();        \n        else \n        {\n            for (int i = 1; i <= MAX_MODULE_ADDR; i++) \n            {\n                if (modules[i].isExisting()) \n                {\n                    sendCellDetails(i, cellId);\n                    delayMicroseconds(500);\n                }\n            }\n        }\n    }\n    else //a specific module\n    {\n        if (cellId == 0xFF) sendModuleSummary(moduleId);\n        else sendCellDetails(moduleId, cellId);\n    }\n}\n\nvoid BMSModuleManager::sendBatterySummary()\n{\n    CAN_FRAME outgoing;\n    outgoing.id = (0x1BA00000ul) + ((settings.batteryID & 0xF) << 16) + 0xFFFF;\n    outgoing.rtr = 0;\n    outgoing.priority = 1;\n    outgoing.extended = true;\n    outgoing.length = 8;\n\n    uint16_t battV = uint16_t(getPackVoltage() * 100.0f);\n    outgoing.data.byte[0] = battV & 0xFF;\n    outgoing.data.byte[1] = battV >> 8;\n    outgoing.data.byte[2] = 0;  //instantaneous current. Not measured at this point\n    outgoing.data.byte[3] = 0;\n    outgoing.data.byte[4] = 50; //state of charge\n    int avgTemp = (int)getAvgTemperature() + 40;\n    if (avgTemp < 0) avgTemp = 0;\n    outgoing.data.byte[5] = avgTemp;\n    avgTemp = (int)lowestPackTemp + 40;\n    if (avgTemp < 0) avgTemp = 0;    \n    outgoing.data.byte[6] = avgTemp;\n    avgTemp = (int)highestPackTemp + 40;\n    if (avgTemp < 0) avgTemp = 0;\n    outgoing.data.byte[7] = avgTemp;\n    Can0.sendFrame(outgoing);\n}\n\nvoid BMSModuleManager::sendModuleSummary(int module)\n{\n    CAN_FRAME outgoing;\n    outgoing.id = (0x1BA00000ul) + ((settings.batteryID & 0xF) << 16) + ((module & 0xFF) << 8) + 0xFF;\n    outgoing.rtr = 0;\n    outgoing.priority = 1;\n    outgoing.extended = true;\n    outgoing.length = 8;\n\n    uint16_t battV = uint16_t(modules[module].getModuleVoltage() * 100.0f);\n    outgoing.data.byte[0] = battV & 0xFF;\n    outgoing.data.byte[1] = battV >> 8;\n    outgoing.data.byte[2] = 0;  //instantaneous current. Not measured at this point\n    outgoing.data.byte[3] = 0;\n    outgoing.data.byte[4] = 50; //state of charge\n    int avgTemp = (int)modules[module].getAvgTemp() + 40;\n    if (avgTemp < 0) avgTemp = 0;\n    outgoing.data.byte[5] = avgTemp;\n    avgTemp = (int)modules[module].getLowestTemp() + 40;\n    if (avgTemp < 0) avgTemp = 0;\n    outgoing.data.byte[6] = avgTemp;\n    avgTemp = (int)modules[module].getHighestTemp() + 40;\n    if (avgTemp < 0) avgTemp = 0;\n    outgoing.data.byte[7] = avgTemp;\n\n    Can0.sendFrame(outgoing);\n}\n\nvoid BMSModuleManager::sendCellDetails(int module, int cell)\n{\n    CAN_FRAME outgoing;\n    outgoing.id = (0x1BA00000ul) + ((settings.batteryID & 0xF) << 16) + ((module & 0xFF) << 8) + (cell & 0xFF);\n    outgoing.rtr = 0;\n    outgoing.priority = 1;\n    outgoing.extended = true;\n    outgoing.length = 8;\n\n    uint16_t battV = uint16_t(modules[module].getCellVoltage(cell) * 100.0f);\n    outgoing.data.byte[0] = battV & 0xFF;\n    outgoing.data.byte[1] = battV >> 8;\n    battV = uint16_t(modules[module].getHighestCellVolt(cell) * 100.0f);\n    outgoing.data.byte[2] = battV & 0xFF;\n    outgoing.data.byte[3] = battV >> 8;\n    battV = uint16_t(modules[module].getLowestCellVolt(cell) * 100.0f);\n    outgoing.data.byte[4] = battV & 0xFF;\n    outgoing.data.byte[5] = battV >> 8;\n    int instTemp = modules[module].getHighTemp() + 40;\n    outgoing.data.byte[6] = instTemp; // should be nearest temperature reading not highest but this works too.\n    outgoing.data.byte[7] = 0; //Bit encoded fault data. No definitions for this yet.\n\n    Can0.sendFrame(outgoing);\n}\n\n//The SerialConsole actually sets the battery ID to a specific value. We just have to set up the CAN filter here to\n//match.\nvoid BMSModuleManager::setBatteryID()\n{\n    //Setup filter for direct access to our registered battery ID\n    uint32_t canID = (0xBAul << 20) + (((uint32_t)settings.batteryID & 0xF) << 16);\n    Can0.setRXFilter(0, canID, 0x1FFF0000ul, true);\n}\n\n"
  },
  {
    "path": "BMSModuleManager.h",
    "content": "#pragma once\n#include \"config.h\"\n#include \"BMSModule.h\"\n#include <due_can.h>\n\nclass BMSModuleManager\n{\npublic:\n    BMSModuleManager();\n    void balanceCells();\n    void setupBoards();\n    void findBoards();\n    void renumberBoardIDs();\n    void clearFaults();\n    void sleepBoards();\n    void wakeBoards();\n    void getAllVoltTemp();\n    void readSetpoints();\n    void setBatteryID();\n    float getPackVoltage();\n    float getAvgTemperature();\n    float getAvgCellVolt();\n    void processCANMsg(CAN_FRAME &frame);\n    void printPackSummary();\n    void printPackDetails();\n\nprivate:\n    float packVolt;                         // All modules added together\n    float lowestPackVolt;\n    float highestPackVolt;\n    float lowestPackTemp;\n    float highestPackTemp;\n    BMSModule modules[MAX_MODULE_ADDR + 1]; // store data for as many modules as we've configured for.\n    int numFoundModules;                    // The number of modules that seem to exist\n    bool isFaulted;\n    \n    void sendBatterySummary();\n    void sendModuleSummary(int module);\n    void sendCellDetails(int module, int cell);\n    \n};\n"
  },
  {
    "path": "BMSUtil.h",
    "content": "#include <Arduino.h>\n#include \"Logger.h\"\n\nclass BMSUtil {    \npublic:\n    \n    static uint8_t genCRC(uint8_t *input, int lenInput)\n    {\n        uint8_t generator = 0x07;\n        uint8_t crc = 0;\n\n        for (int x = 0; x < lenInput; x++)\n        {\n            crc ^= input[x]; /* XOR-in the next input byte */\n\n            for (int i = 0; i < 8; i++)\n            {\n                if ((crc & 0x80) != 0)\n                {\n                    crc = (uint8_t)((crc << 1) ^ generator);\n                }\n                else\n                {\n                    crc <<= 1;\n                }\n            }\n        }\n\n        return crc;\n    }\n\n    static void sendData(uint8_t *data, uint8_t dataLen, bool isWrite)\n    {\n        uint8_t orig = data[0];\n        uint8_t addrByte = data[0];\n        if (isWrite) addrByte |= 1;\n        SERIAL.write(addrByte);\n        SERIAL.write(&data[1], dataLen - 1);  //assumes that there are at least 2 bytes sent every time. There should be, addr and cmd at the least.\n        data[0] = addrByte;\n        if (isWrite) SERIAL.write(genCRC(data, dataLen));        \n\n        if (Logger::isDebug())\n        {\n            SERIALCONSOLE.print(\"Sending: \");\n            SERIALCONSOLE.print(addrByte, HEX);\n            SERIALCONSOLE.print(\" \");\n            for (int x = 1; x < dataLen; x++) {\n                SERIALCONSOLE.print(data[x], HEX);\n                SERIALCONSOLE.print(\" \");\n            }\n            if (isWrite) SERIALCONSOLE.print(genCRC(data, dataLen), HEX);\n            SERIALCONSOLE.println();\n        }\n        \n        data[0] = orig;\n    }\n\n    static int getReply(uint8_t *data, int maxLen)\n    { \n        int numBytes = 0; \n        if (Logger::isDebug()) SERIALCONSOLE.print(\"Reply: \");\n        while (SERIAL.available() && numBytes < maxLen)\n        {\n            data[numBytes] = SERIAL.read();\n            if (Logger::isDebug()) {\n                SERIALCONSOLE.print(data[numBytes], HEX);\n                SERIALCONSOLE.print(\" \");\n            }\n            numBytes++;\n        }\n        if (maxLen == numBytes)\n        {\n            while (SERIAL.available()) SERIAL.read();\n        }\n        if (Logger::isDebug()) SERIALCONSOLE.println();\n        return numBytes;\n    }\n    \n    //Uses above functions to send data then get the response. Will auto retry if response not \n    //the expected return length. This helps to alleviate any comm issues. The Due cannot exactly\n    //match the correct comm speed so sometimes there are data glitches.\n    static int sendDataWithReply(uint8_t *data, uint8_t dataLen, bool isWrite, uint8_t *retData, int retLen)\n    {\n        int attempts = 1;\n        int returnedLength;\n        while (attempts < 4)\n        {\n            sendData(data, dataLen, isWrite);\n            delay(2 * ((retLen / 8) + 1));\n            returnedLength = getReply(retData, retLen);\n            if (returnedLength == retLen) return returnedLength;\n            attempts++;\n        }\n        return returnedLength; //failed to get a proper response.\n    }\n};"
  },
  {
    "path": "Logger.cpp",
    "content": "/*\n * Logger.cpp\n *\n Copyright (c) 2013 Collin Kidder, Michael Neuweiler, Charles Galpin\n\n Permission is hereby granted, free of charge, to any person obtaining\n a copy of this software and associated documentation files (the\n \"Software\"), to deal in the Software without restriction, including\n without limitation the rights to use, copy, modify, merge, publish,\n distribute, sublicense, and/or sell copies of the Software, and to\n permit persons to whom the Software is furnished to do so, subject to\n the following conditions:\n\n The above copyright notice and this permission notice shall be included\n in all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n */\n\n#include \"Logger.h\"\n\nLogger::LogLevel Logger::logLevel = Logger::Info;\nuint32_t Logger::lastLogTime = 0;\n\n/*\n * Output a debug message with a variable amount of parameters.\n * printf() style, see Logger::log()\n *\n */\nvoid Logger::debug(char *message, ...) {\n    if (logLevel > Debug)\n        return;\n    va_list args;\n    va_start(args, message);\n    Logger::log(Debug, message, args);\n    va_end(args);\n}\n\n/*\n * Output a info message with a variable amount of parameters\n * printf() style, see Logger::log()\n */\nvoid Logger::info(char *message, ...) {\n    if (logLevel > Info)\n        return;\n    va_list args;\n    va_start(args, message);\n    Logger::log(Info, message, args);\n    va_end(args);\n}\n\n/*\n * Output a warning message with a variable amount of parameters\n * printf() style, see Logger::log()\n */\nvoid Logger::warn(char *message, ...) {\n    if (logLevel > Warn)\n        return;\n    va_list args;\n    va_start(args, message);\n    Logger::log(Warn, message, args);\n    va_end(args);\n}\n\n/*\n * Output a error message with a variable amount of parameters\n * printf() style, see Logger::log()\n */\nvoid Logger::error(char *message, ...) {\n    if (logLevel > Error)\n        return;\n    va_list args;\n    va_start(args, message);\n    Logger::log(Error, message, args);\n    va_end(args);\n}\n\n/*\n * Output a comnsole message with a variable amount of parameters\n * printf() style, see Logger::logMessage()\n */\nvoid Logger::console(char *message, ...) {\n    va_list args;\n    va_start(args, message);\n    Logger::logMessage(message, args);\n    va_end(args);\n}\n\n/*\n * Set the log level. Any output below the specified log level will be omitted.\n */\nvoid Logger::setLoglevel(LogLevel level) {\n    logLevel = level;\n}\n\n/*\n * Retrieve the current log level.\n */\nLogger::LogLevel Logger::getLogLevel() {\n    return logLevel;\n}\n\n/*\n * Return a timestamp when the last log entry was made.\n */\nuint32_t Logger::getLastLogTime() {\n    return lastLogTime;\n}\n\n/*\n * Returns if debug log level is enabled. This can be used in time critical\n * situations to prevent unnecessary string concatenation (if the message won't\n * be logged in the end).\n *\n * Example:\n * if (Logger::isDebug()) {\n *    Logger::debug(\"current time: %d\", millis());\n * }\n */\nboolean Logger::isDebug() {\n    return logLevel == Debug;\n}\n\n/*\n * Output a log message (called by debug(), info(), warn(), error(), console())\n *\n * Supports printf() like syntax:\n *\n * %% - outputs a '%' character\n * %s - prints the next parameter as string\n * %d - prints the next parameter as decimal\n * %f - prints the next parameter as double float\n * %x - prints the next parameter as hex value\n * %X - prints the next parameter as hex value with '0x' added before\n * %b - prints the next parameter as binary value\n * %B - prints the next parameter as binary value with '0b' added before\n * %l - prints the next parameter as long\n * %c - prints the next parameter as a character\n * %t - prints the next parameter as boolean ('T' or 'F')\n * %T - prints the next parameter as boolean ('true' or 'false')\n */\nvoid Logger::log(LogLevel level, char *format, va_list args) {\n    lastLogTime = millis();\n    SERIALCONSOLE.print(lastLogTime);\n    SERIALCONSOLE.print(\" - \");\n\n    switch (level) {\n    case Debug:\n        SERIALCONSOLE.print(\"DEBUG\");\n        break;\n    case Info:\n        SERIALCONSOLE.print(\"INFO\");\n        break;\n    case Warn:\n        SERIALCONSOLE.print(\"WARNING\");\n        break;\n    case Error:\n        SERIALCONSOLE.print(\"ERROR\");\n        break;\n    }\n    SERIALCONSOLE.print(\": \");\n\n    logMessage(format, args);\n}\n\n/*\n * Output a log message (called by log(), console())\n *\n * Supports printf() like syntax:\n *\n * %% - outputs a '%' character\n * %s - prints the next parameter as string\n * %d - prints the next parameter as decimal\n * %f - prints the next parameter as double float\n * %x - prints the next parameter as hex value\n * %X - prints the next parameter as hex value with '0x' added before\n * %b - prints the next parameter as binary value\n * %B - prints the next parameter as binary value with '0b' added before\n * %l - prints the next parameter as long\n * %c - prints the next parameter as a character\n * %t - prints the next parameter as boolean ('T' or 'F')\n * %T - prints the next parameter as boolean ('true' or 'false')\n */\nvoid Logger::logMessage(char *format, va_list args) {\n    for (; *format != 0; ++format) {\n        if (*format == '%') {\n            ++format;\n            if (*format == '\\0')\n                break;\n            if (*format == '%') {\n                SERIALCONSOLE.print(*format);\n                continue;\n            }\n            if (*format == 's') {\n                register char *s = (char *) va_arg( args, int );\n                SERIALCONSOLE.print(s);\n                continue;\n            }\n            if (*format == 'd' || *format == 'i') {\n                SERIALCONSOLE.print(va_arg( args, int ), DEC);\n                continue;\n            }\n            if (*format == 'f') {\n                SERIALCONSOLE.print(va_arg( args, double ), 3);\n                continue;\n            }\n            if (*format == 'x') {\n                SERIALCONSOLE.print(va_arg( args, int ), HEX);\n                continue;\n            }\n            if (*format == 'X') {\n                SERIALCONSOLE.print(\"0x\");\n                SERIALCONSOLE.print(va_arg( args, int ), HEX);\n                continue;\n            }\n            if (*format == 'b') {\n                SERIALCONSOLE.print(va_arg( args, int ), BIN);\n                continue;\n            }\n            if (*format == 'B') {\n                SERIALCONSOLE.print(\"0b\");\n                SERIALCONSOLE.print(va_arg( args, int ), BIN);\n                continue;\n            }\n            if (*format == 'l') {\n                SERIALCONSOLE.print(va_arg( args, long ), DEC);\n                continue;\n            }\n\n            if (*format == 'c') {\n                SERIALCONSOLE.print(va_arg( args, int ));\n                continue;\n            }\n            if (*format == 't') {\n                if (va_arg( args, int ) == 1) {\n                    SERIALCONSOLE.print(\"T\");\n                } else {\n                    SERIALCONSOLE.print(\"F\");\n                }\n                continue;\n            }\n            if (*format == 'T') {\n                if (va_arg( args, int ) == 1) {\n                    SERIALCONSOLE.print(\"TRUE\");\n                } else {\n                    SERIALCONSOLE.print(\"FALSE\");\n                }\n                continue;\n            }\n\n        }\n        SERIALCONSOLE.print(*format);\n    }\n    SERIALCONSOLE.println();\n}\n\n"
  },
  {
    "path": "Logger.h",
    "content": "/*\n * Logger.h\n *\n Copyright (c) 2013 Collin Kidder, Michael Neuweiler, Charles Galpin\n\n Permission is hereby granted, free of charge, to any person obtaining\n a copy of this software and associated documentation files (the\n \"Software\"), to deal in the Software without restriction, including\n without limitation the rights to use, copy, modify, merge, publish,\n distribute, sublicense, and/or sell copies of the Software, and to\n permit persons to whom the Software is furnished to do so, subject to\n the following conditions:\n\n The above copyright notice and this permission notice shall be included\n in all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n */\n\n#ifndef LOGGER_H_\n#define LOGGER_H_\n\n#include <Arduino.h>\n#include \"config.h\"\n\nclass Logger {\npublic:\n    enum LogLevel {\n        Debug = 0, Info = 1, Warn = 2, Error = 3, Off = 4\n    };\n    static void debug(char *, ...);\n    static void info(char *, ...);\n    static void warn(char *, ...);\n    static void error(char *, ...);\n    static void console(char *, ...);\n    static void setLoglevel(LogLevel);\n    static LogLevel getLogLevel();\n    static uint32_t getLastLogTime();\n    static boolean isDebug();\nprivate:\n    static LogLevel logLevel;\n    static uint32_t lastLogTime;\n\n    static void log(LogLevel, char *format, va_list);\n    static void logMessage(char *format, va_list args);\n};\n\n#endif /* LOGGER_H_ */\n\n\n"
  },
  {
    "path": "README.md",
    "content": "Arduino compatible project to interface with the BMS slave \nboard on Tesla Model S modules.\n\nThe modules are daisy-chained together with a TTL interface.\nThe interface uses a Molex 15-97-5101 connector and runs at\n612500 baud. This can be a difficult baud rate to match with\narduino compatible processors. The Arduino Due and Teensy\n3.5/3.6 boards are confirmed to be able to generate a suitably\nclose baud rate. The factory wiring to each module is comprised\nof two sets of 5 differently colored wires:\n\n* Red = 5V input to the module\n* Green = Gnd for power and signal\n* Gray = Fault output\n* Yellow = UART Wire\n* Blue = UART Wire\n\nThe fault output is active low. Use your own pull up to the fault line and if the line is pulled low then a fault has occurred.\n\nHere is a PDF that explains how the wiring between modules and the master board is supposed to be:\nhttps://cdn.hackaday.io/files/10098432032832/wiring.pdf\n"
  },
  {
    "path": "SerialConsole.cpp",
    "content": "/*\n * SerialConsole.cpp\n *\n Copyright (c) 2017 EVTV / Collin Kidder\n\n Permission is hereby granted, free of charge, to any person obtaining\n a copy of this software and associated documentation files (the\n \"Software\"), to deal in the Software without restriction, including\n without limitation the rights to use, copy, modify, merge, publish,\n distribute, sublicense, and/or sell copies of the Software, and to\n permit persons to whom the Software is furnished to do so, subject to\n the following conditions:\n\n The above copyright notice and this permission notice shall be included\n in all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n */\n\n#include <due_wire.h>\n#include <Wire_EEPROM.h>\n#include \"SerialConsole.h\"\n#include \"Logger.h\"\n#include \"BMSModuleManager.h\"\n\ntemplate<class T> inline Print &operator <<(Print &obj, T arg) { obj.print(arg); return obj; } //Lets us stream SerialUSB\n\nextern EEPROMSettings settings;\nextern BMSModuleManager bms;\n\nbool printPrettyDisplay;\nuint32_t prettyCounter;\nint whichDisplay;\n\nSerialConsole::SerialConsole() {\n    init();\n}\n\nvoid SerialConsole::init() {\n    //State variables for serial console\n    ptrBuffer = 0;\n    state = STATE_ROOT_MENU;\n    loopcount=0;\n    cancel=false;\n    printPrettyDisplay = false;\n    prettyCounter = 0;\n    whichDisplay = 0;\n}\n\nvoid SerialConsole::loop() {  \n    if (SERIALCONSOLE.available()) {\n        serialEvent();\n    }\n    if (printPrettyDisplay && (millis() > (prettyCounter + 3000)))\n    {\n        prettyCounter = millis();\n        if (whichDisplay == 0) bms.printPackSummary();\n        if (whichDisplay == 1) bms.printPackDetails();\n    }\n}\n\nvoid SerialConsole::printMenu() {   \n    Logger::console(\"\\n*************SYSTEM MENU *****************\");\n    Logger::console(\"Enable line endings of some sort (LF, CR, CRLF)\");\n    Logger::console(\"Most commands case sensitive\\n\");\n    Logger::console(\"GENERAL SYSTEM CONFIGURATION\\n\");\n    Logger::console(\"   E = dump system EEPROM values\");\n    Logger::console(\"   h = help (displays this message)\");\n    Logger::console(\"   S = Sleep all boards\");\n    Logger::console(\"   W = Wake up all boards\");\n    Logger::console(\"   C = Clear all board faults\");\n    Logger::console(\"   F = Find all connected boards\");\n    Logger::console(\"   R = Renumber connected boards in sequence\");\n    Logger::console(\"   B = Attempt balancing for 5 seconds\");\n    Logger::console(\"   p = Toggle output of pack summary every 3 seconds\");\n    Logger::console(\"   d = Toggle output of pack details every 3 seconds\");\n\n    Logger::console(\"   LOGLEVEL=%i - set log level (0=debug, 1=info, 2=warn, 3=error, 4=off)\", Logger::getLogLevel());\n    Logger::console(\"   CANSPEED=%i - set first CAN bus speed\", settings.canSpeed);\n    Logger::console(\"   BATTERYID=%i - Set battery ID for CAN protocol (1-14)\", settings.batteryID);\n\n    Logger::console(\"\\nBATTERY MANAGEMENT CONTROLS\\n\");\n    Logger::console(\"   VOLTLIMHI=%f - High limit for cells in volts\", settings.OverVSetpoint);\n    Logger::console(\"   VOLTLIMLO=%f - Low limit for cells in volts\", settings.UnderVSetpoint);\n    Logger::console(\"   TEMPLIMHI=%f - High limit for cell temperature in degrees C\", settings.OverTSetpoint);\n    Logger::console(\"   TEMPLIMLO=%f - Low limit for cell temperature in degrees C\", settings.UnderTSetpoint);\n    Logger::console(\"   BALVOLT=%f - Voltage at which to begin cell balancing\", settings.balanceVoltage);\n    Logger::console(\"   BALHYST=%f - How far voltage must dip before balancing is turned off\", settings.balanceHyst);\n\n    float OverVSetpoint;\n    float UnderVSetpoint;\n    float OverTSetpoint;\n    float UnderTSetpoint;\n    float balanceVoltage;\n    float balanceHyst;\n}\n\n/*\tThere is a help menu (press H or h or ?)\n\n    Commands are submitted by sending line ending (LF, CR, or both)\n */\nvoid SerialConsole::serialEvent() {\n    int incoming;\n    incoming = SERIALCONSOLE.read();\n    if (incoming == -1) { //false alarm....\n        return;\n    }\n\n    if (incoming == 10 || incoming == 13) { //command done. Parse it.\n        handleConsoleCmd();\n        ptrBuffer = 0; //reset line counter once the line has been processed\n    } else {\n        cmdBuffer[ptrBuffer++] = (unsigned char) incoming;\n        if (ptrBuffer > 79)\n            ptrBuffer = 79;\n    }\n}\n\nvoid SerialConsole::handleConsoleCmd() {\n\n    if (state == STATE_ROOT_MENU) {\n        if (ptrBuffer == 1) { //command is a single ascii character\n            handleShortCmd();\n        } else { //if cmd over 1 char then assume (for now) that it is a config line\n            handleConfigCmd();\n        }\n    }\n}\n\n/*For simplicity the configuration setting code uses four characters for each configuration choice. This makes things easier for\n comparison purposes.\n */\nvoid SerialConsole::handleConfigCmd() {\n    int i;\n    int newValue;\n    float newFloat;\n    bool needEEPROMWrite = false;\n\n    //Logger::debug(\"Cmd size: %i\", ptrBuffer);\n    if (ptrBuffer < 6)\n        return; //4 digit command, =, value is at least 6 characters\n    cmdBuffer[ptrBuffer] = 0; //make sure to null terminate\n    String cmdString = String();\n    unsigned char whichEntry = '0';\n    i = 0;\n\n    while (cmdBuffer[i] != '=' && i < ptrBuffer) {\n        cmdString.concat(String(cmdBuffer[i++]));\n    }\n    i++; //skip the =\n    if (i >= ptrBuffer)\n    {\n        Logger::console(\"Command needs a value..ie TORQ=3000\");\n        Logger::console(\"\");\n        return; //or, we could use this to display the parameter instead of setting\n    }\n\n    // strtol() is able to parse also hex values (e.g. a string \"0xCAFE\"), useful for enable/disable by device id\n    newValue = strtol((char *) (cmdBuffer + i), NULL, 0);\n    newFloat = strtof((char *) (cmdBuffer + i), NULL);\n\n    cmdString.toUpperCase();\n\n    if (cmdString == String(\"CANSPEED\")) {\n        if (newValue >= 33000 && newValue <= 1000000) {\n            settings.canSpeed = newValue;\n            Logger::console(\"Setting CAN speed to %i\", newValue);\n            needEEPROMWrite = true;\n        }\n        else Logger::console(\"Invalid speed. Enter a value between 33000 and 1000000\");\n    } else if (cmdString == String(\"LOGLEVEL\")) {\n        switch (newValue) {\n        case 0:\n            Logger::setLoglevel(Logger::Debug);\n            settings.logLevel = 0;\n            Logger::console(\"setting loglevel to 'debug'\");\n            break;\n        case 1:\n            Logger::setLoglevel(Logger::Info);\n            settings.logLevel = 1;\n            Logger::console(\"setting loglevel to 'info'\");\n            break;\n        case 2:\n            Logger::console(\"setting loglevel to 'warning'\");\n            settings.logLevel = 2;\n            Logger::setLoglevel(Logger::Warn);\n            break;\n        case 3:\n            Logger::console(\"setting loglevel to 'error'\");\n            settings.logLevel = 3;\n            Logger::setLoglevel(Logger::Error);\n            break;\n        case 4:\n            Logger::console(\"setting loglevel to 'off'\");\n            settings.logLevel = 4;\n            Logger::setLoglevel(Logger::Off);\n            break;\n        } \n        needEEPROMWrite = true;\n    } else if (cmdString == String(\"BATTERYID\")) {\n        if (newValue > 0 && newValue < 15) {\n            settings.batteryID = newValue;\n            bms.setBatteryID();\n            needEEPROMWrite = true;\n            Logger::console(\"Battery ID set to: %i\", newValue);\n        }\n        else Logger::console(\"Invalid battery ID. Please enter a value between 1 and 14\");\n    } else if (cmdString == String(\"VOLTLIMHI\")) {\n        if (newFloat >= 0.0f && newFloat <= 6.00f) {\n            settings.OverVSetpoint = newFloat; \n            needEEPROMWrite = true;\n            Logger::console(\"Cell Voltage Upper Limit set to: %f\", settings.OverVSetpoint);\n        }\n        else Logger::console(\"Invalid upper cell voltage limit. Please enter a value 0.0 to 6.0\");\n    } else if (cmdString == String(\"VOLTLIMLO\")) {\n        if (newFloat >= 0.0f && newFloat <= 6.0f) {\n            settings.UnderVSetpoint = newFloat;\n            needEEPROMWrite = true;\n            Logger::console(\"Cell Voltage Lower Limit set to %f\", settings.UnderVSetpoint);\n        }\n        else Logger::console(\"Invalid lower cell voltage limit. Please enter a value 0.0 to 6.0\");\n    } else if (cmdString == String(\"BALVOLT\")) {\n        if (newFloat >= 0.0f && newFloat <= 6.0f) {\n            settings.balanceVoltage = newFloat;\n            needEEPROMWrite = true;\n            Logger::console(\"Balance voltage set to %f\", settings.balanceVoltage);\n        }\n        else Logger::console(\"Invalid balancing voltage. Please enter a value 0.0 to 6.0\");\n    } else if (cmdString == String(\"BALHYST\")) {\n        if (newFloat >= 0.0f && newFloat <= 1.0f) {\n            settings.balanceHyst = newFloat;\n            needEEPROMWrite = true;\n            Logger::console(\"Balance hysteresis set to %f\", settings.balanceHyst);\n        }\n        else Logger::console(\"Invalid balance hysteresis. Please enter a value 0.0 to 1.0\");        \n    } else if (cmdString == String(\"TEMPLIMHI\")) {\n        if (newFloat >= 0.0f && newFloat <= 100.0f) {\n            settings.OverTSetpoint = newFloat;\n            needEEPROMWrite=true;\n            Logger::console(\"Module Temperature Upper Limit set to: %f\", settings.OverTSetpoint);\n        }\n        else Logger::console(\"Invalid temperature upper limit please enter a value 0.0 to 100.0\");\n    } else if (cmdString == String(\"TEMPLIMLO\")) {\n        if (newFloat >= -20.00f && newFloat <= 120.0f) {\n            settings.UnderTSetpoint = newFloat;\n            needEEPROMWrite = true;\n            Logger::console(\"Module Temperature Lower Limit set to: %f\", settings.UnderTSetpoint);\n        }\n        else Logger::console(\"Invalid temperature lower limit please enter a value between -20.0 and 120.0\");        \n    } else {\n        Logger::console(\"Unknown command\");\n    }\n    if (needEEPROMWrite)\n    {\n        EEPROM.write(EEPROM_PAGE, settings);\n    }\n}\n\nvoid SerialConsole::handleShortCmd() {\n    uint8_t val;\n\n    switch (cmdBuffer[0]) {\n    case 'h':\n    case '?':\n    case 'H':\n        printMenu();\n        break;\n    case 'S':\n        Logger::console(\"Sleeping all connected boards\");\n        bms.sleepBoards();\n        break;\n    case 'W':\n        Logger::console(\"Waking up all connected boards\");\n        bms.wakeBoards();\n        break;\n    case 'C':\n        Logger::console(\"Clearing all faults\");\n        bms.clearFaults();\n        break;\n    case 'F':\n        bms.findBoards();\n        break;\n    case 'R':\n        Logger::console(\"Renumbering all boards.\");\n        bms.renumberBoardIDs();\n        break;\n    case 'B':\n        bms.balanceCells();\n        break;\n    case 'p':\n        if (whichDisplay == 1 && printPrettyDisplay) whichDisplay = 0;\n        else\n        {\n            printPrettyDisplay = !printPrettyDisplay;\n            if (printPrettyDisplay)\n            {\n                Logger::console(\"Enabling pack summary display, 5 second interval\");\n            }\n            else\n            {\n                Logger::console(\"No longer displaying pack summary.\");\n            }\n        }\n        break;\n    case 'd':\n        if (whichDisplay == 0 && printPrettyDisplay) whichDisplay = 1;\n        else\n        {\n            printPrettyDisplay = !printPrettyDisplay;\n            whichDisplay = 1;\n            if (printPrettyDisplay)\n            {\n                Logger::console(\"Enabling pack details display, 5 second interval\");\n            }\n            else\n            {\n                Logger::console(\"No longer displaying pack details.\");\n            }\n        }\n        break;\n    }\n}\n\n/*\n    if (SERIALCONSOLE.available()) \n    {\n        char y = SERIALCONSOLE.read();\n        switch (y)\n        {\n        case '1': //ascii 1\n            renumberBoardIDs();  // force renumber and read out\n            break;\n        case '2': //ascii 2\n            SERIALCONSOLE.println();\n            findBoards();\n            break;\n        case '3': //activate cell balance for 5 seconds \n            SERIALCONSOLE.println();\n            SERIALCONSOLE.println(\"Balancing\");\n            cellBalance();\n            break;\n      case '4': //clear all faults on all boards, required after Reset or FPO (first power on)\n       SERIALCONSOLE.println();\n       SERIALCONSOLE.println(\"Clearing Faults\");\n       clearFaults();\n      break;\n\n      case '5': //read out the status of first board\n       SERIALCONSOLE.println();\n       SERIALCONSOLE.println(\"Reading status\");\n       readStatus(1);\n      break;\n\n      case '6': //Read out the limit setpoints of first board\n       SERIALCONSOLE.println();\n       SERIALCONSOLE.println(\"Reading Setpoints\");\n       readSetpoint(1);\n       SERIALCONSOLE.println(OVolt);\n       SERIALCONSOLE.println(UVolt);\n       SERIALCONSOLE.println(Tset);\n      break; \n\n      case '0': //Send all boards into Sleep state\n       Serial.println();\n       Serial.println(\"Sleep Mode\");\n       sleepBoards();\n      break;\n\n      case '9'://Pull all boards out of Sleep state\n       Serial.println();\n       Serial.println(\"Wake Boards\");\n       wakeBoards();\n      break;\n        }\n    }\n */\n"
  },
  {
    "path": "SerialConsole.h",
    "content": "/*\n * SerialConsole.h\n *\nCopyright (c) 2017 EVTV / Collin Kidder\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n*/\n\n#ifndef SERIALCONSOLE_H_\n#define SERIALCONSOLE_H_\n\n#include \"config.h\"\n\nclass SerialConsole {\npublic:\n    SerialConsole();\n    void loop();\n    void printMenu();\n\nprotected:\n    enum CONSOLE_STATE\n    {\n        STATE_ROOT_MENU\n    };\n\nprivate:\n    char cmdBuffer[80];\n    int ptrBuffer;\n    int state;\n    int loopcount;\n    bool cancel;\n\n\n    void init();\n    void serialEvent();\n    void handleConsoleCmd();\n    void handleShortCmd();\n    void handleConfigCmd();\n};\n\n#endif /* SERIALCONSOLE_H_ */\n\n\n"
  },
  {
    "path": "SystemIO.cpp",
    "content": "#include <Arduino.h>\n#include \"config.h\"\n#include \"Logger.h\"\n#include \"SystemIO.h\"\n\nint DigitalInputs[4] = {DIN1, DIN2, DIN3, DIN4};\nint DigitalOutputs[4][2] = { {DOUT1_L, DOUT1_H}, {DOUT2_L, DOUT2_H}, {DOUT3_L, DOUT3_H}, {DOUT4_L, DOUT4_H} };\n\nSystemIO::SystemIO()\n{\n    \n}\n\nvoid SystemIO::setup()\n{\n    for (int x = 0; x < 4; x++) pinMode(DigitalInputs[x], INPUT);\n    \n    for (int y = 0; y < 4; y++) {\n        pinMode(DigitalOutputs[y][0], OUTPUT);\n        digitalWrite(DigitalOutputs[y][0], LOW);\n        pinMode(DigitalOutputs[y][1], OUTPUT);\n        digitalWrite(DigitalOutputs[y][1], LOW);\n    }\n}\n\nbool SystemIO::readInput(int pin)\n{\n    if (pin < 0 || pin > 3) return false;\n    return !digitalRead(DigitalInputs[pin]);\n}\n\nvoid SystemIO::setOutput(int pin, OUTPUTSTATE state)\n{\n    if (pin < 0 || pin > 3) return;\n    //first set it floating\n    digitalWrite(DigitalOutputs[pin][0], LOW);\n    digitalWrite(DigitalOutputs[pin][1], LOW);\n    delayMicroseconds(10); //give mosfets some time to turn off\n    if (state == HIGH_12V) digitalWrite(DigitalOutputs[pin][1], HIGH);\n    if (state == GND) digitalWrite(DigitalOutputs[pin][0], HIGH);\n}\n\nSystemIO systemIO;"
  },
  {
    "path": "SystemIO.h",
    "content": "#pragma once\n\nenum OUTPUTSTATE {\n    FLOATING = 0,\n    HIGH_12V = 1,\n    GND = 2\n};\n\nclass SystemIO\n{\npublic:\n    SystemIO();\n    void setup();\n    bool readInput(int pin);\n    void setOutput(int pin, OUTPUTSTATE state);\n    \nprivate:\n\n};\n\nextern SystemIO systemIO;\n"
  },
  {
    "path": "TeslaBMS.ino",
    "content": "#if defined (__arm__) && defined (__SAM3X8E__)\n#include <chip.h>\n#endif\n\n#include <Arduino.h>\n#include \"Logger.h\"\n#include \"SerialConsole.h\"\n#include \"BMSModuleManager.h\"\n#include \"SystemIO.h\"\n#include <due_can.h>\n#include <due_wire.h>\n#include <Wire_EEPROM.h>\n\n//#define BMS_BAUD  612500\n#define BMS_BAUD  617647\n//#define BMS_BAUD  608695\n\nBMSModuleManager bms;\nEEPROMSettings settings;\nSerialConsole console;\nuint32_t lastUpdate;\n\n//This code only applicable to Due to fixup lack of functionality in the arduino core.\n#if defined (__arm__) && defined (__SAM3X8E__)\nvoid serialSpecialInit(Usart *pUsart, uint32_t baudRate)\n{\n  // Reset and disable receiver and transmitter\n  pUsart->US_CR = UART_CR_RSTRX | UART_CR_RSTTX | UART_CR_RXDIS | UART_CR_TXDIS;\n\n  // Configure mode\n  pUsart->US_MR =  US_MR_CHRL_8_BIT | US_MR_NBSTOP_1_BIT | UART_MR_PAR_NO | US_MR_USART_MODE_NORMAL | \n                   US_MR_USCLKS_MCK | US_MR_CHMODE_NORMAL | US_MR_OVER; // | US_MR_INVDATA;\n\n  //Get the integer divisor that can provide the baud rate\n  int divisor = SystemCoreClock / baudRate;\n  int error1 = abs(baudRate - (SystemCoreClock / divisor)); //find out how close that is to the real baud\n  int error2 = abs(baudRate - (SystemCoreClock / (divisor + 1))); //see if bumping up one on the divisor makes it a better match\n\n  if (error2 < error1) divisor++;   //If bumping by one yielded a closer rate then use that instead\n\n  // Configure baudrate including the optional fractional divisor possible on USART\n  pUsart->US_BRGR = (divisor >> 3) | ((divisor & 7) << 16);\n\n  // Enable receiver and transmitter\n  pUsart->US_CR = UART_CR_RXEN | UART_CR_TXEN;\n}\n#endif\n\nvoid loadSettings()\n{\n    EEPROM.read(EEPROM_PAGE, settings);\n\n    if (settings.version != EEPROM_VERSION) //if settings are not the current version then erase them and set defaults\n    {\n        Logger::console(\"Resetting to factory defaults\");\n        settings.version = EEPROM_VERSION;\n        settings.checksum = 0;\n        settings.canSpeed = 500000;\n        settings.batteryID = 0x01; //in the future should be 0xFF to force it to ask for an address\n        settings.OverVSetpoint = 4.1f;\n        settings.UnderVSetpoint = 2.3f;\n        settings.OverTSetpoint = 65.0f;\n        settings.UnderTSetpoint = -10.0f;\n        settings.balanceVoltage = 3.9f;\n        settings.balanceHyst = 0.04f;\n        settings.logLevel = 2;\n        EEPROM.write(EEPROM_PAGE, settings);\n    }\n    else {\n        Logger::console(\"Using stored values from EEPROM\");\n    }\n        \n    Logger::setLoglevel((Logger::LogLevel)settings.logLevel);\n}\n\nvoid initializeCAN()\n{\n    uint32_t id;\n    Can0.begin(settings.canSpeed);\n    if (settings.batteryID < 0xF)\n    {\n        //Setup filter for direct access to our registered battery ID\n        id = (0xBAul << 20) + (((uint32_t)settings.batteryID & 0xF) << 16);\n        Can0.setRXFilter(0, id, 0x1FFF0000ul, true);\n        //Setup filter for request for all batteries to give summary data\n        id = (0xBAul << 20) + (0xFul << 16);\n        Can0.setRXFilter(1, id, 0x1FFF0000ul, true);\n    }\n}\n\nvoid setup() \n{\n    delay(4000);  //just for easy debugging. It takes a few seconds for USB to come up properly on most OS's\n    SERIALCONSOLE.begin(115200);\n    SERIALCONSOLE.println(\"Starting up!\");\n    SERIAL.begin(BMS_BAUD);\n#if defined (__arm__) && defined (__SAM3X8E__)\n    serialSpecialInit(USART0, BMS_BAUD); //required for Due based boards as the stock core files don't support 612500 baud.\n#endif\n\n    SERIALCONSOLE.println(\"Started serial interface to BMS.\");\n\n    pinMode(13, INPUT);\n\n    loadSettings();\n    initializeCAN();\n\n    systemIO.setup();\n\n    bms.renumberBoardIDs();\n\n    //Logger::setLoglevel(Logger::Debug);\n\n    lastUpdate = 0;\n\n    bms.clearFaults();\n}\n\nvoid loop() \n{\n    CAN_FRAME incoming;\n\n    console.loop();\n\n    if (millis() > (lastUpdate + 1000))\n    {    \n        lastUpdate = millis();\n        bms.balanceCells();\n        bms.getAllVoltTemp();\n    }\n\n    if (Can0.available()) {\n        Can0.read(incoming);\n        bms.processCANMsg(incoming);\n    }\n}\n\n"
  },
  {
    "path": "config.h",
    "content": "#pragma once\n\n#include <Arduino.h>\n\n//Set to the proper port for your USB connection - SerialUSB on Due (Native) or Serial for Due (Programming) or Teensy\n#define SERIALCONSOLE   SerialUSB\n\n//Define this to be the serial port the Tesla BMS modules are connected to.\n//On the Due you need to use a USART port (Serial1, Serial2, Serial3) and update the call to serialSpecialInit if not Serial1\n#define SERIAL  Serial1\n\n#define REG_DEV_STATUS      0\n#define REG_GPAI            1\n#define REG_VCELL1          3\n#define REG_VCELL2          5\n#define REG_VCELL3          7\n#define REG_VCELL4          9\n#define REG_VCELL5          0xB\n#define REG_VCELL6          0xD\n#define REG_TEMPERATURE1    0xF\n#define REG_TEMPERATURE2    0x11\n#define REG_ALERT_STATUS    0x20\n#define REG_FAULT_STATUS    0x21\n#define REG_COV_FAULT       0x22\n#define REG_CUV_FAULT       0x23\n#define REG_ADC_CTRL        0x30\n#define REG_IO_CTRL         0x31\n#define REG_BAL_CTRL        0x32\n#define REG_BAL_TIME        0x33\n#define REG_ADC_CONV        0x34\n#define REG_ADDR_CTRL       0x3B\n\n#define MAX_MODULE_ADDR     0x3E\n\n#define EEPROM_VERSION      0x10    //update any time EEPROM struct below is changed.\n#define EEPROM_PAGE         0\n\n#define DIN1                55\n#define DIN2                54\n#define DIN3                57\n#define DIN4                56\n#define DOUT4_H             2\n#define DOUT4_L             3\n#define DOUT3_H             4\n#define DOUT3_L             5\n#define DOUT2_H             6\n#define DOUT2_L             7\n#define DOUT1_H             8\n#define DOUT1_L             9\n\ntypedef struct {\n    uint8_t version;\n    uint8_t checksum;\n    uint32_t canSpeed;\n    uint8_t batteryID;  //which battery ID should this board associate as on the CAN bus\n    uint8_t logLevel;\n    float OverVSetpoint;\n    float UnderVSetpoint;\n    float OverTSetpoint;\n    float UnderTSetpoint;\n    float balanceVoltage;\n    float balanceHyst;\n} EEPROMSettings;"
  }
]