#include "AppCore.h" #include "Logs.h" #define HIBERNATE_DELAY 5000 #define SENSORS_CHECK_INTERVAL 2000 #define SENSORS_REQUEST_DELAY 750 #define TEMP_TRIGGER ((temp_t)(5 * 10.0f)) #define TEMP_INTERVAL ((temp_t)(0.5 * 10.0f)) #define LCD_CHAR_SENSOR 1 #define PIN_ONEWIRE A0 #define PIN_RELAY1 A1 #define PIN_RELAY2 A2 #define PIN_BTN_CANCEL 9 #define PIN_BTN_OK 10 #define PIN_BTN_MINUS 11 #define PIN_BTN_PLUS 12 #define PIN_LCD_LED 2 #define PIN_LCD_RS 8 #define PIN_LCD_ENABLE 7 #define PIN_LCD_D4 6 #define PIN_LCD_D5 5 #define PIN_LCD_D6 4 #define PIN_LCD_D7 3 #define BUTTONS_COUNT (sizeof(g_buttons) / sizeof(*g_buttons)) #define MODE_SEQUENCE_COUNT (sizeof(m_modeSequence) / sizeof(*m_modeSequence)) AppCore::AppCore() : m_appCoreState(new AppCoreState{ .appState = { .lastSensorRequestMs = 0, .hasReadSensors = true, .water = { .current = TEMP_T_INVALID, .setting = 0, .isActive = false, .pinNo = PIN_RELAY1 }, .heater = { .current = TEMP_T_INVALID, .setting = 0, .isActive = false, .pinNo = PIN_RELAY2 } }, .uiState = { .state = Lighting, .lastOpMs = 0, .modeSequenceIndex = MODE_SEQUENCE_COUNT - 1, .isUpdateNeeded = true } }) , m_modeSequence{WaterSetting, HeaterSetting, Lighting} , m_pBtnCancel(new Button{PIN_BTN_CANCEL}) , m_pBtnOk(new Button{PIN_BTN_OK}) , m_pBtnMinus(new Button{PIN_BTN_MINUS}) , m_pBtnPlus(new Button{PIN_BTN_PLUS}) , m_pButtons{m_pBtnOk, m_pBtnMinus, m_pBtnPlus} , m_pLcd(new LiquidCrystal{PIN_LCD_RS, PIN_LCD_ENABLE, PIN_LCD_D4, PIN_LCD_D5, PIN_LCD_D6, PIN_LCD_D7}) , m_pOneWire(new OneWire{PIN_ONEWIRE}) , m_pSensors(new DallasTemperature{m_pOneWire}) , m_sensor1{0} , m_sensor2{0} { } void AppCore::setup() { Serial.begin(9600); LOG_FN_BEGIN(1); m_pSensors->begin(); LOG(5, "Found %i sensors", m_pSensors->getDeviceCount()); m_pSensors->getAddress(m_sensor1, 0); m_pSensors->getAddress(m_sensor2, 1); m_pSensors->setWaitForConversion(false); for (auto& g_button : m_pButtons) { g_button->begin(); } pinMode(PIN_RELAY1, OUTPUT); digitalWrite(PIN_RELAY1, LOW); pinMode(PIN_RELAY2, OUTPUT); digitalWrite(PIN_RELAY2, LOW); pinMode(PIN_LCD_LED, OUTPUT); digitalWrite(PIN_LCD_LED, 1); m_pLcd->begin(16, 2); byte sensorChar[8] = { B00100, B01110, B01110, B01110, B01110, B11111, B11111, B01110, }; m_pLcd->createChar(LCD_CHAR_SENSOR, sensorChar); bool allButtonsPressed = true; for (auto& g_button : m_pButtons) { g_button->read(); allButtonsPressed = allButtonsPressed && g_button->isPressed(); } if (!allButtonsPressed) { LOG(5, "%s: Loading settings", __FUNCTION__); m_storage.load(*m_appCoreState); } else { LOG(5, "%s: Resetting settings", __FUNCTION__); m_storage.save(*m_appCoreState); m_pLcd->clear(); m_pLcd->setCursor(6, 0); m_pLcd->print("Reset"); bool allButtonsPressed = true; while (allButtonsPressed) { for (auto& g_button : m_pButtons) { g_button->read(); allButtonsPressed = allButtonsPressed && g_button->isPressed(); } } } LOG_FN_END(1); } void AppCore::loop() { LOG_FN_BEGIN(50); const auto& currentMs = millis(); if (currentMs - m_appCoreState->appState.lastSensorRequestMs >= SENSORS_CHECK_INTERVAL) { m_appCoreState->appState.lastSensorRequestMs = currentMs; m_appCoreState->appState.hasReadSensors = false; m_pSensors->requestTemperaturesByAddress(m_sensor1); m_pSensors->requestTemperaturesByAddress(m_sensor2); } if (currentMs - m_appCoreState->appState.lastSensorRequestMs >= SENSORS_REQUEST_DELAY && !m_appCoreState->appState.hasReadSensors) { m_appCoreState->appState.hasReadSensors = true; readAndUpdateSensors(&m_appCoreState->appState.water, m_sensor1); readAndUpdateSensors(&m_appCoreState->appState.heater, m_sensor2); checkBoilerItem(&m_appCoreState->appState.water); checkBoilerItem(&m_appCoreState->appState.heater); } for (auto& pButton : m_pButtons) { pButton->read(); if (pButton->isPressed()) { m_appCoreState->uiState.lastOpMs = currentMs; break; } } if (m_appCoreState->uiState.state == Hibernate) { for (auto& pButton : m_pButtons) { if (pButton->wasReleased()) { setState(Lighting); break; } } } else { if (currentMs - m_appCoreState->uiState.lastOpMs >= HIBERNATE_DELAY) { setState(Hibernate); } else { if (m_pBtnOk->wasReleased()) { m_appCoreState->uiState.modeSequenceIndex = (m_appCoreState->uiState.modeSequenceIndex + 1) % MODE_SEQUENCE_COUNT; setState(m_modeSequence[m_appCoreState->uiState.modeSequenceIndex]); } else if (m_pBtnMinus->wasReleased() || m_pBtnPlus->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_pBtnMinus->wasReleased()) { itemState->setting -= TEMP_INTERVAL; } else if (m_pBtnPlus->wasReleased()) { itemState->setting += TEMP_INTERVAL; } LOG(1, "Setting temp to %i (%i)", itemState->setting, TEMP_INTERVAL); m_appCoreState->uiState.isUpdateNeeded = true; } } } } if (m_appCoreState->uiState.isUpdateNeeded) { printState(); } LOG_FN_END(50); } void AppCore::setState( UiStateEnum state ) { LOG_FN_BEGIN(1); LOG(5, "Changing state %i => %i", m_appCoreState->uiState.state, state); 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; LOG_FN_END(1); } void AppCore::checkBoilerItem( BoilerItemState* boilerItemState ) { LOG_FN_BEGIN(2); if (!boilerItemState->isActive && boilerItemState->current != TEMP_T_INVALID && boilerItemState->current <= boilerItemState->setting - TEMP_TRIGGER) { boilerItemState->isActive = true; digitalWrite(boilerItemState->pinNo, HIGH); m_appCoreState->uiState.isUpdateNeeded = true; } else if (boilerItemState->isActive && (boilerItemState->current == TEMP_T_INVALID || boilerItemState->current >= boilerItemState->setting)) { boilerItemState->isActive = false; digitalWrite(boilerItemState->pinNo, LOW); m_appCoreState->uiState.isUpdateNeeded = true; } LOG_FN_END(2); } void AppCore::readAndUpdateSensors( BoilerItemState* boilerItemState , const uint8_t* sensor ) { LOG_FN_BEGIN(2); auto raw = m_pSensors->getTempC(sensor); temp_t temp = TEMP_T_INVALID; if (raw != DEVICE_DISCONNECTED_C) { temp = (temp_t) (raw * 10); } if (temp != boilerItemState->current) { boilerItemState->current = temp; m_appCoreState->uiState.isUpdateNeeded = true; } LOG_FN_END(2); } void AppCore::printState() { LOG_FN_BEGIN(2); m_pLcd->setCursor(0, 0); printStateLine('S', &m_appCoreState->appState.water, m_appCoreState->uiState.state == WaterSetting, m_appCoreState->appState.water.isActive); m_pLcd->setCursor(0, 1); printStateLine('C', &m_appCoreState->appState.heater, m_appCoreState->uiState.state == HeaterSetting, m_appCoreState->appState.heater.isActive); m_appCoreState->uiState.isUpdateNeeded = false; LOG_FN_END(2); } void AppCore::printStateLine( char prefix , const BoilerItemState* boilerItemState , bool isModifying , bool isActive ) { LOG_FN_BEGIN(2); char curTmp[7], setTmp[7], tmp[17]; tempToStr(curTmp, boilerItemState->current, 5); tempToStr(setTmp, boilerItemState->setting, 4); 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_pLcd->print(tmp); LOG_FN_END(2); } void AppCore::tempToStr( char* out , temp_t temp , signed char width ) { LOG_FN_BEGIN(2); LOG(5, "%s: temp=%i", __FUNCTION__, (int)temp); if (temp == TEMP_T_INVALID) { strcpy(out, " --.-"); } else { dtostrf(temp / 10.0f, width, 1, out); } LOG_FN_END(2); }