diff --git a/ESPController/include/DIYBMSServer.h b/ESPController/include/DIYBMSServer.h index 391c57d..95fed54 100644 --- a/ESPController/include/DIYBMSServer.h +++ b/ESPController/include/DIYBMSServer.h @@ -35,6 +35,8 @@ class DIYBMSServer static AsyncWebServer *_myserver; static String UUIDString; + static void PrintStreamComma(AsyncResponseStream *response,const __FlashStringHelper *ifsh, uint32_t value); + static void handleNotFound(AsyncWebServerRequest *request); static void monitor2(AsyncWebServerRequest *request); static void monitor3(AsyncWebServerRequest *request); diff --git a/ESPController/src/DIYBMSServer.cpp b/ESPController/src/DIYBMSServer.cpp index a04b50e..f489b23 100644 --- a/ESPController/src/DIYBMSServer.cpp +++ b/ESPController/src/DIYBMSServer.cpp @@ -308,7 +308,8 @@ void DIYBMSServer::saveRuleConfiguration(AsyncWebServerRequest *request) if (request->hasParam(name, true)) { AsyncWebParameter *p1 = request->getParam(name, true); - mysettings.rulerelaystate[rule][i] = p1->value().equals("X") ? RELAY_X : p1->value().equals("On") ? RelayState::RELAY_ON : RelayState::RELAY_OFF; + mysettings.rulerelaystate[rule][i] = p1->value().equals("X") ? RELAY_X : p1->value().equals("On") ? RelayState::RELAY_ON + : RelayState::RELAY_OFF; } } @@ -686,6 +687,9 @@ void DIYBMSServer::settings(AsyncWebServerRequest *request) settings["MinutesTimeZone"] = mysettings.minutesTimeZone; settings["DST"] = mysettings.daylight; + settings["FreeHeap"] = ESP.getFreeHeap(); + settings["FreeBlockSize"] = ESP.getMaxFreeBlockSize(); + #if defined(ESP8266) settings["now"] = now(); #endif @@ -817,188 +821,340 @@ void DIYBMSServer::handleRestartController(AsyncWebServerRequest *request) void DIYBMSServer::monitor3(AsyncWebServerRequest *request) { - DynamicJsonDocument doc(maximum_controller_cell_modules * 50); - + //DynamicJsonDocument doc(maximum_controller_cell_modules * 50); AsyncResponseStream *response = request->beginResponseStream("application/json"); - //This service exists to ensure that monitor2 is keep as small as possible - //for ESP8266 memory limitations, these values are only updated very infrequently (lazy timer) - JsonArray badpacket = doc.createNestedArray("badpacket"); - JsonArray balancecurrentcount = doc.createNestedArray("balcurrent"); - JsonArray packetreceivedcount = doc.createNestedArray("pktrecvd"); + uint8_t totalModules = mysettings.totalNumberOfBanks * mysettings.totalNumberOfSeriesModules; + uint8_t comma = totalModules - 1; - //doc["FreeHeap"] = ESP.getFreeHeap(); - //doc["FreeBlockSize"] = ESP.getMaxFreeBlockSize(); + response->print("{\"badpacket\":["); - uint8_t totalModules = mysettings.totalNumberOfBanks * mysettings.totalNumberOfSeriesModules; for (uint8_t i = 0; i < totalModules; i++) { if (cmi[i].valid) { - //Just for debug, move these to config packets instead - balancecurrentcount.add(cmi[i].BalanceCurrentCount); - packetreceivedcount.add(cmi[i].PacketReceivedCount); - badpacket.add(cmi[i].badPacketCount); + response->print(cmi[i].badPacketCount); } else { //Return NULL - balancecurrentcount.add((char *)0); - packetreceivedcount.add((char *)0); - badpacket.add((char *)0); + response->print("null"); + } + if (i < comma) + { + response->print(','); } } - serializeJson(doc, *response); + response->print("],\"balcurrent\":["); + + for (uint8_t i = 0; i < totalModules; i++) + { + if (cmi[i].valid) + { + response->print(cmi[i].BalanceCurrentCount); + } + else + { + //Return NULL + response->print("null"); + } + if (i < comma) + { + response->print(','); + } + } + + response->print("],\"pktrecvd\":["); + + for (uint8_t i = 0; i < totalModules; i++) + { + if (cmi[i].valid) + { + response->print(cmi[i].PacketReceivedCount); + } + else + { + //Return NULL + response->print("null"); + } + if (i < comma) + { + response->print(','); + } + } + response->print("]}"); + request->send(response); } +void DIYBMSServer::PrintStreamComma(AsyncResponseStream *response, const __FlashStringHelper *ifsh, uint32_t value) +{ + response->print(ifsh); + response->print(value); + response->print(','); +} + void DIYBMSServer::monitor2(AsyncWebServerRequest *request) { - DynamicJsonDocument doc(maximum_controller_cell_modules * 140); + uint8_t totalModules = mysettings.totalNumberOfBanks * mysettings.totalNumberOfSeriesModules; + const char comma = ','; + const char *null = "null"; - if (doc.capacity() == 0) + AsyncResponseStream *response = request->beginResponseStream("application/json"); + + PrintStreamComma(response, F("{\"banks\":"), mysettings.totalNumberOfBanks); + PrintStreamComma(response, F("\"seriesmodules\":"), mysettings.totalNumberOfSeriesModules); + PrintStreamComma(response, F("\"sent\":"), prg.packetsGenerated); + PrintStreamComma(response, F("\"received\":"), receiveProc.packetsReceived); + PrintStreamComma(response, F("\"modulesfnd\":"), receiveProc.totalModulesFound); + PrintStreamComma(response, F("\"badcrc\":"), receiveProc.totalCRCErrors); + PrintStreamComma(response, F("\"ignored\":"), receiveProc.totalNotProcessedErrors); + PrintStreamComma(response, F("\"roundtrip\":"), receiveProc.packetTimerMillisecond); + PrintStreamComma(response, F("\"oos\":"), receiveProc.totalOutofSequenceErrors); + + response->print(F("\"errors\":[")); + for (size_t i = 0; i < sizeof(rules.ErrorCodes); i++) { - //If memory allocation fails, swap to a small JSON document - //so the interface can report the error. - AsyncResponseStream *response = request->beginResponseStream("application/json"); - DynamicJsonDocument doc2(512); - - doc2["banks"] = mysettings.totalNumberOfBanks; - doc2["seriesmodules"] = mysettings.totalNumberOfSeriesModules; - JsonArray errors = doc2.createNestedArray("errors"); - //JsonArray warnings = doc2.createNestedArray("warnings"); - errors.add(InternalErrorCode::ControllerMemoryError); - doc2["sent"] = prg.packetsGenerated; - doc2["received"] = receiveProc.packetsReceived; - doc2["modulesfnd"] = receiveProc.totalModulesFound; - doc2["badcrc"] = receiveProc.totalCRCErrors; - doc2["ignored"] = receiveProc.totalNotProcessedErrors; - doc2["roundtrip"] = receiveProc.packetTimerMillisecond; - doc2["oos"] = receiveProc.totalOutofSequenceErrors; - - serializeJson(doc2, *response); - request->send(response); + if (rules.ErrorCodes[i] != InternalErrorCode::NoError) + { + //Comma if not zero + if (i) + response->print(comma); + + response->print(rules.ErrorCodes[i]); + } } - else + + response->print("],"); + + response->print(F("\"warnings\":[")); + for (size_t i = 0; i < sizeof(rules.WarningCodes); i++) { - AsyncResponseStream *response = request->beginResponseStream("application/json"); + if (rules.WarningCodes[i] != InternalWarningCode::NoWarning) + { + //Comma if not zero + if (i) + response->print(comma); - doc["banks"] = mysettings.totalNumberOfBanks; - doc["seriesmodules"] = mysettings.totalNumberOfSeriesModules; - JsonArray errors = doc.createNestedArray("errors"); - for (size_t i = 0; i < sizeof(rules.ErrorCodes); i++) + response->print(rules.WarningCodes[i]); + } + } + response->print("],"); + + //voltages + response->print(F("\"voltages\":[")); + + for (uint8_t i = 0; i < totalModules; i++) + { + //Comma if not zero + if (i) + response->print(comma); + + if (cmi[i].valid) { - if (rules.ErrorCodes[i] != InternalErrorCode::NoError) - { - errors.add(rules.ErrorCodes[i]); - } + response->print(cmi[i].voltagemV); } + else + { + //Module is not yet valid so return null values... + response->print(null); + } + } + response->print("],"); + + response->print(F("\"minvoltages\":[")); + + for (uint8_t i = 0; i < totalModules; i++) + { + //Comma if not zero + if (i) + response->print(comma); - JsonArray warnings = doc.createNestedArray("warnings"); - for (size_t i = 0; i < sizeof(rules.WarningCodes); i++) + if (cmi[i].valid) { - if (rules.WarningCodes[i] != InternalWarningCode::NoWarning) - { - warnings.add(rules.WarningCodes[i]); - } + response->print(cmi[i].voltagemVMin); + } + else + { + //Module is not yet valid so return null values... + response->print(null); } + } + response->print("],"); - doc["sent"] = prg.packetsGenerated; - doc["received"] = receiveProc.packetsReceived; - doc["modulesfnd"] = receiveProc.totalModulesFound; - doc["badcrc"] = receiveProc.totalCRCErrors; - doc["ignored"] = receiveProc.totalNotProcessedErrors; - doc["roundtrip"] = receiveProc.packetTimerMillisecond; - doc["oos"] = receiveProc.totalOutofSequenceErrors; + //maxvoltages - uint8_t totalModules = mysettings.totalNumberOfBanks * mysettings.totalNumberOfSeriesModules; + response->print(F("\"maxvoltages\":[")); + + for (uint8_t i = 0; i < totalModules; i++) + { + //Comma if not zero + if (i) + response->print(comma); + + if (cmi[i].valid) + { + response->print(cmi[i].voltagemVMax); + } + else + { + //Module is not yet valid so return null values... + response->print(null); + } + } + response->print("]"); - JsonArray voltages = doc.createNestedArray("voltages"); + response->print(comma); - JsonArray minvoltages = doc.createNestedArray("minvoltages"); - JsonArray maxvoltages = doc.createNestedArray("maxvoltages"); + //inttemp + response->print(F("\"inttemp\":[")); - JsonArray bypass = doc.createNestedArray("bypass"); - JsonArray bypasshot = doc.createNestedArray("bypasshot"); - JsonArray inttemp = doc.createNestedArray("inttemp"); - JsonArray exttemp = doc.createNestedArray("exttemp"); - JsonArray bypasspwm = doc.createNestedArray("bypasspwm"); + for (uint8_t i = 0; i < totalModules; i++) + { + //Comma if not zero + if (i) + response->print(comma); - for (uint8_t i = 0; i < totalModules; i++) + if (cmi[i].valid && cmi[i].internalTemp != -40) { - if (cmi[i].valid) - { - voltages.add(cmi[i].voltagemV); + response->print(cmi[i].internalTemp); + } + else + { + //Module is not yet valid so return null values... + response->print(null); + } + } + response->print("]"); - if (totalModules <= 64) - { - //To preserve memory, only return these parameters when there are less than =64 modules - minvoltages.add(cmi[i].voltagemVMin); - maxvoltages.add(cmi[i].voltagemVMax); - } + response->print(comma); - if (cmi[i].internalTemp != -40) - { - inttemp.add(cmi[i].internalTemp); - } - else - { - inttemp.add((char *)0); - } + //exttemp + response->print(F("\"exttemp\":[")); - if (cmi[i].externalTemp != -40) - { - exttemp.add(cmi[i].externalTemp); - } - else - { - exttemp.add((char *)0); - } + for (uint8_t i = 0; i < totalModules; i++) + { + //Comma if not zero + if (i) + response->print(comma); - bypasspwm.add(cmi[i].inBypass ? cmi[i].PWMValue : 0); - //Convert boolean to 1 or 0 to save bandwidth (every byte counts on this request) - bypass.add(cmi[i].inBypass ? 1 : 0); - bypasshot.add(cmi[i].bypassOverTemp ? 1 : 0); - } - else - { - //Module is not yet valid so return null values... - voltages.add((char *)0); - if (totalModules <= 64) - { - minvoltages.add((char *)0); - maxvoltages.add((char *)0); - //badpacket.add(0); - } - inttemp.add((char *)0); - exttemp.add((char *)0); - bypasspwm.add(0); - //Convert boolean to 1 or 0 to save bandwidth (every byte counts on this request) - bypass.add(0); - bypasshot.add(0); - } + if (cmi[i].valid && cmi[i].externalTemp != -40) + { + response->print(cmi[i].externalTemp); } + else + { + //Module is not yet valid so return null values... + response->print(null); + } + } + response->print(']'); + + response->print(comma); + + //bypass + response->print(F("\"bypass\":[")); + + for (uint8_t i = 0; i < totalModules; i++) + { + //Comma if not zero + if (i) + response->print(comma); - JsonArray bankvoltage = doc.createNestedArray("bankv"); - JsonArray voltagerange = doc.createNestedArray("voltrange"); - for (uint8_t b = 0; b < mysettings.totalNumberOfBanks; b++) + if (cmi[i].valid && cmi[i].inBypass) { - bankvoltage.add(rules.packvoltage[b]); - voltagerange.add(rules.VoltageRangeInBank(b)); + response->print('1'); } + else + { + response->print('0'); + } + } + response->print("]"); - //Current reading in mA - JsonArray current = doc.createNestedArray("current"); - //current.add(10000); - //NULL - current.add((char *)0); + response->print(comma); - response->addHeader("Cache-Control", "no-store"); + //bypasshot + response->print(F("\"bypasshot\":[")); - serializeJson(doc, *response); - request->send(response); + for (uint8_t i = 0; i < totalModules; i++) + { + //Comma if not zero + if (i) + response->print(comma); + + if (cmi[i].valid && cmi[i].bypassOverTemp) + { + response->print('1'); + } + else + { + response->print('0'); + } } + response->print(']'); + + response->print(comma); + + //bypasspwm + response->print(F("\"bypasspwm\":[")); + + for (uint8_t i = 0; i < totalModules; i++) + { + //Comma if not zero + if (i) + response->print(comma); + + if (cmi[i].valid && cmi[i].inBypass) + { + response->print(cmi[i].PWMValue); + } + else + { + response->print('0'); + } + } + response->print(']'); + + response->print(comma); + + //bypasspwm + response->print(F("\"bankv\":[")); + + for (uint8_t i = 0; i < mysettings.totalNumberOfBanks; i++) + { + //Comma if not zero + if (i) + response->print(comma); + + response->print(rules.packvoltage[i]); + } + response->print("]"); + + response->print(comma); + + //bypasspwm + response->print(F("\"voltrange\":[")); + + for (uint8_t i = 0; i < mysettings.totalNumberOfBanks; i++) + { + //Comma if not zero + if (i) + response->print(comma); + + response->print(rules.VoltageRangeInBank(i)); + } + response->print("]"); + + response->print(comma); + response->print(F("\"current\":[")); + response->print(null); + response->print("]"); + + //The END... + response->print('}'); + request->send(response); } String DIYBMSServer::TemplateProcessor(const String &var) diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index e2e3635..6a57b01 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -1554,15 +1554,15 @@ void setup() mqttClient.setCredentials(mysettings.mqtt_username, mysettings.mqtt_password); } - //Ensure we service the cell modules every 6 or 10 seconds, depending on number of cells being serviced + //Ensure we service the cell modules every 5 or 10 seconds, depending on number of cells being serviced //slower stops the queues from overflowing when a lot of cells are being monitored - myTimer.attach((TotalNumberOfCells() <= maximum_cell_modules_per_packet) ? 6:10, timerEnqueueCallback); + myTimer.attach((TotalNumberOfCells() <= maximum_cell_modules_per_packet) ? 5:10, timerEnqueueCallback); //Process rules every 5 seconds myTimerRelay.attach(5, timerProcessRules); //We process the transmit queue every 1 second (this needs to be lower delay than the queue fills) - //and slower than it takes a single module to process a command (about 300ms) + //and slower than it takes a single module to process a command (about 200ms @ 2400baud) myTransmitTimer.attach(1, timerTransmitCallback); //Service reply queue diff --git a/ESPController/web_src/default.htm b/ESPController/web_src/default.htm index f466ff2..c6057b0 100644 --- a/ESPController/web_src/default.htm +++ b/ESPController/web_src/default.htm @@ -114,6 +114,8 @@

Platform & Version

Processor: %PLATFORM%

Version: %GIT_VERSION%

Compiled: %COMPILE_DATE_TIME%

+

Free block size:

+

Free heap:

diff --git a/ESPController/web_src/pagecode.js b/ESPController/web_src/pagecode.js index c032294..97def27 100644 --- a/ESPController/web_src/pagecode.js +++ b/ESPController/web_src/pagecode.js @@ -128,13 +128,17 @@ function queryBMS() { if (v > maxVoltage) { maxVoltage = v; } if (v < minVoltage) { minVoltage = v; } - voltagesmin.push((parseFloat(jsondata.minvoltages[i]) / 1000.0)); - voltagesmax.push((parseFloat(jsondata.maxvoltages[i]) / 1000.0)); + if (jsondata.minvoltages) { + voltagesmin.push((parseFloat(jsondata.minvoltages[i]) / 1000.0)); + } + if (jsondata.maxvoltages) { + voltagesmax.push((parseFloat(jsondata.maxvoltages[i]) / 1000.0)); + } bank.push(bankNumber); cells.push(i); - + cellsInBank++; if (cellsInBank == jsondata.seriesmodules) { cellsInBank = 0; @@ -144,7 +148,7 @@ function queryBMS() { color = jsondata.bypasshot[i] == 1 ? red : stdcolor; tempint.push({ value: jsondata.inttemp[i], itemStyle: { color: color } }); tempext.push({ value: (jsondata.exttemp[i] == -40 ? 0 : jsondata.exttemp[i]), itemStyle: { color: stdcolor } }); - pwm.push({ value: jsondata.bypasspwm[i] == 0 ? null : Math.trunc(jsondata.bypasspwm[i]/255*100) }); + pwm.push({ value: jsondata.bypasspwm[i] == 0 ? null : Math.trunc(jsondata.bypasspwm[i] / 255 * 100) }); } } @@ -238,8 +242,16 @@ function queryBMS() { $.each(cells, function (index, value) { var columns = $(rows[index]).find("td"); $(columns[2]).html(voltages[index].value.toFixed(3)); - $(columns[3]).html(voltagesmin[index].toFixed(3)); - $(columns[4]).html(voltagesmax[index].toFixed(3)); + if (voltagesmin.length > 0) { + $(columns[3]).html(voltagesmin[index].toFixed(3)); + } else { + $(columns[3]).html("n/a"); + } + if (voltagesmax.length > 0) { + $(columns[4]).html(voltagesmax[index].toFixed(3)); + } else { + $(columns[4]).html("n/a"); + } $(columns[5]).html(tempint[index].value); $(columns[6]).html(tempext[index].value); $(columns[7]).html(pwm[index].value); @@ -252,7 +264,7 @@ function queryBMS() { //packets as small as possible - $.getJSON("monitor3.json", function (jsondata) { + $.getJSON("monitor3.json", function (jsondata) { var tbody = $("#modulesRows"); var rows = $(tbody).find("tr"); $.each(cells, function (index, value) { @@ -724,6 +736,8 @@ $(function () { $.getJSON("settings.json", function (data) { + $("#FreeBlockSize").html(data.settings.FreeBlockSize); + $("#FreeHeap").html(data.settings.FreeHeap); $("#aboutPage").show(); }).fail(function () { } );