Sun, 14 Jan 2024 11:19:19 +0100
Some kicad upgrade, no project changes.
/** * @file automation.c * @brief Automation functions. */ #include "config.h" int BoilPower = 100; ///< Boil power 0..100% int RampPower = 100; ///< Boil ramp power 0..100% int LastMashStep = 0; ///< Last valid mash step char temp_buf[64]; ///< Temporary buffer char logline[128]; ///< Log line buffer char strftime_buf[64]; ///< Time buffer bool loop; ///< Loop flag bool CoolBeep = false; ///< Did beep during cooling bool Resume = false; ///< Resume brew flag bool pumpRest = false; ///< Pump is resting bool updateRuntime = false; ///< Update runtime record bool NewMinute = false; ///< We have a new minute bool TempReached = false; ///< Temperature is reached uint8_t MashState = MASH_NONE; ///< Mash states float temp_MLT; ///< MLT temperature float MinMash = 38.0; ///< Minimum edit value float MaxMash = 80.0; ///< Maximum edit value uint32_t power_MLT = 0; ///< MLT power uint32_t power_HLT = 0; ///< HLT power uint32_t counts = 0; ///< Counter for power average float stageTemp = 0.0; ///< Current stage temperature float newTemp = 0.0; ///< New stage temperature uint16_t stageTime = 0; ///< Current stage timer uint16_t TimeWhirlPool = 0; ///< Whirlpool timer uint32_t TimeLeft = 0; ///< Time left in this stage uint32_t oldTimeLeft = 0; ///< The previous time left uint32_t TimeSpent = 0; ///< Total time spent uint32_t SecsCount = 0; ///< Seconds counter uint32_t pumpTime = 0; ///< Pump running time uint32_t TimeBrewing = 0; ///< Brewing time elapsed uint16_t Steady = 0; ///< Temperature is steady bool _NewMinute = false; ///< New minute slave flag bool _UseHLT = false; ///< Use HLT slave flag bool _Prompt = false; ///< Prompt display flag extern bool System_TimeOk; ///< System time is valid extern sButton Buttons[MAXBUTTONS]; ///< Buttons definitions extern int Main_Screen; ///< Current screen extern int Sub_Screen; ///< Sub screen during mash extern DS18B20_State *ds18b20_state; ///< DS18B20 state extern DRIVER_State *driver_state; ///< Relays driver state extern SemaphoreHandle_t xSemaphoreDS18B20; ///< DS18B20 lock semaphore extern SemaphoreHandle_t xSemaphoreDriver; ///< Relays driver lock semaphore extern double Output; ///< Cakculated outpout power extern time_t now; ///< Current time extern struct tm timeinfo; ///< Current time structure #ifdef CONFIG_TEMP_SENSORS_SIMULATOR extern float Fake_MLT; extern float Fake_HLT; #endif extern const char *mashTypes[]; static const char *TAG = "automation"; void change_sp(bool up) { if (up) stageTemp += 0.25; else stageTemp -= 0.25; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = stageTemp; xSemaphoreGive(xSemaphoreDriver); } log_msg(TAG, "Changed sp to %.2f", stageTemp); } void change_tl(int max) { if (max) { if (TimeLeft < max) TimeLeft += 60; else TimeLeft = max; } else { if (TimeLeft > 60) TimeLeft -= 60; else TimeLeft = 0; } log_msg(TAG, "Changed timeleft to %d", TimeLeft / 60); } /* * Automation init function that only runs once when a * new screen is entered. */ bool Automation_Init(void) { char msg[64]; switch (Main_Screen) { case MAIN_AUTO_INIT1: log_msg(TAG, "Automation startup"); #ifdef CONFIG_TEMP_SENSORS_SIMULATOR Fake_MLT = recipe.MashStep[0].Step_temp - 10; Fake_HLT = recipe.SpargeTemp - 45; if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) { ds18b20_state->mlt_temperature = ((int)(Fake_MLT * 16)) / 16.0; ds18b20_state->hlt_temperature = ((int)(Fake_HLT * 16)) / 16.0; xSemaphoreGive(xSemaphoreDS18B20); } #endif if (recipe.Mashsteps > 1) LastMashStep = recipe.Mashsteps - 2; else LastMashStep = 0; ESP_LOGD(TAG, "Last mash step %d", LastMashStep); log_msg(TAG, "Equipent: %s", equipment.Name); log_msg(TAG, "PID : P=%.3f I=%.3f D=%.3f", equipment.PID_kP, equipment.PID_kI, equipment.PID_kD); log_msg(TAG, "Recipe : %s %s", recipe.Code, recipe.Name); // Check for a crashed session. if (runtime.AutoModeStarted) { TopMessage((char *)"Brouwen hervatten?"); Buttons_Add( 40, 100, 80, 40, (char *)"Ja", 0); Buttons_Add(200, 100, 80, 40, (char *)"Nee", 1); Buttons_Show(); SoundPlay(SOUND_Prompt); loop = true; while (loop) { switch (Buttons_Scan()) { case 0: loop = false; Resume = true; Main_Screen = runtime.StageResume; TimeLeft = runtime.StageTimeLeft; TimeBrewing = runtime.TimeBrewing; _UseHLT = runtime.UseHLT; MashState = MASH_NONE; pumpTime = 0; pumpRest = false; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->enable = true; if (_UseHLT) { driver_state->hlt_sp = recipe.SpargeTemp; driver_state->hlt_mode = HLT_MODE_BANG; } xSemaphoreGive(xSemaphoreDriver); } log_msg(TAG, "Resume brew screen %d, time left %d", Main_Screen, TimeLeft); log_begin((time_t)0); update_json(); log_annotation(ANNOTATION_SYSTEM, (char *)"Resume"); return true; break; case 1: loop = false; Resume = false; break; default: break; } vTaskDelay(50 / portTICK_PERIOD_MS); } Buttons_Clear(); TFT_fillScreen(_bg); } if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->enable = true; xSemaphoreGive(xSemaphoreDriver); } runtime.AutoModeStarted = true; runtime.UseHLT = _UseHLT = false; runtime.TimeBrewing = 0; TimeBrewing = 0; runtime.StageResume = MAIN_AUTO_INIT1; runtime.StageTimeLeft = 0; runtime.HopAddition = 0; runtime.Logfile[0] = '\0'; runtime.PumpCooling = false; runtime.MashStep = 0; runtime.MaltAdded = false; runtime.MLT_usage = 0; runtime.HLT_usage = 0; write_runtime(); power_MLT = power_HLT = counts = 0; log_clean(); vTaskDelay(250 / portTICK_PERIOD_MS); // Allow some time break; case MAIN_AUTO_INIT2: break; case MAIN_AUTO_HEATUP: if (runtime.UseHLT) { if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->hlt_mode = HLT_MODE_BANG; xSemaphoreGive(xSemaphoreDriver); } TopMessage((char *)"Spoelwater opwarmen"); MLT_info(71, 26, false); HLT_info(71,150, false, false); } break; case MAIN_AUTO_MASH: if (runtime.MashStep == 0) { MinMash = 38.0; MaxMash = recipe.MashStep[0].Step_temp+ 10.0; TimeBrewing = 0; runtime.TimeBrewing = 0; if (System_TimeOk) { time(&now); localtime_r(&now, &timeinfo); log_begin(now); runtime.BrewStart = now; } else { log_begin((time_t)0); runtime.BrewStart = (time_t)0; } updateRuntime = true; } else if (runtime.MashStep == (recipe.Mashsteps - 1)) { MinMash = 75.0; MaxMash = 80.0; } else { MinMash = recipe.MashStep[runtime.MashStep - 1].Step_temp; MaxMash = recipe.MashStep[runtime.MashStep + 1].Step_temp; if (MaxMash >= 75.0) MaxMash = 74.75; } MashState = Sub_Screen = MASH_NONE; snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); pumpTime = 0; pumpRest = false; runtime.StageResume = Main_Screen; updateRuntime = true; TopMessage((char *)"Maischen"); MLT_info(71, 26, false); if (_UseHLT) { HLT_info(71,170, false, true); } break; case MAIN_AUTO_TOBOIL: if (recipe.BoilTime == 0) { /* no-boil recipe */ log_msg(TAG, "No-boil, start cooling"); Main_Screen = MAIN_AUTO_COOLING_H; return true; } if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = stageTemp = config.BoilTemperature; driver_state->mlt_mode = MLT_MODE_EXT; driver_state->hlt_sp = 0.0; driver_state->hlt_mode = HLT_MODE_NONE; xSemaphoreGive(xSemaphoreDriver); } runtime.StageResume = Main_Screen; updateRuntime = true; TempReached = false; RampPower = equipment.RampPower; TopMessage((char *)"Naar koken"); MLT_info(71, 26, false); Buttons_Add( 5, 30, 60, 40, (char *)"+sp", 0); Buttons_Add(255, 30, 60, 40, (char *)"-sp", 1); Buttons_Add( 3, 110, 60, 40, (char *)"+%", 2); Buttons_Add(257, 110, 60, 40, (char *)"-%", 3); Buttons_Show(); log_msg(TAG, "Mash done, going to boil."); Sub_Screen = 0; break; case MAIN_AUTO_BOILING: if (Resume) { TimeLeft = runtime.StageTimeLeft * 60; } else { // +1 minute for flameout and for a smooth transition. runtime.StageTimeLeft = TimeLeft = (recipe.BoilTime * 60) + 60; runtime.StageResume = Main_Screen; runtime.HopAddition = 0; } SecsCount = 0; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = stageTemp = config.BoilTemperature; driver_state->mlt_mode = MLT_MODE_EXT; xSemaphoreGive(xSemaphoreDriver); } SoundPlay(SOUND_TempReached); BoilPower = equipment.BoilPower; updateRuntime = true; TopMessage((char *)"Koken"); MLT_info(71, 26, false); Buttons_Add( 3, 30, 60, 40, (char *)"+sp", 0); Buttons_Add(257, 30, 60, 40, (char *)"-sp", 1); Buttons_Add( 3, 110, 60, 40, (char *)"+%", 2); Buttons_Add(257, 110, 60, 40, (char *)"-%", 3); Buttons_Add( 3, 190, 60, 40, (char *)"+1m", 4); Buttons_Add(257, 190, 60, 40, (char *)"-1m", 5); Buttons_Show(); log_msg(TAG, "Boil temperature reached, boil %d minutes", (TimeLeft / 60) -1); log_annotation(ANNOTATION_STAGE, (char *)"Koken"); Sub_Screen = 0; break; case MAIN_AUTO_COOLING_H: case MAIN_AUTO_COOLING_M: case MAIN_AUTO_COOLING_C: Sub_Screen = 0; TempReached = false; runtime.StageResume = Main_Screen; runtime.StageTimeLeft = 0; updateRuntime = true; if ((Main_Screen == MAIN_AUTO_COOLING_H) && (! recipe.Whirlpool7)) { // Skip cooling before whirlpool 74 degrees Main_Screen = MAIN_AUTO_COOLING_M; return true; //goto startover; } if ((Main_Screen == MAIN_AUTO_COOLING_M) && (! recipe.Whirlpool6)) { // Skip cooling before whirlpool 63 degrees. Main_Screen = MAIN_AUTO_COOLING_C; return true; //goto startover; } log_msg(TAG, "Prompt start cooling"); TopMessage((char *)"Start koelen?"); Buttons_Add( 40, 100, 80, 40, (char *)"Start", 0); Buttons_Add(200, 100, 80, 40, (char *)"Stop", 1); Buttons[1].dark = true; Buttons_Show(); SoundPlay(SOUND_Prompt); _Prompt = true; break; case MAIN_AUTO_WHIRLPOOL9: case MAIN_AUTO_WHIRLPOOL7: case MAIN_AUTO_WHIRLPOOL6: case MAIN_AUTO_WHIRLPOOL2: TempReached = true; runtime.StageResume = Main_Screen; updateRuntime = true; if ((Main_Screen == MAIN_AUTO_WHIRLPOOL9) && (! recipe.Whirlpool9)) { // Skip whirlpool 93 degrees. Main_Screen = MAIN_AUTO_COOLING_H; return true; //goto startover; } if ((Main_Screen == MAIN_AUTO_WHIRLPOOL7) && (! recipe.Whirlpool7)) { // Skip whirlpool 74 degrees. Main_Screen = MAIN_AUTO_COOLING_M; return true; //goto startover; } if ((Main_Screen == MAIN_AUTO_WHIRLPOOL6) && (! recipe.Whirlpool6)) { // Skip whirlpool 63 degrees. Main_Screen = MAIN_AUTO_COOLING_C; return true; //goto startover; } if ((Main_Screen == MAIN_AUTO_WHIRLPOOL2) && (! recipe.Whirlpool2)) { // Skip final whirlpool. Main_Screen = MAIN_AUTO_DONE; return true; //goto startover; } log_msg(TAG, "Prompt start whirlpool"); TopMessage((char *)"Start Whirlpool?"); Buttons_Add( 40, 100, 80, 40, (char *)"Start", 0); Buttons_Add(200, 100, 80, 40, (char *)"Stop", 1); Buttons[1].dark = true; Buttons_Show(); SoundPlay(SOUND_Prompt); _Prompt = true; break; case MAIN_AUTO_DONE: case MAIN_AUTO_ABORT: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->enable = false; driver_state->mlt_mode = MLT_MODE_NONE; driver_state->mlt_sp = 0.0; driver_state->hlt_mode = HLT_MODE_NONE; driver_state->hlt_sp = 0.0; driver_state->pump_run = 0; xSemaphoreGive(xSemaphoreDriver); } double mwu = (runtime.MLT_usage / 1000.0 / 60.0 / 60.0) * equipment.MLT_watt / 1000.0; double hwu = (runtime.HLT_usage / 1000.0 / 60.0 / 60.0) * equipment.HLT_watt / 1000.0; log_msg(TAG, "MLT usage %.3f KWh, HLT usage %.3f KWh, total %.3f KWh", mwu, hwu, mwu + hwu); _fg = TFT_YELLOW; TFT_setFont(DEJAVU24_FONT, NULL); if (Main_Screen == MAIN_AUTO_DONE) { TFT_print((char *)"Brouwen is gereed.", CENTER, CENTER); log_msg(TAG, "Brew is done"); SoundPlay(SOUND_End); } else { TFT_print((char *)"Brouwen is afgebroken.", CENTER, CENTER); log_msg(TAG, "Brew is aborted"); SoundPlay(SOUND_Warn); } log_close(); runtime.Logfile[0] = '\0'; runtime.BrewStart = (time_t)0; runtime.AutoModeStarted = false; runtime.StageResume = MAIN_MODE_FREE; runtime.PumpCooling = false; runtime.HopAddition = 0; runtime.TimeBrewing = 0; runtime.StageTimeLeft = 0; updateRuntime = true; Buttons_Add(130, 200, 60, 40, (char *)"Ok", 0); Buttons[0].dark = true; Buttons_Show(); break; default: break; } return false; } /* * Automation loop screens. Mostly non-blocking. */ bool Automation_Loop(void) { static bool beeped = false; char tmp[32], msg[256]; uint16_t y; switch (Main_Screen) { case MAIN_AUTO_INIT1: /* * Present selected equipment and recipe. */ Sub_Screen = 1; read_recipe(config.RecipeRec); snprintf(msg, 255, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\",\"brew1\":\"%s\",\"brew2\":\"%s\"}", Main_Screen, Sub_Screen, equipment.Name, recipe.Name); ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); y = 28; TopMessage((char *)"Automaat"); TFT_setFont(DEFAULT_FONT, NULL); ShowText(2,y,(char *)"Installatie", equipment.Name); y += 16; ShowText(2,y,(char *)"Recept", recipe.Name); y += 16; ShowFloat(2, y, (char *)"Maisch in", (char *)" C", recipe.MashStep[0].Infuse_temp, 3); ShowFloat(162, y, (char *)"Spoelwater", (char *)" C", recipe.SpargeTemp, 2); y += 16; _fg = TFT_WHITE; TFT_print((char *)"Maisch stap", 2, y); TFT_print((char *)"T", 200, y); TFT_print((char *)"Temp.", 220, y); TFT_print((char *)"Rust", 280, y); _fg = TFT_YELLOW; y += 16; for (int i = 0; i < recipe.Mashsteps; i++) { TFT_print(recipe.MashStep[i].Name, 2, y); if (recipe.MashStep[i].Type < 3) strcpy(tmp, mashTypes[recipe.MashStep[i].Type]); else tmp[0] = '?'; tmp[1] = '\0'; TFT_print(tmp, 200, y); sprintf(tmp, "%.2f", recipe.MashStep[i].Step_temp); TFT_print(tmp, 220, y); sprintf(tmp, "%2d m", recipe.MashStep[i].Step_time); TFT_print(tmp, 280, y); y += 16; } ShowInteger(2, y, (char *)"Kooktijd", (char *)" min", recipe.BoilTime); ShowFloat(162, y, (char *)"Koel tot", (char *)" C", recipe.CoolTemp, 2); if (recipe.BoilTime) { y += 16; if (recipe.Additions) { _fg = TFT_YELLOW; sprintf(tmp, "%d ", recipe.Additions); TFT_print(tmp, 2, y); _fg = TFT_WHITE; TFT_print((char *)"toevoegingen om", LASTX, y); _fg = TFT_YELLOW; for (int i = 1; i <= recipe.Additions; i++) { sprintf(tmp, " %d", recipe.Addition[i-1].Time); TFT_print(tmp, LASTX, y); } _fg = TFT_WHITE; TFT_print((char *)" minuten", LASTX, y); } else { _fg = TFT_WHITE; TFT_print((char *)"Geen hop toevoegingen.", 2, y); } } y += 16; if (recipe.Whirlpool9) { ShowInteger(2, y, (char *)"Whirlpool 88..100 graden", (char *)" minuten", recipe.Whirlpool9); y += 16; } if (recipe.Whirlpool7) { ShowInteger(2, y, (char *)"Whirlpool 71..77 graden", (char *)" minuten", recipe.Whirlpool7); y += 16; } if (recipe.Whirlpool6) { ShowInteger(2, y, (char *)"Whirlpool 60..66 graden", (char *)" minuten", recipe.Whirlpool6); y += 16; } if (recipe.Whirlpool2) { ShowInteger(2, y, (char *)"Whirlpool koud", (char *)" minuten", recipe.Whirlpool2); y += 16; } Buttons_Add( 0, 210, 70, 30, (char *)"Stop" , 0); Buttons_Add(250, 210, 70, 30, (char *)"Start" , 1); Buttons[0].dark = true; Buttons_Show(); loop = true; while (loop) { switch (Buttons_Scan()) { case 0: loop = false; Main_Screen = MAIN_AUTO_ABORT; break; case 1: loop = false; Main_Screen = MAIN_AUTO_INIT2; log_msg(TAG, "Brew `%s' on `%s'", recipe.Name, equipment.Name); break; default: break; } vTaskDelay(20 / portTICK_PERIOD_MS); } Buttons_Clear(); break; case MAIN_AUTO_INIT2: _UseHLT = false; _bg = TFT_BLACK; TFT_fillScreen(_bg); TopMessage((char *)"Maisch water aanwezig?"); Buttons_Clear(); Buttons_Add( 40, 100, 80, 40, (char *)"Ja", 0); Buttons_Add(200, 100, 80, 40, (char *)"Nee", 1); Buttons_Show(); SoundPlay(SOUND_Prompt); loop = true; while (loop) { switch (Buttons_Scan()) { case 0: loop = false; break; case 1: loop = false; Main_Screen = MAIN_AUTO_ABORT; break; default: break; } vTaskDelay(20 / portTICK_PERIOD_MS); } if (Main_Screen == MAIN_AUTO_ABORT) break; if ((equipment.SSR2 == SSR2_HLT_SHARE) || (equipment.SSR2 == SSR2_HLT_IND)) { TopMessage((char *)"Spoelwater aanwezig?"); SoundPlay(SOUND_Prompt); loop = true; while (loop) { switch (Buttons_Scan()) { case 0: loop = false; _UseHLT = true; break; case 1: loop = false; break; default: break; } vTaskDelay(20 / portTICK_PERIOD_MS); } runtime.UseHLT = _UseHLT; } else { runtime.UseHLT = _UseHLT = false; } ESP_LOGD(TAG, "Use HLT %s", (_UseHLT)?"true":"false"); updateRuntime = true; if (_UseHLT) { /* * Calculate HLT setpoint for pre-heat. * Substract the available Mash rest plus ramp times, * asume 0.67 degrees/minute heat capacity during mash. */ int AvailableTime = 0; for (int i = 0; i < recipe.Mashsteps; i++) { if (recipe.MashStep[i].Step_temp < 75) { AvailableTime += (recipe.MashStep[i].Step_time + recipe.MashStep[i].Ramp_time); } } if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->hlt_sp = recipe.SpargeTemp - ((AvailableTime / 1.5) + 2); if (driver_state->hlt_sp < 10.0) driver_state->hlt_sp = 10.0; log_msg(TAG, "HLT preheat set to %4.2f", driver_state->hlt_sp); xSemaphoreGive(xSemaphoreDriver); } } Buttons_Clear(); 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_LOGD(TAG, "Current %7.4f new %7.4f part %7.4f", stageTemp, newTemp, part); } } oldTimeLeft = TimeLeft; if (TimeLeft == 0) { runtime.StageTimeLeft = 0; updateRuntime = true; TFT_fillRect(0, 120, 320, 50, TFT_BLACK); snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); if (runtime.MashStep == 0 && ! runtime.MaltAdded && config.AskAdd) { /* * Add Mash prompt. */ stageTemp = recipe.MashStep[runtime.MashStep].Step_temp; // Set normal step temperature if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = stageTemp; xSemaphoreGive(xSemaphoreDriver); } log_annotation(ANNOTATION_EVENT, (char *)"Mout storten"); Buttons_Clear(); Buttons_Add( 5,120, 60, 40, (char *)"Halt", 0); Buttons[0].dark = true; Buttons_Add(255,120, 60, 40, (char *)"Ok", 1); Buttons_Show(); _fg = TFT_WHITE; _bg = TFT_BLACK; TFT_setFont(DEJAVU24_FONT, NULL); TFT_print((char *)"Mout storten?", CENTER, 135); SoundPlay(SOUND_Prompt); MashState = Sub_Screen = MASH_ADD; snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"Mout storten?\"}", Main_Screen, Sub_Screen); ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); log_msg(TAG, "Mash add prompt"); break; } if ((runtime.MashStep == LastMashStep) && config.AskIodine) { /* * Iodone test prompt. */ log_annotation(ANNOTATION_EVENT, (char *)"Jodium test"); Buttons_Clear(); Buttons_Add( 5,120, 60, 40, (char *)"Halt", 0); Buttons[0].dark = true; Buttons_Add(255,120, 60, 40, (char *)"Ok", 1); Buttons_Show(); _fg = TFT_WHITE; _bg = TFT_BLACK; TFT_setFont(DEJAVU24_FONT, NULL); TFT_print((char *)"Jodium test?", CENTER, 127); SoundPlay(SOUND_Prompt); beeped = false; TimerSet(config.IodineTime * 60); MashState = Sub_Screen = MASH_IODINE; snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"Jodium test?\"}", Main_Screen, Sub_Screen); ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); log_msg(TAG, "Mash iodine test prompt"); break; } if ((runtime.MashStep == (recipe.Mashsteps - 1)) && config.AskRemove) { /* * Mash remove prompt. */ log_annotation(ANNOTATION_EVENT, (char *)"Mout verwijderen"); Buttons_Clear(); Buttons_Add( 5,120, 60, 40, (char *)"Halt", 0); Buttons[0].dark = true; Buttons_Add(255,120, 60, 40, (char *)"Ok", 1); Buttons_Show(); _fg = TFT_WHITE; _bg = TFT_BLACK; TFT_setFont(DEJAVU18_FONT, NULL); TFT_print((char *)"Mout verwijderen?", CENTER, 135); SoundPlay(SOUND_Prompt); MashState = Sub_Screen = MASH_REMOVE; snprintf(msg, 127, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"Mout verwijderen?\"}", Main_Screen, Sub_Screen); ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); log_msg(TAG, "Mash remove prompt"); break; } if (Main_Screen != MAIN_AUTO_ABORT) { runtime.MashStep++; MashState = Sub_Screen = MASH_NONE; if (runtime.MashStep == recipe.Mashsteps) Main_Screen = MAIN_AUTO_TOBOIL; updateRuntime = true; } TempReached = false; } else { TimerShow(TimeLeft, 65, 122); if (NewMinute) { runtime.StageTimeLeft = TimeLeft / 60; updateRuntime = true; } } } else if (MashState == MASH_ADD || MashState == MASH_IODINE || MashState == MASH_REMOVE) { if (MashState == MASH_IODINE) { if (TimeSpent % 45 == 0) { if (! beeped) { SoundPlay(SOUND_Warn); beeped = true; } } else { beeped = false; } } switch (Buttons_Scan()) { case 0: Main_Screen = MAIN_AUTO_ABORT; break; case 1: //Main_Screen++; if (MashState == MASH_ADD) { MashState = Sub_Screen = MASH_NONE; runtime.MaltAdded = true; updateRuntime = true; } else if (MashState == MASH_IODINE) { MashState = Sub_Screen = MASH_NONE; runtime.MashStep++; if (runtime.MashStep == recipe.Mashsteps) Main_Screen = MAIN_AUTO_TOBOIL; updateRuntime = true; } else if (MashState == MASH_REMOVE) { Main_Screen = MAIN_AUTO_TOBOIL; } TFT_fillRect(0, 120, 320, 50, TFT_BLACK); Buttons_Clear(); break; default: break; } if (MashState == MASH_IODINE && TimeLeft == 0) { MashState = Sub_Screen = MASH_NONE; runtime.MashStep++; if (runtime.MashStep == recipe.Mashsteps) Main_Screen = MAIN_AUTO_TOBOIL; updateRuntime = true; TFT_fillRect(0, 120, 320, 50, TFT_BLACK); Buttons_Clear(); } } else if (MashState == MASH_INFUSE || MashState == MASH_DECOCT) { switch (Buttons_Scan()) { case 0: Main_Screen = MAIN_AUTO_ABORT; break; case 1: #ifdef CONFIG_TEMP_SENSORS_SIMULATOR if (MashState == MASH_DECOCT) { Fake_MLT = recipe.MashStep[runtime.MashStep].Step_temp - 0.72; // Fake the decoction amt. if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) { ds18b20_state->mlt_temperature = ((int)(Fake_MLT * 16)) / 16.0; xSemaphoreGive(xSemaphoreDS18B20); } } #endif if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { // Start PID again. driver_state->mlt_sp = stageTemp; driver_state->mlt_mode = MLT_MODE_PID; xSemaphoreGive(xSemaphoreDriver); } TFT_fillRect(0, 120, 320, 50, TFT_BLACK); Buttons_Clear(); Buttons_Add( 5, 30, 60, 40, (char *)"+sp", 0); Buttons_Add(255, 30, 60, 40, (char *)"-sp", 1); Buttons_Show(); TFT_fillRect(65, 120, 190, 40, TFT_BLACK); if (MashState == MASH_INFUSE) { log_msg(TAG, "Eind infusie"); log_annotation(ANNOTATION_EVENT, (char *)"Eind infusie"); } else { log_msg(TAG, "Eind decoctie"); log_annotation(ANNOTATION_EVENT, (char *)"Eind decoctie"); } MashState = Sub_Screen = MASH_WAITTEMP; snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); break; default: break; } } /* MashState */ MLT_info(71, 26, true); if (_UseHLT) { HLT_info(71, 170, true, true); } break; case MAIN_AUTO_TOBOIL: Output = (int)((RampPower * 255.0) / 100.0); /* * Go to the boil temperature and wait until it is steady. */ if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (driver_state->mlt_pv < stageTemp) { Steady = 0; } else { if (Steady > 10) { Main_Screen = MAIN_AUTO_BOILING; TempReached = true; } } driver_state->pump_run = (equipment.PumpOnBoil && (driver_state->mlt_pv < equipment.PumpMaxTemp)) ? 1 : 0; xSemaphoreGive(xSemaphoreDriver); } MLT_info(71, 26, true); switch (Buttons_Scan()) { case 0: if (stageTemp < 105) { change_sp(true); } break; case 1: if (stageTemp > 80) { change_sp(false); } break; case 2: if (RampPower < 100) RampPower++; log_msg(TAG, "Increase ramppower to %d%%", RampPower); break; case 3: if (RampPower > 0) RampPower--; log_msg(TAG, "Decrease ramppower to %d%%", RampPower); break; default: break; } if (Resume) Resume = false; break; case MAIN_AUTO_BOILING: if (Resume) Resume = false; if (NewMinute) { if ((runtime.HopAddition < recipe.Additions) && (TimeLeft <= ((recipe.Addition[runtime.HopAddition].Time * 60) + 60))) { log_msg(TAG, "Hop addition %d at %d minutes", runtime.HopAddition + 1, recipe.Addition[runtime.HopAddition].Time); TopMessage(recipe.Addition[runtime.HopAddition].Name); log_annotation(ANNOTATION_EVENT, recipe.Addition[runtime.HopAddition].Name); SoundPlay(SOUND_AddHop); runtime.HopAddition++; } else { TopMessage((char *)"Koken"); } runtime.StageTimeLeft = TimeLeft / 60; updateRuntime = true; } if (TimeLeft < 60) { if (Output) { log_annotation(ANNOTATION_STAGE, (char *)"Vlamuit"); log_msg(TAG, "Boil flame off"); } // Flameout Output = 0; } else if (driver_state->mlt_pv >= stageTemp) { Output = (int)((BoilPower * 255.0) / 100.0); } else { Output = (int)((RampPower * 255.0) / 100.0); } MLT_info(71, 26, true); TimerShow(TimeLeft, 65, 190); switch (Buttons_Scan()) { case 0: if (stageTemp < 105) { change_sp(true); } break; case 1: if (stageTemp > 80) { change_sp(false); } break; case 4: change_tl(21600); break; case 5: change_tl(0); break; case 2: if (driver_state->mlt_pv >= stageTemp) { if (BoilPower < 100) BoilPower++; log_msg(TAG, "Increase boilpower to %d%%", BoilPower); } else { if (RampPower < 100) RampPower++; log_msg(TAG, "Increase ramppower to %d%%", RampPower); } break; case 3: if (driver_state->mlt_pv >= stageTemp) { if (BoilPower > 0) BoilPower--; log_msg(TAG, "Decrease boilpower to %d%%", BoilPower); } else { if (RampPower > 0) RampPower--; log_msg(TAG, "Decrease ramppower to %d%%", RampPower); } break; default: break; } if (TimeLeft == 0) { Main_Screen = MAIN_AUTO_WHIRLPOOL9; log_msg(TAG, "Boil is ready"); } break; case MAIN_AUTO_COOLING_H: case MAIN_AUTO_COOLING_M: case MAIN_AUTO_COOLING_C: if (_Prompt) { /* * Prompt mode */ switch (Buttons_Scan()) { case 0: _Prompt = false; Buttons_Clear(); break; case 1: Main_Screen = MAIN_AUTO_DONE; Buttons_Clear(); return true; //goto startover; break; default: break; } if (! _Prompt) { /* * Starting cooling, setup the screen. */ Buttons_Clear(); TFT_fillScreen(_bg); Sub_Screen = 1; if (Main_Screen == MAIN_AUTO_COOLING_H) { stageTemp = 77.0; } else if (Main_Screen == MAIN_AUTO_COOLING_M) { stageTemp = 66.0; } else { stageTemp = recipe.CoolTemp; } snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); CoolBeep = false; log_msg(TAG, "Start cooling from %6.2f to %4.1f", ds18b20_state->mlt_temperature, stageTemp); if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_mode = MLT_MODE_OFF; driver_state->mlt_sp = stageTemp; xSemaphoreGive(xSemaphoreDriver); } log_annotation(ANNOTATION_STAGE, (char *)"Koelen"); TopMessage((char *)"Koelen"); MLT_info(71, 26, false); Buttons_Add( 5, 200, 60, 40, (char *)"Stop", 0); Buttons[0].dark = true; Buttons_Add( 5, 26, 60, 40, (char *)"+0.5", 1); Buttons_Add(255, 26, 60, 40, (char *)"-0.5", 2); /* * The next key is not a mistake, but we need a key entry which * will later become the pump key. The keyscan routine will find * the original key if pressed. */ Buttons_Add(255, 26, 60, 40, (char *)"-0.5", 3); Buttons_Show(); } } else { /* * Not in prompt mode. */ #ifdef CONFIG_TEMP_SENSORS_SIMULATOR if (Fake_MLT > 12.0) { if (driver_state->pump_run) Fake_MLT -= 0.00025 * (Fake_MLT - 12.0); else Fake_MLT -= 0.00015 * (Fake_MLT - 12.0); } if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) { ds18b20_state->mlt_temperature = ((int)(Fake_MLT * 16)) / 16.0; xSemaphoreGive(xSemaphoreDS18B20); } #endif MLT_info(71, 26, true); if (Sub_Screen == 1 && xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { /* * If the pump safe temperature is reached, add the control button. * Redefine key number 3 if it is at the position of key 2. */ if ((driver_state->mlt_pv < equipment.PumpMaxTemp) && (Buttons[3].x == Buttons[2].x) &&(Buttons[3].y == Buttons[2].y)) { Buttons_Add(255, 200, 60, 40, (char *)"Pomp", 3); Buttons_Show(); Sub_Screen = 2; } xSemaphoreGive(xSemaphoreDriver); if (Sub_Screen == 2) { snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); } } switch (Buttons_Scan()) { case 1: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (Main_Screen == MAIN_AUTO_COOLING_H) { if (driver_state->mlt_sp < 77.0) driver_state->mlt_sp += 0.5; } else if (Main_Screen == MAIN_AUTO_COOLING_M) { if (driver_state->mlt_sp < 66.0) driver_state->mlt_sp += 0.5; } else if (Main_Screen == MAIN_AUTO_COOLING_C) { if (driver_state->mlt_sp < 45.0) driver_state->mlt_sp += 0.5; } xSemaphoreGive(xSemaphoreDriver); } log_msg(TAG, "Increase sp to %.2f", driver_state->mlt_sp); break; case 0: Buttons_Add( 60, 150, 90, 40, (char *)"Stoppen", 4); Buttons[4].dark = true; Buttons_Add(170, 150, 90, 40, (char *)"Sorry", 5); Buttons_Show(); break; case 2: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (Main_Screen == MAIN_AUTO_COOLING_H) { if (driver_state->mlt_sp > 71.0) driver_state->mlt_sp -= 0.5; } else if (Main_Screen == MAIN_AUTO_COOLING_M) { if (driver_state->mlt_sp > 60.0) driver_state->mlt_sp -= 0.5; } else if (Main_Screen == MAIN_AUTO_COOLING_C) { if (driver_state->mlt_sp > 10.0) driver_state->mlt_sp -= 0.5; } xSemaphoreGive(xSemaphoreDriver); } log_msg(TAG, "Decrease sp to %.2f", driver_state->mlt_sp); break; case 3: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (driver_state->mlt_pv < equipment.PumpMaxTemp) { if (driver_state->pump_run) driver_state->pump_run = 0; else driver_state->pump_run = 1; } else { driver_state->pump_run = 0; } runtime.PumpCooling = driver_state->pump_run; log_msg(TAG, "Pump turned %s", (driver_state->pump_run)?"on":"off"); updateRuntime = true; xSemaphoreGive(xSemaphoreDriver); } break; case 4: Main_Screen++; break; case 5: Buttons[4].x = Buttons[5].x = -1; TFT_fillRect(60, 150, 200, 40, TFT_BLACK); break; default: break; } if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (! CoolBeep && (driver_state->mlt_pv < (driver_state->mlt_sp + 2.0))) { SoundPlay(SOUND_Warn); CoolBeep = true; } if (driver_state->mlt_pv <= driver_state->mlt_sp) { SoundPlay(SOUND_TempReached); Main_Screen++; Sub_Screen = 0; } xSemaphoreGive(xSemaphoreDriver); } } break; case MAIN_AUTO_WHIRLPOOL9: case MAIN_AUTO_WHIRLPOOL7: case MAIN_AUTO_WHIRLPOOL6: case MAIN_AUTO_WHIRLPOOL2: if (_Prompt) { switch (Buttons_Scan()) { case 0: _Prompt = false; break; case 1: if (Main_Screen == MAIN_AUTO_WHIRLPOOL2) { Main_Screen = MAIN_AUTO_DONE; } else { Main_Screen++; } Buttons_Clear(); return true; //goto startover; break; default: break; } if (! _Prompt) { /* * Prepare the screen for the actual whirpool. */ Buttons_Clear(); TFT_fillScreen(_bg); Sub_Screen = 1; snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); if (Main_Screen == MAIN_AUTO_WHIRLPOOL9) { TimeWhirlPool = recipe.Whirlpool9; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = 93.0; driver_state->mlt_mode = MLT_MODE_PID; xSemaphoreGive(xSemaphoreDriver); } TopMessage((char *)"Whirlpool 88..100"); } else if (Main_Screen == MAIN_AUTO_WHIRLPOOL7) { TimeWhirlPool = recipe.Whirlpool7; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = 74.0; driver_state->mlt_mode = MLT_MODE_PID; xSemaphoreGive(xSemaphoreDriver); } TopMessage((char *)"Whirlpool 71..77"); } else if (Main_Screen == MAIN_AUTO_WHIRLPOOL6) { TimeWhirlPool = recipe.Whirlpool6; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = 63.0; driver_state->mlt_mode = MLT_MODE_PID; xSemaphoreGive(xSemaphoreDriver); } TopMessage((char *)"Whirlpool 60..66"); } else { TimeWhirlPool = recipe.Whirlpool2; TopMessage((char *)"Koude whirlpool"); } if (Resume) { TimeWhirlPool = runtime.StageTimeLeft; } /* * If the pump is allowed at the current temperature, turn it on. */ if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->pump_run = (driver_state->mlt_pv < equipment.PumpMaxTemp) ? 1 : 0; xSemaphoreGive(xSemaphoreDriver); } log_annotation(ANNOTATION_EVENT, (char *)"Whirlpool"); TimerSet(TimeWhirlPool * 60); runtime.StageTimeLeft = TimeWhirlPool; updateRuntime = true; MLT_info(71, 26, false); log_msg(TAG, "Whirlpool %d minutes, sp %4.1f", TimeWhirlPool, driver_state->mlt_sp); Buttons_Add(255, 120, 60, 40, (char *)"+1m", 0); Buttons_Add( 5, 120, 60, 40, (char *)"-1m", 1); Buttons_Add(255, 200, 60, 40, (char *)"Pomp", 2); Buttons_Show(); } } else { /* * Not running in prompt mode, do the whirlpool. */ if (TimeLeft == 120) { /* * Drop the temperature when whirlpool is almost ready. * If we are lucky the heater element will cool down so * the next cooling stage will not wast too much energy. */ if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (Main_Screen == MAIN_AUTO_WHIRLPOOL9) driver_state->mlt_sp = 88.0; if (Main_Screen == MAIN_AUTO_WHIRLPOOL7) driver_state->mlt_sp = 71.0; if (Main_Screen == MAIN_AUTO_WHIRLPOOL6) driver_state->mlt_sp = 60.0; xSemaphoreGive(xSemaphoreDriver); } } MLT_info(71, 26, true); TimerShow(TimeLeft, 65, 122); switch (Buttons_Scan()) { case 0: change_tl(7200); break; case 1: change_tl(0); break; case 2: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (driver_state->mlt_pv < equipment.PumpMaxTemp) { if (driver_state->pump_run) driver_state->pump_run = 0; else driver_state->pump_run = 1; log_msg(TAG, "Pump switched %s", (driver_state->pump_run)?"on":"off"); } else { driver_state->pump_run = 0; } xSemaphoreGive(xSemaphoreDriver); } } if (NewMinute) { runtime.StageTimeLeft = TimeLeft / 60; updateRuntime = true; } if ((TimeLeft == 0)) { if (Main_Screen == MAIN_AUTO_WHIRLPOOL9) { Main_Screen = MAIN_AUTO_COOLING_H; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->pump_run = runtime.PumpCooling; xSemaphoreGive(xSemaphoreDriver); } } else if (Main_Screen == MAIN_AUTO_WHIRLPOOL7) { Main_Screen = MAIN_AUTO_COOLING_M; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->pump_run = runtime.PumpCooling; xSemaphoreGive(xSemaphoreDriver); } } else if (Main_Screen == MAIN_AUTO_WHIRLPOOL6) { Main_Screen = MAIN_AUTO_COOLING_C; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->pump_run = runtime.PumpCooling; xSemaphoreGive(xSemaphoreDriver); } } else if (Main_Screen == MAIN_AUTO_WHIRLPOOL2) { Main_Screen = MAIN_AUTO_DONE; } } } break; case MAIN_AUTO_DONE: case MAIN_AUTO_ABORT: if (Buttons_Scan() == 0) Main_Screen = MAIN_MODE_FREE; break; default: break; } return false; }