#include #include #include "../../Static.h" #include #include "../../LogService.h" U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, 16, 15, 4); class U8Display : public Task { public: U8Display() : Task("U8Display") {} enum ScreenState { BootSplash, Running, Message, Idle = Running }; void onStart() { Log.trace("display: starting redraw thread"); xTaskCreatePinnedToCore( &U8Display::redrawTask, name, 2000, this, 1, &m_renderTask, 0 ); } void onStop() { Log.trace("display: stopping redraw thread"); vTaskDelete(m_renderTask); } void handleEvent(const InputEvent& evt) { m_lastEvent = evt; if (m_state == Idle) { switch(evt.intent) { case InputEvent::NetworkStatus: m_nextState = Message; m_message = evt.asBool() ? "Online!" : "Offline :["; break; case InputEvent::SetPattern: m_nextState = Message; m_message = "Pattern: " + String(evt.asString()); break; case InputEvent::SetPower: m_nextState = Message; m_message = evt.asBool() ? "Power On" : "Power Off"; break; case InputEvent::SaveConfigurationRequest: m_nextState = Message; m_message = "Settings Saved!"; break; case InputEvent::FirmwareUpdate: m_nextState = Message; m_message = "Firmware update"; break; case InputEvent::PreviousPattern: m_nextState = Message; m_message = "Previous Pattern"; break; case InputEvent::NextPattern: m_nextState = Message; m_message = "Next Pattern"; break; } } } void drawSplash() { //u8g2.setFont(u8g2_font_VCR_OSD_mr); u8g2.setFont(u8g2_font_HelvetiPixelOutline_tr); u8g2.setDrawColor(1); uint8_t splashWidth = u8g2.getStrWidth("Renderbug!"); u8g2.drawStr(64 - splashWidth / 2, 32 - 15, "Renderbug!"); u8g2.setFont(u8g2_font_7x13B_mr); u8g2.setCursor(15, 64 - 7); u8g2.print("Version "); u8g2.print(RENDERBUG_VERSION); for(int i = 0; i < 3; i++) { uint8_t sparkleX = (64 - splashWidth/2) + scale8(7-i, beatsin8(40)) * (splashWidth/7) + 5; uint8_t sparkleY = scale8(3+i, beatsin8(40)) * 3 + 7; u8g2.setDrawColor(2); if (beatsin8(60*4) + i * 3 >= 170) { u8g2.drawLine(sparkleX - 3, sparkleY - 3, sparkleX + 3, sparkleY + 3); u8g2.drawLine(sparkleX + 3, sparkleY - 3, sparkleX - 3, sparkleY + 3); } else if (beatsin8(60*4) + i * 2 >= 82) { u8g2.drawLine(sparkleX - 4, sparkleY - 4, sparkleX + 2, sparkleY + 2); u8g2.drawLine(sparkleX - 2, sparkleY - 2, sparkleX + 4, sparkleY + 4); u8g2.drawLine(sparkleX + 4, sparkleY - 4, sparkleX - 2, sparkleY + 2); u8g2.drawLine(sparkleX + 2, sparkleY - 2, sparkleX - 4, sparkleY + 4); } else { u8g2.drawLine(sparkleX - 2, sparkleY, sparkleX + 2, sparkleY); u8g2.drawLine(sparkleX, sparkleY - 2, sparkleX, sparkleY + 2); } } } void drawMessage() { //u8g2.setFont(u8g2_font_VCR_OSD_mr); u8g2.setFont(u8g2_font_HelvetiPixelOutline_tr); uint8_t splashWidth = u8g2.getStrWidth(m_message.c_str()); if (splashWidth >= 128) { u8g2.setFont(u8g2_font_7x13B_mr); splashWidth = u8g2.getStrWidth(m_message.c_str()); } u8g2.drawStr(64 - splashWidth / 2, 32 - 15, m_message.c_str()); } void drawTime() { u8g2.setFont(u8g2_font_7x13O_tn); u8g2.setCursor(0, 64); struct tm timeinfo; Platform::getLocalTime(&timeinfo); uint8_t hour = timeinfo.tm_hour; uint8_t minute = timeinfo.tm_min; u8g2.print(hour); u8g2.print(":"); u8g2.print(minute); } void drawState(ScreenState state) { switch(state) { case BootSplash: drawSplash(); break; case Message: drawMessage(); break; case Running: uint8_t y = 11; u8g2.setFont(u8g2_font_7x13B_mr); u8g2.setCursor(0, y); u8g2.print("FPS: "); u8g2.setFont(u8g2_font_7x13O_tn); u8g2.print(FastLED.getFPS()); y += 12; u8g2.setCursor(0, y); u8g2.setFont(u8g2_font_7x13B_mr); u8g2.print("Last event: "); y += 7; u8g2.setCursor(10, y); u8g2.setFont(u8g2_font_4x6_tr); const char* intentName = LogService::intentName(m_lastEvent.intent); if (intentName) { u8g2.print(intentName); } else { u8g2.print("<"); u8g2.print(m_lastEvent.intent); u8g2.print(">"); } y += 12; u8g2.setCursor(15, y); u8g2.setFont(u8g2_font_7x13O_tf); u8g2.print(LogService::eventValue(m_lastEvent)); drawTime(); break; } } void loop() { EVERY_N_MILLISECONDS(8) { xTaskNotifyGive(m_renderTask); } } private: ScreenState m_state = BootSplash; ScreenState m_nextState = BootSplash; uint8_t m_transitionFrame = 0; uint8_t m_screenStartTime = 0; InputEvent m_lastEvent; String m_message; TaskHandle_t m_renderTask; void redraw() { if (m_state != m_nextState) { EVERY_N_MILLISECONDS(8) { constexpr uint8_t speed = 11; if (m_transitionFrame <= 255 - speed) { m_transitionFrame += speed; } else { m_transitionFrame = 255; } uint8_t offset = ease8InOutApprox(m_transitionFrame); u8g2.clearBuffer(); if (m_transitionFrame <= 128) { uint8_t width = scale8(128, offset * 2); u8g2.setDrawColor(1); drawState(m_state); u8g2.drawBox(0, 0, width, 64); u8g2.setDrawColor(2); u8g2.drawBox(width, 0, 8, 64); } else { uint8_t width = scale8(128, offset/2)*2; u8g2.setDrawColor(1); drawState(m_nextState); u8g2.setDrawColor(2); u8g2.drawBox(std::max(0, width - 8), 0, 8, 64); u8g2.setDrawColor(1); u8g2.drawBox(width, 0, 128, 64); } u8g2.sendBuffer(); if (m_transitionFrame == 255) { m_state = m_nextState; m_screenStartTime = millis(); m_transitionFrame = 0; } } } else { u8g2.clearBuffer(); drawState(m_state); u8g2.sendBuffer(); uint16_t screenTime = millis() - m_screenStartTime; if (screenTime >= 7000 && m_state != Idle) { m_nextState = Idle; } } } static void redrawTask(void* data) { u8g2.begin(); U8Display* self = static_cast(data); self->m_screenStartTime = millis(); while (true) { self->redraw(); ulTaskNotifyTake(0, portMAX_DELAY); } } }; STATIC_ALLOC(U8Display); STATIC_TASK(U8Display);