#include "HermitCrab.h" #include "Config.h" #include "ConnectWiFi.h" #include "TimeManager.h" #include "WiFiHost.h" #include "UI.h" #include // 35 #include // 25 #include #include #define TAG_UI "UI" // Buttons #define DEBOUNCE_DELAY 100 #define LONG_PRESS_DURATION 1500 // Display #define SPACING 2 #define FONT_DESCENT 3 #define POS_X_UNIT 112 #define WIDTH_UNIT 16 #define HEIGHT_UNIT 16 #define POS_Y_BOTTOM (SCREEN_HEIGHT - 1) #define WIDTH_D3 20 #define POS_X_D3 (POS_X_UNIT - WIDTH_D3 - SPACING) #define HEIGHT_D3 26 #define WIDTH_DOT 7 #define POS_X_DOT (POS_X_D3 - WIDTH_DOT - SPACING) #define HEIGHT_DOT 7 #define WIDTH_D2 26 #define POS_X_D2 (POS_X_DOT - WIDTH_D2 - SPACING) #define HEIGHT_D2 36 #define WIDTH_D1 WIDTH_D2 #define POS_X_D1 (POS_X_D2 - WIDTH_D1 - SPACING) #define HEIGHT_D1 HEIGHT_D2 #define WIDTH_D0 WIDTH_D1 #define POS_X_D0 (POS_X_D1 - WIDTH_D1 - SPACING) #define HEIGHT_D0 HEIGHT_D1 enum MAIN_MODE { MODE_TEMP, MODE_HUMID, MODE_CLOCK }; //Adafruit_SSD1306 ssd1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); CUI ui; const uint8_t logo[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xC0, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x26, 0x17, 0xC0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x88, 0x20, 0x30, 0x00, 0x00, 0x80, 0x04, 0x02, 0x02, 0xC1, 0xFC, 0x00, 0x00, 0x80, 0x04, 0x06, 0x5E, 0x03, 0xC3, 0x00, 0x00, 0x80, 0x04, 0x04, 0xF8, 0x03, 0x00, 0xC0, 0x00, 0x80, 0x04, 0x00, 0xF0, 0x07, 0x00, 0x30, 0x00, 0x80, 0x08, 0x11, 0x18, 0x0D, 0x00, 0xC8, 0x03, 0x00, 0x08, 0x12, 0x20, 0x0C, 0x81, 0xFC, 0x05, 0x00, 0x10, 0x26, 0x50, 0x0F, 0x81, 0xFE, 0x0A, 0x00, 0x08, 0x64, 0xF0, 0x1D, 0x86, 0x67, 0x14, 0x00, 0x08, 0x41, 0x80, 0x18, 0xC7, 0x31, 0xF8, 0x00, 0x08, 0xCB, 0x80, 0x3B, 0xC6, 0x1B, 0x20, 0x00, 0x08, 0xCB, 0x80, 0x7C, 0x66, 0x1F, 0xE0, 0x00, 0x09, 0xDF, 0xB0, 0x10, 0x60, 0x39, 0xE0, 0x00, 0x09, 0x87, 0xE0, 0x38, 0xF8, 0x79, 0x90, 0x00, 0x0B, 0xAE, 0x00, 0x7C, 0x7C, 0xF1, 0xFF, 0x00, 0x13, 0x6E, 0x00, 0x3C, 0x60, 0x98, 0xFD, 0x80, 0x13, 0x08, 0x40, 0x7C, 0x01, 0x9E, 0xFD, 0xC0, 0x17, 0x1C, 0x00, 0xFC, 0x03, 0x8D, 0xFB, 0xC0, 0x16, 0x5C, 0x02, 0x3C, 0x0E, 0xFF, 0xFB, 0xE0, 0x0E, 0x5C, 0x04, 0x78, 0x1F, 0x9F, 0x99, 0xF0, 0x0C, 0x9C, 0x60, 0x78, 0x77, 0xFF, 0xFF, 0xF8, 0x04, 0x9F, 0xC0, 0xB3, 0xFF, 0xF6, 0x9F, 0x7C, 0x06, 0x9F, 0x01, 0x1F, 0x7E, 0xFF, 0xFF, 0xBC, 0x06, 0x1C, 0x00, 0x7F, 0xFB, 0x16, 0xEF, 0xFC, 0x02, 0x48, 0x01, 0xF7, 0xFF, 0xC7, 0xCF, 0xFE, 0x01, 0x08, 0x03, 0xBE, 0xF7, 0xF4, 0x0F, 0xFA, 0x00, 0x90, 0x0F, 0xFA, 0xFF, 0xB4, 0x07, 0x7E, 0x00, 0x40, 0x1D, 0xFF, 0x77, 0x42, 0x07, 0xBA, 0x00, 0x40, 0x2F, 0xF7, 0x7B, 0x41, 0x06, 0x5B, 0x00, 0x41, 0xFF, 0x6E, 0xDC, 0xC3, 0x07, 0x7F, 0x00, 0x27, 0x7D, 0x3C, 0xDF, 0xCB, 0x87, 0x5B, 0x00, 0x3F, 0xFF, 0xF6, 0xCE, 0x59, 0xC6, 0xF9, 0x00, 0x1E, 0xF7, 0x7A, 0xC7, 0xFB, 0xC7, 0x6D, 0x00, 0x0B, 0xFE, 0xDF, 0xE8, 0x9B, 0x86, 0x7D, 0x00, 0x0B, 0xC5, 0x8F, 0xF0, 0xC3, 0x80, 0x3F, 0x00, 0x17, 0x85, 0x85, 0xE8, 0xFB, 0xC0, 0x37, 0x00, 0x3E, 0x0B, 0x05, 0x68, 0xBE, 0xE0, 0x37, 0x00, 0x7C, 0x13, 0x05, 0xF8, 0xBF, 0xF0, 0x16, 0x00, 0xB8, 0x26, 0x09, 0x70, 0xFD, 0xF0, 0x1E, 0x01, 0x70, 0x24, 0x0A, 0x70, 0xFF, 0xF8, 0x1C, 0x03, 0xC0, 0x58, 0x0A, 0x50, 0xDF, 0xB8, 0x1C, 0x07, 0x80, 0x98, 0x0E, 0x50, 0xFB, 0xF8, 0x1C, 0x1F, 0x00, 0xB0, 0x16, 0x70, 0x7D, 0xF0, 0x18, 0x3C, 0x01, 0x60, 0x14, 0x60, 0x3D, 0xF0, 0x00, 0x78, 0x01, 0xE0, 0x14, 0x60, 0x1E, 0xF0, 0x00, 0xF0, 0x03, 0x80, 0x18, 0xE0, 0x07, 0xE0, 0x00, 0xC0, 0x07, 0x00, 0x38, 0xE0, 0x03, 0x80, 0x00, 0x00, 0x0E, 0x00, 0x31, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x71, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const uint8_t degreeC[] = { 0x60, 0x00, 0x90, 0x00, 0x90, 0x00, 0x63, 0xE4, 0x0E, 0x1C, 0x1C, 0x04, 0x18, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x1C, 0x04, 0x0E, 0x08, 0x07, 0xF0, 0x00, 0x00 }; const uint8_t percentSign[] = { 0x00, 0x00, 0x38, 0x18, 0x7C, 0x30, 0xC6, 0x30, 0xC6, 0x60, 0xC6, 0xc0, 0x7C, 0xC0, 0x39, 0x80, 0x01, 0x9C, 0x03, 0x3E, 0x03, 0x63, 0x06, 0x63, 0x0C, 0x63, 0x0C, 0x3E, 0x18, 0x1C, 0x00, 0x00 }; enum UI_ITEM { ITEM_CLOCK, // Sensor ITEM_TEMP1, ITEM_TEMP2, ITEM_TEMP3, ITEM_HUMID, ITEM_HUMID2, // SV ITEM_TEMP_TARGET, ITEM_HUMID_TARGET, // CV ITEM_HEAT1, ITEM_HEAT2, ITEM_MIST, ITEM_FAN, ITEM_MOTOR, ITEM_LIGHT, ITEM_HEAT1_MANUAL, ITEM_HEAT2_MANUAL, ITEM_MIST_MANUAL, ITEM_FAN_MANUAL, ITEM_MOTOR_MANUAL, ITEM_LIGHT_MANUAL, ITEM_CHECK_AC, ITEM_COUNT }; char *title[] = { "Time:", "Temp1", "Temp2", "Temp3", "RH1", "RH2", "TT", "RHT", "Heat1", "Heat2", "Mist", "Fan", "Pump", "Lum", "Ht1 M", "Ht2 M", "Mst M", "Fan M", "Pmp M", "Lum M", "Chk AC" }; uint8_t *unit[] = { (uint8_t *) degreeC, (uint8_t *) percentSign, nullptr }; const uint16_t main_unit_idx[] = { 0, 1, 2 }; const uint16_t item_unit_idx[] = { 2, // Time 0, 0, 0, // Temp 1, 1, // Humid 0, 1, // SV 1, 1, 1, 1, 1, 1, // CV 2, 2, 2, 2, 2, 2, // Manual 2 }; const bool set_idx[] = { false, false, false, false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true }; const bool fineControl[] = { false, false, false, false, false, false, true, true, false, false, false, false, false, false, true, true, true, true, true, true, true }; // // Buttons // void buttonSetISR(); void buttonUpISR(); void buttonDownISR(); // Constructor for CUI class //CUI::CUI(Adafruit_SSD1306 &display) CUI::CUI() : SSD1306() {} void CUI::setup() { // Initialize the display ESP_LOGI(TAG_UI," UI - setup()"); bOK = false; bDot = false; bButtonChanged = false; m_nMessageMode = 0; m_nMainMode = 0; m_nItemMode = 0; m_nItem = 0; m_nValue = 0; m_pUnit = (uint8_t *) unit[item_unit_idx[m_nItem]]; m_nD0 = m_nD1 = m_nD2 = m_nD3 = 0; m_pDUnit = nullptr; lastMessageMode = 1; lastMainMode = 1; lastItemMode = 1; lastItem = 999; lastValue = 999; lastUnit = nullptr; lastD0 = 999; lastD1 = 999; lastD2 = 999; lastD3 = 999; lastpDUnit = nullptr; // Check if device exists for (int i = 0; i < 5; i++) { Wire.beginTransmission(i2caddr); if (Wire.endTransmission() == 0) { bOK = true; ESP_LOGI(TAG_UI," UI - device Found at 0x%02X\n", i2caddr); break; } delay(50); } if (!bOK) { ESP_LOGI(TAG_UI," UI - device NOT found at 0x%02X\n", i2caddr); return; } // Init Display hardware begin(SSD1306_SWITCHCAPVCC, i2caddr); //setContrast(contrast / 2); setTextColor(SSD1306_WHITE); updateScreen(); // Display Logo int x = (width() - 64); int y = (height() - 64) / 2; drawBitmap(x, y, logo, 64, 64, 1); updateScreen(); ssd1306_hscroll(true, 0, 7, x, 32); // Box boxMode = { 0, HEIGHT_UNIT, 24, 8 }; boxTitle = { 0, 0, 64, 16 }; boxValue = { 0, 0, 0, 16 }; boxUnit = { POS_X_UNIT, 0, WIDTH_UNIT, HEIGHT_UNIT }; boxDUnit = {POS_X_UNIT, POS_Y_BOTTOM - HEIGHT_UNIT, WIDTH_UNIT, HEIGHT_UNIT}; // 16 boxD3 = {POS_X_D3, POS_Y_BOTTOM - HEIGHT_D3, WIDTH_D3, HEIGHT_D3}; // 17 + 2 boxDot = {POS_X_DOT, POS_Y_BOTTOM - HEIGHT_DOT, WIDTH_DOT, HEIGHT_DOT}; // 7 + 2 boxD2 = {POS_X_D2, POS_Y_BOTTOM - HEIGHT_D2, WIDTH_D2, HEIGHT_D2}; // 17 + 2 boxD1 = {POS_X_D1, POS_Y_BOTTOM - HEIGHT_D1, WIDTH_D1, HEIGHT_D1}; // 17 + 2 boxD0 = {POS_X_D0, POS_Y_BOTTOM - HEIGHT_D0, WIDTH_D0, HEIGHT_D0}; // 17 + 2 // // Buttons // pinMode(PIN_SW_SET, INPUT_PULLUP); pinMode(PIN_SW_UP, INPUT_PULLUP); pinMode(PIN_SW_DOWN, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(PIN_SW_SET), buttonSetISR, CHANGE); attachInterrupt(digitalPinToInterrupt(PIN_SW_UP), buttonUpISR, CHANGE); attachInterrupt(digitalPinToInterrupt(PIN_SW_DOWN), buttonDownISR, CHANGE); initButtonState(); } void CUI::start() { vTaskDelay(1500/portTICK_PERIOD_MS); clearDisplayBuffer(); updateScreen(); initButtonState(); //getBoundaries(); } void CUI::getBoundaries() { // Display Boundary { int16_t x, y; uint16_t w, h; char sz[2]; sz[1] = 0; int nMaxW24 = 0; int nMaxH24 = 0; int nMaxW18 = 0; int nMaxH18 = 0; int nMaxW9 = 0; int nMaxH9 = 0; int nMaxW24A = 0; int nMaxH24A = 0; int nMaxW18A = 0; int nMaxH18A = 0; int nMaxW9A = 0; int nMaxH9A = 0; for (int i = 0; i < 10; i++) { sz[0] = 0x30 + i; setFont(&FreeSansBold24pt7b); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); ESP_LOGI(TAG_UI,"TextBound24 - '%c' w(%d) h(%d)\n", sz[0], w, h); if (w > nMaxW24) nMaxW24 = w; if (h > nMaxH24) nMaxH24 = h; setFont(&FreeSans18pt7b); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); ESP_LOGI(TAG_UI,"TextBound18 - '%c' w(%d) h(%d)\n", sz[0], w, h); if (w > nMaxW18) nMaxW18 = w; if (h > nMaxH18) nMaxH18 = h; setFont(&FreeSans9pt7b); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); ESP_LOGI(TAG_UI,"TextBound18 - '%c' w(%d) h(%d)\n", sz[0], w, h); if (w > nMaxW9) nMaxW9 = w; if (h > nMaxH9) nMaxH9 = h; } for (int i = 0; i < 26; i++) { sz[0] = 'A' + i; setFont(&FreeSansBold24pt7b); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); ESP_LOGI(TAG_UI,"TextBound24 - '%c' w(%d) h(%d)\n", sz[0], w, h); if (w > nMaxW24A) nMaxW24A = w; if (h > nMaxH24A) nMaxH24A = h; setFont(&FreeSans18pt7b); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); ESP_LOGI(TAG_UI,"TextBound18 - '%c' w(%d) h(%d)\n", sz[0], w, h); if (w > nMaxW18A) nMaxW18A = w; if (h > nMaxH18A) nMaxH18A = h; setFont(&FreeSans9pt7b); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); ESP_LOGI(TAG_UI,"TextBound18 - '%c' w(%d) h(%d)\n", sz[0], w, h); if (w > nMaxW9A) nMaxW9A = w; if (h > nMaxH9A) nMaxH9A = h; } { sz[0] = ':'; setFont(&FreeSansBold24pt7b); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); ESP_LOGI(TAG_UI,"TextBound24 - '%c' w(%d) h(%d)\n", sz[0], w, h); setFont(&FreeSans18pt7b); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); ESP_LOGI(TAG_UI,"TextBound18 - '%c' w(%d) h(%d)\n", sz[0], w, h); setFont(&FreeSans9pt7b); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); ESP_LOGI(TAG_UI,"TextBound18 - '%c' w(%d) h(%d)\n", sz[0], w, h); } { sz[0] = '.'; setFont(&FreeSansBold24pt7b); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); ESP_LOGI(TAG_UI,"TextBound24 - '%c' w(%d) h(%d)\n", sz[0], w, h); setFont(&FreeSans18pt7b); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); ESP_LOGI(TAG_UI,"TextBound18 - '%c' w(%d) h(%d)\n", sz[0], w, h); setFont(&FreeSans9pt7b); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); ESP_LOGI(TAG_UI,"TextBound18 - '%c' w(%d) h(%d)\n", sz[0], w, h); } ESP_LOGI(TAG_UI,"Font24 Max w(%d) h(%d)\n", nMaxW24, nMaxH24); ESP_LOGI(TAG_UI,"Font24A Max w(%d) h(%d)\n", nMaxW24A, nMaxH24A); ESP_LOGI(TAG_UI,"Font18 Max w(%d) h(%d)\n", nMaxW18, nMaxH18); ESP_LOGI(TAG_UI,"Font18A Max w(%d) h(%d)\n", nMaxW18A, nMaxH18A); ESP_LOGI(TAG_UI,"Font_9 Max w(%d) h(%d)\n", nMaxW9, nMaxH9); ESP_LOGI(TAG_UI,"Font_9A Max w(%d) h(%d)\n", nMaxW9A, nMaxH9A); } } MY_IRAM_ATTR void CUI::message(uint8_t lineNo, char *sz) { int16_t x, y; uint16_t w, h; if (!bOK) return; getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); if (w > 128) w = 128; fillRect(0,lineNo * 8,w,8, SSD1306_BLACK); setFont(NULL); setTextSize(1); setCursor(0, lineNo * 8); print(sz); updateRegion(lineNo,lineNo,0,w); } // Main function to monitor and update display MY_IRAM_ATTR void CUI::updateDisplay(unsigned long tickSecond) { if (!bOK) return; static unsigned long lastUpdate = 0; static unsigned long lastMainModeChange = 0; bool bUpdateTop = false; bool bUpdateBottom = false; bUpdateTop = displayTop(); if (tickSecond != lastUpdate) { bUpdateBottom = displayBottom(); lastUpdate = tickSecond; } if (bUpdateTop && bUpdateBottom) { updateScreen(); } else if (bUpdateTop) { updateRegion(0, 1, 0, SCREEN_WIDTH - 1); } else if (bUpdateBottom) { updateRegion(3, 7, 0, SCREEN_WIDTH - 1); } if (tickSecond - lastMainModeChange >= 20) { lastMainMode = m_nMainMode; m_nMainMode = (m_nMainMode + 1) % 2; lastMainModeChange = tickSecond; } } MY_IRAM_ATTR void CUI::updateDisplayTop(unsigned long tick) { if (!bOK) return; if (displayTop()) { updateRegion(0, 1, 0, SCREEN_WIDTH - 1); } } MY_IRAM_ATTR void CUI::updateDisplayBottom(unsigned long tickSecond) { if (!bOK) return; static unsigned long lastMainModeChange = 0; if (displayBottom()) { updateRegion(3, 7, 0, SCREEN_WIDTH - 1); } if (tickSecond - lastMainModeChange >= 20) { lastMainMode = m_nMainMode; m_nMainMode = (m_nMainMode + 1) % 2; lastMainModeChange = tickSecond; } } MY_IRAM_ATTR bool CUI::displayTop() { static uint8_t lastHour = 255, lastMin = 255, lastSec = 255; bool bUpdateTop = false; char sz[32]; int16_t x, y; uint16_t w, h; int16_t firstC = SCREEN_WIDTH - 1; int16_t lastC = 0; int16_t endX = SCREEN_WIDTH - WIDTH_UNIT - SPACING * 2; // Message Mode if (!bButtonChanged) { if (config.bCheckAC && !isWiFiConnected()) m_nMessageMode = MESSAGE_MODE::MODE_NO_WIFI; else if (config.bCheckAC && (status.nFlags & FLAG_ZCD_AC)) m_nMessageMode = MESSAGE_MODE::MODE_AC; else if (config.bCheckAC && (status.nFlags & FLAG_ZCD_LOAD)) m_nMessageMode = MESSAGE_MODE::MODE_LOAD; else m_nMessageMode = MESSAGE_MODE::MODE_NONE; if (m_nMessageMode != lastMessageMode) { char sz[32]; switch(m_nMessageMode) { case MESSAGE_MODE::MODE_NO_WIFI: strcpy(sz, "No WiFi! "); break; case MESSAGE_MODE::MODE_AC: strcpy(sz, "Check AC! "); break; case MESSAGE_MODE::MODE_LOAD: strcpy(sz, "Check Load! "); break; } if (m_nMessageMode != MESSAGE_MODE::MODE_NONE) { fillRect(0, 0, SCREEN_WIDTH, HEIGHT_UNIT, SSD1306_BLACK); setFont(&FreeSans9pt7b); setCursor(boxTitle.x, boxTitle.y + 15 - FONT_DESCENT); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); print(sz); updateRegion(0, 1, 0, w - 1); lastItemMode = 999; lastMessageMode = m_nMessageMode; return false; } } else { if (m_nMessageMode != MESSAGE_MODE::MODE_NONE) { //ESP_LOGI(TAG_UI,"UI - Message NOT CHANGED and MODE is NOT NORMAL"); return false; } } } lastMessageMode = MESSAGE_MODE::MODE_NONE; // ItemMode if (m_nItemMode != lastItemMode) { setFont(NULL); setTextSize(1); boxMode.w = 24; fillRect(boxMode.x, boxMode.y, boxMode.w, boxMode.h, SSD1306_BLACK); switch(m_nItemMode) { case ITEM_MODE::MODE_SET: setCursor(boxMode.x, boxMode.y); print("SET"); //ESP_LOGI(TAG_UI,"UI - Item Mode Change - SET"); break; case ITEM_MODE::MODE_CONFIG: setCursor(boxMode.x, boxMode.y); print("CFG"); //ESP_LOGI(TAG_UI,"UI - Item Mode Change - CONFIG"); break; case ITEM_MODE::MODE_NORMAL: //ESP_LOGI(TAG_UI,"UI - Item Mode Change - NORMAL"); break; } updateRegion(2,2,0,boxMode.w); lastItemMode = m_nItemMode; lastItem = 999; lastValue = m_nValue + 1111; } // Top setFont(&FreeSans9pt7b); if (m_nItem != lastItem) { // Title fillRect(0, 0, SCREEN_WIDTH, HEIGHT_UNIT, SSD1306_BLACK); setCursor(boxTitle.x, boxTitle.y + 15 - FONT_DESCENT); getTextBounds((const char *)title[m_nItem], 0, 0, &x, &y, &w, &h); boxTitle.w = w + 2; print(title[m_nItem]); // Value if (m_nItem == UI_ITEM::ITEM_CLOCK) { sprintf(sz, "%02d:%02d.%02d", g_nHour, g_nMinute, g_nSecond); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); boxValue.x = boxTitle.w + 8; boxValue.w = SCREEN_WIDTH - boxValue.x; lastHour = g_nHour; lastMin = g_nMinute; lastSec = g_nSecond; } else { if (m_nItem < ITEM_HEAT1_MANUAL) { sprintf(sz, "%.1f", m_nValue / 10.0f); } else { if (m_nItem < ITEM_CHECK_AC) sprintf(sz, "%s", m_nValue ? "Manual" : "Auto"); else sprintf(sz, "%s", m_nValue ? "Yes" : "No"); endX = SCREEN_WIDTH - 1; } getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); int16_t newX = endX - w - SPACING; uint16_t newW = endX - newX; boxValue.x = newX; boxValue.w = newW; lastValue = m_nValue; } setCursor(boxValue.x, boxValue.y + 15 - FONT_DESCENT); print(sz); // Item Unit if (m_nItem != UI_ITEM::ITEM_CLOCK && m_nItem < UI_ITEM::ITEM_HEAT1_MANUAL) { m_pUnit = (uint8_t *) unit[item_unit_idx[m_nItem]]; drawBitmap(boxUnit.x, boxUnit.y, m_pUnit, boxUnit.w, boxUnit.h, SSD1306_WHITE); lastUnit = m_pUnit; } else { m_pUnit = nullptr; } lastItem = m_nItem; bUpdateTop = true; firstC = 0; lastC = 127; return true; } // value if (m_nItem == UI_ITEM::ITEM_CLOCK) { if (lastHour != g_nHour || lastMin != g_nMinute || lastSec != g_nSecond) { lastHour = g_nHour; lastMin = g_nMinute; lastSec = g_nSecond; fillRect(boxValue.x, boxValue.y, boxValue.w, boxValue.h, SSD1306_BLACK); sprintf(sz, "%02d:%02d.%02d", g_nHour, g_nMinute, g_nSecond); getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); boxValue.x = boxTitle.w + 8; boxValue.w = SCREEN_WIDTH - boxValue.x; setCursor(boxValue.x, boxValue.y + 15 - FONT_DESCENT); print(sz); bUpdateTop = true; firstC = boxValue.x; lastC = 127; } } else if (m_nValue != lastValue) { fillRect(boxValue.x, boxValue.y, boxValue.w + SPACING, boxValue.h, SSD1306_BLACK); if (m_nItem < ITEM_HEAT1_MANUAL) { sprintf(sz, "%.1f", m_nValue / 10.0f); } else { if (m_nItem < ITEM_CHECK_AC) sprintf(sz, "%s", m_nValue ? "Manual" : "Auto"); else sprintf(sz, "%s", m_nValue ? "Yes" : "No"); endX = SCREEN_WIDTH - SPACING - 1; } getTextBounds((const char *)sz, 0, 0, &x, &y, &w, &h); int16_t newX = endX - w - SPACING; uint16_t newW = endX - newX; setCursor(newX, boxValue.y + 15 - FONT_DESCENT); print(sz); bUpdateTop = true; firstC = boxValue.x < newX ? boxValue.x : newX; lastC = endX + SPACING; boxValue.x = newX; boxValue.w = newW; lastValue = m_nValue; } if (bUpdateTop && lastC - firstC > 0) { //unsigned long m = millis(); updateRegion(0, 1, firstC, lastC); //m = millis() - m; //ESP_LOGI(TAG_UI,"UI - Update Top(%d~%d): %d msec\n", firstC, lastC, m); } return false; } MY_IRAM_ATTR bool CUI::displayBottom() { bool bUnitChange = false; bool bD3Change = false; bool bD2Change = false; bool bD1Change = false; bool bD0Change = false; int firstC = SCREEN_WIDTH - 1; int lastC = 0; char sz[32]; static uint8_t bLastNTP = 0xFF; static uint8_t bLastBLE = ~FLAG_BLE_NODATA; static uint8_t bLastConn = 0xFF; static uint8_t bLastWiFi = 0xFF; // Bottom // Update temperature and humidity if they have changed int16_t value; switch (m_nMainMode) { case MAIN_MODE::MODE_TEMP: value = status.nTemp1; if (value < 0) value = 0; m_pDUnit = (uint8_t *) °reeC[0]; break; case MAIN_MODE::MODE_HUMID: value = status.nHumid1; if (value < 0) value = 0; m_pDUnit = (uint8_t *) &percentSign[0]; break; default: value = 0; break; } // Unit if (m_pDUnit != lastpDUnit) { fillRect(boxDUnit.x, boxDUnit.y, boxDUnit.w, boxDUnit.h, SSD1306_BLACK); drawBitmap( boxDUnit.x, boxDUnit.y, m_pDUnit, boxDUnit.w, boxDUnit.h, SSD1306_WHITE); lastpDUnit = m_pDUnit; bUnitChange = true; firstC = boxDUnit.x; lastC = boxDUnit.x + boxDUnit.w - 1; } // Value m_nD3 = value % 10; value /= 10; m_nD2 = value % 10; value /= 10; m_nD1 = value % 10; m_nD0 = value / 10; // Update Tenths digit if (m_nD3 != lastD3) { fillRect(boxD3.x, boxD3.y, boxD3.w, boxD3.h, SSD1306_BLACK); setFont(&FreeSans18pt7b); // 25 pixels high sprintf(sz, "%d", m_nD3); setCursor(boxD3.x, SCREEN_HEIGHT - FONT_DESCENT); print(sz); lastD3 = m_nD3; bD3Change = true; firstC = boxD3.x; int end = boxD3.x + boxD3.w - 1; if (lastC < end) lastC = end; } setFont(&FreeSansBold24pt7b); // 35 pixels high // Dot if (!bDot) { setCursor(boxDot.x, SCREEN_HEIGHT - FONT_DESCENT); print("."); bDot = true; firstC = boxDot.x; int end = boxDot.x + boxDot.w - 1; if (lastC < end) lastC = end; } // Update One's digit if (m_nD2 != lastD2) { fillRect(boxD2.x, boxD2.y, boxD2.w, boxD2.h, SSD1306_BLACK); sprintf(sz, "%d", m_nD2); setCursor(boxD2.x, SCREEN_HEIGHT - FONT_DESCENT); print(sz); lastD2 = m_nD2; bD2Change = true; firstC = boxD2.x; int end = boxD2.x + boxD2.w - 1; if (lastC < end) lastC = end; } // Update Ten's digit if (m_nD1 != lastD1) { fillRect(boxD1.x, boxD1.y, boxD1.w, boxD1.h, SSD1306_BLACK); if (m_nD0 > 0 || m_nD1 > 0) { sprintf(sz, "%d", m_nD1); setCursor(boxD1.x, SCREEN_HEIGHT - FONT_DESCENT); print(sz); } lastD1 = m_nD1; bD1Change = true; firstC = boxD1.x; int end = boxD1.x + boxD1.w - 1; if (lastC < end) lastC = end; } // Update Hundred's digit if (m_nD0 != lastD0) { fillRect(boxD0.x, boxD0.y, boxD0.w, boxD0.h, SSD1306_BLACK); if (m_nD0 > 0) { sprintf(sz, "%d", m_nD0); setCursor(boxD0.x, SCREEN_HEIGHT - FONT_DESCENT); print(sz); } lastD0 = m_nD0; bD0Change = true; firstC = boxD0.x; int end = boxD0.x + boxD0.w - 1; if (lastC < end) lastC = end; } uint8_t sp = (bD0Change || bD1Change || bD2Change) ? 3 : 4; int updateWidth = lastC - firstC; if (updateWidth > 96) { return true; } else if (updateWidth > 0) { //unsigned long m = millis(); updateRegion(sp, 7, firstC, lastC); //m = millis() - m; //ESP_LOGI(TAG_UI,"UI - Update Bottom(%d~%d): %d msec\n", firstC, lastC, m); } // Bottom Left { bool bUpdate = false; setFont(NULL); setTextSize(1); // WiFi Status uint8_t bWiFi = isWiFiConnected(); if (bWiFi != bLastWiFi) { if (!isWiFiConnected()) { setCursor(0, 7 * 8); print('W'); } else { fillRect(0, 7 * 8, 8, 8, SSD1306_BLACK); } bLastWiFi = bWiFi; bUpdate = true; } // Time Manager uint8_t bNTP = timeManager.hasNTPUpdate() ? 1 : 0; if (bNTP != bLastNTP) { if (!timeManager.hasNTPUpdate()) { setCursor(0, 6 * 8); print('T'); } else { fillRect(0, 6 * 8, 8, 8, SSD1306_BLACK); } bLastNTP = bNTP; bUpdate = true; } // Client Connection uint8_t bConn = host.isConnected(); if (bConn != bLastConn) { if (bConn) { setCursor(0, 7 * 8); print('C'); } else { fillRect(0, 7 * 8, 8, 8, SSD1306_BLACK); } bLastConn = bConn; bUpdate = true; } if (bUpdate) { updateRegion(6,7,0,7); lastMainMode = m_nMainMode; } } return false; } // // Buttons // MY_IRAM_ATTR void CUI::loopButton(unsigned long tickMillis) { //if (host.isConnected()) { // initButtonState(); // return; //} static unsigned long lastButtonAction = 0; checkButtonStates(tickMillis); if (bButtonSetDown || bButtonSetUp || bButtonUpDown || bButtonUpUp || bButtonDownDown || bButtonDownUp) { bButtonChanged = true; lastButtonAction = tickMillis; } else if (tickMillis - lastButtonAction > 20000) { bButtonChanged = false; } if (bButtonSetUp) { switch(m_nItemMode) { case ITEM_MODE::MODE_NORMAL: if (set_idx[m_nItem]) m_nItemMode = buttonSetDownDuration > 1000 ? MODE_CONFIG : MODE_SET; break; case ITEM_MODE::MODE_SET: if (set_idx[m_nItem]) { switch(m_nItem) { case ITEM_TEMP_TARGET: config.nTempTarget = m_nValue; break; case ITEM_HUMID_TARGET: config.nHumidTarget = m_nValue; break; case ITEM_HEAT1: status.nHeater1Duty = m_nValue * 10; break; case ITEM_HEAT2: status.nHeater2Duty = m_nValue * 10; break; case ITEM_MIST: status.nMistDuty = m_nValue * 10; break; case ITEM_FAN: status.nFanDuty = m_nValue; break; case ITEM_MOTOR: status.nMotorDuty = m_nValue; break; case ITEM_LIGHT: status.nLightTargetDuty = m_nValue; break; case ITEM_HEAT1_MANUAL: status.nFlags = m_nValue ? status.nFlags | FLAG_MANUAL_HEATER1 : status.nFlags & ~FLAG_MANUAL_HEATER1; break; case ITEM_HEAT2_MANUAL: status.nFlags = m_nValue ? status.nFlags | FLAG_MANUAL_HEATER2 : status.nFlags & ~FLAG_MANUAL_HEATER2; break; case ITEM_MIST_MANUAL: status.nFlags = m_nValue ? status.nFlags | FLAG_MANUAL_MIST : status.nFlags & ~FLAG_MANUAL_MIST; break; case ITEM_FAN_MANUAL: status.nFlags = m_nValue ? status.nFlags | FLAG_MANUAL_FAN : status.nFlags & ~FLAG_MANUAL_FAN; break; case ITEM_MOTOR_MANUAL: status.nFlags = m_nValue ? status.nFlags | FLAG_MANUAL_MOTOR : status.nFlags & ~FLAG_MANUAL_MOTOR; break; case ITEM_LIGHT_MANUAL: status.nFlags = m_nValue ? status.nFlags | FLAG_MANUAL_LIGHT : status.nFlags & ~FLAG_MANUAL_LIGHT; break; case ITEM_CHECK_AC: config.bCheckAC = m_nValue ? true : false; } m_nItemMode = MODE_NORMAL; } break; case ITEM_MODE::MODE_CONFIG: m_nItemMode = MODE_NORMAL; break; } } else { switch (m_nItemMode) { case MODE_NORMAL: if (bButtonUpUp) { m_nItem = m_nItem > 0 ? --m_nItem : UI_ITEM::ITEM_COUNT - 1; lastValue = m_nValue + 1; } if (bButtonDownUp) { m_nItem = m_nItem < ITEM_COUNT - 1 ? ++m_nItem : 0; lastValue = m_nValue + 1; } break; case MODE_SET: if (bButtonUpUp) { if (fineControl[m_nItem]) { if (m_nItem >= ITEM_HEAT1_MANUAL) { m_nValue = m_nValue ? 0 : 1; } else if (m_nValue < 1000) { m_nValue++; } } else { m_nValue = (m_nValue / 10 + 1) * 10; if (m_nValue > 1000) m_nValue = 1000; } } if (bButtonDownUp) { if (fineControl[m_nItem]) { if (m_nItem >= ITEM_HEAT1_MANUAL) { m_nValue = m_nValue ? 0 : 1; } else if (m_nValue > 0) { m_nValue--; } } else { m_nValue = (m_nValue / 10 - 1) * 10; if (m_nValue < 0) m_nValue = 0; } } break; } } if (m_nItemMode == ITEM_MODE::MODE_NORMAL || !set_idx[m_nItem]) { switch(m_nItem) { case ITEM_TEMP1: m_nValue = status.nTemp1; break; case ITEM_TEMP2: m_nValue = status.nTemp2; break; case ITEM_TEMP3: m_nValue = status.nTemp3; break; case ITEM_HUMID: m_nValue = status.nHumid1; break; case ITEM_HUMID2:m_nValue = status.nHumid2; break; case ITEM_TEMP_TARGET: m_nValue = config.nTempTarget; break; case ITEM_HUMID_TARGET: m_nValue = config.nHumidTarget; break; case ITEM_HEAT1: m_nValue = status.nHeater1Duty / 10; break; case ITEM_HEAT2: m_nValue = status.nHeater2Duty / 10; break; case ITEM_MIST: m_nValue = status.nMistDuty / 10; break; case ITEM_FAN: m_nValue = status.nFanDuty; break; case ITEM_MOTOR: m_nValue = status.nMotorDuty; break; case ITEM_LIGHT: m_nValue = status.nLightTargetDuty; break; case ITEM_HEAT1_MANUAL: m_nValue = status.nFlags & FLAG_MANUAL_HEATER1 ? 1 : 0; break; case ITEM_HEAT2_MANUAL: m_nValue = status.nFlags & FLAG_MANUAL_HEATER2 ? 1 : 0; break; case ITEM_MIST_MANUAL: m_nValue = status.nFlags & FLAG_MANUAL_MIST ? 1 : 0; break; case ITEM_FAN_MANUAL: m_nValue = status.nFlags & FLAG_MANUAL_FAN ? 1 : 0; break; case ITEM_MOTOR_MANUAL: m_nValue = status.nFlags & FLAG_MANUAL_MOTOR ? 1 : 0; break; case ITEM_LIGHT_MANUAL: m_nValue = status.nFlags & FLAG_MANUAL_LIGHT ? 1 : 0; break; case ITEM_CHECK_AC: m_nValue = config.bCheckAC ? 1 : 0; break; } } bButtonSetUp = false; bButtonUpUp = false; bButtonDownUp = false; } void CUI::initButtonState() { buttonSetChangeTime = 0; // Time when button was pressed buttonUpChangeTime = 0; // Time when button was pressed buttonDownChangeTime = 0; // Time when button was pressed buttonSetDownTime = 0; // Time when button was pressed buttonUpDownTime = 0; // Time when button was pressed buttonDownDownTime = 0; // Time when button was pressed buttonSetDownDuration = 0; // Time when button was pressed buttonUpDownDuration = 0; // Time when button was pressed buttonDownDownDuration = 0; // Time when button was pressed bButtonSetChanged = false; bButtonSetUp = false; bButtonSetDown = false; bButtonUpChanged = false; bButtonUpUp = false; bButtonUpDown = false; bButtonDownChanged = false; bButtonDownUp = false; bButtonDownDown = false; m_nMainMode = 0; m_nItemMode = 0; m_nItem = 0; m_nValue = 0; m_pUnit = (uint8_t *) unit[item_unit_idx[m_nItem]]; m_nD0 = m_nD1 = m_nD2 = m_nD3 = 0; lastMainMode = 1; lastItemMode = 1; lastItem = UI_ITEM::ITEM_COUNT; lastValue = 1; lastUnit = nullptr; lastD0 = lastD1 = lastD2 = lastD3 = 1; } void CUI::checkButtonStates(unsigned long currentMillis) { if (bButtonSetChanged) { // Compare with the last interrupt time to ensure debounce delay if (currentMillis - buttonSetChangeTime > DEBOUNCE_DELAY) { if (digitalRead(PIN_SW_SET) == LOW) { // Button pressed buttonSetDownTime = buttonSetChangeTime; bButtonSetDown = true; } else { // Button released if (bButtonSetDown) { bButtonSetUp = true; bButtonSetDown = false; buttonSetDownDuration = currentMillis - buttonSetDownTime; //ESP_LOGI(TAG_UI,"UI Button - SET button RELEASED. Down for %dms\n", buttonSetDownDuration); } } //lastProcessedTime = currentMillis; // Update processed time bButtonSetChanged = false; // Reset the flag } } if (bButtonUpChanged) { // Compare with the last interrupt time to ensure debounce delay if (currentMillis - buttonUpChangeTime > DEBOUNCE_DELAY) { if (digitalRead(PIN_SW_UP) == LOW) { // Button pressed buttonUpDownTime = buttonUpChangeTime; bButtonUpDown = true; } else { // Button released if (bButtonUpDown) { bButtonUpUp = true; bButtonUpDown = false; buttonUpDownDuration = currentMillis - buttonUpDownTime; //ESP_LOGI(TAG_UI,"UI Button - UP button RELEASED. Down for %dms\n", buttonUpDownDuration); } } //lastProcessedTime = currentMillis; // Update processed time bButtonUpChanged = false; // Reset the flag } } if (bButtonDownChanged) { // Compare with the last interrupt time to ensure debounce delay if (currentMillis - buttonDownChangeTime > DEBOUNCE_DELAY) { if (digitalRead(PIN_SW_DOWN) == LOW) { // Button pressed buttonDownDownTime = buttonDownChangeTime; bButtonDownDown = true; } else { // Button released if (bButtonDownDown) { bButtonDownUp = true; bButtonDownDown = false; buttonDownDownDuration = currentMillis - buttonDownDownTime; //ESP_LOGI(TAG_UI,"UI Button - DOWN button RELEASED. Down for %dms\n", buttonDownDownDuration); } } //lastProcessedTime = currentMillis; // Update processed time bButtonDownChanged = false; // Reset the flag } } } // ISR for the Set button handling IRAM_ATTR void buttonSetISR() { // Record the time of the button interrupt and set a flag ui.buttonSetChangeTime = millis(); ui.bButtonSetChanged = true; // Flag for main loop to process } // ISR for the Up button handling IRAM_ATTR void buttonUpISR() { ui.buttonUpChangeTime = millis(); ui.bButtonUpChanged = true; // Flag for main loop to process } // ISR for the Down button handling IRAM_ATTR void buttonDownISR() { ui.buttonDownChangeTime = millis(); ui.bButtonDownChanged = true; // Flag for main loop to process }