#include "display_manager.h" #include "power_manager.h" #include // Map 6-color palette indices to RGB565 for M5GFX static const uint16_t PALETTE_565[6] = { 0x0000, // Black 0xFFFF, // White 0xF800, // Red (approximate) 0x07E0, // Green (approximate) 0x001F, // Blue (approximate) 0xFFE0 // Yellow (approximate) }; void DisplayManager::begin(PowerManager& power) { _power = &power; _display = &M5.Display; // Set rotation for landscape (device physically rotated) _display->setRotation(3); Serial.printf("[display] Initialized: %dx%d, rotation=%d\n", _display->width(), _display->height(), _display->getRotation()); showMessage("Immich Frame", "Starting up..."); } void DisplayManager::showImage(const ProcessedImage& img) { if (!img.valid || img.framebuffer == nullptr) { Serial.println("[display] Invalid image — skipping"); return; } _power->enableEPDPower(); Serial.println("[display] Writing framebuffer to e-ink..."); unsigned long start = millis(); // Write pixel by pixel using palette-mapped colors _display->startWrite(); for (uint16_t y = 0; y < img.height; y++) { for (uint16_t x = 0; x < img.width; x++) { uint8_t colorIdx = img.framebuffer[y * img.width + x]; _display->writePixel(x, y, PALETTE_565[colorIdx]); } } _display->endWrite(); triggerRefresh(); unsigned long elapsed = millis() - start; Serial.printf("[display] Refresh complete in %lu ms\n", elapsed); _power->disableEPDPower(); } void DisplayManager::showMessage(const char* title, const char* body) { _power->enableEPDPower(); _display->fillScreen(TFT_WHITE); _display->setTextColor(TFT_BLACK); _display->setTextDatum(middle_center); _display->setTextSize(2); _display->drawString(title, DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2 - 30); _display->setTextSize(1); _display->drawString(body, DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2 + 20); triggerRefresh(); _power->disableEPDPower(); } void DisplayManager::showMetadata(const AssetInfo& info, uint8_t metaFlags, MetaPosition pos) { if (metaFlags == 0) return; // No metadata to show // Build metadata string String metaText = ""; if ((metaFlags & META_DATE) && info.dateTime.length() > 0) { // Extract just the date portion (YYYY-MM-DD) metaText += info.dateTime.substring(0, 10); } if ((metaFlags & META_LOCATION) && info.city.length() > 0) { if (metaText.length() > 0) metaText += " | "; metaText += info.city; } if ((metaFlags & META_PEOPLE) && !info.people.empty()) { if (metaText.length() > 0) metaText += " | "; for (size_t i = 0; i < info.people.size(); i++) { if (i > 0) metaText += ", "; metaText += info.people[i]; } } if ((metaFlags & META_ALBUM)) { // Album name would need to be passed separately — skip for now } if ((metaFlags & META_CAMERA) && info.camera.length() > 0) { if (metaText.length() > 0) metaText += " | "; metaText += info.camera; } if (metaText.length() == 0) return; // Draw semi-transparent bar with text uint16_t barY = (pos == MetaPosition::Top) ? 0 : (DISPLAY_HEIGHT - 30); _display->fillRect(0, barY, DISPLAY_WIDTH, 30, TFT_BLACK); _display->setTextColor(TFT_WHITE); _display->setTextDatum(middle_center); _display->setTextSize(1); _display->drawString(metaText.c_str(), DISPLAY_WIDTH / 2, barY + 15); } void DisplayManager::triggerRefresh() { // M5GFX handles e-ink refresh internally when using the EPD panel driver // The display() call triggers the actual e-ink refresh cycle (10-20s) _display->display(); } uint16_t DisplayManager::paletteToColor565(uint8_t index) { if (index >= DISPLAY_COLORS) return 0; return PALETTE_565[index]; }