#include #include #include "Boiler.h" #define HIBERNATE_DELAY 5000 #define TEMP_TRIGGER 10 #define LCD_CHAR_SENSOR 1 #define PIN_BTN_MODE 11 #define PIN_BTN_MINUS 10 #define PIN_BTN_PLUS 9 #define PIN_LCD_LED 8 #define PIN_LCD_RS 7 #define PIN_LCD_ENABLE 6 #define PIN_LCD_D0 5 #define PIN_LCD_D1 4 #define PIN_LCD_D2 3 #define PIN_LCD_D3 2 byte g_sensorChar[8] = { B00100, B01110, B01110, B01110, B01110, B11111, B11111, B01110, }; Button g_btnMode(PIN_BTN_MODE); Button g_btnMinus(PIN_BTN_MINUS); Button g_btnPlus(PIN_BTN_PLUS); Button* g_buttons[3] = {&g_btnMode, &g_btnMinus, &g_btnPlus}; #define BUTTONS_COUNT (sizeof(g_buttons) / sizeof(*g_buttons)) LiquidCrystal g_lcd(PIN_LCD_RS, PIN_LCD_ENABLE, PIN_LCD_D0, PIN_LCD_D1, PIN_LCD_D2, PIN_LCD_D3); UiStateEnum g_modeSequence[3] = {WaterSetting, HeaterSetting, Lighting}; #define MODE_SEQUENCE_COUNT (sizeof(g_modeSequence) / sizeof(*g_modeSequence)) GlobalState g_globalState = { .appState = { .water = { .current = 44, .setting = 44, .isActive = false }, .heater = { .current = 44, .setting = 44, .isActive = false } }, .uiState = { .state = Lighting, .lastOpMs = 0, .modeSequenceIndex = MODE_SEQUENCE_COUNT - 1, .isUpdateNeeded = true } }; void printStateLine(char prefix, const BoilerItemState& boilerItemState, bool isModifying, bool isActive, LiquidCrystal lcd) { char curTmp[5], setTmp[5], tmp[17]; dtostrf(boilerItemState.current / 2.0f, 2, 1, curTmp); dtostrf(boilerItemState.setting / 2.0f, 2, 1, setTmp); int count = snprintf(tmp, sizeof(tmp), "%c: %s [%s]%c%c", prefix, curTmp, setTmp, isModifying ? '<' : ' ', isActive ? LCD_CHAR_SENSOR : ' '); for (; count < 17; ++count) { tmp[count] = ' '; } tmp[count] = 0; lcd.print(tmp); } void printState(const GlobalState& globalState, LiquidCrystal lcd) { Serial.println("Updating display"); lcd.setCursor(0, 0); printStateLine('S', globalState.appState.water, globalState.uiState.state == WaterSetting, globalState.appState.water.isActive, lcd); lcd.setCursor(0, 1); printStateLine('C', globalState.appState.heater, globalState.uiState.state == HeaterSetting, globalState.appState.heater.isActive, lcd); } void setState(GlobalState& boilerState, UiStateEnum state) { char tmp[50]; snprintf(tmp, sizeof(tmp), "Changing state %i => %i", g_globalState.uiState.state, state); Serial.println(tmp); if (state == Lighting) { digitalWrite(PIN_LCD_LED, 1); } else if (state == Hibernate) { digitalWrite(PIN_LCD_LED, 0); g_globalState.uiState.modeSequenceIndex = MODE_SEQUENCE_COUNT - 1; } g_globalState.uiState.state = state; g_globalState.uiState.isUpdateNeeded = true; } void checkBoilerItem(BoilerItemState& boilerItemState, UiState uiState) { if (!boilerItemState.isActive && (boilerItemState.current <= boilerItemState.setting - TEMP_TRIGGER)) { boilerItemState.isActive = true; uiState.isUpdateNeeded = true; } else if (boilerItemState.isActive && (boilerItemState.current >= boilerItemState.setting)) { boilerItemState.isActive = false; uiState.isUpdateNeeded = true; } } void setup() { for (unsigned i = 0; i < BUTTONS_COUNT; ++i) { g_buttons[i]->begin(); } pinMode(PIN_LCD_LED, OUTPUT); digitalWrite(PIN_LCD_LED, 1); g_lcd.begin(16, 2); g_lcd.createChar(LCD_CHAR_SENSOR, g_sensorChar); Serial.begin(9600); } void loop() { const auto& currentMs = millis(); for (unsigned i = 0; i < BUTTONS_COUNT; ++i) { g_buttons[i]->read(); if (g_buttons[i]->isPressed()) { g_globalState.uiState.lastOpMs = currentMs; break; } } if (g_globalState.uiState.state == Hibernate) { for (unsigned i = 0; i < BUTTONS_COUNT; ++i) { if (g_buttons[i]->wasReleased()) { setState(g_globalState, Lighting); break; } } } else { if (currentMs - g_globalState.uiState.lastOpMs >= HIBERNATE_DELAY) { setState(g_globalState, Hibernate); } else { if (g_btnMode.wasReleased()) { g_globalState.uiState.modeSequenceIndex = (g_globalState.uiState.modeSequenceIndex + 1) % MODE_SEQUENCE_COUNT; setState(g_globalState, g_modeSequence[g_globalState.uiState.modeSequenceIndex]); } else if (g_btnMinus.wasReleased() || g_btnPlus.wasReleased()) { BoilerItemState* itemState = NULL; if (g_globalState.uiState.state == WaterSetting) { itemState = &g_globalState.appState.water; } else if (g_globalState.uiState.state == HeaterSetting) { itemState = &g_globalState.appState.heater; } if (itemState) { if (g_btnMinus.wasReleased()) { --itemState->setting; } else if (g_btnPlus.wasReleased()) { ++itemState->setting; } g_globalState.uiState.isUpdateNeeded = true; } } } } checkBoilerItem(g_globalState.appState.water, g_globalState.uiState); checkBoilerItem(g_globalState.appState.heater, g_globalState.uiState); if (g_globalState.uiState.isUpdateNeeded) { printState(g_globalState, g_lcd); g_globalState.uiState.isUpdateNeeded = false; } }