#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 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(new 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} , m_sensor2{0} { } void AppCore::setup() { Serial.begin(9600); LOG_FN_BEGIN(1); m_sensors.begin(); LOG(5, "Found %i sensors", m_sensors.getDeviceCount()); m_sensors.getAddress(m_sensor1, 0); m_sensors.getAddress(m_sensor2, 1); 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); bool allButtonsPressed = true; for (auto& g_button : m_buttons) { 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); } 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_sensors.requestTemperaturesByAddress(m_sensor1); m_sensors.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_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 -= TEMP_INTERVAL; } else if (m_btnPlus.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; 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; } LOG_FN_END(2); } void AppCore::readAndUpdateSensors( BoilerItemState* boilerItemState , const uint8_t* sensor ) { LOG_FN_BEGIN(2); auto raw = m_sensors.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_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); 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_lcd.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); }