dashboard: render battery soc gauge

This commit is contained in:
2026-06-09 23:42:22 -06:00
parent 72af4cbe80
commit a29d86ab4a
2 changed files with 154 additions and 27 deletions
@@ -14,9 +14,11 @@ using namespace esp_panel::board;
static DashboardStatus status_model;
static lv_obj_t *soc_value_label = nullptr;
static lv_obj_t *battery_soc_arc = nullptr;
static lv_obj_t *battery_charge_pulse_arc = nullptr;
static lv_obj_t *battery_voltage_label = nullptr;
static lv_obj_t *battery_current_label = nullptr;
static lv_obj_t *battery_soc_label = nullptr;
static lv_obj_t *battery_capacity_label = nullptr;
static lv_obj_t *battery_health_label = nullptr;
static lv_obj_t *inside_temp_label = nullptr;
@@ -44,9 +46,9 @@ static bool pending_relay_state = false;
static int pending_relay_index = -1;
static bool pending_relay_previous_state = false;
static String last_soc_text;
static String last_battery_voltage_text;
static String last_battery_current_text;
static String last_battery_soc_text;
static String last_battery_capacity_text;
static String last_battery_health_text;
static String last_inside_temp_text;
@@ -58,6 +60,93 @@ static String on_off(bool value)
return value ? "ON" : "OFF";
}
static int clamp_int(int value, int min_value, int max_value)
{
if (value < min_value) {
return min_value;
}
if (value > max_value) {
return max_value;
}
return value;
}
static void update_battery_current_label(const String &text, float current)
{
if (battery_current_label == nullptr || last_battery_current_text == text) {
return;
}
last_battery_current_text = text;
lv_color_t current_color = lv_color_hex(0xB8C0C8);
if (current > 0.05) {
current_color = lv_color_hex(0x32D583);
} else if (current < -0.05) {
current_color = lv_color_hex(0xF97066);
}
lvgl_port_lock(-1);
if (text.length() == 0) {
lv_obj_add_flag(battery_current_label, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_clear_flag(battery_current_label, LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(battery_current_label, text.c_str());
lv_obj_set_style_text_color(battery_current_label, current_color, 0);
}
lvgl_port_unlock();
}
static void update_battery_soc_gauge()
{
if (battery_soc_arc == nullptr || battery_charge_pulse_arc == nullptr) {
return;
}
int soc = clamp_int(status_model.soc, 0, 100);
bool charging = status_model.current > 0.05;
lvgl_port_lock(-1);
lv_arc_set_value(battery_soc_arc, soc);
lv_arc_set_value(battery_charge_pulse_arc, soc);
if (charging) {
lv_obj_clear_flag(battery_charge_pulse_arc, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(battery_charge_pulse_arc, LV_OBJ_FLAG_HIDDEN);
}
lvgl_port_unlock();
}
static void update_battery_charge_animation()
{
if (battery_charge_pulse_arc == nullptr || status_model.current <= 0.05) {
return;
}
static unsigned long last_pulse_ms = 0;
static bool pulse_high = false;
if (millis() - last_pulse_ms < 450) {
return;
}
last_pulse_ms = millis();
pulse_high = !pulse_high;
lvgl_port_lock(-1);
lv_obj_set_style_arc_width(battery_charge_pulse_arc, pulse_high ? 20 : 12, LV_PART_INDICATOR);
lv_obj_set_style_arc_opa(battery_charge_pulse_arc, pulse_high ? LV_OPA_80 : LV_OPA_35, LV_PART_INDICATOR);
lvgl_port_unlock();
}
static void set_label_text_if_changed(lv_obj_t *label, String &last_text, const String &text)
{
if (label == nullptr || last_text == text) {
@@ -257,17 +346,20 @@ static void update_system_status_label()
static void update_overview_widgets()
{
String soc_text;
soc_text += status_model.soc >= 0 ? String(status_model.soc) : "--";
soc_text += "%";
String voltage_text;
voltage_text += String(status_model.voltage, 2);
voltage_text += " V";
String current_text;
current_text += String(status_model.current, 2);
current_text += " A";
String current_text = "";
if (status_model.current > 0.05 || status_model.current < -0.05) {
current_text += status_model.current > 0 ? "+" : "";
current_text += String(status_model.current, 2);
current_text += " A";
}
String soc_text;
soc_text += status_model.soc >= 0 ? String(status_model.soc) : "--";
soc_text += "% SOC";
String capacity_text;
capacity_text += String(status_model.remaining_ah, 1);
@@ -287,9 +379,10 @@ static void update_overview_widgets()
health_text += " mV";
}
set_label_text_if_changed(soc_value_label, last_soc_text, soc_text);
update_battery_soc_gauge();
set_label_text_if_changed(battery_voltage_label, last_battery_voltage_text, voltage_text);
set_label_text_if_changed(battery_current_label, last_battery_current_text, current_text);
update_battery_current_label(current_text, status_model.current);
set_label_text_if_changed(battery_soc_label, last_battery_soc_text, soc_text);
set_label_text_if_changed(battery_capacity_label, last_battery_capacity_text, capacity_text);
set_label_text_if_changed(battery_health_label, last_battery_health_text, health_text);
set_label_text_if_changed(system_status_label, last_system_status_text, last_system_status_text);
@@ -485,39 +578,72 @@ static void create_overland_overview_screen()
lv_obj_t *relay_card = create_card(screen, "Outputs", 300, 255, LV_ALIGN_TOP_RIGHT, -32, 80);
lv_obj_t *system_card = create_card(screen, "System", 620, 175, LV_ALIGN_BOTTOM_LEFT, 32, -50);
soc_value_label = lv_label_create(battery_card);
lv_label_set_text(soc_value_label, "--%");
lv_obj_set_style_text_color(soc_value_label, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(soc_value_label, &lv_font_montserrat_30, 0);
lv_obj_align(soc_value_label, LV_ALIGN_TOP_LEFT, 0, 34);
battery_charge_pulse_arc = lv_arc_create(battery_card);
lv_obj_set_size(battery_charge_pulse_arc, 172, 172);
lv_obj_align(battery_charge_pulse_arc, LV_ALIGN_TOP_MID, 0, 30);
lv_arc_set_range(battery_charge_pulse_arc, 0, 100);
lv_arc_set_value(battery_charge_pulse_arc, 0);
lv_arc_set_rotation(battery_charge_pulse_arc, 135);
lv_arc_set_bg_angles(battery_charge_pulse_arc, 0, 270);
lv_obj_remove_style(battery_charge_pulse_arc, NULL, LV_PART_KNOB);
lv_obj_clear_flag(battery_charge_pulse_arc, LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_style_arc_opa(battery_charge_pulse_arc, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_arc_color(battery_charge_pulse_arc, lv_color_hex(0x32D583), LV_PART_INDICATOR);
lv_obj_set_style_arc_width(battery_charge_pulse_arc, 14, LV_PART_INDICATOR);
lv_obj_set_style_arc_opa(battery_charge_pulse_arc, LV_OPA_35, LV_PART_INDICATOR);
lv_obj_add_flag(battery_charge_pulse_arc, LV_OBJ_FLAG_HIDDEN);
lv_obj_t *soc_caption = lv_label_create(battery_card);
lv_label_set_text(soc_caption, "STATE OF CHARGE");
lv_obj_set_style_text_color(soc_caption, lv_color_hex(0x7D8996), 0);
lv_obj_align(soc_caption, LV_ALIGN_TOP_LEFT, 0, 82);
battery_soc_arc = lv_arc_create(battery_card);
lv_obj_set_size(battery_soc_arc, 158, 158);
lv_obj_align(battery_soc_arc, LV_ALIGN_TOP_MID, 0, 37);
lv_arc_set_range(battery_soc_arc, 0, 100);
lv_arc_set_value(battery_soc_arc, 0);
lv_arc_set_rotation(battery_soc_arc, 135);
lv_arc_set_bg_angles(battery_soc_arc, 0, 270);
lv_obj_remove_style(battery_soc_arc, NULL, LV_PART_KNOB);
lv_obj_clear_flag(battery_soc_arc, LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_style_arc_color(battery_soc_arc, lv_color_hex(0x3A4652), LV_PART_MAIN);
lv_obj_set_style_arc_width(battery_soc_arc, 13, LV_PART_MAIN);
lv_obj_set_style_arc_color(battery_soc_arc, lv_color_hex(0x84CAFF), LV_PART_INDICATOR);
lv_obj_set_style_arc_width(battery_soc_arc, 13, LV_PART_INDICATOR);
battery_voltage_label = lv_label_create(battery_card);
lv_label_set_text(battery_voltage_label, "-- V");
lv_obj_set_style_text_color(battery_voltage_label, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(battery_voltage_label, LV_ALIGN_TOP_LEFT, 0, 112);
lv_obj_set_style_text_font(battery_voltage_label, &lv_font_montserrat_30, 0);
lv_obj_set_width(battery_voltage_label, 180);
lv_obj_set_style_text_align(battery_voltage_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(battery_voltage_label, LV_ALIGN_TOP_MID, 0, 83);
battery_current_label = lv_label_create(battery_card);
lv_label_set_text(battery_current_label, "-- A");
lv_obj_set_style_text_color(battery_current_label, lv_color_hex(0xFFFFFF), 0);
lv_obj_align(battery_current_label, LV_ALIGN_TOP_LEFT, 135, 112);
lv_label_set_text(battery_current_label, "");
lv_obj_set_style_text_color(battery_current_label, lv_color_hex(0xB8C0C8), 0);
lv_obj_set_width(battery_current_label, 170);
lv_obj_set_style_text_align(battery_current_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(battery_current_label, LV_ALIGN_TOP_MID, 0, 122);
lv_obj_add_flag(battery_current_label, LV_OBJ_FLAG_HIDDEN);
battery_soc_label = lv_label_create(battery_card);
lv_label_set_text(battery_soc_label, "--% SOC");
lv_obj_set_style_text_color(battery_soc_label, lv_color_hex(0xB8C0C8), 0);
lv_obj_set_width(battery_soc_label, 180);
lv_obj_set_style_text_align(battery_soc_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(battery_soc_label, LV_ALIGN_TOP_MID, 0, 155);
battery_capacity_label = lv_label_create(battery_card);
lv_label_set_text(battery_capacity_label, "-- / -- Ah");
lv_obj_set_style_text_color(battery_capacity_label, lv_color_hex(0xDDE3EA), 0);
lv_obj_set_width(battery_capacity_label, 260);
lv_obj_align(battery_capacity_label, LV_ALIGN_TOP_LEFT, 0, 148);
lv_obj_set_style_text_align(battery_capacity_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(battery_capacity_label, LV_ALIGN_TOP_MID, 0, 190);
battery_health_label = lv_label_create(battery_card);
lv_label_set_text(battery_health_label, "Waiting for BMS...");
lv_obj_set_style_text_color(battery_health_label, lv_color_hex(0xB8C0C8), 0);
lv_obj_set_width(battery_health_label, 260);
lv_label_set_long_mode(battery_health_label, LV_LABEL_LONG_WRAP);
lv_obj_align(battery_health_label, LV_ALIGN_TOP_LEFT, 0, 182);
lv_obj_set_style_text_align(battery_health_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(battery_health_label, LV_ALIGN_TOP_MID, 0, 214);
inside_temp_label = lv_label_create(temp_card);
lv_label_set_text(inside_temp_label, "Inside\n--");
@@ -612,6 +738,7 @@ void loop()
update_system_status_label();
process_pending_relay_command();
poll_status_api();
update_battery_charge_animation();
delay(250);
}