diff --git a/data/js/app.js b/data/js/app.js index 11cc61aa..6fc7e456 100644 --- a/data/js/app.js +++ b/data/js/app.js @@ -24,7 +24,7 @@ const vueApp = Vue.createApp({ // post parameter array the same as if it was posted from a form (the values are already updated // from the v-model bindings) var formBody = []; - + this.parameters.forEach(param => { var encodedKey = encodeURIComponent(param.name); var encodedValue = encodeURIComponent(param.value); @@ -41,7 +41,7 @@ const vueApp = Vue.createApp({ cache: 'no-cache', body: formBody }; - + this.isPostingForm = true fetch("/parameters", requestOptions) @@ -74,13 +74,14 @@ const vueApp = Vue.createApp({ sectionName(sectionId) { const sectionNames = { 0: 'PID Parameters', - 1: 'Temperature and Preinfusion', - 2: 'Brew Detection and Brew PID Parameters', + 1: 'Temperature', + 2: 'Brew PID Parameters', 3: 'Brew Control', 4: 'Scale Parameters', - 5: 'Power Settings' + 5: 'Display Settings', + 6: 'Power Settings' } - + return sectionNames[sectionId] }, diff --git a/src/brewHandler.h b/src/brewHandler.h index 58e6007b..40355037 100644 --- a/src/brewHandler.h +++ b/src/brewHandler.h @@ -4,6 +4,11 @@ * @brief Handler for brewing * */ +// TODO: +// Flush Timer configurable and seperated from shottimer? +// show heating logo if steam temp isn´t reached? +// show sections on website only if needed +// add pressure to shot timer? #pragma once @@ -11,47 +16,59 @@ enum BrewSwitchState { kBrewSwitchIdle = 10, - kBrewSwitchBrew = 20, - kBrewSwitchBrewAbort = 30, - kBrewSwitchFlushOff = 31, - kBrewSwitchReset = 40 + kBrewSwitchPressed = 20, + kBrewSwitchShortPressed = 30, + kBrewSwitchLongPressed = 40, + kBrewSwitchWaitForRelease = 50 }; enum BrewState { kBrewIdle = 10, kPreinfusion = 20, - kWaitPreinfusion = 21, kPreinfusionPause = 30, - kWaitPreinfusionPause = 31, kBrewRunning = 40, - kWaitBrew = 41, - kBrewFinished = 42, - kWaitBrewOff = 43 + kBrewFinished = 50, +}; + +enum ManualFlushState { + kManualFlushIdle = 10, + kManualFlushRunning = 20, }; enum BackflushState { - kBackflushWaitBrewswitchOn = 10, - kBackflushFillingStart = 20, - kBackflushFilling = 21, - kBackflushFlushingStart = 30, - kBackflushFlushing = 31, - kBackflushWaitBrewswitchOff = 43 + kBackflushIdle = 10, + kBackflushFilling = 20, + kBackflushFlushing = 30, + kBackflushFinished = 40 }; -// Normal Brew +// Brew control states +BrewSwitchState currBrewSwitchState = kBrewSwitchIdle; BrewState currBrewState = kBrewIdle; - -uint8_t currStateBrewSwitch = LOW; -uint8_t currBrewSwitchStateMomentary = LOW; -int brewSwitchState = kBrewSwitchIdle; -boolean brewSwitchWasOff = false; - -double totalBrewTime = 0; // total brewtime set in software -double timeBrewed = 0; // total brewed time -double lastBrewTimeMillis = 0; // for shottimer delay after disarmed button -double lastBrewTime = 0; -unsigned long startingTime = 0; // start time of brew -boolean brewPIDDisabled = false; // is PID disabled for delay after brew has started? +ManualFlushState currManualFlushState = kManualFlushIdle; +BackflushState currBackflushState = kBackflushIdle; + +uint8_t brewSwitchReading = LOW; +uint8_t currReadingBrewSwitch = LOW; +bool brewSwitchWasOff = false; + +// Brew values +uint8_t featureBrewControl = FEATURE_BREW_CONTROL; // enables control of pumpe and valve +double brewTime = BREW_TIME; // brewtime in s +double preinfusion = PRE_INFUSION_TIME; // preinfusion time in s +double preinfusionPause = PRE_INFUSION_PAUSE_TIME; // preinfusion pause time in s +double totalBrewTime = 0; // total brewtime including preinfusion and preinfusion pause +double timeBrewed = 0; // total brewed time +double lastBrewTimeMillis = 0; // for shottimer delay after brew is finished +unsigned long startingTime = 0; // start time of brew +bool brewPIDDisabled = false; // is PID disabled for delay after brew has started? + +// Backflush values +int backflushCycles = BACKFLUSH_CYCLES; +double backflushFillTime = BACKFLUSH_FILL_TIME; +double backflushFlushTime = BACKFLUSH_FLUSH_TIME; +int backflushOn = 0; +int currBackflushCycles = 1; // Shot timer with or without scale #if FEATURE_SCALE == 1 @@ -59,9 +76,9 @@ boolean scaleCalibrationOn = 0; boolean scaleTareOn = 0; int shottimerCounter = 10; float calibrationValue = SCALE_CALIBRATION_FACTOR; // use calibration example to get value -float weight = 0; // value from HX711 -float weightPreBrew = 0; // value of scale before wrew started -float weightBrew = 0; // weight value of brew +float currWeight = 0; // value from HX711 +float weightPreBrew = 0; // value of scale before brew started +float weightBrewed = 0; // weight value of brew float scaleDelayValue = 2.5; // value in gramm that takes still flows onto the scale after brew is stopped bool scaleFailure = false; const unsigned long intervalWeight = 200; // weight scale @@ -77,68 +94,65 @@ HX711_ADC LoadCell2(PIN_HXDAT2, PIN_HXCLK); * @brief Toggle or momentary input for Brew Switch */ void checkbrewswitch() { - uint8_t brewSwitchReading = brewSwitch->isPressed(); + brewSwitchReading = brewSwitch->isPressed(); if (BREWSWITCH_TYPE == Switch::TOGGLE) { - currStateBrewSwitch = brewSwitchReading; + if (brewSwitchReading == HIGH && currBrewSwitchState != kBrewSwitchShortPressed) { + currBrewSwitchState = kBrewSwitchShortPressed; + LOG(DEBUG, "Toggle Brew switch is ON -> got to currBrewSwitchState = kBrewSwitchShortPressed"); + } + else if (brewSwitchReading == LOW && currBrewSwitchState != kBrewSwitchIdle) { + currBrewSwitchState = kBrewSwitchIdle; + LOG(DEBUG, "Toggle Brew switch is OFF -> got to currBrewSwitchState = kBrewSwitchIdle"); + } } else if (BREWSWITCH_TYPE == Switch::MOMENTARY) { - if (currBrewSwitchStateMomentary != brewSwitchReading) { - currBrewSwitchStateMomentary = brewSwitchReading; + if (currReadingBrewSwitch != brewSwitchReading) { + currReadingBrewSwitch = brewSwitchReading; } // Convert momentary brew switch input to brew switch state - switch (brewSwitchState) { + switch (currBrewSwitchState) { case kBrewSwitchIdle: - if (currBrewSwitchStateMomentary == HIGH && machineState != kWaterEmpty) { - brewSwitchState = kBrewSwitchBrew; - LOG(DEBUG, "brewSwitchState = kBrewSwitchIdle; waiting for brew switch input"); + if (currReadingBrewSwitch == HIGH) { + currBrewSwitchState = kBrewSwitchPressed; + LOG(DEBUG, "Brew switch press detected -> got to currBrewSwitchState = kBrewSwitchPressed"); } break; - case kBrewSwitchBrew: - // Brew switch short pressed - start brew - if (currBrewSwitchStateMomentary == LOW) { - // Brew trigger - currStateBrewSwitch = HIGH; - brewSwitchState = kBrewSwitchBrewAbort; - LOG(DEBUG, "brewSwitchState = kBrewSwitchBrew; brew switch short pressed - start Brew"); + case kBrewSwitchPressed: // Brew switch pressed - check for short or long press + if (currReadingBrewSwitch == LOW) { // Brew switch short press detected + currBrewSwitchState = kBrewSwitchShortPressed; + LOG(DEBUG, "Brew switch short press detected -> got to currBrewSwitchState = kBrewSwitchShortPressed; start brew"); } - - // Brew switch more than brewSwitchMomentaryLongPress pressed - start flushing - if (currBrewSwitchStateMomentary == HIGH && brewSwitch->longPressDetected() && machineState != kWaterEmpty) { - brewSwitchState = kBrewSwitchFlushOff; - valveRelay.on(); - pumpRelay.on(); - startingTime = millis(); - LOG(DEBUG, "brewSwitchState = kBrewSwitchBrew: brew switch long pressed - start flushing"); + else if (currReadingBrewSwitch == HIGH && brewSwitch->longPressDetected()) { // Brew switch long press detected + currBrewSwitchState = kBrewSwitchLongPressed; + LOG(DEBUG, "Brew switch long press detected -> got to currBrewSwitchState = kBrewSwitchLongPressed; start manual flush"); } break; - case kBrewSwitchBrewAbort: - // Brew switch got short pressed while brew is running - abort brew - if ((currBrewSwitchStateMomentary == HIGH && currStateBrewSwitch == HIGH) || (machineState == kShotTimerAfterBrew) || (backflushState == kBackflushWaitBrewswitchOff)) { - currStateBrewSwitch = LOW; - brewSwitchState = kBrewSwitchReset; - LOG(DEBUG, "brewSwitchState = kBrewSwitchBrewAbort: brew switch short pressed - stop brew"); + case kBrewSwitchShortPressed: + if (currReadingBrewSwitch == HIGH) { // Brew switch short press detected while brew is running - abort brew + currBrewSwitchState = kBrewSwitchWaitForRelease; + LOG(DEBUG, "Brew switch short press detected -> got to currBrewSwitchState = kBrewSwitchWaitForRelease; brew or backflush stopped manually"); + } + else if ((currBrewState == kBrewFinished) || (currBackflushState == kBackflushFinished)) { // Brew reached target and stopped or blackflush cycle done + currBrewSwitchState = kBrewSwitchWaitForRelease; + LOG(DEBUG, "Brew reached target or backflush done -> got to currBrewSwitchState = kBrewSwitchWaitForRelease"); } break; - case kBrewSwitchFlushOff: - // Brew switch got released - stop flushing - if (currBrewSwitchStateMomentary == LOW && currStateBrewSwitch == LOW) { - brewSwitchState = kBrewSwitchReset; - LOG(DEBUG, "brewswitchTriggerCase = kBrewSwitchFlushOff: brew switch long press released - stop flushing"); - valveRelay.off(); - pumpRelay.off(); + case kBrewSwitchLongPressed: + if (currReadingBrewSwitch == LOW) { // Brew switch got released after long press detected - reset brewswitch + currBrewSwitchState = kBrewSwitchWaitForRelease; + LOG(DEBUG, "Brew switch long press released -> got to currBrewSwitchState = kBrewSwitchWaitForRelease; stop manual flush"); } break; - case kBrewSwitchReset: - // Brew switch is released - go back to start and wait for next brew switch input - if (currBrewSwitchStateMomentary == LOW) { - brewSwitchState = kBrewSwitchIdle; - LOG(DEBUG, "brewSwitchState = kBrewSwitchReset: brew switch released - go to kBrewSwitchIdle "); + case kBrewSwitchWaitForRelease: // wait for brew switch got released + if (currReadingBrewSwitch == LOW) { + currBrewSwitchState = kBrewSwitchIdle; + LOG(DEBUG, "Brew switch reset -> got to currBrewSwitchState = kBrewSwitchIdle"); } break; } @@ -146,215 +160,260 @@ void checkbrewswitch() { } /** - * @brief Backflush + * @brief Brew process handeling including timer and state machine for brew-by-time and brew-by-weight + * @return true if brew is running, false otherwise */ -void backflush() { - if (backflushState != kBackflushWaitBrewswitchOn && backflushOn == 0) { - backflushState = kBackflushWaitBrewswitchOff; // Force reset in case backflushOn is reset during backflush! - LOG(INFO, "Backflush: Disabled via Webinterface"); +bool brew() { + unsigned long currentMillisTemp = millis(); + checkbrewswitch(); + + // abort function for state machine from every state + if (currBrewSwitchState == kBrewSwitchIdle && currBrewState > kBrewIdle && currBrewState < kBrewFinished) { + if (currBrewState != kBrewFinished) { + LOG(INFO, "Brew stopped manually"); + } + currBrewState = kBrewFinished; } - else if (offlineMode == 1 || currBrewState > kBrewIdle || backflushCycles <= 0 || backflushOn == 0) { - return; + // calculated brew time while brew is running + if (currBrewState > kBrewIdle && currBrewState < kBrewFinished) { + timeBrewed = currentMillisTemp - startingTime; } - if (bPID.GetMode() == 1) { // Deactivate PID - bPID.SetMode(0); - pidOutput = 0; - } + if (featureBrewControl) { // brew-by-time and brew-by-weight - heaterRelay.off(); // Stop heating + // check if brewswitch was turned off after a brew; Brew only runs once even brewswitch is still pressed + if (currBrewSwitchState == kBrewSwitchIdle) { + brewSwitchWasOff = true; + } - checkbrewswitch(); + // set brew time every cycle, in case changes are done during brew + if (brewTime > 0) { + totalBrewTime = (preinfusion * 1000) + (preinfusionPause * 1000) + (brewTime * 1000); + } + else { + // Stop by time deactivated --> brewTime = 0 + totalBrewTime = 0; + } - if (currStateBrewSwitch == LOW && backflushState != kBackflushWaitBrewswitchOn) { // Abort function for state machine from every state - backflushState = kBackflushWaitBrewswitchOff; - } + // state machine for brew + switch (currBrewState) { + case kBrewIdle: // waiting step for brew switch turning on + if (currBrewSwitchState == kBrewSwitchShortPressed && brewSwitchWasOff && backflushOn == 0 && machineState != kWaterEmpty && machineState != kBackflush) { + startingTime = millis(); + timeBrewed = 0; // reset timeBrewed, last brew is still stored + LOG(INFO, "Brew started"); + + if (preinfusionPause == 0 || preinfusion == 0) { + LOG(INFO, "Brew running"); + currBrewState = kBrewRunning; + } + else { + LOG(INFO, "Preinfusion running"); + currBrewState = kPreinfusion; + } + } - // State machine for backflush - switch (backflushState) { - case kBackflushWaitBrewswitchOn: - if (currStateBrewSwitch == HIGH && backflushOn) { - startingTime = millis(); - backflushState = kBackflushFillingStart; - } + break; - break; + case kPreinfusion: + valveRelay.on(); + pumpRelay.on(); - case kBackflushFillingStart: - LOG(INFO, "Backflush: Portafilter filling..."); - valveRelay.on(); - pumpRelay.on(); - backflushState = kBackflushFilling; + if (timeBrewed > (preinfusion * 1000)) { + LOG(INFO, "Preinfusion pause running"); + currBrewState = kPreinfusionPause; + } - break; + break; - case kBackflushFilling: - if (millis() - startingTime > (backflushFillTime * 1000)) { - startingTime = millis(); - backflushState = kBackflushFlushingStart; - } + case kPreinfusionPause: + valveRelay.on(); + pumpRelay.off(); - break; + if (timeBrewed > ((preinfusion + preinfusionPause) * 1000)) { + LOG(INFO, "Brew running"); + currBrewState = kBrewRunning; + } - case kBackflushFlushingStart: - LOG(INFO, "Backflush: Flushing to drip tray..."); - valveRelay.off(); - pumpRelay.off(); - currBackflushCycles++; - backflushState = kBackflushFlushing; + break; - break; + case kBrewRunning: + valveRelay.on(); + pumpRelay.on(); - case kBackflushFlushing: - if (millis() - startingTime > (backflushFlushTime * 1000) && currBackflushCycles < backflushCycles) { - startingTime = millis(); - backflushState = kBackflushFillingStart; - } - else if (currBackflushCycles >= backflushCycles) { - backflushState = kBackflushWaitBrewswitchOff; - } + // stop brew if target-time is reached --> No stop if stop by time is deactivated via Parameter (0) + if ((timeBrewed > totalBrewTime) && ((brewTime > 0))) { + LOG(INFO, "Brew reached time target"); + currBrewState = kBrewFinished; + } +#if (FEATURE_SCALE == 1) + // stop brew if target-weight is reached --> No stop if stop by weight is deactivated via Parameter (0) + else if (((FEATURE_SCALE == 1) && (weightBrewed > weightSetpoint)) && (weightSetpoint > 0)) { + LOG(INFO, "Brew reached weight target"); + currBrewState = kBrewFinished; + } +#endif - break; + break; - case kBackflushWaitBrewswitchOff: - if (currStateBrewSwitch == LOW) { - LOG(INFO, "Backflush: Finished!"); + case kBrewFinished: valveRelay.off(); pumpRelay.off(); - currBackflushCycles = 0; - backflushState = kBackflushWaitBrewswitchOn; - } + currentMillisTemp = 0; + lastBrewTimeMillis = millis(); // time brew finished for shottimer delay + brewSwitchWasOff = false; + LOG(INFO, "Brew finished"); + LOGF(INFO, "Shot time: %4.1f s", timeBrewed / 1000); + LOG(INFO, "Brew idle"); + currBrewState = kBrewIdle; - break; + break; + } + } + else { // brewControlOn == 0, only brew time + + switch (currBrewState) { + case kBrewIdle: // waiting step for brew switch turning on + if (currBrewSwitchState == kBrewSwitchShortPressed && machineState != kWaterEmpty) { + startingTime = millis(); + timeBrewed = 0; // reset timeBrewed, last brew is still stored + LOG(INFO, "Brew timer started"); + currBrewState = kBrewRunning; + } + + break; + + case kBrewRunning: + if (currBrewSwitchState == kBrewSwitchIdle && currBrewState == kBrewRunning) { + currBrewState = kBrewFinished; + } + + break; + + case kBrewFinished: + currentMillisTemp = 0; + lastBrewTimeMillis = millis(); // time brew finished for shottimer delay + LOG(INFO, "Brew finished"); + LOGF(INFO, "Shot time: %4.1f s", timeBrewed / 1000); + LOG(INFO, "Brew idle"); + currBrewState = kBrewIdle; + + break; + } } + return (currBrewState != kBrewIdle && currBrewState != kBrewFinished); } -#if (FEATURE_BREWCONTROL == 1) /** - * @brief Time base brew mode + * @brief manual grouphead flush + * @return true if manual flush is running, false otherwise */ -void brew() { +bool manualFlush() { unsigned long currentMillisTemp = millis(); checkbrewswitch(); - - if (currStateBrewSwitch == LOW && currBrewState > kBrewIdle) { - // abort function for state machine from every state - LOG(INFO, "Brew stopped manually"); - currBrewState = kWaitBrewOff; - } - - if (currBrewState > kBrewIdle && currBrewState < kWaitBrewOff || brewSwitchState == kBrewSwitchFlushOff) { + if (currManualFlushState == kManualFlushRunning) { timeBrewed = currentMillisTemp - startingTime; } - if (currStateBrewSwitch == LOW) { - // check if brewswitch was turned off at least once, last time, - brewSwitchWasOff = true; - } - - if (brewTime > 0) { - totalBrewTime = (preinfusion * 1000) + (preinfusionPause * 1000) + (brewTime * 1000); // running every cycle, in case changes are done during brew - } - else { - // Stop by time deactivated --> brewTime = 0 - totalBrewTime = 0; - } - - // state machine for brew - switch (currBrewState) { - case kBrewIdle: // waiting step for brew switch turning on - if (currStateBrewSwitch == HIGH && backflushState == 10 && backflushOn == 0 && brewSwitchWasOff && machineState != kWaterEmpty) { + switch (currManualFlushState) { + case kManualFlushIdle: + if (currBrewSwitchState == kBrewSwitchLongPressed && machineState != kWaterEmpty) { startingTime = millis(); - - if (preinfusionPause == 0 || preinfusion == 0) { - currBrewState = kBrewRunning; - } - else { - currBrewState = kPreinfusion; - } + valveRelay.on(); + pumpRelay.on(); + LOG(INFO, "Manual flush started"); + currManualFlushState = kManualFlushRunning; } - else { - backflush(); - } - - break; - - case kPreinfusion: // preinfusioon - LOG(INFO, "Preinfusion"); - valveRelay.on(); - pumpRelay.on(); - currBrewState = kWaitPreinfusion; - break; - case kWaitPreinfusion: // waiting time preinfusion - if (timeBrewed > (preinfusion * 1000)) { - currBrewState = kPreinfusionPause; + case kManualFlushRunning: + if (currBrewSwitchState != kBrewSwitchLongPressed) { + valveRelay.off(); + pumpRelay.off(); + LOG(INFO, "Manual flush stopped"); + LOGF(INFO, "Manual flush time: %4.1f s", timeBrewed / 1000); + currManualFlushState = kManualFlushIdle; } - break; + } + return (currManualFlushState == kManualFlushRunning); +} - case kPreinfusionPause: // preinfusion pause - LOG(INFO, "Preinfusion pause"); - valveRelay.on(); - pumpRelay.off(); - currBrewState = kWaitPreinfusionPause; +/** + * @brief Backflush + */ +void backflush() { + checkbrewswitch(); + if (currBackflushState != kBackflushIdle && backflushOn == 0) { + currBackflushState = kBackflushFinished; // Force reset in case backflushOn is reset during backflush! + LOG(INFO, "Backflush: Disabled via webinterface"); + } + else if (offlineMode == 1 || currBrewState > kBrewIdle || backflushCycles <= 0 || backflushOn == 0) { + return; + } - break; + // abort function for state machine from every state + if (currBrewSwitchState == kBrewSwitchIdle && currBackflushState > kBackflushIdle && currBackflushState < kBackflushFinished) { + currBackflushState = kBackflushFinished; + if (currBackflushState != kBackflushFinished) { + LOG(INFO, "Backflush stopped manually"); + } + } + + // check if brewswitch was turned off after a backflush; Backflush only runs once even brewswitch is still pressed + if (currBrewSwitchState == kBrewSwitchIdle) { + brewSwitchWasOff = true; + } - case kWaitPreinfusionPause: // waiting time preinfusion pause - if (timeBrewed > ((preinfusion * 1000) + (preinfusionPause * 1000))) { - currBrewState = kBrewRunning; + // State machine for backflush + switch (currBackflushState) { + case kBackflushIdle: + if (currBrewSwitchState == kBrewSwitchShortPressed && backflushOn && brewSwitchWasOff && machineState != kWaterEmpty) { + startingTime = millis(); + valveRelay.on(); + pumpRelay.on(); + LOGF(INFO, "Start backflush cycle %d", currBackflushCycles); + LOG(INFO, "Backflush: filling portafilter"); + currBackflushState = kBackflushFilling; } break; - case kBrewRunning: // brew running - LOG(INFO, "Brew started"); - valveRelay.on(); - pumpRelay.on(); - currBrewState = kWaitBrew; - + case kBackflushFilling: + if (millis() - startingTime > (backflushFillTime * 1000)) { + startingTime = millis(); + valveRelay.off(); + pumpRelay.off(); + LOG(INFO, "Backflush: flushing into drip tray"); + currBackflushState = kBackflushFlushing; + } break; - case kWaitBrew: // waiting time or weight brew - lastBrewTime = timeBrewed; - - // stop brew if target-time is reached --> No stop if stop by time is deactivated via Parameter (0) - if ((timeBrewed > totalBrewTime) && ((brewTime > 0))) { - currBrewState = kBrewFinished; - } -#if (FEATURE_SCALE == 1) - // stop brew if target-weight is reached --> No stop if stop by weight is deactivated via Parameter (0) - else if (((FEATURE_SCALE == 1) && (weightBrew > weightSetpoint)) && (weightSetpoint > 0)) { - currBrewState = kBrewFinished; + case kBackflushFlushing: + if (millis() - startingTime > (backflushFlushTime * 1000)) { + if (currBackflushCycles < backflushCycles) { + startingTime = millis(); + valveRelay.on(); + pumpRelay.on(); + currBackflushCycles++; + LOGF(INFO, "Backflush: next backflush cycle %d", currBackflushCycles); + LOG(INFO, "Backflush: filling portafilter"); + currBackflushState = kBackflushFilling; + } + else { + currBackflushState = kBackflushFinished; + } } -#endif - break; - case kBrewFinished: // brew finished - LOG(INFO, "Brew stopped"); + case kBackflushFinished: valveRelay.off(); pumpRelay.off(); - currBrewState = kWaitBrewOff; - - break; - - case kWaitBrewOff: // waiting for brewswitch off position - if (currStateBrewSwitch == LOW) { - valveRelay.off(); - pumpRelay.off(); - - // disarmed button - currentMillisTemp = 0; - brewDetected = 0; // rearm brewDetection - currBrewState = kBrewIdle; - lastBrewTime = timeBrewed; // store brewtime to show in Shottimer after brew is finished - timeBrewed = 0; - } + LOGF(INFO, "Backflush finished after %d cycles", currBackflushCycles); + currBackflushCycles = 1; + brewSwitchWasOff = false; + currBackflushState = kBackflushIdle; break; } } -#endif diff --git a/src/defaults.h b/src/defaults.h index 978f3e83..a097a10c 100644 --- a/src/defaults.h +++ b/src/defaults.h @@ -37,9 +37,7 @@ int writeSysParamsToStorage(void); #define AGGBTN 0 // PID Tn (brew detection phase) #define AGGBTV 20 // PID Tv (brew detection phase) #define BREW_TIME 25 // brew time in seconds (only used if pump is being controlled) -#define BREW_SW_TIME 25 // keep brew PID params for this many seconds after detection (only for software BD) #define BREW_PID_DELAY 10 // delay until enabling PID controller during brew (no heating during this time) -#define BD_SENSITIVITY 120 // brew detection sensitivity, be careful: if too low, then there is the risk of wrong brew detection and rising temperature #define PRE_INFUSION_TIME 2 // pre-infusion time in seconds #define PRE_INFUSION_PAUSE_TIME 5 // pre-infusion pause time in seconds #define SCALE_WEIGHTSETPOINT 30 // Target weight in grams @@ -49,54 +47,55 @@ int writeSysParamsToStorage(void); #define BACKFLUSH_CYCLES 5 // number of cycles the backflush should run #define BACKFLUSH_FILL_TIME 5 // time in seconds the pump is running during backflush #define BACKFLUSH_FLUSH_TIME 10 // time in seconds the 3-way valve is open during backflush +#define FEATURE_BREW_CONTROL 0 // enables function to control pump and solenoid valve +#define FEATURE_SHOT_TIMER 0 // enables full screen shot timer +#define SHOT_TIMER_DISPLAY_DELAY 3 // time in seconds that shot timer will be shown after brew finished +#define FEATURE_HEATING_LOGO 1 // enables full screen logo if mashine is heating +#define FEATURE_PID_OFF_LOGO 1 // enables full screen logo if pid is switched off -#define PID_KP_START_MIN 0 -#define PID_KP_START_MAX 999 -#define PID_TN_START_MIN 0 -#define PID_TN_START_MAX 999 -#define PID_KP_REGULAR_MIN 0 -#define PID_KP_REGULAR_MAX 999 -#define PID_TN_REGULAR_MIN 0 -#define PID_TN_REGULAR_MAX 999 -#define PID_TV_REGULAR_MIN 0 -#define PID_TV_REGULAR_MAX 999 -#define PID_I_MAX_REGULAR_MIN 0 -#define PID_I_MAX_REGULAR_MAX 999 -#define PID_KP_BD_MIN 0 -#define PID_KP_BD_MAX 999 -#define PID_TN_BD_MIN 0 -#define PID_TN_BD_MAX 999 -#define PID_TV_BD_MIN 0 -#define PID_TV_BD_MAX 999 -#define BREW_SETPOINT_MIN 20 -#define BREW_SETPOINT_MAX 110 -#define STEAM_SETPOINT_MIN 100 -#define STEAM_SETPOINT_MAX 140 -#define BREW_TEMP_OFFSET_MIN 0 -#define BREW_TEMP_OFFSET_MAX 20 -#define BREW_TEMP_TIME_MIN 1 -#define BREW_TEMP_TIME_MAX 180 -#define BREW_TIME_MIN 0 -#define BREW_TIME_MAX 180 -#define BREW_PID_DELAY_MIN 0 -#define BREW_PID_DELAY_MAX 60 -#define BREW_SW_TIME_MIN 1 -#define BREW_SW_TIME_MAX 180 -#define BD_THRESHOLD_MIN 0 -#define BD_THRESHOLD_MAX 999 -#define PRE_INFUSION_TIME_MIN 0 -#define PRE_INFUSION_TIME_MAX 60 -#define PRE_INFUSION_PAUSE_MIN 0 -#define PRE_INFUSION_PAUSE_MAX 60 -#define WEIGHTSETPOINT_MIN 0 -#define WEIGHTSETPOINT_MAX 500 -#define PID_KP_STEAM_MIN 0 -#define PID_KP_STEAM_MAX 500 -#define STANDBY_MODE_TIME_MIN 30 -#define STANDBY_MODE_TIME_MAX 120 -#define BACKFLUSH_CYCLES_MIN 2 -#define BACKFLUSH_CYCLES_MAX 20 -#define BACKFLUSH_FILL_TIME_MIN 5 -#define BACKFLUSH_FILL_TIME_MAX 20 -#define BACKFLUSH_FLUSH_TIME_MIN 5 -#define BACKFLUSH_FLUSH_TIME_MAX 20 +#define PID_KP_START_MIN 0 +#define PID_KP_START_MAX 999 +#define PID_TN_START_MIN 0 +#define PID_TN_START_MAX 999 +#define PID_KP_REGULAR_MIN 0 +#define PID_KP_REGULAR_MAX 999 +#define PID_TN_REGULAR_MIN 0 +#define PID_TN_REGULAR_MAX 999 +#define PID_TV_REGULAR_MIN 0 +#define PID_TV_REGULAR_MAX 999 +#define PID_I_MAX_REGULAR_MIN 0 +#define PID_I_MAX_REGULAR_MAX 999 +#define PID_KP_BD_MIN 0 +#define PID_KP_BD_MAX 999 +#define PID_TN_BD_MIN 0 +#define PID_TN_BD_MAX 999 +#define PID_TV_BD_MIN 0 +#define PID_TV_BD_MAX 999 +#define BREW_SETPOINT_MIN 20 +#define BREW_SETPOINT_MAX 110 +#define STEAM_SETPOINT_MIN 100 +#define STEAM_SETPOINT_MAX 140 +#define BREW_TEMP_OFFSET_MIN 0 +#define BREW_TEMP_OFFSET_MAX 20 +#define BREW_TIME_MIN 0 +#define BREW_TIME_MAX 180 +#define BREW_PID_DELAY_MIN 0 +#define BREW_PID_DELAY_MAX 60 +#define PRE_INFUSION_TIME_MIN 0 +#define PRE_INFUSION_TIME_MAX 60 +#define PRE_INFUSION_PAUSE_MIN 0 +#define PRE_INFUSION_PAUSE_MAX 60 +#define WEIGHTSETPOINT_MIN 0 +#define WEIGHTSETPOINT_MAX 500 +#define PID_KP_STEAM_MIN 0 +#define PID_KP_STEAM_MAX 500 +#define STANDBY_MODE_TIME_MIN 30 +#define STANDBY_MODE_TIME_MAX 120 +#define BACKFLUSH_CYCLES_MIN 2 +#define BACKFLUSH_CYCLES_MAX 20 +#define BACKFLUSH_FILL_TIME_MIN 5 +#define BACKFLUSH_FILL_TIME_MAX 20 +#define BACKFLUSH_FLUSH_TIME_MIN 5 +#define BACKFLUSH_FLUSH_TIME_MAX 20 +#define SHOT_TIMER_DISPLAY_DELAY_MIN 0 +#define SHOT_TIMER_DISPLAY_DELAY_MAX 60 diff --git a/src/display/displayCommon.h b/src/display/displayCommon.h index 5c28eb35..af1f3644 100644 --- a/src/display/displayCommon.h +++ b/src/display/displayCommon.h @@ -234,66 +234,41 @@ void displayLogo(String displaymessagetext, String displaymessagetext2) { } /** - * @brief display shot timer + * @brief display shot and flush timer */ bool displayShottimer() { - if (FEATURE_SHOTTIMER == 0) { + if (featureShotTimer == 0) { return false; } - if (machineState == kBrew || brewSwitchState == kBrewSwitchFlushOff) { + if (machineState == kBrew || (millis() - lastBrewTimeMillis) < (shotTimerDisplayDelay * 1000) || machineState == kManualFlush) { u8g2.clearBuffer(); - if (brewSwitchState != kBrewSwitchFlushOff) { + if (machineState != kManualFlush) { u8g2.drawXBMP(-1, 11, Brew_Cup_Logo_width, Brew_Cup_Logo_height, Brew_Cup_Logo); +#if (FEATURE_SCALE == 1) + u8g2.setFont(u8g2_font_profont22_tf); + u8g2.setCursor(64, 15); + u8g2.print(timeBrewed / 1000, 1); + u8g2.print("s"); + u8g2.setCursor(64, 38); + u8g2.print(weightBrewed, 1); + u8g2.print("g"); + u8g2.setFont(u8g2_font_profont11_tf); +#else + displayBrewtime(48, 25, timeBrewed); +#endif } else { u8g2.drawXBMP(0, 12, Manual_Flush_Logo_width, Manual_Flush_Logo_height, Manual_Flush_Logo); + displayBrewtime(48, 25, timeBrewed); } -#if (FEATURE_SCALE == 1) - u8g2.setFont(u8g2_font_profont22_tf); - u8g2.setCursor(64, 15); - u8g2.print(timeBrewed / 1000, 1); - u8g2.print("s"); - u8g2.setCursor(64, 38); - u8g2.print(weightBrew, 1); - u8g2.print("g"); - u8g2.setFont(u8g2_font_profont11_tf); -#else - displayBrewtime(48, 25, timeBrewed); -#endif - displayWaterIcon(119, 1); u8g2.sendBuffer(); return true; } - /* if the totalBrewTime is reached automatically, - * nothing should be done, otherwise wrong time is displayed - * because the switch is pressed later than totalBrewTime - */ - else if (machineState == kShotTimerAfterBrew && brewSwitchState != kBrewSwitchFlushOff) { - u8g2.clearBuffer(); - u8g2.drawXBMP(-1, 11, Brew_Cup_Logo_width, Brew_Cup_Logo_height, Brew_Cup_Logo); - -#if (FEATURE_SCALE == 1) - u8g2.setFont(u8g2_font_profont22_tf); - u8g2.setCursor(64, 15); - u8g2.print(lastBrewTime / 1000, 1); - u8g2.print("s"); - u8g2.setCursor(64, 38); - u8g2.print(weightBrew, 1); - u8g2.print("g"); - u8g2.setFont(u8g2_font_profont11_tf); -#else - displayBrewtime(48, 25, lastBrewTime); -#endif - - displayWaterIcon(119, 1); - u8g2.sendBuffer(); - return true; - } return false; } @@ -302,7 +277,7 @@ bool displayShottimer() { */ bool displayMachineState() { // Show the heating logo when we are in regular PID mode and more than 5degC below the set point - if (FEATURE_HEATINGLOGO > 0 && machineState == kPidNormal && (setpoint - temperature) > 5. && brewSwitchState != kBrewSwitchFlushOff) { + if (featureHeatingLogo > 0 && machineState == kPidNormal && (setpoint - temperature) > 5.) { // For status info u8g2.clearBuffer(); @@ -318,7 +293,7 @@ bool displayMachineState() { return true; } // Offline logo - else if (FEATURE_PIDOFF_LOGO == 1 && machineState == kPidDisabled) { + else if (featurePidOffLogo == 1 && machineState == kPidDisabled) { u8g2.clearBuffer(); u8g2.drawXBMP(38, 0, Off_Logo_width, Off_Logo_height, Off_Logo); u8g2.setCursor(0, 55); @@ -328,7 +303,7 @@ bool displayMachineState() { u8g2.sendBuffer(); return true; } - else if (FEATURE_PIDOFF_LOGO == 1 && machineState == kStandby) { + else if (featurePidOffLogo == 1 && machineState == kStandby) { u8g2.clearBuffer(); u8g2.drawXBMP(38, 0, Off_Logo_width, Off_Logo_height, Off_Logo); u8g2.setCursor(36, 55); @@ -339,7 +314,7 @@ bool displayMachineState() { return true; } // Steam - else if (machineState == kSteam && brewSwitchState != kBrewSwitchFlushOff) { + else if (machineState == kSteam) { u8g2.clearBuffer(); u8g2.drawXBMP(-1, 12, Steam_Logo_width, Steam_Logo_height, Steam_Logo); @@ -350,7 +325,7 @@ bool displayMachineState() { return true; } // Water empty - else if (machineState == kWaterEmpty && brewSwitchState != kBrewSwitchFlushOff) { + else if (machineState == kWaterEmpty) { u8g2.clearBuffer(); u8g2.drawXBMP(45, 0, Water_Empty_Logo_width, Water_Empty_Logo_height, Water_Empty_Logo); u8g2.setFont(u8g2_font_profont11_tf); @@ -364,8 +339,8 @@ bool displayMachineState() { u8g2.setCursor(2, 10); u8g2.print("Backflush"); - switch (backflushState) { - case kBackflushWaitBrewswitchOn: + switch (currBackflushState) { + case kBackflushIdle: u8g2.setFont(u8g2_font_profont12_tf); u8g2.setCursor(4, 37); u8g2.print(langstring_backflush_press); @@ -373,7 +348,7 @@ bool displayMachineState() { u8g2.print(langstring_backflush_start); break; - case kBackflushWaitBrewswitchOff: + case kBackflushFinished: u8g2.setFont(u8g2_font_profont12_tf); u8g2.setCursor(4, 37); u8g2.print(langstring_backflush_press); @@ -384,7 +359,7 @@ bool displayMachineState() { default: u8g2.setFont(u8g2_font_fub17_tf); u8g2.setCursor(42, 42); - u8g2.print(currBackflushCycles + 1, 0); + u8g2.print(currBackflushCycles, 0); u8g2.print("/"); u8g2.print(backflushCycles, 0); break; diff --git a/src/display/displayRotateUpright.h b/src/display/displayRotateUpright.h index 391bf618..1f2e6e2f 100644 --- a/src/display/displayRotateUpright.h +++ b/src/display/displayRotateUpright.h @@ -88,7 +88,7 @@ void displayEmergencyStop(void) { * @brief display shot timer */ bool displayShottimer() { - if (((timeBrewed > 0 && FEATURE_BREWCONTROL == 0) || (FEATURE_BREWCONTROL > 0 && currBrewState > kBrewIdle && currBrewState <= kBrewFinished)) && FEATURE_SHOTTIMER == 1) { + if (((timeBrewed > 0 && featureBrewControl == 0) || (featureBrewControl > 0 && currBrewState > kBrewIdle && currBrewState <= kBrewFinished)) && featureShotTimer == 1) { u8g2.clearBuffer(); // draw temp icon @@ -100,8 +100,8 @@ bool displayShottimer() { u8g2.sendBuffer(); return true; } - else if (FEATURE_SHOTTIMER == 1 && millis() >= lastBrewTimeMillis && // directly after creating lastbrewTimeMillis (happens when turning off the brew switch, case 43 in the code) should be started - lastBrewTimeMillis + SHOTTIMERDISPLAYDELAY >= millis() && // should run until millis() has caught up with SHOTTIMERDISPLAYDELAY, this can be used to control the display duration + else if (featureShotTimer == 1 && millis() >= lastBrewTimeMillis && // directly after creating lastbrewTimeMillis (happens when turning off the brew switch, case 43 in the code) should be started + lastBrewTimeMillis + (shotTimerDisplayDelay * 1000) >= millis() && // should run until millis() has caught up with shotTimerDisplayDelay, this can be used to control the display duration lastBrewTimeMillis < totalBrewTime) // if the totalBrewTime is reached automatically, nothing should be done, otherwise wrong time will be displayed because switch is pressed later than totalBrewTime { u8g2.clearBuffer(); diff --git a/src/display/displayTemplateMinimal.h b/src/display/displayTemplateMinimal.h index 7bcb235a..3deb69de 100644 --- a/src/display/displayTemplateMinimal.h +++ b/src/display/displayTemplateMinimal.h @@ -78,26 +78,41 @@ void printScreen() { u8g2.setFont(u8g2_font_profont11_tf); - if (isBrewDetected == 1 && currBrewState == kBrewIdle) { - u8g2.setCursor(38, 44); - u8g2.print("BD: "); - u8g2.print((millis() - timeBrewDetection) / 1000, 1); - u8g2.print("/"); - u8g2.print(brewtimesoftware, 0); - } - else { - u8g2.setCursor(34, 44); - u8g2.print(langstring_brew); - u8g2.print(timeBrewed / 1000, 0); - u8g2.print("/"); +// Brew time +#if (FEATURE_BREWSWITCH == 1) + if (featureBrewControl) { + // Shown brew time while machine is brewing and after the brewing during shotTimerDisplayDelay + if (machineState == kBrew || (millis() - lastBrewTimeMillis) < (shotTimerDisplayDelay * 1000)) { + u8g2.setCursor(34, 44); + u8g2.print(langstring_brew); + u8g2.print(timeBrewed / 1000, 0); + u8g2.print("/"); + u8g2.print(totalBrewTime / 1000, 0); + } + + // Flush time - if (FEATURE_BREWCONTROL == 0) { - u8g2.print(brewtimesoftware, 0); + // Shown flush time while machine is flushing + if (machineState == kManualFlush) { + u8g2.setDrawColor(0); + u8g2.drawBox(34, 44, 100, 15); + u8g2.setDrawColor(1); + u8g2.setCursor(34, 44); + u8g2.print(langstring_manual_flush); + u8g2.print(timeBrewed / 1000, 0); } - else { - u8g2.print(totalBrewTime / 1000, 0); + } + + // Brew Timer with optocoupler + else { + // Shown brew time while machine is brewing and after the brewing during shotTimerDisplayDelay + if (machineState == kBrew || (millis() - lastBrewTimeMillis) < (shotTimerDisplayDelay * 1000)) { + u8g2.setCursor(34, 44); + u8g2.print(langstring_brew); + u8g2.print(timeBrewed / 1000, 0); } } +#endif // Show heater output in % displayProgressbar(pidOutput / 10, 15, 60, 100); diff --git a/src/display/displayTemplateScale.h b/src/display/displayTemplateScale.h index 5120dd2b..e27646eb 100644 --- a/src/display/displayTemplateScale.h +++ b/src/display/displayTemplateScale.h @@ -51,53 +51,91 @@ void printScreen() { u8g2.print("/"); u8g2.print(setpoint, 1); + /** + * @brief Shot timer for scale + * + * If scale has an error show fault on the display otherwise show current reading of the scale + * if brew is running show current brew time and current brew weight + * if brewControl is enabled and time or weight target is set show targets + * if brewControl is enabled show flush time during manualFlush + * if FEATURE_PRESSURESENSOR is enabled show current pressure during brew + * if brew is finished show brew values for shotTimerDisplayDelay + */ + + // Show current weight if scale has no error u8g2.setCursor(32, 26); - u8g2.print("W: "); - + u8g2.print(langstring_weight); + u8g2.setCursor(82, 26); if (scaleFailure) { u8g2.print("fault"); } else { - if (machineState == kBrew) { - u8g2.print(weightBrew, 0); - } - else { - u8g2.print(weight, 0); - } - - if (weightSetpoint > 0) { - u8g2.print("/"); - u8g2.print(weightSetpoint, 0); - } - - u8g2.print(" ("); - u8g2.print(weightBrew, 1); - u8g2.print(")"); + u8g2.print(currWeight, 0); + u8g2.print(" g"); } - // Brew - u8g2.setCursor(32, 36); - u8g2.print("t: "); - u8g2.print(timeBrewed / 1000, 0); - - if (FEATURE_BREWCONTROL == 0) { - u8g2.print("/"); - u8g2.print(brewtimesoftware, 0); + if (featureBrewControl) { + // Shown brew time and weight while machine is brewing and after the brewing during shotTimerDisplayDelay + if (machineState == kBrew || (millis() - lastBrewTimeMillis) < (shotTimerDisplayDelay * 1000)) { + + // weight + u8g2.setCursor(32, 26); + u8g2.print(langstring_weight); + u8g2.setCursor(82, 26); + u8g2.print(weightBrewed, 0); + + if (weightSetpoint > 0) { + u8g2.print("/"); + u8g2.print(weightSetpoint, 0); + u8g2.print(" g"); + } + // time + u8g2.setCursor(32, 36); + u8g2.print(langstring_brew); + u8g2.setCursor(82, 36); + u8g2.print(timeBrewed / 1000, 0); + + if (brewTime > 0) { + u8g2.print("/"); + u8g2.print(totalBrewTime / 1000, 0); + u8g2.print(" s"); + } + } + // Shown flush time while machine is flushing + if (machineState == kManualFlush) { + u8g2.setDrawColor(0); + u8g2.drawBox(32, 26, 100, 40); + u8g2.setDrawColor(1); + u8g2.setCursor(32, 26); + u8g2.print(langstring_manual_flush); + u8g2.setCursor(82, 26); + u8g2.print(timeBrewed / 1000, 0); + u8g2.print(" s"); + } } else { - if (brewTime > 0) { - u8g2.print("/"); - u8g2.print(totalBrewTime / 1000, 0); + // Brew Timer with optocoupler + + // Shown brew time and weight while machine is brewing and after the brewing during shotTimerDisplayDelay + if (machineState == kBrew || (millis() - lastBrewTimeMillis) < (shotTimerDisplayDelay * 1000)) { + // weight + u8g2.setCursor(32, 26); + u8g2.print(langstring_weight); + u8g2.setCursor(82, 26); + u8g2.print(weightBrewed, 0); + u8g2.print(" g"); + // time + u8g2.setCursor(32, 36); + u8g2.print(langstring_brew); + u8g2.setCursor(82, 36); + u8g2.print(timeBrewed / 1000, 0); + u8g2.print(" s"); } - - u8g2.print(" ("); - u8g2.print(lastBrewTime / 1000, 1); - u8g2.print(")"); } #if (FEATURE_PRESSURESENSOR == 1) u8g2.setCursor(32, 46); - u8g2.print("P: "); + u8g2.print(langstring_pressure); u8g2.print(inputPressure, 1); #endif diff --git a/src/display/displayTemplateStandard.h b/src/display/displayTemplateStandard.h index 0d481e6d..c26b4ded 100644 --- a/src/display/displayTemplateStandard.h +++ b/src/display/displayTemplateStandard.h @@ -32,18 +32,18 @@ void printScreen() { displayStatusbar(); - u8g2.setCursor(35, 16); + u8g2.setCursor(34, 16); u8g2.print(langstring_current_temp); u8g2.setCursor(84, 16); u8g2.print(temperature, 1); - u8g2.setCursor(114, 16); + u8g2.setCursor(115, 16); u8g2.print((char)176); u8g2.print("C"); - u8g2.setCursor(35, 26); + u8g2.setCursor(34, 26); u8g2.print(langstring_set_temp); u8g2.setCursor(84, 26); u8g2.print(setpoint, 1); - u8g2.setCursor(114, 26); + u8g2.setCursor(115, 26); u8g2.print((char)176); u8g2.print("C"); @@ -59,52 +59,74 @@ void printScreen() { drawTemperaturebar(8, 50, 30); } - // Brew time - u8g2.setCursor(35, 36); +// Brew and flush time +#if (FEATURE_BREWSWITCH == 1) - // Shot timer shown if machine is brewing and after the brew - if (machineState == kBrew || machineState == kShotTimerAfterBrew) { + if (featureBrewControl) { + // Shown brew time while machine is brewing and after the brewing during shotTimerDisplayDelay + u8g2.setCursor(34, 36); u8g2.print(langstring_brew); u8g2.setCursor(84, 36); u8g2.print(timeBrewed / 1000, 0); u8g2.print("/"); + u8g2.print(totalBrewTime / 1000, 0); + u8g2.print(" s"); + } - if (FEATURE_BREWCONTROL == 0) { - u8g2.print(brewtimesoftware, 0); - } - else { - u8g2.print(totalBrewTime / 1000, 1); - } + // Shown flush time while machine is flushing + if (machineState == kManualFlush) { + u8g2.setDrawColor(0); + u8g2.drawBox(34, 36, 100, 10); + u8g2.setDrawColor(1); + u8g2.setCursor(34, 36); + u8g2.print(langstring_manual_flush); + u8g2.setCursor(84, 36); + u8g2.print(timeBrewed / 1000, 0); + u8g2.print(" s"); + } +} +else { + // Brew Timer with optocoupler + + // Shown brew time while machine is brewing and after the brewing during shotTimerDisplayDelay + if (machineState == kBrew || (millis() - lastBrewTimeMillis) < (shotTimerDisplayDelay * 1000)) { + u8g2.setCursor(34, 36); + u8g2.print(langstring_brew); + u8g2.setCursor(84, 36); + u8g2.print(timeBrewed / 1000, 0); + u8g2.print(" s"); } +} +#endif - // PID values over heat bar - u8g2.setCursor(38, 47); +// PID values over heat bar +u8g2.setCursor(38, 47); - u8g2.print(bPID.GetKp(), 0); - u8g2.print("|"); +u8g2.print(bPID.GetKp(), 0); +u8g2.print("|"); - if (bPID.GetKi() != 0) { - u8g2.print(bPID.GetKp() / bPID.GetKi(), 0); - } - else { - u8g2.print("0"); - } +if (bPID.GetKi() != 0) { + u8g2.print(bPID.GetKp() / bPID.GetKi(), 0); +} +else { + u8g2.print("0"); +} - u8g2.print("|"); - u8g2.print(bPID.GetKd() / bPID.GetKp(), 0); - u8g2.setCursor(96, 47); +u8g2.print("|"); +u8g2.print(bPID.GetKd() / bPID.GetKp(), 0); +u8g2.setCursor(96, 47); - if (pidOutput < 99) { - u8g2.print(pidOutput / 10, 1); - } - else { - u8g2.print(pidOutput / 10, 0); - } +if (pidOutput < 99) { + u8g2.print(pidOutput / 10, 1); +} +else { + u8g2.print(pidOutput / 10, 0); +} - u8g2.print("%"); +u8g2.print("%"); - // Show heater output in % - displayProgressbar(pidOutput / 10, 30, 60, 98); +// Show heater output in % +displayProgressbar(pidOutput / 10, 30, 60, 98); - u8g2.sendBuffer(); +u8g2.sendBuffer(); } diff --git a/src/display/displayTemplateUpright.h b/src/display/displayTemplateUpright.h index f83eac83..ffc27bc5 100644 --- a/src/display/displayTemplateUpright.h +++ b/src/display/displayTemplateUpright.h @@ -18,138 +18,151 @@ void printScreen() { } // If no specific machine state was printed, print default: - if (((machineState == kPidNormal || machineState == kBrewDetectionTrailing) || ((machineState == kBrew || machineState == kShotTimerAfterBrew) && FEATURE_SHOTTIMER == 0) || // shottimer == 0, auch Bezug anzeigen - (machineState == kPidNormal && (setpoint - temperature) > 5. && FEATURE_HEATINGLOGO == 0) || ((machineState == kPidDisabled) && FEATURE_PIDOFF_LOGO == 0)) && - (brewSwitchState != kBrewSwitchFlushOff)) { - if (!tempSensor->hasError()) { - u8g2.clearBuffer(); - u8g2.setCursor(1, 14); - u8g2.print(langstring_current_temp_rot_ur); - u8g2.print(temperature, 1); - u8g2.print(" "); - u8g2.print((char)176); - u8g2.print("C"); - u8g2.setCursor(1, 24); - u8g2.print(langstring_set_temp_rot_ur); - u8g2.print(setpoint, 1); - u8g2.print(" "); - u8g2.print((char)176); - u8g2.print("C"); - - // Draw heat bar - u8g2.drawLine(0, 124, 63, 124); - u8g2.drawLine(0, 124, 0, 127); - u8g2.drawLine(64, 124, 63, 127); - u8g2.drawLine(0, 127, 63, 127); - u8g2.drawLine(1, 125, (pidOutput / 16.13) + 1, 125); - u8g2.drawLine(1, 126, (pidOutput / 16.13) + 1, 126); - - // print heating status - u8g2.setCursor(1, 50); - u8g2.setFont(u8g2_font_profont22_tf); - - if (fabs(temperature - setpoint) < 0.3) { - if (isrCounter < 500) { - u8g2.print("OK"); - } - } - else { - u8g2.print("WAIT"); + if (!tempSensor->hasError()) { + u8g2.clearBuffer(); + u8g2.setCursor(1, 14); + u8g2.print(langstring_current_temp_rot_ur); + u8g2.print(temperature, 1); + u8g2.print(" "); + u8g2.print((char)176); + u8g2.print("C"); + u8g2.setCursor(1, 24); + u8g2.print(langstring_set_temp_rot_ur); + u8g2.print(setpoint, 1); + u8g2.print(" "); + u8g2.print((char)176); + u8g2.print("C"); + + // Draw heat bar + u8g2.drawLine(0, 124, 63, 124); + u8g2.drawLine(0, 124, 0, 127); + u8g2.drawLine(64, 124, 63, 127); + u8g2.drawLine(0, 127, 63, 127); + u8g2.drawLine(1, 125, (pidOutput / 16.13) + 1, 125); + u8g2.drawLine(1, 126, (pidOutput / 16.13) + 1, 126); + + // print heating status + u8g2.setCursor(1, 50); + u8g2.setFont(u8g2_font_profont22_tf); + + if (fabs(temperature - setpoint) < 0.3) { + if (isrCounter < 500) { + u8g2.print("OK"); } + } + else { + u8g2.print("WAIT"); + } - u8g2.setFont(u8g2_font_profont11_tf); - - if (isBrewDetected == 1) { - u8g2.setCursor(1, 75); - u8g2.print("BD "); - u8g2.print((millis() - timeBrewDetection) / 1000, 1); - u8g2.print("/"); - u8g2.print(brewtimesoftware, 0); - } + u8g2.setFont(u8g2_font_profont11_tf); - // PID values above heater output bar - u8g2.setCursor(1, 84); - u8g2.print("P: "); - u8g2.print(bPID.GetKp(), 0); + // PID values above heater output bar + u8g2.setCursor(1, 84); + u8g2.print("P: "); + u8g2.print(bPID.GetKp(), 0); - u8g2.setCursor(1, 93); - u8g2.print("I: "); + u8g2.setCursor(1, 93); + u8g2.print("I: "); - if (bPID.GetKi() != 0) { - u8g2.print(bPID.GetKp() / bPID.GetKi(), 0); - } - else { - u8g2.print("0"); - } - - u8g2.setCursor(1, 102); - u8g2.print("D: "); - u8g2.print(bPID.GetKd() / bPID.GetKp(), 0); + if (bPID.GetKi() != 0) { + u8g2.print(bPID.GetKp() / bPID.GetKi(), 0); + } + else { + u8g2.print("0"); + } - u8g2.setCursor(1, 111); + u8g2.setCursor(1, 102); + u8g2.print("D: "); + u8g2.print(bPID.GetKd() / bPID.GetKp(), 0); - if (pidOutput < 99) { - u8g2.print(pidOutput / 10, 1); - } - else { - u8g2.print(pidOutput / 10, 0); - } + u8g2.setCursor(1, 111); - u8g2.print("%"); + if (pidOutput < 99) { + u8g2.print(pidOutput / 10, 1); + } + else { + u8g2.print(pidOutput / 10, 0); + } - // Brew - u8g2.setCursor(1, 34); - u8g2.print(langstring_brew_rot_ur); - u8g2.print(timeBrewed / 1000, 0); - u8g2.print("/"); + u8g2.print("%"); - if (FEATURE_BREWCONTROL == 0) { - u8g2.print(brewtimesoftware, 0); // deaktivieren wenn Preinfusion ( // voransetzen ) +// Brew time +#if (FEATURE_BREWSWITCH == 1) + if (featureBrewControl) { + // Show brew time; after brew finished show lastBrewTime during SHOTTIMERDISPLAYDELAY + if (machineState == kBrew || (millis() - lastBrewTimeMillis) < SHOTTIMERDISPLAYDELAY) { + u8g2.setCursor(1, 34); + u8g2.print(langstring_brew_rot_ur); + u8g2.print(timeBrewed / 1000, 0); + u8g2.print("/"); + u8g2.print(totalBrewTime / 1000, 0); + u8g2.print(" s"); } - else { - u8g2.print(totalBrewTime / 1000, 0); // aktivieren wenn Preinfusion + + // Flush time + + // Shown flush time while machine is flushing + if (machineState == kManualFlush) { + u8g2.setDrawColor(0); + u8g2.drawBox(1, 34, 100, 15); + u8g2.setDrawColor(1); + u8g2.setCursor(1, 34); + u8g2.print(langstring_manual_flush_rot_ur); + u8g2.print(timeBrewed / 1000, 0); + u8g2.print(" s"); } + } + else { - u8g2.print(" s"); + // Brew Timer with optocoupler - // For status info - u8g2.drawFrame(0, 0, 64, 12); + // Shown brew time while machine is brewing and after the brewing during SHOTTIMERDISPLAYDELAY + if (machineState == kBrew || (millis() - lastBrewTimeMillis) < SHOTTIMERDISPLAYDELAY) { + u8g2.setCursor(1, 34); + u8g2.print(langstring_brew_rot_ur); + u8g2.print(timeBrewed / 1000, 0); + u8g2.print(" s"); + } + } +#endif - if (offlineMode == 0) { - getSignalStrength(); + // For status info + u8g2.drawFrame(0, 0, 64, 12); - if (WiFi.status() == WL_CONNECTED) { - u8g2.drawXBMP(4, 2, 8, 8, Antenna_OK_Icon); + if (offlineMode == 0) { + getSignalStrength(); - for (int b = 0; b <= getSignalStrength(); b++) { - u8g2.drawVLine(13 + (b * 2), 10 - (b * 2), b * 2); - } - } - else { - u8g2.drawXBMP(4, 2, 8, 8, Antenna_NOK_Icon); - u8g2.setCursor(56, 2); - u8g2.print("RC: "); - u8g2.print(wifiReconnects); - } + if (WiFi.status() == WL_CONNECTED) { + u8g2.drawXBMP(4, 2, 8, 8, Antenna_OK_Icon); - if (FEATURE_MQTT == 1) { - if (mqtt.connected() == 1) { - u8g2.setCursor(24, 2); - u8g2.setFont(u8g2_font_profont11_tf); - u8g2.print("MQTT"); - } - else { - u8g2.setCursor(24, 2); - u8g2.print(""); - } + for (int b = 0; b <= getSignalStrength(); b++) { + u8g2.drawVLine(13 + (b * 2), 10 - (b * 2), b * 2); } } else { - u8g2.setCursor(4, 1); - u8g2.print("Offline"); + u8g2.drawXBMP(4, 2, 8, 8, Antenna_NOK_Icon); + u8g2.setCursor(56, 2); + u8g2.print("RC: "); + u8g2.print(wifiReconnects); } - u8g2.sendBuffer(); + if (FEATURE_MQTT == 1) { + if (mqtt.connected() == 1) { + u8g2.setCursor(24, 2); + u8g2.setFont(u8g2_font_profont11_tf); + u8g2.print("MQTT"); + } + else { + u8g2.setCursor(24, 2); + u8g2.print(""); + } + } } + else { + u8g2.setCursor(4, 1); + u8g2.print("Offline"); + } + + u8g2.sendBuffer(); } } diff --git a/src/embeddedWebserver.h b/src/embeddedWebserver.h index 31039067..e9f87ff0 100644 --- a/src/embeddedWebserver.h +++ b/src/embeddedWebserver.h @@ -58,7 +58,7 @@ void serverSetup(); void setEepromWriteFcn(int (*fcnPtr)(void)); // editable vars are specified in main.cpp -#define EDITABLE_VARS_LEN 36 +#define EDITABLE_VARS_LEN 41 extern std::map editableVars; // EEPROM diff --git a/src/languages.h b/src/languages.h index 439c3cdf..6a5327c2 100644 --- a/src/languages.h +++ b/src/languages.h @@ -12,24 +12,23 @@ #if (DISPLAYTEMPLATE <= 4) static const char* langstring_set_temp = "Soll: "; static const char* langstring_current_temp = "Ist: "; -static const char* langstring_brew = "Brew: "; +static const char* langstring_brew = "Bezug: "; +static const char* langstring_weight = "Gewicht: "; +static const char* langstring_manual_flush = "Spuelen: "; +static const char* langstring_pressure = "Druck: "; static const char* langstring_uptime = "Uptime: "; #endif #if (DISPLAYTEMPLATE >= 20) // vertical templates static const char* langstring_set_temp_rot_ur = "S: "; static const char* langstring_current_temp_rot_ur = "I: "; static const char* langstring_brew_rot_ur = "B: "; +static const char* langstring_manual_flush_rot_ur = "S: "; #endif static const char* langstring_offlinemode = "Offline"; -#if TOF == 1 -static const char* langstring_waterempty = "Wasser leer"; -#endif - static const char* langstring_wifirecon = "Wifi reconnect:"; static const char* langstring_connectwifi1 = "1: Verbinde WLAN:"; static const char* langstring_nowifi[] = {"Kein ", "WLAN"}; - static const char* langstring_error_tsensor[] = {"Fehler, Temp: ", "Temp.-Sensor ueberpruefen!"}; // static const char *langstring_emergencyStop[] = {"HEATING", "STOPPED"}; @@ -42,19 +41,19 @@ static const char* langstring_backflush_finish = "um zu beenden..."; static const char* langstring_set_temp = "Set: "; static const char* langstring_current_temp = "Temp: "; static const char* langstring_brew = "Brew: "; +static const char* langstring_weight = "Weight: "; +static const char* langstring_manual_flush = "Flush: "; +static const char* langstring_pressure = "Pressure: "; static const char* langstring_uptime = "Uptime: "; #endif #if (DISPLAYTEMPLATE >= 20) // vertical templates static const char* langstring_set_temp_rot_ur = "S: "; static const char* langstring_current_temp_rot_ur = "T: "; static const char* langstring_brew_rot_ur = "B: "; +static const char* langstring_manual_flush_rot_ur = "F: "; #endif static const char* langstring_offlinemode = "Offline"; -#if TOF == 1 -static const char* langstring_waterempty = "Empty water"; -#endif - static const char* langstring_wifirecon = "Wifi reconnect:"; static const char* langstring_connectwifi1 = "1: Connecting Wifi:"; static const char* langstring_nowifi[] = {"No ", "WIFI"}; @@ -71,19 +70,19 @@ static const char* langstring_backflush_finish = "to finish..."; static const char* langstring_set_temp = "Obj: "; static const char* langstring_current_temp = "T: "; static const char* langstring_brew = "Brew: "; +static const char* langstring_weight = "Peso: "; +static const char* langstring_manual_flush = "Fregar: "; +static const char* langstring_pressure = "Presión: "; static const char* langstring_uptime = "Uptime: "; #endif #if (DISPLAYTEMPLATE >= 20) // vertical templates static const char* langstring_set_temp_rot_ur = "O: "; static const char* langstring_current_temp_rot_ur = "T: "; static const char* langstring_brew_rot_ur = "B: "; +static const char* langstring_manual_flush_rot_ur = "F: "; #endif static const char* langstring_offlinemode = "Offline"; -#if TOF == 1 -static const char* langstring_waterempty = "Agua vacía"; -#endif - static const char* langstring_wifirecon = "Reconecta wifi:"; static const char* langstring_connectwifi1 = "1: Wifi conectado :"; static const char* langstring_nowifi[] = {"No ", "WIFI"}; diff --git a/src/main.cpp b/src/main.cpp index d9f6df9b..d599df85 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,6 +47,11 @@ #include "defaults.h" #include "userConfig.h" // needs to be configured by the user +// Version of userConfig need to match, checked by preprocessor +#if (FW_VERSION != USR_FW_VERSION) || (FW_SUBVERSION != USR_FW_SUBVERSION) || (FW_HOTFIX != USR_FW_HOTFIX) +#error Version of userConfig file and main.cpp need to match! +#endif + hw_timer_t* timer = NULL; #if (FEATURE_PRESSURESENSOR == 1) @@ -68,11 +73,6 @@ hw_timer_t* timer = NULL; #include #endif -// Version of userConfig need to match, checked by preprocessor -#if (FW_VERSION != USR_FW_VERSION) || (FW_SUBVERSION != USR_FW_SUBVERSION) || (FW_HOTFIX != USR_FW_HOTFIX) -#error Version of userConfig file and main.cpp need to match! -#endif - MACHINE machine = (enum MACHINE)MACHINEID; #define HIGH_ACCURACY @@ -81,8 +81,7 @@ enum MachineState { kInit = 0, kPidNormal = 20, kBrew = 30, - kShotTimerAfterBrew = 31, - kBrewDetectionTrailing = 35, + kManualFlush = 35, kSteam = 40, kBackflush = 50, kWaterEmpty = 70, @@ -101,12 +100,14 @@ int lastmachinestatepid = -1; int connectmode = CONNECTMODE; int offlineMode = 0; -const int brewDetectionMode = BREWDETECTION_TYPE; -const int optocouplerType = OPTOCOUPLER_TYPE; const boolean ota = OTA; // Display uint8_t oled_i2c = OLED_I2C; +uint8_t featureShotTimer = FEATURE_SHOT_TIMER; +double shotTimerDisplayDelay = SHOT_TIMER_DISPLAY_DELAY; +uint8_t featureHeatingLogo = FEATURE_HEATING_LOGO; +uint8_t featurePidOffLogo = FEATURE_PID_OFF_LOGO; // WiFi uint8_t wifiCredentialsSaved = 0; @@ -121,28 +122,6 @@ unsigned int wifiReconnects = 0; // actual number of reconnects // OTA const char* OTApass = OTAPASS; -// Backflush values -int backflushCycles = BACKFLUSH_CYCLES; -double backflushFillTime = BACKFLUSH_FILL_TIME; -double backflushFlushTime = BACKFLUSH_FLUSH_TIME; -int backflushOn = 0; -int backflushState = 10; -int currBackflushCycles = 0; // number of active flush cycles - -// Optocoupler -unsigned long previousMillisOptocouplerReading = millis(); -const unsigned long intervalOptocoupler = 200; -int optocouplerOn, optocouplerOff; - -// QuickMill thermoblock steam-mode (only for BREWDETECTION_TYPE = 3) -const int maxBrewDurationForSteamModeQM_ON = 200; // if brewtime is shorter steam-mode starts -const int minOptocouplerOffTimedForSteamModeQM_Off = 1500; // if optocoupler-off-time is longer steam-mode ends -unsigned long timeOptocouplerOn = 0; // time optocoupler switched to ON -unsigned long lastTimeOptocouplerOn = 0; // last time optocoupler was ON -bool steamQM_active = false; // steam-mode is active -bool brewSteamDetectedQM = false; // brew/steam detected, not sure yet what it is -bool coolingFlushDetectedQM = false; - // Pressure sensor #if (FEATURE_PRESSURESENSOR == 1) float inputPressure = 0; @@ -190,8 +169,6 @@ void loopLED(); void checkWater(); void printMachineState(); char const* machinestateEnumToString(MachineState machineState); -void initSteamQM(); -boolean checkSteamOffQM(); char* number2string(double in); char* number2string(float in); char* number2string(int in); @@ -202,15 +179,12 @@ void updateStandbyTimer(void); void resetStandbyTimer(void); // system parameters -uint8_t pidON = 0; // 1 = control loop in closed loop +uint8_t pidON = 0; // 1 = control loop in closed loop +uint8_t usePonM = 0; // 1 = use PonM for cold start PID, 0 = use normal PID for cold start double brewSetpoint = SETPOINT; double brewTempOffset = TEMPOFFSET; double setpoint = brewSetpoint; double steamSetpoint = STEAMSETPOINT; -float scaleCalibration = SCALE_CALIBRATION_FACTOR; -float scale2Calibration = SCALE_CALIBRATION_FACTOR; -float scaleKnownWeight = SCALE_KNOWN_WEIGHT; -uint8_t usePonM = 0; // 1 = use PonM for cold start PID, 0 = use normal PID for cold start double steamKp = STEAMKP; double startKp = STARTKP; double startTn = STARTTN; @@ -218,9 +192,11 @@ double aggKp = AGGKP; double aggTn = AGGTN; double aggTv = AGGTV; double aggIMax = AGGIMAX; -double brewTime = BREW_TIME; // brewtime in s -double preinfusion = PRE_INFUSION_TIME; // preinfusion time in s -double preinfusionPause = PRE_INFUSION_PAUSE_TIME; // preinfusion pause time in s + +// Scale +float scaleCalibration = SCALE_CALIBRATION_FACTOR; +float scale2Calibration = SCALE_CALIBRATION_FACTOR; +float scaleKnownWeight = SCALE_KNOWN_WEIGHT; double weightSetpoint = SCALE_WEIGHTSETPOINT; // PID - values for offline brew detection @@ -236,15 +212,36 @@ double aggbKi = aggbKp / aggbTn; #endif double aggbKd = aggbTv * aggbKp; -double brewtimesoftware = BREW_SW_TIME; // use userConfig time until disabling BD PID -double brewSensitivity = BD_SENSITIVITY; // use userConfig brew detection sensitivity -double brewPIDDelay = BREW_PID_DELAY; // use userConfig brew detection PID delay +double brewPIDDelay = BREW_PID_DELAY; // Time PID will be disabled after brew started uint8_t standbyModeOn = 0; double standbyModeTime = STANDBY_MODE_TIME; #include "standby.h" +// Variables to hold PID values (Temp input, Heater output) +double temperature, pidOutput; +int steamON = 0; +int steamFirstON = 0; + +#if startTn == 0 +double startKi = 0; +#else +double startKi = startKp / startTn; +#endif + +#if aggTn == 0 +double aggKi = 0; +#else +double aggKi = aggKp / aggTn; +#endif + +double aggKd = aggTv * aggKp; + +PID bPID(&temperature, &pidOutput, &setpoint, aggKp, aggKi, aggKd, 1, DIRECT); + +#include "brewHandler.h" + // system parameter EEPROM storage wrappers (current value as pointer to variable, minimum, maximum, optional storage ID) SysPara sysParaPidOn(&pidON, 0, 1, STO_ITEM_PID_ON); SysPara sysParaUsePonM(&usePonM, 0, 1, STO_ITEM_PID_START_PONM); @@ -262,8 +259,6 @@ SysPara sysParaTempOffset(&brewTempOffset, BREW_TEMP_OFFSET_MIN, BREW_TE SysPara sysParaBrewPIDDelay(&brewPIDDelay, BREW_PID_DELAY_MIN, BREW_PID_DELAY_MAX, STO_ITEM_BREW_PID_DELAY); SysPara sysParaUseBDPID(&useBDPID, 0, 1, STO_ITEM_USE_BD_PID); SysPara sysParaBrewTime(&brewTime, BREW_TIME_MIN, BREW_TIME_MAX, STO_ITEM_BREW_TIME); -SysPara sysParaBrewSwTime(&brewtimesoftware, BREW_SW_TIME_MIN, BREW_SW_TIME_MAX, STO_ITEM_BREW_SW_TIME); -SysPara sysParaBrewThresh(&brewSensitivity, BD_THRESHOLD_MIN, BD_THRESHOLD_MAX, STO_ITEM_BD_THRESHOLD); SysPara sysParaWifiCredentialsSaved(&wifiCredentialsSaved, 0, 1, STO_ITEM_WIFI_CREDENTIALS_SAVED); SysPara sysParaPreInfTime(&preinfusion, PRE_INFUSION_TIME_MIN, PRE_INFUSION_TIME_MAX, STO_ITEM_PRE_INFUSION_TIME); SysPara sysParaPreInfPause(&preinfusionPause, PRE_INFUSION_PAUSE_MIN, PRE_INFUSION_PAUSE_MAX, STO_ITEM_PRE_INFUSION_PAUSE); @@ -278,12 +273,16 @@ SysPara sysParaScaleKnownWeight(&scaleKnownWeight, 0, 2000, STO_ITEM_SCAL SysPara sysParaBackflushCycles(&backflushCycles, BACKFLUSH_CYCLES_MIN, BACKFLUSH_CYCLES_MAX, STO_ITEM_BACKFLUSH_CYCLES); SysPara sysParaBackflushFillTime(&backflushFillTime, BACKFLUSH_FILL_TIME_MIN, BACKFLUSH_FILL_TIME_MAX, STO_ITEM_BACKFLUSH_FILL_TIME); SysPara sysParaBackflushFlushTime(&backflushFlushTime, BACKFLUSH_FLUSH_TIME_MIN, BACKFLUSH_FLUSH_TIME_MAX, STO_ITEM_BACKFLUSH_FLUSH_TIME); +SysPara sysParaFeatureBrewControl(&featureBrewControl, 0, 1, STO_ITEM_FEATURE_BREW_CONTROL); +SysPara sysParaFeatureShotTimer(&featureShotTimer, 0, 1, STO_ITEM_FEATURE_SHOT_TIMER); +SysPara sysParaShotTimerDisplayDelay(&shotTimerDisplayDelay, SHOT_TIMER_DISPLAY_DELAY_MIN, SHOT_TIMER_DISPLAY_DELAY_MAX, STO_ITEM_SHOT_TIMER_DISPLAY_DELAY); +SysPara sysParaFeatureHeatingLogo(&featureHeatingLogo, 0, 1, STO_ITEM_FEATURE_HEATING_LOGO); +SysPara sysParaFeaturePidOffLogo(&featurePidOffLogo, 0, 1, STO_ITEM_FEATURE_PID_OFF_LOGO); // Other variables boolean emergencyStop = false; // Emergency stop if temperature is too high const double EmergencyStopTemp = 145; // Temp EmergencyStopTemp float inX = 0, inY = 0, inOld = 0, inSum = 0; // used for filterPressureValue() -boolean brewDetected = 0; boolean setupDone = false; // Water sensor @@ -292,41 +291,12 @@ Timer loopWater(&checkWater, 200); // Check water level every 200 ms int waterCheckConsecutiveReads = 0; // Counter for consecutive readings of water sensor const int waterCountsNeeded = 3; // Number of same readings to change water sensing -// Moving average for software brew detection -unsigned long timeBrewDetection = 0; -int isBrewDetected = 0; // flag is set if brew was detected - // PID controller unsigned long previousMillistemp; // initialisation at the end of init() double setpointTemp; double previousInput = 0; -// Variables to hold PID values (Temp input, Heater output) -double temperature, pidOutput; -int steamON = 0; -int steamFirstON = 0; - -#if startTn == 0 -double startKi = 0; -#else -double startKi = startKp / startTn; -#endif - -#if aggTn == 0 -double aggKi = 0; -#else -double aggKi = aggKp / aggTn; -#endif - -double aggKd = aggTv * aggKp; - -PID bPID(&temperature, &pidOutput, &setpoint, aggKp, aggKi, aggKd, 1, DIRECT); - -#include "brewHandler.h" - -Timer logbrew([&]() { LOGF(DEBUG, "(tB,T,hra) --> %5.2f %6.2f %8.2f", (double)(millis() - startingTime) / 1000, temperature, tempSensor->getAverageTemperatureRate()); }, 500); - // Embedded HTTP Server #include "embeddedWebserver.h" @@ -336,6 +306,7 @@ enum SectionNames { sBDSection, sBrewSection, sScaleSection, + sDisplaySection, sPowerSection, sOtherSection }; @@ -545,126 +516,6 @@ char* number2string(unsigned int in) { return number2string_uint; } -/** - * @brief detect if a brew is running - */ -void brewDetection() { - if (brewDetectionMode == 1 && brewSensitivity == 0) return; // abort brewdetection if deactivated - - // Brew detection: 1 = software solution, 2 = hardware, 3 = voltage sensor - if (brewDetectionMode == 1) { - if (isBrewDetected == 1) { - timeBrewed = millis() - timeBrewDetection; - } - - // deactivate brewtimer after end of brewdetection pid - if (millis() - timeBrewDetection > brewtimesoftware * 1000 && isBrewDetected == 1) { - isBrewDetected = 0; // rearm brewDetection - timeBrewed = 0; - } - } - else if (brewDetectionMode == 2) { - if (millis() - timeBrewDetection > brewtimesoftware * 1000 && isBrewDetected == 1) { - isBrewDetected = 0; // rearm brewDetection - } - } - else if (brewDetectionMode == 3) { - // timeBrewed counter - if ((digitalRead(PIN_BREWSWITCH) == optocouplerOn) && brewDetected == 1) { - timeBrewed = millis() - startingTime; - lastBrewTime = timeBrewed; - } - - // OFF: reset brew - if ((digitalRead(PIN_BREWSWITCH) == optocouplerOff) && (brewDetected == 1 || coolingFlushDetectedQM == true)) { - isBrewDetected = 0; // rearm brewDetection - brewDetected = 0; - timeOptocouplerOn = timeBrewed; // for QuickMill - timeBrewed = 0; - startingTime = 0; - coolingFlushDetectedQM = false; - LOG(DEBUG, "HW Brew - optocoupler - End"); - } - } - - // Activate brew detection - if (brewDetectionMode == 1) { // SW BD - // BD PID only +/- 4 °C, no detection if HW was active - if (tempSensor->getAverageTemperatureRate() <= -brewSensitivity && isBrewDetected == 0 && (fabs(temperature - brewSetpoint) < 5)) { - LOG(DEBUG, "SW Brew detected"); - timeBrewDetection = millis(); - isBrewDetected = 1; - } - } - else if (brewDetectionMode == 2) { // HW BD - if (currBrewState > kBrewIdle && brewDetected == 0) { - LOG(DEBUG, "HW Brew detected"); - timeBrewDetection = millis(); - isBrewDetected = 1; - brewDetected = 1; - } - } - else if (brewDetectionMode == 3) { // voltage sensor - switch (machine) { - case QuickMill: - if (!coolingFlushDetectedQM) { - int pvs = digitalRead(PIN_BREWSWITCH); - - if (pvs == optocouplerOn && brewDetected == 0 && brewSteamDetectedQM == 0 && !steamQM_active) { - timeBrewDetection = millis(); - timeOptocouplerOn = millis(); - isBrewDetected = 1; - brewDetected = 0; - brewSteamDetectedQM = 1; - LOG(DEBUG, "Quick Mill: setting brewSteamDetectedQM = 1"); - logbrew.reset(); - } - - const unsigned long minBrewDurationForSteamModeQM_ON = 50; - if (brewSteamDetectedQM == 1 && millis() - timeOptocouplerOn > minBrewDurationForSteamModeQM_ON) { - if (pvs == optocouplerOff) { - brewSteamDetectedQM = 0; - - if (millis() - timeOptocouplerOn < maxBrewDurationForSteamModeQM_ON) { - LOG(DEBUG, "Quick Mill: steam-mode detected"); - initSteamQM(); - } - else { - LOG(ERROR, "QuickMill: neither brew nor steam"); - } - } - else if (millis() - timeOptocouplerOn > maxBrewDurationForSteamModeQM_ON) { - if (temperature < brewSetpoint + 2) { - LOG(DEBUG, "Quick Mill: brew-mode detected"); - startingTime = timeOptocouplerOn; - brewDetected = 1; - brewSteamDetectedQM = 0; - } - else { - LOG(DEBUG, "Quick Mill: cooling-flush detected"); - coolingFlushDetectedQM = true; - brewSteamDetectedQM = 0; - } - } - } - } - break; - - // no Quickmill: - default: - previousMillisOptocouplerReading = millis(); - - if (digitalRead(PIN_BREWSWITCH) == optocouplerOn && brewDetected == 0) { - LOG(DEBUG, "HW Brew - Voltage Sensor - Start"); - timeBrewDetection = millis(); - startingTime = millis(); - isBrewDetected = 1; - brewDetected = 1; - } - } - } -} - /** * @brief Filter input value using exponential moving average filter (using fixed coefficients) * After ~28 cycles the input is set to 99,66% if the real input value sum of inX and inY @@ -679,32 +530,6 @@ float filterPressureValue(float input) { return inSum; } -void initSteamQM() { - // Initialize monitoring for steam switch off for QuickMill thermoblock - lastTimeOptocouplerOn = millis(); // time when optocoupler changes from ON to OFF - steamQM_active = true; - timeOptocouplerOn = 0; - steamON = 1; -} - -boolean checkSteamOffQM() { - /* Monitor optocoupler during active steam mode of QuickMill - * thermoblock. Once the optocoupler remains OFF for longer than a - * pump-pulse time peride the switch is turned off and steam mode finished. - */ - if (digitalRead(PIN_BREWSWITCH) == optocouplerOn) { - lastTimeOptocouplerOn = millis(); - } - - if ((millis() - lastTimeOptocouplerOn) > minOptocouplerOffTimedForSteamModeQM_Off) { - lastTimeOptocouplerOn = 0; - - return true; - } - - return false; -} - /** * @brief Handle the different states of the machine */ @@ -729,9 +554,8 @@ void handleMachineState() { break; case kPidNormal: - brewDetection(); - if ((timeBrewed > 0 && FEATURE_BREWCONTROL == 0) || (FEATURE_BREWCONTROL == 1 && currBrewState > kBrewIdle && currBrewState <= kBrewFinished)) { + if (brew()) { machineState = kBrew; if (standbyModeOn) { @@ -739,15 +563,15 @@ void handleMachineState() { } } - if (steamON == 1) { - machineState = kSteam; + if (manualFlush()) { + machineState = kManualFlush; if (standbyModeOn) { resetStandbyTimer(); } } - if (backflushOn || backflushState > kBackflushWaitBrewswitchOn) { + if (backflushOn) { machineState = kBackflush; if (standbyModeOn) { @@ -755,6 +579,14 @@ void handleMachineState() { } } + if (steamON == 1) { + machineState = kSteam; + + if (standbyModeOn) { + resetStandbyTimer(); + } + } + if (emergencyStop) { machineState = kEmergencyStop; } @@ -779,58 +611,15 @@ void handleMachineState() { break; case kBrew: - brewDetection(); - - // Output brew time, temp and tempRateAverage during brew (used for SW BD only) - if (FEATURE_BREWDETECTION == 1 && BREWDETECTION_TYPE == 1) { - logbrew(); - } - - if ((timeBrewed == 0 && brewDetectionMode == 3 && FEATURE_BREWCONTROL == 0) || // PID + optocoupler: optocoupler BD timeBrewed == 0 -> switch is off again - ((currBrewState == kBrewIdle || currBrewState == kWaitBrewOff) && FEATURE_BREWCONTROL == 1)) // Hardware BD - { - // delay shot timer display for voltage sensor or hw brew toggle switch (brew counter) - machineState = kShotTimerAfterBrew; - lastBrewTimeMillis = millis(); // for delay - } - else if (brewDetectionMode == 1 && FEATURE_BREWCONTROL == 0 && isBrewDetected == 0) { // SW BD, kBrew was active for set time - // when Software brew is finished, direct to PID BD - machineState = kBrewDetectionTrailing; - } - - if (steamON == 1) { - machineState = kSteam; - } - if (emergencyStop) { - machineState = kEmergencyStop; - } - - if (pidON == 0) { - machineState = kPidDisabled; - } - - if (tempSensor->hasError()) { - machineState = kSensorError; - } - break; - - case kShotTimerAfterBrew: - brewDetection(); - - if (millis() - lastBrewTimeMillis > SHOTTIMERDISPLAYDELAY) { - LOGF(INFO, "Shot time: %4.1f s", lastBrewTime / 1000); - machineState = kBrewDetectionTrailing; + if (!brew()) { + machineState = kPidNormal; } if (steamON == 1) { machineState = kSteam; } - if (backflushOn || backflushState > kBackflushWaitBrewswitchOn) { - machineState = kBackflush; - } - if (emergencyStop) { machineState = kEmergencyStop; } @@ -839,35 +628,21 @@ void handleMachineState() { machineState = kPidDisabled; } - if (!waterFull) { - machineState = kWaterEmpty; - } - if (tempSensor->hasError()) { machineState = kSensorError; } break; - case kBrewDetectionTrailing: - brewDetection(); + case kManualFlush: - if (isBrewDetected == 0) { + if (!manualFlush()) { machineState = kPidNormal; } - if ((timeBrewed > 0 && FEATURE_BREWCONTROL == 0 && brewDetectionMode == 3) || // Allow brew directly after BD only when using FEATURE_BREWCONTROL 0 AND hardware brew switch detection - (FEATURE_BREWCONTROL == 1 && currBrewState > kBrewIdle && currBrewState <= kBrewFinished)) { - machineState = kBrew; - } - if (steamON == 1) { machineState = kSteam; } - if (backflushOn || backflushState > kBackflushWaitBrewswitchOn) { - machineState = kBackflush; - } - if (emergencyStop) { machineState = kEmergencyStop; } @@ -876,10 +651,6 @@ void handleMachineState() { machineState = kPidDisabled; } - if (!waterFull) { - machineState = kWaterEmpty; - } - if (tempSensor->hasError()) { machineState = kSensorError; } @@ -894,10 +665,6 @@ void handleMachineState() { machineState = kEmergencyStop; } - if (backflushOn || backflushState > kBackflushWaitBrewswitchOn) { - machineState = kBackflush; - } - if (pidON == 0) { machineState = kPidDisabled; } @@ -912,6 +679,9 @@ void handleMachineState() { break; case kBackflush: + + backflush(); + if (backflushOn == 0) { machineState = kPidNormal; } @@ -924,7 +694,7 @@ void handleMachineState() { machineState = kPidDisabled; } - if (!waterFull && (backflushState == kBackflushWaitBrewswitchOn || backflushState == kBackflushWaitBrewswitchOff)) { + if (!waterFull && (currBackflushState == kBackflushIdle || currBackflushState == kBackflushFinished)) { machineState = kWaterEmpty; } @@ -984,40 +754,52 @@ void handleMachineState() { #endif } - brewDetection(); + if (pidON) { + pidON = 1; + resetStandbyTimer(); +#if OLED_DISPLAY != 0 + u8g2.setPowerSave(0); +#endif + machineState = kPidNormal; + } + if (steamON) { + pidON = 1; + resetStandbyTimer(); +#if OLED_DISPLAY != 0 + u8g2.setPowerSave(0); +#endif + machineState = kSteam; + } - if (pidON || steamON || isBrewDetected) { + if (brew()) { pidON = 1; resetStandbyTimer(); #if OLED_DISPLAY != 0 u8g2.setPowerSave(0); #endif - if (steamON) { - machineState = kSteam; - } - else if (isBrewDetected) { - machineState = kBrew; - } - else { - machineState = kPidNormal; + if (manualFlush()) { + pidON = 1; + resetStandbyTimer(); +#if OLED_DISPLAY != 0 + u8g2.setPowerSave(0); +#endif } - } - if (tempSensor->hasError()) { - machineState = kSensorError; - } - break; + if (tempSensor->hasError()) { + machineState = kSensorError; + } + break; - case kSensorError: - machineState = kSensorError; - break; + case kSensorError: + machineState = kSensorError; + break; - case kEepromError: - machineState = kEepromError; - break; + case kEepromError: + machineState = kEepromError; + break; + } } - if (machineState != lastmachinestate) { printMachineState(); lastmachinestate = machineState; @@ -1036,10 +818,8 @@ char const* machinestateEnumToString(MachineState machineState) { return "PID Normal"; case kBrew: return "Brew"; - case kShotTimerAfterBrew: - return "Shot Timer After Brew"; - case kBrewDetectionTrailing: - return "Brew Detection Trailing"; + case kManualFlush: + return "Manual Flush"; case kSteam: return "Steam"; case kBackflush: @@ -1301,13 +1081,24 @@ void setup() { .maxValue = STEAM_SETPOINT_MAX, .ptr = (void*)&steamSetpoint}; + editableVars["BREWCONTROL"] = {.displayName = F("Brew Control"), + .hasHelpText = true, + .helpText = F("Enables brew-by-time or brew-by-weight"), + .type = kUInt8, + .section = sBrewSection, + .position = 14, + .show = [] { return true && FEATURE_BREWSWITCH == 1; }, + .minValue = false, + .maxValue = true, + .ptr = (void*)&featureBrewControl}; + editableVars["BREW_TIME"] = {.displayName = F("Brew Time (s)"), .hasHelpText = true, .helpText = F("Stop brew after this time. Set to 0 to deactivate brew-by-time-feature."), .type = kDouble, .section = sBrewSection, - .position = 14, - .show = [] { return true && FEATURE_BREWCONTROL == 1; }, + .position = 15, + .show = [] { return true && featureBrewControl == 1; }, .minValue = BREW_TIME_MIN, .maxValue = BREW_TIME_MAX, .ptr = (void*)&brewTime}; @@ -1317,8 +1108,8 @@ void setup() { .helpText = "", .type = kDouble, .section = sBrewSection, - .position = 15, - .show = [] { return true && FEATURE_BREWCONTROL == 1; }, + .position = 16, + .show = [] { return true && featureBrewControl == 1; }, .minValue = PRE_INFUSION_PAUSE_MIN, .maxValue = PRE_INFUSION_PAUSE_MAX, .ptr = (void*)&preinfusionPause}; @@ -1328,8 +1119,8 @@ void setup() { .helpText = "", .type = kDouble, .section = sBrewSection, - .position = 16, - .show = [] { return true && FEATURE_BREWCONTROL == 1; }, + .position = 17, + .show = [] { return true && featureBrewControl == 1; }, .minValue = PRE_INFUSION_TIME_MIN, .maxValue = PRE_INFUSION_TIME_MAX, .ptr = (void*)&preinfusion}; @@ -1339,8 +1130,8 @@ void setup() { .helpText = "Number of cycles of filling and flushing during a backflush", .type = kInteger, .section = sBrewSection, - .position = 17, - .show = [] { return true && FEATURE_BREWCONTROL == 1; }, + .position = 18, + .show = [] { return true && featureBrewControl == 1; }, .minValue = BACKFLUSH_CYCLES_MIN, .maxValue = BACKFLUSH_CYCLES_MAX, .ptr = (void*)&backflushCycles}; @@ -1350,8 +1141,8 @@ void setup() { .helpText = "Time in seconds the pump is running during one backflush cycle", .type = kDouble, .section = sBrewSection, - .position = 18, - .show = [] { return true && FEATURE_BREWCONTROL == 1; }, + .position = 19, + .show = [] { return true && featureBrewControl == 1; }, .minValue = BACKFLUSH_FILL_TIME_MIN, .maxValue = BACKFLUSH_FILL_TIME_MAX, .ptr = (void*)&backflushFillTime}; @@ -1361,8 +1152,8 @@ void setup() { .helpText = "Time in seconds the selenoid valve stays open during one backflush cycle", .type = kDouble, .section = sBrewSection, - .position = 19, - .show = [] { return true && FEATURE_BREWCONTROL == 1; }, + .position = 20, + .show = [] { return true && featureBrewControl == 1; }, .minValue = BACKFLUSH_FLUSH_TIME_MIN, .maxValue = BACKFLUSH_FLUSH_TIME_MAX, .ptr = (void*)&backflushFlushTime}; @@ -1372,8 +1163,8 @@ void setup() { .helpText = F("Brew is running until this weight has been measured. Set to 0 to deactivate brew-by-weight-feature."), .type = kDouble, .section = sBrewSection, - .position = 20, - .show = [] { return true && FEATURE_SCALE == 1; }, + .position = 21, + .show = [] { return true && FEATURE_SCALE == 1 && featureBrewControl == 1; }, .minValue = WEIGHTSETPOINT_MIN, .maxValue = WEIGHTSETPOINT_MAX, .ptr = (void*)&weightSetpoint}; @@ -1386,7 +1177,7 @@ void setup() { "Silvia. Set to 0 for thermoblock machines."), .type = kDouble, .section = sBDSection, - .position = 21, + .position = 22, .show = [] { return true; }, .minValue = BREW_PID_DELAY_MIN, .maxValue = BREW_PID_DELAY_MAX, @@ -1397,8 +1188,8 @@ void setup() { .helpText = F("Use separate PID parameters while brew is running"), .type = kUInt8, .section = sBDSection, - .position = 22, - .show = [] { return true && FEATURE_BREWDETECTION == 1; }, + .position = 23, + .show = [] { return true && FEATURE_BREWSWITCH == 1; }, .minValue = 0, .maxValue = 1, .ptr = (void*)&useBDPID}; @@ -1415,8 +1206,8 @@ void setup() { "#post-1453641' target='_blank'>Details)"), .type = kDouble, .section = sBDSection, - .position = 23, - .show = [] { return true && FEATURE_BREWDETECTION == 1 && useBDPID; }, + .position = 24, + .show = [] { return true && FEATURE_BREWSWITCH == 1 && useBDPID; }, .minValue = PID_KP_BD_MIN, .maxValue = PID_KP_BD_MAX, .ptr = (void*)&aggbKp}; @@ -1427,8 +1218,8 @@ void setup() { "brewing has been detected."), .type = kDouble, .section = sBDSection, - .position = 24, - .show = [] { return true && FEATURE_BREWDETECTION == 1 && useBDPID; }, + .position = 25, + .show = [] { return true && FEATURE_BREWSWITCH == 1 && useBDPID; }, .minValue = PID_TN_BD_MIN, .maxValue = PID_TN_BD_MAX, .ptr = (void*)&aggbTn}; @@ -1439,50 +1230,24 @@ void setup() { "when brewing has been detected."), .type = kDouble, .section = sBDSection, - .position = 25, - .show = [] { return true && FEATURE_BREWDETECTION == 1 && useBDPID; }, + .position = 26, + .show = [] { return true && FEATURE_BREWSWITCH == 1 && useBDPID; }, .minValue = PID_TV_BD_MIN, .maxValue = PID_TV_BD_MAX, .ptr = (void*)&aggbTv}; - editableVars["PID_BD_TIME"] = {.displayName = F("PID BD Time (s)"), - .hasHelpText = true, - .helpText = F("Fixed time in seconds for which the BD PID will stay " - "enabled (also after Brew switch is inactive again)."), - .type = kDouble, - .section = sBDSection, - .position = 26, - .show = [] { return true && FEATURE_BREWDETECTION == 1 && (useBDPID || BREWDETECTION_TYPE == 1); }, - .minValue = BREW_SW_TIME_MIN, - .maxValue = BREW_SW_TIME_MAX, - .ptr = (void*)&brewtimesoftware}; - - editableVars["PID_BD_SENSITIVITY"] = {.displayName = F("PID BD Sensitivity"), - .hasHelpText = true, - .helpText = F("Software brew detection sensitivity that looks at " - "average temperature, Details. " - "Needs to be >0 also for Hardware switch detection."), - .type = kDouble, - .section = sBDSection, - .position = 27, - .show = [] { return true && BREWDETECTION_TYPE == 1; }, - .minValue = BD_THRESHOLD_MIN, - .maxValue = BD_THRESHOLD_MAX, - .ptr = (void*)&brewSensitivity}; - editableVars["STEAM_MODE"] = { - .displayName = F("Steam Mode"), .hasHelpText = false, .helpText = "", .type = kUInt8, .section = sOtherSection, .position = 28, .show = [] { return false; }, .minValue = 0, .maxValue = 1, .ptr = (void*)&steamON}; + .displayName = F("Steam Mode"), .hasHelpText = false, .helpText = "", .type = kUInt8, .section = sOtherSection, .position = 27, .show = [] { return false; }, .minValue = 0, .maxValue = 1, .ptr = (void*)&steamON}; editableVars["BACKFLUSH_ON"] = { - .displayName = F("Backflush"), .hasHelpText = false, .helpText = "", .type = kUInt8, .section = sOtherSection, .position = 29, .show = [] { return false; }, .minValue = 0, .maxValue = 1, .ptr = (void*)&backflushOn}; + .displayName = F("Backflush"), .hasHelpText = false, .helpText = "", .type = kUInt8, .section = sOtherSection, .position = 28, .show = [] { return false; }, .minValue = 0, .maxValue = 1, .ptr = (void*)&backflushOn}; editableVars["STANDBY_MODE_ON"] = {.displayName = F("Enable Standby Timer"), .hasHelpText = true, .helpText = F("Turn heater off after standby time has elapsed."), .type = kUInt8, .section = sPowerSection, - .position = 30, + .position = 29, .show = [] { return true; }, .minValue = 0, .maxValue = 1, @@ -1490,15 +1255,26 @@ void setup() { editableVars["STANDBY_MODE_TIMER"] = {.displayName = F("Standby Time"), .hasHelpText = true, - .helpText = F("Time in minutes until the heater is turned off. Timer is reset by brew detection."), + .helpText = F("Time in minutes until the heater is turned off. Timer is reset by brew, manual flush, backflush and steam."), .type = kDouble, .section = sPowerSection, - .position = 31, + .position = 30, .show = [] { return true; }, .minValue = STANDBY_MODE_TIME_MIN, .maxValue = STANDBY_MODE_TIME_MAX, .ptr = (void*)&standbyModeTime}; + editableVars["BREWCONTROL"] = {.displayName = F("Enable Brew Control"), + .hasHelpText = true, + .helpText = F("Enables brew-by-time or brew-by-weight"), + .type = kUInt8, + .section = sBrewSection, + .position = 31, + .show = [] { return true && FEATURE_BREWSWITCH == 1; }, + .minValue = false, + .maxValue = true, + .ptr = (void*)&featureBrewControl}; + #if FEATURE_SCALE == 1 editableVars["TARE_ON"] = { .displayName = F("Tare"), .hasHelpText = false, .helpText = "", .type = kUInt8, .section = sScaleSection, .position = 32, .show = [] { return false; }, .minValue = 0, .maxValue = 1, .ptr = (void*)&scaleTareOn}; @@ -1548,8 +1324,51 @@ void setup() { .ptr = (void*)&scale2Calibration}; #endif + editableVars["SHOT_TIMER"] = {.displayName = F("Enable Shot Timer"), + .hasHelpText = true, + .helpText = "Enables a full screen shot and flush timer", + .type = kUInt8, + .section = sDisplaySection, + .position = 37, + .show = [] { return true; }, + .minValue = 0, + .maxValue = 1, + .ptr = (void*)&featureShotTimer}; + + editableVars["SHOT_TIMER_DISPLAY_DELAY"] = {.displayName = F("Shot Timer Display Delay (s)"), + .hasHelpText = true, + .helpText = "time in ms that shot timer will be shown after brew finished", + .type = kDouble, + .section = sDisplaySection, + .position = 38, + .show = [] { return true; }, + .minValue = SHOT_TIMER_DISPLAY_DELAY_MIN, + .maxValue = SHOT_TIMER_DISPLAY_DELAY_MAX, + .ptr = (void*)&shotTimerDisplayDelay}; + + editableVars["HEATING_LOGO"] = {.displayName = F("Enable Heating Logo"), + .hasHelpText = true, + .helpText = "full screen logo will be shown if temperature is 5°C below setpoint", + .type = kUInt8, + .section = sDisplaySection, + .position = 39, + .show = [] { return true; }, + .minValue = 0, + .maxValue = 1, + .ptr = (void*)&featureHeatingLogo}; + + editableVars["PID_OFF_LOGO"] = {.displayName = F("Enable ´PID Disabled´ Logo"), + .hasHelpText = true, + .helpText = "full screen logo will be shown if pid is disabled", + .type = kUInt8, + .section = sDisplaySection, + .position = 40, + .show = [] { return true; }, + .minValue = 0, + .maxValue = 1, + .ptr = (void*)&featurePidOffLogo}; editableVars["VERSION"] = { - .displayName = F("Version"), .hasHelpText = false, .helpText = "", .type = kCString, .section = sOtherSection, .position = 33, .show = [] { return false; }, .minValue = 0, .maxValue = 1, .ptr = (void*)sysVersion}; + .displayName = F("Version"), .hasHelpText = false, .helpText = "", .type = kCString, .section = sOtherSection, .position = 41, .show = [] { return false; }, .minValue = 0, .maxValue = 1, .ptr = (void*)sysVersion}; // when adding parameters, set EDITABLE_VARS_LEN to max of .position #if (FEATURE_PRESSURESENSOR == 1) @@ -1562,8 +1381,6 @@ void setup() { mqttVars["brewTempOffset"] = [] { return &editableVars.at("BREW_TEMP_OFFSET"); }; mqttVars["steamON"] = [] { return &editableVars.at("STEAM_MODE"); }; mqttVars["steamSetpoint"] = [] { return &editableVars.at("STEAM_SETPOINT"); }; - mqttVars["brewPidDelay"] = [] { return &editableVars.at("PID_BD_DELAY"); }; - mqttVars["backflushOn"] = [] { return &editableVars.at("BACKFLUSH_ON"); }; mqttVars["startUsePonM"] = [] { return &editableVars.at("START_USE_PONM"); }; mqttVars["startKp"] = [] { return &editableVars.at("START_KP"); }; mqttVars["startTn"] = [] { return &editableVars.at("START_TN"); }; @@ -1573,38 +1390,18 @@ void setup() { mqttVars["aggIMax"] = [] { return &editableVars.at("PID_I_MAX"); }; mqttVars["steamKp"] = [] { return &editableVars.at("STEAM_KP"); }; mqttVars["standbyModeOn"] = [] { return &editableVars.at("STANDBY_MODE_ON"); }; - - if (FEATURE_BREWCONTROL == 1) { - mqttVars["brewtime"] = [] { return &editableVars.at("BREW_TIME"); }; - mqttVars["preinfusion"] = [] { return &editableVars.at("BREW_PREINFUSION"); }; - mqttVars["preinfusionPause"] = [] { return &editableVars.at("BREW_PREINFUSIONPAUSE"); }; - mqttVars["backflushCycles"] = [] { return &editableVars.at("BACKFLUSH_CYCLES"); }; - mqttVars["backflushFillTime"] = [] { return &editableVars.at("BACKFLUSH_FILL_TIME"); }; - mqttVars["backflushFlushTime"] = [] { return &editableVars.at("BACKFLUSH_FLUSH_TIME"); }; - } - - if (FEATURE_SCALE == 1) { - mqttVars["weightSetpoint"] = [] { return &editableVars.at("SCALE_WEIGHTSETPOINT"); }; - mqttVars["scaleCalibration"] = [] { return &editableVars.at("SCALE_CALIBRATION"); }; -#if SCALE_TYPE == 0 - mqttVars["scale2Calibration"] = [] { return &editableVars.at("SCALE2_CALIBRATION"); }; -#endif - mqttVars["scaleKnownWeight"] = [] { return &editableVars.at("SCALE_KNOWN_WEIGHT"); }; - mqttVars["scaleTareOn"] = [] { return &editableVars.at("TARE_ON"); }; - mqttVars["scaleCalibrationOn"] = [] { return &editableVars.at("CALIBRATION_ON"); }; - } - - if (FEATURE_BREWDETECTION == 1) { - mqttVars["pidUseBD"] = [] { return &editableVars.at("PID_BD_ON"); }; - mqttVars["aggbKp"] = [] { return &editableVars.at("PID_BD_KP"); }; - mqttVars["aggbTn"] = [] { return &editableVars.at("PID_BD_TN"); }; - mqttVars["aggbTv"] = [] { return &editableVars.at("PID_BD_TV"); }; - - if (BREWDETECTION_TYPE == 1) { - mqttVars["brewTimer"] = [] { return &editableVars.at("PID_BD_TIME"); }; - mqttVars["brewLimit"] = [] { return &editableVars.at("PID_BD_SENSITIVITY"); }; - } - } + mqttVars["brewTime"] = [] { return &editableVars.at("BREW_TIME"); }; + mqttVars["preinfusion"] = [] { return &editableVars.at("BREW_PREINFUSION"); }; + mqttVars["preinfusionPause"] = [] { return &editableVars.at("BREW_PREINFUSIONPAUSE"); }; + mqttVars["backflushOn"] = [] { return &editableVars.at("BACKFLUSH_ON"); }; + mqttVars["backflushCycles"] = [] { return &editableVars.at("BACKFLUSH_CYCLES"); }; + mqttVars["backflushFillTime"] = [] { return &editableVars.at("BACKFLUSH_FILL_TIME"); }; + mqttVars["backflushFlushTime"] = [] { return &editableVars.at("BACKFLUSH_FLUSH_TIME"); }; + mqttVars["brewPidDelay"] = [] { return &editableVars.at("PID_BD_DELAY"); }; + mqttVars["pidUseBD"] = [] { return &editableVars.at("PID_BD_ON"); }; + mqttVars["aggbKp"] = [] { return &editableVars.at("PID_BD_KP"); }; + mqttVars["aggbTn"] = [] { return &editableVars.at("PID_BD_TN"); }; + mqttVars["aggbTv"] = [] { return &editableVars.at("PID_BD_TV"); }; // Values reported to MQTT mqttSensors["temperature"] = [] { return temperature; }; @@ -1615,27 +1412,31 @@ void setup() { mqttSensors["currentKd"] = [] { return bPID.GetKd(); }; mqttSensors["machineState"] = [] { return machineState; }; -#if FEATURE_PRESSURESENSOR == 1 - mqttSensors["pressure"] = [] { return inputPressureFilter; }; +#if FEATURE_BREWSWITCH == 1 + mqttSensors["timeBrewed"] = [] { return timeBrewed / 1000; }; #endif #if FEATURE_SCALE == 1 - mqttSensors["currentWeight"] = [] { return weight; }; + mqttVars["weightSetpoint"] = [] { return &editableVars.at("SCALE_WEIGHTSETPOINT"); }; + mqttVars["scaleCalibration"] = [] { return &editableVars.at("SCALE_CALIBRATION"); }; +#if SCALE_TYPE == 0 + mqttVars["scale2Calibration"] = [] { return &editableVars.at("SCALE2_CALIBRATION"); }; +#endif + mqttVars["scaleKnownWeight"] = [] { return &editableVars.at("SCALE_KNOWN_WEIGHT"); }; + mqttVars["scaleTareOn"] = [] { return &editableVars.at("TARE_ON"); }; + mqttVars["scaleCalibrationOn"] = [] { return &editableVars.at("CALIBRATION_ON"); }; + + mqttSensors["currWeight"] = [] { return currWeight; }; + mqttSensors["weightBrewed"] = [] { return weightBrewed; }; #endif +#if FEATURE_PRESSURESENSOR == 1 + mqttSensors["pressure"] = [] { return inputPressureFilter; }; +#endif initTimer1(); storageSetup(); - if (optocouplerType == HIGH) { - optocouplerOn = HIGH; - optocouplerOff = LOW; - } - else { - optocouplerOn = LOW; - optocouplerOff = HIGH; - } - heaterRelay.off(); valveRelay.off(); pumpRelay.off(); @@ -1648,16 +1449,7 @@ void setup() { steamSwitch = new IOSwitch(PIN_STEAMSWITCH, GPIOPin::IN_HARDWARE, STEAMSWITCH_TYPE, STEAMSWITCH_MODE); } - // IF optocoupler selected - if (BREWDETECTION_TYPE == 3) { - if (optocouplerType == HIGH) { - pinMode(PIN_BREWSWITCH, INPUT_PULLDOWN); - } - else { - pinMode(PIN_BREWSWITCH, INPUT_PULLUP); - } - } - else if (FEATURE_BREWSWITCH) { + if (FEATURE_BREWSWITCH) { brewSwitch = new IOSwitch(PIN_BREWSWITCH, GPIOPin::IN_HARDWARE, BREWSWITCH_TYPE, BREWSWITCH_MODE); } @@ -1747,7 +1539,6 @@ void setup() { previousMillistemp = currentTime; windowStartTime = currentTime; previousMillisMQTT = currentTime; - previousMillisOptocouplerReading = currentTime; lastMQTTConnectionAttempt = currentTime; #if FEATURE_SCALE == 1 @@ -1854,9 +1645,7 @@ void looppid() { LOGF(TRACE, "Current PID Output: %f", pidOutput); LOGF(TRACE, "Current Machinestate: %s", machinestateEnumToString(machineState)); LOGF(TRACE, "timeBrewed %f", timeBrewed); - LOGF(TRACE, "brewtimesoftware %f", brewtimesoftware); - LOGF(TRACE, "isBrewDetected %i", isBrewDetected); - LOGF(TRACE, "brewDetectionMode %i", brewDetectionMode); + LOGF(TRACE, "Brew detected %i", brew()); } } @@ -1865,8 +1654,9 @@ void looppid() { shottimerscale(); // Calculation of weight of shot while brew is running #endif -#if (FEATURE_BREWCONTROL == 1) +#if (FEATURE_BREWSWITCH == 1) brew(); + manualFlush(); #endif #if (FEATURE_PRESSURESENSOR == 1) @@ -1899,7 +1689,8 @@ void looppid() { printDisplayTimer(); #endif - if (machineState == kPidDisabled || machineState == kWaterEmpty || machineState == kSensorError || machineState == kEmergencyStop || machineState == kEepromError || machineState == kStandby || brewPIDDisabled) { + if (machineState == kPidDisabled || machineState == kWaterEmpty || machineState == kSensorError || machineState == kEmergencyStop || machineState == kEepromError || machineState == kStandby || + machineState == kBackflush || brewPIDDisabled) { if (bPID.GetMode() == 1) { // Force PID shutdown bPID.SetMode(0); @@ -1936,13 +1727,13 @@ void looppid() { } // BD PID - if (machineState >= kBrew && machineState <= kBrewDetectionTrailing) { + if (machineState == kBrew) { if (brewPIDDelay > 0 && timeBrewed > 0 && timeBrewed < brewPIDDelay * 1000) { // disable PID for brewPIDDelay seconds, enable PID again with new tunings after that if (!brewPIDDisabled) { brewPIDDisabled = true; bPID.SetMode(MANUAL); - LOGF(DEBUG, "disabled PID, waiting for %d seconds before enabling PID again", brewPIDDelay); + LOGF(DEBUG, "disabled PID, waiting for %.0f seconds before enabling PID again", brewPIDDelay); } } else { @@ -2105,8 +1896,6 @@ int readSysParamsFromStorage(void) { if (sysParaPidTnBd.getStorage() != 0) return -1; if (sysParaPidTvBd.getStorage() != 0) return -1; if (sysParaBrewTime.getStorage() != 0) return -1; - if (sysParaBrewSwTime.getStorage() != 0) return -1; - if (sysParaBrewThresh.getStorage() != 0) return -1; if (sysParaPreInfTime.getStorage() != 0) return -1; if (sysParaPreInfPause.getStorage() != 0) return -1; if (sysParaPidKpSteam.getStorage() != 0) return -1; @@ -2121,6 +1910,11 @@ int readSysParamsFromStorage(void) { if (sysParaBackflushCycles.getStorage() != 0) return -1; if (sysParaBackflushFillTime.getStorage() != 0) return -1; if (sysParaBackflushFlushTime.getStorage() != 0) return -1; + if (sysParaFeatureBrewControl.getStorage() != 0) return -1; + if (sysParaFeatureShotTimer.getStorage() != 0) return -1; + if (sysParaShotTimerDisplayDelay.getStorage() != 0) return -1; + if (sysParaFeatureHeatingLogo.getStorage() != 0) return -1; + if (sysParaFeaturePidOffLogo.getStorage() != 0) return -1; return 0; } @@ -2147,8 +1941,6 @@ int writeSysParamsToStorage(void) { if (sysParaPidTnBd.setStorage() != 0) return -1; if (sysParaPidTvBd.setStorage() != 0) return -1; if (sysParaBrewTime.setStorage() != 0) return -1; - if (sysParaBrewSwTime.setStorage() != 0) return -1; - if (sysParaBrewThresh.setStorage() != 0) return -1; if (sysParaPreInfTime.setStorage() != 0) return -1; if (sysParaPreInfPause.setStorage() != 0) return -1; if (sysParaPidKpSteam.setStorage() != 0) return -1; @@ -2163,6 +1955,11 @@ int writeSysParamsToStorage(void) { if (sysParaBackflushCycles.setStorage() != 0) return -1; if (sysParaBackflushFillTime.setStorage() != 0) return -1; if (sysParaBackflushFlushTime.setStorage() != 0) return -1; + if (sysParaFeatureBrewControl.setStorage() != 0) return -1; + if (sysParaFeatureShotTimer.setStorage() != 0) return -1; + if (sysParaShotTimerDisplayDelay.setStorage() != 0) return -1; + if (sysParaFeatureHeatingLogo.setStorage() != 0) return -1; + if (sysParaFeaturePidOffLogo.setStorage() != 0) return -1; return storageCommit(); } diff --git a/src/mqtt.h b/src/mqtt.h index 0b6ac311..bdd8f90d 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -155,31 +155,36 @@ void assignMQTTParam(char* param, double value) { break; case kFloat: - *(float*)var->ptr = value; - mqtt_publish(param, number2string(value), true); + *(float*)var->ptr = static_cast(value); + mqtt_publish(param, number2string(static_cast(value)), true); break; case kUInt8: - *(uint8_t*)var->ptr = value; + *(uint8_t*)var->ptr = static_cast(value); if (strcasecmp(param, "steamON") == 0) { steamFirstON = value; } - mqtt_publish(param, number2string(value), true); + mqtt_publish(param, number2string(static_cast(value)), true); writeSysParamsToStorage(); break; + case kInteger: + *(int*)var->ptr = static_cast(value); + mqtt_publish(param, number2string(static_cast(value)), true); + break; + default: LOGF(WARNING, "%s is not a recognized type for this MQTT parameter.", var->type); break; } } else { - LOGF(WARNING, "Value out of range for MQTT parameter %s", param); + LOGF(WARNING, "%s is not a valid MQTT parameter.", param); } } catch (const std::out_of_range& e) { - LOGF(WARNING, "%s is not a valid MQTT parameter.", param); + LOGF(WARNING, "Value out of range for MQTT parameter %s", param); } } @@ -504,11 +509,15 @@ DiscoveryObject GenerateNumberDevice(String name, String displayName, int min_va * @return 0 if successful, MQTT connection error code if failed to send messages */ int sendHASSIODiscoveryMsg() { - // Number Devices + // Sensor, number and switch objects which will always be published + + DiscoveryObject machineStateDevice = GenerateSensorDevice("machineState", "Machine State", "", "enum"); + DiscoveryObject actual_temperature = GenerateSensorDevice("temperature", "Boiler Temperature", "°C", "temperature"); + DiscoveryObject heaterPower = GenerateSensorDevice("heaterPower", "Heater Power", "%", "power_factor"); + DiscoveryObject brewSetpoint = GenerateNumberDevice("brewSetpoint", "Brew setpoint", BREW_SETPOINT_MIN, BREW_SETPOINT_MAX, 0.1, "°C"); - DiscoveryObject steamSetPoint = GenerateNumberDevice("steamSetpoint", "Steam setpoint", STEAM_SETPOINT_MIN, STEAM_SETPOINT_MAX, 0.1, "°C"); + DiscoveryObject steamSetpoint = GenerateNumberDevice("steamSetpoint", "Steam setpoint", STEAM_SETPOINT_MIN, STEAM_SETPOINT_MAX, 0.1, "°C"); DiscoveryObject brewTempOffset = GenerateNumberDevice("brewTempOffset", "Brew Temp. Offset", BREW_TEMP_OFFSET_MIN, BREW_TEMP_OFFSET_MAX, 0.1, "°C"); - DiscoveryObject brewPidDelay = GenerateNumberDevice("brewPidDelay", "Brew Pid Delay", BREW_PID_DELAY_MIN, BREW_PID_DELAY_MAX, 0.1, ""); DiscoveryObject startKp = GenerateNumberDevice("startKp", "Start kP", PID_KP_START_MIN, PID_KP_START_MAX, 0.1, ""); DiscoveryObject startTn = GenerateNumberDevice("startTn", "Start Tn", PID_TN_START_MIN, PID_TN_START_MAX, 0.1, ""); DiscoveryObject steamKp = GenerateNumberDevice("steamKp", "Start Kp", PID_KP_STEAM_MIN, PID_KP_STEAM_MAX, 0.1, ""); @@ -516,76 +525,56 @@ int sendHASSIODiscoveryMsg() { DiscoveryObject aggTn = GenerateNumberDevice("aggTn", "aggTn", PID_TN_REGULAR_MIN, PID_TN_REGULAR_MAX, 0.1, ""); DiscoveryObject aggTv = GenerateNumberDevice("aggTv", "aggTv", PID_TV_REGULAR_MIN, PID_TV_REGULAR_MAX, 0.1, ""); DiscoveryObject aggIMax = GenerateNumberDevice("aggIMax", "aggIMax", PID_I_MAX_REGULAR_MIN, PID_I_MAX_REGULAR_MAX, 0.1, ""); - -#if FEATURE_BREWCONTROL == 1 - DiscoveryObject brewtime = GenerateNumberDevice("brewtime", "Brew time", BREW_TIME_MIN, BREW_TIME_MAX, 0.1, "s"); + DiscoveryObject brewTime = GenerateNumberDevice("brewTime", "Brew time", BREW_TIME_MIN, BREW_TIME_MAX, 0.1, "s"); DiscoveryObject preinfusion = GenerateNumberDevice("preinfusion", "Preinfusion filling time", PRE_INFUSION_TIME_MIN, PRE_INFUSION_TIME_MAX, 0.1, "s"); DiscoveryObject preinfusionPause = GenerateNumberDevice("preinfusionPause", "Preinfusion pause time", PRE_INFUSION_PAUSE_MIN, PRE_INFUSION_PAUSE_MAX, 0.1, "s"); DiscoveryObject backflushCycles = GenerateNumberDevice("backflushCycles", "Backflush Cycles", BACKFLUSH_CYCLES_MIN, BACKFLUSH_CYCLES_MAX, 1, ""); DiscoveryObject backflushFillTime = GenerateNumberDevice("backflushFillTime", "Backflush filling time", BACKFLUSH_FILL_TIME_MIN, BACKFLUSH_FILL_TIME_MAX, 0.1, "s"); DiscoveryObject backflushFlushTime = GenerateNumberDevice("backflushFlushTime", "Backflush flushing time", BACKFLUSH_FLUSH_TIME_MIN, BACKFLUSH_FLUSH_TIME_MAX, 0.1, "s"); -#endif - - // Sensor Devices - DiscoveryObject actual_temperature = GenerateSensorDevice("temperature", "Boiler Temperature", "°C", "temperature"); - DiscoveryObject heaterPower = GenerateSensorDevice("heaterPower", "Heater Power", "%", "power_factor"); - DiscoveryObject machineStateDevice = GenerateSensorDevice("machineState", "Machine State", "", "enum"); - DiscoveryObject currentWeight = GenerateSensorDevice("currentWeight", "Weight", "g", "weight"); - -#if FEATURE_PRESSURESENSOR == 1 - DiscoveryObject pressure = GenerateSensorDevice("pressure", "Pressure", "bar", "pressure"); -#endif - - // Switch Devices DiscoveryObject pidOn = GenerateSwitchDevice("pidON", "Use PID"); DiscoveryObject steamON = GenerateSwitchDevice("steamON", "Steam"); -#if FEATURE_BREWCONTROL == 1 + DiscoveryObject startUsePonM = GenerateSwitchDevice("startUsePonM", "Use PonM"); DiscoveryObject backflushOn = GenerateSwitchDevice("backflushOn", "Backflush"); + + // List of all DiscoveryObjects, will be always published + std::vector discoveryObjects = { + machineStateDevice, actual_temperature, heaterPower, brewSetpoint, steamSetpoint, brewTempOffset, startKp, startTn, steamKp, aggKp, aggTn, aggTv, aggIMax, + brewTime, preinfusion, preinfusionPause, backflushCycles, backflushFillTime, backflushFlushTime, pidOn, steamON, startUsePonM, backflushOn}; + + // Sensor, number and switch object which will be published based on feature set +#if FEATURE_BREWSWITCH == 1 + + DiscoveryObject timeBrewed = GenerateSensorDevice("timeBrewed", "Time brewed", "s", "duration"); + + DiscoveryObject brewPidDelay = GenerateNumberDevice("brewPidDelay", "Brew Pid Delay", BREW_PID_DELAY_MIN, BREW_PID_DELAY_MAX, 0.1, "s"); + + discoveryObjects.push_back(timeBrewed); + discoveryObjects.push_back(brewPidDelay); #endif - DiscoveryObject startUsePonM = GenerateSwitchDevice("startUsePonM", "Use PonM"); - // Button Devices +#if FEATURE_SCALE == 1 + + DiscoveryObject currWeight = GenerateSensorDevice("currWeight", "Weight", "g", "weight"); + DiscoveryObject weightBrewed = GenerateSensorDevice("weightBrewed", "Weight brewed", "g", "weight"); + DiscoveryObject scaleCalibrateButton = GenerateButtonDevice("scaleCalibrationOn", "Calibrate Scale"); DiscoveryObject scaleTareButton = GenerateButtonDevice("scaleTareOn", "Tare Scale"); - std::vector discoveryObjects = {brewSetpoint, - steamSetPoint, - brewTempOffset, - brewPidDelay, - startKp, - startTn, - steamKp, - aggKp, - aggTn, - aggTv, - aggIMax, - actual_temperature, - heaterPower, - machineStateDevice, -#if FEATURE_BREWCONTROL == 1 - brewtime, - preinfusion, - preinfusionPause, - backflushOn, - backflushCycles, - backflushFillTime, - backflushFlushTime, + DiscoveryObject weightSetpoint = GenerateNumberDevice("weightSetpoint", "Weight setpoint", WEIGHTSETPOINT_MIN, WEIGHTSETPOINT_MAX, 0.1, "g"); + + discoveryObjects.push_back(currWeight); + discoveryObjects.push_back(weightBrewed); + discoveryObjects.push_back(scaleCalibrateButton); + discoveryObjects.push_back(scaleTareButton); + discoveryObjects.push_back(weightSetpoint); #endif - pidOn, - steamON, - startUsePonM #if FEATURE_PRESSURESENSOR == 1 - , - pressure -#endif - }; -#if FEATURE_SCALE == 1 - discoveryObjects.push_back(currentWeight); - discoveryObjects.push_back(scaleCalibrateButton); - discoveryObjects.push_back(scaleTareButton); + DiscoveryObject pressure = GenerateSensorDevice("pressure", "Pressure", "bar", "pressure"); + + discoveryObjects.push_back(pressure); #endif // Send the Objects to Hassio diff --git a/src/scaleHandler.h b/src/scaleHandler.h index 906f2ba7..41ce32c3 100644 --- a/src/scaleHandler.h +++ b/src/scaleHandler.h @@ -78,9 +78,9 @@ void checkWeight() { } #if SCALE_TYPE == 0 - weight = w1 + w2; + currWeight = w1 + w2; #else - weight = w1; + currWeight = w1; #endif if (scaleCalibrationOn) { @@ -177,24 +177,24 @@ void shottimerscale() { case 10: // waiting step for brew switch turning on if (preinfusionPause == 0 || preinfusion == 0) { if (timeBrewed > 0) { - weightPreBrew = weight; + weightPreBrew = currWeight; shottimerCounter = 20; } } else { if (timeBrewed > preinfusion * 1000) { - weightPreBrew = weight; + weightPreBrew = currWeight; shottimerCounter = 20; } else if (timeBrewed > 0) { - weightBrew = 0; + weightBrewed = 0; } } break; case 20: - weightBrew = weight - weightPreBrew; + weightBrewed = currWeight - weightPreBrew; if (timeBrewed == 0) { shottimerCounter = 10; diff --git a/src/steamHandler.h b/src/steamHandler.h index 9dcff5a5..ef56b5aa 100644 --- a/src/steamHandler.h +++ b/src/steamHandler.h @@ -20,20 +20,6 @@ void checkSteamSwitch() { if (steamSwitchReading == LOW && !steamFirstON) { steamON = 0; } - - // monitor QuickMill thermoblock steam-mode - if (machine == QuickMill) { - if (steamQM_active == true) { - if (checkSteamOffQM() == true) { // if true: steam-mode can be turned off - steamON = 0; - steamQM_active = false; - lastTimeOptocouplerOn = 0; - } - else { - steamON = 1; - } - } - } } else if (STEAMSWITCH_TYPE == Switch::MOMENTARY) { if (steamSwitchReading != currStateSteamSwitch) { diff --git a/src/storage.h b/src/storage.h index 71ab26c9..9bf4688e 100644 --- a/src/storage.h +++ b/src/storage.h @@ -24,9 +24,7 @@ typedef enum { STO_ITEM_BREW_TEMP_OFFSET, // brew temp offset STO_ITEM_USE_BD_PID, // use separate PID for brew detection (otherwise continue with regular PID) STO_ITEM_BREW_TIME, // brew time - STO_ITEM_BREW_SW_TIME, // brew software time STO_ITEM_BREW_PID_DELAY, // brew PID delay - STO_ITEM_BD_THRESHOLD, // brew detection limit STO_ITEM_WIFI_CREDENTIALS_SAVED, // flag for wifisetup STO_ITEM_PRE_INFUSION_TIME, // pre-infusion time STO_ITEM_PRE_INFUSION_PAUSE, // pre-infusion pause @@ -46,6 +44,11 @@ typedef enum { STO_ITEM_BACKFLUSH_CYCLES, // number of cycles the backflush should run STO_ITEM_BACKFLUSH_FILL_TIME, // time in ms the pump is running during backflush STO_ITEM_BACKFLUSH_FLUSH_TIME, // time in ms the 3-way valve is open during backflush + STO_ITEM_FEATURE_BREW_CONTROL, // enables function to control pump and solenoid valve + STO_ITEM_FEATURE_SHOT_TIMER, // enables full screen shot timer + STO_ITEM_SHOT_TIMER_DISPLAY_DELAY, // time in ms that shot timer will be shown after brew finished + STO_ITEM_FEATURE_HEATING_LOGO, // enables full screen logo if mashine is heating + STO_ITEM_FEATURE_PID_OFF_LOGO, // enables full screen logo if pid is switched off /* WHEN ADDING NEW ITEMS, THE FOLLOWING HAS TO BE UPDATED: * - storage structure: sto_data_t @@ -94,18 +97,18 @@ typedef struct __attribute__((packed)) { uint8_t freeToUse8[2]; double pidTvBd; uint8_t freeToUse9[2]; - double brewSwTimeSec; + double freeToUse10; double brewPIDDelaySec; - uint8_t freeToUse10; - double brewDetectionThreshold; + uint8_t freeToUse11; + double freeToUse12; uint8_t wifiCredentialsSaved; uint8_t useStartPonM; double pidKpStart; - uint8_t freeToUse12[2]; + uint8_t freeToUse13[2]; uint8_t softApEnabledCheck; - uint8_t freeToUse13[9]; + uint8_t freeToUse14[9]; double pidTnStart; - uint8_t freeToUse14[2]; + uint8_t freeToUse15[2]; char wifiSSID[25 + 1]; char wifiPassword[25 + 1]; double weightSetpoint; @@ -116,6 +119,11 @@ typedef struct __attribute__((packed)) { int backflushCycles; double backflushFillTimeMs; double backflushFlushTimeMs; + uint8_t featureBrewControl; + uint8_t featureShotTimer; + double shotTimerDisplayDelay; + uint8_t featureHeatingLogo; + uint8_t featurePidOffLogo; } sto_data_t; @@ -145,10 +153,10 @@ static const sto_data_t itemDefaults PROGMEM = { {0xFF, 0xFF}, // free to use AGGBTV, // STO_ITEM_PID_TV_BD {0xFF, 0xFF}, // free to use - BREW_SW_TIME, // STO_ITEM_BREW_SW_TIME + 0xFF, // free to use BREW_PID_DELAY, // STO_ITEM_BREW_PID_DELAY 0xFF, // free to use - BD_SENSITIVITY, // STO_ITEM_BD_THRESHOLD + 0xFF, // free to use WIFI_CREDENTIALS_SAVED, // STO_ITEM_WIFI_CREDENTIALS_SAVED 0, // STO_ITEM_USE_START_PON_M STARTKP, // STO_ITEM_PID_KP_START @@ -167,6 +175,11 @@ static const sto_data_t itemDefaults PROGMEM = { BACKFLUSH_CYCLES, // STO_ITEM_BACKFLUSH_CYCLES BACKFLUSH_FILL_TIME, // STO_ITEM_BACKFLUSH_FILLTIME BACKFLUSH_FLUSH_TIME, // STO_ITEM_BACKFLUSH_FLUSHTIME + FEATURE_BREW_CONTROL, // STO_ITEM_FEATURE_BREW_CONTROL + FEATURE_SHOT_TIMER, // STO_ITEM_FEATURE_SHOT_TIMER + SHOT_TIMER_DISPLAY_DELAY, // STO_ITEM_SHOT_TIMER_DISPLAY_DELAY + FEATURE_HEATING_LOGO, // STO_ITEM_FEATURE_HEATING_LOGO + FEATURE_PID_OFF_LOGO, // STO_ITEM_FEATURE_PID_OFF_LOGO }; /** @@ -254,16 +267,6 @@ static inline int32_t getItemAddr(sto_item_id_t itemId, uint16_t* maxItemSize = size = STRUCT_MEMBER_SIZE(sto_data_t, pidTvBd); break; - case STO_ITEM_BREW_SW_TIME: - addr = offsetof(sto_data_t, brewSwTimeSec); - size = STRUCT_MEMBER_SIZE(sto_data_t, brewSwTimeSec); - break; - - case STO_ITEM_BD_THRESHOLD: - addr = offsetof(sto_data_t, brewDetectionThreshold); - size = STRUCT_MEMBER_SIZE(sto_data_t, brewDetectionThreshold); - break; - case STO_ITEM_WIFI_CREDENTIALS_SAVED: addr = offsetof(sto_data_t, wifiCredentialsSaved); size = STRUCT_MEMBER_SIZE(sto_data_t, wifiCredentialsSaved); @@ -359,6 +362,31 @@ static inline int32_t getItemAddr(sto_item_id_t itemId, uint16_t* maxItemSize = size = STRUCT_MEMBER_SIZE(sto_data_t, backflushFlushTimeMs); break; + case STO_ITEM_FEATURE_BREW_CONTROL: + addr = offsetof(sto_data_t, featureBrewControl); + size = STRUCT_MEMBER_SIZE(sto_data_t, featureBrewControl); + break; + + case STO_ITEM_FEATURE_SHOT_TIMER: + addr = offsetof(sto_data_t, featureShotTimer); + size = STRUCT_MEMBER_SIZE(sto_data_t, featureShotTimer); + break; + + case STO_ITEM_SHOT_TIMER_DISPLAY_DELAY: + addr = offsetof(sto_data_t, shotTimerDisplayDelay); + size = STRUCT_MEMBER_SIZE(sto_data_t, shotTimerDisplayDelay); + break; + + case STO_ITEM_FEATURE_HEATING_LOGO: + addr = offsetof(sto_data_t, featureHeatingLogo); + size = STRUCT_MEMBER_SIZE(sto_data_t, featureHeatingLogo); + break; + + case STO_ITEM_FEATURE_PID_OFF_LOGO: + addr = offsetof(sto_data_t, featurePidOffLogo); + size = STRUCT_MEMBER_SIZE(sto_data_t, featurePidOffLogo); + break; + default: LOGF(ERROR, "invalid item ID %i!", itemId); addr = -1; @@ -682,7 +710,7 @@ int storageGet(sto_item_id_t itemId, String& itemValue) { * The value is set in the RAM only! Use 'commit=true' or call * storageCommit() to write the RAM content to the non-volatile memory! * - * @param itemId - storage item ID + * @param itemId - storage item ID * @param itemValue - item value to set * @param commit - true=write current RAM content to NV memory (optional, default=false) * diff --git a/src/userConfig_sample.h b/src/userConfig_sample.h index 7849f7f5..07b800a9 100644 --- a/src/userConfig_sample.h +++ b/src/userConfig_sample.h @@ -29,18 +29,14 @@ enum MACHINE { #define MACHINEID RancilioSilvia // Machine type (see enum MACHINE) // Display -#define OLED_DISPLAY 2 // 0 = deactivated, 1 = SH1106 (e.g. 1.3 "128x64), 2 = SSD1306 (e.g. 0.96" 128x64), 3 = SH1106_126x64_SPI -#define OLED_I2C 0x3C // I2C address for OLED, 0x3C by default -#define DISPLAYTEMPLATE 3 // 1 = Standard display template, 2 = Minimal template, 3 = only temperature, 4 = scale template, 20 = vertical display (see git Handbook for further information) -#define DISPLAYROTATE U8G2_R0 // rotate display clockwise: U8G2_R0 = no rotation; U8G2_R1 = 90°; U8G2_R2 = 180°; U8G2_R3 = 270° -#define SCREEN_WIDTH 128 // OLED display width, in pixels -#define SCREEN_HEIGHT 64 // OLED display height, in pixels -#define FEATURE_SHOTTIMER 1 // 0 = deactivated, 1 = activated (with weight if FEATURE_SCALE activated) -#define FEATURE_HEATINGLOGO 1 // 0 = deactivated, 1 = activated -#define FEATURE_PIDOFF_LOGO 1 // 0 = deactivated, 1 = activated -#define SHOTTIMERDISPLAYDELAY 3000 // time in ms that shot timer will be shown after brew finished - -#define LANGUAGE 0 // LANGUAGE = 0 (DE), LANGUAGE = 1 (EN), LANGUAGE = 2 (ES) +#define OLED_DISPLAY 2 // 0 = deactivated, 1 = SH1106 (e.g. 1.3 "128x64), 2 = SSD1306 (e.g. 0.96" 128x64), 3 = SH1106_126x64_SPI +#define OLED_I2C 0x3C // I2C address for OLED, 0x3C by default +#define DISPLAYTEMPLATE 3 // 1 = Standard display template, 2 = Minimal template, 3 = only temperature, 4 = scale template, 20 = vertical display (see git Handbook for further information) +#define DISPLAYROTATE U8G2_R0 // rotate display clockwise: U8G2_R0 = no rotation; U8G2_R1 = 90°; U8G2_R2 = 180°; U8G2_R3 = 270° +#define SCREEN_WIDTH 128 // OLED display width, in pixels +#define SCREEN_HEIGHT 64 // OLED display height, in pixels + +#define LANGUAGE 0 // LANGUAGE = 0 (DE), LANGUAGE = 1 (EN), LANGUAGE = 2 (ES) // Connectivity #define CONNECTMODE 1 // 0 = offline 1 = WIFI-MODE @@ -50,28 +46,24 @@ enum MACHINE { #define WIFICONNECTIONDELAY 10000 // delay between reconnects in ms // PID & Hardware -#define FEATURE_BREWCONTROL 0 // 0 = deactivated, 1 = activated -#define FEATURE_BREWDETECTION 1 // 0 = deactivated, 1 = activated -#define BREWDETECTION_TYPE 1 // 1 = Software (FEATURE_BREWCONTROL 0), 2 = Hardware (FEATURE_BREWCONTROL 1), 3 = Optocoupler (FEATURE_BREWCONTROL 0) -#define FEATURE_POWERSWITCH 0 // 0 = deactivated, 1 = activated -#define POWERSWITCH_TYPE Switch::TOGGLE // Switch::TOGGLE or Switch::MOMENTARY (trigger) -#define POWERSWITCH_MODE Switch::NORMALLY_OPEN // Switch::NORMALLY_OPEN or Switch::NORMALLY_CLOSED -#define FEATURE_BREWSWITCH 0 // 0 = deactivated, 1 = activated -#define BREWSWITCH_TYPE Switch::TOGGLE // Switch::TOGGLE or Switch::MOMENTARY (trigger) -#define BREWSWITCH_MODE Switch::NORMALLY_OPEN // Switch::NORMALLY_OPEN or Switch::NORMALLY_CLOSED -#define FEATURE_STEAMSWITCH 0 // 0 = deactivated, 1 = activated -#define STEAMSWITCH_TYPE Switch::TOGGLE // Switch::TOGGLE or Switch::MOMENTARY (trigger) -#define OPTOCOUPLER_TYPE HIGH // BREWDETECTION 3 configuration; HIGH or LOW trigger optocoupler -#define STEAMSWITCH_MODE Switch::NORMALLY_OPEN // Switch::NORMALLY_OPEN or Switch::NORMALLY_CLOSED -#define HEATER_SSR_TYPE Relay::HIGH_TRIGGER // HIGH_TRIGGER = relay switches when input is HIGH, vice versa for LOW_TRIGGER -#define PUMP_VALVE_SSR_TYPE Relay::HIGH_TRIGGER // HIGH_TRIGGER = relay switches when input is HIGH, vice versa for LOW_TRIGGER -#define FEATURE_STATUS_LED 0 // Blink status LED when temp is in range, 0 = deactivated, 1 = activated -#define FEATURE_BREW_LED 0 // Turn on brew LED when brew is started, 0 = deactivated, 1 = activated -#define LED_TYPE LED::STANDARD // STANDARD_LED for an LED connected to a GPIO pin, WS2812 for adressable LEDs -#define FEATURE_WATER_SENS 0 // 0 = deactivated, 1 = activated -#define WATER_SENS_TYPE Switch::NORMALLY_CLOSED // Switch::NORMALLY_CLOSED for sensor XKC-Y25-NPN or Switch::NORMALLY_OPEN for XKC-Y25-PNP - -#define FEATURE_PRESSURESENSOR 0 // 0 = deactivated, 1 = activated +#define FEATURE_POWERSWITCH 0 // 0 = deactivated, 1 = activated +#define POWERSWITCH_TYPE Switch::TOGGLE // Switch::TOGGLE or Switch::MOMENTARY (trigger) +#define POWERSWITCH_MODE Switch::NORMALLY_OPEN // Switch::NORMALLY_OPEN or Switch::NORMALLY_CLOSED +#define FEATURE_BREWSWITCH 0 // 0 = deactivated, 1 = activated +#define BREWSWITCH_TYPE Switch::TOGGLE // Switch::TOGGLE or Switch::MOMENTARY (trigger) +#define BREWSWITCH_MODE Switch::NORMALLY_OPEN // Switch::NORMALLY_OPEN or Switch::NORMALLY_CLOSED +#define FEATURE_STEAMSWITCH 0 // 0 = deactivated, 1 = activated +#define STEAMSWITCH_TYPE Switch::TOGGLE // Switch::TOGGLE or Switch::MOMENTARY (trigger) +#define STEAMSWITCH_MODE Switch::NORMALLY_OPEN // Switch::NORMALLY_OPEN or Switch::NORMALLY_CLOSED +#define HEATER_SSR_TYPE Relay::HIGH_TRIGGER // HIGH_TRIGGER = relay switches when input is HIGH, vice versa for LOW_TRIGGER +#define PUMP_VALVE_SSR_TYPE Relay::HIGH_TRIGGER // HIGH_TRIGGER = relay switches when input is HIGH, vice versa for LOW_TRIGGER +#define FEATURE_STATUS_LED 0 // Blink status LED when temp is in range, 0 = deactivated, 1 = activated +#define FEATURE_BREW_LED 0 // Turn on brew LED when brew is started, 0 = deactivated, 1 = activated +#define LED_TYPE LED::STANDARD // STANDARD_LED for an LED connected to a GPIO pin, WS2812 for adressable LEDs +#define FEATURE_WATER_SENS 0 // 0 = deactivated, 1 = activated +#define WATER_SENS_TYPE Switch::NORMALLY_CLOSED // Switch::NORMALLY_CLOSED for sensor XKC-Y25-NPN or Switch::NORMALLY_OPEN for XKC-Y25-PNP + +#define FEATURE_PRESSURESENSOR 0 // 0 = deactivated, 1 = activated // Brew Scale #define FEATURE_SCALE 0 // 0 = deactivated, 1 = activated