diff --git a/firmware/esp32-s3-dashboard/README.md b/firmware/esp32-s3-dashboard/README.md index cd403df..5af2e33 100644 --- a/firmware/esp32-s3-dashboard/README.md +++ b/firmware/esp32-s3-dashboard/README.md @@ -65,3 +65,13 @@ Current dashboard firmware files: - `esp_panel_board_custom_conf.h` - Waveshare board/display configuration This refactor keeps behavior unchanged while preparing for later `api` and `ui` file splits. + + +## Status Field Filtering + +The dashboard uses field-filtered `/api/v1/status` calls: + +- Fast poll: `/api/v1/status?fields=battery,temps,relays` +- Slow poll: `/api/v1/status?fields=network,system,config` + +This keeps the endpoint model simple while reducing payload size, JSON parsing, and UI churn. diff --git a/firmware/esp32-s3-dashboard/dashboard_config.h b/firmware/esp32-s3-dashboard/dashboard_config.h index cc9b21c..b5348f7 100644 --- a/firmware/esp32-s3-dashboard/dashboard_config.h +++ b/firmware/esp32-s3-dashboard/dashboard_config.h @@ -1,10 +1,12 @@ #pragma once -static const char *DASHBOARD_VERSION = "0.0.8-refactor-config-model"; +static const char *DASHBOARD_VERSION = "0.0.9-status-fields"; // Update these if your Cargo ESP AP credentials are different. static const char *CARGO_WIFI_SSID = "OverlandController"; static const char *CARGO_WIFI_PASSWORD = "overland1234"; static const char *CARGO_API_STATUS_URL = "http://192.168.4.1/api/v1/status"; +static const char *CARGO_API_FAST_STATUS_URL = "http://192.168.4.1/api/v1/status?fields=battery,temps,relays"; +static const char *CARGO_API_SLOW_STATUS_URL = "http://192.168.4.1/api/v1/status?fields=network,system,config"; static const char *CARGO_API_RELAY_SET_URL = "http://192.168.4.1/api/v1/relay/set"; diff --git a/firmware/esp32-s3-dashboard/esp32-s3-dashboard.ino b/firmware/esp32-s3-dashboard/esp32-s3-dashboard.ino index 6a546c8..4390ef8 100644 --- a/firmware/esp32-s3-dashboard/esp32-s3-dashboard.ino +++ b/firmware/esp32-s3-dashboard/esp32-s3-dashboard.ino @@ -29,6 +29,7 @@ static uint32_t touch_count = 0; static unsigned long last_wifi_attempt_ms = 0; static unsigned long last_wifi_label_update_ms = 0; static unsigned long last_status_poll_ms = 0; +static unsigned long last_metadata_poll_ms = 0; static String last_battery_text; static String last_temps_text; @@ -264,33 +265,42 @@ static void parse_status_json(const String &body) status_model.api_ok = true; status_model.http_code = 200; - JsonObject battery = doc["battery"]; - status_model.bms_connected = battery["connected"] | false; - status_model.soc = battery["soc"] | -1; - status_model.voltage = battery["voltage"] | 0.0; - status_model.current = battery["current"] | 0.0; - status_model.battery_temp_f = battery["temperature_f"] | 0.0; - status_model.remaining_ah = battery["remaining_ah"] | 0.0; - status_model.capacity_ah = battery["capacity_ah"] | 0.0; - status_model.cell_delta_mv = battery["cell_delta_mv"] | -1; + if (doc["battery"].is()) { + JsonObject battery = doc["battery"]; + status_model.bms_connected = battery["connected"] | status_model.bms_connected; + status_model.soc = battery["soc"] | status_model.soc; + status_model.voltage = battery["voltage"] | status_model.voltage; + status_model.current = battery["current"] | status_model.current; + status_model.battery_temp_f = battery["temperature_f"] | status_model.battery_temp_f; + status_model.remaining_ah = battery["remaining_ah"] | status_model.remaining_ah; + status_model.capacity_ah = battery["capacity_ah"] | status_model.capacity_ah; + status_model.cell_delta_mv = battery["cell_delta_mv"] | status_model.cell_delta_mv; + } - JsonObject config = doc["config"]; - status_model.hardware_profile = String((const char *)(config["hardware_profile"] | "unknown")); - status_model.output_count = config["output_count"] | 0; + if (doc["config"].is()) { + JsonObject config = doc["config"]; + status_model.hardware_profile = String((const char *)(config["hardware_profile"] | status_model.hardware_profile.c_str())); + status_model.output_count = config["output_count"] | status_model.output_count; + } - JsonObject system = doc["system"]; - status_model.firmware_version = String((const char *)(system["firmware_version"] | "unknown")); - status_model.uptime_seconds = system["uptime_seconds"] | 0; + if (doc["system"].is()) { + 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; + } - JsonObject network = doc["network"]; - status_model.cargo_ap_ssid = String((const char *)(network["ap_ssid"] | "unknown")); - status_model.cargo_sta_ip = String((const char *)(network["sta_ip"] | "n/a")); + if (doc["network"].is()) { + 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())); + } - String temps = ""; - JsonArray temp_array = doc["temps"]; - int shown_temps = 0; + if (doc["temps"].is()) { + String temps = ""; + JsonArray temp_array = doc["temps"]; + int shown_temps = 0; - for (JsonObject temp : temp_array) { + for (JsonObject temp : temp_array) { bool enabled = temp["enabled"] | false; bool online = temp["online"] | false; @@ -318,21 +328,25 @@ static void parse_status_json(const String &body) temps += "\n"; shown_temps++; - if (shown_temps >= 5) { - break; + if (shown_temps >= 5) { + break; + } } + + if (temps.length() == 0) { + temps = "No temp sensors reported"; + } + + status_model.temps_text = temps; } - if (temps.length() == 0) { - temps = "No temp sensors reported"; - } + if (doc["relays"].is()) { + String outputs = ""; + JsonArray relay_array = doc["relays"]; - String outputs = ""; - JsonArray relay_array = doc["relays"]; + relay_count = 0; - relay_count = 0; - - for (JsonObject relay : relay_array) { + for (JsonObject relay : relay_array) { const char *id = relay["id"] | ""; const char *name = relay["name"] | relay["id"] | "Output"; bool enabled = relay["enabled"] | false; @@ -360,18 +374,17 @@ static void parse_status_json(const String &body) outputs += on_off(state); } - outputs += "\n"; + outputs += "\n"; + } + + if (outputs.length() == 0) { + outputs = "No outputs reported"; + } + + status_model.outputs_text = outputs; + update_relay_buttons(); } - if (outputs.length() == 0) { - outputs = "No outputs reported"; - } - - status_model.temps_text = temps; - status_model.outputs_text = outputs; - - update_relay_buttons(); - Serial.println("Parsed status summary:"); Serial.print(" SOC: "); Serial.println(status_model.soc); @@ -448,6 +461,58 @@ static void update_wifi_label() set_label_text_if_changed(wifi_label, last_wifi_text, text); } +static bool fetch_status_fields(const char *url, const char *label) +{ + if (WiFi.status() != WL_CONNECTED) { + set_label_text_if_changed(system_label, last_system_text, "API: waiting for WiFi"); + return false; + } + + Serial.print("GET "); + Serial.println(url); + + HTTPClient http; + http.setTimeout(5000); + http.begin(url); + int code = http.GET(); + + status_model.http_code = code; + + Serial.print(label); + Serial.print(" HTTP code: "); + Serial.println(code); + + if (code == 200) { + String body = http.getString(); + + Serial.print(label); + Serial.print(" body length: "); + Serial.println(body.length()); + + parse_status_json(body); + http.end(); + return true; + } + + status_model.api_ok = false; + + String system; + system += "API: error\n"; + system += label; + system += "\nHTTP: "; + system += String(code); + if (code <= 0) { + system += "\n"; + system += http.errorToString(code); + } + + Serial.println(system); + set_label_text_if_changed(system_label, last_system_text, system); + + http.end(); + return false; +} + static void poll_status_api() { if (WiFi.status() != WL_CONNECTED) { @@ -455,61 +520,15 @@ static void poll_status_api() return; } - if (millis() - last_status_poll_ms < 5000) { - return; + if (millis() - last_status_poll_ms >= 2000) { + last_status_poll_ms = millis(); + fetch_status_fields(CARGO_API_FAST_STATUS_URL, "fast status"); } - last_status_poll_ms = millis(); - - String requesting; - requesting += "API: requesting\n"; - requesting += "/api/v1/status\n"; - requesting += "Dashboard IP:\n"; - requesting += WiFi.localIP().toString(); - set_label_text_if_changed(system_label, last_system_text, requesting); - - Serial.print("GET "); - Serial.println(CARGO_API_STATUS_URL); - - HTTPClient http; - http.setTimeout(5000); - http.begin(CARGO_API_STATUS_URL); - int code = http.GET(); - - status_model.http_code = code; - - Serial.print("HTTP code: "); - Serial.println(code); - - if (code == 200) { - String body = http.getString(); - - Serial.print("Body length: "); - Serial.println(body.length()); - Serial.println(body); - - String received; - received += "API: received\nHTTP: 200\nBytes: "; - received += String(body.length()); - set_label_text_if_changed(system_label, last_system_text, received); - - parse_status_json(body); - } else { - status_model.api_ok = false; - - String system; - system += "API: error\nHTTP: "; - system += String(code); - if (code <= 0) { - system += "\n"; - system += http.errorToString(code); - } - - Serial.println(system); - set_label_text_if_changed(system_label, last_system_text, system); + if (millis() - last_metadata_poll_ms >= 30000) { + last_metadata_poll_ms = millis(); + fetch_status_fields(CARGO_API_SLOW_STATUS_URL, "slow status"); } - - http.end(); } static lv_obj_t *create_card(lv_obj_t *parent, const char *title, int width, int height, lv_align_t align, int x, int y) @@ -624,7 +643,7 @@ static void create_overland_status_screen() lv_obj_align(touch_count_label, LV_ALIGN_TOP_LEFT, 0, 142); lv_obj_t *footer = lv_label_create(screen); - lv_label_set_text(footer, "Polling Cargo ESP /api/v1/status every 5 seconds"); + lv_label_set_text(footer, "Fast: battery/temps/relays every 2s | Slow: system/config/network every 30s"); lv_obj_set_style_text_color(footer, lv_color_hex(0x7D8996), 0); lv_obj_set_width(footer, 900); lv_obj_set_style_text_align(footer, LV_TEXT_ALIGN_CENTER, 0);