main/automation.c

Sun, 28 Oct 2018 13:10:46 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Sun, 28 Oct 2018 13:10:46 +0100
changeset 26
373a33699605
parent 24
bb3d6e22e78b
child 27
e82484ef9d12
permissions
-rw-r--r--

Graph title includes beer code. Hop additions now only display the additions themselfes.

/**
 * @file automation.c
 * @brief Automation functions.
 */

#include "config.h"

int	    			BoilPower = 100;		///< Boil power 0..100%
int				LastMashStep = 0;		///< Last valid mash step
char        			temp_buf[64];			///< Temporary buffer
char				logline[128];			///< Log line buffer
char				strftime_buf[64];		///< Time buffer
bool        			loop;				///< Loop flag
bool				CoolBeep = false;		///< Did beep during cooling
bool				Resume = false;			///< Resume brew flag
bool				pumpRest = false;		///< Pump is resting
bool				updateRuntime = false;		///< Update runtime record
bool        			NewMinute = false;		///< We have a new minute
bool				TempReached = false;		///< Temperature is reached
uint8_t     			MashState = MASH_NONE;		///< Mash states
float       			temp_MLT;			///< MLT temperature
float				MinMash = 38.0;			///< Minimum edit value
float				MaxMash = 80.0;			///< Maximum edit value
uint32_t    			power_MLT = 0;			///< MLT power
uint32_t			power_HLT = 0;			///< HLT power
uint32_t			counts = 0;			///< Counter for power average
float                           stageTemp = 0.0;		///< Current stage temperature
uint16_t                        stageTime = 0;			///< Current stage timer
uint16_t                        TimeWhirlPool = 0;		///< Whirlpool timer
uint32_t                        TimeLeft = 0;			///< Tie left in this stage
uint32_t                        TimeSpent = 0;			///< Tota time spent
uint32_t                        SecsCount = 0;			///< Seconds counter
uint32_t                        pumpTime = 0;			///< Pump running time
uint32_t                        TimeBrewing = 0;		///< Brewing time elapsed
uint16_t                        Steady = 0;			///< Temperature is steady
bool                            _NewMinute = false;		///< New minute slave flag
bool                            _UseHLT = false;		///< Use HLT slave flag
bool				_Prompt = false;		///< Prompt display flag

extern bool			System_TimeOk;			///< System time is valid
extern sButton			Buttons[MAXBUTTONS];		///< Buttons definitions
extern int			Main_Screen;			///< Current screen
extern DS18B20_State            *ds18b20_state;			///< DS18B20 state
extern DRIVER_State             *driver_state;			///< Relays driver state
extern SemaphoreHandle_t        xSemaphoreDS18B20;		///< DS18B20 lock semaphore
extern SemaphoreHandle_t        xSemaphoreDriver;		///< Relays driver lock semaphore
extern double                   Output;				///< Cakculated outpout power
extern time_t			now;				///< Current time
extern struct tm		timeinfo;			///< Current time structure

#ifdef CONFIG_TEMP_SENSORS_SIMULATOR
extern float                    Fake_MLT;
extern float                    Fake_HLT;
#endif

extern const char 		*mashTypes[];

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("T", 200, y);
		TFT_print("Temp.", 220, y);
		TFT_print("Rust", 280, 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);
			strcpy(tmp, mashTypes[recipe.MashStep[i].Type]);
			tmp[1] = '\0';
			TFT_print(tmp, 200, y);
			sprintf(tmp, "%.2f", recipe.MashStep[i].Temperature);
			TFT_print(tmp, 220, y);
			sprintf(tmp, "%2d m", recipe.MashStep[i].Resttime);
			TFT_print(tmp, 280, y);
			y += 16;
		    }
		}
		ShowInteger(2, y, "Kooktijd", " min", recipe.BoilTime);
		ShowFloat(162, y, "Koel tot", " C", recipe.CoolTemp, 2);
		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;
		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 type: %s time: %d temp: %4.1f  min: %4.1f max: %4.1f",
				    Main_Screen - MAIN_AUTO_MASH_IN, mashTypes[recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN].Type],
                                    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);
                    }
		    if ((Main_Screen > MAIN_AUTO_MASH_IN) && (Main_Screen < MAIN_AUTO_MASH_OUT) && 
			 (recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN].Type == MASHTYPE_INFUSION)) {
			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);
			sprintf(temp_buf, "Infuse %.1f L/%.1f C", recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN].Infusion_amount,
					recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN].Infusion_temp);
			TFT_print(temp_buf, CENTER, 135);
			SoundPlay(SOUND_Prompt);
			MashState = MASH_INFUSE;
			if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
			    // No heating during the infusion.
			    driver_state->mlt_sp = stageTemp;
			    driver_state->mlt_mode = MLT_MODE_OFF;
			    xSemaphoreGive(xSemaphoreDriver);
			}
			ESP_LOGI(TAG, "Mash infusion prompt");
		    } else {
			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;
		    }
		} else if (MashState == MASH_INFUSE) {
		    switch (Buttons_Scan()) {
			case 0:     Main_Screen = MAIN_AUTO_ABORT;
				    break;
			case 1:	    MashState = MASH_WAITTEMP;
				    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
					// Start PID again.
					driver_state->mlt_sp = stageTemp;
					driver_state->mlt_mode = MLT_MODE_PID;
					xSemaphoreGive(xSemaphoreDriver);
				    }
				    Buttons_Clear();
			    	    Buttons_Add(  5,  30, 60, 40, "+sp",   0);
				    Buttons_Add(255,  30, 60, 40, "-sp",   1);
				    Buttons_Show();
				    TFT_fillRect(65, 120, 190, 40, TFT_BLACK);
				    log_annotation(ANNOTATION_STAGE, "Eind infusie");
				    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);
			log_annotation(ANNOTATION_EVENT, recipe.Addition[runtime.HopAddition].Name);
                        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