#include "AppCore.h" #include "Logs.h" #define xstr(s) str(s) #define str(s) #s #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_RELAY2 A1 #define PIN_RELAY1 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 UiStateEnum AppCore::s_menuSequence[4]{MenuWaterSetting, MenuHeaterSetting, MenuTempTrigger, MenuVersion}; 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 }, .tempTrigger = TEMP_TRIGGER, .pCurrentSettingEdit = nullptr, .currentSettingEditTmp = 0 }, .uiState = { .state = HomeLighting, .lastOpMs = 0, .isUpdateNeeded = true } }) , 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_pBtnCancel, 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 == HomeHibernate) { for (auto& pButton : m_pButtons) { if (pButton->wasReleased()) { setState(HomeLighting); break; } } } else { if (currentMs - m_appCoreState->uiState.lastOpMs >= HIBERNATE_DELAY) { setState(HomeHibernate); } else { if (m_appCoreState->uiState.state == HomeLighting) { if (m_pBtnCancel->wasReleased()) { setState(HomeHibernate); } else if (m_pBtnOk->wasReleased()) { setState(s_menuSequence[0]); } } else if (m_appCoreState->uiState.state == MenuWaterSetting || m_appCoreState->uiState.state == MenuHeaterSetting || m_appCoreState->uiState.state == MenuTempTrigger || m_appCoreState->uiState.state == MenuVersion) { if (m_pBtnCancel->wasReleased()) { setState(HomeLighting); } else if (m_pBtnOk->wasReleased()) { UiStateEnum nextState = m_appCoreState->uiState.state; if (m_appCoreState->uiState.state == MenuWaterSetting) { nextState = MenuWaterSettingEdit; } else if (m_appCoreState->uiState.state == MenuHeaterSetting) { nextState = MenuHeaterSettingEdit; } else if (m_appCoreState->uiState.state == MenuTempTrigger) { nextState = MenuTempTriggerEdit; } setState(nextState); } else if (m_pBtnMinus->wasReleased() || m_pBtnPlus->wasReleased()) { int idx = 0; while (m_appCoreState->uiState.state != s_menuSequence[idx]) { ++idx; } auto shift = m_pBtnMinus->wasReleased() ? (-1) : 1; setState(s_menuSequence[(idx + shift) % ((sizeof(s_menuSequence) / sizeof(*s_menuSequence)))]); } } else if (m_appCoreState->uiState.state == MenuWaterSettingEdit || m_appCoreState->uiState.state == MenuHeaterSettingEdit || m_appCoreState->uiState.state == MenuTempTriggerEdit) { if (m_pBtnCancel->wasReleased() || m_pBtnOk->wasReleased()) { UiStateEnum nextState = m_appCoreState->uiState.state; if (m_appCoreState->uiState.state == MenuWaterSettingEdit) { nextState = MenuWaterSetting; } else if (m_appCoreState->uiState.state == MenuHeaterSettingEdit) { nextState = MenuHeaterSetting; } else if (m_appCoreState->uiState.state == MenuTempTriggerEdit) { nextState = MenuTempTrigger; } if (m_pBtnOk->wasReleased()) { *m_appCoreState->appState.pCurrentSettingEdit = m_appCoreState->appState.currentSettingEditTmp; } m_appCoreState->appState.currentSettingEditTmp = 0; m_appCoreState->appState.pCurrentSettingEdit = nullptr; setState(nextState); } else if (m_pBtnMinus->wasReleased() || m_pBtnPlus->wasReleased()) { m_appCoreState->appState.currentSettingEditTmp += (m_pBtnMinus->wasReleased() ? -1 : 1) * 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 == HomeLighting) { digitalWrite(PIN_LCD_LED, 1); } else if (state == HomeHibernate) { digitalWrite(PIN_LCD_LED, 0); m_storage.save(*m_appCoreState); } else if (state == MenuWaterSettingEdit) { m_appCoreState->appState.currentSettingEditTmp = m_appCoreState->appState.water.setting; m_appCoreState->appState.pCurrentSettingEdit = &m_appCoreState->appState.water.setting; } else if (state == MenuHeaterSettingEdit) { m_appCoreState->appState.currentSettingEditTmp = m_appCoreState->appState.heater.setting; m_appCoreState->appState.pCurrentSettingEdit = &m_appCoreState->appState.heater.setting; } else if (state == MenuTempTriggerEdit) { m_appCoreState->appState.currentSettingEditTmp = m_appCoreState->appState.tempTrigger; m_appCoreState->appState.pCurrentSettingEdit = &m_appCoreState->appState.tempTrigger; } 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 - m_appCoreState->appState.tempTrigger) { 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); if (m_appCoreState->uiState.state == HomeLighting || m_appCoreState->uiState.state == HomeHibernate) { m_pLcd->setCursor(0, 0); printStateLine('S', &m_appCoreState->appState.water); m_pLcd->setCursor(0, 1); printStateLine('C', &m_appCoreState->appState.heater); } else if (m_appCoreState->uiState.state == MenuWaterSetting || m_appCoreState->uiState.state == MenuHeaterSetting || m_appCoreState->uiState.state == MenuTempTrigger || m_appCoreState->uiState.state == MenuWaterSettingEdit || m_appCoreState->uiState.state == MenuHeaterSettingEdit || m_appCoreState->uiState.state == MenuTempTriggerEdit) { const auto isEdit = m_appCoreState->uiState.state == MenuWaterSettingEdit || m_appCoreState->uiState.state == MenuHeaterSettingEdit || m_appCoreState->uiState.state == MenuTempTriggerEdit; const char* pTitle = nullptr; temp_t* pTemp = nullptr; if (isEdit) { pTemp = &m_appCoreState->appState.currentSettingEditTmp; } else { if (m_appCoreState->uiState.state == MenuWaterSetting || m_appCoreState->uiState.state == MenuWaterSettingEdit) { pTitle = "Sanitaire"; pTemp = &m_appCoreState->appState.water.setting; } else if (m_appCoreState->uiState.state == MenuHeaterSetting || m_appCoreState->uiState.state == MenuHeaterSettingEdit) { pTitle = "Chauffage"; pTemp = &m_appCoreState->appState.heater.setting; } else if (m_appCoreState->uiState.state == MenuTempTrigger || m_appCoreState->uiState.state == MenuTempTriggerEdit) { pTitle = "Delta"; pTemp = &m_appCoreState->appState.tempTrigger; } } char tmpStr[17], tmpTemp[7]; m_pLcd->setCursor(0, 0); snprintf(tmpStr, sizeof(tmpStr), "%s : ", pTitle); m_pLcd->print(tmpStr); m_pLcd->setCursor(0, 1); tempToStr(tmpTemp, *pTemp, 4); snprintf(tmpStr, sizeof(tmpStr), "%s%s C ", isEdit ? "> " : " ", tmpTemp); m_pLcd->print(tmpStr); } else if (m_appCoreState->uiState.state == MenuVersion) { char tmpStr[17]; m_pLcd->setCursor(0, 0); snprintf(tmpStr, sizeof(tmpStr), "%s : ", "Version"); m_pLcd->print(tmpStr); m_pLcd->setCursor(0, 1); snprintf(tmpStr, sizeof(tmpStr), "%s - %s ", xstr(APP_CORE_VERSION), xstr(APP_CORE_COMMIT)); m_pLcd->print(tmpStr); } m_appCoreState->uiState.isUpdateNeeded = false; LOG_FN_END(2); } void AppCore::printStateLine( char prefix , const BoilerItemState* boilerItemState ) { 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", prefix, curTmp, setTmp, boilerItemState->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); }