main/automation.c

Wed, 10 Jun 2020 09:43:51 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Wed, 10 Jun 2020 09:43:51 +0200
changeset 87
47253f294a9f
parent 82
7d17e2cb31a8
child 89
fa44bd094e01
permissions
-rw-r--r--

SDK settings to reduce bin size. Some log messages to debug level. Added KWH usage registration. Added equipment power usage for HLT and MLT. Equipment database upgraded to version 2, expandable. Fixed some screen errors during temperature mash steps.

/**
 * @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
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);

                // 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;

                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_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, 190, 60, 40, (char *)"+1m",  2);
                Buttons_Add(257, 190, 60, 40, (char *)"-1m",  3);
                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 KWU, HLT usage %.3f KWU, total %.3f KWU", 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 = 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 (stageTemp < 105) {
				    change_sp(true);
				}
                                break;

                    case 1:     if (stageTemp > 80) {
				    change_sp(false);
				}
                                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);
                    if (Buttons[4].x == -1) {
                        Buttons_Add(  3,110, 60, 40, (char *)"+%", 4);
                        Buttons_Add(257,110, 60, 40, (char *)"-%", 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 (stageTemp < 105) {
				    change_sp(true);
				}
                                break;

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

                    case 2:     change_tl(21600);
                                break;

                    case 3:     change_tl(0);
                                break;

                    case 4:     if (BoilPower < 100)
                                    BoilPower++;
				log_msg(TAG, "Increase boilpower to %d%%", BoilPower);
                                break;

                    case 5:     if (BoilPower > 0)
                                    BoilPower--;
				log_msg(TAG, "Decrease boilpower to %d%%", BoilPower);
                                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 *)"+1",   1);
			Buttons_Add(255,  26, 60, 40, (char *)"-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, (char *)"-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 (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 += 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 < 45.0)
                                            driver_state->mlt_sp += 1.0;
                                    }
                                    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 -= 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);
                                }
				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