From 28cc8ebaee87830aa9baa0d215d752c6a84de442 Mon Sep 17 00:00:00 2001 From: Electro707 Date: Mon, 26 Jan 2026 01:51:32 -0500 Subject: [PATCH 01/11] Added simulator for web server --- test/webServerTest.py | 143 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 test/webServerTest.py diff --git a/test/webServerTest.py b/test/webServerTest.py new file mode 100644 index 0000000..22d87df --- /dev/null +++ b/test/webServerTest.py @@ -0,0 +1,143 @@ +""" +This spawns a webserver with API callbacks meant to emulate a device + +Can be used to test the webpage separately from the ESP + +Partially ChatGPT generated, with human oversight +""" +from http.server import HTTPServer, SimpleHTTPRequestHandler +from urllib.parse import urlparse, parse_qs +import json +import os +import sys +import threading +import time + +HOST = 'localhost' +PORT = 8080 +BASE_PATH = '../data/web' + +class InfoClass(): + def __init__(self): + self.info = { + 'wifiIP': '1.2.3.4', + 'wifiConnected': True, + 'wifiSSID': 'TestSSID', + 'wifi.networks': [ + {'ssid': 'ABC', 'rssi': 0, 'enc': 7}, + {'ssid': 'Hi There!', 'rssi': -50, 'enc': 5} + ] + } + self.lock = threading.Lock() + + def set(self, key, val): + with self.lock: + self.info[key] = val + + def get(self, key): + with self.lock: + if key not in self.info: + return '' + return self.info[key] + + +class Handler(SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.directory = BASE_PATH + + def translate_path(self, path): + orig = super().translate_path(path) + rel = os.path.relpath(orig, os.getcwd()) + return os.path.join(BASE_PATH, rel) + + def _json(self, code=200, payload=None): + self.send_response(code) + self.send_header("Content-Type", "application/json") + self.end_headers() + if payload is not None: + self.wfile.write(json.dumps(payload).encode()) + + def do_GET(self): + p = urlparse(self.path).path + # print(f"Made GET request: {p}") # todo: debug + + if p == "/api/v1/wifi/status": + return self._json(payload={ + 'connected': info.get('wifiConnected'), + 'ssid': info.get('wifiSSID'), + 'ip': info.get('wifiIP'), + }) + elif p == "/api/v1/wifi/scan": + return self._json(payload=info.get('wifi.networks')) + else: + return super().do_GET() + + def do_POST(self): + p = urlparse(self.path).path + # print(f"Made POST request: {p}") # todo: debug + length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(length) + + if p == "/api/v1/wifi/connect": + data = parse_qs(body.decode(errors="ignore")) + ssid = (data.get("ssid") or ['NONE'])[0] + ip = "4.5.6.7" + print(f"Attempted to connect to SSID {ssid}") + info.set("wifiConnected", True) + info.set("wifiSSID", ssid) + info.set("wifiIP", ip) + time.sleep(info.get('d.connDelay')) + return self._json(payload={"status": 'connected', "ssid": ssid, 'ip': ip}) + elif p == "/api/v1/reboot": + print("Requested to reboot!") + else: + return self._json(404, {"ok": False, "error": "unknown route"}) + +def run_server(httpd: HTTPServer): + httpd.serve_forever(poll_interval=0.2) + + +def run_cli(httpd: HTTPServer): + while True: + try: + line = input("> ").strip() + line = line.split(' ') + except (EOFError, KeyboardInterrupt): + line = "q" + + try: + cmd = line[0] + + if cmd in ['q', 'quit', 'exit']: + print("Exiting") + httpd.shutdown() + httpd.server_close() + return + elif cmd == 'set': + noun = line[1] + if noun == 'ip': + info.set('ip', line[2]) + elif cmd == 'help': + print("...coming soon!") + else: + print("invalid command") + + except IndexError: + print("Invalid command (index error)") + + + +if __name__ == "__main__": + httpd = HTTPServer((HOST, PORT), Handler) + + # a global variables class that is grabbed by Handler and set by the CLI + info = InfoClass() + + info.set('d.connDelay', 1) + + t = threading.Thread(target=run_server, args=(httpd,), daemon=True) + t.start() + print(f"HTTP listening on http://{HOST}:{PORT} (serving {BASE_PATH}/)") + + run_cli(httpd) \ No newline at end of file From ab99a7edd8da85349da175aa13adf6ba749b62a6 Mon Sep 17 00:00:00 2001 From: Electro707 Date: Mon, 26 Jan 2026 02:11:38 -0500 Subject: [PATCH 02/11] added some GIF stuff. other improvments --- test/webServerTest.py | 86 ++++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/test/webServerTest.py b/test/webServerTest.py index 22d87df..6b325e0 100644 --- a/test/webServerTest.py +++ b/test/webServerTest.py @@ -19,15 +19,7 @@ class InfoClass(): def __init__(self): - self.info = { - 'wifiIP': '1.2.3.4', - 'wifiConnected': True, - 'wifiSSID': 'TestSSID', - 'wifi.networks': [ - {'ssid': 'ABC', 'rssi': 0, 'enc': 7}, - {'ssid': 'Hi There!', 'rssi': -50, 'enc': 5} - ] - } + self.info = {} self.lock = threading.Lock() def set(self, key, val): @@ -62,14 +54,21 @@ def do_GET(self): p = urlparse(self.path).path # print(f"Made GET request: {p}") # todo: debug + time.sleep(info.get('d.responseDelay')) + if p == "/api/v1/wifi/status": + time.sleep(info.get('d.getActionDelay')) return self._json(payload={ - 'connected': info.get('wifiConnected'), - 'ssid': info.get('wifiSSID'), - 'ip': info.get('wifiIP'), + 'connected': info.get('wifi.connected'), + 'ssid': info.get('wifi.ssid'), + 'ip': info.get('wifi.ip'), }) elif p == "/api/v1/wifi/scan": + time.sleep(info.get('d.getActionDelay')) return self._json(payload=info.get('wifi.networks')) + elif p == "/api/v1/gif": + time.sleep(info.get('d.getActionDelay')) + return self._json(payload=info.get('gif.list')) else: return super().do_GET() @@ -79,20 +78,41 @@ def do_POST(self): length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(length) + time.sleep(info.get('d.responseDelay')) + if p == "/api/v1/wifi/connect": - data = parse_qs(body.decode(errors="ignore")) - ssid = (data.get("ssid") or ['NONE'])[0] + data = json.loads(body) + print(data) + ssid = (data.get("ssid") or '') + passw = (data.get("password") or '') ip = "4.5.6.7" - print(f"Attempted to connect to SSID {ssid}") - info.set("wifiConnected", True) - info.set("wifiSSID", ssid) - info.set("wifiIP", ip) - time.sleep(info.get('d.connDelay')) + print(f"Attempted to connect to SSID '{ssid}' with password '{passw}'") + info.set("wifi.connected", True) + info.set("wifi.ssid", ssid) + info.set("wifi.ip", ip) + time.sleep(info.get('d.wifiConnDelay')) return self._json(payload={"status": 'connected', "ssid": ssid, 'ip': ip}) elif p == "/api/v1/reboot": print("Requested to reboot!") else: return self._json(404, {"ok": False, "error": "unknown route"}) + + def do_DELETE(self): + p = urlparse(self.path).path + # print(f"Made POST request: {p}") # todo: debug + length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(length) + + time.sleep(info.get('d.responseDelay')) + + if p == "/api/v1/gif": + data = json.loads(body) + print(data) + name = data['name'] + print(f"Requested to delete GIF with name '{name}'") + return self._json(payload={"status": 'success', "message": 'file removed', 'file': name}) + else: + return self._json(404, {"ok": False, "error": "unknown route"}) def run_server(httpd: HTTPServer): httpd.serve_forever(poll_interval=0.2) @@ -116,8 +136,8 @@ def run_cli(httpd: HTTPServer): return elif cmd == 'set': noun = line[1] - if noun == 'ip': - info.set('ip', line[2]) + # todo: convert to appropriate type (int, float, str, etc) + info.set(noun, line[2]) elif cmd == 'help': print("...coming soon!") else: @@ -134,7 +154,29 @@ def run_cli(httpd: HTTPServer): # a global variables class that is grabbed by Handler and set by the CLI info = InfoClass() - info.set('d.connDelay', 1) + info.set('d.wifiConnDelay', 1) # to simulate the wifi connection delay + info.set('d.responseDelay', 0) # to simulate a slower network response + # info.set('d.responseDelay', 0.1) # to simulate a slower network response + info.set('d.getActionDelay', 0.5) # to simulate the esp8266 doing stuff on a task/action + + info.set('wifi.ip', '1.2.3.4') + info.set('wifi.connected', True) + info.set('wifi.ssid', 'TestSSID') + info.set('wifi.networks', [ + {'ssid': 'ABC', 'rssi': 0, 'enc': 7}, + {'ssid': 'Hi There!', 'rssi': -50, 'enc': 5} + ] + ) + info.set('gif.list', { + 'usedBytes': 1500, + 'totalBytes': 50000, + 'freeBytes': 50000-1500, + 'files': [ + {'name': 'test.gif', 'size': 1000}, + {'name': '[BIG SHOT].gif', 'size': 500} + ], + } + ) t = threading.Thread(target=run_server, args=(httpd,), daemon=True) t.start() From 16caf55dd2fa88f77c67ba6f2ea818eb27f0841b Mon Sep 17 00:00:00 2001 From: Electro707 Date: Mon, 26 Jan 2026 02:16:25 -0500 Subject: [PATCH 03/11] Added comments --- test/webServerTest.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/test/webServerTest.py b/test/webServerTest.py index 6b325e0..8effe6c 100644 --- a/test/webServerTest.py +++ b/test/webServerTest.py @@ -1,7 +1,7 @@ """ This spawns a webserver with API callbacks meant to emulate a device - -Can be used to test the webpage separately from the ESP +Can be used to test the webpage separately from the ESP8266 +This file should be ran from this local directory Partially ChatGPT generated, with human oversight """ @@ -13,9 +13,13 @@ import threading import time -HOST = 'localhost' -PORT = 8080 -BASE_PATH = '../data/web' +########## variables to customize +HOST = 'localhost' # the webserver host +PORT = 8080 # the webserver port +BASE_PATH = '../data/web' # the base path for the webserver. setup to point to this repo's web folder + + +########## begin code class InfoClass(): def __init__(self): @@ -94,6 +98,7 @@ def do_POST(self): return self._json(payload={"status": 'connected', "ssid": ssid, 'ip': ip}) elif p == "/api/v1/reboot": print("Requested to reboot!") + return self._json(payload={"status": 'rebooting'}) else: return self._json(404, {"ok": False, "error": "unknown route"}) @@ -155,8 +160,8 @@ def run_cli(httpd: HTTPServer): info = InfoClass() info.set('d.wifiConnDelay', 1) # to simulate the wifi connection delay - info.set('d.responseDelay', 0) # to simulate a slower network response - # info.set('d.responseDelay', 0.1) # to simulate a slower network response + info.set('d.responseDelay', 0) # to simulate a slower network response + # info.set('d.responseDelay', 0.1) # to simulate a slower network response info.set('d.getActionDelay', 0.5) # to simulate the esp8266 doing stuff on a task/action info.set('wifi.ip', '1.2.3.4') From a80ac5caac78378327db2c9029c99aab3fbb5efc Mon Sep 17 00:00:00 2001 From: Electro707 Date: Mon, 26 Jan 2026 03:40:00 -0500 Subject: [PATCH 04/11] Removed LCD classes from heap, now they are statically defined --- include/display/DisplayManager.h | 2 - src/display/DisplayManager.cpp | 151 ++++++++----------------------- src/display/Gif.cpp | 12 --- src/main.cpp | 36 ++++---- src/web/Api.cpp | 44 ++++----- 5 files changed, 74 insertions(+), 171 deletions(-) diff --git a/include/display/DisplayManager.h b/include/display/DisplayManager.h index 41bab7f..6da0af1 100644 --- a/include/display/DisplayManager.h +++ b/include/display/DisplayManager.h @@ -36,8 +36,6 @@ static constexpr int THREE_LINES_SPACE = 60; class DisplayManager { public: static void begin(); - static bool isReady(); - static void ensureInit(); static Arduino_GFX* getGfx(); static void drawStartup(String currentIP); static void drawTextWrapped(int16_t xPos, int16_t yPos, const String& text, uint8_t textSize, uint16_t fgColor, diff --git a/src/display/DisplayManager.cpp b/src/display/DisplayManager.cpp index 0ade253..b0a1392 100644 --- a/src/display/DisplayManager.cpp +++ b/src/display/DisplayManager.cpp @@ -32,13 +32,10 @@ static Gif s_gif; extern ConfigManager configManager; -static Arduino_DataBus* g_lcdBus = nullptr; -static Arduino_GFX* g_lcd = nullptr; -static bool g_lcdReady = false; -static bool g_lcdInitializing = false; -static uint32_t g_lcdInitAttempts = 0; -static uint32_t g_lcdInitLastMs = 0; -static bool g_lcdInitOk = false; +static GeekMagicSPIBus g_lcdBus = + GeekMagicSPIBus(LCD_DC_GPIO, LCD_CS_GPIO, LCD_CS_ACTIVE_HIGH, (int32_t)LCD_SPI_HZ, (int8_t)LCD_SPI_MODE); +static Arduino_ST7789 g_lcd = Arduino_ST7789(&g_lcdBus, -1, 0, true, LCD_W, LCD_H); + static constexpr uint32_t LCD_HARDWARE_RESET_DELAY_MS = 100; static constexpr uint32_t LCD_BEGIN_DELAY_MS = 10; static constexpr int16_t DISPLAY_PADDING = 10; @@ -209,7 +206,7 @@ static constexpr uint8_t ST7789_ADDR_END_LOW = 0xEF; * * @return Pointer to the Arduino_GFX instance */ -auto DisplayManager::getGfx() -> Arduino_GFX* { return g_lcd; } +auto DisplayManager::getGfx() -> Arduino_GFX* { return &g_lcd; } /** * @brief Turn the LCD backlight on @@ -232,30 +229,14 @@ static inline void lcdBacklightOn() { * * @return void */ -static inline void ST7789_WriteCommand(uint8_t cmd) { - if (g_lcdBus == nullptr) { - Logger::error("No data bus for LCD", "DisplayManager"); - - return; - } - - g_lcdBus->writeCommand(cmd); -} +static inline void ST7789_WriteCommand(uint8_t cmd) { g_lcdBus.writeCommand(cmd); } /** * @brief Write a single data byte to the ST7789 via the data bus * * @return void */ -static inline void ST7789_WriteData(uint8_t data) { - if (g_lcdBus == nullptr) { - Logger::error("No data bus for LCD", "DisplayManager"); - - return; - }; - - g_lcdBus->write(data); -} +static inline void ST7789_WriteData(uint8_t data) { g_lcdBus.write(data); } /** * @brief Run a vendor-specific initialization sequence for the ST7789 panel @@ -283,13 +264,7 @@ static inline void ST7789_WriteData(uint8_t data) { * @return void */ static void lcdRunVendorInit() { - if (g_lcdBus == nullptr) { - Logger::error("No data bus for LCD", "DisplayManager"); - - return; - }; - - g_lcdBus->beginWrite(); + g_lcdBus.beginWrite(); ST7789_WriteCommand(ST7789_SLEEP_OUT); delay(ST7789_SLEEP_DELAY_MS); @@ -397,7 +372,7 @@ static void lcdRunVendorInit() { ST7789_WriteCommand(ST7789_RAMWR); yield(); - g_lcdBus->endWrite(); + g_lcdBus.endWrite(); } /** @@ -429,66 +404,39 @@ static void lcdHardReset() { * @return void */ static void lcdEnsureInit() { - if (!configManager.getLCDEnableSafe() || g_lcdReady || g_lcdInitializing) { - return; - }; - - g_lcdInitializing = true; - g_lcdInitAttempts++; - g_lcdInitLastMs = millis(); - g_lcdInitOk = false; - Logger::info("Initialization started", "DisplayManager"); lcdBacklightOn(); lcdHardReset(); - if (g_lcd != nullptr) { - delete static_cast(g_lcd); - g_lcd = nullptr; - } - if (g_lcdBus != nullptr) { - delete static_cast(g_lcdBus); - g_lcdBus = nullptr; - } - SPI.begin(); - int8_t dc_gpio = configManager.getLCDDcGpioSafe(); - int8_t cs_gpio = configManager.getLCDCsGpioSafe(); - bool cs_active_high = configManager.getLCDCsActiveHighSafe(); uint32_t spi_hz = configManager.getLCDSpiHzSafe(); uint8_t spi_mode = configManager.getLCDSpiModeSafe(); uint8_t rotation = configManager.getLCDRotationSafe(); - int16_t lcd_w = configManager.getLCDWidthSafe(); - int16_t lcd_h = configManager.getLCDHeightSafe(); - g_lcdBus = new GeekMagicSPIBus(dc_gpio, cs_gpio, cs_active_high, (int32_t)spi_hz, (int8_t)spi_mode); - g_lcd = new Arduino_ST7789(g_lcdBus, -1, rotation, true, lcd_w, lcd_h); + /* + todo: for some reason just calling g_lcdBus.begin, hardReset, and vendorInit doesn't init the display, but + given all g_lcd.begin() does is initialize the bus and sends some data (which is discarded with the hard reset) + this is the all more confusing + */ - g_lcdBus->begin((int32_t)spi_hz, (int8_t)spi_mode); + g_lcdBus.begin((int32_t)spi_hz, (int8_t)spi_mode); - g_lcd->begin(); + g_lcd.begin(); delay(LCD_BEGIN_DELAY_MS); lcdHardReset(); - g_lcdBus->begin((int32_t)spi_hz, (int8_t)spi_mode); + g_lcdBus.begin((int32_t)spi_hz, (int8_t)spi_mode); lcdRunVendorInit(); - g_lcd->setRotation(rotation); + g_lcd.setRotation(rotation); - g_lcdReady = true; - g_lcdInitializing = false; - g_lcdInitOk = true; + Logger::info(("Width=" + String(g_lcd.width()) + " height=" + String(g_lcd.height())).c_str(), "DisplayManager"); - Logger::info( - ("Pointers g_lcd=" + String((uintptr_t)g_lcd, HEX) + " g_lcdBus=" + String((uintptr_t)g_lcdBus, HEX)).c_str(), - "DisplayManager"); - Logger::info(("Width=" + String(g_lcd->width()) + " height=" + String(g_lcd->height())).c_str(), "DisplayManager"); - - g_lcd->fillScreen(LCD_BLACK); - g_lcd->setTextColor(LCD_WHITE, LCD_BLACK); + g_lcd.fillScreen(LCD_BLACK); + g_lcd.setTextColor(LCD_WHITE, LCD_BLACK); Logger::info("Initialization completed", "DisplayManager"); } @@ -579,8 +527,8 @@ static auto lcdWrapTextToBuffer(const String& text, int maxCharsPerLine, int max */ static void lcdDrawTextWrapped(int16_t startX, int16_t startY, const String& text, uint8_t textSize, uint16_t fgColor, uint16_t bgColor, bool clearBg) { - const auto screenW = static_cast(g_lcd->width()); - const auto screenH = static_cast(g_lcd->height()); + const auto screenW = static_cast(g_lcd.width()); + const auto screenH = static_cast(g_lcd.height()); if (startX < 0) { startX = 0; @@ -621,16 +569,16 @@ static void lcdDrawTextWrapped(int16_t startX, int16_t startY, const String& tex if (clearBg) { const auto heightPixels = static_cast(static_cast(lineCount) * static_cast(charH)); - g_lcd->fillRect(startX, startY, static_cast(screenW - startX), static_cast(heightPixels), - bgColor); + g_lcd.fillRect(startX, startY, static_cast(screenW - startX), static_cast(heightPixels), + bgColor); } - g_lcd->setTextSize(textSize); - g_lcd->setTextColor(fgColor, bgColor); + g_lcd.setTextSize(textSize); + g_lcd.setTextColor(fgColor, bgColor); for (int li = 0; li < lineCount; ++li) { - g_lcd->setCursor(startX, static_cast(startY + li * charH)); - g_lcd->print(lines[li].data()); + g_lcd.setCursor(startX, static_cast(startY + li * charH)); + g_lcd.print(lines[li].data()); } } @@ -643,35 +591,22 @@ static void lcdDrawTextWrapped(int16_t startX, int16_t startY, const String& tex */ auto DisplayManager::begin() -> void { lcdEnsureInit(); } -/** - * @brief Check if the display is ready for drawing - * - * @return true if ready false otherwise - */ -auto DisplayManager::isReady() -> bool { return g_lcdReady && g_lcd != nullptr && g_lcdInitOk; } - /** * @brief Draw the startup screen on the LCD * * @return void */ auto DisplayManager::drawStartup(String currentIP) -> void { - if (!DisplayManager::isReady()) { - Logger::warn("Display not ready", "DisplayManager"); - - return; - } - int constexpr rgbDelayMs = 1000; - g_lcd->fillScreen(LCD_RED); + g_lcd.fillScreen(LCD_RED); delay(rgbDelayMs); - g_lcd->fillScreen(LCD_GREEN); + g_lcd.fillScreen(LCD_GREEN); delay(rgbDelayMs); - g_lcd->fillScreen(LCD_BLUE); + g_lcd.fillScreen(LCD_BLUE); delay(rgbDelayMs); - g_lcd->fillScreen(LCD_BLACK); + g_lcd.fillScreen(LCD_BLACK); int constexpr titleY = 10; int constexpr fontSize = 2; @@ -687,9 +622,9 @@ auto DisplayManager::drawStartup(String currentIP) -> void { const int16_t gap = 20; const int16_t boxY = titleY + (THREE_LINES_SPACE * 2) + ONE_LINE_SPACE; - g_lcd->fillRect(DISPLAY_PADDING, boxY, box, box, LCD_RED); - g_lcd->fillRect((int16_t)(DISPLAY_PADDING + box + gap), boxY, box, box, LCD_GREEN); - g_lcd->fillRect((int16_t)(DISPLAY_PADDING + (box + gap) * 2), boxY, box, box, LCD_BLUE); + g_lcd.fillRect(DISPLAY_PADDING, boxY, box, box, LCD_RED); + g_lcd.fillRect((int16_t)(DISPLAY_PADDING + box + gap), boxY, box, box, LCD_GREEN); + g_lcd.fillRect((int16_t)(DISPLAY_PADDING + (box + gap) * 2), boxY, box, box, LCD_BLUE); yield(); @@ -726,22 +661,18 @@ void DisplayManager::drawTextWrapped(int16_t xPos, int16_t yPos, const String& t */ void DisplayManager::drawLoadingBar(float progress, int yPos, int barWidth, int barHeight, uint16_t fgColor, uint16_t bgColor) { - if ((g_lcd == nullptr) || (!g_lcdReady)) { - return; - } - auto barXPos = (static_cast(configManager.getLCDWidthSafe()) - static_cast(barWidth)) / 2; auto barXPos16 = static_cast(barXPos); auto yPos16 = static_cast(yPos); auto barWidth16 = static_cast(barWidth); auto barHeight16 = static_cast(barHeight); - g_lcd->fillRect(barXPos16, yPos16, barWidth16, barHeight16, bgColor); + g_lcd.fillRect(barXPos16, yPos16, barWidth16, barHeight16, bgColor); auto fillWidthF = static_cast(barWidth) * progress; auto fillWidth16 = static_cast(fillWidthF); if (fillWidth16 > 0) { - g_lcd->fillRect(barXPos16, yPos16, fillWidth16, barHeight16, fgColor); + g_lcd.fillRect(barXPos16, yPos16, fillWidth16, barHeight16, fgColor); } yield(); @@ -817,8 +748,4 @@ auto DisplayManager::update() -> void { s_gif.update(); } * * @return void */ -auto DisplayManager::clearScreen() -> void { - if (g_lcdReady && g_lcd != nullptr) { - g_lcd->fillScreen(LCD_BLACK); - } -} +auto DisplayManager::clearScreen() -> void { g_lcd.fillScreen(LCD_BLACK); } diff --git a/src/display/Gif.cpp b/src/display/Gif.cpp index 7730c63..4d7b819 100644 --- a/src/display/Gif.cpp +++ b/src/display/Gif.cpp @@ -188,10 +188,6 @@ auto Gif::gifSeekFile(GIFFILE* pFile, int32_t iPosition) -> int32_t { */ auto Gif::gifDraw(GIFDRAW* pDraw) -> void // NOLINT(readability-function-cognitive-complexity) { - if (!DisplayManager::isReady()) { - return; - } - auto* gfx = DisplayManager::getGfx(); if (gfx == nullptr) { return; @@ -447,10 +443,6 @@ auto Gif::gifDraw(GIFDRAW* pDraw) -> void // NOLINT(readability-function-cognit * @return true if playback started successfully false otherwise */ auto Gif::playOne(const String& path) -> bool { - if (!DisplayManager::isReady()) { - return false; - } - if (m_gif == nullptr) { if (!begin()) { return false; @@ -578,10 +570,6 @@ auto Gif::playAllFromLittleFS() -> bool { begin(); } - if (!DisplayManager::isReady()) { - return false; - } - if (!LittleFS.begin()) { return false; } diff --git a/src/main.cpp b/src/main.cpp index e0bd663..7fff3cb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -85,13 +85,13 @@ void setup() { Serial.println(""); Logger::info(("GeekMagic Open Firmware " + String(PROJECT_VER_STR)).c_str()); + DisplayManager::begin(); + constexpr int TOTAL_STEPS = 6; int step = 0; if (!LittleFS.begin()) { - if (DisplayManager::isReady()) { - DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); - } + DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); Logger::error("Failed to mount LittleFS"); return; } @@ -111,28 +111,24 @@ void setup() { } step++; - DisplayManager::begin(); - if (DisplayManager::isReady()) { - DisplayManager::drawTextWrapped(LOADING_BAR_TEXT_X, LOADING_BAR_TEXT_Y, "Starting...", 2, LCD_WHITE, LCD_BLACK, - true); - DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); - } + DisplayManager::drawTextWrapped(LOADING_BAR_TEXT_X, LOADING_BAR_TEXT_Y, "Starting...", 2, LCD_WHITE, LCD_BLACK, + true); + DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); step++; wifiManager = new WiFiManager(configManager.getSSID(), configManager.getPassword(), AP_SSID, AP_PASSWORD); wifiManager->begin(); - if (DisplayManager::isReady()) { - DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); - } + + DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); + step++; webserver = new Webserver(); webserver->begin(); // capture initial free heap after core subsystems initialized initial_free_heap = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) - if (DisplayManager::isReady()) { - DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); - } + + DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); step++; registerApiEndpoints(webserver); @@ -151,13 +147,15 @@ void setup() { webserver->registerStaticDir("/web/css", "/css", "text/css"); webserver->registerStaticDir("/web/js", "/js", "application/javascript"); - if (DisplayManager::isReady()) { - DisplayManager::drawLoadingBar(1.0F, LOADING_BAR_Y); - } + DisplayManager::drawLoadingBar(1.0F, LOADING_BAR_Y); delay(LOADING_DELAY_MS); DisplayManager::drawStartup(wifiManager->getIP().toString()); + + // enable watchdog before going to loop() + // 2 seconds should be way more than the main loop needs to do stuff + ESP.wdtEnable(WDTO_2S); } void loop() { @@ -183,4 +181,6 @@ void loop() { snprintf(msgBuf, sizeof(msgBuf), "Free heap: %s (initial: %s)", freeBuf, initBuf); Logger::info(msgBuf); } + + ESP.wdtFeed(); // kick watchdog } diff --git a/src/web/Api.cpp b/src/web/Api.cpp index 840409d..b9c08e9 100644 --- a/src/web/Api.cpp +++ b/src/web/Api.cpp @@ -688,12 +688,10 @@ static void otaHandleStart(HTTPUpload& upload, int mode) { otaCancelRequested = false; otaTotal = static_cast(upload.contentLength); - if (DisplayManager::isReady()) { - DisplayManager::clearScreen(); - DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Uploading...", 2, LCD_WHITE, LCD_BLACK, - true); - DisplayManager::drawLoadingBar(0.0F, OTA_LOADING_Y_OFFSET); - } + DisplayManager::clearScreen(); + DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Uploading...", 2, LCD_WHITE, LCD_BLACK, + true); + DisplayManager::drawLoadingBar(0.0F, OTA_LOADING_Y_OFFSET); int constexpr security_space = 0x1000; u_int constexpr bin_mask = 0xFFFFF000; @@ -729,11 +727,9 @@ static void otaHandleWrite(HTTPUpload& upload) { otaInProgress = false; Logger::warn("OTA canceled by user", "API::OTA"); - if (DisplayManager::isReady()) { - DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Canceled", 2, LCD_WHITE, - LCD_BLACK, true); - DisplayManager::drawLoadingBar(0.0F, OTA_LOADING_Y_OFFSET); - } + DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Canceled", 2, LCD_WHITE, LCD_BLACK, + true); + DisplayManager::drawLoadingBar(0.0F, OTA_LOADING_Y_OFFSET); return; } @@ -746,14 +742,12 @@ static void otaHandleWrite(HTTPUpload& upload) { otaSize += upload.currentSize; - if (DisplayManager::isReady()) { - float progress = 0.0F; - if (otaTotal > 0) { - progress = static_cast(otaSize) / static_cast(otaTotal); - } - - DisplayManager::drawLoadingBar(progress, OTA_LOADING_Y_OFFSET); + float progress = 0.0F; + if (otaTotal > 0) { + progress = static_cast(otaSize) / static_cast(otaTotal); } + + DisplayManager::drawLoadingBar(progress, OTA_LOADING_Y_OFFSET); } } @@ -776,11 +770,9 @@ static void otaHandleEnd(HTTPUpload& /*upload*/, int mode) { otaStatus = String("Update OK (") + String(otaSize) + " bytes)"; Logger::info(otaStatus.c_str(), "API::OTA"); - if (DisplayManager::isReady()) { - DisplayManager::drawLoadingBar(1.0F, OTA_LOADING_Y_OFFSET); - DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Success!", 2, LCD_WHITE, - LCD_BLACK, true); - } + DisplayManager::drawLoadingBar(1.0F, OTA_LOADING_Y_OFFSET); + DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Success!", 2, LCD_WHITE, LCD_BLACK, + true); } else { otaError = true; otaStatus = Update.getErrorString(); @@ -802,8 +794,6 @@ static void otaHandleAborted(HTTPUpload& /*upload*/) { otaInProgress = false; otaCancelRequested = false; - if (DisplayManager::isReady()) { - DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Aborted", 2, LCD_WHITE, LCD_BLACK, true); - DisplayManager::drawLoadingBar(0.0F, OTA_LOADING_Y_OFFSET); - } + DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Aborted", 2, LCD_WHITE, LCD_BLACK, true); + DisplayManager::drawLoadingBar(0.0F, OTA_LOADING_Y_OFFSET); } From 1a4f2316a0a813d8e8884c2b6fb4cca7f732fa2a Mon Sep 17 00:00:00 2001 From: Electro707 Date: Mon, 26 Jan 2026 03:45:02 -0500 Subject: [PATCH 05/11] Removed file that was from another commit...oops --- test/webServerTest.py | 190 ------------------------------------------ 1 file changed, 190 deletions(-) delete mode 100644 test/webServerTest.py diff --git a/test/webServerTest.py b/test/webServerTest.py deleted file mode 100644 index 8effe6c..0000000 --- a/test/webServerTest.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -This spawns a webserver with API callbacks meant to emulate a device -Can be used to test the webpage separately from the ESP8266 -This file should be ran from this local directory - -Partially ChatGPT generated, with human oversight -""" -from http.server import HTTPServer, SimpleHTTPRequestHandler -from urllib.parse import urlparse, parse_qs -import json -import os -import sys -import threading -import time - -########## variables to customize -HOST = 'localhost' # the webserver host -PORT = 8080 # the webserver port -BASE_PATH = '../data/web' # the base path for the webserver. setup to point to this repo's web folder - - -########## begin code - -class InfoClass(): - def __init__(self): - self.info = {} - self.lock = threading.Lock() - - def set(self, key, val): - with self.lock: - self.info[key] = val - - def get(self, key): - with self.lock: - if key not in self.info: - return '' - return self.info[key] - - -class Handler(SimpleHTTPRequestHandler): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.directory = BASE_PATH - - def translate_path(self, path): - orig = super().translate_path(path) - rel = os.path.relpath(orig, os.getcwd()) - return os.path.join(BASE_PATH, rel) - - def _json(self, code=200, payload=None): - self.send_response(code) - self.send_header("Content-Type", "application/json") - self.end_headers() - if payload is not None: - self.wfile.write(json.dumps(payload).encode()) - - def do_GET(self): - p = urlparse(self.path).path - # print(f"Made GET request: {p}") # todo: debug - - time.sleep(info.get('d.responseDelay')) - - if p == "/api/v1/wifi/status": - time.sleep(info.get('d.getActionDelay')) - return self._json(payload={ - 'connected': info.get('wifi.connected'), - 'ssid': info.get('wifi.ssid'), - 'ip': info.get('wifi.ip'), - }) - elif p == "/api/v1/wifi/scan": - time.sleep(info.get('d.getActionDelay')) - return self._json(payload=info.get('wifi.networks')) - elif p == "/api/v1/gif": - time.sleep(info.get('d.getActionDelay')) - return self._json(payload=info.get('gif.list')) - else: - return super().do_GET() - - def do_POST(self): - p = urlparse(self.path).path - # print(f"Made POST request: {p}") # todo: debug - length = int(self.headers.get("Content-Length", 0)) - body = self.rfile.read(length) - - time.sleep(info.get('d.responseDelay')) - - if p == "/api/v1/wifi/connect": - data = json.loads(body) - print(data) - ssid = (data.get("ssid") or '') - passw = (data.get("password") or '') - ip = "4.5.6.7" - print(f"Attempted to connect to SSID '{ssid}' with password '{passw}'") - info.set("wifi.connected", True) - info.set("wifi.ssid", ssid) - info.set("wifi.ip", ip) - time.sleep(info.get('d.wifiConnDelay')) - return self._json(payload={"status": 'connected', "ssid": ssid, 'ip': ip}) - elif p == "/api/v1/reboot": - print("Requested to reboot!") - return self._json(payload={"status": 'rebooting'}) - else: - return self._json(404, {"ok": False, "error": "unknown route"}) - - def do_DELETE(self): - p = urlparse(self.path).path - # print(f"Made POST request: {p}") # todo: debug - length = int(self.headers.get("Content-Length", 0)) - body = self.rfile.read(length) - - time.sleep(info.get('d.responseDelay')) - - if p == "/api/v1/gif": - data = json.loads(body) - print(data) - name = data['name'] - print(f"Requested to delete GIF with name '{name}'") - return self._json(payload={"status": 'success', "message": 'file removed', 'file': name}) - else: - return self._json(404, {"ok": False, "error": "unknown route"}) - -def run_server(httpd: HTTPServer): - httpd.serve_forever(poll_interval=0.2) - - -def run_cli(httpd: HTTPServer): - while True: - try: - line = input("> ").strip() - line = line.split(' ') - except (EOFError, KeyboardInterrupt): - line = "q" - - try: - cmd = line[0] - - if cmd in ['q', 'quit', 'exit']: - print("Exiting") - httpd.shutdown() - httpd.server_close() - return - elif cmd == 'set': - noun = line[1] - # todo: convert to appropriate type (int, float, str, etc) - info.set(noun, line[2]) - elif cmd == 'help': - print("...coming soon!") - else: - print("invalid command") - - except IndexError: - print("Invalid command (index error)") - - - -if __name__ == "__main__": - httpd = HTTPServer((HOST, PORT), Handler) - - # a global variables class that is grabbed by Handler and set by the CLI - info = InfoClass() - - info.set('d.wifiConnDelay', 1) # to simulate the wifi connection delay - info.set('d.responseDelay', 0) # to simulate a slower network response - # info.set('d.responseDelay', 0.1) # to simulate a slower network response - info.set('d.getActionDelay', 0.5) # to simulate the esp8266 doing stuff on a task/action - - info.set('wifi.ip', '1.2.3.4') - info.set('wifi.connected', True) - info.set('wifi.ssid', 'TestSSID') - info.set('wifi.networks', [ - {'ssid': 'ABC', 'rssi': 0, 'enc': 7}, - {'ssid': 'Hi There!', 'rssi': -50, 'enc': 5} - ] - ) - info.set('gif.list', { - 'usedBytes': 1500, - 'totalBytes': 50000, - 'freeBytes': 50000-1500, - 'files': [ - {'name': 'test.gif', 'size': 1000}, - {'name': '[BIG SHOT].gif', 'size': 500} - ], - } - ) - - t = threading.Thread(target=run_server, args=(httpd,), daemon=True) - t.start() - print(f"HTTP listening on http://{HOST}:{PORT} (serving {BASE_PATH}/)") - - run_cli(httpd) \ No newline at end of file From c3c3f022541382f7a3d2ce9dff1e6ecdb629e388 Mon Sep 17 00:00:00 2001 From: Electro707 Date: Mon, 26 Jan 2026 23:53:16 -0500 Subject: [PATCH 06/11] removed uneeded get functions for system-level defines removed yield functions inside of init, not needed --- include/config/ConfigManager.h | 46 ++------------- src/config/ConfigManager.cpp | 101 --------------------------------- src/display/DisplayManager.cpp | 71 +++++++---------------- 3 files changed, 25 insertions(+), 193 deletions(-) diff --git a/include/config/ConfigManager.h b/include/config/ConfigManager.h index ce48975..3a54c8a 100644 --- a/include/config/ConfigManager.h +++ b/include/config/ConfigManager.h @@ -24,6 +24,7 @@ #include "config/SecureStorage.h" #include #include +#include // LCD configuration defaults for hellocubic lite static constexpr bool LCD_ENABLE = true; @@ -32,12 +33,11 @@ static constexpr int16_t LCD_H = 240; static constexpr uint8_t LCD_ROTATION = 4; static constexpr int8_t LCD_MOSI_GPIO = 13; static constexpr int8_t LCD_SCK_GPIO = 14; -static constexpr int8_t LCD_CS_GPIO = 2; +static constexpr int8_t LCD_CS_GPIO = 15; static constexpr int8_t LCD_DC_GPIO = 0; -static constexpr int8_t LCD_RST_GPIO = 15; +static constexpr int8_t LCD_RST_GPIO = 2; static constexpr bool LCD_CS_ACTIVE_HIGH = true; -static constexpr bool LCD_DC_CMD_HIGH = false; -static constexpr uint8_t LCD_SPI_MODE = 0; +static constexpr uint8_t LCD_SPI_MODE = SPI_MODE0; static constexpr uint32_t LCD_SPI_HZ = 40000000; static constexpr int8_t LCD_BACKLIGHT_GPIO = 5; static constexpr bool LCD_BACKLIGHT_ACTIVE_LOW = true; @@ -51,18 +51,8 @@ class ConfigManager { const char* getSSID() const; const char* getPassword() const; bool getLCDEnable() const; - int16_t getLCDWidth() const; - int16_t getLCDHeight() const; uint8_t getLCDRotation() const; - int8_t getLCDMosiGpio() const; - int8_t getLCDSckGpio() const; - int8_t getLCDCsGpio() const; - int8_t getLCDDcGpio() const; - int8_t getLCDRstGpio() const; - bool getLCDCsActiveHigh() const; bool getLCDDcCmdHigh() const; - uint8_t getLCDSpiMode() const; - bool getLCDKeepCsAsserted() const; uint32_t getLCDSpiHz() const; int8_t getLCDBacklightGpio() const; bool getLCDBacklightActiveLow() const; @@ -70,43 +60,15 @@ class ConfigManager { public: bool getLCDEnableSafe() const { return lcd_enable; } - int16_t getLCDWidthSafe() const { return (lcd_w > 0) ? lcd_w : LCD_W; } - int16_t getLCDHeightSafe() const { return (lcd_h > 0) ? lcd_h : LCD_H; } uint8_t getLCDRotationSafe() const { return lcd_rotation; } - int8_t getLCDMosiGpioSafe() const { return (lcd_mosi_gpio >= 0) ? lcd_mosi_gpio : LCD_MOSI_GPIO; } - int8_t getLCDSckGpioSafe() const { return (lcd_sck_gpio >= 0) ? lcd_sck_gpio : LCD_SCK_GPIO; } - int8_t getLCDCsGpioSafe() const { return (lcd_cs_gpio >= 0) ? lcd_cs_gpio : LCD_CS_GPIO; } - int8_t getLCDDcGpioSafe() const { return (lcd_dc_gpio >= 0) ? lcd_dc_gpio : LCD_DC_GPIO; } - int8_t getLCDRstGpioSafe() const { return (lcd_rst_gpio >= 0) ? lcd_rst_gpio : LCD_RST_GPIO; } - bool getLCDCsActiveHighSafe() const { return lcd_cs_active_high; } - bool getLCDDcCmdHighSafe() const { return lcd_dc_cmd_high; } - uint8_t getLCDSpiModeSafe() const { return lcd_spi_mode; } - bool getLCDKeepCsAssertedSafe() const { return lcd_keep_cs_asserted; } uint32_t getLCDSpiHzSafe() const { return (lcd_spi_hz > 0) ? lcd_spi_hz : LCD_SPI_HZ; } - int8_t getLCDBacklightGpioSafe() const { - return (lcd_backlight_gpio >= 0) ? lcd_backlight_gpio : LCD_BACKLIGHT_GPIO; - } - bool getLCDBacklightActiveLowSafe() const { return lcd_backlight_active_low; } std::string ssid; std::string password; std::string filename; SecureStorage secure; bool lcd_enable = true; - int16_t lcd_w = 240; - int16_t lcd_h = 240; uint8_t lcd_rotation = 4; - int8_t lcd_mosi_gpio = 13; - int8_t lcd_sck_gpio = 14; - int8_t lcd_cs_gpio = 2; - int8_t lcd_dc_gpio = 0; - int8_t lcd_rst_gpio = 15; - bool lcd_cs_active_high = true; - bool lcd_dc_cmd_high = false; - uint8_t lcd_spi_mode = 0; - bool lcd_keep_cs_asserted = true; uint32_t lcd_spi_hz = 40000000; - int8_t lcd_backlight_gpio = 5; - bool lcd_backlight_active_low = true; }; #endif // CONFIG_MANAGER_H diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 9b42dbf..5fa3ac2 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -66,21 +66,8 @@ auto ConfigManager::load() -> bool { String password = doc["wifi_password"] | ""; this->lcd_enable = doc["lcd_enable"] | lcd_enable; - this->lcd_w = doc["lcd_w"] | lcd_w; - this->lcd_h = doc["lcd_h"] | lcd_h; this->lcd_rotation = doc["lcd_rotation"] | lcd_rotation; - this->lcd_mosi_gpio = doc["lcd_mosi_gpio"] | lcd_mosi_gpio; - this->lcd_sck_gpio = doc["lcd_sck_gpio"] | lcd_sck_gpio; - this->lcd_cs_gpio = doc["lcd_cs_gpio"] | lcd_cs_gpio; - this->lcd_dc_gpio = doc["lcd_dc_gpio"] | lcd_dc_gpio; - this->lcd_rst_gpio = doc["lcd_rst_gpio"] | lcd_rst_gpio; - this->lcd_cs_active_high = doc["lcd_cs_active_high"] | lcd_cs_active_high; - this->lcd_dc_cmd_high = doc["lcd_dc_cmd_high"] | lcd_dc_cmd_high; - this->lcd_spi_mode = doc["lcd_spi_mode"] | lcd_spi_mode; - this->lcd_keep_cs_asserted = doc["lcd_keep_cs_asserted"] | lcd_keep_cs_asserted; this->lcd_spi_hz = doc["lcd_spi_hz"] | lcd_spi_hz; - this->lcd_backlight_gpio = doc["lcd_backlight_gpio"] | lcd_backlight_gpio; - this->lcd_backlight_active_low = doc["lcd_backlight_active_low"] | lcd_backlight_active_low; String nvs_ssid = secure.get("wifi_ssid", ""); String nvs_password = secure.get("wifi_password", ""); @@ -125,18 +112,6 @@ auto ConfigManager::getPassword() const -> const char* { return password.c_str() */ auto ConfigManager::getLCDEnable() const -> bool { return lcd_enable; } -/** - * @brief Retrieves the LCD width - * - * @return The width of the LCD in pixels - */ -auto ConfigManager::getLCDWidth() const -> int16_t { return lcd_w; } -/** - * @brief Retrieves the LCD height - * - * @return The height of the LCD in pixels - */ -auto ConfigManager::getLCDHeight() const -> int16_t { return lcd_h; } /** * @brief Retrieves the LCD rotation setting * @@ -144,68 +119,6 @@ auto ConfigManager::getLCDHeight() const -> int16_t { return lcd_h; } */ auto ConfigManager::getLCDRotation() const -> uint8_t { return lcd_rotation; } -/** - * @brief Retrieves the GPIO pin number for LCD MOSI - * - * @return The GPIO pin number for LCD MOSI - */ -auto ConfigManager::getLCDMosiGpio() const -> int8_t { return lcd_mosi_gpio; } - -/** - * @brief Retrieves the GPIO pin number for LCD SCK - * - * @return The GPIO pin number for LCD SCK - */ -auto ConfigManager::getLCDSckGpio() const -> int8_t { return lcd_sck_gpio; } - -/** - * @brief Retrieves the GPIO pin number for LCD CS - * - * @return The GPIO pin number for LCD CS - */ -auto ConfigManager::getLCDCsGpio() const -> int8_t { return lcd_cs_gpio; } - -/** - * @brief Retrieves the GPIO pin number for LCD DC - * - * @return The GPIO pin number for LCD DC - */ -auto ConfigManager::getLCDDcGpio() const -> int8_t { return lcd_dc_gpio; } - -/** - * @brief Retrieves the GPIO pin number for LCD RST - * - * @return The GPIO pin number for LCD RST - */ -auto ConfigManager::getLCDRstGpio() const -> int8_t { return lcd_rst_gpio; } - -/** - * @brief Returns whether the LCD CS pin is active high - * - * @return true if the LCD CS pin is active high false otherwise - */ -auto ConfigManager::getLCDCsActiveHigh() const -> bool { return lcd_cs_active_high; } -/** - * @brief Returns whether the LCD DC pin is command high - * - * @return true if the LCD DC pin is command high false otherwise - */ -auto ConfigManager::getLCDDcCmdHigh() const -> bool { return lcd_dc_cmd_high; } - -/** - * @brief Retrieves the LCD SPI mode - * - * @return The SPI mode of the LCD - */ -auto ConfigManager::getLCDSpiMode() const -> uint8_t { return lcd_spi_mode; } - -/** - * @brief Returns whether the LCD CS pin is kept asserted - * - * @return true if the LCD CS pin is kept asserted false otherwise - */ -auto ConfigManager::getLCDKeepCsAsserted() const -> bool { return lcd_keep_cs_asserted; } - /** * @brief Retrieves the SPI clock frequency for the LCD * @@ -213,20 +126,6 @@ auto ConfigManager::getLCDKeepCsAsserted() const -> bool { return lcd_keep_cs_as */ auto ConfigManager::getLCDSpiHz() const -> uint32_t { return lcd_spi_hz; } -/** - * @brief Retrieves the GPIO pin number for the LCD backlight - * - * @return The GPIO pin number for the LCD backlight - */ -auto ConfigManager::getLCDBacklightGpio() const -> int8_t { return lcd_backlight_gpio; } - -/** - * @brief Returns whether the LCD backlight pin is active low - * - * @return true if the LCD backlight pin is active low false otherwise - */ -auto ConfigManager::getLCDBacklightActiveLow() const -> bool { return lcd_backlight_active_low; } - /** * @brief Set WiFi credentials in memory * @param newSsid The SSID diff --git a/src/display/DisplayManager.cpp b/src/display/DisplayManager.cpp index b0a1392..c2e291a 100644 --- a/src/display/DisplayManager.cpp +++ b/src/display/DisplayManager.cpp @@ -36,7 +36,7 @@ static GeekMagicSPIBus g_lcdBus = GeekMagicSPIBus(LCD_DC_GPIO, LCD_CS_GPIO, LCD_CS_ACTIVE_HIGH, (int32_t)LCD_SPI_HZ, (int8_t)LCD_SPI_MODE); static Arduino_ST7789 g_lcd = Arduino_ST7789(&g_lcdBus, -1, 0, true, LCD_W, LCD_H); -static constexpr uint32_t LCD_HARDWARE_RESET_DELAY_MS = 100; +static constexpr uint32_t LCD_HARDWARE_RESET_DELAY_MS = 120; static constexpr uint32_t LCD_BEGIN_DELAY_MS = 10; static constexpr int16_t DISPLAY_PADDING = 10; static constexpr int16_t DISPLAY_INFO_Y = 100; @@ -144,6 +144,7 @@ static constexpr uint8_t ST7789_SLEEP_DELAY_MS = 120; static constexpr uint8_t ST7789_SLEEP_OUT = 0x11; static constexpr uint8_t ST7789_PORCH = 0xB2; static constexpr uint8_t ST7789_PORCH_SETTINGS = 0x1F; +static constexpr uint8_t ST7789_SW_RESET = 0x01; static constexpr uint8_t ST7789_TEARING_EFFECT = 0x35; static constexpr uint8_t ST7789_MEMORY_ACCESS_CONTROL = 0x36; @@ -166,6 +167,7 @@ static constexpr uint8_t ST7789_GAMMA_CTRL = 0xE4; static constexpr uint8_t ST7789_INVERSION_ON = 0x21; static constexpr uint8_t ST7789_DISPLAY_ON = 0x29; +static constexpr uint8_t ST7789_NORMAL_DISPLAY_MODE = 0x13; // Porch parameters used in sequence static constexpr uint8_t ST7789_PORCH_PARAM_HS = 0x1F; @@ -214,14 +216,8 @@ auto DisplayManager::getGfx() -> Arduino_GFX* { return &g_lcd; } * @return void */ static inline void lcdBacklightOn() { - int8_t gpio = configManager.getLCDBacklightGpioSafe(); - if (gpio < 0) { - Logger::warn("No backlight GPIO defined", "DisplayManager"); - return; - } - - pinMode((uint8_t)gpio, OUTPUT); - digitalWrite((uint8_t)gpio, configManager.getLCDBacklightActiveLowSafe() ? LOW : HIGH); + pinMode((uint8_t)LCD_BACKLIGHT_GPIO, OUTPUT); + digitalWrite((uint8_t)LCD_BACKLIGHT_GPIO, LCD_BACKLIGHT_ACTIVE_LOW ? LOW : HIGH); } /** @@ -268,7 +264,6 @@ static void lcdRunVendorInit() { ST7789_WriteCommand(ST7789_SLEEP_OUT); delay(ST7789_SLEEP_DELAY_MS); - yield(); ST7789_WriteCommand(ST7789_PORCH); ST7789_WriteData(ST7789_PORCH_PARAM_HS); @@ -276,101 +271,82 @@ static void lcdRunVendorInit() { ST7789_WriteData(ST7789_PORCH_PARAM_DUMMY); ST7789_WriteData(ST7789_PORCH_PARAM_HBP); ST7789_WriteData(ST7789_PORCH_PARAM_VBP); - yield(); ST7789_WriteCommand(ST7789_TEARING_EFFECT); ST7789_WriteData(ST7789_TEARING_PARAM_OFF); - yield(); ST7789_WriteCommand(ST7789_MEMORY_ACCESS_CONTROL); ST7789_WriteData(ST7789_MADCTL_PARAM_DEFAULT); - yield(); ST7789_WriteCommand(ST7789_COLORMODE); ST7789_WriteData(ST7789_COLORMODE_RGB565); - yield(); ST7789_WriteCommand(ST7789_POWER_B7); ST7789_WriteData(ST7789_B7_PARAM_DEFAULT); - yield(); ST7789_WriteCommand(ST7789_POWER_BB); ST7789_WriteData(ST7789_BB_PARAM_VOLTAGE); - yield(); ST7789_WriteCommand(ST7789_POWER_C0); ST7789_WriteData(ST7789_C0_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_POWER_C2); ST7789_WriteData(ST7789_C2_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_POWER_C3); ST7789_WriteData(ST7789_C3_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_POWER_C4); ST7789_WriteData(ST7789_C4_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_POWER_C6); ST7789_WriteData(ST7789_C6_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_POWER_D6); ST7789_WriteData(ST7789_D6_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_POWER_D0); ST7789_WriteData(ST7789_D0_PARAM_1); ST7789_WriteData(ST7789_D0_PARAM_2); - yield(); ST7789_WriteCommand(ST7789_POWER_D6); ST7789_WriteData(ST7789_D6_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_GAMMA_POS); for (uint8_t v : ST7789_GAMMA_POS_DATA) { ST7789_WriteData(v); } - yield(); ST7789_WriteCommand(ST7789_GAMMA_NEG); for (uint8_t v : ST7789_GAMMA_NEG_DATA) { ST7789_WriteData(v); } - yield(); ST7789_WriteCommand(ST7789_GAMMA_CTRL); for (uint8_t v : ST7789_GAMMA_CTRL_DATA) { ST7789_WriteData(v); } - yield(); ST7789_WriteCommand(ST7789_INVERSION_ON); - yield(); + + ST7789_WriteCommand(ST7789_NORMAL_DISPLAY_MODE); + delay(10); ST7789_WriteCommand(ST7789_DISPLAY_ON); - yield(); ST7789_WriteCommand(ST7789_CASET); ST7789_WriteData(ST7789_ADDR_START_HIGH); ST7789_WriteData(ST7789_ADDR_START_LOW); ST7789_WriteData(ST7789_ADDR_END_HIGH); ST7789_WriteData(ST7789_ADDR_END_LOW); - yield(); ST7789_WriteCommand(ST7789_RASET); ST7789_WriteData(ST7789_ADDR_START_HIGH); ST7789_WriteData(ST7789_ADDR_START_LOW); ST7789_WriteData(ST7789_ADDR_END_HIGH); ST7789_WriteData(ST7789_ADDR_END_LOW); - yield(); ST7789_WriteCommand(ST7789_RAMWR); - yield(); g_lcdBus.endWrite(); } @@ -383,18 +359,12 @@ static void lcdRunVendorInit() { * @return void */ static void lcdHardReset() { - int8_t rst_gpio = configManager.getLCDRstGpioSafe(); - if (rst_gpio < 0) { - Logger::warn("No reset GPIO defined", "DisplayManager"); - return; - } - - pinMode((uint8_t)rst_gpio, OUTPUT); - digitalWrite((uint8_t)rst_gpio, HIGH); + pinMode((uint8_t)LCD_RST_GPIO, OUTPUT); + digitalWrite((uint8_t)LCD_RST_GPIO, HIGH); delay(LCD_HARDWARE_RESET_DELAY_MS); - digitalWrite((uint8_t)rst_gpio, LOW); + digitalWrite((uint8_t)LCD_RST_GPIO, LOW); delay(LCD_HARDWARE_RESET_DELAY_MS); - digitalWrite((uint8_t)rst_gpio, HIGH); + digitalWrite((uint8_t)LCD_RST_GPIO, HIGH); delay(LCD_HARDWARE_RESET_DELAY_MS); } @@ -407,29 +377,30 @@ static void lcdEnsureInit() { Logger::info("Initialization started", "DisplayManager"); lcdBacklightOn(); - lcdHardReset(); SPI.begin(); uint32_t spi_hz = configManager.getLCDSpiHzSafe(); - uint8_t spi_mode = configManager.getLCDSpiModeSafe(); uint8_t rotation = configManager.getLCDRotationSafe(); /* todo: for some reason just calling g_lcdBus.begin, hardReset, and vendorInit doesn't init the display, but given all g_lcd.begin() does is initialize the bus and sends some data (which is discarded with the hard reset) this is the all more confusing - */ - g_lcdBus.begin((int32_t)spi_hz, (int8_t)spi_mode); + todo2: so with RST and D/C this still needs both g_lcd.begin() and the custom init. But the g_lcd.begin() overrides + the SPI mode to mode 2, while we internally use mode 0. Strange stuff, one of the two functions is probably sending + bogus data + */ + lcdHardReset(); g_lcd.begin(); delay(LCD_BEGIN_DELAY_MS); - lcdHardReset(); - g_lcdBus.begin((int32_t)spi_hz, (int8_t)spi_mode); - + g_lcdBus.begin((int32_t)spi_hz, (int8_t)SPI_MODE0); + // lcdHardReset(); // no longer needed after RST/DC pin swap lcdRunVendorInit(); + delay(LCD_BEGIN_DELAY_MS); g_lcd.setRotation(rotation); @@ -661,7 +632,7 @@ void DisplayManager::drawTextWrapped(int16_t xPos, int16_t yPos, const String& t */ void DisplayManager::drawLoadingBar(float progress, int yPos, int barWidth, int barHeight, uint16_t fgColor, uint16_t bgColor) { - auto barXPos = (static_cast(configManager.getLCDWidthSafe()) - static_cast(barWidth)) / 2; + auto barXPos = (static_cast(LCD_W) - static_cast(barWidth)) / 2; auto barXPos16 = static_cast(barXPos); auto yPos16 = static_cast(yPos); auto barWidth16 = static_cast(barWidth); From 77e012435809dcb80fb4094e20295b3e0feee061 Mon Sep 17 00:00:00 2001 From: Electro707 Date: Mon, 26 Jan 2026 23:55:06 -0500 Subject: [PATCH 07/11] removed unused config parameter --- include/config/ConfigManager.h | 3 --- src/config/ConfigManager.cpp | 8 -------- 2 files changed, 11 deletions(-) diff --git a/include/config/ConfigManager.h b/include/config/ConfigManager.h index 3a54c8a..dd1cfda 100644 --- a/include/config/ConfigManager.h +++ b/include/config/ConfigManager.h @@ -27,7 +27,6 @@ #include // LCD configuration defaults for hellocubic lite -static constexpr bool LCD_ENABLE = true; static constexpr int16_t LCD_W = 240; static constexpr int16_t LCD_H = 240; static constexpr uint8_t LCD_ROTATION = 4; @@ -59,14 +58,12 @@ class ConfigManager { bool migrateWiFiToSecureStorage(String ssid, String password); public: - bool getLCDEnableSafe() const { return lcd_enable; } uint8_t getLCDRotationSafe() const { return lcd_rotation; } uint32_t getLCDSpiHzSafe() const { return (lcd_spi_hz > 0) ? lcd_spi_hz : LCD_SPI_HZ; } std::string ssid; std::string password; std::string filename; SecureStorage secure; - bool lcd_enable = true; uint8_t lcd_rotation = 4; uint32_t lcd_spi_hz = 40000000; }; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 5fa3ac2..1ee516e 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -65,7 +65,6 @@ auto ConfigManager::load() -> bool { String ssid = doc["wifi_ssid"] | ""; String password = doc["wifi_password"] | ""; - this->lcd_enable = doc["lcd_enable"] | lcd_enable; this->lcd_rotation = doc["lcd_rotation"] | lcd_rotation; this->lcd_spi_hz = doc["lcd_spi_hz"] | lcd_spi_hz; @@ -105,13 +104,6 @@ auto ConfigManager::getSSID() const -> const char* { return ssid.c_str(); } */ auto ConfigManager::getPassword() const -> const char* { return password.c_str(); } -/** - * @brief Returns the current status of the LCD enable flag - * - * @return true if the LCD is enabled false otherwise - */ -auto ConfigManager::getLCDEnable() const -> bool { return lcd_enable; } - /** * @brief Retrieves the LCD rotation setting * From 9e7f2ee1f7abb7e480c04e8b99485e66cace15e4 Mon Sep 17 00:00:00 2001 From: Electro707 Date: Mon, 26 Jan 2026 23:55:25 -0500 Subject: [PATCH 08/11] Removed not defined functions from removal --- include/config/ConfigManager.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/config/ConfigManager.h b/include/config/ConfigManager.h index dd1cfda..38352fd 100644 --- a/include/config/ConfigManager.h +++ b/include/config/ConfigManager.h @@ -49,12 +49,8 @@ class ConfigManager { void setWiFi(const char* newSsid, const char* newPassword); const char* getSSID() const; const char* getPassword() const; - bool getLCDEnable() const; uint8_t getLCDRotation() const; - bool getLCDDcCmdHigh() const; uint32_t getLCDSpiHz() const; - int8_t getLCDBacklightGpio() const; - bool getLCDBacklightActiveLow() const; bool migrateWiFiToSecureStorage(String ssid, String password); public: From 3bbe83a60b3879ae7a14c67482ab47ff4c985ef2 Mon Sep 17 00:00:00 2001 From: Electro707 Date: Sat, 31 Jan 2026 21:36:33 -0500 Subject: [PATCH 09/11] Turns out CS is just tied low in HW, so no need for any special bus handler. Removed tft library's init as we are doing our own init anyways --- include/config/ConfigManager.h | 6 +-- include/display/GeekMagicSPIBus.h | 65 ---------------------- readme.md | 29 +++++----- src/config/ConfigManager.cpp | 8 --- src/display/DisplayManager.cpp | 27 +++------- src/display/GeekMagicSPIBus.cpp | 89 ------------------------------- 6 files changed, 19 insertions(+), 205 deletions(-) delete mode 100644 include/display/GeekMagicSPIBus.h delete mode 100644 src/display/GeekMagicSPIBus.cpp diff --git a/include/config/ConfigManager.h b/include/config/ConfigManager.h index 38352fd..035d63f 100644 --- a/include/config/ConfigManager.h +++ b/include/config/ConfigManager.h @@ -32,11 +32,9 @@ static constexpr int16_t LCD_H = 240; static constexpr uint8_t LCD_ROTATION = 4; static constexpr int8_t LCD_MOSI_GPIO = 13; static constexpr int8_t LCD_SCK_GPIO = 14; -static constexpr int8_t LCD_CS_GPIO = 15; static constexpr int8_t LCD_DC_GPIO = 0; static constexpr int8_t LCD_RST_GPIO = 2; -static constexpr bool LCD_CS_ACTIVE_HIGH = true; -static constexpr uint8_t LCD_SPI_MODE = SPI_MODE0; +static constexpr uint8_t LCD_SPI_MODE = SPI_MODE3; static constexpr uint32_t LCD_SPI_HZ = 40000000; static constexpr int8_t LCD_BACKLIGHT_GPIO = 5; static constexpr bool LCD_BACKLIGHT_ACTIVE_LOW = true; @@ -55,13 +53,11 @@ class ConfigManager { public: uint8_t getLCDRotationSafe() const { return lcd_rotation; } - uint32_t getLCDSpiHzSafe() const { return (lcd_spi_hz > 0) ? lcd_spi_hz : LCD_SPI_HZ; } std::string ssid; std::string password; std::string filename; SecureStorage secure; uint8_t lcd_rotation = 4; - uint32_t lcd_spi_hz = 40000000; }; #endif // CONFIG_MANAGER_H diff --git a/include/display/GeekMagicSPIBus.h b/include/display/GeekMagicSPIBus.h deleted file mode 100644 index a851c18..0000000 --- a/include/display/GeekMagicSPIBus.h +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -/* - * GeekMagic Open Firmware - * Copyright (C) 2026 Times-Z - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef HELLO_CUBIC_SPI_BUS_H -#define HELLO_CUBIC_SPI_BUS_H - -#include -#include - -/** - * @brief Determines whether the Chip Select (CS) line should remain asserted (active low) between SPI transactions - */ -static constexpr bool LCD_KEEP_CS_ASSERTED = true; - -/** - * @class GeekMagicSPIBus - * @brief Custom SPI bus class for GeekMagicSPIBus display - * - * This class extends Arduino_DataBus to provide SPI communication with custom Chip Select (CS) handling for the - * GeekMagicSPIBus display - */ -class GeekMagicSPIBus : public Arduino_DataBus { - public: - GeekMagicSPIBus(int8_t dataCmdPin, int8_t csPin, bool csActiveHigh, int32_t defaultSpeed, int8_t defaultDataMode); - - bool begin(int32_t speed = GFX_NOT_DEFINED, int8_t dataMode = GFX_NOT_DEFINED) override; - void beginWrite() override; - void endWrite() override; - - virtual ~GeekMagicSPIBus() {} - - void writeCommand(uint8_t c) override { _spi.writeCommand(c); } - void writeCommand16(uint16_t c) override { _spi.writeCommand16(c); } - void writeCommandBytes(uint8_t* data, uint32_t len) override { _spi.writeCommandBytes(data, len); } - void write(uint8_t d) override { _spi.write(d); } - void write16(uint16_t d) override { _spi.write16(d); } - void writeRepeat(uint16_t p, uint32_t len) override { _spi.writeRepeat(p, len); } - void writeBytes(uint8_t* data, uint32_t len) override { _spi.writeBytes(data, len); } - void writePixels(uint16_t* data, uint32_t len) override { _spi.writePixels(data, len); } - - private: - Arduino_HWSPI _spi; - int8_t _cs; - bool _csActiveHigh; - int32_t _defaultSpeed; - int8_t _defaultDataMode; -}; - -#endif diff --git a/readme.md b/readme.md index 75ad80c..5bbcb0c 100644 --- a/readme.md +++ b/readme.md @@ -70,7 +70,7 @@ - **Resolution**: 240x240 pixels - **Color Format**: RGB565 (16-bit color) - **Interface**: SPI (Serial Peripheral Interface) -- **SPI Speed**: up to 80 MHz (40 MHz is more stable) +- **SPI Speed**: up to 40 MHz (80 MHz is possible, but unstable and outside datasheet spec) - **Rotation**: Upside-down for cube display, normal for the small tv ### Pin wiring @@ -83,9 +83,9 @@ The display is connected to the ESP8266 using the following GPIO pins: | ------------- | -------- | ----------------------------------------------------- | | **MOSI** | GPIO 13 | SPI Master Out Slave In (data from ESP8266 to screen) | | **SCK** | GPIO 14 | SPI Clock | -| **CS** | GPIO 2 | Chip Select (Active HIGH) | +| **CS** | GND | Chip Select, tied permanently to GND | | **DC** | GPIO 0 | Data/Command select (LOW=command, HIGH=data) | -| **RST** | GPIO 15 | Reset pin | +| **RST** | GPIO 2 | Reset pin | | **Backlight** | GPIO 5 | Backlight control (Active LOW) |
@@ -96,9 +96,9 @@ The display is connected to the ESP8266 using the following GPIO pins: ### Important configuration details -**Chip select (CS) polarity**: This board uses **active-high** CS, which is non-standard. Most SPI displays use active-low CS. The CS pin must be driven HIGH to select the display +**Chip select (CS) polarity**: This board ties CS of the display permanently to GND. -**SPI mode**: SPI Mode 0 (CPOL=0, CPHA=0) +**SPI mode**: SPI Mode 3 (CPOL=1, CPHA=1) **Data/command pin**: LOW for commands, HIGH for data @@ -109,8 +109,8 @@ The display is connected to the ESP8266 using the following GPIO pins: ### Initialization sequence 1. **Backlight control**: GPIO 5 is set as output and driven LOW to turn on the backlight -2. **Hardware reset**: The RST pin (GPIO 15) is toggled to reset the ST7789 controller -3. **SPI bus setup**: Hardware SPI is initialized with 80 MHz clock speed and Mode 0 +2. **Hardware reset**: The RST pin (GPIO 2) is toggled to reset the ST7789 controller +3. **SPI bus setup**: Hardware SPI is initialized with 40 MHz clock speed and Mode 3 4. **Display controller init**: The ST7789 is configured using a vendor-specific initialization sequence that includes: - Sleep out (0x11) - Porch settings (0xB2) @@ -128,19 +128,15 @@ The display is connected to the ESP8266 using the following GPIO pins: The display uses **SPI** protocol for communication: -1. **Chip select**: CS is driven HIGH to select the display -2. **Command/data mode**: The DC pin indicates whether the data on MOSI is a command (DC=LOW) or pixel data (DC=HIGH) -3. **Data transfer**: Data is shifted out on the MOSI pin, synchronized with the SCK clock signal -4. **Chip deselect**: CS can be kept HIGH during continuous operations for better performance, or dropped LOW between operations +1. **Command/data mode**: The DC pin indicates whether the data on MOSI is a command (DC=LOW) or pixel data (DC=HIGH) +2. **Data transfer**: Data is shifted out on the MOSI pin, synchronized with the SCK clock signal ### Drawing to the screen The firmware uses the Arduino_GFX library with a custom ESP8266SPIWithCustomCS bus driver that handles the active-high CS polarity. Graphics operations: -1. **Begin write**: Assert CS (set HIGH) -2. **Write commands**: Set DC LOW, send command bytes via SPI -3. **Write data**: Set DC HIGH, send pixel data via SPI -4. **End write**: Optionally deassert CS (set LOW) - can be kept asserted for continuous operations +1. **Write commands**: Set DC LOW, send command bytes via SPI +2. **Write data**: Set DC HIGH, send pixel data via SPI ### Color format @@ -160,8 +156,7 @@ Example colors: ### Performance optimizations -- **High SPI speed**: 80 MHz clock for fast data transfer -- **CS kept asserted**: During continuous operations, CS stays HIGH to reduce overhead +- **High SPI speed**: 40 MHz clock for fast data transfer - **Hardware SPI**: Uses ESP8266's hardware SPI peripheral for efficient transfers - **Batch writes**: Multiple operations are batched between beginWrite/endWrite calls - **Direct frame buffer writes**: GIF frames are streamed directly to avoid intermediate buffering diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 1ee516e..04210a0 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -66,7 +66,6 @@ auto ConfigManager::load() -> bool { String password = doc["wifi_password"] | ""; this->lcd_rotation = doc["lcd_rotation"] | lcd_rotation; - this->lcd_spi_hz = doc["lcd_spi_hz"] | lcd_spi_hz; String nvs_ssid = secure.get("wifi_ssid", ""); String nvs_password = secure.get("wifi_password", ""); @@ -111,13 +110,6 @@ auto ConfigManager::getPassword() const -> const char* { return password.c_str() */ auto ConfigManager::getLCDRotation() const -> uint8_t { return lcd_rotation; } -/** - * @brief Retrieves the SPI clock frequency for the LCD - * - * @return The SPI clock frequency in Hz - */ -auto ConfigManager::getLCDSpiHz() const -> uint32_t { return lcd_spi_hz; } - /** * @brief Set WiFi credentials in memory * @param newSsid The SSID diff --git a/src/display/DisplayManager.cpp b/src/display/DisplayManager.cpp index c2e291a..13cba2a 100644 --- a/src/display/DisplayManager.cpp +++ b/src/display/DisplayManager.cpp @@ -21,10 +21,10 @@ #include #include #include +#include #include "project_version.h" #include "display/DisplayManager.h" -#include "display/GeekMagicSPIBus.h" #include "config/ConfigManager.h" #include "display/Gif.h" @@ -32,8 +32,7 @@ static Gif s_gif; extern ConfigManager configManager; -static GeekMagicSPIBus g_lcdBus = - GeekMagicSPIBus(LCD_DC_GPIO, LCD_CS_GPIO, LCD_CS_ACTIVE_HIGH, (int32_t)LCD_SPI_HZ, (int8_t)LCD_SPI_MODE); +static Arduino_HWSPI g_lcdBus = Arduino_HWSPI(LCD_DC_GPIO, -1, &SPI, true); static Arduino_ST7789 g_lcd = Arduino_ST7789(&g_lcdBus, -1, 0, true, LCD_W, LCD_H); static constexpr uint32_t LCD_HARDWARE_RESET_DELAY_MS = 120; @@ -378,27 +377,13 @@ static void lcdEnsureInit() { lcdBacklightOn(); - SPI.begin(); - - uint32_t spi_hz = configManager.getLCDSpiHzSafe(); uint8_t rotation = configManager.getLCDRotationSafe(); - /* - todo: for some reason just calling g_lcdBus.begin, hardReset, and vendorInit doesn't init the display, but - given all g_lcd.begin() does is initialize the bus and sends some data (which is discarded with the hard reset) - this is the all more confusing - - todo2: so with RST and D/C this still needs both g_lcd.begin() and the custom init. But the g_lcd.begin() overrides - the SPI mode to mode 2, while we internally use mode 0. Strange stuff, one of the two functions is probably sending - bogus data - */ - + // SPI mode 3 is required. This toggles the pin from LOW to HIGH after reset, which my guess + // is after reset "initializes" the SPI interface of the display, as CS is tied to GND? + // ...strange that SPI_MODE0 will not work as the IC doesn't care about CLK's polarity + g_lcdBus.begin((int32_t)LCD_SPI_HZ, (int8_t)LCD_SPI_MODE); lcdHardReset(); - g_lcd.begin(); - delay(LCD_BEGIN_DELAY_MS); - - g_lcdBus.begin((int32_t)spi_hz, (int8_t)SPI_MODE0); - // lcdHardReset(); // no longer needed after RST/DC pin swap lcdRunVendorInit(); delay(LCD_BEGIN_DELAY_MS); diff --git a/src/display/GeekMagicSPIBus.cpp b/src/display/GeekMagicSPIBus.cpp deleted file mode 100644 index 4575e72..0000000 --- a/src/display/GeekMagicSPIBus.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -/* - * GeekMagic Open Firmware - * Copyright (C) 2026 Times-Z - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "display/GeekMagicSPIBus.h" - -/** - * @brief Construct a new Geek Magic SPI Bus:: Geek Magic SPI Bus object - * - * @param dc Data/Command pin - * @param cs Chip Select pin - * @param csActiveHigh Whether CS is active high - * @param defaultSpeed Default SPI speed - * @param defaultDataMode Default SPI data mode - */ -GeekMagicSPIBus::GeekMagicSPIBus(int8_t dataCmdPin, int8_t csPin, bool csActiveHigh, int32_t defaultSpeed, - int8_t defaultDataMode) - : _spi(dataCmdPin, GFX_NOT_DEFINED, &SPI, true), - _cs(csPin), - _csActiveHigh(csActiveHigh), - _defaultSpeed(defaultSpeed), - _defaultDataMode(defaultDataMode) {} - -/** - * @brief Initializes the SPI bus with the specified speed and data mode - * @param speed SPI speed in Hz - * @param dataMode SPI data mode - * - * @return true if initialization is successful false otherwise - */ -auto GeekMagicSPIBus::begin(int32_t speed, int8_t dataMode) -> bool { - if (speed == GFX_NOT_DEFINED) { - speed = _defaultSpeed; - } - if (dataMode == GFX_NOT_DEFINED) { - dataMode = _defaultDataMode; - } - - if (_cs != GFX_NOT_DEFINED) { - pinMode((uint8_t)_cs, OUTPUT); - digitalWrite((uint8_t)_cs, _csActiveHigh ? LOW : HIGH); - } - - return _spi.begin(speed, dataMode); -} - -/** - * @brief Begins an SPI write transaction - * - * @return void - */ -auto GeekMagicSPIBus::beginWrite() -> void { - if (_cs != GFX_NOT_DEFINED) { - digitalWrite((uint8_t)_cs, _csActiveHigh ? HIGH : LOW); - } - _spi.beginWrite(); -} - -/** - * @brief Ends an SPI write transaction - * - * @return void - */ -auto GeekMagicSPIBus::endWrite() -> void { - _spi.endWrite(); - - if (LCD_KEEP_CS_ASSERTED) { - return; - } - - if (_cs != GFX_NOT_DEFINED) { - digitalWrite((uint8_t)_cs, _csActiveHigh ? LOW : HIGH); - } -} From 860bd69b7cde71f0afe6730e07c24727c98aaeb0 Mon Sep 17 00:00:00 2001 From: Electro707 Date: Sat, 31 Jan 2026 21:40:18 -0500 Subject: [PATCH 10/11] Made clang-tidy happy --- src/display/DisplayManager.cpp | 3 --- src/main.cpp | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/display/DisplayManager.cpp b/src/display/DisplayManager.cpp index 13cba2a..7530ae1 100644 --- a/src/display/DisplayManager.cpp +++ b/src/display/DisplayManager.cpp @@ -328,9 +328,6 @@ static void lcdRunVendorInit() { ST7789_WriteCommand(ST7789_INVERSION_ON); - ST7789_WriteCommand(ST7789_NORMAL_DISPLAY_MODE); - delay(10); - ST7789_WriteCommand(ST7789_DISPLAY_ON); ST7789_WriteCommand(ST7789_CASET); diff --git a/src/main.cpp b/src/main.cpp index 7fff3cb..82850ab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -155,7 +155,7 @@ void setup() { // enable watchdog before going to loop() // 2 seconds should be way more than the main loop needs to do stuff - ESP.wdtEnable(WDTO_2S); + EspClass::wdtEnable(WDTO_2S); } void loop() { @@ -182,5 +182,5 @@ void loop() { Logger::info(msgBuf); } - ESP.wdtFeed(); // kick watchdog + EspClass::wdtFeed(); // kick watchdog } From 564f5b4f7e772787940d0a563bee007d2490b861 Mon Sep 17 00:00:00 2001 From: Electro707 Date: Sat, 31 Jan 2026 22:27:21 -0500 Subject: [PATCH 11/11] Rewording on readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 5bbcb0c..2306cc6 100644 --- a/readme.md +++ b/readme.md @@ -70,7 +70,7 @@ - **Resolution**: 240x240 pixels - **Color Format**: RGB565 (16-bit color) - **Interface**: SPI (Serial Peripheral Interface) -- **SPI Speed**: up to 40 MHz (80 MHz is possible, but unstable and outside datasheet spec) +- **SPI Speed**: 40 MHz (80 MHz is possible, but unstable and outside datasheet spec) - **Rotation**: Upside-down for cube display, normal for the small tv ### Pin wiring