main/automation.c

Sun, 18 Jul 2021 11:25:55 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Sun, 18 Jul 2021 11:25:55 +0200
changeset 109
72af8958b469
parent 104
03c38ad63e8c
child 117
b6d5c4cb61bb
permissions
-rw-r--r--

Version 0.3.17. Made the Hendi PWM change official.

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

#include "config.h"

int	    			BoilPower = 100;		///< Boil power 0..100%
int				RampPower = 100;		///< Boil ramp 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
float				newTemp = 0.0;			///< New stage temperature
uint16_t                        stageTime = 0;			///< Current stage timer
uint16_t                        TimeWhirlPool = 0;		///< Whirlpool timer
uint32_t                        TimeLeft = 0;			///< Time left in this stage
uint32_t			oldTimeLeft = 0;		///< The previous time left
uint32_t                        TimeSpent = 0;			///< Total 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 int			Sub_Screen;			///< Sub screen during mash
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";


void change_sp(bool up)
{
    if (up)
	stageTemp += 0.25;
    else
	stageTemp -= 0.25;
    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
	driver_state->mlt_sp = stageTemp;
	xSemaphoreGive(xSemaphoreDriver);
    }
    log_msg(TAG, "Changed sp to %.2f", stageTemp);
}



void change_tl(int max)
{
    if (max) {
	if (TimeLeft < max)
	    TimeLeft += 60;
	else
	    TimeLeft = max;
    } else {
	if (TimeLeft > 60)
            TimeLeft -= 60;
        else
            TimeLeft = 0;
    }
    log_msg(TAG, "Changed timeleft to %d", TimeLeft / 60);
}



/*
 * Automation init function that only runs once when a
 * new screen is entered.
 */
bool Automation_Init(void)
{
    char	msg[64];

    switch (Main_Screen) {
	case MAIN_AUTO_INIT1:
		log_msg(TAG, "Automation startup");
#ifdef CONFIG_TEMP_SENSORS_SIMULATOR
		Fake_MLT = recipe.MashStep[0].Step_temp - 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
		if (recipe.Mashsteps > 1)
		    LastMashStep = recipe.Mashsteps - 2;
		else
		    LastMashStep = 0;
                ESP_LOGD(TAG, "Last mash step %d", LastMashStep);

		log_msg(TAG, "Equipent: %s", equipment.Name);
		log_msg(TAG, "PID     : P=%.3f I=%.3f D=%.3f", equipment.PID_kP, equipment.PID_kI, equipment.PID_kD);
		log_msg(TAG, "Recipe  : %s %s", recipe.Code, recipe.Name);

                // Check for a crashed session.
                if (runtime.AutoModeStarted) {
                    TopMessage((char *)"Brouwen hervatten?");
                    Buttons_Add( 40, 100, 80, 40, (char *)"Ja", 0);
                    Buttons_Add(200, 100, 80, 40, (char *)"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);
					}
					log_msg(TAG, "Resume brew screen %d, time left %d", Main_Screen, TimeLeft);
					log_begin((time_t)0);
					update_json();
					log_annotation(ANNOTATION_SYSTEM, (char *)"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_INIT1;
		runtime.StageTimeLeft = 0;
		runtime.HopAddition = 0;
		runtime.Logfile[0] = '\0';
		runtime.PumpCooling = false;
		runtime.MashStep = 0;
		runtime.MaltAdded = false;
		runtime.MLT_usage = 0;
		runtime.HLT_usage = 0;
		write_runtime();
                power_MLT = power_HLT = counts = 0;
		log_clean();
		vTaskDelay(250 / portTICK_PERIOD_MS);	// Allow some time
                break;

	case MAIN_AUTO_INIT2:
	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((char *)"Spoelwater opwarmen");
		    MLT_info(71, 26, false);
		    HLT_info(71,150, false, false);
		}
		break;

	case MAIN_AUTO_MASH:
                if (runtime.MashStep == 0) {
                    MinMash = 38.0;
                    MaxMash = recipe.MashStep[0].Step_temp+ 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 (runtime.MashStep == (recipe.Mashsteps - 1)) {
                    MinMash = 75.0;
                    MaxMash = 80.0;
                } else {
                    MinMash = recipe.MashStep[runtime.MashStep - 1].Step_temp;
                    MaxMash = recipe.MashStep[runtime.MashStep + 1].Step_temp;
		    if (MaxMash >= 75.0)
			MaxMash = 74.75;
                }
                MashState = Sub_Screen = MASH_NONE;
		snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen);
		ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
                pumpTime = 0;
                pumpRest = false;
		runtime.StageResume = Main_Screen;
		updateRuntime = true;
                TopMessage((char *)"Maischen");
                MLT_info(71, 26, false);
                if (_UseHLT) {
                    HLT_info(71,170, false, true);
                }
                break;

	case MAIN_AUTO_TOBOIL:
		if (recipe.BoilTime == 0) {
		    /* no-boil recipe */
		    log_msg(TAG, "No-boil, start cooling");
		    Main_Screen = MAIN_AUTO_COOLING_H;
		    return true;
		}

                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;
		RampPower = equipment.RampPower;

                TopMessage((char *)"Naar koken");
                MLT_info(71, 26, false);
                Buttons_Add(  5,  30, 60, 40, (char *)"+sp", 0);
                Buttons_Add(255,  30, 60, 40, (char *)"-sp", 1);
		Buttons_Add(  3, 110, 60, 40, (char *)"+%",  2);
                Buttons_Add(257, 110, 60, 40, (char *)"-%",  3);
                Buttons_Show();
                log_msg(TAG, "Mash done, going to boil.");
		Sub_Screen = 0;
                break;

	case MAIN_AUTO_BOILING:
                if (Resume) {
                    TimeLeft = runtime.StageTimeLeft * 60;
                } else {
                    // +1 minute for flameout and 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((char *)"Koken");
                MLT_info(71, 26, false);
                Buttons_Add(  3,  30, 60, 40, (char *)"+sp", 0);
                Buttons_Add(257,  30, 60, 40, (char *)"-sp", 1);
		Buttons_Add(  3, 110, 60, 40, (char *)"+%",  2);
		Buttons_Add(257, 110, 60, 40, (char *)"-%",  3);
                Buttons_Add(  3, 190, 60, 40, (char *)"+1m", 4);
                Buttons_Add(257, 190, 60, 40, (char *)"-1m", 5);
                Buttons_Show();
                log_msg(TAG, "Boil temperature reached, boil %d minutes", (TimeLeft / 60) -1);
		log_annotation(ANNOTATION_STAGE, (char *)"Koken");
		Sub_Screen = 0;
                break;

	case MAIN_AUTO_COOLING_H:
	case MAIN_AUTO_COOLING_M:
	case MAIN_AUTO_COOLING_C:
		Sub_Screen = 0;
		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;
                }
		log_msg(TAG, "Prompt start cooling");
                TopMessage((char *)"Start koelen?");
                Buttons_Add( 40, 100, 80, 40, (char *)"Start", 0);
                Buttons_Add(200, 100, 80, 40, (char *)"Stop",  1);
		Buttons[1].dark = true;
                Buttons_Show();
                SoundPlay(SOUND_Prompt);
		_Prompt = true;
                break;

	case MAIN_AUTO_WHIRLPOOL9:
	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;
                }

		log_msg(TAG, "Prompt start whirlpool");
                TopMessage((char *)"Start Whirlpool?");
                Buttons_Add( 40, 100, 80, 40, (char *)"Start", 0);
                Buttons_Add(200, 100, 80, 40, (char *)"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);
                }
		double mwu = (runtime.MLT_usage / 1000.0 / 60.0 / 60.0) * equipment.MLT_watt / 1000.0;
		double hwu = (runtime.HLT_usage / 1000.0 / 60.0 / 60.0) * equipment.HLT_watt / 1000.0;
		log_msg(TAG, "MLT usage %.3f KWh, HLT usage %.3f KWh, total %.3f KWh", mwu, hwu, mwu + hwu);
                _fg = TFT_YELLOW;
                TFT_setFont(DEJAVU24_FONT, NULL);
                if (Main_Screen == MAIN_AUTO_DONE) {
                    TFT_print((char *)"Brouwen is gereed.", CENTER, CENTER);
                    log_msg(TAG, "Brew is done");
                    SoundPlay(SOUND_End);
                } else {
                    TFT_print((char *)"Brouwen is afgebroken.", CENTER, CENTER);
                    log_msg(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, (char *)"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], msg[256];
    uint16_t	y;

    switch (Main_Screen) {

	case MAIN_AUTO_INIT1:
		/*
		 * Present selected equipment and recipe.
		 */
		Sub_Screen = 1;
		read_recipe(config.RecipeRec);
		snprintf(msg, 255, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\",\"brew1\":\"%s\",\"brew2\":\"%s\"}", 
			Main_Screen, Sub_Screen, equipment.Name, recipe.Name);
		ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
		y = 28;
		TopMessage((char *)"Automaat");
		TFT_setFont(DEFAULT_FONT, NULL);
		ShowText(2,y,(char *)"Installatie", equipment.Name);
		y += 16;
		ShowText(2,y,(char *)"Recept", recipe.Name);
		y += 16;
		ShowFloat(2, y, (char *)"Maisch in", (char *)" C", recipe.MashStep[0].Infuse_temp, 3);
		ShowFloat(162, y, (char *)"Spoelwater", (char *)" C", recipe.SpargeTemp, 2);
		y += 16;
		_fg = TFT_WHITE;
		TFT_print((char *)"Maisch stap", 2, y);
		TFT_print((char *)"T", 200, y);
		TFT_print((char *)"Temp.", 220, y);
		TFT_print((char *)"Rust", 280, y);
		_fg = TFT_YELLOW;
		y += 16;
		for (int i = 0; i < recipe.Mashsteps; i++) {
		    TFT_print(recipe.MashStep[i].Name, 2, y);
		    if (recipe.MashStep[i].Type < 3)
		    	strcpy(tmp, mashTypes[recipe.MashStep[i].Type]);
		    else
			tmp[0] = '?';
		    tmp[1] = '\0';
		    TFT_print(tmp, 200, y);
		    sprintf(tmp, "%.2f", recipe.MashStep[i].Step_temp);
		    TFT_print(tmp, 220, y);
		    sprintf(tmp, "%2d m", recipe.MashStep[i].Step_time);
		    TFT_print(tmp, 280, y);
		    y += 16;
		}
		ShowInteger(2, y, (char *)"Kooktijd", (char *)" min", recipe.BoilTime);
		ShowFloat(162, y, (char *)"Koel tot", (char *)" C", recipe.CoolTemp, 2);
		if (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((char *)"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((char *)" minuten", LASTX, y);
		    } else {
		    	_fg = TFT_WHITE;
		    	TFT_print((char *)"Geen hop toevoegingen.", 2, y);
		    }
		}
		y += 16;
		if (recipe.Whirlpool9) {
		    ShowInteger(2, y, (char *)"Whirlpool 88..100 graden", (char *)" minuten", recipe.Whirlpool9);
		    y += 16;
		}
		if (recipe.Whirlpool7) {
		    ShowInteger(2, y, (char *)"Whirlpool 71..77 graden", (char *)" minuten", recipe.Whirlpool7);
		    y += 16;
		}
		if (recipe.Whirlpool6) {
		    ShowInteger(2, y, (char *)"Whirlpool 60..66 graden", (char *)" minuten", recipe.Whirlpool6);
		    y += 16;
		}
		if (recipe.Whirlpool2) {
		    ShowInteger(2, y, (char *)"Whirlpool koud", (char *)" minuten", recipe.Whirlpool2);
		    y += 16;
		}
		Buttons_Add(  0, 210, 70, 30, (char *)"Stop"   , 0);
		Buttons_Add(250, 210, 70, 30, (char *)"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;
					Main_Screen = MAIN_AUTO_INIT2;
					log_msg(TAG, "Brew `%s' on `%s'", recipe.Name, equipment.Name); 
					break;

			default:        break;
		    }
		    vTaskDelay(20 / portTICK_PERIOD_MS);
		}
		Buttons_Clear();
		break;

	case MAIN_AUTO_INIT2:
                _UseHLT = false;
		_bg = TFT_BLACK;
		TFT_fillScreen(_bg);
                TopMessage((char *)"Maisch water aanwezig?");
		Buttons_Clear();
                Buttons_Add( 40, 100, 80, 40, (char *)"Ja", 0);
                Buttons_Add(200, 100, 80, 40, (char *)"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((char *)"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;
		}
		ESP_LOGD(TAG, "Use HLT %s", (_UseHLT)?"true":"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].Step_time;
                    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
                        driver_state->hlt_sp = recipe.SpargeTemp - ((AvailableTime / 2) + 2);
                        log_msg(TAG, "HLT preheat set to %4.2f", 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;
                    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;
                        driver_state->hlt_sp = recipe.SpargeTemp;       // Set final setpoint
			ESP_LOGD(TAG, "HLT preheat done");
                    }
                    xSemaphoreGive(xSemaphoreDriver);
                }
                break;

	case MAIN_AUTO_MASH:
                temp_MLT = 0.0;
                if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
                    temp_MLT = driver_state->mlt_pv;

		    if (MashState == MASH_ADD || MashState == MASH_REMOVE || MashState == MASH_INFUSE) {
			driver_state->pump_run = 0;
		    } else if (MashState != MASH_NONE) {
                        if (runtime.MashStep == 0) {
                            driver_state->pump_run = (equipment.PumpPreMash && ! pumpRest) ? 1 : 0;
                        } else if (runtime.MashStep == (recipe.Mashsteps -1)) {
                            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) {
		    if (runtime.MashStep == 0 && ! runtime.MaltAdded && recipe.MashStep[0].Infuse_temp) {
			stageTemp = recipe.MashStep[0].Infuse_temp;
		    } else {
                    	stageTemp = recipe.MashStep[runtime.MashStep].Step_temp;
		    }
                    stageTime = recipe.MashStep[runtime.MashStep].Step_time;
                    TempReached = false;
                    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
                        driver_state->mlt_sp = stageTemp;
                        driver_state->mlt_mode = MLT_MODE_PID;
                        xSemaphoreGive(xSemaphoreDriver);
                    }
                    MashState = Sub_Screen = MASH_WAITTEMP;
		    snprintf(msg, 255, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen);
                    ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
                    log_msg(TAG, "Mash step %d type: %s time: %d sp: %6.4f-%6.4f sv: %6.4f",
				    runtime.MashStep, mashTypes[recipe.MashStep[runtime.MashStep].Type],
                                    stageTime, stageTemp, recipe.MashStep[runtime.MashStep].End_temp, temp_MLT);

		    if (runtime.MashStep) {
			// Do not annotate before the log is open.
			if (runtime.MashStep == (recipe.Mashsteps -1)) {
			    log_annotation(ANNOTATION_STAGE, (char *)"Uitmaischen");
			} else {
                    	    sprintf(logline, "Maisch: %d", runtime.MashStep);
		    	    log_annotation(ANNOTATION_STAGE, logline);
			}
		    }

		    if (! runtime.MaltAdded) {
			TopMessage((char *)"Maischwater opwarmen");
                    } else if (strlen(recipe.MashStep[runtime.MashStep].Name)) {
                        TopMessage(recipe.MashStep[runtime.MashStep].Name);
                    } else {
                        sprintf(temp_buf, "Maisch stap #%d", runtime.MashStep);
                        TopMessage(temp_buf);
                    }
		    if (runtime.MashStep && (runtime.MashStep < recipe.Mashsteps) && (recipe.MashStep[runtime.MashStep].Type != MASHTYPE_TEMPERATURE)) {
			TFT_fillRect(0, 120, 320, 50, TFT_BLACK);
                        Buttons_Clear();
			Buttons_Add(  5,120, 60, 40, (char *)"Halt", 0);
			Buttons[0].dark = true;
			Buttons_Add(255,120, 60, 40, (char *)"Ok",   1);
			Buttons_Show();
			_fg = TFT_WHITE;
			_bg = TFT_BLACK;
			TFT_setFont(DEJAVU18_FONT, NULL);
			if (recipe.MashStep[runtime.MashStep].Type == MASHTYPE_INFUSION) {
			    sprintf(temp_buf, "Infusie %.1f L/%.1f C", recipe.MashStep[runtime.MashStep].Infuse_amount,
					recipe.MashStep[runtime.MashStep].Infuse_temp);
			    MashState = Sub_Screen = MASH_INFUSE;
			    log_msg(TAG, "Mash infusion prompt");
			} else if (recipe.MashStep[runtime.MashStep].Type == MASHTYPE_DECOCTION) {
			    sprintf(temp_buf, "Decoctie %.1f Liter", recipe.MashStep[runtime.MashStep].Infuse_amount);
			    MashState = Sub_Screen = MASH_DECOCT;
			    log_msg(TAG, "Mash decoction prompt");
			}
			TFT_print(temp_buf, CENTER, 135);
			SoundPlay(SOUND_Prompt);
			snprintf(msg, 255, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"%s\"}", Main_Screen, Sub_Screen, temp_buf);
                	ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
			if (recipe.MashStep[runtime.MashStep].Type == MASHTYPE_INFUSION) {
			    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
			    	// No heating during the infusion.
			    	driver_state->mlt_sp = stageTemp;
			    	driver_state->mlt_mode = MLT_MODE_OFF;
			    	xSemaphoreGive(xSemaphoreDriver);
			    }
			}
		    } else {
			Buttons_Add(  5,  30, 60, 40, (char *)"+sp",   0);
			Buttons_Add(255,  30, 60, 40, (char *)"-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;
			newTemp = stageTemp;
                        MashState = Sub_Screen = MASH_REST;
			snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen);
                	ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
                        if (! runtime.MaltAdded && runtime.MashStep == 0) {
                            TimerSet(0);
                        } else {
                            if (Resume && (runtime.StageTimeLeft < stageTime))
                                TimerSet(runtime.StageTimeLeft * 60);
                            else
                                TimerSet(stageTime * 60);
                        }
			Resume = false;
			runtime.StageTimeLeft = TimeLeft / 60;
			oldTimeLeft = TimeLeft;
			updateRuntime = true;
                        log_msg(TAG, "Mash step %d temperature reached, rest %d minutes", runtime.MashStep, TimeLeft / 60);
                        Buttons_Clear();
                        Buttons_Add(  0, 120, 60, 40, (char *)"+1m",   0);
                        Buttons_Add(260, 120, 60, 40, (char *)"-1m",   1);
                        Buttons_Show();
                    }
                    switch (Buttons_Scan()) {
                        case 0:         if (stageTemp < MaxMash)
					    change_sp(true);
                                        break;

                        case 1:         if (stageTemp > MinMash)
					    change_sp(false);
                                        break;

                        default:
                                        break;
                    }
		    if (NewMinute)
			updateRuntime = true;

                } else if (MashState == MASH_REST) {
                    /*
                     * Mash step rest time and pump control
                     */
                    if (((runtime.MashStep == (recipe.Mashsteps -1)) && equipment.PumpMashOut) ||
                        ((runtime.MashStep < recipe.Mashsteps) && 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;
                                log_msg(TAG, "Pump stop");
                            }
                        } else {
                            if (pumpRest) {
                                pumpRest = false;
                                log_msg(TAG, "Pump start");
                            }
                        }
                    }
                    if (TimeLeft) {
                        switch (Buttons_Scan()) {
                            case 0:     change_tl(21600);
					runtime.StageTimeLeft = TimeLeft / 60;
					updateRuntime = true;
                                        break;

                            case 1:     change_tl(0);
					runtime.StageTimeLeft = TimeLeft / 60;
					updateRuntime = true;
                                        break;

                            default:    break;
                        }
                    }

		    if ((recipe.MashStep[runtime.MashStep].Step_temp != recipe.MashStep[runtime.MashStep].End_temp) &&
			(recipe.MashStep[runtime.MashStep].Type != MASHTYPE_DECOCTION) && (oldTimeLeft != TimeLeft)) {
			float part = ((stageTime * 60.0) - TimeLeft) / (stageTime * 60.0);
			newTemp = ((int)(((part * (recipe.MashStep[runtime.MashStep].End_temp - recipe.MashStep[runtime.MashStep].Step_temp)) +
				  recipe.MashStep[runtime.MashStep].Step_temp) * 16)) / 16.0;
			if (newTemp != stageTemp) {
			    stageTemp = newTemp;
			    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
                        	driver_state->mlt_sp = stageTemp;
                        	xSemaphoreGive(xSemaphoreDriver);
                    	    }
			//ESP_LOGI(TAG, "Current %7.4f new %7.4f part %7.4f", stageTemp, newTemp, part);
			}
		    }
		    oldTimeLeft = TimeLeft;

                    if (TimeLeft == 0) {
			runtime.StageTimeLeft = 0;
			updateRuntime = true;
			TFT_fillRect(0, 120, 320, 50, TFT_BLACK);
			snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen);
                        ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));

                        if (runtime.MashStep == 0 && ! runtime.MaltAdded && config.AskAdd) {
                            /*
                             * Add Mash prompt.
                             */
			    stageTemp = recipe.MashStep[runtime.MashStep].Step_temp; // Set normal step temperature
                    	    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
                        	driver_state->mlt_sp = stageTemp;
                        	xSemaphoreGive(xSemaphoreDriver);
                    	    }
			    log_annotation(ANNOTATION_EVENT, (char *)"Mout storten");
                            Buttons_Clear();
                            Buttons_Add(  5,120, 60, 40, (char *)"Halt", 0);
			    Buttons[0].dark = true;
                            Buttons_Add(255,120, 60, 40, (char *)"Ok",   1);
                            Buttons_Show();
                            _fg = TFT_WHITE;
                            _bg = TFT_BLACK;
                            TFT_setFont(DEJAVU24_FONT, NULL);
                            TFT_print((char *)"Mout storten?", CENTER, 135);
                            SoundPlay(SOUND_Prompt);
			    MashState = Sub_Screen = MASH_ADD;
			    snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"Mout storten?\"}", Main_Screen, Sub_Screen);
                	    ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
			    log_msg(TAG, "Mash add prompt");
			    break;
                        }
                        if ((runtime.MashStep == LastMashStep) && config.AskIodine) {
                            /*
                             * Iodone test prompt.
                             */
			    log_annotation(ANNOTATION_EVENT, (char *)"Jodium test");
                            Buttons_Clear();
                            Buttons_Add(  5,120, 60, 40, (char *)"Halt", 0);
			    Buttons[0].dark = true;
                            Buttons_Add(255,120, 60, 40, (char *)"Ok",   1);
                            Buttons_Show();
                            _fg = TFT_WHITE;
                            _bg = TFT_BLACK;
                            TFT_setFont(DEJAVU24_FONT, NULL);
                            TFT_print((char *)"Jodium test?", CENTER, 127);
                            SoundPlay(SOUND_Prompt);
                            beeped = false;
                            TimerSet(config.IodineTime * 60);
			    MashState = Sub_Screen = MASH_IODINE;
			    snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"Jodium test?\"}", Main_Screen, Sub_Screen);
                	    ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
			    log_msg(TAG, "Mash iodine test prompt");
			    break;
                        }
                        if ((runtime.MashStep == (recipe.Mashsteps - 1)) && config.AskRemove) {
                            /*
                             * Mash remove prompt.
                             */
			    log_annotation(ANNOTATION_EVENT, (char *)"Mout verwijderen");
                            Buttons_Clear();
                            Buttons_Add(  5,120, 60, 40, (char *)"Halt", 0);
			    Buttons[0].dark = true;
                            Buttons_Add(255,120, 60, 40, (char *)"Ok",   1);
                            Buttons_Show();
                            _fg = TFT_WHITE;
                            _bg = TFT_BLACK;
                            TFT_setFont(DEJAVU18_FONT, NULL);
                            TFT_print((char *)"Mout verwijderen?", CENTER, 135);
                            SoundPlay(SOUND_Prompt);
			    MashState = Sub_Screen = MASH_REMOVE;
			    snprintf(msg, 127, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"Mout verwijderen?\"}", Main_Screen, Sub_Screen);
                	    ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
			    log_msg(TAG, "Mash remove prompt");
			    break;
                        }
                        if (Main_Screen != MAIN_AUTO_ABORT) {
                            runtime.MashStep++;
			    MashState = Sub_Screen = MASH_NONE;
			    if (runtime.MashStep == recipe.Mashsteps)
				Main_Screen = MAIN_AUTO_TOBOIL;
			    updateRuntime = true;
			}
                        TempReached = false;
                    } else {
                        TimerShow(TimeLeft, 65, 122);
			if (NewMinute) {
			    runtime.StageTimeLeft = TimeLeft / 60;
			    updateRuntime = true;
			}
                    }
		} else if (MashState == MASH_ADD || MashState == MASH_IODINE || MashState == MASH_REMOVE) {
		    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++;
				    if (MashState == MASH_ADD) {
					MashState = Sub_Screen = MASH_NONE;
					runtime.MaltAdded = true;
					updateRuntime = true;
				    } else if (MashState == MASH_IODINE) {
					MashState = Sub_Screen = MASH_NONE;
					runtime.MashStep++;
					if (runtime.MashStep == recipe.Mashsteps)
					    Main_Screen = MAIN_AUTO_TOBOIL;
					updateRuntime = true;
				    } else if (MashState == MASH_REMOVE) {
					Main_Screen = MAIN_AUTO_TOBOIL;
				    }
				    TFT_fillRect(0, 120, 320, 50, TFT_BLACK);
				    Buttons_Clear();
				    break;
			default:    break;
		    }
		    if (MashState == MASH_IODINE && TimeLeft == 0) {
			MashState = Sub_Screen = MASH_NONE;
                        runtime.MashStep++;
                        if (runtime.MashStep == recipe.Mashsteps)
                            Main_Screen = MAIN_AUTO_TOBOIL;
                        updateRuntime = true;
			TFT_fillRect(0, 120, 320, 50, TFT_BLACK);
			Buttons_Clear();
		    }
		} else if (MashState == MASH_INFUSE || MashState == MASH_DECOCT) {
		    switch (Buttons_Scan()) {
			case 0:     Main_Screen = MAIN_AUTO_ABORT;
				    break;
			case 1:
#ifdef CONFIG_TEMP_SENSORS_SIMULATOR
				    if (MashState == MASH_DECOCT) {
					Fake_MLT = recipe.MashStep[runtime.MashStep].Step_temp - 0.72; // Fake the decoction amt.
					if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) {
					    ds18b20_state->mlt_temperature = ((int)(Fake_MLT * 16)) / 16.0;
					    xSemaphoreGive(xSemaphoreDS18B20);
					}
				    }
#endif
				    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
					// Start PID again.
					driver_state->mlt_sp = stageTemp;
					driver_state->mlt_mode = MLT_MODE_PID;
					xSemaphoreGive(xSemaphoreDriver);
				    }
				    TFT_fillRect(0, 120, 320, 50, TFT_BLACK);
				    Buttons_Clear();
			    	    Buttons_Add(  5,  30, 60, 40, (char *)"+sp",   0);
				    Buttons_Add(255,  30, 60, 40, (char *)"-sp",   1);
				    Buttons_Show();
				    TFT_fillRect(65, 120, 190, 40, TFT_BLACK);
				    if (MashState == MASH_INFUSE) {
					log_msg(TAG, "Eind infusie");
				    	log_annotation(ANNOTATION_EVENT, (char *)"Eind infusie");
				    } else {
					log_msg(TAG, "Eind decoctie");
					log_annotation(ANNOTATION_EVENT, (char *)"Eind decoctie");
				    }
				    MashState = Sub_Screen = MASH_WAITTEMP;
				    snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen);
                                    ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
				    break;
			default:    break;
		    }
                } /* MashState */
                MLT_info(71, 26, true);
                if (_UseHLT) {
                    HLT_info(71, 170, true, true);
                }
                break;

	case MAIN_AUTO_TOBOIL:
                Output = (int)((RampPower * 255.0) / 100.0);
                /*
                 * 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 (stageTemp < 105) {
				    change_sp(true);
				}
                                break;

                    case 1:     if (stageTemp > 80) {
				    change_sp(false);
				}
                                break;

		    case 2:	if (RampPower < 100)
				    RampPower++;
				log_msg(TAG, "Increase ramppower to %d%%", RampPower);
				break;

		    case 3:	if (RampPower > 0)
				    RampPower--;
				log_msg(TAG, "Decrease ramppower to %d%%", RampPower);
				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))) {
                        log_msg(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((char *)"Koken");
                    }
                    runtime.StageTimeLeft = TimeLeft / 60;
                    updateRuntime = true;
                }
                if (TimeLeft < 60) {
		    if (Output) {
			log_annotation(ANNOTATION_STAGE, (char *)"Vlamuit");
			log_msg(TAG, "Boil flame off");
		    }
                    // Flameout
                    Output = 0;
                } else if (driver_state->mlt_pv >= stageTemp) {
                    Output = (int)((BoilPower * 255.0) / 100.0);
                } else {
                    Output = (int)((RampPower * 255.0) / 100.0);;
                }

                MLT_info(71, 26, true);
                TimerShow(TimeLeft, 65, 190);

                switch (Buttons_Scan()) {
                    case 0:     if (stageTemp < 105) {
				    change_sp(true);
				}
                                break;

                    case 1:     if (stageTemp > 80) {
				    change_sp(false);
				}
                                break;

                    case 4:     change_tl(21600);
                                break;

                    case 5:     change_tl(0);
                                break;

                    case 2:     if (driver_state->mlt_pv >= stageTemp) {
				    if (BoilPower < 100)
                                    	BoilPower++;
				    log_msg(TAG, "Increase boilpower to %d%%", BoilPower);
				} else {
				    if (RampPower < 100)
					RampPower++;
				    log_msg(TAG, "Increase ramppower to %d%%", RampPower);
				}
                                break;

                    case 3:     if (driver_state->mlt_pv >= stageTemp) {
				    if (BoilPower > 0)
                                    	BoilPower--;
				    log_msg(TAG, "Decrease boilpower to %d%%", BoilPower);
				} else {
				    if (RampPower > 0)
					RampPower--;
				    log_msg(TAG, "Decrease ramppower to %d%%", RampPower);
				}
                                break;

                    default:    break;
                }

                if (TimeLeft == 0) {
                    Main_Screen = MAIN_AUTO_WHIRLPOOL9;
                    log_msg(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);
			Sub_Screen = 1;
			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;
			}
			snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen);
                        ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
			CoolBeep = false;
			log_msg(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, (char *)"Koelen");
			TopMessage((char *)"Koelen");
			MLT_info(71, 26, false);
			Buttons_Add(  5, 200, 60, 40, (char *)"Stop", 0);
			Buttons[0].dark = true;
			Buttons_Add(  5,  26, 60, 40, (char *)"+0.5",   1);
			Buttons_Add(255,  26, 60, 40, (char *)"-0.5",   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, (char *)"-0.5",   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 (Sub_Screen == 1 && 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, (char *)"Pomp", 3);
                            Buttons_Show();
			    Sub_Screen = 2;
                    	}
                    	xSemaphoreGive(xSemaphoreDriver);
			if (Sub_Screen == 2) {
			    snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen);
                            ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
			}
                    }
                    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 += 0.5;
                                    } else if (Main_Screen == MAIN_AUTO_COOLING_M) {
                                        if (driver_state->mlt_sp < 66.0)
                                            driver_state->mlt_sp += 0.5;
                                    } else if (Main_Screen == MAIN_AUTO_COOLING_C) {
                                        if (driver_state->mlt_sp < 45.0)
                                            driver_state->mlt_sp += 0.5;
                                    }
                                    xSemaphoreGive(xSemaphoreDriver);
                                }
				log_msg(TAG, "Increase sp to %.2f", driver_state->mlt_sp);
                                break;

                    	case 0: Buttons_Add( 60, 150, 90, 40, (char *)"Stoppen", 4);
				Buttons[4].dark = true;
                                Buttons_Add(170, 150, 90, 40, (char *)"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 -= 0.5;
                                    } else if (Main_Screen == MAIN_AUTO_COOLING_M) {
                                        if (driver_state->mlt_sp > 60.0)
                                            driver_state->mlt_sp -= 0.5;
                                    } else if (Main_Screen == MAIN_AUTO_COOLING_C) {
                                        if (driver_state->mlt_sp > 10.0)
                                            driver_state->mlt_sp -= 0.5;
                                    }
                                    xSemaphoreGive(xSemaphoreDriver);
                                }
				log_msg(TAG, "Decrease sp to %.2f", driver_state->mlt_sp);
                                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;
				    log_msg(TAG, "Pump turned %s", (driver_state->pump_run)?"on":"off");
				    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++;
			    Sub_Screen = 0;
                    	}
                    	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);
			Sub_Screen = 1;
                        snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen);
                        ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
			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((char *)"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((char *)"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((char *)"Whirlpool 60..66");
			} else {
			    TimeWhirlPool = recipe.Whirlpool2;
			    TopMessage((char *)"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_EVENT, (char *)"Whirlpool");

			TimerSet(TimeWhirlPool * 60);
			runtime.StageTimeLeft = TimeWhirlPool;
			updateRuntime = true;
			MLT_info(71, 26, false);
			log_msg(TAG, "Whirlpool %d minutes, sp %4.1f", TimeWhirlPool, driver_state->mlt_sp);
			Buttons_Add(255, 120, 60, 40, (char *)"+1m", 0);
			Buttons_Add(  5, 120, 60, 40, (char *)"-1m", 1);
			Buttons_Add(255, 200, 60, 40, (char *)"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: change_tl(7200);
                                break;
                    	case 1: change_tl(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;
					log_msg(TAG, "Pump switched %s", (driver_state->pump_run)?"on":"off");
                                    } 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