dashboard: add hidden diagnostics overlay
This commit is contained in:
@@ -973,3 +973,9 @@ Each temperature sensor supports optional high-temperature alerting:
|
||||
- `high_alert`
|
||||
|
||||
When enabled and the live sensor temperature exceeds the configured threshold, the Cargo ESP includes `high_alert: true` for that sensor in `/api/v1/status`. The ESP32-S3 dashboard colors the affected grouped temperature tile red if any sensor in that displayed group is in alert.
|
||||
|
||||
### Hidden Dashboard Diagnostics
|
||||
|
||||
The dashboard has a hidden diagnostics overlay. Tap the top status card five times within roughly three seconds to open it. Double-tap the diagnostics overlay to return to the main dashboard.
|
||||
|
||||
The diagnostics overlay shows dashboard version, uptime, heap, WiFi RSSI/IP, Cargo API status, Cargo firmware version, Cargo uptime, Cargo heap, Cargo AP client count, BMS status, relay count, and basic control-loop state.
|
||||
|
||||
@@ -295,3 +295,9 @@ When enabled and the live sensor temperature exceeds the configured threshold, t
|
||||
## M9N GPS Module
|
||||
|
||||
The dashboard enclosure will include an M9N GPS module connected to the ESP32-S3 dashboard over UART. It will provide GPS fix status, satellite count, position, speed, and UTC time. Future work will use GPS time/location for automatic timezone and DST handling.
|
||||
|
||||
### Hidden Dashboard Diagnostics
|
||||
|
||||
The dashboard has a hidden diagnostics overlay. Tap the top status card five times within roughly three seconds to open it. Double-tap the diagnostics overlay to return to the main dashboard.
|
||||
|
||||
The diagnostics overlay shows dashboard version, uptime, heap, WiFi RSSI/IP, Cargo API status, Cargo firmware version, Cargo uptime, Cargo heap, Cargo AP client count, BMS status, relay count, and basic control-loop state.
|
||||
|
||||
@@ -362,3 +362,9 @@ When enabled and the live sensor temperature exceeds the configured threshold, t
|
||||
## M9N GPS Module
|
||||
|
||||
The dashboard enclosure will include an M9N GPS module connected to the ESP32-S3 dashboard over UART. It will provide GPS fix status, satellite count, position, speed, and UTC time. Future work will use GPS time/location for automatic timezone and DST handling.
|
||||
|
||||
### Hidden Dashboard Diagnostics
|
||||
|
||||
The dashboard has a hidden diagnostics overlay. Tap the top status card five times within roughly three seconds to open it. Double-tap the diagnostics overlay to return to the main dashboard.
|
||||
|
||||
The diagnostics overlay shows dashboard version, uptime, heap, WiFi RSSI/IP, Cargo API status, Cargo firmware version, Cargo uptime, Cargo heap, Cargo AP client count, BMS status, relay count, and basic control-loop state.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
static const char *DASHBOARD_VERSION = "0.1.74-relay-latest-state-queue";
|
||||
static const char *DASHBOARD_VERSION = "0.1.75-hidden-diagnostics";
|
||||
|
||||
// Update these if your Cargo ESP AP credentials are different.
|
||||
static const char *CARGO_WIFI_SSID = "OverlandController";
|
||||
|
||||
@@ -24,4 +24,10 @@ struct DashboardStatus {
|
||||
unsigned long uptime_seconds = 0;
|
||||
String cargo_ap_ssid = "unknown";
|
||||
String cargo_sta_ip = "unknown";
|
||||
int cargo_ap_clients = 0;
|
||||
int cargo_sta_rssi_dbm = 0;
|
||||
unsigned long cargo_free_heap = 0;
|
||||
unsigned long cargo_min_free_heap = 0;
|
||||
String cargo_chip_model = "unknown";
|
||||
int cargo_cpu_mhz = 0;
|
||||
};
|
||||
|
||||
@@ -40,6 +40,13 @@ static lv_obj_t *vehicle_fuel_value_label = nullptr;
|
||||
static lv_obj_t *relay_buttons[6] = {nullptr};
|
||||
static lv_obj_t *relay_button_labels[6] = {nullptr};
|
||||
|
||||
static lv_obj_t *diagnostics_overlay = nullptr;
|
||||
static lv_obj_t *diagnostics_label = nullptr;
|
||||
static unsigned long diagnostics_last_update_ms = 0;
|
||||
static unsigned long diagnostics_last_tap_ms = 0;
|
||||
static unsigned long system_card_last_tap_ms = 0;
|
||||
static int system_card_tap_count = 0;
|
||||
|
||||
static String relay_ids[6];
|
||||
static String relay_names[6];
|
||||
static bool relay_states[6] = {false};
|
||||
@@ -353,8 +360,200 @@ static void request_fast_status_refresh()
|
||||
fast_status_refresh_requested = false;
|
||||
}
|
||||
|
||||
|
||||
static String format_diag_uptime(unsigned long seconds)
|
||||
{
|
||||
unsigned long days = seconds / 86400;
|
||||
seconds %= 86400;
|
||||
unsigned long hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
unsigned long minutes = seconds / 60;
|
||||
|
||||
String out;
|
||||
if (days > 0) {
|
||||
out += String(days);
|
||||
out += "d ";
|
||||
}
|
||||
out += String(hours);
|
||||
out += "h ";
|
||||
out += String(minutes);
|
||||
out += "m";
|
||||
return out;
|
||||
}
|
||||
|
||||
static void update_diagnostics_overlay()
|
||||
{
|
||||
if (diagnostics_overlay == nullptr || diagnostics_label == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (millis() - diagnostics_last_update_ms < 500) {
|
||||
return;
|
||||
}
|
||||
|
||||
diagnostics_last_update_ms = millis();
|
||||
|
||||
String text;
|
||||
text += "DIAGNOSTICS\n\n";
|
||||
|
||||
text += "Dashboard ESP32-S3\n";
|
||||
text += " Version: ";
|
||||
text += DASHBOARD_VERSION;
|
||||
text += "\n Uptime: ";
|
||||
text += format_diag_uptime(millis() / 1000);
|
||||
text += "\n Free heap: ";
|
||||
text += String(ESP.getFreeHeap());
|
||||
text += "\n Min heap: ";
|
||||
text += String(ESP.getMinFreeHeap());
|
||||
text += "\n WiFi: ";
|
||||
text += WiFi.status() == WL_CONNECTED ? "connected" : "offline";
|
||||
text += "\n RSSI: ";
|
||||
text += WiFi.status() == WL_CONNECTED ? String(WiFi.RSSI()) : String("--");
|
||||
text += " dBm\n IP: ";
|
||||
text += WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : String("--");
|
||||
text += "\n Last HTTP: ";
|
||||
text += String(status_model.http_code);
|
||||
text += "\n Cargo API: ";
|
||||
text += status_model.api_ok ? "OK" : "FAIL";
|
||||
|
||||
text += "\n\nCargo ESP\n";
|
||||
text += " Version: ";
|
||||
text += status_model.firmware_version;
|
||||
text += "\n Uptime: ";
|
||||
text += format_diag_uptime(status_model.uptime_seconds);
|
||||
text += "\n Chip: ";
|
||||
text += status_model.cargo_chip_model;
|
||||
text += "\n CPU: ";
|
||||
text += String(status_model.cargo_cpu_mhz);
|
||||
text += " MHz";
|
||||
text += "\n Free heap: ";
|
||||
text += String(status_model.cargo_free_heap);
|
||||
text += "\n Min heap: ";
|
||||
text += String(status_model.cargo_min_free_heap);
|
||||
|
||||
text += "\n\nCargo Network\n";
|
||||
text += " AP SSID: ";
|
||||
text += status_model.cargo_ap_ssid;
|
||||
text += "\n AP clients: ";
|
||||
text += String(status_model.cargo_ap_clients);
|
||||
text += "\n STA IP: ";
|
||||
text += status_model.cargo_sta_ip.length() ? status_model.cargo_sta_ip : String("--");
|
||||
text += "\n STA RSSI: ";
|
||||
text += status_model.cargo_sta_rssi_dbm == 0 ? String("--") : String(status_model.cargo_sta_rssi_dbm);
|
||||
text += " dBm";
|
||||
|
||||
text += "\n\nPower / Sensors\n";
|
||||
text += " BMS: ";
|
||||
text += status_model.bms_connected ? "connected" : "offline";
|
||||
text += "\n SOC: ";
|
||||
text += status_model.soc >= 0 ? String(status_model.soc) + "%" : String("--");
|
||||
text += "\n Current: ";
|
||||
text += String(status_model.current, 2);
|
||||
text += " A";
|
||||
text += "\n Cell delta: ";
|
||||
text += String(status_model.cell_delta_mv);
|
||||
text += " mV";
|
||||
|
||||
text += "\n\nControls\n";
|
||||
text += " Relays visible: ";
|
||||
text += String(relay_count);
|
||||
text += "\n API busy: ";
|
||||
text += api_request_in_progress ? "yes" : "no";
|
||||
|
||||
text += "\n\nDouble tap anywhere to return.";
|
||||
|
||||
lvgl_port_lock(-1);
|
||||
lv_label_set_text(diagnostics_label, text.c_str());
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
static void close_diagnostics_overlay()
|
||||
{
|
||||
if (diagnostics_overlay == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
lvgl_port_lock(-1);
|
||||
lv_obj_del(diagnostics_overlay);
|
||||
diagnostics_overlay = nullptr;
|
||||
diagnostics_label = nullptr;
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
static void diagnostics_overlay_event_cb(lv_event_t *event)
|
||||
{
|
||||
if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long now = millis();
|
||||
if (now - diagnostics_last_tap_ms < 650) {
|
||||
close_diagnostics_overlay();
|
||||
diagnostics_last_tap_ms = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
diagnostics_last_tap_ms = now;
|
||||
}
|
||||
|
||||
static void show_diagnostics_overlay()
|
||||
{
|
||||
if (diagnostics_overlay != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
lvgl_port_lock(-1);
|
||||
|
||||
diagnostics_overlay = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(diagnostics_overlay, 1024, 600);
|
||||
lv_obj_align(diagnostics_overlay, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_obj_set_style_bg_color(diagnostics_overlay, lv_color_hex(0x101418), 0);
|
||||
lv_obj_set_style_bg_opa(diagnostics_overlay, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(diagnostics_overlay, 0, 0);
|
||||
lv_obj_set_style_pad_all(diagnostics_overlay, 22, 0);
|
||||
lv_obj_add_flag(diagnostics_overlay, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(diagnostics_overlay, diagnostics_overlay_event_cb, LV_EVENT_CLICKED, nullptr);
|
||||
|
||||
diagnostics_label = lv_label_create(diagnostics_overlay);
|
||||
lv_label_set_text(diagnostics_label, "DIAGNOSTICS\nLoading...");
|
||||
lv_obj_set_style_text_color(diagnostics_label, lv_color_hex(0xDDE3EA), 0);
|
||||
lv_obj_set_style_text_font(diagnostics_label, &lv_font_montserrat_26, 0);
|
||||
lv_obj_set_width(diagnostics_label, 960);
|
||||
lv_label_set_long_mode(diagnostics_label, LV_LABEL_LONG_WRAP);
|
||||
lv_obj_align(diagnostics_label, LV_ALIGN_TOP_LEFT, 8, 8);
|
||||
|
||||
lvgl_port_unlock();
|
||||
|
||||
diagnostics_last_update_ms = 0;
|
||||
update_diagnostics_overlay();
|
||||
}
|
||||
|
||||
static void system_card_event_cb(lv_event_t *event)
|
||||
{
|
||||
if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long now = millis();
|
||||
if (now - system_card_last_tap_ms > 3000) {
|
||||
system_card_tap_count = 0;
|
||||
}
|
||||
|
||||
system_card_last_tap_ms = now;
|
||||
system_card_tap_count++;
|
||||
|
||||
if (system_card_tap_count >= 5) {
|
||||
system_card_tap_count = 0;
|
||||
show_diagnostics_overlay();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void create_overland_overview_screen();
|
||||
static void show_boot_screen();
|
||||
static void show_diagnostics_overlay();
|
||||
static void close_diagnostics_overlay();
|
||||
static void update_diagnostics_overlay();
|
||||
|
||||
static void layout_relay_buttons()
|
||||
{
|
||||
@@ -903,12 +1102,18 @@ static void parse_status_json(const String &body)
|
||||
JsonObject system = doc["system"];
|
||||
status_model.firmware_version = String((const char *)(system["firmware_version"] | status_model.firmware_version.c_str()));
|
||||
status_model.uptime_seconds = system["uptime_seconds"] | status_model.uptime_seconds;
|
||||
status_model.cargo_free_heap = system["free_heap"] | status_model.cargo_free_heap;
|
||||
status_model.cargo_min_free_heap = system["min_free_heap"] | status_model.cargo_min_free_heap;
|
||||
status_model.cargo_chip_model = String((const char *)(system["chip_model"] | status_model.cargo_chip_model.c_str()));
|
||||
status_model.cargo_cpu_mhz = system["cpu_mhz"] | status_model.cargo_cpu_mhz;
|
||||
}
|
||||
|
||||
if (doc["network"].is<JsonObject>()) {
|
||||
JsonObject network = doc["network"];
|
||||
status_model.cargo_ap_ssid = String((const char *)(network["ap_ssid"] | status_model.cargo_ap_ssid.c_str()));
|
||||
status_model.cargo_sta_ip = String((const char *)(network["sta_ip"] | status_model.cargo_sta_ip.c_str()));
|
||||
status_model.cargo_ap_clients = network["ap_clients"] | status_model.cargo_ap_clients;
|
||||
status_model.cargo_sta_rssi_dbm = network["sta_rssi_dbm"] | status_model.cargo_sta_rssi_dbm;
|
||||
}
|
||||
|
||||
if (doc["temps"].is<JsonArray>()) {
|
||||
@@ -1188,6 +1393,8 @@ static void create_overland_overview_screen()
|
||||
lv_obj_set_style_bg_color(screen, lv_color_hex(0x101418), 0);
|
||||
|
||||
lv_obj_t *system_card = create_card(screen, "", 960, 48, LV_ALIGN_TOP_MID, 0, 12);
|
||||
lv_obj_add_flag(system_card, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(system_card, system_card_event_cb, LV_EVENT_CLICKED, nullptr);
|
||||
lv_obj_t *battery_card = create_card(screen, "", 380, 310, LV_ALIGN_TOP_LEFT, 32, 68);
|
||||
battery_card_obj = battery_card;
|
||||
lv_obj_t *temp_card = create_card(screen, "", 260, 310, LV_ALIGN_TOP_LEFT, 426, 68);
|
||||
|
||||
@@ -2442,10 +2442,12 @@ void buildStatusDocument(JsonDocument& doc) {
|
||||
network["ap_ssid"] = apSsid;
|
||||
network["ap_auth"] = "wpa2";
|
||||
network["ap_ip"] = WiFi.softAPIP().toString();
|
||||
network["ap_clients"] = WiFi.softAPgetStationNum();
|
||||
network["sta_enabled"] = wifiNetworkCount > 0;
|
||||
network["sta_connected"] = WiFi.status() == WL_CONNECTED;
|
||||
network["sta_ssid"] = activeStaSsid;
|
||||
network["sta_ip"] = WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "";
|
||||
network["sta_rssi_dbm"] = WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : 0;
|
||||
network["saved_network_count"] = wifiNetworkCount;
|
||||
JsonArray savedNetworks = network.createNestedArray("saved_networks");
|
||||
for (int i = 0; i < wifiNetworkCount; i++) {
|
||||
@@ -2470,6 +2472,10 @@ void buildStatusDocument(JsonDocument& doc) {
|
||||
system["build_date"] = __DATE__;
|
||||
system["build_time"] = __TIME__;
|
||||
system["uptime_seconds"] = millis() / 1000;
|
||||
system["free_heap"] = ESP.getFreeHeap();
|
||||
system["min_free_heap"] = ESP.getMinFreeHeap();
|
||||
system["chip_model"] = ESP.getChipModel();
|
||||
system["cpu_mhz"] = ESP.getCpuFreqMHz();
|
||||
|
||||
JsonObject configObj = doc.createNestedObject("config");
|
||||
buildConfigDocument(configObj);
|
||||
@@ -3355,6 +3361,7 @@ void handleHealth() {
|
||||
network["ap_ssid"] = apSsid;
|
||||
network["ap_auth"] = "wpa2";
|
||||
network["ap_ip"] = WiFi.softAPIP().toString();
|
||||
network["ap_clients"] = WiFi.softAPgetStationNum();
|
||||
network["sta_enabled"] = wifiNetworkCount > 0;
|
||||
network["sta_connected"] = WiFi.status() == WL_CONNECTED;
|
||||
network["sta_ssid"] = activeStaSsid;
|
||||
|
||||
Reference in New Issue
Block a user