Merge pull request #1433 from dalathegreat/bugfix/double-UI-bugs

Improvement: UI visualization of contactor states
This commit is contained in:
Daniel Öster 2025-08-23 21:17:01 +03:00 committed by GitHub
commit 3087ff9caf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 107 additions and 124 deletions

View file

@ -70,7 +70,7 @@ class KiaHyundai64Battery : public CanBattery {
uint16_t CellVoltMin_mV = 3700;
uint16_t allowedDischargePower = 0;
uint16_t allowedChargePower = 0;
uint16_t batteryVoltage = 0;
uint16_t batteryVoltage = 3700;
uint16_t inverterVoltageFrameHigh = 0;
uint16_t inverterVoltage = 0;
uint16_t cellvoltages_mv[98];

View file

@ -55,7 +55,7 @@ const int OFF = 0;
#define OFF 1
#endif //NC_CONTACTORS
#define MAX_ALLOWED_FAULT_TICKS 1000
#define MAX_ALLOWED_FAULT_TICKS 1000 //1000 = 10 seconds
#define NEGATIVE_CONTACTOR_TIME_MS \
500 // Time after negative contactor is turned on, to start precharge (not actual precharge time!)
#define PRECHARGE_COMPLETED_TIME_MS \
@ -192,7 +192,7 @@ void handle_contactors() {
set(negPin, OFF, PWM_OFF_DUTY);
set(posPin, OFF, PWM_OFF_DUTY);
set_event(EVENT_ERROR_OPEN_CONTACTOR, 0);
datalayer.system.status.contactors_engaged = false;
datalayer.system.status.contactors_engaged = 2;
return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured)
}
@ -201,10 +201,9 @@ void handle_contactors() {
set(prechargePin, OFF);
set(negPin, OFF, PWM_OFF_DUTY);
set(posPin, OFF, PWM_OFF_DUTY);
datalayer.system.status.contactors_engaged = false;
datalayer.system.status.contactors_engaged = 0;
if (datalayer.system.status.battery_allows_contactor_closing &&
datalayer.system.status.inverter_allows_contactor_closing &&
if (datalayer.system.status.inverter_allows_contactor_closing &&
!datalayer.system.settings.equipment_stop_active) {
contactorStatus = START_PRECHARGE;
}
@ -263,7 +262,7 @@ void handle_contactors() {
set(posPin, ON, PWM_HOLD_DUTY);
dbg_contactors("PRECHARGE_OFF");
contactorStatus = COMPLETED;
datalayer.system.status.contactors_engaged = true;
datalayer.system.status.contactors_engaged = 1;
}
break;
default:

View file

@ -310,8 +310,8 @@ struct DATALAYER_SYSTEM_STATUS_TYPE {
/** True if the inverter allows for the contactors to close */
bool inverter_allows_contactor_closing = true;
/** True if the contactor controlled by battery-emulator is closed */
bool contactors_engaged = false;
/** 0 if starting up, 1 if contactors engaged, 2 if the contactors controlled by battery-emulator is opened */
uint8_t contactors_engaged = 0;
/** True if the contactor controlled by battery-emulator is closed. Determined by check_interconnect_available(); if voltage is OK */
bool contactors_battery2_engaged = false;

View file

@ -271,8 +271,8 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) {
case EVENT_INTERFACE_MISSING:
return "Configuration trying to use CAN interface not baked into the software. Recompile software!";
case EVENT_ERROR_OPEN_CONTACTOR:
return "Too much time spent in error state. Opening contactors, not safe to continue charging. "
"Check other error code for reason!";
return "Too much time spent in error state. Opening contactors, not safe to continue. "
"Check other active ERROR code for reason. Reboot emulator after problem is solved!";
case EVENT_MODBUS_INVERTER_MISSING:
return "Modbus inverter has not sent any data. Inspect communication wiring!";
case EVENT_NO_ENABLE_DETECTED:

View file

@ -836,6 +836,28 @@ String processor(const String& var) {
content += "button:hover { background-color: #3A4A52; }";
content += "h2 { font-size: 1.2em; margin: 0.3em 0 0.5em 0; }";
content += "h4 { margin: 0.6em 0; line-height: 1.2; }";
//content += ".tooltip { position: relative; display: inline-block; }";
content += ".tooltip .tooltiptext {";
content += " visibility: hidden;";
content += " width: 200px;";
content += " background-color: #3A4A52;"; // Matching your button hover color
content += " color: white;";
content += " text-align: center;";
content += " border-radius: 6px;";
content += " padding: 8px;";
content += " position: absolute;";
content += " z-index: 1;";
content += " bottom: 125%;";
content += " left: 50%;";
content += " margin-left: -100px;";
content += " opacity: 0;";
content += " transition: opacity 0.3s;";
content += " font-size: 0.9em;";
content += " font-weight: normal;";
content += " line-height: 1.4;";
content += "}";
content += ".tooltip:hover .tooltiptext { visibility: visible; opacity: 1; }";
content += ".tooltip-icon { color: #505E67; cursor: help; }"; // Matching your button color
content += "</style>";
// Compact header
@ -1112,58 +1134,6 @@ String processor(const String& var) {
}
}
content += "<h4>Battery allows contactor closing: ";
if (datalayer.system.status.battery_allows_contactor_closing == true) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Inverter allows contactor closing: ";
if (datalayer.system.status.inverter_allows_contactor_closing == true) {
content += "<span>&#10003;</span></h4>";
} else {
content += "<span style='color: red;'>&#10005;</span></h4>";
}
if (emulator_pause_status == NORMAL)
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
else
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
if (contactor_control_enabled) {
content += "<h4>Contactors controlled by emulator, state: ";
if (datalayer.system.status.contactors_engaged) {
content += "<span style='color: green;'>ON</span>";
} else {
content += "<span style='color: red;'>OFF</span>";
}
content += "</h4>";
if (contactor_control_enabled_double_battery) {
if (pwm_contactor_control) {
content += "<h4>Cont. Neg.: ";
if (datalayer.system.status.contactors_battery2_engaged) {
content += "<span style='color: green;'>Economized</span>";
content += " Cont. Pos.: ";
content += "<span style='color: green;'>Economized</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
content += " Cont. Pos.: ";
content += "<span style='color: red;'>&#10005;</span>";
}
} else if (
esp32hal->SECOND_BATTERY_CONTACTORS_PIN() !=
GPIO_NUM_NC) { // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
content += "<h4>Cont. Neg.: ";
if (digitalRead(esp32hal->SECOND_BATTERY_CONTACTORS_PIN()) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
} //no PWM_CONTACTOR_CONTROL
content += "</h4>";
}
}
// Close the block
content += "</div>";
@ -1261,71 +1231,85 @@ String processor(const String& var) {
} else { // > 0
content += "<h4>Battery charging!</h4>";
}
content += "<h4>Automatic contactor closing allowed:</h4>";
content += "<h4>Battery: ";
if (datalayer.system.status.battery2_allowed_contactor_closing == true) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Inverter: ";
if (datalayer.system.status.inverter_allows_contactor_closing == true) {
content += "<span>&#10003;</span></h4>";
} else {
content += "<span style='color: red;'>&#10005;</span></h4>";
}
if (emulator_pause_status == NORMAL)
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
else
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
if (contactor_control_enabled) {
content += "<h4>Contactors controlled by emulator, state: ";
if (datalayer.system.status.contactors_battery2_engaged) {
content += "<span style='color: green;'>ON</span>";
} else {
content += "<span style='color: red;'>OFF</span>";
}
content += "</h4>";
if (contactor_control_enabled_double_battery) {
content += "<h4>Cont. Neg.: ";
if (pwm_contactor_control) {
if (datalayer.system.status.contactors_battery2_engaged) {
content += "<span style='color: green;'>Economized</span>";
content += " Cont. Pos.: ";
content += "<span style='color: green;'>Economized</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
content += " Cont. Pos.: ";
content += "<span style='color: red;'>&#10005;</span>";
}
} else { // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
#if defined(SECOND_POSITIVE_CONTACTOR_PIN) && defined(SECOND_NEGATIVE_CONTACTOR_PIN)
if (digitalRead(SECOND_NEGATIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Cont. Pos.: ";
if (digitalRead(SECOND_POSITIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
#endif
}
content += "</h4>";
}
}
content += "</div>";
content += "</div>";
}
}
// Block for Contactor status and component request status
// Start a new block with gray background color
content += "<div style='background-color: #333; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
if (emulator_pause_status == NORMAL) {
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
} else {
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
}
content += "<h4>Emulator allows contactor closing: ";
if (datalayer.battery.status.bms_status == FAULT) {
content += "<span style='color: red;'>&#10005;</span>";
} else {
content += "<span>&#10003;</span>";
}
content += " Inverter allows contactor closing: ";
if (datalayer.system.status.inverter_allows_contactor_closing == true) {
content += "<span>&#10003;</span></h4>";
} else {
content += "<span style='color: red;'>&#10005;</span></h4>";
}
if (battery2) {
content += "<h4>Secondary battery allowed to join ";
if (datalayer.system.status.battery2_allowed_contactor_closing == true) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005; (voltage mismatch)</span>";
}
}
if (!contactor_control_enabled) {
content += "<div class=\"tooltip\">";
content += "<h4>Contactors not fully controlled via emulator <span style=\"color:orange\">[?]</span></h4>";
content +=
"<span class=\"tooltiptext\">This means you are either running CAN controlled contactors OR manually "
"powering the contactors. Battery-Emulator will have limited amount of control over the contactors!</span>";
content += "</div>";
} else { //contactor_control_enabled TRUE
content += "<div class=\"tooltip\"><h4>Contactors controlled by emulator, state: ";
if (datalayer.system.status.contactors_engaged == 0) {
content += "<span style='color: green;'>PRECHARGE</span>";
} else if (datalayer.system.status.contactors_engaged == 1) {
content += "<span style='color: green;'>ON</span>";
} else if (datalayer.system.status.contactors_engaged == 2) {
content += "<span style='color: red;'>OFF</span>";
content += "<span class=\"tooltip-icon\"> [!]</span>";
content +=
"<span class=\"tooltiptext\">Emulator spent too much time in critical FAULT event. Investigate event "
"causing this via Events page. Reboot required to resume operation!</span>";
}
content += "</h4></div>";
if (contactor_control_enabled_double_battery && battery2) {
content += "<h4>Secondary battery contactor, state: ";
if (pwm_contactor_control) {
if (datalayer.system.status.contactors_battery2_engaged) {
content += "<span style='color: green;'>Economized</span>";
} else {
content += "<span style='color: red;'>OFF</span>";
}
} else if (
esp32hal->SECOND_BATTERY_CONTACTORS_PIN() !=
GPIO_NUM_NC) { // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
if (digitalRead(esp32hal->SECOND_BATTERY_CONTACTORS_PIN()) == HIGH) {
content += "<span style='color: green;'>ON</span>";
} else {
content += "<span style='color: red;'>OFF</span>";
}
} //no PWM_CONTACTOR_CONTROL
content += "</h4>";
}
}
// Close the block
content += "</div>";
if (charger) {
// Start a new block with orange background color