Sat, 06 Jun 2020 13:28:46 +0200
Changed the recipe database so that it is expandable, version 2. More mash fields and allow 16 steps. Allow 20 Additions. Removed separate mash steps from the state machine, the steps are moved to the runtime data. There is no fixed step number for mashout anymore. There is no fixed step for mash-in anymore, just use the first step and heat to the infusion temperature. After malt add, switch to the normal step temperature. Implemented decoction 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 uint16_t stageTime = 0; ///< Current stage timer uint16_t TimeWhirlPool = 0; ///< Whirlpool timer uint32_t TimeLeft = 0; ///< Tie left in this stage uint32_t TimeSpent = 0; ///< Tota time spent uint32_t SecsCount = 0; ///< Seconds counter uint32_t pumpTime = 0; ///< Pump running time uint32_t TimeBrewing = 0; ///< Brewing time elapsed uint16_t Steady = 0; ///< Temperature is steady bool _NewMinute = false; ///< New minute slave flag bool _UseHLT = false; ///< Use HLT slave flag bool _Prompt = false; ///< Prompt display flag extern bool System_TimeOk; ///< System time is valid extern sButton Buttons[MAXBUTTONS]; ///< Buttons definitions extern int Main_Screen; ///< Current screen extern 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; log_msg(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; 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\",\"step\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen, runtime.MashStep); ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); ESP_LOGI(TAG, "%s", 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: 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); } _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; } log_msg(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 log_msg(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; ESP_LOGI(TAG, "** Step %d Stage %d Malt %s", runtime.MashStep, MashState, runtime.MaltAdded ? (char *)"yes":(char *)"no"); snprintf(msg, 255, "{\"main\":\"%d\",\"sub\":\"%d\",\"step\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen, runtime.MashStep); ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); log_msg(TAG, "Mash step %d type: %s time: %d sp: %6.4f sv: %6.4f", runtime.MashStep, mashTypes[recipe.MashStep[runtime.MashStep].Type], stageTime, stageTemp, 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 *)"Opwarmen MLT"); } 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\",\"step\":\"%d\",\"timer\":\"%s\"}", Main_Screen, Sub_Screen, runtime.MashStep, 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; MashState = Sub_Screen = MASH_REST; ESP_LOGI(TAG, "** Step %d Stage %d Malt %s", runtime.MashStep, MashState, runtime.MaltAdded ? (char *)"yes":(char *)"no"); snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"step\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen, runtime.MashStep); 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; 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 (TimeLeft == 0) { ESP_LOGI(TAG, "** Step %d Stage %d Malt %s", runtime.MashStep, MashState, runtime.MaltAdded ? (char *)"yes":(char *)"no"); runtime.StageTimeLeft = 0; updateRuntime = true; 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"); 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(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\",\"step\":\"%d\",\"timer\":\"Mout storten?\"}", Main_Screen, Sub_Screen, runtime.MashStep); 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"); 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(DEJAVU24_FONT, NULL); TFT_print((char *)"Jodium test?", CENTER, 127); SoundPlay(SOUND_Prompt); beeped = false; TimerSet(config.IodineTime * 60); MashState = Sub_Screen = MASH_IODINE; ESP_LOGI(TAG, "** Step %d Stage %d Malt %s", runtime.MashStep, MashState, runtime.MaltAdded ? (char *)"yes":(char *)"no"); snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"step\":\"%d\",\"timer\":\"Jodium test?\"}", Main_Screen, Sub_Screen, runtime.MashStep); 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"); 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); TFT_print((char *)"Mout verwijderen?", CENTER, 135); SoundPlay(SOUND_Prompt); MashState = Sub_Screen = MASH_REMOVE; ESP_LOGI(TAG, "** Step %d Stage %d Malt %s", runtime.MashStep, MashState, runtime.MaltAdded ? (char *)"yes":(char *)"no"); snprintf(msg, 127, "{\"main\":\"%d\",\"sub\":\"%d\",\"step\":\"%d\",\"timer\":\"Mout verwijderen?\"}", Main_Screen, Sub_Screen, runtime.MashStep); 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(); ESP_LOGI(TAG, "** Step %d Stage %d Malt %s", runtime.MashStep, MashState, runtime.MaltAdded ? (char *)"yes":(char *)"no"); 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\",\"step\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen, runtime.MashStep); 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 (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(); } xSemaphoreGive(xSemaphoreDriver); } switch (Buttons_Scan()) { case 1: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (Main_Screen == MAIN_AUTO_COOLING_H) { if (driver_state->mlt_sp < 77.0) driver_state->mlt_sp += 1.0; } else if (Main_Screen == MAIN_AUTO_COOLING_M) { if (driver_state->mlt_sp < 66.0) driver_state->mlt_sp += 1.0; } else if (Main_Screen == MAIN_AUTO_COOLING_C) { if (driver_state->mlt_sp < 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_STAGE, (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; }