dashboard: create first real overview UI
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
static const char *DASHBOARD_VERSION = "0.0.12-queued-relay-commands";
|
||||
static const char *DASHBOARD_VERSION = "0.1.0-overview-ui";
|
||||
|
||||
// Update these if your Cargo ESP AP credentials are different.
|
||||
static const char *CARGO_WIFI_SSID = "OverlandController";
|
||||
|
||||
@@ -12,24 +12,26 @@
|
||||
using namespace esp_panel::drivers;
|
||||
using namespace esp_panel::board;
|
||||
|
||||
static lv_obj_t *battery_label = nullptr;
|
||||
static lv_obj_t *temps_label = nullptr;
|
||||
static lv_obj_t *outputs_label = nullptr;
|
||||
static DashboardStatus status_model;
|
||||
|
||||
static lv_obj_t *soc_value_label = nullptr;
|
||||
static lv_obj_t *battery_detail_label = nullptr;
|
||||
static lv_obj_t *inside_temp_label = nullptr;
|
||||
static lv_obj_t *outside_temp_label = nullptr;
|
||||
static lv_obj_t *system_status_label = nullptr;
|
||||
static lv_obj_t *relay_buttons[6] = {nullptr};
|
||||
static lv_obj_t *relay_button_labels[6] = {nullptr};
|
||||
|
||||
static String relay_ids[6];
|
||||
static String relay_names[6];
|
||||
static bool relay_states[6] = {false};
|
||||
static int relay_count = 0;
|
||||
static lv_obj_t *system_label = nullptr;
|
||||
static lv_obj_t *wifi_label = nullptr;
|
||||
static lv_obj_t *touch_count_label = nullptr;
|
||||
|
||||
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 bool api_request_in_progress = false;
|
||||
static bool fast_status_refresh_requested = false;
|
||||
|
||||
@@ -39,33 +41,63 @@ static bool pending_relay_state = false;
|
||||
static int pending_relay_index = -1;
|
||||
static bool pending_relay_previous_state = false;
|
||||
|
||||
static String last_battery_text;
|
||||
static String last_temps_text;
|
||||
static String last_outputs_text;
|
||||
static String last_system_text;
|
||||
static String last_wifi_text;
|
||||
|
||||
static DashboardStatus status_model;
|
||||
static String last_soc_text;
|
||||
static String last_battery_detail_text;
|
||||
static String last_inside_temp_text;
|
||||
static String last_outside_temp_text;
|
||||
static String last_system_status_text;
|
||||
|
||||
static String on_off(bool value)
|
||||
{
|
||||
return value ? "ON" : "OFF";
|
||||
}
|
||||
|
||||
static void set_label_text_if_changed(lv_obj_t *label, String &last_text, const String &text)
|
||||
{
|
||||
if (label == nullptr || last_text == text) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_text = text;
|
||||
|
||||
lvgl_port_lock(-1);
|
||||
lv_label_set_text(label, text.c_str());
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
static void request_fast_status_refresh()
|
||||
{
|
||||
fast_status_refresh_requested = true;
|
||||
}
|
||||
|
||||
static bool post_relay_state(const String &relay_id, bool state)
|
||||
static void update_relay_buttons()
|
||||
{
|
||||
if (api_request_in_progress) {
|
||||
Serial.println("Relay command skipped: API request already in progress");
|
||||
return false;
|
||||
lvgl_port_lock(-1);
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (relay_buttons[i] == nullptr || relay_button_labels[i] == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i < relay_count) {
|
||||
lv_obj_clear_flag(relay_buttons[i], LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
String label = relay_names[i].length() > 0 ? relay_names[i] : relay_ids[i];
|
||||
label += "\n";
|
||||
label += on_off(relay_states[i]);
|
||||
|
||||
lv_label_set_text(relay_button_labels[i], label.c_str());
|
||||
} else {
|
||||
lv_obj_add_flag(relay_buttons[i], LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
Serial.println("Relay command skipped: WiFi not connected");
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
static bool post_relay_state(const String &relay_id, bool state)
|
||||
{
|
||||
if (api_request_in_progress || WiFi.status() != WL_CONNECTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -109,175 +141,155 @@ static void relay_button_event_cb(lv_event_t *event)
|
||||
intptr_t index_value = reinterpret_cast<intptr_t>(lv_event_get_user_data(event));
|
||||
int index = static_cast<int>(index_value);
|
||||
|
||||
if (index < 0 || index >= relay_count) {
|
||||
Serial.println("Relay button index out of range");
|
||||
if (index < 0 || index >= relay_count || relay_command_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (relay_command_pending) {
|
||||
set_label_text_if_changed(system_label, last_system_text, "Relay command already pending");
|
||||
Serial.println("Relay tap ignored: command already pending");
|
||||
return;
|
||||
}
|
||||
|
||||
String relay_id = relay_ids[index];
|
||||
bool previous_state = relay_states[index];
|
||||
bool next_state = !previous_state;
|
||||
|
||||
relay_states[index] = next_state;
|
||||
update_relay_buttons();
|
||||
update_outputs_label_from_relays();
|
||||
|
||||
pending_relay_id = relay_id;
|
||||
pending_relay_id = relay_ids[index];
|
||||
pending_relay_state = next_state;
|
||||
pending_relay_index = index;
|
||||
pending_relay_previous_state = previous_state;
|
||||
relay_command_pending = true;
|
||||
|
||||
String pending_text = "Queued relay command\n";
|
||||
pending_text += relay_id;
|
||||
pending_text += " -> ";
|
||||
pending_text += on_off(next_state);
|
||||
set_label_text_if_changed(system_label, last_system_text, pending_text);
|
||||
String text = "Queued ";
|
||||
text += relay_names[index];
|
||||
text += " ";
|
||||
text += on_off(next_state);
|
||||
|
||||
Serial.print("Queued relay command: ");
|
||||
Serial.print(relay_id);
|
||||
Serial.print(" -> ");
|
||||
Serial.println(on_off(next_state));
|
||||
set_label_text_if_changed(system_status_label, last_system_status_text, text);
|
||||
}
|
||||
|
||||
|
||||
static void update_outputs_label_from_relays()
|
||||
static void process_pending_relay_command()
|
||||
{
|
||||
String outputs = "";
|
||||
|
||||
for (int i = 0; i < relay_count && i < 6; i++) {
|
||||
outputs += String(i + 1);
|
||||
outputs += ". ";
|
||||
outputs += relay_names[i].length() > 0 ? relay_names[i] : relay_ids[i];
|
||||
outputs += ": ";
|
||||
outputs += on_off(relay_states[i]);
|
||||
outputs += "\n";
|
||||
}
|
||||
|
||||
if (outputs.length() == 0) {
|
||||
outputs = "No outputs reported";
|
||||
}
|
||||
|
||||
status_model.outputs_text = outputs;
|
||||
set_label_text_if_changed(outputs_label, last_outputs_text, outputs);
|
||||
}
|
||||
|
||||
static void update_relay_buttons()
|
||||
{
|
||||
lvgl_port_lock(-1);
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (relay_buttons[i] == nullptr || relay_button_labels[i] == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i < relay_count) {
|
||||
lv_obj_clear_flag(relay_buttons[i], LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
String label = relay_ids[i];
|
||||
label += relay_states[i] ? " OFF" : " ON";
|
||||
lv_label_set_text(relay_button_labels[i], label.c_str());
|
||||
} else {
|
||||
lv_obj_add_flag(relay_buttons[i], LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!relay_command_pending || api_request_in_progress) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_text = text;
|
||||
String relay_id = pending_relay_id;
|
||||
bool next_state = pending_relay_state;
|
||||
int index = pending_relay_index;
|
||||
bool previous_state = pending_relay_previous_state;
|
||||
|
||||
lvgl_port_lock(-1);
|
||||
lv_label_set_text(label, text.c_str());
|
||||
lvgl_port_unlock();
|
||||
bool ok = post_relay_state(relay_id, next_state);
|
||||
|
||||
relay_command_pending = false;
|
||||
pending_relay_id = "";
|
||||
pending_relay_index = -1;
|
||||
|
||||
if (ok) {
|
||||
set_label_text_if_changed(system_status_label, last_system_status_text, "Relay command sent");
|
||||
request_fast_status_refresh();
|
||||
} else {
|
||||
if (index >= 0 && index < relay_count) {
|
||||
relay_states[index] = previous_state;
|
||||
update_relay_buttons();
|
||||
}
|
||||
|
||||
set_label_text_if_changed(system_status_label, last_system_status_text, "Relay command failed");
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_wifi_if_needed()
|
||||
{
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (millis() - last_wifi_attempt_ms < 10000) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_wifi_attempt_ms = millis();
|
||||
|
||||
Serial.print("Connecting to Cargo ESP AP: ");
|
||||
Serial.println(CARGO_WIFI_SSID);
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(CARGO_WIFI_SSID, CARGO_WIFI_PASSWORD);
|
||||
}
|
||||
|
||||
static void update_system_status_label()
|
||||
{
|
||||
if (millis() - last_wifi_label_update_ms < 5000) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_wifi_label_update_ms = millis();
|
||||
|
||||
String text;
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
text += "Dashboard WiFi: connected";
|
||||
text += "\nIP: ";
|
||||
text += WiFi.localIP().toString();
|
||||
text += " RSSI: ";
|
||||
text += String(WiFi.RSSI());
|
||||
text += " dBm";
|
||||
} else {
|
||||
text += "Dashboard WiFi: connecting";
|
||||
text += "\nSSID: ";
|
||||
text += CARGO_WIFI_SSID;
|
||||
}
|
||||
|
||||
text += "\nCargo: ";
|
||||
text += status_model.api_ok ? "online" : "waiting";
|
||||
|
||||
text += "\nProfile: ";
|
||||
text += status_model.hardware_profile;
|
||||
|
||||
text += "\nFirmware: ";
|
||||
text += status_model.firmware_version;
|
||||
|
||||
set_label_text_if_changed(system_status_label, last_system_status_text, text);
|
||||
}
|
||||
|
||||
static void update_overview_widgets()
|
||||
{
|
||||
String battery;
|
||||
battery += "SOC: ";
|
||||
battery += status_model.soc >= 0 ? String(status_model.soc) + "%" : "n/a";
|
||||
battery += "\nVoltage: ";
|
||||
battery += String(status_model.voltage, 2);
|
||||
battery += " V\nCurrent: ";
|
||||
battery += String(status_model.current, 2);
|
||||
battery += " A\nRemaining: ";
|
||||
battery += String(status_model.remaining_ah, 1);
|
||||
battery += " / ";
|
||||
battery += String(status_model.capacity_ah, 0);
|
||||
battery += " Ah\nBatt Temp: ";
|
||||
battery += String(status_model.battery_temp_f, 1);
|
||||
battery += " F\nBMS: ";
|
||||
battery += status_model.bms_connected ? "online" : "offline";
|
||||
String soc_text;
|
||||
soc_text += status_model.soc >= 0 ? String(status_model.soc) : "--";
|
||||
soc_text += "%";
|
||||
|
||||
String battery_detail;
|
||||
battery_detail += String(status_model.voltage, 2);
|
||||
battery_detail += " V\n";
|
||||
battery_detail += String(status_model.current, 2);
|
||||
battery_detail += " A\n";
|
||||
battery_detail += String(status_model.remaining_ah, 1);
|
||||
battery_detail += " / ";
|
||||
battery_detail += String(status_model.capacity_ah, 0);
|
||||
battery_detail += " Ah\n";
|
||||
battery_detail += String(status_model.battery_temp_f, 1);
|
||||
battery_detail += " F battery\nBMS ";
|
||||
battery_detail += status_model.bms_connected ? "online" : "offline";
|
||||
if (status_model.cell_delta_mv >= 0) {
|
||||
battery += "\nCell Delta: ";
|
||||
battery += String(status_model.cell_delta_mv);
|
||||
battery += " mV";
|
||||
battery_detail += "\nCell delta ";
|
||||
battery_detail += String(status_model.cell_delta_mv);
|
||||
battery_detail += " mV";
|
||||
}
|
||||
|
||||
String system;
|
||||
system += "API: ";
|
||||
system += status_model.api_ok ? "connected" : "error";
|
||||
system += "\nHTTP: ";
|
||||
system += String(status_model.http_code);
|
||||
system += "\nProfile: ";
|
||||
system += status_model.hardware_profile;
|
||||
system += "\nOutputs: ";
|
||||
system += String(status_model.output_count);
|
||||
system += "\nFirmware: ";
|
||||
system += status_model.firmware_version;
|
||||
system += "\nUptime: ";
|
||||
system += String(status_model.uptime_seconds);
|
||||
system += " sec\nCargo AP: ";
|
||||
system += status_model.cargo_ap_ssid;
|
||||
system += "\nCargo STA IP: ";
|
||||
system += status_model.cargo_sta_ip;
|
||||
|
||||
set_label_text_if_changed(battery_label, last_battery_text, battery);
|
||||
set_label_text_if_changed(temps_label, last_temps_text, status_model.temps_text);
|
||||
set_label_text_if_changed(outputs_label, last_outputs_text, status_model.outputs_text);
|
||||
set_label_text_if_changed(system_label, last_system_text, system);
|
||||
set_label_text_if_changed(soc_value_label, last_soc_text, soc_text);
|
||||
set_label_text_if_changed(battery_detail_label, last_battery_detail_text, battery_detail);
|
||||
set_label_text_if_changed(system_status_label, last_system_status_text, last_system_status_text);
|
||||
}
|
||||
|
||||
static void parse_status_json(const String &body)
|
||||
{
|
||||
DynamicJsonDocument doc(20000);
|
||||
DynamicJsonDocument doc(16000);
|
||||
DeserializationError error = deserializeJson(doc, body);
|
||||
|
||||
if (error) {
|
||||
status_model.api_ok = false;
|
||||
status_model.http_code = 200;
|
||||
|
||||
String parse_error;
|
||||
parse_error += "JSON parse failed\n";
|
||||
parse_error += error.c_str();
|
||||
|
||||
Serial.println(parse_error);
|
||||
|
||||
set_label_text_if_changed(battery_label, last_battery_text, "Battery\nJSON parse failed");
|
||||
set_label_text_if_changed(temps_label, last_temps_text, parse_error);
|
||||
set_label_text_if_changed(outputs_label, last_outputs_text, "Outputs\nNo parsed data");
|
||||
set_label_text_if_changed(system_label, last_system_text, parse_error);
|
||||
set_label_text_if_changed(system_status_label, last_system_status_text, String("JSON parse failed: ") + error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.println("JSON parse OK");
|
||||
|
||||
status_model.api_ok = true;
|
||||
status_model.http_code = 200;
|
||||
|
||||
if (doc["battery"].is<JsonObject>()) {
|
||||
JsonObject battery = doc["battery"];
|
||||
@@ -310,281 +322,82 @@ static void parse_status_json(const String &body)
|
||||
}
|
||||
|
||||
if (doc["temps"].is<JsonArray>()) {
|
||||
String temps = "";
|
||||
JsonArray temp_array = doc["temps"];
|
||||
int shown_temps = 0;
|
||||
String inside = "Inside\n--";
|
||||
String outside = "Outside\n--";
|
||||
|
||||
for (JsonObject temp : temp_array) {
|
||||
bool enabled = temp["enabled"] | false;
|
||||
bool online = temp["online"] | false;
|
||||
for (JsonObject temp : doc["temps"].as<JsonArray>()) {
|
||||
bool enabled = temp["enabled"] | false;
|
||||
bool online = temp["online"] | false;
|
||||
bool weather = temp["weather"] | false;
|
||||
|
||||
if (!enabled && shown_temps >= 2) {
|
||||
continue;
|
||||
}
|
||||
if (!enabled || !online || temp["temperature_f"].isNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *name = temp["name"] | temp["id"] | "Temp";
|
||||
String value = String((float)(temp["temperature_f"] | 0.0), 1);
|
||||
value += " F";
|
||||
|
||||
temps += name;
|
||||
temps += ": ";
|
||||
|
||||
if (!enabled) {
|
||||
temps += "disabled";
|
||||
} else if (!online) {
|
||||
temps += "offline";
|
||||
} else if (temp["temperature_f"].isNull()) {
|
||||
temps += "n/a";
|
||||
} else {
|
||||
float temperature_f = temp["temperature_f"] | 0.0;
|
||||
temps += String(temperature_f, 1);
|
||||
temps += " F";
|
||||
}
|
||||
|
||||
temps += "\n";
|
||||
shown_temps++;
|
||||
|
||||
if (shown_temps >= 5) {
|
||||
break;
|
||||
if (weather) {
|
||||
outside = "Outside\n" + value;
|
||||
} else {
|
||||
const char *name = temp["name"] | "Inside";
|
||||
inside = String(name) + "\n" + value;
|
||||
}
|
||||
}
|
||||
|
||||
if (temps.length() == 0) {
|
||||
temps = "No temp sensors reported";
|
||||
}
|
||||
|
||||
status_model.temps_text = temps;
|
||||
set_label_text_if_changed(inside_temp_label, last_inside_temp_text, inside);
|
||||
set_label_text_if_changed(outside_temp_label, last_outside_temp_text, outside);
|
||||
}
|
||||
|
||||
if (doc["relays"].is<JsonArray>()) {
|
||||
String outputs = "";
|
||||
JsonArray relay_array = doc["relays"];
|
||||
|
||||
relay_count = 0;
|
||||
|
||||
for (JsonObject relay : relay_array) {
|
||||
const char *id = relay["id"] | "";
|
||||
const char *name = relay["name"] | relay["id"] | "Output";
|
||||
bool enabled = relay["enabled"] | false;
|
||||
bool available = relay["available"] | true;
|
||||
bool state = relay["state"] | false;
|
||||
int channel = relay["hardware_channel"] | 0;
|
||||
for (JsonObject relay : doc["relays"].as<JsonArray>()) {
|
||||
const char *id = relay["id"] | "";
|
||||
const char *name = relay["name"] | relay["id"] | "Output";
|
||||
bool enabled = relay["enabled"] | false;
|
||||
bool available = relay["available"] | true;
|
||||
bool state = relay["state"] | false;
|
||||
|
||||
if (relay_count < 6 && enabled && available) {
|
||||
relay_ids[relay_count] = String(id);
|
||||
relay_names[relay_count] = String(name);
|
||||
relay_states[relay_count] = state;
|
||||
relay_count++;
|
||||
if (relay_count < 6 && enabled && available) {
|
||||
relay_ids[relay_count] = String(id);
|
||||
relay_names[relay_count] = String(name);
|
||||
relay_states[relay_count] = state;
|
||||
relay_count++;
|
||||
}
|
||||
}
|
||||
|
||||
outputs += String(channel);
|
||||
outputs += ". ";
|
||||
outputs += name;
|
||||
outputs += ": ";
|
||||
|
||||
if (!available) {
|
||||
outputs += "unavailable";
|
||||
} else if (!enabled) {
|
||||
outputs += "disabled";
|
||||
} else {
|
||||
outputs += on_off(state);
|
||||
}
|
||||
|
||||
outputs += "\n";
|
||||
}
|
||||
|
||||
if (outputs.length() == 0) {
|
||||
outputs = "No outputs reported";
|
||||
}
|
||||
|
||||
status_model.outputs_text = outputs;
|
||||
update_relay_buttons();
|
||||
}
|
||||
|
||||
Serial.println("Parsed status summary:");
|
||||
Serial.print(" SOC: ");
|
||||
Serial.println(status_model.soc);
|
||||
Serial.print(" Voltage: ");
|
||||
Serial.println(status_model.voltage);
|
||||
Serial.print(" Temps text: ");
|
||||
Serial.println(status_model.temps_text);
|
||||
Serial.print(" Outputs text: ");
|
||||
Serial.println(status_model.outputs_text);
|
||||
|
||||
update_overview_widgets();
|
||||
}
|
||||
|
||||
static void touch_test_event_cb(lv_event_t *event)
|
||||
{
|
||||
if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
|
||||
return;
|
||||
}
|
||||
|
||||
touch_count++;
|
||||
|
||||
if (touch_count_label != nullptr) {
|
||||
lvgl_port_lock(-1);
|
||||
lv_label_set_text_fmt(touch_count_label, "Touch count: %lu", (unsigned long)touch_count);
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
Serial.print("Touch count: ");
|
||||
Serial.println(touch_count);
|
||||
}
|
||||
|
||||
static void process_pending_relay_command()
|
||||
{
|
||||
if (!relay_command_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (api_request_in_progress) {
|
||||
return;
|
||||
}
|
||||
|
||||
String relay_id = pending_relay_id;
|
||||
bool next_state = pending_relay_state;
|
||||
int index = pending_relay_index;
|
||||
bool previous_state = pending_relay_previous_state;
|
||||
|
||||
String sending_text = "Sending relay command\n";
|
||||
sending_text += relay_id;
|
||||
sending_text += " -> ";
|
||||
sending_text += on_off(next_state);
|
||||
set_label_text_if_changed(system_label, last_system_text, sending_text);
|
||||
|
||||
bool ok = post_relay_state(relay_id, next_state);
|
||||
|
||||
relay_command_pending = false;
|
||||
pending_relay_id = "";
|
||||
pending_relay_index = -1;
|
||||
|
||||
if (ok) {
|
||||
String sent_text = "Relay command sent\n";
|
||||
sent_text += relay_id;
|
||||
sent_text += " -> ";
|
||||
sent_text += on_off(next_state);
|
||||
sent_text += "\nRefreshing status...";
|
||||
set_label_text_if_changed(system_label, last_system_text, sent_text);
|
||||
request_fast_status_refresh();
|
||||
} else {
|
||||
if (index >= 0 && index < relay_count) {
|
||||
relay_states[index] = previous_state;
|
||||
update_relay_buttons();
|
||||
update_outputs_label_from_relays();
|
||||
}
|
||||
|
||||
set_label_text_if_changed(system_label, last_system_text, "Relay command failed");
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_wifi_if_needed()
|
||||
{
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (millis() - last_wifi_attempt_ms < 10000) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_wifi_attempt_ms = millis();
|
||||
|
||||
Serial.print("Connecting to Cargo ESP AP: ");
|
||||
Serial.println(CARGO_WIFI_SSID);
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(CARGO_WIFI_SSID, CARGO_WIFI_PASSWORD);
|
||||
}
|
||||
|
||||
static void update_wifi_label()
|
||||
{
|
||||
if (millis() - last_wifi_label_update_ms < 5000) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_wifi_label_update_ms = millis();
|
||||
|
||||
String text;
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
text += "Dashboard WiFi: connected";
|
||||
text += "\nIP: ";
|
||||
text += WiFi.localIP().toString();
|
||||
text += "\nRSSI: ";
|
||||
text += String(WiFi.RSSI());
|
||||
text += " dBm";
|
||||
} else {
|
||||
text += "Dashboard WiFi: not connected";
|
||||
text += "\nSSID: ";
|
||||
text += CARGO_WIFI_SSID;
|
||||
}
|
||||
|
||||
set_label_text_if_changed(wifi_label, last_wifi_text, text);
|
||||
update_system_status_label();
|
||||
}
|
||||
|
||||
static bool fetch_status_fields(const char *url, const char *label)
|
||||
{
|
||||
if (api_request_in_progress) {
|
||||
Serial.print("Skipping ");
|
||||
Serial.print(label);
|
||||
Serial.println(": request already in progress");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
set_label_text_if_changed(system_label, last_system_text, "API: waiting for WiFi");
|
||||
if (api_request_in_progress || WiFi.status() != WL_CONNECTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
api_request_in_progress = true;
|
||||
unsigned long start_ms = millis();
|
||||
|
||||
Serial.print("GET ");
|
||||
Serial.println(url);
|
||||
|
||||
HTTPClient http;
|
||||
http.setTimeout(5000);
|
||||
|
||||
unsigned long begin_start_ms = millis();
|
||||
http.begin(url);
|
||||
unsigned long begin_ms = millis() - begin_start_ms;
|
||||
|
||||
unsigned long get_start_ms = millis();
|
||||
int code = http.GET();
|
||||
unsigned long get_ms = millis() - get_start_ms;
|
||||
|
||||
status_model.http_code = code;
|
||||
|
||||
Serial.print(label);
|
||||
Serial.print(" HTTP code: ");
|
||||
Serial.println(code);
|
||||
|
||||
if (code == 200) {
|
||||
unsigned long body_start_ms = millis();
|
||||
String body = http.getString();
|
||||
unsigned long body_ms = millis() - body_start_ms;
|
||||
|
||||
Serial.print(label);
|
||||
Serial.print(" body length: ");
|
||||
Serial.println(body.length());
|
||||
|
||||
unsigned long parse_start_ms = millis();
|
||||
parse_status_json(body);
|
||||
unsigned long parse_ms = millis() - parse_start_ms;
|
||||
|
||||
unsigned long total_ms = millis() - start_ms;
|
||||
|
||||
Serial.print("TIMING ");
|
||||
Serial.print(label);
|
||||
Serial.print(" begin=");
|
||||
Serial.print(begin_ms);
|
||||
Serial.print("ms get=");
|
||||
Serial.print(get_ms);
|
||||
Serial.print("ms body=");
|
||||
Serial.print(body_ms);
|
||||
Serial.print("ms parse+ui=");
|
||||
Serial.print(parse_ms);
|
||||
Serial.print("ms total=");
|
||||
Serial.print(total_ms);
|
||||
Serial.print(" total=");
|
||||
Serial.print(millis() - start_ms);
|
||||
Serial.print("ms bytes=");
|
||||
Serial.println(body.length());
|
||||
|
||||
@@ -593,27 +406,8 @@ static bool fetch_status_fields(const char *url, const char *label)
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned long total_ms = millis() - start_ms;
|
||||
Serial.print("TIMING ");
|
||||
Serial.print(label);
|
||||
Serial.print(" failed total=");
|
||||
Serial.print(total_ms);
|
||||
Serial.println("ms");
|
||||
|
||||
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);
|
||||
set_label_text_if_changed(system_status_label, last_system_status_text, String("API error: HTTP ") + code);
|
||||
|
||||
http.end();
|
||||
api_request_in_progress = false;
|
||||
@@ -623,7 +417,6 @@ static bool fetch_status_fields(const char *url, const char *label)
|
||||
static void poll_status_api()
|
||||
{
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
set_label_text_if_changed(system_label, last_system_text, "API: waiting for WiFi");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -644,32 +437,21 @@ static lv_obj_t *create_card(lv_obj_t *parent, const char *title, int width, int
|
||||
lv_obj_t *card = lv_obj_create(parent);
|
||||
lv_obj_set_size(card, width, height);
|
||||
lv_obj_align(card, align, x, y);
|
||||
lv_obj_set_style_radius(card, 16, 0);
|
||||
lv_obj_set_style_radius(card, 18, 0);
|
||||
lv_obj_set_style_bg_color(card, lv_color_hex(0x1F2933), 0);
|
||||
lv_obj_set_style_border_color(card, lv_color_hex(0x3A4652), 0);
|
||||
lv_obj_set_style_border_width(card, 2, 0);
|
||||
lv_obj_set_style_pad_all(card, 12, 0);
|
||||
lv_obj_set_style_pad_all(card, 14, 0);
|
||||
|
||||
lv_obj_t *title_label = lv_label_create(card);
|
||||
lv_label_set_text(title_label, title);
|
||||
lv_obj_set_style_text_color(title_label, lv_color_hex(0xFFFFFF), 0);
|
||||
lv_obj_set_style_text_color(title_label, lv_color_hex(0xB8C0C8), 0);
|
||||
lv_obj_align(title_label, LV_ALIGN_TOP_LEFT, 0, 0);
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
static lv_obj_t *create_body_label(lv_obj_t *card, int width)
|
||||
{
|
||||
lv_obj_t *label = lv_label_create(card);
|
||||
lv_label_set_text(label, "Waiting for /api/v1/status...");
|
||||
lv_obj_set_style_text_color(label, lv_color_hex(0xDDE3EA), 0);
|
||||
lv_obj_set_width(label, width);
|
||||
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
|
||||
lv_obj_align(label, LV_ALIGN_TOP_LEFT, 0, 32);
|
||||
return label;
|
||||
}
|
||||
|
||||
static void create_overland_status_screen()
|
||||
static void create_overland_overview_screen()
|
||||
{
|
||||
lv_obj_t *screen = lv_scr_act();
|
||||
lv_obj_clean(screen);
|
||||
@@ -681,40 +463,42 @@ static void create_overland_status_screen()
|
||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_30, 0);
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 16);
|
||||
|
||||
lv_obj_t *subtitle = lv_label_create(screen);
|
||||
lv_label_set_text(subtitle, "ESP32-S3 Dashboard - Cargo ESP Overview");
|
||||
lv_obj_set_style_text_color(subtitle, lv_color_hex(0xB8C0C8), 0);
|
||||
lv_obj_set_width(subtitle, 920);
|
||||
lv_obj_set_style_text_align(subtitle, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_align_to(subtitle, title, LV_ALIGN_OUT_BOTTOM_MID, 0, 6);
|
||||
lv_obj_t *battery_card = create_card(screen, "Greta Battery", 300, 255, LV_ALIGN_TOP_LEFT, 32, 80);
|
||||
lv_obj_t *temp_card = create_card(screen, "Temperatures", 300, 255, LV_ALIGN_TOP_MID, 0, 80);
|
||||
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);
|
||||
|
||||
lv_obj_t *battery_card = create_card(screen, "Battery", 300, 230, LV_ALIGN_TOP_LEFT, 32, 96);
|
||||
lv_obj_t *temps_card = create_card(screen, "Temperatures", 300, 230, LV_ALIGN_TOP_MID, 0, 96);
|
||||
lv_obj_t *outputs_card = create_card(screen, "Outputs", 300, 230, LV_ALIGN_TOP_RIGHT, -32, 96);
|
||||
lv_obj_t *system_card = create_card(screen, "System / Network", 620, 190, LV_ALIGN_BOTTOM_LEFT, 32, -50);
|
||||
lv_obj_t *touch_card = create_card(screen, "Relay Controls", 300, 190, LV_ALIGN_BOTTOM_RIGHT, -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, 40);
|
||||
|
||||
battery_label = create_body_label(battery_card, 260);
|
||||
temps_label = create_body_label(temps_card, 260);
|
||||
outputs_label = create_body_label(outputs_card, 260);
|
||||
battery_detail_label = lv_label_create(battery_card);
|
||||
lv_label_set_text(battery_detail_label, "Waiting for battery data...");
|
||||
lv_obj_set_style_text_color(battery_detail_label, lv_color_hex(0xDDE3EA), 0);
|
||||
lv_obj_set_width(battery_detail_label, 260);
|
||||
lv_label_set_long_mode(battery_detail_label, LV_LABEL_LONG_WRAP);
|
||||
lv_obj_align(battery_detail_label, LV_ALIGN_TOP_LEFT, 0, 95);
|
||||
|
||||
lv_label_set_text(battery_label, "Waiting for battery data...");
|
||||
lv_label_set_text(temps_label, "Waiting for temp data...");
|
||||
lv_label_set_text(outputs_label, "Waiting for output data...");
|
||||
inside_temp_label = lv_label_create(temp_card);
|
||||
lv_label_set_text(inside_temp_label, "Inside\n--");
|
||||
lv_obj_set_style_text_color(inside_temp_label, lv_color_hex(0xFFFFFF), 0);
|
||||
lv_obj_set_style_text_font(inside_temp_label, &lv_font_montserrat_30, 0);
|
||||
lv_obj_set_width(inside_temp_label, 260);
|
||||
lv_obj_align(inside_temp_label, LV_ALIGN_TOP_LEFT, 0, 45);
|
||||
|
||||
wifi_label = create_body_label(system_card, 275);
|
||||
outside_temp_label = lv_label_create(temp_card);
|
||||
lv_label_set_text(outside_temp_label, "Outside\n--");
|
||||
lv_obj_set_style_text_color(outside_temp_label, lv_color_hex(0xFFFFFF), 0);
|
||||
lv_obj_set_style_text_font(outside_temp_label, &lv_font_montserrat_30, 0);
|
||||
lv_obj_set_width(outside_temp_label, 260);
|
||||
lv_obj_align(outside_temp_label, LV_ALIGN_TOP_LEFT, 0, 145);
|
||||
|
||||
system_label = lv_label_create(system_card);
|
||||
lv_label_set_text(system_label, "API: waiting");
|
||||
lv_obj_set_style_text_color(system_label, lv_color_hex(0xB8C0C8), 0);
|
||||
lv_obj_set_width(system_label, 285);
|
||||
lv_label_set_long_mode(system_label, LV_LABEL_LONG_WRAP);
|
||||
lv_obj_align(system_label, LV_ALIGN_TOP_LEFT, 305, 32);
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
relay_buttons[i] = lv_btn_create(touch_card);
|
||||
lv_obj_set_size(relay_buttons[i], 120, 48);
|
||||
lv_obj_align(relay_buttons[i], LV_ALIGN_TOP_LEFT, i * 132, 34);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
relay_buttons[i] = lv_btn_create(relay_card);
|
||||
lv_obj_set_size(relay_buttons[i], 125, 60);
|
||||
lv_obj_align(relay_buttons[i], LV_ALIGN_TOP_LEFT, (i % 2) * 140, 40 + ((i / 2) * 68));
|
||||
lv_obj_add_event_cb(
|
||||
relay_buttons[i],
|
||||
relay_button_event_cb,
|
||||
@@ -723,32 +507,18 @@ static void create_overland_status_screen()
|
||||
);
|
||||
|
||||
relay_button_labels[i] = lv_label_create(relay_buttons[i]);
|
||||
lv_label_set_text(relay_button_labels[i], "Relay");
|
||||
lv_obj_center(relay_button_labels[i]);
|
||||
}
|
||||
|
||||
for (int i = 2; i < 6; i++) {
|
||||
relay_buttons[i] = lv_btn_create(touch_card);
|
||||
lv_obj_set_size(relay_buttons[i], 120, 42);
|
||||
lv_obj_align(relay_buttons[i], LV_ALIGN_TOP_LEFT, ((i - 2) % 2) * 132, 92 + (((i - 2) / 2) * 48));
|
||||
lv_obj_add_event_cb(
|
||||
relay_buttons[i],
|
||||
relay_button_event_cb,
|
||||
LV_EVENT_CLICKED,
|
||||
reinterpret_cast<void *>(static_cast<intptr_t>(i))
|
||||
);
|
||||
|
||||
relay_button_labels[i] = lv_label_create(relay_buttons[i]);
|
||||
lv_label_set_text(relay_button_labels[i], "Relay");
|
||||
lv_label_set_text(relay_button_labels[i], "Output\n--");
|
||||
lv_obj_set_style_text_align(relay_button_labels[i], LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_center(relay_button_labels[i]);
|
||||
lv_obj_add_flag(relay_buttons[i], LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
touch_count_label = lv_label_create(touch_card);
|
||||
lv_label_set_text(touch_count_label, "Relay controls");
|
||||
lv_obj_set_style_text_color(touch_count_label, lv_color_hex(0xB8C0C8), 0);
|
||||
lv_obj_set_width(touch_count_label, 250);
|
||||
lv_obj_align(touch_count_label, LV_ALIGN_TOP_LEFT, 0, 142);
|
||||
system_status_label = lv_label_create(system_card);
|
||||
lv_label_set_text(system_status_label, "Connecting to Cargo ESP...");
|
||||
lv_obj_set_style_text_color(system_status_label, lv_color_hex(0xDDE3EA), 0);
|
||||
lv_obj_set_width(system_status_label, 580);
|
||||
lv_label_set_long_mode(system_status_label, LV_LABEL_LONG_WRAP);
|
||||
lv_obj_align(system_status_label, LV_ALIGN_TOP_LEFT, 0, 35);
|
||||
|
||||
lv_obj_t *footer = lv_label_create(screen);
|
||||
lv_label_set_text(footer, "Fast: battery/temps/relays every 2s | Slow: system/config/network every 30s");
|
||||
@@ -792,18 +562,18 @@ void setup()
|
||||
lvgl_port_init(board->getLCD(), board->getTouch());
|
||||
|
||||
lvgl_port_lock(-1);
|
||||
create_overland_status_screen();
|
||||
create_overland_overview_screen();
|
||||
lvgl_port_unlock();
|
||||
|
||||
Serial.println("Dashboard clean overview ready");
|
||||
Serial.println("Dashboard overview UI ready");
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
connect_wifi_if_needed();
|
||||
update_wifi_label();
|
||||
update_system_status_label();
|
||||
process_pending_relay_command();
|
||||
poll_status_api();
|
||||
|
||||
delay(500);
|
||||
delay(250);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user