main/automation.c

changeset 0
b74b0e4902c3
child 1
ad2c8b13eb88
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/automation.c	Sat Oct 20 13:23:15 2018 +0200
@@ -0,0 +1,1272 @@
+/**
+ * @file automation.c
+ * @brief Automation functions.
+ */
+
+#include "config.h"
+
+int	    			BoilPower = 100;
+int				LastMashStep = 0;
+char        			temp_buf[64];
+char				logline[128];
+char				strftime_buf[64];
+bool        			loop;
+bool				CoolBeep = false;
+bool				Resume = false;
+bool				pumpRest = false;
+bool				updateRuntime = false;
+bool        			NewMinute = false;
+bool				TempReached = false;
+uint8_t     			MashState = MASH_NONE;
+float       			temp_MLT;
+float				MinMash = 38.0;
+float				MaxMash = 80.0;
+uint32_t    			power_MLT = 0;
+uint32_t			power_HLT = 0;
+uint32_t			counts = 0;
+float                           stageTemp = 0.0;
+uint16_t                        stageTime = 0;
+uint16_t                        TimeWhirlPool = 0;
+uint32_t                        TimeLeft = 0;
+uint32_t                        TimeSpent = 0;
+uint32_t                        SecsCount = 0;
+uint32_t                        pumpTime = 0;
+uint32_t                        TimeBrewing = 0;
+uint16_t                        Steady = 0;
+bool                            _NewMinute = false;
+bool                            _UseHLT = false;
+bool				_Prompt = false;
+
+extern bool			System_TimeOk;
+extern sButton			Buttons[MAXBUTTONS];
+extern int			Main_Screen;
+extern DS18B20_State            *ds18b20_state;
+extern DRIVER_State             *driver_state;
+extern SemaphoreHandle_t        xSemaphoreDS18B20;
+extern SemaphoreHandle_t        xSemaphoreDriver;
+extern double                   Output;
+extern time_t			now;
+extern struct tm		timeinfo;
+
+#ifdef CONFIG_TEMP_SENSORS_SIMULATOR
+extern float                    Fake_MLT;
+extern float                    Fake_HLT;
+#endif
+
+static const char		*TAG = "automation";
+
+
+/*
+ * Automation init function that only runs once when a
+ * new screen is entered.
+ */
+bool Automation_Init(void)
+{
+    switch (Main_Screen) {
+	case MAIN_AUTO_INIT:
+#ifdef CONFIG_TEMP_SENSORS_SIMULATOR
+		Fake_MLT = recipe.MashStep[0].Temperature - 10;
+		Fake_HLT = recipe.SpargeTemp - 15;
+                if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) {
+                    ds18b20_state->mlt_temperature = ((int)(Fake_MLT * 16)) / 16.0;
+                    ds18b20_state->hlt_temperature = ((int)(Fake_HLT * 16)) / 16.0;
+                    xSemaphoreGive(xSemaphoreDS18B20);
+                }
+#endif
+                for (int i = 0; i < 7; i++) {
+                    if (recipe.MashStep[i].Resttime)
+                        LastMashStep = i;
+                }
+                ESP_LOGI(TAG, "Last mash step %d", LastMashStep);
+
+                // Check for a crashed session.
+                if (runtime.AutoModeStarted) {
+                    TopMessage("Brouwen hervatten?");
+                    Buttons_Add( 40, 100, 80, 40, "Ja", 0);
+                    Buttons_Add(200, 100, 80, 40, "Nee",  1);
+                    Buttons_Show();
+                    SoundPlay(SOUND_Prompt);
+                    loop = true;
+                    while (loop) {
+                        switch (Buttons_Scan()) {
+                            case 0:     loop = false;
+                                        Resume = true;
+                                        Main_Screen = runtime.StageResume;
+                                        TimeLeft = runtime.StageTimeLeft;
+					TimeBrewing = runtime.TimeBrewing;
+					_UseHLT = runtime.UseHLT;
+                                        MashState = MASH_NONE;
+                                        pumpTime = 0;
+                                        pumpRest = false;
+					if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+					    driver_state->enable = true;
+					    if (_UseHLT) {
+					    	driver_state->hlt_sp = recipe.SpargeTemp;
+						driver_state->hlt_mode = HLT_MODE_BANG;
+					    }
+					    xSemaphoreGive(xSemaphoreDriver);
+					}
+					ESP_LOGI(TAG, "Resume brew screen %d, time left %d", Main_Screen, TimeLeft);
+					log_begin((time_t)0);
+					update_json();
+					log_annotation(ANNOTATION_SYSTEM, "Resume");
+					return true;
+                                        break;
+
+                            case 1:     loop = false;
+                                        Resume = false;
+                                        break;
+
+                            default:
+                                        break;
+                        }
+                        vTaskDelay(50 / portTICK_PERIOD_MS);
+                    }
+                    Buttons_Clear();
+                    TFT_fillScreen(_bg);
+                }
+
+                if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                    driver_state->enable = true;
+                    xSemaphoreGive(xSemaphoreDriver);
+                }
+                runtime.AutoModeStarted = true;
+                runtime.UseHLT = _UseHLT = false;
+		runtime.TimeBrewing = 0;
+                TimeBrewing = 0;
+		runtime.StageResume = MAIN_AUTO_INIT;
+		runtime.StageTimeLeft = 0;
+		runtime.HopAddition = 0;
+		runtime.Logfile[0] = '\0';
+		runtime.PumpCooling = false;
+		write_runtime();
+                power_MLT = power_HLT = counts = 0;
+		log_clean();
+		vTaskDelay(250 / portTICK_PERIOD_MS);	// Allow some time
+                break;
+
+	case MAIN_AUTO_DELAYSTART:
+		break;
+
+	case MAIN_AUTO_HEATUP:
+		if (runtime.UseHLT) {
+		    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+			driver_state->hlt_mode = HLT_MODE_BANG;
+			xSemaphoreGive(xSemaphoreDriver);
+		    }
+		    TopMessage("Spoelwater opwarmen");
+		    MLT_info(71, 26, false);
+		    HLT_info(71,150, false, false);
+		}
+		break;
+
+	case MAIN_AUTO_MASH_IN:
+        case MAIN_AUTO_MASH_1:
+        case MAIN_AUTO_MASH_2:
+        case MAIN_AUTO_MASH_3:
+        case MAIN_AUTO_MASH_4:
+        case MAIN_AUTO_MASH_5:
+        case MAIN_AUTO_MASH_6:
+        case MAIN_AUTO_MASH_OUT:
+                if (Main_Screen == MAIN_AUTO_MASH_IN) {
+                    MinMash = 38.0;
+                    MaxMash = recipe.MashStep[1].Temperature + 10.0;
+                    TimeBrewing = 0;
+		    runtime.TimeBrewing = 0;
+                    if (System_TimeOk) {
+                        time(&now);
+                        localtime_r(&now, &timeinfo);
+                        log_begin(now);
+			runtime.BrewStart = now;
+                    } else {
+                        log_begin((time_t)0);
+			runtime.BrewStart = (time_t)0;
+                    }
+		    updateRuntime = true;
+                } else if (Main_Screen == MAIN_AUTO_MASH_OUT) {
+                    MinMash = 75.0;
+                    MaxMash = 80.0;
+                } else {
+                    MinMash = recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN - 1].Temperature;
+                    if (recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN + 1].Resttime) {
+                        MaxMash = recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN + 1].Temperature;
+                    } else {
+                        MaxMash = 75.0;
+                    }
+                }
+                MashState = MASH_NONE;
+                pumpTime = 0;
+                pumpRest = false;
+		runtime.StageResume = Main_Screen;
+		updateRuntime = true;
+                TopMessage("Maischen");
+                MLT_info(71, 26, false);
+                if (_UseHLT) {
+                    HLT_info(71,170, false, true);
+                }
+                break;
+
+	case MAIN_AUTO_TOBOIL:
+                if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                    driver_state->mlt_sp = stageTemp = config.BoilTemperature;
+                    driver_state->mlt_mode = MLT_MODE_EXT;
+                    driver_state->hlt_sp = 0.0;
+                    driver_state->hlt_mode = HLT_MODE_NONE;
+                    xSemaphoreGive(xSemaphoreDriver);
+                }
+
+                runtime.StageResume = Main_Screen;
+                updateRuntime = true;
+		TempReached = false;
+
+                TopMessage("Naar koken");
+                MLT_info(71, 26, false);
+                Buttons_Add(  5,  30, 60, 40, "+sp",  0);
+                Buttons_Add(255,  30, 60, 40, "-sp",  1);
+                Buttons_Show();
+                ESP_LOGI(TAG, "Mash done, going to boil.");
+                break;
+
+	case MAIN_AUTO_BOILING:
+                if (Resume) {
+                    TimeLeft = runtime.StageTimeLeft * 60;
+                } else {
+                    // +1 minute for flameout and 2 seconds for a smooth transition.
+                    runtime.StageTimeLeft = TimeLeft = (recipe.BoilTime * 60) + 60;
+                    runtime.StageResume = Main_Screen;
+                    runtime.HopAddition = 0;
+                }
+                SecsCount = 0;
+
+                if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                    driver_state->mlt_sp = stageTemp = config.BoilTemperature;
+                    driver_state->mlt_mode = MLT_MODE_EXT;
+                    xSemaphoreGive(xSemaphoreDriver);
+                }
+                SoundPlay(SOUND_TempReached);
+                BoilPower = equipment.BoilPower;
+                updateRuntime = true;
+                TopMessage("Koken");
+                MLT_info(71, 26, false);
+                Buttons_Add(  3,  30, 60, 40, "+sp",  0);
+                Buttons_Add(257,  30, 60, 40, "-sp",  1);
+                Buttons_Add(  3, 190, 60, 40, "+1m",  2);
+                Buttons_Add(257, 190, 60, 40, "-1m",  3);
+                Buttons_Show();
+                ESP_LOGI(TAG, "Boil temperature reached, boil %d minutes", (TimeLeft / 60) -1);
+		log_annotation(ANNOTATION_STAGE, "Koken");
+                break;
+
+	case MAIN_AUTO_COOLING_H:
+	case MAIN_AUTO_COOLING_M:
+	case MAIN_AUTO_COOLING_C:
+		TempReached = false;
+                runtime.StageResume = Main_Screen;
+                runtime.StageTimeLeft = 0;
+                updateRuntime = true;
+                if ((Main_Screen == MAIN_AUTO_COOLING_H) && (! recipe.Whirlpool7)) {
+                    // Skip cooling before whirlpool 74 degrees
+                    Main_Screen = MAIN_AUTO_COOLING_M;
+                    return true; //goto startover;
+                }
+                if ((Main_Screen == MAIN_AUTO_COOLING_M) && (! recipe.Whirlpool6)) {
+                    // Skip cooling before whirlpool 63 degrees.
+                    Main_Screen = MAIN_AUTO_COOLING_C;
+                    return true; //goto startover;
+                }
+                TopMessage("Start koelen?");
+                Buttons_Add( 40, 100, 80, 40, "Start", 0);
+                Buttons_Add(200, 100, 80, 40, "Stop",  1);
+		Buttons[1].dark = true;
+                Buttons_Show();
+                SoundPlay(SOUND_Prompt);
+		_Prompt = true;
+                break;
+
+	case MAIN_AUTO_WHIRLPOOL7:
+	case MAIN_AUTO_WHIRLPOOL6:
+	case MAIN_AUTO_WHIRLPOOL2:
+		TempReached = true;
+                runtime.StageResume = Main_Screen;
+                updateRuntime = true;
+                if ((Main_Screen == MAIN_AUTO_WHIRLPOOL9) && (! recipe.Whirlpool9)) {
+                    // Skip whirlpool 93 degrees.
+                    Main_Screen = MAIN_AUTO_COOLING_H;
+                    return true; //goto startover;
+                }
+                if ((Main_Screen == MAIN_AUTO_WHIRLPOOL7) && (! recipe.Whirlpool7)) {
+                    // Skip whirlpool 74 degrees.
+                    Main_Screen = MAIN_AUTO_COOLING_M;
+                    return true; //goto startover;
+                }
+                if ((Main_Screen == MAIN_AUTO_WHIRLPOOL6) && (! recipe.Whirlpool6)) {
+                    // Skip whirlpool 63 degrees.
+                    Main_Screen = MAIN_AUTO_COOLING_C;
+                    return true; //goto startover;
+                }
+                if ((Main_Screen == MAIN_AUTO_WHIRLPOOL2) && (! recipe.Whirlpool2)) {
+                    // Skip final whirlpool.
+                    Main_Screen = MAIN_AUTO_DONE;
+                    return true; //goto startover;
+                }
+
+                TopMessage("Start Whirlpool?");
+                Buttons_Add( 40, 100, 80, 40, "Start", 0);
+                Buttons_Add(200, 100, 80, 40, "Stop",  1);
+		Buttons[1].dark = true;
+                Buttons_Show();
+                SoundPlay(SOUND_Prompt);
+		_Prompt = true;
+                break;
+
+	case MAIN_AUTO_DONE:
+	case MAIN_AUTO_ABORT:
+                if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                    driver_state->enable = false;
+                    driver_state->mlt_mode = MLT_MODE_NONE;
+                    driver_state->mlt_sp = 0.0;
+                    driver_state->hlt_mode = HLT_MODE_NONE;
+                    driver_state->hlt_sp = 0.0;
+                    driver_state->pump_run = 0;
+                    xSemaphoreGive(xSemaphoreDriver);
+                }
+                _fg = TFT_YELLOW;
+                TFT_setFont(DEJAVU24_FONT, NULL);
+                if (Main_Screen == MAIN_AUTO_DONE) {
+                    TFT_print("Brouwen is gereed.", CENTER, CENTER);
+                    ESP_LOGI(TAG, "Brew is done");
+                    SoundPlay(SOUND_End);
+                } else {
+                    TFT_print("Brouwen is afgebroken.", CENTER, CENTER);
+                    ESP_LOGI(TAG, "Brew is aborted");
+                    SoundPlay(SOUND_Warn);
+                }
+                log_close();
+		runtime.Logfile[0] = '\0';
+		runtime.BrewStart = (time_t)0;
+                runtime.AutoModeStarted = false;
+                runtime.StageResume = MAIN_MODE_FREE;
+		runtime.PumpCooling = false;
+		runtime.HopAddition = 0;
+		runtime.TimeBrewing = 0;
+		runtime.StageTimeLeft = 0;
+                updateRuntime = true;
+                Buttons_Add(130, 200, 60, 40, "Ok", 0);
+		Buttons[0].dark = true;
+                Buttons_Show();
+                break;
+
+	default:
+		break;
+    }
+
+    return false;
+}
+
+
+
+/*
+ * Automation loop screens. Mostly non-blocking.
+ */
+bool Automation_Loop(void)
+{
+    static bool beeped = false;
+    char	tmp[32];
+    uint16_t	y;
+
+    switch (Main_Screen) {
+
+	case MAIN_AUTO_INIT:
+		/*
+		 * Present selected equipment and recipe.
+		 */
+		read_recipe(config.RecipeRec);
+		y = 28;
+		TopMessage("Automaat");
+		TFT_setFont(DEFAULT_FONT, NULL);
+		ShowText(2,y,"Installatie", equipment.Name);
+		y += 16;
+		ShowText(2,y,"Recept", recipe.Name);
+		y += 16;
+		ShowFloat(2, y, "Maisch in", " C", recipe.MashStep[0].Temperature, 2);
+		ShowFloat(162, y, "Spoelwater", " C", recipe.SpargeTemp, 2);
+		y += 16;
+		_fg = TFT_WHITE;
+		TFT_print("Maisch stap", 2, y);
+		TFT_print("Temp.", 200, y);
+		TFT_print("Rust", 260, y);
+		_fg = TFT_YELLOW;
+		y += 16;
+		for (int i = 1; i < 8; i++) {
+		    if (recipe.MashStep[i].Resttime) {
+			TFT_print(recipe.MashStep[i].Name, 2, y);
+			sprintf(tmp, "%.2f", recipe.MashStep[i].Temperature);
+			TFT_print(tmp, 200, y);
+			sprintf(tmp, "%2d min", recipe.MashStep[i].Resttime);
+			TFT_print(tmp, 260, y);
+			y += 16;
+		    }
+		}
+		ShowInteger(2, y, "Kooktijd", " miniuten", recipe.BoilTime);
+		y += 16;
+		if (recipe.Additions) {
+		    _fg = TFT_YELLOW;
+		    sprintf(tmp, "%d ", recipe.Additions);
+		    TFT_print(tmp, 2, y);
+		    _fg = TFT_WHITE;
+		    TFT_print("toevoegingen om", LASTX, y);
+		    _fg = TFT_YELLOW;
+		    for (int i = 1; i <= recipe.Additions; i++) {
+			sprintf(tmp, " %d", recipe.Addition[i-1].Time);
+			TFT_print(tmp, LASTX, y);
+		    }
+		    _fg = TFT_WHITE;
+		    TFT_print(" minuten", LASTX, y);
+		} else {
+		    _fg = TFT_WHITE;
+		    TFT_print("Geen hop toevoegingen.", 2, y);
+		}
+		y += 16;
+		ShowFloat(2, y, "Koelen tot", " C", recipe.CoolTemp, 2);
+		if (recipe.Whirlpool9) {
+		    ShowInteger(2, y, "Whirlpool 88..100 graden", " minuten", recipe.Whirlpool9);
+		    y += 16;
+		}
+		if (recipe.Whirlpool7) {
+		    ShowInteger(2, y, "Whirlpool 71..77 graden", " minuten", recipe.Whirlpool7);
+		    y += 16;
+		}
+		if (recipe.Whirlpool6) {
+		    ShowInteger(2, y, "Whirlpool 60..66 graden", " minuten", recipe.Whirlpool6);
+		    y += 16;
+		}
+		if (recipe.Whirlpool2) {
+		    ShowInteger(2, y, "Whirlpool koud", " minuten", recipe.Whirlpool2);
+		    y += 16;
+		}
+		Buttons_Add(  0, 210, 70, 30, "Stop"   , 0);
+		Buttons_Add(250, 210, 70, 30, "Start"  , 1);
+		Buttons[0].dark = true;
+		Buttons_Show();
+		loop = true;
+		while (loop) {
+		    switch (Buttons_Scan()) {
+			case 0:         loop = false;
+					Main_Screen = MAIN_AUTO_ABORT;
+					break;
+
+			case 1:         loop = false;
+					break;
+
+			default:        break;
+		    }
+		    vTaskDelay(20 / portTICK_PERIOD_MS);
+		}
+		if (Main_Screen == MAIN_AUTO_ABORT)
+		    break;
+
+                _UseHLT = false;
+		_bg = TFT_BLACK;
+		TFT_fillScreen(_bg);
+                TopMessage("Maisch water aanwezig?");
+		Buttons_Clear();
+                Buttons_Add( 40, 100, 80, 40, "Ja", 0);
+                Buttons_Add(200, 100, 80, 40, "Nee",  1);
+                Buttons_Show();
+                SoundPlay(SOUND_Prompt);
+                loop = true;
+                while (loop) {
+                    switch (Buttons_Scan()) {
+                        case 0:         loop = false;
+                                        break;
+
+                        case 1:         loop = false;
+                                        Main_Screen = MAIN_AUTO_ABORT;
+                                        break;
+
+                        default:        break;
+                    }
+                    vTaskDelay(20 / portTICK_PERIOD_MS);
+                }
+                if (Main_Screen == MAIN_AUTO_ABORT)
+                    break;
+
+		if ((equipment.SSR2 == SSR2_HLT_SHARE) || (equipment.SSR2 == SSR2_HLT_IND)) {
+                    TopMessage("Spoelwater aanwezig?");
+                    SoundPlay(SOUND_Prompt);
+                    loop = true;
+                    while (loop) {
+                    	switch (Buttons_Scan()) {
+                            case 0:         loop = false;
+                                        _UseHLT = true;
+                                        break;
+
+                            case 1:         loop = false;
+                                        break;
+
+                            default:        break;
+                    	}
+                   	vTaskDelay(20 / portTICK_PERIOD_MS);
+                    }
+                    runtime.UseHLT = _UseHLT;
+		} else {
+		    runtime.UseHLT = _UseHLT = false;
+		}
+                updateRuntime = true;
+                if (_UseHLT) {
+                    /*
+                     * Calculate HLT setpoint for pre-heat. Substract the
+                     * available Mash rest times, asume 0.5 degrees/minute
+                     * heat capacity during mash.
+                     */
+                    int AvailableTime = 0;
+                    for (int i = 1; i < 6; i++) // Only normal Mash steps
+                        AvailableTime += recipe.MashStep[i].Resttime;
+                    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                        driver_state->hlt_sp = recipe.SpargeTemp - ((AvailableTime / 2) + 2);
+                        ESP_LOGI(TAG, "HLT preheat set to %4.1f", driver_state->hlt_sp);
+                        xSemaphoreGive(xSemaphoreDriver);
+                    }
+                }
+                Buttons_Clear();
+                Main_Screen = MAIN_AUTO_DELAYSTART;
+                break;
+
+	case MAIN_AUTO_DELAYSTART:
+                Main_Screen = MAIN_AUTO_HEATUP;
+                break;
+
+	case MAIN_AUTO_HEATUP:
+                if (! runtime.UseHLT) {         // Skip if HLT is off
+                    Main_Screen = MAIN_AUTO_MASH_IN;
+                    break;
+                }
+
+                MLT_info(71, 26, true);
+                HLT_info(71,150, true, false);
+                if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                    if (driver_state->hlt_pv >= driver_state->hlt_sp) {
+                        Main_Screen = MAIN_AUTO_MASH_IN;
+                        driver_state->hlt_sp = recipe.SpargeTemp;       // Set final setpoint
+                    }
+                    xSemaphoreGive(xSemaphoreDriver);
+                }
+                break;
+
+	case MAIN_AUTO_MASH_IN:
+	case MAIN_AUTO_MASH_1:
+	case MAIN_AUTO_MASH_2:
+	case MAIN_AUTO_MASH_3:
+	case MAIN_AUTO_MASH_4:
+	case MAIN_AUTO_MASH_5:
+	case MAIN_AUTO_MASH_6:
+	case MAIN_AUTO_MASH_OUT:
+                temp_MLT = 0.0;
+                if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                    temp_MLT = driver_state->mlt_pv;
+
+		    if (MashState == MASH_ADD || MashState == MASH_REMOVE) {
+			driver_state->pump_run = 0;
+		    } else if (MashState != MASH_NONE) {
+                        if (Main_Screen == MAIN_AUTO_MASH_IN) {
+                            driver_state->pump_run = (equipment.PumpPreMash && ! pumpRest) ? 1 : 0;
+                        } else if (Main_Screen == MAIN_AUTO_MASH_OUT) {
+                            driver_state->pump_run = (equipment.PumpMashOut && ! pumpRest) ? 1 : 0;
+                        } else {
+                            driver_state->pump_run = (equipment.PumpOnMash && ! pumpRest) ? 1 : 0;
+                        }
+                    }
+                    xSemaphoreGive(xSemaphoreDriver);
+                }
+                if (MashState == MASH_NONE) {
+                    stageTemp = recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN].Temperature;
+                    stageTime = recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN].Resttime;
+                    TempReached = false;
+                    if (stageTime == 0) {
+                        ESP_LOGI(TAG, "Mash step %d skipped", Main_Screen - MAIN_AUTO_MASH_IN);
+                        Main_Screen++;
+                        break;
+                    }
+                    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                        driver_state->mlt_sp = stageTemp;
+                        driver_state->mlt_mode = MLT_MODE_PID;
+                        xSemaphoreGive(xSemaphoreDriver);
+                    }
+                    MashState = MASH_WAITTEMP;
+                    ESP_LOGI(TAG, "Mash step %d time: %d temp: %4.1f  min: %4.1f max: %4.1f",
+                                    Main_Screen - MAIN_AUTO_MASH_IN, stageTime, stageTemp, MinMash, MaxMash);
+
+		    if (Main_Screen > MAIN_AUTO_MASH_IN) {
+			// Do not annotate before the log is open.
+			if (Main_Screen == MAIN_AUTO_MASH_OUT) {
+			    log_annotation(ANNOTATION_STAGE, "Uitmaischen");
+			} else {
+                    	    sprintf(logline, "Maisch: %d", Main_Screen - MAIN_AUTO_MASH_IN);
+		    	    log_annotation(ANNOTATION_STAGE, logline);
+			}
+		    }
+
+                    if (strlen(recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN].Name)) {
+                        TopMessage(recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN].Name);
+                    } else {
+                        sprintf(temp_buf, "Maisch stap #%d", Main_Screen - MAIN_AUTO_MASH_IN);
+                        TopMessage(temp_buf);
+                    }
+                    Buttons_Add(  5,  30, 60, 40, "+sp",   0);
+                    Buttons_Add(255,  30, 60, 40, "-sp",   1);
+                    Buttons_Show();
+
+                } else if (MashState == MASH_WAITTEMP) {
+                    pumpRest = false;
+                    if (temp_MLT < stageTemp) {
+                        Steady = 0;
+                    }
+                    if ((temp_MLT >= stageTemp) && (Steady > 10)) {
+                        SoundPlay(SOUND_TempReached);
+                        TempReached = true;
+                        MashState = MASH_REST;
+                        if (Main_Screen == MAIN_AUTO_MASH_IN) {
+                            TimerSet(0);
+                        } else {
+                            if (Resume && (runtime.StageTimeLeft < stageTime))
+                                TimerSet(runtime.StageTimeLeft * 60);
+                            else
+                                TimerSet(stageTime * 60);
+                        }
+			Resume = false;
+			runtime.StageTimeLeft = TimeLeft / 60;
+			updateRuntime = true;
+                        ESP_LOGI(TAG, "Mash step %d temperature reached, rest time %d", Main_Screen - MAIN_AUTO_MASH_IN, TimeLeft / 60);
+                        Buttons_Clear();
+                        Buttons_Add(  0, 120, 60, 40, "+1m",   0);
+                        Buttons_Add(260, 120, 60, 40, "-1m",   1);
+                        Buttons_Show();
+                    }
+                    switch (Buttons_Scan()) {
+                        case 0:         if (stageTemp < MaxMash) {
+                                            stageTemp += 0.25;
+                                            if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                                                driver_state->mlt_sp = stageTemp;
+                                                xSemaphoreGive(xSemaphoreDriver);
+                                            }
+                                        }
+                                        break;
+
+                        case 1:         if (stageTemp > MinMash) {
+                                            stageTemp -= 0.25;
+                                            if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                                                driver_state->mlt_sp = stageTemp;
+                                                xSemaphoreGive(xSemaphoreDriver);
+                                            }
+                                        }
+                                        break;
+
+                        default:
+                                        break;
+                    }
+		    if (NewMinute)
+			updateRuntime = true;
+
+                } else if (MashState == MASH_REST) {
+                    /*
+                     * Mash step rest time and pump control
+                     */
+                    if (((Main_Screen == MAIN_AUTO_MASH_OUT) && equipment.PumpMashOut) ||
+                        ((Main_Screen != MAIN_AUTO_MASH_OUT) && equipment.PumpOnMash)) {
+                        float DeltaTemp = equipment.PumpRest * stageTemp / 120; // Maximum temperature drop before heating again.
+                        if (pumpTime >= (equipment.PumpCycle + equipment.PumpRest) || ((stageTemp - temp_MLT) > DeltaTemp)) {
+                            pumpTime = 0;
+                        }
+                        if (pumpTime >= equipment.PumpCycle) {
+                            if (! pumpRest) {
+                                pumpRest = true;
+                                ESP_LOGI(TAG, "Pump rest");
+                            }
+                        } else {
+                            if (pumpRest) {
+                                pumpRest = false;
+                                ESP_LOGI(TAG, "Pump start");
+                            }
+                        }
+                    }
+                    if (TimeLeft) {
+                        switch (Buttons_Scan()) {
+                            case 0:     TimeLeft += 60;
+					runtime.StageTimeLeft = TimeLeft / 60;
+					updateRuntime = true;
+                                        break;
+
+                            case 1:     if (TimeLeft < 60)
+                                            TimeLeft = 0;
+                                        else
+                                            TimeLeft -= 60;
+					runtime.StageTimeLeft = TimeLeft / 60;
+					updateRuntime = true;
+                                        break;
+
+                            default:    break;
+                        }
+                    }
+
+                    if (TimeLeft == 0) {
+			runtime.StageTimeLeft = TimeLeft / 60;
+			updateRuntime = true;
+                        if ((Main_Screen == MAIN_AUTO_MASH_IN) && config.AskAdd) {
+                            /*
+                             * Add Mash prompt.
+                             */
+			    log_annotation(ANNOTATION_EVENT, "Mout storten");
+                            Buttons_Clear();
+                            Buttons_Add(  5,120, 60, 40, "Halt", 0);
+			    Buttons[0].dark = true;
+                            Buttons_Add(255,120, 60, 40, "Ok",   1);
+                            Buttons_Show();
+                            _fg = TFT_WHITE;
+                            _bg = TFT_BLACK;
+                            TFT_setFont(DEJAVU24_FONT, NULL);
+                            TFT_print("Mout storten?", CENTER, 135);
+                            SoundPlay(SOUND_Prompt);
+			    MashState = MASH_ADD;
+			    ESP_LOGI(TAG, "Mash add prompt");
+			    break;
+                        }
+                        if (((Main_Screen - MAIN_AUTO_MASH_IN) == LastMashStep) && config.AskIodine) {
+                            /*
+                             * Iodone test prompt.
+                             */
+			    log_annotation(ANNOTATION_EVENT, "Jodium test");
+                            TFT_fillRect(0, 120, 320, 50, TFT_BLACK);
+                            Buttons_Clear();
+                            Buttons_Add(  5,120, 60, 40, "Halt", 0);
+			    Buttons[0].dark = true;
+                            Buttons_Add(255,120, 60, 40, "Ok",   1);
+                            Buttons_Show();
+                            _fg = TFT_WHITE;
+                            _bg = TFT_BLACK;
+                            TFT_setFont(DEJAVU24_FONT, NULL);
+                            TFT_print("Jodium test?", CENTER, 127);
+                            SoundPlay(SOUND_Prompt);
+                            beeped = false;
+                            TimerSet(config.IodineTime * 60);
+			    MashState = MASH_IODINE;
+			    ESP_LOGI(TAG, "Mash iodine test prompt");
+			    break;
+                        }
+                        if ((Main_Screen == MAIN_AUTO_MASH_OUT) && config.AskRemove) {
+                            /*
+                             * Mash remove prompt.
+                             */
+			    log_annotation(ANNOTATION_EVENT, "Mout verwijderen");
+                            TFT_fillRect(0, 120, 320, 50, TFT_BLACK);
+                            Buttons_Clear();
+                            Buttons_Add(  5,120, 60, 40, "Halt", 0);
+			    Buttons[0].dark = true;
+                            Buttons_Add(255,120, 60, 40, "Ok",   1);
+                            Buttons_Show();
+                            _fg = TFT_WHITE;
+                            _bg = TFT_BLACK;
+                            TFT_setFont(DEJAVU18_FONT, NULL);
+                            TFT_print("Mout verwijderen?", CENTER, 135);
+                            SoundPlay(SOUND_Prompt);
+			    MashState = MASH_REMOVE;
+			    ESP_LOGI(TAG, "Mash remove prompt");
+			    break;
+                        }
+                        if (Main_Screen != MAIN_AUTO_ABORT)
+                            Main_Screen++;
+                        TempReached = false;
+                    } else {
+                        TimerShow(TimeLeft, 65, 122);
+			if (NewMinute) {
+			    runtime.StageTimeLeft = TimeLeft / 60;
+			    updateRuntime = true;
+			}
+                    }
+		} else if (MashState == MASH_ADD) {
+		    switch (Buttons_Scan()) {
+			case 0:     Main_Screen = MAIN_AUTO_ABORT;
+				    break;
+			case 1:     Main_Screen++;
+				    break;
+			default:    break;
+		    }
+		} else if (MashState == MASH_IODINE) {
+		    if (TimeSpent % 45 == 0) {
+			if (! beeped) {
+			    SoundPlay(SOUND_Warn);
+			    beeped = true;
+			}
+		    } else {
+			beeped = false;
+		    }
+		    switch (Buttons_Scan()) {
+			case 0:     Main_Screen = MAIN_AUTO_ABORT;
+				    break;
+			case 1:     Main_Screen++;
+				    break;
+			default:    break;
+		    }
+		    if (TimeLeft == 0) {
+			Main_Screen++;
+		    }
+		} else if (MashState == MASH_REMOVE) {
+		    switch (Buttons_Scan()) {
+			case 0:     Main_Screen = MAIN_AUTO_ABORT;
+				    break;
+			case 1:     Main_Screen++;
+				    break;
+			default:    break;
+		    }
+                } /* MashState */
+                MLT_info(71, 26, true);
+                if (_UseHLT) {
+                    HLT_info(71, 170, true, true);
+                }
+                break;
+
+	case MAIN_AUTO_TOBOIL:
+                Output = 255;
+                /*
+                 * Go to the boil temperature and wait until it is steady.
+                 */
+                if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                    if (driver_state->mlt_pv < stageTemp) {
+                        Steady = 0;
+                    } else {
+                        if (Steady > 10) {
+                            Main_Screen = MAIN_AUTO_BOILING;
+			    TempReached = true;
+                        }
+                    }
+                    driver_state->pump_run = (equipment.PumpOnBoil && (driver_state->mlt_pv < equipment.PumpMaxTemp)) ? 1 : 0;
+                    xSemaphoreGive(xSemaphoreDriver);
+                }
+
+                MLT_info(71, 26, true);
+                switch (Buttons_Scan()) {
+                    case 0:     if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                                    driver_state->mlt_sp += 0.25;
+                                    xSemaphoreGive(xSemaphoreDriver);
+                                }
+                                break;
+
+                    case 1:     if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                                    driver_state->mlt_sp -= 0.25;
+                                    xSemaphoreGive(xSemaphoreDriver);
+                                }
+                                break;
+
+                    default:    break;
+                }
+		if (Resume)
+		    Resume = false;
+                break;
+
+	case MAIN_AUTO_BOILING:
+		if (Resume)
+		    Resume = false;
+                if (NewMinute) {
+                    if ((runtime.HopAddition < recipe.Additions) && (TimeLeft <= ((recipe.Addition[runtime.HopAddition].Time * 60) + 60))) {
+                        ESP_LOGI(TAG, "Hop addition %d at %d minutes", runtime.HopAddition + 1, recipe.Addition[runtime.HopAddition].Time);
+                        TopMessage(recipe.Addition[runtime.HopAddition].Name);
+                        sprintf(logline, "Hopgift %d %s", runtime.HopAddition + 1, recipe.Addition[runtime.HopAddition].Name);
+			log_annotation(ANNOTATION_EVENT, logline);
+                        SoundPlay(SOUND_AddHop);
+                        runtime.HopAddition++;
+                    } else {
+                        TopMessage("Koken");
+                    }
+                    runtime.StageTimeLeft = TimeLeft / 60;
+                    updateRuntime = true;
+                }
+                if (TimeLeft < 60) {
+		    if (Output) {
+			log_annotation(ANNOTATION_STAGE, "Vlamuit");
+		    }
+                    // Flameout
+                    Output = 0;
+                } else if (driver_state->mlt_pv >= stageTemp) {
+                    Output = (int)((BoilPower * 255.0) / 100.0);
+                    if (Buttons[4].x == -1) {
+                        Buttons_Add(  3,110, 60, 40, "+%", 4);
+                        Buttons_Add(257,110, 60, 40, "-%", 5);
+                        Buttons_Show();
+                    }
+                } else {
+                    Output = 255;
+                    if (Buttons[4].x != -1) {
+                        Buttons[4].x = Buttons[5].x = -1;
+                        Buttons_Show();
+                        TFT_fillRect(  3,110, 60, 40, TFT_BLACK);
+                        TFT_fillRect(257,110, 60, 40, TFT_BLACK);
+                    }
+                }
+
+                MLT_info(71, 26, true);
+                TimerShow(TimeLeft, 65, 190);
+
+                switch (Buttons_Scan()) {
+                    case 0:     if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                                    driver_state->mlt_sp += 0.25;
+                                    xSemaphoreGive(xSemaphoreDriver);
+                                }
+                                break;
+
+                    case 1:     if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                                    driver_state->mlt_sp -= 0.25;
+                                    xSemaphoreGive(xSemaphoreDriver);
+                                }
+                                break;
+
+                    case 2:     TimeLeft += 60;
+                                break;
+
+                    case 3:     if (TimeLeft > 60)
+                                    TimeLeft -= 60;
+                                else
+                                    TimeLeft = 0;
+                                break;
+
+                    case 4:     if (BoilPower < 100)
+                                    BoilPower++;
+                                break;
+
+                    case 5:     if (BoilPower > 0)
+                                    BoilPower--;
+                                break;
+
+                    default:    break;
+                }
+
+                if (TimeLeft == 0) {
+                    Main_Screen = MAIN_AUTO_WHIRLPOOL9;
+                    ESP_LOGI(TAG, "Boil is ready");
+                }
+                break;
+
+	case MAIN_AUTO_COOLING_H:
+	case MAIN_AUTO_COOLING_M:
+	case MAIN_AUTO_COOLING_C:
+		if (_Prompt) {
+		    /*
+		     * Prompt mode
+		     */
+		    switch (Buttons_Scan()) {
+			case 0: _Prompt = false;
+				Buttons_Clear();
+				break;
+			case 1: Main_Screen = MAIN_AUTO_DONE;
+				Buttons_Clear();
+				return true; //goto startover;
+				break;
+			default:
+				break;
+		    }
+
+		    if (! _Prompt) {
+			/*
+			 * Starting cooling, setup the screen.
+			 */
+			Buttons_Clear();
+			TFT_fillScreen(_bg);
+			if (Main_Screen == MAIN_AUTO_COOLING_H) {
+			    stageTemp = 77.0;
+			} else if (Main_Screen == MAIN_AUTO_COOLING_M) {
+			    stageTemp = 66.0;
+			} else {
+			    stageTemp = recipe.CoolTemp;
+			}
+			CoolBeep = false;
+			ESP_LOGI(TAG, "Start cooling from %6.2f to %4.1f", ds18b20_state->mlt_temperature, stageTemp);
+			if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+			    driver_state->mlt_mode = MLT_MODE_OFF;
+			    driver_state->mlt_sp = stageTemp;
+			    xSemaphoreGive(xSemaphoreDriver);
+			}
+			log_annotation(ANNOTATION_STAGE, "Koelen");
+			TopMessage("Koelen");
+			MLT_info(71, 26, false);
+			Buttons_Add(  5, 200, 60, 40, "Stop", 0);
+			Buttons[0].dark = true;
+			Buttons_Add(  5,  26, 60, 40, "+1",   1);
+			Buttons_Add(255,  26, 60, 40, "-1",   2);
+			/*
+			 * The next key is not a mistake, but we need a key entry which
+			 * will later become the pump key. The keyscan routine will find
+			 * the original key if pressed.
+			 */
+			Buttons_Add(255,  26, 60, 40, "-1",   3);
+			Buttons_Show();
+		    }
+		} else {
+		    /*
+		     * Not in prompt mode.
+		     */
+#ifdef CONFIG_TEMP_SENSORS_SIMULATOR
+		    if (Fake_MLT > 12.0) {
+			if (driver_state->pump_run)
+			    Fake_MLT -= 0.00025 * (Fake_MLT - 12.0);
+			else
+			    Fake_MLT -= 0.00015 * (Fake_MLT - 12.0);
+		    }
+		    if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) {
+			ds18b20_state->mlt_temperature = ((int)(Fake_MLT * 16)) / 16.0;
+			xSemaphoreGive(xSemaphoreDS18B20);
+		    }
+#endif
+                    MLT_info(71, 26, true);
+                    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                    	/*
+                    	 * If the pump safe temperature is reached, add the control button.
+		    	 * Redefine key number 3 if it is at the position of key 2.
+                    	 */
+                    	if ((driver_state->mlt_pv < equipment.PumpMaxTemp) && (Buttons[3].x == Buttons[2].x) &&(Buttons[3].y == Buttons[2].y)) {
+                            Buttons_Add(255, 200, 60, 40, "Pomp", 3);
+                            Buttons_Show();
+                    	}
+                    	xSemaphoreGive(xSemaphoreDriver);
+                    }
+                    switch (Buttons_Scan()) {
+                    	case 1:	if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                                    if (Main_Screen == MAIN_AUTO_COOLING_H) {
+                                        if (driver_state->mlt_sp < 77.0)
+                                            driver_state->mlt_sp += 1.0;
+                                    } else if (Main_Screen == MAIN_AUTO_COOLING_M) {
+                                        if (driver_state->mlt_sp < 66.0)
+                                            driver_state->mlt_sp += 1.0;
+                                    } else if (Main_Screen == MAIN_AUTO_COOLING_C) {
+                                        if (driver_state->mlt_sp < 30.0)
+                                            driver_state->mlt_sp += 1.0;
+                                    }
+                                    xSemaphoreGive(xSemaphoreDriver);
+                                }
+                                break;
+
+                    	case 0: Buttons_Add( 60, 150, 90, 40, "Stoppen", 4);
+				Buttons[4].dark = true;
+                                Buttons_Add(170, 150, 90, 40, "Sorry",   5);
+                                Buttons_Show();
+                                break;
+
+                    	case 2: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                                    if (Main_Screen == MAIN_AUTO_COOLING_H) {
+                                        if (driver_state->mlt_sp > 71.0)
+                                            driver_state->mlt_sp -= 1.0;
+                                    } else if (Main_Screen == MAIN_AUTO_COOLING_M) {
+                                        if (driver_state->mlt_sp > 60.0)
+                                            driver_state->mlt_sp -= 1.0;
+                                    } else if (Main_Screen == MAIN_AUTO_COOLING_C) {
+                                        if (driver_state->mlt_sp > 10.0)
+                                            driver_state->mlt_sp -= 1.0;
+                                    }
+                                    xSemaphoreGive(xSemaphoreDriver);
+                                }
+                                break;
+
+                    	case 3: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                                    if (driver_state->mlt_pv < equipment.PumpMaxTemp) {
+                                        if (driver_state->pump_run)
+                                            driver_state->pump_run = 0;
+                                        else
+                                            driver_state->pump_run = 1;
+                                    } else {
+                                        driver_state->pump_run = 0;
+                                    }
+				    runtime.PumpCooling = driver_state->pump_run;
+				    updateRuntime = true;
+                                    xSemaphoreGive(xSemaphoreDriver);
+                                }
+                                break;
+
+		    	case 4:	Main_Screen++;
+				break;
+
+		    	case 5:	Buttons[4].x = Buttons[5].x = -1;
+				TFT_fillRect(60, 150, 200, 40, TFT_BLACK);
+				break;
+
+		    	default:
+				break;
+                    }
+
+                    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                    	if (! CoolBeep && (driver_state->mlt_pv < (driver_state->mlt_sp + 2.0))) {
+                            SoundPlay(SOUND_Warn);
+                            CoolBeep = true;
+                    	}
+                    	if (driver_state->mlt_pv <= driver_state->mlt_sp) {
+                            SoundPlay(SOUND_TempReached);
+                            Main_Screen++;
+                    	}
+                    	xSemaphoreGive(xSemaphoreDriver);
+                    }
+		}
+                break;
+
+	case MAIN_AUTO_WHIRLPOOL9:
+	case MAIN_AUTO_WHIRLPOOL7:
+	case MAIN_AUTO_WHIRLPOOL6:
+	case MAIN_AUTO_WHIRLPOOL2:
+		if (_Prompt) {
+
+		    switch (Buttons_Scan()) {
+			case 0: _Prompt = false;
+				break;
+			case 1: if (Main_Screen == MAIN_AUTO_WHIRLPOOL2) {
+				    Main_Screen = MAIN_AUTO_DONE;
+				} else {
+				    Main_Screen++;
+				}
+				Buttons_Clear();
+				return true; //goto startover;
+				break;
+			default:
+				break;
+		    }
+
+		    if (! _Prompt) {
+			/*
+			 * Prepare the screen for the actual whirpool.
+			 */
+			Buttons_Clear();
+			TFT_fillScreen(_bg);
+			if (Main_Screen == MAIN_AUTO_WHIRLPOOL9) {
+			    TimeWhirlPool = recipe.Whirlpool9;
+			    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+				driver_state->mlt_sp = 93.0;
+				driver_state->mlt_mode = MLT_MODE_PID;
+				xSemaphoreGive(xSemaphoreDriver);
+			    }
+			    TopMessage("Whirlpool 88..100");
+			} else if (Main_Screen == MAIN_AUTO_WHIRLPOOL7) {
+			    TimeWhirlPool = recipe.Whirlpool7;
+			    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+				driver_state->mlt_sp = 74.0;
+				driver_state->mlt_mode = MLT_MODE_PID;
+				xSemaphoreGive(xSemaphoreDriver);
+			    }
+			    TopMessage("Whirlpool 71..77");
+			} else if (Main_Screen == MAIN_AUTO_WHIRLPOOL6) {
+			    TimeWhirlPool = recipe.Whirlpool6;
+			    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+				driver_state->mlt_sp = 63.0;
+				driver_state->mlt_mode = MLT_MODE_PID;
+				xSemaphoreGive(xSemaphoreDriver);
+			    }
+			    TopMessage("Whirlpool 60..66");
+			} else {
+			    TimeWhirlPool = recipe.Whirlpool2;
+			    TopMessage("Koude whirlpool");
+			}
+			if (Resume) {
+			    TimeWhirlPool = runtime.StageTimeLeft;
+			}
+
+			/*
+			 * If the pump is allowed at the current temperature, turn it on.
+			 */
+			if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+			    driver_state->pump_run = (driver_state->mlt_pv < equipment.PumpMaxTemp) ? 1 : 0;
+			    xSemaphoreGive(xSemaphoreDriver);
+			}
+			log_annotation(ANNOTATION_STAGE, "Whirlpool");
+
+			TimerSet(TimeWhirlPool * 60);
+			runtime.StageTimeLeft = TimeWhirlPool;
+			updateRuntime = true;
+			MLT_info(71, 26, false);
+			ESP_LOGI(TAG, "Whirlpool %d minutes, sp %4.1f", TimeWhirlPool, driver_state->mlt_sp);
+			Buttons_Add(255, 120, 60, 40, "+1m", 0);
+			Buttons_Add(  5, 120, 60, 40, "-1m", 1);
+			Buttons_Add(130, 200, 60, 40, "Pomp",  2);
+			Buttons_Show();
+		    }
+		} else {
+		    /*
+		     * Not running in prompt mode, do the whirlpool.
+		     */
+                    if (TimeLeft == 120) {
+                    	/*
+                    	 * Drop the temperature when whirlpool is almost ready.
+                    	 * If we are lucky the heater element will cool down so
+                    	 * the next cooling stage will not wast too much energy.
+                    	 */
+                    	if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                            if (Main_Screen == MAIN_AUTO_WHIRLPOOL9)
+                            	driver_state->mlt_sp = 88.0;
+                            if (Main_Screen == MAIN_AUTO_WHIRLPOOL7)
+                            	driver_state->mlt_sp = 71.0;
+                            if (Main_Screen == MAIN_AUTO_WHIRLPOOL6)
+                            	driver_state->mlt_sp = 60.0;
+                            xSemaphoreGive(xSemaphoreDriver);
+                    	}
+                    }
+
+                    MLT_info(71, 26, true);
+                    TimerShow(TimeLeft, 65, 122);
+                    switch (Buttons_Scan()) {
+                    	case 0:     TimeLeft += 60;
+                                break;
+                    	case 1:     if (TimeLeft > 60)
+                                    TimeLeft -= 60;
+                                else
+                                    TimeLeft = 0;
+                                break;
+                    	case 2:     if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+                                    if (driver_state->mlt_pv < equipment.PumpMaxTemp) {
+                                        if (driver_state->pump_run)
+                                            driver_state->pump_run = 0;
+                                        else
+                                            driver_state->pump_run = 1;
+                                    } else {
+                                        driver_state->pump_run = 0;
+                                    }
+                                    xSemaphoreGive(xSemaphoreDriver);
+                                }
+                    }
+
+                    if (NewMinute) {
+                    	runtime.StageTimeLeft = TimeLeft / 60;
+                    	updateRuntime = true;
+                    }
+
+                    if ((TimeLeft == 0)) {
+                    	if (Main_Screen == MAIN_AUTO_WHIRLPOOL9) {
+                            Main_Screen = MAIN_AUTO_COOLING_H;
+			    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+			    	driver_state->pump_run = runtime.PumpCooling;
+			    	xSemaphoreGive(xSemaphoreDriver);
+			    }
+                    	} else if (Main_Screen == MAIN_AUTO_WHIRLPOOL7) {
+                            Main_Screen = MAIN_AUTO_COOLING_M;
+			    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+			    	driver_state->pump_run = runtime.PumpCooling;
+			    	xSemaphoreGive(xSemaphoreDriver);
+			    }
+                    	} else if (Main_Screen == MAIN_AUTO_WHIRLPOOL6) {
+                            Main_Screen = MAIN_AUTO_COOLING_C;
+			    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
+			    	driver_state->pump_run = runtime.PumpCooling;
+			    	xSemaphoreGive(xSemaphoreDriver);
+			    }
+                        } else if (Main_Screen == MAIN_AUTO_WHIRLPOOL2) {
+                            Main_Screen = MAIN_AUTO_DONE;
+                    	}
+                    }
+		}
+                break;
+
+	case MAIN_AUTO_DONE:
+	case MAIN_AUTO_ABORT:
+		if (Buttons_Scan() == 0)
+                    Main_Screen = MAIN_MODE_FREE;
+                break;
+
+	default:
+		break;
+    }
+
+    return false;
+}
+
+

mercurial