#ifdef FIRMWARE_VARIANT_HEATCONTROL void heatcontrol_switchPump(bool _state) { if (heatcontrol_out_pump && !_state) { // pump is running and is now switched off // -> set heatcontrol_pump_lastRunMillis heatcontrol_pump_lastRunMillis = millis(); heatcontrol_pump_inForceRunMode = false; } heatcontrol_out_pump = _state; pcf8574.digitalWrite(P6, !_state); // output active low! char _statName[5]; if (_state) sprintf(_statName, PGMStr_ON); else sprintf(_statName, PGMStr_OFF); sprintf_P(logBuf, PGMStr_heatc_pumpSwitchedTo, PGMStr_heatc_logName, _statName); sendLog(logBuf, LOGLEVEL_INFO); heatcontrol_publish_out_pump(); } void heatcontrol_switchHeating(bool _state) { heatcontrol_out_heat = _state; pcf8574.digitalWrite(P7, !_state); // output active low! char _statName[5]; if (_state) sprintf(_statName, PGMStr_ON); else sprintf(_statName, PGMStr_OFF); sprintf_P(logBuf, PGMStr_heatc_heatingSwitchedTo, PGMStr_heatc_logName, _statName); sendLog(logBuf, LOGLEVEL_INFO); heatcontrol_publish_out_heat(); } // Pump Backlash void heatcontrol_pumpBacklash_begin() { heatcontrol_pumpBacklash_startMillis = millis(); heatcontrol_state_pumpBacklash_active = true; sprintf_P(logBuf, PGMStr_heatc_pumpBacklashStarted, PGMStr_heatc_logName); sendLog(logBuf, LOGLEVEL_INFO); heatcontrol_publish_pumpBacklash(); } void heatcontrol_pumpBacklash_cancel() { heatcontrol_pumpBacklash_startMillis = 0; heatcontrol_state_pumpBacklash_active = false; heatcontrol_publish_pumpBacklash(); sprintf_P(logBuf, PGMStr_heatc_pumpBacklashCanceled, PGMStr_heatc_logName); sendLog(logBuf, LOGLEVEL_INFO); } void heatcontrol_pumpBacklash_handleTimeout() { // switch off pump after backlash time has exceeded if (heatcontrol_pumpBacklash_startMillis > 0) { unsigned long _pumpBacklashMillis; if (heatcontrol_pump_inForceRunMode) { _pumpBacklashMillis = confHeatc.pumpTime_forceRun * 60000; } else { _pumpBacklashMillis = confHeatc.pumpBacklash * 60000; } if ((millis() - heatcontrol_pumpBacklash_startMillis) > _pumpBacklashMillis) { sprintf_P(logBuf, PGMStr_heatc_pumpBacklashFinished, PGMStr_heatc_logName); sendLog(logBuf, LOGLEVEL_INFO); heatcontrol_switchPump(false); // pump OFF heatcontrol_pumpBacklash_startMillis = 0; heatcontrol_state_pumpBacklash_active = false; heatcontrol_publish_pumpBacklash(); display_updateImmediately = true; } } } uint8_t heatcontrol_pumpBacklash_getRemainingMins() { if(heatcontrol_pumpBacklash_startMillis > 0) { unsigned long _pumpBacklashMillis = confHeatc.pumpBacklash * 60000; unsigned long _elapsedMillis = (millis() - heatcontrol_pumpBacklash_startMillis); unsigned long _remainingMinutes = (_pumpBacklashMillis - _elapsedMillis) / 60000; return _remainingMinutes + 1; } else return 0; } void heatcontrol_lockTime_begin() { if (heatcontrol_skipLocktimeOnce) { // if last turned on by test mode -> no lock time after that heatcontrol_skipLocktimeOnce = false; } else { uint8_t _lockTimeMinutes; pcf8574.digitalWrite(P1, LOW); // output active low! // get current lock time depending on outside temp int _tOut = (int)owTemp_out; if (_tOut <= -20) _lockTimeMinutes = confHeatc.heatLocktime[0]; else if (_tOut <= -15) _lockTimeMinutes = confHeatc.heatLocktime[1]; else if (_tOut <= -10) _lockTimeMinutes = confHeatc.heatLocktime[2]; else if (_tOut <= -5) _lockTimeMinutes = confHeatc.heatLocktime[3]; else if (_tOut <= 0) _lockTimeMinutes = confHeatc.heatLocktime[4]; else if (_tOut <= 5) _lockTimeMinutes = confHeatc.heatLocktime[5]; else if (_tOut <= 10) _lockTimeMinutes = confHeatc.heatLocktime[6]; else if (_tOut <= 15) _lockTimeMinutes = confHeatc.heatLocktime[7]; else if (_tOut <= 20) _lockTimeMinutes = confHeatc.heatLocktime[8]; else if (_tOut > 20) _lockTimeMinutes = confHeatc.heatLocktime[8]; // also use value for <=20 for >20 else _lockTimeMinutes = 0; #ifdef DEBUGMODE // i dont like to wait during development if (_lockTimeMinutes == 0) _lockTimeMinutes = 1; #endif heatcontrol_lockTime_startMillis = millis(); heatcontrol_lockTime_totalMillis = _lockTimeMinutes * 60000; heatcontrol_lastState_lockActive = true; sprintf_P(logBuf, PGMStr_heatc_locktimeStart, PGMStr_heatc_logName, _lockTimeMinutes); sendLog(logBuf, LOGLEVEL_INFO); heatcontrol_publish_lockTime(); heatcontrol_publish_lockActive(); display_updateImmediately = true; } } void heatcontrol_lockTime_handleTimeout() { // handle lock time if (heatcontrol_lockTime_startMillis > 0) { if ((millis() - heatcontrol_lockTime_startMillis) > heatcontrol_lockTime_totalMillis) { // locktime is over heatcontrol_lockTime_startMillis = 0; heatcontrol_lockTime_totalMillis = 0; heatcontrol_lock_release(); sprintf_P(logBuf, PGMStr_heatc_locktimeStop, PGMStr_heatc_logName); sendLog(logBuf, LOGLEVEL_INFO); heatcontrol_publish_lockTime(); heatcontrol_publish_lockActive(); display_updateImmediately = true; } } else { heatcontrol_lock_release(); } } uint8_t heatcontrol_lockTime_getRemainingMins() { if(heatcontrol_lockTime_startMillis > 0) { unsigned long _elapsedMillis = (millis() - heatcontrol_lockTime_startMillis); unsigned long _remainingMinutes = (heatcontrol_lockTime_totalMillis - _elapsedMillis) / 60000; return _remainingMinutes + 1; } else return 0; } void heatcontrol_lock_release() { bool _currStat = heatcontrol_getLockActive(); if(!_currStat) { pcf8574.digitalWrite(P1, HIGH); // output active low! if (_currStat != heatcontrol_lastState_lockActive) { heatcontrol_publish_lockActive(); heatcontrol_lastState_lockActive = _currStat; } } } bool heatcontrol_getLockActive() { if (heatcontrol_lockTime_startMillis == 0 && !heatcontrol_lockTemp_active()) return false; else return true; } bool heatcontrol_lockTemp_active() { int _tFeed = (int)owTemp_feed; if (!confHeatc.useHeatCurve) return false; else if (_tFeed <= (heatcontrol_currentHeatCurveValue - confHeatc.hystereseOn)) { return false; } else { pcf8574.digitalWrite(P1, LOW); // output active low! // achtung published zu oft //////if (!heatcontrol_lastState_lockActive) { ////// heatcontrol_publish_lockActive(); ////// heatcontrol_lastState_lockActive = true; //////} return true; } } void heatcontrol_mainFunction() { // stop testmode after timeout if (heatcontrol_testmode_startMillis > 0) { unsigned long _testmodeMillis = HEATCONTROL_TESTMODE_MINUTES * 60000; if ((millis() - heatcontrol_testmode_startMillis) > _testmodeMillis) { heatcontrol_testMode_off(); //heatcontrol_testmode_startMillis = 0; // is already done in heatcontrol_testMode_off() } } // really start testmode if it was set // MUST be separate if and not included above, or it will instantly switch back on after timeout!! if (heatcontrol_testmode_startMillis > 0) { if ((millis() - heatcontrol_testmode_startMillis) > 2800 && !heatcontrol_state_testmode_active) { heatcontrol_testMode_reallyStart(); //heatcontrol_testmode_startMillis = 0; // is already done in heatcontrol_testMode_reallyStart() } } heatcontrol_lockTime_handleTimeout(); int _tOut = (int)owTemp_out; int _tFeed = (int)owTemp_feed; //int _tReturn = (int)owTemp_return; // heat curve - get current value if enabled if (confHeatc.useHeatCurve) { if (_tOut <= -20) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[0]; else if (_tOut <= -15) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[1]; else if (_tOut <= -10) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[2]; else if (_tOut <= -5) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[3]; else if (_tOut <= 0) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[4]; else if (_tOut <= 5) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[5]; else if (_tOut <= 10) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[6]; else if (_tOut <= 15) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[7]; else if (_tOut <= 20) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[8]; else if (_tOut > 20) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[8]; // also use value for <=20 for >20 else heatcontrol_currentHeatCurveValue = 0; } else heatcontrol_currentHeatCurveValue = 0; #ifdef DEBUGMODE // during development i prefer working in a heated room // so i need this to be able to do some tests ;-) if (confHeatc.useHeatCurve && heatcontrol_currentHeatCurveValue == 0) heatcontrol_currentHeatCurveValue = 35; #endif // switch heating and pump if requested // only if currently heating is inactive if (!heatcontrol_out_heat) { // check pre conditions bool _switchOnPreCondition = false; // no locktime is currently active (and usage of lock time is not disabled) if (heatcontrol_lockTime_startMillis == 0) _switchOnPreCondition = true; // overrule precondition to false if outside temp is above limit if ( _tOut >= confHeatc.disableAboveTOut) _switchOnPreCondition = false; // overrule precondition if testmode is requested if (heatcontrol_state_testmode_active) _switchOnPreCondition = true; if (_switchOnPreCondition) { // check if any switch-on condition is met bool _switchOnCondition = false; // test mode has been activated if (heatcontrol_state_testmode_active) { sprintf_P(logBuf, PGMStr_heatc_switchOnCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOnCondition_heatTestmode); sendLog(logBuf); _switchOnCondition = true; } // heat request, heat curve enabled AND temp_feed has fallen below treshold else if (heatcontrol_in_heat_request && confHeatc.useHeatCurve && !heatcontrol_lockTemp_active() ) { sprintf_P(logBuf, PGMStr_heatc_switchOnCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOnCondition_heatRequestHeatcurve); sendLog(logBuf); _switchOnCondition = true; } // heat request, heat curve disabled else if (heatcontrol_in_heat_request && !confHeatc.useHeatCurve) { sprintf_P(logBuf, PGMStr_heatc_switchOnCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOnCondition_heatRequest); sendLog(logBuf); _switchOnCondition = true; } // heat curve forced / heat request ignored // and current feed-temperature <= (currentHeatCurveValue - hystereseOn) else if ( confHeatc.useHeatCurve && confHeatc.forceHeatCurve && confHeatc.forceHeatCurveAlsoForSwitchOn && confHeatc.hystereseOn >= 3 && _tFeed <= (heatcontrol_currentHeatCurveValue - confHeatc.hystereseOn) ) { sprintf_P(logBuf, PGMStr_heatc_switchOnCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOnCondition_heatCurveForced); sendLog(logBuf); _switchOnCondition = true; } if(_switchOnCondition) { if (!heatcontrol_out_pump && !heatcontrol_in_sw_disableControl_heating) heatcontrol_switchPump(true); // pump ON if (!heatcontrol_out_heat && !heatcontrol_in_sw_disableControl_pump) heatcontrol_switchHeating(true); // heating ON if (!heatcontrol_in_sw_disableControl_pump) heatcontrol_pumpBacklash_cancel(); // if pump is in backlash - avoid it going off during heating is running display_updateImmediately = true; } } } // heating is currently switched on else if ( heatcontrol_out_heat ) { // check if any switch-off condition is met bool _switchOffCondition = false; // heat request and/or testmode has stopped and // useHeatCurve and/or at least forceHeatCurve is disabled // note - if confHeatc.forceHeatCurve == true switching off is ONLY handled when heat curve target is reached! if ( (!confHeatc.useHeatCurve || (confHeatc.useHeatCurve && !confHeatc.forceHeatCurve) ) && !heatcontrol_in_heat_request && !heatcontrol_state_testmode_active) { sprintf_P(logBuf, PGMStr_heatc_switchOffCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOffCondition_heatRequest); sendLog(logBuf); _switchOffCondition = true; } // test mode is NOT active, // heatcurve is enabled and // feed temperature exceeds heat curve value ( + confHeatc.hystereseOff) else if (!heatcontrol_state_testmode_active && confHeatc.useHeatCurve && _tFeed >= (heatcontrol_currentHeatCurveValue + confHeatc.hystereseOff) ) { sprintf_P(logBuf, PGMStr_heatc_switchOffCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOffCondition_heatCurve); sendLog(logBuf); _switchOffCondition = true; } // feed temperature exceeds safety limit else if ((int)owTemp_feed >= confHeatc.tempFeedLimit) { sprintf_P(logBuf, PGMStr_heatc_switchOffCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOffCondition_tempFeedLimit); sendLog(logBuf); _switchOffCondition = true; } if (_switchOffCondition) { // --> switch heating off // --> switch pump in backlash heatcontrol_switchHeating(false); // heating OFF heatcontrol_pumpBacklash_begin(); // pump - enable backlash heatcontrol_lockTime_begin(); display_updateImmediately = true; } } else { // just for safety --> switch heating off in all other cases if(heatcontrol_out_heat) heatcontrol_switchHeating(false); // heating OFF } // pump off if in backlash mode after timeout if (heatcontrol_state_pumpBacklash_active) { heatcontrol_pumpBacklash_handleTimeout(); } } // TEST MODE void heatcontrol_testMode_toggle() { if(!heatcontrol_state_testmode_started) { heatcontrol_testMode_on(); } else { heatcontrol_testMode_off(); } } void heatcontrol_testMode_on() { heatcontrol_state_testmode_started = true; heatcontrol_state_testmode_active = false; // will be set by heatcontrol_testMode_reallyStart() heatcontrol_testmode_startMillis = millis(); // Status-LED TESTMODE -> ON heatcontrol_out_stat_testmode = LOW; // LOW = LED ON pcf8574.digitalWrite(P0, heatcontrol_out_stat_testmode); heatcontrol_publish_testMode(); char _dispMsg[17]; snprintf_P(_dispMsg, 17, PGMStr_heatc_display_testModeON); display_showRow2OverlayMessage((char*)_dispMsg); } void heatcontrol_testMode_off() { heatcontrol_state_testmode_started = false; heatcontrol_state_testmode_active = false; heatcontrol_testmode_startMillis = 0; heatcontrol_skipLocktimeOnce = true; // Status-LED TESTMODE -> OFF heatcontrol_out_stat_testmode = HIGH; // HIGH = LED OFF pcf8574.digitalWrite(P0, heatcontrol_out_stat_testmode); heatcontrol_publish_testMode(); //heatcontrol_switchPump(false); //heatcontrol_switchHeating(false); char _dispMsg[17]; snprintf_P(_dispMsg, 17, PGMStr_heatc_display_testModeOFF); display_showRow2OverlayMessage((char*)_dispMsg); sprintf_P(logBuf, PGMStr_heatc_testmodeStop, PGMStr_heatc_logName); sendLog(logBuf); } void heatcontrol_testMode_reallyStart() { heatcontrol_state_testmode_started = true; heatcontrol_state_testmode_active = true; heatcontrol_testmode_startMillis = millis(); display_updateImmediately = true; sprintf_P(logBuf, PGMStr_heatc_testmodeStart, PGMStr_heatc_logName); sendLog(logBuf); } uint8_t heatcontrol_testMode_getRemainingMins() { if(heatcontrol_testmode_startMillis > 0) { unsigned long _testmodeMillis = HEATCONTROL_TESTMODE_MINUTES * 60000; unsigned long _elapsedMillis = (millis() - heatcontrol_testmode_startMillis); unsigned long _remainingMinutes = (_testmodeMillis - _elapsedMillis) / 60000; return _remainingMinutes + 1; } else return 0; } void heatcontrol_pump_forceRunAfterTimeout() { if (!heatcontrol_pump_inForceRunMode) { bool _runCondition = false; #ifdef DEBUGMODE // use minutes rather than hours for testing // pump has not run since system start and forceRunInterval is exceeded if (heatcontrol_pump_lastRunMillis == 0 && millis() >= (confHeatc.pump_forceRunInterval * 60000) ) { _runCondition = true; } // pump last run longer ago than forceRunInterval if (heatcontrol_pump_lastRunMillis > 0 && (millis() - heatcontrol_pump_lastRunMillis) >= (confHeatc.pump_forceRunInterval * 60000) ) { _runCondition = true; } #else // normal operation - interval is set in hours // pump has not run since system start and forceRunInterval is exceeded if (heatcontrol_pump_lastRunMillis == 0 && millis() >= (confHeatc.pump_forceRunInterval * 3600000) ) { _runCondition = true; } // pump last run longer ago than forceRunInterval if (heatcontrol_pump_lastRunMillis > 0 && (millis() - heatcontrol_pump_lastRunMillis) >= (confHeatc.pump_forceRunInterval * 3600000) ) { _runCondition = true; } #endif bool _runCondition_inTimeWindow = false; if (_runCondition) { sprintf_P(logBuf, PGMStr_heatc_pumpForceRun, PGMStr_heatc_logName); sendLog(logBuf); #ifdef ENABLE_FEATURE_NTP_TIME if (confTime.ntpEnable && confHeatc.pump_forceRunTimeBegin != 0 && confHeatc.pump_forceRunTimeBegin != 0) { updateTime(); if (lt.tm_year > 70) { // check if time has been synced // check if we are in the configured time window if (lt.tm_hour >= confHeatc.pump_forceRunTimeBegin && lt.tm_hour < confHeatc.pump_forceRunTimeBegin) { _runCondition_inTimeWindow = true; } } else { // NTP time is not synced so always assume we are in the time window _runCondition_inTimeWindow = true; } } else { // NTP is disabled in so always assume we are in the time window _runCondition_inTimeWindow = true; } #else // NTP is not compiled in so always assume we are in the time window _runCondition_inTimeWindow = true; #endif if (_runCondition_inTimeWindow) { heatcontrol_pump_inForceRunMode = true; heatcontrol_switchPump(true); heatcontrol_pumpBacklash_begin(); } } } } #endif // FIRMWARE_VARIANT_HEATCONTROL