dashboard: add cargo relay controls

This commit is contained in:
2026-06-09 22:52:43 -06:00
parent bc2bcdeeec
commit 8daba01bbb
@@ -10,16 +10,22 @@
using namespace esp_panel::drivers; using namespace esp_panel::drivers;
using namespace esp_panel::board; using namespace esp_panel::board;
static const char *DASHBOARD_VERSION = "0.0.4-clean-overview"; static const char *DASHBOARD_VERSION = "0.0.5-relay-control";
// Update these if your Cargo ESP AP credentials are different. // Update these if your Cargo ESP AP credentials are different.
static const char *CARGO_WIFI_SSID = "OverlandController"; static const char *CARGO_WIFI_SSID = "OverlandController";
static const char *CARGO_WIFI_PASSWORD = "overland1234"; 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_STATUS_URL = "http://192.168.4.1/api/v1/status";
static const char *CARGO_API_RELAY_SET_URL = "http://192.168.4.1/api/v1/relay/set";
static lv_obj_t *battery_label = nullptr; static lv_obj_t *battery_label = nullptr;
static lv_obj_t *temps_label = nullptr; static lv_obj_t *temps_label = nullptr;
static lv_obj_t *outputs_label = nullptr; static lv_obj_t *outputs_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 bool relay_states[6] = {false};
static int relay_count = 0;
static lv_obj_t *system_label = nullptr; static lv_obj_t *system_label = nullptr;
static lv_obj_t *wifi_label = nullptr; static lv_obj_t *wifi_label = nullptr;
static lv_obj_t *touch_count_label = nullptr; static lv_obj_t *touch_count_label = nullptr;
@@ -66,6 +72,101 @@ static String on_off(bool value)
return value ? "ON" : "OFF"; return value ? "ON" : "OFF";
} }
static void force_status_poll_soon()
{
last_status_poll_ms = 0;
}
static bool post_relay_state(const String &relay_id, bool state)
{
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Relay command skipped: WiFi not connected");
return false;
}
DynamicJsonDocument request(256);
request["id"] = relay_id;
request["state"] = state;
String payload;
serializeJson(request, payload);
Serial.print("POST ");
Serial.println(CARGO_API_RELAY_SET_URL);
Serial.println(payload);
HTTPClient http;
http.setTimeout(5000);
http.begin(CARGO_API_RELAY_SET_URL);
http.addHeader("Content-Type", "application/json");
int code = http.POST(payload);
String body = http.getString();
Serial.print("Relay HTTP code: ");
Serial.println(code);
Serial.println(body);
http.end();
return code >= 200 && code < 300;
}
static void relay_button_event_cb(lv_event_t *event)
{
if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
return;
}
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");
return;
}
String relay_id = relay_ids[index];
bool next_state = !relay_states[index];
String pending_text = "Sending 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);
bool ok = post_relay_state(relay_id, next_state);
String result_text = ok ? "Relay command sent\nRefreshing status..." : "Relay command failed";
set_label_text_if_changed(system_label, last_system_text, result_text);
force_status_poll_soon();
}
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) static void set_label_text_if_changed(lv_obj_t *label, String &last_text, const String &text)
{ {
if (label == nullptr || last_text == text) { if (label == nullptr || last_text == text) {
@@ -219,13 +320,22 @@ static void parse_status_json(const String &body)
String outputs = ""; String outputs = "";
JsonArray relay_array = doc["relays"]; JsonArray relay_array = doc["relays"];
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"; const char *name = relay["name"] | relay["id"] | "Output";
bool enabled = relay["enabled"] | false; bool enabled = relay["enabled"] | false;
bool available = relay["available"] | true; bool available = relay["available"] | true;
bool state = relay["state"] | false; bool state = relay["state"] | false;
int channel = relay["hardware_channel"] | 0; int channel = relay["hardware_channel"] | 0;
if (relay_count < 6 && enabled && available) {
relay_ids[relay_count] = String(id);
relay_states[relay_count] = state;
relay_count++;
}
outputs += String(channel); outputs += String(channel);
outputs += ". "; outputs += ". ";
outputs += name; outputs += name;
@@ -249,6 +359,8 @@ static void parse_status_json(const String &body)
status_model.temps_text = temps; status_model.temps_text = temps;
status_model.outputs_text = outputs; status_model.outputs_text = outputs;
update_relay_buttons();
Serial.println("Parsed status summary:"); Serial.println("Parsed status summary:");
Serial.print(" SOC: "); Serial.print(" SOC: ");
Serial.println(status_model.soc); Serial.println(status_model.soc);
@@ -442,7 +554,7 @@ static void create_overland_status_screen()
lv_obj_t *temps_card = create_card(screen, "Temperatures", 300, 230, LV_ALIGN_TOP_MID, 0, 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 *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 *system_card = create_card(screen, "System / Network", 620, 190, LV_ALIGN_BOTTOM_LEFT, 32, -50);
lv_obj_t *touch_card = create_card(screen, "Touch", 300, 190, LV_ALIGN_BOTTOM_RIGHT, -32, -50); lv_obj_t *touch_card = create_card(screen, "Relay Controls", 300, 190, LV_ALIGN_BOTTOM_RIGHT, -32, -50);
battery_label = create_body_label(battery_card, 260); battery_label = create_body_label(battery_card, 260);
temps_label = create_body_label(temps_card, 260); temps_label = create_body_label(temps_card, 260);
@@ -461,20 +573,44 @@ static void create_overland_status_screen()
lv_label_set_long_mode(system_label, LV_LABEL_LONG_WRAP); lv_label_set_long_mode(system_label, LV_LABEL_LONG_WRAP);
lv_obj_align(system_label, LV_ALIGN_TOP_LEFT, 305, 32); lv_obj_align(system_label, LV_ALIGN_TOP_LEFT, 305, 32);
lv_obj_t *button = lv_btn_create(touch_card); for (int i = 0; i < 2; i++) {
lv_obj_set_size(button, 170, 54); relay_buttons[i] = lv_btn_create(touch_card);
lv_obj_align(button, LV_ALIGN_TOP_LEFT, 0, 36); lv_obj_set_size(relay_buttons[i], 120, 48);
lv_obj_add_event_cb(button, touch_test_event_cb, LV_EVENT_CLICKED, nullptr); lv_obj_align(relay_buttons[i], LV_ALIGN_TOP_LEFT, i * 132, 34);
lv_obj_add_event_cb(
relay_buttons[i],
relay_button_event_cb,
LV_EVENT_CLICKED,
reinterpret_cast<void *>(static_cast<intptr_t>(i))
);
lv_obj_t *button_label = lv_label_create(button); relay_button_labels[i] = lv_label_create(relay_buttons[i]);
lv_label_set_text(button_label, "Touch Test"); lv_label_set_text(relay_button_labels[i], "Relay");
lv_obj_center(button_label); 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_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); touch_count_label = lv_label_create(touch_card);
lv_label_set_text(touch_count_label, "Touch count: 0"); 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_style_text_color(touch_count_label, lv_color_hex(0xB8C0C8), 0);
lv_obj_set_width(touch_count_label, 250); lv_obj_set_width(touch_count_label, 250);
lv_obj_align(touch_count_label, LV_ALIGN_TOP_LEFT, 0, 105); lv_obj_align(touch_count_label, LV_ALIGN_TOP_LEFT, 0, 142);
lv_obj_t *footer = lv_label_create(screen); 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, "Polling Cargo ESP /api/v1/status every 5 seconds");