#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 #define BUTTONS_COUNT (sizeof(g_buttons) / sizeof(*g_buttons)) #define MODE_SEQUENCE_COUNT (sizeof(m_modeSequence) / sizeof(*m_modeSequence)) AppCore::AppCore() : m_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 } } , m_modeSequence{WaterSetting, HeaterSetting, Lighting} , m_btnMode{PIN_BTN_MODE} , m_btnMinus{PIN_BTN_MINUS} , m_btnPlus{PIN_BTN_PLUS} , m_buttons{&m_btnMode, &m_btnMinus, &m_btnPlus} , m_lcd{PIN_LCD_RS, PIN_LCD_ENABLE, PIN_LCD_D0, PIN_LCD_D1, PIN_LCD_D2, PIN_LCD_D3} , m_oneWire{PIN_ONEWIRE} , m_sensors{&m_oneWire} , m_sensor1{0} { } void AppCore::setup() { m_sensors.begin(); m_sensors.getAddress(m_sensor1, 0); m_sensors.setWaitForConversion(false); for (auto& g_button : m_buttons) { g_button->begin(); } pinMode(PIN_LCD_LED, OUTPUT); digitalWrite(PIN_LCD_LED, 1); m_lcd.begin(16, 2); byte sensorChar[8] = { B00100, B01110, B01110, B01110, B01110, B11111, B11111, B01110, }; m_lcd.createChar(LCD_CHAR_SENSOR, sensorChar); Serial.begin(9600); bool allButtonsPressed = true; for (auto& g_button : m_buttons) { allButtonsPressed = allButtonsPressed && g_button->isPressed(); } if (!allButtonsPressed) { m_storage.load(m_appCoreState); } else { m_storage.save(m_appCoreState); } } void AppCore::loop() { const auto& currentMs = millis(); if (currentMs - m_appCoreState.appState.lastSensorRequestMs >= SENSORS_CHECK_INTERVAL) { m_appCoreState.appState.lastSensorRequestMs = currentMs; m_appCoreState.appState.hasReadSensors = false; m_sensors.requestTemperaturesByAddress(m_sensor1); } if (currentMs - m_appCoreState.appState.lastSensorRequestMs >= SENSORS_REQUEST_DELAY && !m_appCoreState.appState.hasReadSensors) { m_appCoreState.appState.hasReadSensors = true; auto raw = m_sensors.getTempC(m_sensor1); temp_t temp = TEMP_T_INVALID; if (raw != DEVICE_DISCONNECTED_C) { temp = (temp_t) (raw * 2); } if (temp != m_appCoreState.appState.water.current) { m_appCoreState.appState.water.current = temp; m_appCoreState.uiState.isUpdateNeeded = true; } } for (auto& pButton : m_buttons) { pButton->read(); if (pButton->isPressed()) { m_appCoreState.uiState.lastOpMs = currentMs; break; } } if (m_appCoreState.uiState.state == Hibernate) { for (auto& pButton : m_buttons) { if (pButton->wasReleased()) { setState(Lighting); break; } } } else { if (currentMs - m_appCoreState.uiState.lastOpMs >= HIBERNATE_DELAY) { setState(Hibernate); } else { if (m_btnMode.wasReleased()) { m_appCoreState.uiState.modeSequenceIndex = (m_appCoreState.uiState.modeSequenceIndex + 1) % MODE_SEQUENCE_COUNT; setState(m_modeSequence[m_appCoreState.uiState.modeSequenceIndex]); } else if (m_btnMinus.wasReleased() || m_btnPlus.wasReleased()) { BoilerItemState* itemState = nullptr; if (m_appCoreState.uiState.state == WaterSetting) { itemState = &m_appCoreState.appState.water; } else if (m_appCoreState.uiState.state == HeaterSetting) { itemState = &m_appCoreState.appState.heater; } if (itemState) { if (m_btnMinus.wasReleased()) { --itemState->setting; } else if (m_btnPlus.wasReleased()) { ++itemState->setting; } m_appCoreState.uiState.isUpdateNeeded = true; } } } } checkBoilerItem(m_appCoreState.appState.water); checkBoilerItem(m_appCoreState.appState.heater); if (m_appCoreState.uiState.isUpdateNeeded) { printState(); m_appCoreState.uiState.isUpdateNeeded = false; } } void AppCore::setState( UiStateEnum state ) { char tmp[50]; snprintf(tmp, sizeof(tmp), "Changing state %i => %i", m_appCoreState.uiState.state, state); Serial.println(tmp); if (state == Lighting) { digitalWrite(PIN_LCD_LED, 1); } else if (state == Hibernate) { digitalWrite(PIN_LCD_LED, 0); m_appCoreState.uiState.modeSequenceIndex = MODE_SEQUENCE_COUNT - 1; m_storage.save(m_appCoreState); } m_appCoreState.uiState.state = state; m_appCoreState.uiState.isUpdateNeeded = true; } void AppCore::checkBoilerItem( BoilerItemState& boilerItemState ) { if (!boilerItemState.isActive && boilerItemState.current != TEMP_T_INVALID && boilerItemState.current <= boilerItemState.setting - TEMP_TRIGGER) { boilerItemState.isActive = true; m_appCoreState.uiState.isUpdateNeeded = true; } else if (boilerItemState.isActive && (boilerItemState.current == TEMP_T_INVALID || boilerItemState.current >= boilerItemState.setting)) { boilerItemState.isActive = false; m_appCoreState.uiState.isUpdateNeeded = true; } } void AppCore::printState() { Serial.println("Updating display"); m_lcd.setCursor(0, 0); printStateLine('S', m_appCoreState.appState.water, m_appCoreState.uiState.state == WaterSetting, m_appCoreState.appState.water.isActive); m_lcd.setCursor(0, 1); printStateLine('C', m_appCoreState.appState.heater, m_appCoreState.uiState.state == HeaterSetting, m_appCoreState.appState.heater.isActive); } void AppCore::printStateLine( char prefix , const BoilerItemState& boilerItemState , bool isModifying , bool isActive ) { char curTmp[7], setTmp[7], tmp[17]; tempToStr(curTmp, boilerItemState.current); tempToStr(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; m_lcd.print(tmp); } void AppCore::tempToStr( 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); } }