#include #include #include #include #include "Boiler.h" #include "Storage.h" #include "AppCore.h" #define HIBERNATE_DELAY 5000 #define SENSORS_CHECK_INTERVAL 2000 #define SENSORS_REQUEST_DELAY 750 #define TEMP_TRIGGER 10 #define LCD_CHAR_SENSOR 1 #define PIN_ONEWIRE 12 #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, }; Storage g_storage; OneWire g_oneWire(PIN_ONEWIRE); DallasTemperature g_sensors(&g_oneWire); DeviceAddress g_sensor1; 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)) AppCoreState g_appCoreState = { .appState = { .lastSensorRequestMs = 0, .hasReadSensors = true, .water = { .current = TEMP_T_INVALID, .setting = 0, .isActive = false }, .heater = { .current = TEMP_T_INVALID, .setting = 0, .isActive = false } }, .uiState = { .state = Lighting, .lastOpMs = 0, .modeSequenceIndex = MODE_SEQUENCE_COUNT - 1, .isUpdateNeeded = true } }; void tmpToStr(char* out, temp_t temp) { if (temp == TEMP_T_INVALID) { strcpy(out, " -- "); } else { char tmp[7]; dtostrf(temp / 2.0f, 2, 1, tmp); auto len = 4 - strlen(tmp); for (auto i = 0; i < len; ++i) { out[i] = ' '; } strcpy(&out[len], tmp); } } void printStateLine(char prefix, const BoilerItemState& boilerItemState, bool isModifying, bool isActive, LiquidCrystal lcd) { char curTmp[7], setTmp[7], tmp[17]; tmpToStr(curTmp, boilerItemState.current); tmpToStr(setTmp, boilerItemState.setting); 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 AppCoreState& appCoreState, LiquidCrystal lcd) { Serial.println("Updating display"); lcd.setCursor(0, 0); printStateLine('S', appCoreState.appState.water, appCoreState.uiState.state == WaterSetting, appCoreState.appState.water.isActive, lcd); lcd.setCursor(0, 1); printStateLine('C', appCoreState.appState.heater, appCoreState.uiState.state == HeaterSetting, appCoreState.appState.heater.isActive, lcd); } void setState(AppCoreState& boilerState, UiStateEnum state) { char tmp[50]; snprintf(tmp, sizeof(tmp), "Changing state %i => %i", g_appCoreState.uiState.state, state); Serial.println(tmp); if (state == Lighting) { digitalWrite(PIN_LCD_LED, 1); } else if (state == Hibernate) { digitalWrite(PIN_LCD_LED, 0); g_appCoreState.uiState.modeSequenceIndex = MODE_SEQUENCE_COUNT - 1; } g_appCoreState.uiState.state = state; g_appCoreState.uiState.isUpdateNeeded = true; } void checkBoilerItem(BoilerItemState& boilerItemState, UiState uiState) { if (!boilerItemState.isActive && boilerItemState.current != TEMP_T_INVALID && boilerItemState.current <= boilerItemState.setting - TEMP_TRIGGER) { boilerItemState.isActive = true; uiState.isUpdateNeeded = true; } else if (boilerItemState.isActive && (boilerItemState.current == TEMP_T_INVALID || boilerItemState.current >= boilerItemState.setting)) { boilerItemState.isActive = false; uiState.isUpdateNeeded = true; } } void AppCore::setup() { g_sensors.begin(); g_sensors.getAddress(g_sensor1, 0); g_sensors.setWaitForConversion(false); 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); bool allButtonsPressed = true; for (unsigned i = 0; i < BUTTONS_COUNT; ++i) { allButtonsPressed = allButtonsPressed && g_buttons[i]->isPressed(); } if (!allButtonsPressed) { g_storage.load(g_appCoreState); } else { g_storage.save(g_appCoreState); } } void AppCore::loop() { const auto& currentMs = millis(); if (currentMs - g_appCoreState.appState.lastSensorRequestMs >= SENSORS_CHECK_INTERVAL) { g_appCoreState.appState.lastSensorRequestMs = currentMs; g_appCoreState.appState.hasReadSensors = false; g_sensors.requestTemperaturesByAddress(g_sensor1); } if (currentMs - g_appCoreState.appState.lastSensorRequestMs >= SENSORS_REQUEST_DELAY && !g_appCoreState.appState.hasReadSensors) { g_appCoreState.appState.hasReadSensors = true; auto raw = g_sensors.getTempC(g_sensor1); temp_t temp = TEMP_T_INVALID; if (raw != DEVICE_DISCONNECTED_C) { temp = (temp_t) (raw * 2); } if (temp != g_appCoreState.appState.water.current) { g_appCoreState.appState.water.current = temp; g_appCoreState.uiState.isUpdateNeeded = true; } } for (unsigned i = 0; i < BUTTONS_COUNT; ++i) { g_buttons[i]->read(); if (g_buttons[i]->isPressed()) { g_appCoreState.uiState.lastOpMs = currentMs; break; } } if (g_appCoreState.uiState.state == Hibernate) { for (unsigned i = 0; i < BUTTONS_COUNT; ++i) { if (g_buttons[i]->wasReleased()) { setState(g_appCoreState, Lighting); break; } } } else { if (currentMs - g_appCoreState.uiState.lastOpMs >= HIBERNATE_DELAY) { setState(g_appCoreState, Hibernate); } else { if (g_btnMode.wasReleased()) { g_appCoreState.uiState.modeSequenceIndex = (g_appCoreState.uiState.modeSequenceIndex + 1) % MODE_SEQUENCE_COUNT; setState(g_appCoreState, g_modeSequence[g_appCoreState.uiState.modeSequenceIndex]); } else if (g_btnMinus.wasReleased() || g_btnPlus.wasReleased()) { BoilerItemState* itemState = NULL; if (g_appCoreState.uiState.state == WaterSetting) { itemState = &g_appCoreState.appState.water; } else if (g_appCoreState.uiState.state == HeaterSetting) { itemState = &g_appCoreState.appState.heater; } if (itemState) { if (g_btnMinus.wasReleased()) { --itemState->setting; } else if (g_btnPlus.wasReleased()) { ++itemState->setting; } g_appCoreState.uiState.isUpdateNeeded = true; } } } } checkBoilerItem(g_appCoreState.appState.water, g_appCoreState.uiState); checkBoilerItem(g_appCoreState.appState.heater, g_appCoreState.uiState); if (g_appCoreState.uiState.isUpdateNeeded) { printState(g_appCoreState, g_lcd); g_appCoreState.uiState.isUpdateNeeded = false; } }