diff --git a/platformio.ini b/platformio.ini index 6dfdf68..0829580 100644 --- a/platformio.ini +++ b/platformio.ini @@ -16,11 +16,18 @@ framework = arduino monitor_speed = 115200 monitor_port = /dev/ttyACM0 +board_build.arduino.memory_type = qio_opi + build_flags = -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 + ; Konfiguration für Arduino_GFX + -DCONFIG_ARDUINO_GFX_DEFAULT_BUS_TYPE=0 + -D LV_CONF_SKIP + -D LV_COLOR_16_SWAP=0 lib_deps = moononournation/GFX Library for Arduino - knolleary/PubSubClient \ No newline at end of file + knolleary/PubSubClient + lvgl/lvgl @ ^8.3.11 \ No newline at end of file diff --git a/src/Application.cpp b/src/Application.cpp index 66d0f47..41dd069 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -17,10 +17,7 @@ void Application::begin() display.begin(); - display.showStatus( - wifi.ip(), - mqtt.connected() ? "verbunden" : "offline" - ); + // display.showStatus(); } diff --git a/src/Config.h b/src/Config.h index 44beaef..10ab057 100644 --- a/src/Config.h +++ b/src/Config.h @@ -3,8 +3,8 @@ namespace Config { // WLAN - constexpr const char* WIFI_SSID = "RS20W"; - constexpr const char* WIFI_PASSWORD = "***"; + constexpr const char* WIFI_SSID = ""; + constexpr const char* WIFI_PASSWORD = ""; // MQTT constexpr const char* MQTT_HOST = "mqtt.diereuthers.home"; diff --git a/src/Display/DisplayManager.cpp b/src/Display/DisplayManager.cpp index 9fd2e51..9228622 100644 --- a/src/Display/DisplayManager.cpp +++ b/src/Display/DisplayManager.cpp @@ -1,31 +1,323 @@ -#include - #include "DisplayManager.h" +#include "WifiManager.h" +// Externer Zeiger auf deinen WifiManager (in main.cpp instanziiert) +extern WifiManager wifi; +DisplayManager* DisplayManager::instance = nullptr; + +// Statische Pointer für die LVGL-Elemente auf der Config-Seite +static lv_obj_t *ta_ssid; +static lv_obj_t *ta_pass; +static lv_obj_t *kb; + +DisplayManager::DisplayManager() +{ + instance = this; + + bus = new Arduino_ESP32QSPI( + TFT_CS, TFT_SCK, TFT_D0, TFT_D1, TFT_D2, TFT_D3 + ); + + // Physisches Display im Nativ-Modus (320x480) + output_display = new Arduino_AXS15231B( + bus, TFT_RST, 0 /* Rotation 0 */, false, 320, 480 + ); + + // Canvas mit physischem Puffer + gfx = new Arduino_Canvas(320, 480, output_display, 0, 0, 0); +} + +DisplayManager::~DisplayManager() +{ + delete gfx; + delete output_display; + delete bus; +} + +// --------------------------------------------------------------------- +// LVGL CALLBACKS +// --------------------------------------------------------------------- +void DisplayManager::my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) +{ + uint32_t w = (area->x2 - area->x1 + 1); + uint32_t h = (area->y2 - area->y1 + 1); + + instance->gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h); + instance->gfx->flush(); + + lv_disp_flush_ready(disp); +} + +void DisplayManager::my_touch_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) +{ + TouchPoint tp = instance->readTouch(); + if (tp.touched) { + data->state = LV_INDEV_STATE_PR; + data->point.x = tp.x; + data->point.y = tp.y; + } else { + data->state = LV_INDEV_STATE_REL; + } +} + +// --------------------------------------------------------------------- +// INITIALISIERUNG +// --------------------------------------------------------------------- void DisplayManager::begin() { - Serial.println("DisplayManager gestartet"); + pinMode(TFT_BL, OUTPUT); + digitalWrite(TFT_BL, HIGH); + + if (!gfx->begin()) { + Serial.println("[Display] FEHLER: Canvas konnte nicht gestartet werden!"); + return; + } + + // Canvas logisch auf Querformat (480x320) drehen + gfx->setRotation(1); + _initialized = true; + + initTouch(); + initLVGL(); } - -void DisplayManager::showStatus( - const String& wifi, - const String& mqtt -) +void DisplayManager::initTouch() { - Serial.println("----------------"); - Serial.println("DISPLAY"); - Serial.print("WLAN: "); - Serial.println(wifi); + pinMode(TOUCH_RST, OUTPUT); + digitalWrite(TOUCH_RST, HIGH); + delay(10); + digitalWrite(TOUCH_RST, LOW); + delay(50); + digitalWrite(TOUCH_RST, HIGH); + delay(100); - Serial.print("MQTT: "); - Serial.println(mqtt); - - Serial.println("----------------"); + pinMode(TOUCH_INT, INPUT_PULLUP); + Wire.begin(TOUCH_SDA, TOUCH_SCL, 400000); + Wire.setTimeOut(100); } +void DisplayManager::initLVGL() +{ + lv_init(); + + static lv_disp_draw_buf_t draw_buf; + static lv_color_t *buf = (lv_color_t *)heap_caps_malloc(480 * 40 * sizeof(lv_color_t), MALLOC_CAP_SPIRAM); + lv_disp_draw_buf_init(&draw_buf, buf, NULL, 480 * 40); + + static lv_disp_drv_t disp_drv; + lv_disp_drv_init(&disp_drv); + disp_drv.hor_res = 480; + disp_drv.ver_res = 320; + disp_drv.flush_cb = my_disp_flush; + disp_drv.draw_buf = &draw_buf; + lv_disp_drv_register(&disp_drv); + + static lv_indev_drv_t indev_drv; + lv_indev_drv_init(&indev_drv); + indev_drv.type = LV_INDEV_TYPE_POINTER; + indev_drv.read_cb = my_touch_read; + lv_indev_drv_register(&indev_drv); + + // Beide Oberflächen initial aufbauen + buildMainUI(); + buildConfigUI(); +} + +// --------------------------------------------------------------------- +// UI BUILDER +// --------------------------------------------------------------------- +void DisplayManager::buildMainUI() +{ + main_screen = lv_obj_create(NULL); + lv_obj_set_style_bg_color(main_screen, lv_color_hex(0x121212), LV_PART_MAIN); + + lv_obj_t *title = lv_label_create(main_screen); + lv_label_set_text(title, "MQTTPanel32 Querformat"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15); + lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), LV_PART_MAIN); + + // Test Button + lv_obj_t *btn = lv_btn_create(main_screen); + lv_obj_set_size(btn, 200, 50); + lv_obj_align(btn, LV_ALIGN_CENTER, -60, 0); + + lv_obj_add_event_cb(btn, [](lv_event_t *e) { + lv_obj_t *btn_obj = lv_event_get_target(e); + lv_obj_t *label = lv_obj_get_child(btn_obj, 0); + static bool state = false; + state = !state; + if (state) { + lv_label_set_text(label, "Status: AN"); + lv_obj_set_style_bg_color(btn_obj, lv_palette_main(LV_PALETTE_GREEN), LV_PART_MAIN); + } else { + lv_label_set_text(label, "Status: AUS"); + lv_obj_set_style_bg_color(btn_obj, lv_palette_main(LV_PALETTE_RED), LV_PART_MAIN); + } + }, LV_EVENT_CLICKED, NULL); + + lv_obj_t *btn_label = lv_label_create(btn); + lv_label_set_text(btn_label, "Drueck mich!"); + lv_obj_center(btn_label); + + // Button zu den WLAN Einstellungen + lv_obj_t *btn_cfg = lv_btn_create(main_screen); + lv_obj_set_size(btn_cfg, 100, 50); + lv_obj_align(btn_cfg, LV_ALIGN_CENTER, 120, 0); + lv_obj_set_style_bg_color(btn_cfg, lv_palette_main(LV_PALETTE_GREY), LV_PART_MAIN); + + lv_obj_add_event_cb(btn_cfg, [](lv_event_t *e) { + instance->showConfigScreen(); + }, LV_EVENT_CLICKED, NULL); + + lv_obj_t *lbl_cfg = lv_label_create(btn_cfg); + lv_label_set_text(lbl_cfg, "WLAN Setup"); + lv_obj_center(lbl_cfg); +} + +void DisplayManager::buildConfigUI() +{ + config_screen = lv_obj_create(NULL); + lv_obj_set_style_bg_color(config_screen, lv_color_hex(0x1E1E1E), LV_PART_MAIN); + + String saved_ssid = wifi.getSavedSSID(); + String saved_pass = wifi.getSavedPassword(); + + lv_obj_t *title = lv_label_create(config_screen); + lv_label_set_text(title, "WLAN Konfiguration"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); + lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), LV_PART_MAIN); + + // Textfeld SSID + ta_ssid = lv_textarea_create(config_screen); + lv_textarea_set_one_line(ta_ssid, true); + lv_textarea_set_placeholder_text(ta_ssid, "SSID"); + lv_textarea_set_text(ta_ssid, saved_ssid.c_str()); + lv_obj_set_size(ta_ssid, 110, 40); + lv_obj_align(ta_ssid, LV_ALIGN_TOP_LEFT, 10, 40); + + // Textfeld Passwort + ta_pass = lv_textarea_create(config_screen); + lv_textarea_set_one_line(ta_pass, true); + lv_textarea_set_password_mode(ta_pass, true); + lv_textarea_set_placeholder_text(ta_pass, "Passwort"); + lv_textarea_set_text(ta_pass, saved_pass.c_str()); + lv_obj_set_size(ta_pass, 180, 40); + lv_obj_align(ta_pass, LV_ALIGN_TOP_LEFT, 130, 40); + + // On-Screen Tastatur + kb = lv_keyboard_create(config_screen); + lv_obj_set_size(kb, 480, 150); + lv_obj_align(kb, LV_ALIGN_BOTTOM_MID, 0, 0); + + auto ta_event_cb = [](lv_event_t *e) { + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t *ta = lv_event_get_target(e); + if (code == LV_EVENT_FOCUSED) { + lv_keyboard_set_textarea(kb, ta); + } + }; + lv_obj_add_event_cb(ta_ssid, ta_event_cb, LV_EVENT_ALL, NULL); + lv_obj_add_event_cb(ta_pass, ta_event_cb, LV_EVENT_ALL, NULL); + + // Speichern-Button + lv_obj_t *btn_save = lv_btn_create(config_screen); + lv_obj_set_size(btn_save, 70, 40); + lv_obj_align(btn_save, LV_ALIGN_TOP_RIGHT, -90, 40); + lv_obj_set_style_bg_color(btn_save, lv_palette_main(LV_PALETTE_GREEN), LV_PART_MAIN); + + lv_obj_t *lbl_save = lv_label_create(btn_save); + lv_label_set_text(lbl_save, "Save"); + lv_obj_center(lbl_save); + + lv_obj_add_event_cb(btn_save, [](lv_event_t *e) { + const char *s = lv_textarea_get_text(ta_ssid); + const char *p = lv_textarea_get_text(ta_pass); + + // Übergabe der Daten an den WifiManager + wifi.saveCredentials(s, p); + }, LV_EVENT_CLICKED, NULL); + + // Cancel Button + lv_obj_t *btn_cancel = lv_btn_create(config_screen); + lv_obj_set_size(btn_cancel, 70, 40); + lv_obj_align(btn_cancel, LV_ALIGN_TOP_RIGHT, -10, 40); + lv_obj_set_style_bg_color(btn_cancel, lv_palette_main(LV_PALETTE_RED), LV_PART_MAIN); + + lv_obj_t *lbl_cancel = lv_label_create(btn_cancel); + lv_label_set_text(lbl_cancel, "Cancel"); + lv_obj_center(lbl_cancel); + + lv_obj_add_event_cb(btn_cancel, [](lv_event_t *e) { + // Zurück zur Hauptseite wechseln, ohne Änderungen zu speichern + instance->showMainScreen(); + }, LV_EVENT_CLICKED, NULL); +} + +// --------------------------------------------------------------------- +// HARDWARE TOUCH-TREIBER & MAPPING +// --------------------------------------------------------------------- +TouchPoint DisplayManager::readTouch() +{ + TouchPoint tp; + uint8_t cmd[8] = {0xB5, 0xAB, 0xA5, 0x5A, 0x00, 0x00, 0x00, 0x08}; + + Wire.beginTransmission(TOUCH_I2C_ADDR); + Wire.write(cmd, 8); + if (Wire.endTransmission() != 0) return tp; + + uint8_t buf[8] = {0}; + if (Wire.requestFrom((uint8_t)TOUCH_I2C_ADDR, (uint8_t)8) == 8) { + for (int i = 0; i < 8; i++) buf[i] = Wire.read(); + + uint8_t touch_count = buf[1] & 0x0F; + + uint16_t x_raw = ((uint16_t)(buf[2] & 0x0F) << 8) | buf[3]; + uint16_t y_raw = ((uint16_t)(buf[4] & 0x0F) << 8) | buf[5]; + + if (touch_count >= 1 && touch_count <= 5) { + // Umrechnung Hardware-Portrait -> Screen-Landscape (480x320) + uint16_t x_mapped = y_raw; + uint16_t y_mapped = 320 - x_raw; + + if (x_mapped < 480 && y_mapped < 320) { + tp.touched = true; + tp.x = x_mapped; + tp.y = y_mapped; + } + } + } + return tp; +} + +// --------------------------------------------------------------------- +// SCREEN SWITCHING & MAIN LOOP +// --------------------------------------------------------------------- +void DisplayManager::showMainScreen() { + if (main_screen) lv_scr_load(main_screen); +} + +void DisplayManager::showConfigScreen() { + if (config_screen) lv_scr_load(config_screen); +} void DisplayManager::loop() { + if (!_initialized) return; + + // LVGL Zeitgeber aktualisieren + static uint32_t last_tick = 0; + uint32_t now = millis(); + lv_tick_inc(now - last_tick); + last_tick = now; + + // LVGL Aufgaben verarbeiten + lv_timer_handler(); + delay(5); +} + +void DisplayManager::showStatus(const String& wifi, const String& mqtt) +{ + // Hier kannst du später z. B. ein LVGL Label aktualisieren + // Vorerst lassen wir die Methode leer, damit der Compiler zufrieden ist. } \ No newline at end of file diff --git a/src/Display/DisplayManager.h b/src/Display/DisplayManager.h index af6456f..332d8b3 100644 --- a/src/Display/DisplayManager.h +++ b/src/Display/DisplayManager.h @@ -1,18 +1,67 @@ #pragma once #include +#include +#include +#include +struct TouchPoint { + bool touched = false; + uint16_t x = 0; + uint16_t y = 0; +}; class DisplayManager { public: + DisplayManager(); + ~DisplayManager(); void begin(); - - void showStatus( - const String& wifi, - const String& mqtt - ); - void loop(); + + void showStatus(const String& wifi, const String& mqtt); + + // Öffentlich aufrufbare Methoden zum Umschalten der Screens + void showMainScreen(); + void showConfigScreen(); + +private: + static constexpr int8_t TFT_BL = 1; + static constexpr int8_t TFT_CS = 45; + static constexpr int8_t TFT_SCK = 47; + static constexpr int8_t TFT_D0 = 21; + static constexpr int8_t TFT_D1 = 48; + static constexpr int8_t TFT_D2 = 40; + static constexpr int8_t TFT_D3 = 39; + static constexpr int8_t TFT_RST = GFX_NOT_DEFINED; + + static constexpr int8_t TOUCH_SDA = 4; + static constexpr int8_t TOUCH_SCL = 8; + static constexpr int8_t TOUCH_INT = 3; + static constexpr int8_t TOUCH_RST = 12; + static constexpr uint8_t TOUCH_I2C_ADDR = 0x3B; + + Arduino_DataBus *bus = nullptr; + Arduino_GFX *output_display = nullptr; + Arduino_Canvas *gfx = nullptr; + + bool _initialized = false; + + // LVGL Screen-Pointer + lv_obj_t *main_screen = nullptr; + lv_obj_t *config_screen = nullptr; + + // Interne Methoden + void initTouch(); + TouchPoint readTouch(); + + void initLVGL(); + void buildMainUI(); + void buildConfigUI(); + + static void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p); + static void my_touch_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data); + + static DisplayManager* instance; }; \ No newline at end of file diff --git a/src/WifiManager.cpp b/src/WifiManager.cpp index 7f34919..1377cf1 100644 --- a/src/WifiManager.cpp +++ b/src/WifiManager.cpp @@ -2,7 +2,7 @@ #include #include "WifiManager.h" -#include "Config.h" +#include "Config.h" // Kann für HOSTNAME oder Fallback-SSID bleiben void WifiManager::begin() { @@ -10,38 +10,50 @@ void WifiManager::begin() WiFi.setHostname(Config::HOSTNAME); - WiFi.begin( - Config::WIFI_SSID, - Config::WIFI_PASSWORD - ); + // Credentials aus NVS lesen + String ssid = getSavedSSID(); + String pass = getSavedPassword(); + + // Fallback: Wenn NVS leer ist, versuche Werte aus Config.h (falls definiert) + if (ssid.length() == 0) { + #ifdef WIFI_SSID + ssid = Config::WIFI_SSID; + pass = Config::WIFI_PASSWORD; + #endif + } + + if (ssid.length() == 0) { + Serial.println("Keine WLAN-Zugangsdaten im NVS gefunden!"); + _connected = false; + return; + } + + Serial.printf("Verbinde mit SSID: %s\n", ssid.c_str()); + WiFi.begin(ssid.c_str(), pass.c_str()); uint8_t counter = 0; - while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); - counter++; - if (counter > 40) + if (counter > 30) // ~15 Sekunden Timeout { Serial.println(); Serial.println("WLAN Timeout"); + _connected = false; return; } } _connected = true; - Serial.println(); Serial.println("WLAN verbunden"); - Serial.print("IP: "); Serial.println(WiFi.localIP()); } - void WifiManager::loop() { bool state = (WiFi.status() == WL_CONNECTED); @@ -49,29 +61,52 @@ void WifiManager::loop() if (state != _connected) { _connected = state; - if (_connected) - { Serial.println("WLAN wieder verbunden"); - } else - { Serial.println("WLAN getrennt"); - } } } - bool WifiManager::connected() { return _connected; } - String WifiManager::ip() { if (_connected) return WiFi.localIP().toString(); return "0.0.0.0"; +} + +// --- NVS Speicher-Methoden --- + +void WifiManager::saveCredentials(const String& ssid, const String& password) +{ + _prefs.begin("wifi-config", false); + _prefs.putString("ssid", ssid); + _prefs.putString("pass", password); + _prefs.end(); + + Serial.println("[WifiManager] Neue Zugangsdaten gespeichert. Starte neu..."); + delay(500); + ESP.restart(); // Reboot um sauber neu zu verbinden +} + +String WifiManager::getSavedSSID() +{ + _prefs.begin("wifi-config", true); + String ssid = _prefs.getString("ssid", ""); + _prefs.end(); + return ssid; +} + +String WifiManager::getSavedPassword() +{ + _prefs.begin("wifi-config", true); + String pass = _prefs.getString("pass", ""); + _prefs.end(); + return pass; } \ No newline at end of file diff --git a/src/WifiManager.h b/src/WifiManager.h index 24d4444..08da148 100644 --- a/src/WifiManager.h +++ b/src/WifiManager.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + class WifiManager { public: @@ -9,6 +12,12 @@ public: bool connected(); String ip(); + // Neue Methoden für NVS-Speicherung + void saveCredentials(const String& ssid, const String& password); + String getSavedSSID(); + String getSavedPassword(); + private: bool _connected = false; + Preferences _prefs; }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 7ff8509..048e20f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,26 @@ #include -#include "Application.h" +#include "WifiManager.h" +#include "Display/DisplayManager.h" -Application app; +WifiManager wifi; +DisplayManager display; -void setup() -{ +void setup() { Serial.begin(115200); delay(1000); - app.begin(); + display.begin(); + wifi.begin(); + + // Prüfen ob WLAN geklappt hat + if (wifi.connected()) { + display.showMainScreen(); + } else { + display.showConfigScreen(); + } } -void loop() -{ - app.loop(); +void loop() { + wifi.loop(); + display.loop(); } \ No newline at end of file