HCesp/UI.cpp

1140 lines
37 KiB
C++

#include "HermitCrab.h"
#include "Config.h"
#include "ConnectWiFi.h"
#include "TimeManager.h"
#include "WiFiHost.h"
#include "UI.h"
#include <Fonts/FreeSansBold24pt7b.h> // 35
#include <Fonts/FreeSans18pt7b.h> // 25
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSans9pt7b.h>
#include "driver/gpio.h"
#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
};
const 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 };
// Constructor for CUI class
//CUI::CUI(Adafruit_SSD1306 &display)
CUI::CUI()
: SSD1306()
{}
void CUI::setup() {
// Initialize the display
DPRINTLN("[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
// Wire.begin();
for (int i = 0; i < 5; i++) {
Wire.beginTransmission(i2caddr);
if (Wire.endTransmission() == 0) {
bOK = true;
DPRINTF("[UI] Setup - device Found at 0x%02X\n", i2caddr);
break;
}
delay(50);
}
if (!bOK) {
DPRINTF("[UI] Setup - 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
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 = (uint8_t)(~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 *) &degreeC[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 (gpio_get_level((gpio_num_t)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 (gpio_get_level((gpio_num_t)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 (gpio_get_level((gpio_num_t)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
void IRAM_ATTR buttonSetISR(void *) {
// 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
void IRAM_ATTR buttonUpISR(void *) {
ui.buttonUpChangeTime = millis();
ui.bButtonUpChanged = true; // Flag for main loop to process
}
// ISR for the Down button handling
void IRAM_ATTR buttonDownISR(void *) {
ui.buttonDownChangeTime = millis();
ui.bButtonDownChanged = true; // Flag for main loop to process
}