Reduce double battery define usage

This commit is contained in:
Jaakko Haakana 2025-06-01 11:20:18 +03:00
parent 2b4b86e8ee
commit de552e434e
4 changed files with 248 additions and 244 deletions

View file

@ -319,7 +319,6 @@ void init_serial() {
#endif // DEBUG_VIA_USB
}
#ifdef DOUBLE_BATTERY
void check_interconnect_available() {
if (datalayer.battery.status.voltage_dV == 0 || datalayer.battery2.status.voltage_dV == 0) {
return; // Both voltage values need to be available to start check
@ -339,7 +338,6 @@ void check_interconnect_available() {
set_event(EVENT_VOLTAGE_DIFFERENCE, (uint8_t)(voltage_diff / 10));
}
}
#endif // DOUBLE_BATTERY
void update_calculated_values() {
/* Update CPU temperature*/
@ -399,11 +397,11 @@ void update_calculated_values() {
}
}
#ifdef DOUBLE_BATTERY
/* Calculate active power based on voltage and current for battery 2*/
datalayer.battery2.status.active_power_W =
(datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100));
#endif // DOUBLE_BATTERY
if (battery2) {
/* Calculate active power based on voltage and current for battery 2*/
datalayer.battery2.status.active_power_W =
(datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100));
}
if (datalayer.battery.settings.soc_scaling_active) {
/** SOC Scaling
@ -445,57 +443,62 @@ void update_calculated_values() {
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
}
#ifdef DOUBLE_BATTERY
// If battery info is valid
if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) {
if (battery2) {
// If battery info is valid
if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) {
datalayer.battery2.info.reported_total_capacity_Wh = scaled_total_capacity;
// Scale remaining capacity based on scaled SOC
datalayer.battery2.status.reported_remaining_capacity_Wh = (scaled_total_capacity * scaled_soc) / 10000;
datalayer.battery2.info.reported_total_capacity_Wh = scaled_total_capacity;
// Scale remaining capacity based on scaled SOC
datalayer.battery2.status.reported_remaining_capacity_Wh = (scaled_total_capacity * scaled_soc) / 10000;
} else {
// Fallback if scaling cannot be performed
datalayer.battery2.info.reported_total_capacity_Wh = datalayer.battery2.info.total_capacity_Wh;
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
} else {
// Fallback if scaling cannot be performed
datalayer.battery2.info.reported_total_capacity_Wh = datalayer.battery2.info.total_capacity_Wh;
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
}
//Since we are running double battery, the scaled value of battery1 becomes the sum of battery1+battery2
//This way the inverter connected to the system sees both batteries as one large battery
datalayer.battery.info.reported_total_capacity_Wh += datalayer.battery2.info.reported_total_capacity_Wh;
datalayer.battery.status.reported_remaining_capacity_Wh +=
datalayer.battery2.status.reported_remaining_capacity_Wh;
}
//Since we are running double battery, the scaled value of battery1 becomes the sum of battery1+battery2
//This way the inverter connected to the system sees both batteries as one large battery
datalayer.battery.info.reported_total_capacity_Wh += datalayer.battery2.info.reported_total_capacity_Wh;
datalayer.battery.status.reported_remaining_capacity_Wh += datalayer.battery2.status.reported_remaining_capacity_Wh;
#endif // DOUBLE_BATTERY
} else { // soc_scaling_active == false. No SOC window wanted. Set scaled to same as real.
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
datalayer.battery.info.reported_total_capacity_Wh = datalayer.battery.info.total_capacity_Wh;
#ifdef DOUBLE_BATTERY
datalayer.battery2.status.reported_soc = datalayer.battery2.status.real_soc;
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
datalayer.battery2.info.reported_total_capacity_Wh = datalayer.battery2.info.total_capacity_Wh;
#endif
}
#ifdef DOUBLE_BATTERY
// Perform extra SOC sanity checks on double battery setups
if (datalayer.battery.status.real_soc < 100) { //If this battery is under 1.00%, use this as SOC instead of average
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
}
if (datalayer.battery2.status.real_soc < 100) { //If this battery is under 1.00%, use this as SOC instead of average
datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
if (battery2) {
datalayer.battery2.status.reported_soc = datalayer.battery2.status.real_soc;
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
datalayer.battery2.info.reported_total_capacity_Wh = datalayer.battery2.info.total_capacity_Wh;
}
}
if (datalayer.battery.status.real_soc > 9900) { //If this battery is over 99.00%, use this as SOC instead of average
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
if (battery2) {
// Perform extra SOC sanity checks on double battery setups
if (datalayer.battery.status.real_soc < 100) { //If this battery is under 1.00%, use this as SOC instead of average
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
}
if (datalayer.battery2.status.real_soc <
100) { //If this battery is under 1.00%, use this as SOC instead of average
datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
}
if (datalayer.battery.status.real_soc >
9900) { //If this battery is over 99.00%, use this as SOC instead of average
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
}
if (datalayer.battery2.status.real_soc >
9900) { //If this battery is over 99.00%, use this as SOC instead of average
datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
}
}
if (datalayer.battery2.status.real_soc > 9900) { //If this battery is over 99.00%, use this as SOC instead of average
datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
}
#endif // DOUBLE_BATTERY
// Check if millis has overflowed. Used in events to keep better track of time
if (currentMillis < lastMillisOverflowCheck) { // Overflow detected
datalayer.system.status.millisrolloverCount++;

View file

@ -169,9 +169,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame);
void transmit_can_battery(unsigned long currentMillis);
#endif
#ifdef DOUBLE_BATTERY
void update_values_battery2();
void handle_incoming_can_frame_battery2(CAN_frame rx_frame);
#endif
#endif

View file

@ -241,65 +241,65 @@ void update_machineryprotection() {
}
}
#ifdef DOUBLE_BATTERY // Additional Double-Battery safeties are checked here
// Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise a warning
// Additional Double-Battery safeties are checked here
if (battery2) {
// Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise a warning
// Pause function is on
if (emulator_pause_request_ON) {
datalayer.battery2.status.max_discharge_power_W = 0;
datalayer.battery2.status.max_charge_power_W = 0;
}
if (!datalayer.battery2.status.CAN_battery_still_alive) {
set_event(EVENT_CAN_BATTERY2_MISSING, can_config.battery_double);
} else {
datalayer.battery2.status.CAN_battery_still_alive--;
clear_event(EVENT_CAN_BATTERY2_MISSING);
}
// Too many malformed CAN messages recieved!
if (datalayer.battery2.status.CAN_error_counter > MAX_CAN_FAILURES) {
set_event(EVENT_CAN_CORRUPTED_WARNING, can_config.battery_double);
} else {
clear_event(EVENT_CAN_CORRUPTED_WARNING);
}
// Cell overvoltage, critical latching error without automatic reset. Requires user action.
if (datalayer.battery2.status.cell_max_voltage_mV >= datalayer.battery2.info.max_cell_voltage_mV) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
// Cell undervoltage, critical latching error without automatic reset. Requires user action.
if (datalayer.battery2.status.cell_min_voltage_mV <= datalayer.battery2.info.min_cell_voltage_mV) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
// Check diff between highest and lowest cell
cell_deviation_mV = (datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV);
if (cell_deviation_mV > datalayer.battery2.info.max_cell_voltage_deviation_mV) {
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
// Check if SOH% between the packs is too large
if ((datalayer.battery.status.soh_pptt != 9900) && (datalayer.battery2.status.soh_pptt != 9900)) {
// Both values available, check diff
uint16_t soh_diff_pptt;
if (datalayer.battery.status.soh_pptt > datalayer.battery2.status.soh_pptt) {
soh_diff_pptt = datalayer.battery.status.soh_pptt - datalayer.battery2.status.soh_pptt;
} else {
soh_diff_pptt = datalayer.battery2.status.soh_pptt - datalayer.battery.status.soh_pptt;
// Pause function is on
if (emulator_pause_request_ON) {
datalayer.battery2.status.max_discharge_power_W = 0;
datalayer.battery2.status.max_charge_power_W = 0;
}
if (soh_diff_pptt > MAX_SOH_DEVIATION_PPTT) {
set_event(EVENT_SOH_DIFFERENCE, (uint8_t)(MAX_SOH_DEVIATION_PPTT / 100));
if (!datalayer.battery2.status.CAN_battery_still_alive) {
set_event(EVENT_CAN_BATTERY2_MISSING, can_config.battery_double);
} else {
clear_event(EVENT_SOH_DIFFERENCE);
datalayer.battery2.status.CAN_battery_still_alive--;
clear_event(EVENT_CAN_BATTERY2_MISSING);
}
// Too many malformed CAN messages recieved!
if (datalayer.battery2.status.CAN_error_counter > MAX_CAN_FAILURES) {
set_event(EVENT_CAN_CORRUPTED_WARNING, can_config.battery_double);
} else {
clear_event(EVENT_CAN_CORRUPTED_WARNING);
}
// Cell overvoltage, critical latching error without automatic reset. Requires user action.
if (datalayer.battery2.status.cell_max_voltage_mV >= datalayer.battery2.info.max_cell_voltage_mV) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
// Cell undervoltage, critical latching error without automatic reset. Requires user action.
if (datalayer.battery2.status.cell_min_voltage_mV <= datalayer.battery2.info.min_cell_voltage_mV) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
// Check diff between highest and lowest cell
cell_deviation_mV = (datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV);
if (cell_deviation_mV > datalayer.battery2.info.max_cell_voltage_deviation_mV) {
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
// Check if SOH% between the packs is too large
if ((datalayer.battery.status.soh_pptt != 9900) && (datalayer.battery2.status.soh_pptt != 9900)) {
// Both values available, check diff
uint16_t soh_diff_pptt;
if (datalayer.battery.status.soh_pptt > datalayer.battery2.status.soh_pptt) {
soh_diff_pptt = datalayer.battery.status.soh_pptt - datalayer.battery2.status.soh_pptt;
} else {
soh_diff_pptt = datalayer.battery2.status.soh_pptt - datalayer.battery.status.soh_pptt;
}
if (soh_diff_pptt > MAX_SOH_DEVIATION_PPTT) {
set_event(EVENT_SOH_DIFFERENCE, (uint8_t)(MAX_SOH_DEVIATION_PPTT / 100));
} else {
clear_event(EVENT_SOH_DIFFERENCE);
}
}
}
#endif // DOUBLE_BATTERY
//Safeties verified, Zero charge/discharge ampere values incase any safety wrote the W to 0
if (datalayer.battery.status.max_discharge_power_W == 0) {
datalayer.battery.status.max_discharge_current_dA = 0;
@ -357,10 +357,10 @@ void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop, bo
emulator_pause_status = PAUSING;
datalayer.battery.status.max_discharge_power_W = 0;
datalayer.battery.status.max_charge_power_W = 0;
#ifdef DOUBLE_BATTERY
datalayer.battery2.status.max_discharge_power_W = 0;
datalayer.battery2.status.max_charge_power_W = 0;
#endif
if (battery2) {
datalayer.battery2.status.max_discharge_power_W = 0;
datalayer.battery2.status.max_charge_power_W = 0;
}
} else {
clear_event(EVENT_PAUSE_BEGIN);

View file

@ -16,21 +16,24 @@ String cellmonitor_processor(const String& var) {
content += ".cell { width: 48%; margin: 1%; padding: 10px; border: 1px solid white; text-align: center; }";
content += ".low-voltage { color: red; }"; // Style for low voltage text
content += ".voltage-values { margin-bottom: 10px; }"; // Style for voltage values section
#ifdef DOUBLE_BATTERY
content +=
"#graph, #graph2 {display: flex;align-items: flex-end;height: 200px;border: 1px solid #ccc;position: "
"relative;}";
#else
content += "#graph {display: flex;align-items: flex-end;height: 200px;border: 1px solid #ccc;position: relative;}";
#endif
if (battery2) {
content +=
"#graph, #graph2 {display: flex;align-items: flex-end;height: 200px;border: 1px solid #ccc;position: "
"relative;}";
} else {
content +=
"#graph {display: flex;align-items: flex-end;height: 200px;border: 1px solid #ccc;position: relative;}";
}
content +=
".bar {margin: 0 0px;background-color: blue;display: inline-block;position: relative;cursor: pointer;border: "
"1px solid white; /* Add this line */}";
#ifdef DOUBLE_BATTERY
content += "#valueDisplay, #valueDisplay2 {text-align: left;font-weight: bold;margin-top: 10px;}";
#else
content += "#valueDisplay {text-align: left;font-weight: bold;margin-top: 10px;}";
#endif
if (battery2) {
content += "#valueDisplay, #valueDisplay2 {text-align: left;font-weight: bold;margin-top: 10px;}";
} else {
content += "#valueDisplay {text-align: left;font-weight: bold;margin-top: 10px;}";
}
content += "</style>";
content += "<button onclick='home()'>Back to main page</button>";
@ -50,24 +53,24 @@ String cellmonitor_processor(const String& var) {
// Close the block
content += "</div>";
#ifdef DOUBLE_BATTERY
// Start a new block with a specific background color
content += "<div style='background-color: #303E41; padding: 10px; margin-bottom: 10px; border-radius: 50px'>";
if (battery2) {
// Start a new block with a specific background color
content += "<div style='background-color: #303E41; padding: 10px; margin-bottom: 10px; border-radius: 50px'>";
// Display max, min, and deviation voltage values
content += "<div id='voltageValues2' class='voltage-values'></div>";
// Display cells
content += "<div id='cellContainer2' class='container'></div>";
// Display bars
content += "<div id='graph2'></div>";
// Display single hovered value
content += "<div id='valueDisplay2'>Value: ...</div>";
// Display max, min, and deviation voltage values
content += "<div id='voltageValues2' class='voltage-values'></div>";
// Display cells
content += "<div id='cellContainer2' class='container'></div>";
// Display bars
content += "<div id='graph2'></div>";
// Display single hovered value
content += "<div id='valueDisplay2'>Value: ...</div>";
// Close the block
content += "</div>";
// Close the block
content += "</div>";
content += "<button onclick='home()'>Back to main page</button>";
#endif // DOUBLE_BATTERY
content += "<button onclick='home()'>Back to main page</button>";
}
content += "<script>";
// Populate cell data
@ -184,120 +187,120 @@ String cellmonitor_processor(const String& var) {
"available';";
content += "}";
#ifdef DOUBLE_BATTERY
// Populate cell data
content += "const data2 = [";
for (uint8_t i = 0u; i < datalayer.battery2.info.number_of_cells; i++) {
if (datalayer.battery2.status.cell_voltages_mV[i] == 0) {
continue;
if (battery2) {
// Populate cell data
content += "const data2 = [";
for (uint8_t i = 0u; i < datalayer.battery2.info.number_of_cells; i++) {
if (datalayer.battery2.status.cell_voltages_mV[i] == 0) {
continue;
}
content += String(datalayer.battery2.status.cell_voltages_mV[i]) + ",";
}
content += String(datalayer.battery2.status.cell_voltages_mV[i]) + ",";
content += "];";
content += "const min_mv2 = Math.min(...data2) - 20;";
content += "const max_mv2 = Math.max(...data2) + 20;";
content += "const min_index2 = data2.indexOf(Math.min(...data2));";
content += "const max_index2 = data2.indexOf(Math.max(...data2));";
content += "const graphContainer2 = document.getElementById('graph2');";
content += "const valueDisplay2 = document.getElementById('valueDisplay2');";
content += "const cellContainer2 = document.getElementById('cellContainer2');";
// Arduino-style map() function
content +=
"function map2(value, fromLow, fromHigh, toLow, toHigh) {return (value - fromLow) * (toHigh - toLow) / "
"(fromHigh - fromLow) + toLow;}";
// Mark cell and bar with highest/lowest values
content +=
"function checkMinMax2(cell2, bar2, index2) {if ((index2 == min_index2) || (index2 == max_index2)) "
"{cell2.style.borderColor = 'red';bar2.style.borderColor = 'red';}}";
// Bar function. Basically get the mV, scale the height and add a bar div to its container
content +=
"function createBars2(data2) {"
"data2.forEach((mV, index2) => {"
"const bar2 = document.createElement('div');"
"const mV_limited2 = map2(mV, min_mv2, max_mv2, 20, 200);"
"bar2.className = 'bar';"
"bar2.id = `barIndex2${index2}`;"
"bar2.style.height = `${mV_limited2}px`;"
"bar2.style.width = `${750/data2.length}px`;"
"const cell2 = document.getElementById(`cellIndex2${index2}`);"
"checkMinMax2(cell2, bar2, index2);"
"bar2.addEventListener('mouseenter', () => {"
"valueDisplay2.textContent = `Value: ${mV}`;"
"bar2.style.backgroundColor = `lightblue`;"
"cell2.style.backgroundColor = `blue`;"
"});"
"bar2.addEventListener('mouseleave', () => {"
"valueDisplay2.textContent = 'Value: ...';"
"bar2.style.backgroundColor = `blue`;"
"cell2.style.removeProperty('background-color');"
"});"
"graphContainer2.appendChild(bar2);"
"});"
"}";
// Cell population function. For each value, add a cell block with its value
content +=
"function createCells2(data2) {"
"data2.forEach((mV, index2) => {"
"const cell2 = document.createElement('div');"
"cell2.className = 'cell';"
"cell2.id = `cellIndex2${index2}`;"
"let cellContent2 = `Cell ${index2 + 1}<br>${mV} mV`;"
"if (mV < 3000) {"
"cellContent2 = `<span class='low-voltage'>${cellContent2}</span>`;"
"}"
"cell2.innerHTML = cellContent2;"
"cell2.addEventListener('mouseenter', () => {"
"let bar2 = document.getElementById(`barIndex2${index2}`);"
"valueDisplay2.textContent = `Value: ${mV}`;"
"bar2.style.backgroundColor = `lightblue`;"
"cell2.style.backgroundColor = `blue`;"
"});"
"cell2.addEventListener('mouseleave', () => {"
"let bar2 = document.getElementById(`barIndex2${index2}`);"
"bar2.style.backgroundColor = `blue`;"
"cell2.style.removeProperty('background-color');"
"});"
"cellContainer2.appendChild(cell2);"
"});"
"}";
// On fetch, update the header of max/min/deviation client-side for consistency
content +=
"function updateVoltageValues2(data2) {"
"const min_mv2 = Math.min(...data2);"
"const max_mv2 = Math.max(...data2);"
"const cell_dev2 = max_mv2 - min_mv2;"
"const voltVal2 = document.getElementById('voltageValues2');"
"voltVal2.innerHTML = `Max Voltage : ${max_mv2} mV<br>Min Voltage: ${min_mv2} mV<br>Voltage Deviation: "
"${cell_dev2} mV`"
"}";
// If we have values, do the thing. Otherwise, display friendly message and wait
content += "if (data2.length != 0) {";
content += "createCells2(data2);";
content += "createBars2(data2);";
content += "updateVoltageValues2(data2);";
content += "}";
content += "else {";
content +=
"document.getElementById('voltageValues2').textContent = 'Cell information not yet fetched, or information "
"not "
"available';";
content += "}";
}
content += "];";
content += "const min_mv2 = Math.min(...data2) - 20;";
content += "const max_mv2 = Math.max(...data2) + 20;";
content += "const min_index2 = data2.indexOf(Math.min(...data2));";
content += "const max_index2 = data2.indexOf(Math.max(...data2));";
content += "const graphContainer2 = document.getElementById('graph2');";
content += "const valueDisplay2 = document.getElementById('valueDisplay2');";
content += "const cellContainer2 = document.getElementById('cellContainer2');";
// Arduino-style map() function
content +=
"function map2(value, fromLow, fromHigh, toLow, toHigh) {return (value - fromLow) * (toHigh - toLow) / "
"(fromHigh - fromLow) + toLow;}";
// Mark cell and bar with highest/lowest values
content +=
"function checkMinMax2(cell2, bar2, index2) {if ((index2 == min_index2) || (index2 == max_index2)) "
"{cell2.style.borderColor = 'red';bar2.style.borderColor = 'red';}}";
// Bar function. Basically get the mV, scale the height and add a bar div to its container
content +=
"function createBars2(data2) {"
"data2.forEach((mV, index2) => {"
"const bar2 = document.createElement('div');"
"const mV_limited2 = map2(mV, min_mv2, max_mv2, 20, 200);"
"bar2.className = 'bar';"
"bar2.id = `barIndex2${index2}`;"
"bar2.style.height = `${mV_limited2}px`;"
"bar2.style.width = `${750/data2.length}px`;"
"const cell2 = document.getElementById(`cellIndex2${index2}`);"
"checkMinMax2(cell2, bar2, index2);"
"bar2.addEventListener('mouseenter', () => {"
"valueDisplay2.textContent = `Value: ${mV}`;"
"bar2.style.backgroundColor = `lightblue`;"
"cell2.style.backgroundColor = `blue`;"
"});"
"bar2.addEventListener('mouseleave', () => {"
"valueDisplay2.textContent = 'Value: ...';"
"bar2.style.backgroundColor = `blue`;"
"cell2.style.removeProperty('background-color');"
"});"
"graphContainer2.appendChild(bar2);"
"});"
"}";
// Cell population function. For each value, add a cell block with its value
content +=
"function createCells2(data2) {"
"data2.forEach((mV, index2) => {"
"const cell2 = document.createElement('div');"
"cell2.className = 'cell';"
"cell2.id = `cellIndex2${index2}`;"
"let cellContent2 = `Cell ${index2 + 1}<br>${mV} mV`;"
"if (mV < 3000) {"
"cellContent2 = `<span class='low-voltage'>${cellContent2}</span>`;"
"}"
"cell2.innerHTML = cellContent2;"
"cell2.addEventListener('mouseenter', () => {"
"let bar2 = document.getElementById(`barIndex2${index2}`);"
"valueDisplay2.textContent = `Value: ${mV}`;"
"bar2.style.backgroundColor = `lightblue`;"
"cell2.style.backgroundColor = `blue`;"
"});"
"cell2.addEventListener('mouseleave', () => {"
"let bar2 = document.getElementById(`barIndex2${index2}`);"
"bar2.style.backgroundColor = `blue`;"
"cell2.style.removeProperty('background-color');"
"});"
"cellContainer2.appendChild(cell2);"
"});"
"}";
// On fetch, update the header of max/min/deviation client-side for consistency
content +=
"function updateVoltageValues2(data2) {"
"const min_mv2 = Math.min(...data2);"
"const max_mv2 = Math.max(...data2);"
"const cell_dev2 = max_mv2 - min_mv2;"
"const voltVal2 = document.getElementById('voltageValues2');"
"voltVal2.innerHTML = `Max Voltage : ${max_mv2} mV<br>Min Voltage: ${min_mv2} mV<br>Voltage Deviation: "
"${cell_dev2} mV`"
"}";
// If we have values, do the thing. Otherwise, display friendly message and wait
content += "if (data2.length != 0) {";
content += "createCells2(data2);";
content += "createBars2(data2);";
content += "updateVoltageValues2(data2);";
content += "}";
content += "else {";
content +=
"document.getElementById('voltageValues2').textContent = 'Cell information not yet fetched, or information not "
"available';";
content += "}";
#endif //DOUBLE_BATTERY
// Automatic refresh is nice
content += "setTimeout(function(){ location.reload(true); }, 20000);";