dashboard: add first-boot cargo WiFi setup
This commit is contained in:
@@ -319,3 +319,14 @@ The diagnostics overlay shows dashboard version, uptime, heap, WiFi RSSI/IP, Car
|
||||
- Sends standard 11-bit functional request ID `0x7DF` and accepts ECU responses from `0x7E8` through `0x7EF`.
|
||||
- Diagnostics page shows TWAI state, coolant value, TX/RX counts, and last OBD status.
|
||||
- Overview coolant gauge shows `--°` until a valid OBD response is received.
|
||||
|
||||
|
||||
## Dashboard first-boot WiFi setup
|
||||
|
||||
The ESP32-S3 dashboard no longer hardcodes Cargo ESP WiFi credentials or the Cargo API base URL. On first boot, it shows a touchscreen setup page where the user enters:
|
||||
|
||||
- Cargo ESP WiFi SSID
|
||||
- Cargo ESP WiFi password
|
||||
- Cargo API base URL, for example `http://192.168.4.1`
|
||||
|
||||
The dashboard saves these values in ESP32 Preferences/NVS and builds all `/api/v1` endpoint URLs at runtime. The boot screen also provides a Setup button for changing saved settings later.
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
static const char *DASHBOARD_VERSION = "0.1.126-restore-boot-screen";
|
||||
static const char *DASHBOARD_VERSION = "0.1.127-first-boot-wifi";
|
||||
|
||||
// Update these if your Cargo ESP AP credentials are different.
|
||||
static const char *CARGO_WIFI_SSID = "OverlandController";
|
||||
static const char *CARGO_WIFI_PASSWORD = "Ward1707";
|
||||
|
||||
//static const char *CARGO_WIFI_SSID = "WardAP";
|
||||
//static const char *CARGO_WIFI_PASSWORD = "Ward5213";
|
||||
|
||||
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";
|
||||
|
||||
//static const char *CARGO_API_STATUS_URL = "http://192.168.88.181/api/v1/status";
|
||||
//static const char *CARGO_API_FAST_STATUS_URL = "http://192.168.88.181/api/v1/status?fields=battery,temps,relays";
|
||||
//static const char *CARGO_API_SLOW_STATUS_URL = "http://192.168.88.181/api/v1/status?fields=network,system,config";
|
||||
//static const char *CARGO_API_RELAY_SET_URL = "http://192.168.88.181/api/v1/relay/set";
|
||||
// First-boot defaults. Credentials are stored in Preferences/NVS after setup.
|
||||
static const char *DASHBOARD_DEFAULT_CARGO_WIFI_SSID = "";
|
||||
static const char *DASHBOARD_DEFAULT_CARGO_WIFI_PASSWORD = "";
|
||||
static const char *DASHBOARD_DEFAULT_CARGO_API_BASE_URL = "http://192.168.4.1";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <Preferences.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <esp_display_panel.hpp>
|
||||
@@ -16,6 +17,23 @@ using namespace esp_panel::drivers;
|
||||
using namespace esp_panel::board;
|
||||
|
||||
static DashboardStatus status_model;
|
||||
|
||||
static Preferences dashboard_preferences;
|
||||
static String cargo_wifi_ssid;
|
||||
static String cargo_wifi_password;
|
||||
static String cargo_api_base_url;
|
||||
static String cargo_api_status_url;
|
||||
static String cargo_api_fast_status_url;
|
||||
static String cargo_api_slow_status_url;
|
||||
static String cargo_api_relay_set_url;
|
||||
static bool dashboard_configured = false;
|
||||
|
||||
static lv_obj_t *setup_ssid_textarea = nullptr;
|
||||
static lv_obj_t *setup_password_textarea = nullptr;
|
||||
static lv_obj_t *setup_api_textarea = nullptr;
|
||||
static lv_obj_t *setup_status_label = nullptr;
|
||||
static lv_obj_t *setup_keyboard = nullptr;
|
||||
|
||||
static constexpr gpio_num_t OBD_CAN_TX_PIN = GPIO_NUM_15;
|
||||
static constexpr gpio_num_t OBD_CAN_RX_PIN = GPIO_NUM_16;
|
||||
static constexpr uint32_t OBD_REQUEST_ID = 0x7DF;
|
||||
@@ -733,6 +751,7 @@ static void reset_battery_detail_widget_cache()
|
||||
|
||||
static void update_battery_detail_widgets();
|
||||
static void show_boot_screen();
|
||||
static void show_dashboard_setup_screen();
|
||||
static void show_diagnostics_overlay();
|
||||
static void close_diagnostics_overlay();
|
||||
static void update_diagnostics_overlay();
|
||||
@@ -1142,6 +1161,228 @@ static void update_relay_buttons()
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
|
||||
static String normalize_cargo_api_base_url(String value)
|
||||
{
|
||||
value.trim();
|
||||
|
||||
if (value.length() == 0) {
|
||||
value = DASHBOARD_DEFAULT_CARGO_API_BASE_URL;
|
||||
}
|
||||
|
||||
if (!value.startsWith("http://") && !value.startsWith("https://")) {
|
||||
value = "http://" + value;
|
||||
}
|
||||
|
||||
while (value.endsWith("/")) {
|
||||
value.remove(value.length() - 1);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static void rebuild_cargo_api_urls()
|
||||
{
|
||||
cargo_api_base_url = normalize_cargo_api_base_url(cargo_api_base_url);
|
||||
cargo_api_status_url = cargo_api_base_url + "/api/v1/status";
|
||||
cargo_api_fast_status_url = cargo_api_base_url + "/api/v1/status?fields=battery,temps,relays";
|
||||
cargo_api_slow_status_url = cargo_api_base_url + "/api/v1/status?fields=network,system,config";
|
||||
cargo_api_relay_set_url = cargo_api_base_url + "/api/v1/relay/set";
|
||||
}
|
||||
|
||||
static void load_dashboard_connection_config()
|
||||
{
|
||||
dashboard_preferences.begin("dashcfg", false);
|
||||
|
||||
cargo_wifi_ssid = dashboard_preferences.getString("wifi_ssid", DASHBOARD_DEFAULT_CARGO_WIFI_SSID);
|
||||
cargo_wifi_password = dashboard_preferences.getString("wifi_pass", DASHBOARD_DEFAULT_CARGO_WIFI_PASSWORD);
|
||||
cargo_api_base_url = dashboard_preferences.getString("api_base", DASHBOARD_DEFAULT_CARGO_API_BASE_URL);
|
||||
|
||||
rebuild_cargo_api_urls();
|
||||
dashboard_configured = cargo_wifi_ssid.length() > 0;
|
||||
|
||||
Serial.print("Dashboard cargo SSID configured: ");
|
||||
Serial.println(dashboard_configured ? "yes" : "no");
|
||||
Serial.print("Dashboard cargo API base: ");
|
||||
Serial.println(cargo_api_base_url);
|
||||
}
|
||||
|
||||
static void save_dashboard_connection_config(const String &ssid, const String &password, const String &api_base)
|
||||
{
|
||||
cargo_wifi_ssid = ssid;
|
||||
cargo_wifi_password = password;
|
||||
cargo_api_base_url = api_base;
|
||||
rebuild_cargo_api_urls();
|
||||
|
||||
dashboard_preferences.putString("wifi_ssid", cargo_wifi_ssid);
|
||||
dashboard_preferences.putString("wifi_pass", cargo_wifi_password);
|
||||
dashboard_preferences.putString("api_base", cargo_api_base_url);
|
||||
|
||||
dashboard_configured = cargo_wifi_ssid.length() > 0;
|
||||
}
|
||||
|
||||
static void setup_textarea_event_cb(lv_event_t *event)
|
||||
{
|
||||
lv_event_code_t code = lv_event_get_code(event);
|
||||
lv_obj_t *target = lv_event_get_target(event);
|
||||
|
||||
if (code == LV_EVENT_FOCUSED || code == LV_EVENT_CLICKED) {
|
||||
if (setup_keyboard != nullptr) {
|
||||
lv_keyboard_set_textarea(setup_keyboard, target);
|
||||
lv_obj_clear_flag(setup_keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static lv_obj_t *create_setup_textarea(lv_obj_t *parent, const char *label_text, const String &value, int y, bool password)
|
||||
{
|
||||
lv_obj_t *label = lv_label_create(parent);
|
||||
lv_label_set_text(label, label_text);
|
||||
lv_obj_set_style_text_color(label, lv_color_hex(0xDDE3EA), 0);
|
||||
lv_obj_set_style_text_font(label, &lv_font_montserrat_26, 0);
|
||||
lv_obj_align(label, LV_ALIGN_TOP_LEFT, 80, y);
|
||||
|
||||
lv_obj_t *textarea = lv_textarea_create(parent);
|
||||
lv_obj_set_size(textarea, 760, 52);
|
||||
lv_obj_align(textarea, LV_ALIGN_TOP_LEFT, 80, y + 32);
|
||||
lv_textarea_set_one_line(textarea, true);
|
||||
lv_textarea_set_text(textarea, value.c_str());
|
||||
lv_textarea_set_password_mode(textarea, password);
|
||||
lv_obj_set_style_text_font(textarea, &lv_font_montserrat_26, 0);
|
||||
lv_obj_add_event_cb(textarea, setup_textarea_event_cb, LV_EVENT_ALL, nullptr);
|
||||
|
||||
return textarea;
|
||||
}
|
||||
|
||||
static void setup_save_event_cb(lv_event_t *event)
|
||||
{
|
||||
if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
|
||||
return;
|
||||
}
|
||||
|
||||
String ssid = lv_textarea_get_text(setup_ssid_textarea);
|
||||
String password = lv_textarea_get_text(setup_password_textarea);
|
||||
String api_base = lv_textarea_get_text(setup_api_textarea);
|
||||
|
||||
ssid.trim();
|
||||
api_base.trim();
|
||||
|
||||
if (ssid.length() == 0) {
|
||||
if (setup_status_label != nullptr) {
|
||||
lv_label_set_text(setup_status_label, "SSID is required.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
save_dashboard_connection_config(ssid, password, api_base);
|
||||
|
||||
WiFi.disconnect(true);
|
||||
was_wifi_connected = false;
|
||||
overview_screen_created = false;
|
||||
last_wifi_attempt_ms = 0;
|
||||
last_status_poll_ms = 0;
|
||||
last_metadata_poll_ms = 0;
|
||||
metadata_refresh_requested = true;
|
||||
|
||||
lvgl_port_lock(-1);
|
||||
if (dashboard_configured) {
|
||||
show_boot_screen();
|
||||
} else {
|
||||
show_dashboard_setup_screen();
|
||||
}
|
||||
lvgl_port_unlock();
|
||||
|
||||
Serial.println("Dashboard connection settings saved.");
|
||||
}
|
||||
|
||||
static void setup_reset_event_cb(lv_event_t *event)
|
||||
{
|
||||
if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
|
||||
return;
|
||||
}
|
||||
|
||||
dashboard_preferences.clear();
|
||||
cargo_wifi_ssid = "";
|
||||
cargo_wifi_password = "";
|
||||
cargo_api_base_url = DASHBOARD_DEFAULT_CARGO_API_BASE_URL;
|
||||
rebuild_cargo_api_urls();
|
||||
dashboard_configured = false;
|
||||
|
||||
WiFi.disconnect(true);
|
||||
|
||||
lv_textarea_set_text(setup_ssid_textarea, "");
|
||||
lv_textarea_set_text(setup_password_textarea, "");
|
||||
lv_textarea_set_text(setup_api_textarea, cargo_api_base_url.c_str());
|
||||
|
||||
if (setup_status_label != nullptr) {
|
||||
lv_label_set_text(setup_status_label, "Saved settings cleared.");
|
||||
}
|
||||
}
|
||||
|
||||
static void show_dashboard_setup_screen()
|
||||
{
|
||||
lv_obj_t *screen = lv_scr_act();
|
||||
lv_obj_clean(screen);
|
||||
lv_obj_set_style_bg_color(screen, lv_color_hex(0x101418), 0);
|
||||
|
||||
lv_obj_t *title = lv_label_create(screen);
|
||||
lv_label_set_text(title, "Dashboard Setup");
|
||||
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
|
||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_48, 0);
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 28);
|
||||
|
||||
lv_obj_t *subtitle = lv_label_create(screen);
|
||||
lv_label_set_text(subtitle, "Enter Cargo ESP WiFi and API settings.");
|
||||
lv_obj_set_style_text_color(subtitle, lv_color_hex(0xB8C0C8), 0);
|
||||
lv_obj_set_style_text_font(subtitle, &lv_font_montserrat_26, 0);
|
||||
lv_obj_align(subtitle, LV_ALIGN_TOP_MID, 0, 86);
|
||||
|
||||
setup_ssid_textarea = create_setup_textarea(screen, "Cargo WiFi SSID", cargo_wifi_ssid, 130, false);
|
||||
setup_password_textarea = create_setup_textarea(screen, "Cargo WiFi Password", cargo_wifi_password, 220, true);
|
||||
setup_api_textarea = create_setup_textarea(screen, "Cargo API Base URL", cargo_api_base_url, 310, false);
|
||||
|
||||
lv_obj_t *save_button = lv_btn_create(screen);
|
||||
lv_obj_set_size(save_button, 210, 58);
|
||||
lv_obj_align(save_button, LV_ALIGN_TOP_LEFT, 80, 408);
|
||||
lv_obj_add_event_cb(save_button, setup_save_event_cb, LV_EVENT_CLICKED, nullptr);
|
||||
lv_obj_t *save_label = lv_label_create(save_button);
|
||||
lv_label_set_text(save_label, "Save");
|
||||
lv_obj_center(save_label);
|
||||
|
||||
lv_obj_t *clear_button = lv_btn_create(screen);
|
||||
lv_obj_set_size(clear_button, 210, 58);
|
||||
lv_obj_align(clear_button, LV_ALIGN_TOP_LEFT, 310, 408);
|
||||
lv_obj_add_event_cb(clear_button, setup_reset_event_cb, LV_EVENT_CLICKED, nullptr);
|
||||
lv_obj_t *clear_label = lv_label_create(clear_button);
|
||||
lv_label_set_text(clear_label, "Clear");
|
||||
lv_obj_center(clear_label);
|
||||
|
||||
setup_status_label = lv_label_create(screen);
|
||||
lv_label_set_text(setup_status_label, dashboard_configured ? "Settings loaded." : "First boot: setup required.");
|
||||
lv_obj_set_style_text_color(setup_status_label, lv_color_hex(0xDDE3EA), 0);
|
||||
lv_obj_set_style_text_font(setup_status_label, &lv_font_montserrat_26, 0);
|
||||
lv_obj_set_width(setup_status_label, 420);
|
||||
lv_label_set_long_mode(setup_status_label, LV_LABEL_LONG_WRAP);
|
||||
lv_obj_align(setup_status_label, LV_ALIGN_TOP_LEFT, 548, 410);
|
||||
|
||||
setup_keyboard = lv_keyboard_create(screen);
|
||||
lv_obj_set_size(setup_keyboard, 1024, 180);
|
||||
lv_obj_align(setup_keyboard, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_add_flag(setup_keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
static void setup_button_event_cb(lv_event_t *event)
|
||||
{
|
||||
if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
|
||||
return;
|
||||
}
|
||||
|
||||
lvgl_port_lock(-1);
|
||||
show_dashboard_setup_screen();
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
|
||||
static bool post_relay_state(const String &relay_id, bool state)
|
||||
{
|
||||
if (api_request_in_progress || WiFi.status() != WL_CONNECTED) {
|
||||
@@ -1158,12 +1399,12 @@ static bool post_relay_state(const String &relay_id, bool state)
|
||||
serializeJson(request, payload);
|
||||
|
||||
Serial.print("POST ");
|
||||
Serial.println(CARGO_API_RELAY_SET_URL);
|
||||
Serial.println(cargo_api_relay_set_url);
|
||||
Serial.println(payload);
|
||||
|
||||
HTTPClient http;
|
||||
http.setTimeout(1200);
|
||||
http.begin(CARGO_API_RELAY_SET_URL);
|
||||
http.begin(cargo_api_relay_set_url);
|
||||
http.setReuse(false);
|
||||
http.addHeader("Connection", "close");
|
||||
http.addHeader("Content-Type", "application/json");
|
||||
@@ -1280,6 +1521,10 @@ static void process_pending_relay_command()
|
||||
|
||||
static void connect_wifi_if_needed()
|
||||
{
|
||||
if (!dashboard_configured) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
return;
|
||||
}
|
||||
@@ -1291,10 +1536,10 @@ static void connect_wifi_if_needed()
|
||||
last_wifi_attempt_ms = millis();
|
||||
|
||||
Serial.print("Connecting to Cargo ESP AP: ");
|
||||
Serial.println(CARGO_WIFI_SSID);
|
||||
Serial.println(cargo_wifi_ssid);
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(CARGO_WIFI_SSID, CARGO_WIFI_PASSWORD);
|
||||
WiFi.begin(cargo_wifi_ssid.c_str(), cargo_wifi_password.c_str());
|
||||
}
|
||||
|
||||
static void update_system_status_label()
|
||||
@@ -1835,13 +2080,13 @@ static void poll_status_api()
|
||||
if (fast_status_refresh_requested || millis() - last_status_poll_ms >= 1500) {
|
||||
fast_status_refresh_requested = false;
|
||||
last_status_poll_ms = millis();
|
||||
fetch_status_fields(CARGO_API_FAST_STATUS_URL, "fast status");
|
||||
fetch_status_fields(cargo_api_fast_status_url.c_str(), "fast status");
|
||||
}
|
||||
|
||||
if (metadata_refresh_requested || millis() - last_metadata_poll_ms >= 30000) {
|
||||
metadata_refresh_requested = false;
|
||||
last_metadata_poll_ms = millis();
|
||||
fetch_status_fields(CARGO_API_SLOW_STATUS_URL, "slow status");
|
||||
fetch_status_fields(cargo_api_slow_status_url.c_str(), "slow status");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1908,6 +2153,15 @@ static void show_boot_screen()
|
||||
lv_obj_set_width(api, 700);
|
||||
lv_obj_set_style_text_align(api, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_align(api, LV_ALIGN_CENTER, 0, 118);
|
||||
|
||||
lv_obj_t *setup_button = lv_btn_create(screen);
|
||||
lv_obj_set_size(setup_button, 180, 54);
|
||||
lv_obj_align(setup_button, LV_ALIGN_BOTTOM_MID, 0, -34);
|
||||
lv_obj_add_event_cb(setup_button, setup_button_event_cb, LV_EVENT_CLICKED, nullptr);
|
||||
|
||||
lv_obj_t *setup_label = lv_label_create(setup_button);
|
||||
lv_label_set_text(setup_label, "Setup");
|
||||
lv_obj_center(setup_label);
|
||||
}
|
||||
|
||||
static void create_overland_overview_screen()
|
||||
@@ -2181,6 +2435,8 @@ void setup()
|
||||
Serial.println("Display: 1024x600");
|
||||
Serial.println("API Base: /api/v1");
|
||||
|
||||
load_dashboard_connection_config();
|
||||
|
||||
Board *board = new Board();
|
||||
board->init();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user