Sat, 20 Oct 2018 13:23:15 +0200
Initial checkin brewboard
/** * @file automation.c * @brief Automation functions. */ #include "config.h" int BoilPower = 100; int LastMashStep = 0; char temp_buf[64]; char logline[128]; char strftime_buf[64]; bool loop; bool CoolBeep = false; bool Resume = false; bool pumpRest = false; bool updateRuntime = false; bool NewMinute = false; bool TempReached = false; uint8_t MashState = MASH_NONE; float temp_MLT; float MinMash = 38.0; float MaxMash = 80.0; uint32_t power_MLT = 0; uint32_t power_HLT = 0; uint32_t counts = 0; float stageTemp = 0.0; uint16_t stageTime = 0; uint16_t TimeWhirlPool = 0; uint32_t TimeLeft = 0; uint32_t TimeSpent = 0; uint32_t SecsCount = 0; uint32_t pumpTime = 0; uint32_t TimeBrewing = 0; uint16_t Steady = 0; bool _NewMinute = false; bool _UseHLT = false; bool _Prompt = false; extern bool System_TimeOk; extern sButton Buttons[MAXBUTTONS]; extern int Main_Screen; extern DS18B20_State *ds18b20_state; extern DRIVER_State *driver_state; extern SemaphoreHandle_t xSemaphoreDS18B20; extern SemaphoreHandle_t xSemaphoreDriver; extern double Output; extern time_t now; extern struct tm timeinfo; #ifdef CONFIG_TEMP_SENSORS_SIMULATOR extern float Fake_MLT; extern float Fake_HLT; #endif static const char *TAG = "automation"; /* * Automation init function that only runs once when a * new screen is entered. */ bool Automation_Init(void) { switch (Main_Screen) { case MAIN_AUTO_INIT: #ifdef CONFIG_TEMP_SENSORS_SIMULATOR Fake_MLT = recipe.MashStep[0].Temperature - 10; Fake_HLT = recipe.SpargeTemp - 15; if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) { ds18b20_state->mlt_temperature = ((int)(Fake_MLT * 16)) / 16.0; ds18b20_state->hlt_temperature = ((int)(Fake_HLT * 16)) / 16.0; xSemaphoreGive(xSemaphoreDS18B20); } #endif for (int i = 0; i < 7; i++) { if (recipe.MashStep[i].Resttime) LastMashStep = i; } ESP_LOGI(TAG, "Last mash step %d", LastMashStep); // Check for a crashed session. if (runtime.AutoModeStarted) { TopMessage("Brouwen hervatten?"); Buttons_Add( 40, 100, 80, 40, "Ja", 0); Buttons_Add(200, 100, 80, 40, "Nee", 1); Buttons_Show(); SoundPlay(SOUND_Prompt); loop = true; while (loop) { switch (Buttons_Scan()) { case 0: loop = false; Resume = true; Main_Screen = runtime.StageResume; TimeLeft = runtime.StageTimeLeft; TimeBrewing = runtime.TimeBrewing; _UseHLT = runtime.UseHLT; MashState = MASH_NONE; pumpTime = 0; pumpRest = false; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->enable = true; if (_UseHLT) { driver_state->hlt_sp = recipe.SpargeTemp; driver_state->hlt_mode = HLT_MODE_BANG; } xSemaphoreGive(xSemaphoreDriver); } ESP_LOGI(TAG, "Resume brew screen %d, time left %d", Main_Screen, TimeLeft); log_begin((time_t)0); update_json(); log_annotation(ANNOTATION_SYSTEM, "Resume"); return true; break; case 1: loop = false; Resume = false; break; default: break; } vTaskDelay(50 / portTICK_PERIOD_MS); } Buttons_Clear(); TFT_fillScreen(_bg); } if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->enable = true; xSemaphoreGive(xSemaphoreDriver); } runtime.AutoModeStarted = true; runtime.UseHLT = _UseHLT = false; runtime.TimeBrewing = 0; TimeBrewing = 0; runtime.StageResume = MAIN_AUTO_INIT; runtime.StageTimeLeft = 0; runtime.HopAddition = 0; runtime.Logfile[0] = '\0'; runtime.PumpCooling = false; write_runtime(); power_MLT = power_HLT = counts = 0; log_clean(); vTaskDelay(250 / portTICK_PERIOD_MS); // Allow some time break; case MAIN_AUTO_DELAYSTART: break; case MAIN_AUTO_HEATUP: if (runtime.UseHLT) { if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->hlt_mode = HLT_MODE_BANG; xSemaphoreGive(xSemaphoreDriver); } TopMessage("Spoelwater opwarmen"); MLT_info(71, 26, false); HLT_info(71,150, false, false); } break; case MAIN_AUTO_MASH_IN: case MAIN_AUTO_MASH_1: case MAIN_AUTO_MASH_2: case MAIN_AUTO_MASH_3: case MAIN_AUTO_MASH_4: case MAIN_AUTO_MASH_5: case MAIN_AUTO_MASH_6: case MAIN_AUTO_MASH_OUT: if (Main_Screen == MAIN_AUTO_MASH_IN) { MinMash = 38.0; MaxMash = recipe.MashStep[1].Temperature + 10.0; TimeBrewing = 0; runtime.TimeBrewing = 0; if (System_TimeOk) { time(&now); localtime_r(&now, &timeinfo); log_begin(now); runtime.BrewStart = now; } else { log_begin((time_t)0); runtime.BrewStart = (time_t)0; } updateRuntime = true; } else if (Main_Screen == MAIN_AUTO_MASH_OUT) { MinMash = 75.0; MaxMash = 80.0; } else { MinMash = recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN - 1].Temperature; if (recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN + 1].Resttime) { MaxMash = recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN + 1].Temperature; } else { MaxMash = 75.0; } } MashState = MASH_NONE; pumpTime = 0; pumpRest = false; runtime.StageResume = Main_Screen; updateRuntime = true; TopMessage("Maischen"); MLT_info(71, 26, false); if (_UseHLT) { HLT_info(71,170, false, true); } break; case MAIN_AUTO_TOBOIL: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = stageTemp = config.BoilTemperature; driver_state->mlt_mode = MLT_MODE_EXT; driver_state->hlt_sp = 0.0; driver_state->hlt_mode = HLT_MODE_NONE; xSemaphoreGive(xSemaphoreDriver); } runtime.StageResume = Main_Screen; updateRuntime = true; TempReached = false; TopMessage("Naar koken"); MLT_info(71, 26, false); Buttons_Add( 5, 30, 60, 40, "+sp", 0); Buttons_Add(255, 30, 60, 40, "-sp", 1); Buttons_Show(); ESP_LOGI(TAG, "Mash done, going to boil."); break; case MAIN_AUTO_BOILING: if (Resume) { TimeLeft = runtime.StageTimeLeft * 60; } else { // +1 minute for flameout and 2 seconds for a smooth transition. runtime.StageTimeLeft = TimeLeft = (recipe.BoilTime * 60) + 60; runtime.StageResume = Main_Screen; runtime.HopAddition = 0; } SecsCount = 0; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = stageTemp = config.BoilTemperature; driver_state->mlt_mode = MLT_MODE_EXT; xSemaphoreGive(xSemaphoreDriver); } SoundPlay(SOUND_TempReached); BoilPower = equipment.BoilPower; updateRuntime = true; TopMessage("Koken"); MLT_info(71, 26, false); Buttons_Add( 3, 30, 60, 40, "+sp", 0); Buttons_Add(257, 30, 60, 40, "-sp", 1); Buttons_Add( 3, 190, 60, 40, "+1m", 2); Buttons_Add(257, 190, 60, 40, "-1m", 3); Buttons_Show(); ESP_LOGI(TAG, "Boil temperature reached, boil %d minutes", (TimeLeft / 60) -1); log_annotation(ANNOTATION_STAGE, "Koken"); break; case MAIN_AUTO_COOLING_H: case MAIN_AUTO_COOLING_M: case MAIN_AUTO_COOLING_C: TempReached = false; runtime.StageResume = Main_Screen; runtime.StageTimeLeft = 0; updateRuntime = true; if ((Main_Screen == MAIN_AUTO_COOLING_H) && (! recipe.Whirlpool7)) { // Skip cooling before whirlpool 74 degrees Main_Screen = MAIN_AUTO_COOLING_M; return true; //goto startover; } if ((Main_Screen == MAIN_AUTO_COOLING_M) && (! recipe.Whirlpool6)) { // Skip cooling before whirlpool 63 degrees. Main_Screen = MAIN_AUTO_COOLING_C; return true; //goto startover; } TopMessage("Start koelen?"); Buttons_Add( 40, 100, 80, 40, "Start", 0); Buttons_Add(200, 100, 80, 40, "Stop", 1); Buttons[1].dark = true; Buttons_Show(); SoundPlay(SOUND_Prompt); _Prompt = true; break; case MAIN_AUTO_WHIRLPOOL7: case MAIN_AUTO_WHIRLPOOL6: case MAIN_AUTO_WHIRLPOOL2: TempReached = true; runtime.StageResume = Main_Screen; updateRuntime = true; if ((Main_Screen == MAIN_AUTO_WHIRLPOOL9) && (! recipe.Whirlpool9)) { // Skip whirlpool 93 degrees. Main_Screen = MAIN_AUTO_COOLING_H; return true; //goto startover; } if ((Main_Screen == MAIN_AUTO_WHIRLPOOL7) && (! recipe.Whirlpool7)) { // Skip whirlpool 74 degrees. Main_Screen = MAIN_AUTO_COOLING_M; return true; //goto startover; } if ((Main_Screen == MAIN_AUTO_WHIRLPOOL6) && (! recipe.Whirlpool6)) { // Skip whirlpool 63 degrees. Main_Screen = MAIN_AUTO_COOLING_C; return true; //goto startover; } if ((Main_Screen == MAIN_AUTO_WHIRLPOOL2) && (! recipe.Whirlpool2)) { // Skip final whirlpool. Main_Screen = MAIN_AUTO_DONE; return true; //goto startover; } TopMessage("Start Whirlpool?"); Buttons_Add( 40, 100, 80, 40, "Start", 0); Buttons_Add(200, 100, 80, 40, "Stop", 1); Buttons[1].dark = true; Buttons_Show(); SoundPlay(SOUND_Prompt); _Prompt = true; break; case MAIN_AUTO_DONE: case MAIN_AUTO_ABORT: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->enable = false; driver_state->mlt_mode = MLT_MODE_NONE; driver_state->mlt_sp = 0.0; driver_state->hlt_mode = HLT_MODE_NONE; driver_state->hlt_sp = 0.0; driver_state->pump_run = 0; xSemaphoreGive(xSemaphoreDriver); } _fg = TFT_YELLOW; TFT_setFont(DEJAVU24_FONT, NULL); if (Main_Screen == MAIN_AUTO_DONE) { TFT_print("Brouwen is gereed.", CENTER, CENTER); ESP_LOGI(TAG, "Brew is done"); SoundPlay(SOUND_End); } else { TFT_print("Brouwen is afgebroken.", CENTER, CENTER); ESP_LOGI(TAG, "Brew is aborted"); SoundPlay(SOUND_Warn); } log_close(); runtime.Logfile[0] = '\0'; runtime.BrewStart = (time_t)0; runtime.AutoModeStarted = false; runtime.StageResume = MAIN_MODE_FREE; runtime.PumpCooling = false; runtime.HopAddition = 0; runtime.TimeBrewing = 0; runtime.StageTimeLeft = 0; updateRuntime = true; Buttons_Add(130, 200, 60, 40, "Ok", 0); Buttons[0].dark = true; Buttons_Show(); break; default: break; } return false; } /* * Automation loop screens. Mostly non-blocking. */ bool Automation_Loop(void) { static bool beeped = false; char tmp[32]; uint16_t y; switch (Main_Screen) { case MAIN_AUTO_INIT: /* * Present selected equipment and recipe. */ read_recipe(config.RecipeRec); y = 28; TopMessage("Automaat"); TFT_setFont(DEFAULT_FONT, NULL); ShowText(2,y,"Installatie", equipment.Name); y += 16; ShowText(2,y,"Recept", recipe.Name); y += 16; ShowFloat(2, y, "Maisch in", " C", recipe.MashStep[0].Temperature, 2); ShowFloat(162, y, "Spoelwater", " C", recipe.SpargeTemp, 2); y += 16; _fg = TFT_WHITE; TFT_print("Maisch stap", 2, y); TFT_print("Temp.", 200, y); TFT_print("Rust", 260, y); _fg = TFT_YELLOW; y += 16; for (int i = 1; i < 8; i++) { if (recipe.MashStep[i].Resttime) { TFT_print(recipe.MashStep[i].Name, 2, y); sprintf(tmp, "%.2f", recipe.MashStep[i].Temperature); TFT_print(tmp, 200, y); sprintf(tmp, "%2d min", recipe.MashStep[i].Resttime); TFT_print(tmp, 260, y); y += 16; } } ShowInteger(2, y, "Kooktijd", " miniuten", 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("toevoegingen om", LASTX, y); _fg = TFT_YELLOW; for (int i = 1; i <= recipe.Additions; i++) { sprintf(tmp, " %d", recipe.Addition[i-1].Time); TFT_print(tmp, LASTX, y); } _fg = TFT_WHITE; TFT_print(" minuten", LASTX, y); } else { _fg = TFT_WHITE; TFT_print("Geen hop toevoegingen.", 2, y); } y += 16; ShowFloat(2, y, "Koelen tot", " C", recipe.CoolTemp, 2); if (recipe.Whirlpool9) { ShowInteger(2, y, "Whirlpool 88..100 graden", " minuten", recipe.Whirlpool9); y += 16; } if (recipe.Whirlpool7) { ShowInteger(2, y, "Whirlpool 71..77 graden", " minuten", recipe.Whirlpool7); y += 16; } if (recipe.Whirlpool6) { ShowInteger(2, y, "Whirlpool 60..66 graden", " minuten", recipe.Whirlpool6); y += 16; } if (recipe.Whirlpool2) { ShowInteger(2, y, "Whirlpool koud", " minuten", recipe.Whirlpool2); y += 16; } Buttons_Add( 0, 210, 70, 30, "Stop" , 0); Buttons_Add(250, 210, 70, 30, "Start" , 1); Buttons[0].dark = true; Buttons_Show(); loop = true; while (loop) { switch (Buttons_Scan()) { case 0: loop = false; Main_Screen = MAIN_AUTO_ABORT; break; case 1: loop = false; break; default: break; } vTaskDelay(20 / portTICK_PERIOD_MS); } if (Main_Screen == MAIN_AUTO_ABORT) break; _UseHLT = false; _bg = TFT_BLACK; TFT_fillScreen(_bg); TopMessage("Maisch water aanwezig?"); Buttons_Clear(); Buttons_Add( 40, 100, 80, 40, "Ja", 0); Buttons_Add(200, 100, 80, 40, "Nee", 1); Buttons_Show(); SoundPlay(SOUND_Prompt); loop = true; while (loop) { switch (Buttons_Scan()) { case 0: loop = false; break; case 1: loop = false; Main_Screen = MAIN_AUTO_ABORT; break; default: break; } vTaskDelay(20 / portTICK_PERIOD_MS); } if (Main_Screen == MAIN_AUTO_ABORT) break; if ((equipment.SSR2 == SSR2_HLT_SHARE) || (equipment.SSR2 == SSR2_HLT_IND)) { TopMessage("Spoelwater aanwezig?"); SoundPlay(SOUND_Prompt); loop = true; while (loop) { switch (Buttons_Scan()) { case 0: loop = false; _UseHLT = true; break; case 1: loop = false; break; default: break; } vTaskDelay(20 / portTICK_PERIOD_MS); } runtime.UseHLT = _UseHLT; } else { runtime.UseHLT = _UseHLT = false; } updateRuntime = true; if (_UseHLT) { /* * Calculate HLT setpoint for pre-heat. Substract the * available Mash rest times, asume 0.5 degrees/minute * heat capacity during mash. */ int AvailableTime = 0; for (int i = 1; i < 6; i++) // Only normal Mash steps AvailableTime += recipe.MashStep[i].Resttime; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->hlt_sp = recipe.SpargeTemp - ((AvailableTime / 2) + 2); ESP_LOGI(TAG, "HLT preheat set to %4.1f", driver_state->hlt_sp); xSemaphoreGive(xSemaphoreDriver); } } Buttons_Clear(); Main_Screen = MAIN_AUTO_DELAYSTART; break; case MAIN_AUTO_DELAYSTART: Main_Screen = MAIN_AUTO_HEATUP; break; case MAIN_AUTO_HEATUP: if (! runtime.UseHLT) { // Skip if HLT is off Main_Screen = MAIN_AUTO_MASH_IN; break; } MLT_info(71, 26, true); HLT_info(71,150, true, false); if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (driver_state->hlt_pv >= driver_state->hlt_sp) { Main_Screen = MAIN_AUTO_MASH_IN; driver_state->hlt_sp = recipe.SpargeTemp; // Set final setpoint } xSemaphoreGive(xSemaphoreDriver); } break; case MAIN_AUTO_MASH_IN: case MAIN_AUTO_MASH_1: case MAIN_AUTO_MASH_2: case MAIN_AUTO_MASH_3: case MAIN_AUTO_MASH_4: case MAIN_AUTO_MASH_5: case MAIN_AUTO_MASH_6: case MAIN_AUTO_MASH_OUT: temp_MLT = 0.0; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { temp_MLT = driver_state->mlt_pv; if (MashState == MASH_ADD || MashState == MASH_REMOVE) { driver_state->pump_run = 0; } else if (MashState != MASH_NONE) { if (Main_Screen == MAIN_AUTO_MASH_IN) { driver_state->pump_run = (equipment.PumpPreMash && ! pumpRest) ? 1 : 0; } else if (Main_Screen == MAIN_AUTO_MASH_OUT) { driver_state->pump_run = (equipment.PumpMashOut && ! pumpRest) ? 1 : 0; } else { driver_state->pump_run = (equipment.PumpOnMash && ! pumpRest) ? 1 : 0; } } xSemaphoreGive(xSemaphoreDriver); } if (MashState == MASH_NONE) { stageTemp = recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN].Temperature; stageTime = recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN].Resttime; TempReached = false; if (stageTime == 0) { ESP_LOGI(TAG, "Mash step %d skipped", Main_Screen - MAIN_AUTO_MASH_IN); Main_Screen++; break; } if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = stageTemp; driver_state->mlt_mode = MLT_MODE_PID; xSemaphoreGive(xSemaphoreDriver); } MashState = MASH_WAITTEMP; ESP_LOGI(TAG, "Mash step %d time: %d temp: %4.1f min: %4.1f max: %4.1f", Main_Screen - MAIN_AUTO_MASH_IN, stageTime, stageTemp, MinMash, MaxMash); if (Main_Screen > MAIN_AUTO_MASH_IN) { // Do not annotate before the log is open. if (Main_Screen == MAIN_AUTO_MASH_OUT) { log_annotation(ANNOTATION_STAGE, "Uitmaischen"); } else { sprintf(logline, "Maisch: %d", Main_Screen - MAIN_AUTO_MASH_IN); log_annotation(ANNOTATION_STAGE, logline); } } if (strlen(recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN].Name)) { TopMessage(recipe.MashStep[Main_Screen - MAIN_AUTO_MASH_IN].Name); } else { sprintf(temp_buf, "Maisch stap #%d", Main_Screen - MAIN_AUTO_MASH_IN); TopMessage(temp_buf); } Buttons_Add( 5, 30, 60, 40, "+sp", 0); Buttons_Add(255, 30, 60, 40, "-sp", 1); Buttons_Show(); } else if (MashState == MASH_WAITTEMP) { pumpRest = false; if (temp_MLT < stageTemp) { Steady = 0; } if ((temp_MLT >= stageTemp) && (Steady > 10)) { SoundPlay(SOUND_TempReached); TempReached = true; MashState = MASH_REST; if (Main_Screen == MAIN_AUTO_MASH_IN) { TimerSet(0); } else { if (Resume && (runtime.StageTimeLeft < stageTime)) TimerSet(runtime.StageTimeLeft * 60); else TimerSet(stageTime * 60); } Resume = false; runtime.StageTimeLeft = TimeLeft / 60; updateRuntime = true; ESP_LOGI(TAG, "Mash step %d temperature reached, rest time %d", Main_Screen - MAIN_AUTO_MASH_IN, TimeLeft / 60); Buttons_Clear(); Buttons_Add( 0, 120, 60, 40, "+1m", 0); Buttons_Add(260, 120, 60, 40, "-1m", 1); Buttons_Show(); } switch (Buttons_Scan()) { case 0: if (stageTemp < MaxMash) { stageTemp += 0.25; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = stageTemp; xSemaphoreGive(xSemaphoreDriver); } } break; case 1: if (stageTemp > MinMash) { stageTemp -= 0.25; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = stageTemp; xSemaphoreGive(xSemaphoreDriver); } } break; default: break; } if (NewMinute) updateRuntime = true; } else if (MashState == MASH_REST) { /* * Mash step rest time and pump control */ if (((Main_Screen == MAIN_AUTO_MASH_OUT) && equipment.PumpMashOut) || ((Main_Screen != MAIN_AUTO_MASH_OUT) && equipment.PumpOnMash)) { float DeltaTemp = equipment.PumpRest * stageTemp / 120; // Maximum temperature drop before heating again. if (pumpTime >= (equipment.PumpCycle + equipment.PumpRest) || ((stageTemp - temp_MLT) > DeltaTemp)) { pumpTime = 0; } if (pumpTime >= equipment.PumpCycle) { if (! pumpRest) { pumpRest = true; ESP_LOGI(TAG, "Pump rest"); } } else { if (pumpRest) { pumpRest = false; ESP_LOGI(TAG, "Pump start"); } } } if (TimeLeft) { switch (Buttons_Scan()) { case 0: TimeLeft += 60; runtime.StageTimeLeft = TimeLeft / 60; updateRuntime = true; break; case 1: if (TimeLeft < 60) TimeLeft = 0; else TimeLeft -= 60; runtime.StageTimeLeft = TimeLeft / 60; updateRuntime = true; break; default: break; } } if (TimeLeft == 0) { runtime.StageTimeLeft = TimeLeft / 60; updateRuntime = true; if ((Main_Screen == MAIN_AUTO_MASH_IN) && config.AskAdd) { /* * Add Mash prompt. */ log_annotation(ANNOTATION_EVENT, "Mout storten"); Buttons_Clear(); Buttons_Add( 5,120, 60, 40, "Halt", 0); Buttons[0].dark = true; Buttons_Add(255,120, 60, 40, "Ok", 1); Buttons_Show(); _fg = TFT_WHITE; _bg = TFT_BLACK; TFT_setFont(DEJAVU24_FONT, NULL); TFT_print("Mout storten?", CENTER, 135); SoundPlay(SOUND_Prompt); MashState = MASH_ADD; ESP_LOGI(TAG, "Mash add prompt"); break; } if (((Main_Screen - MAIN_AUTO_MASH_IN) == LastMashStep) && config.AskIodine) { /* * Iodone test prompt. */ log_annotation(ANNOTATION_EVENT, "Jodium test"); TFT_fillRect(0, 120, 320, 50, TFT_BLACK); Buttons_Clear(); Buttons_Add( 5,120, 60, 40, "Halt", 0); Buttons[0].dark = true; Buttons_Add(255,120, 60, 40, "Ok", 1); Buttons_Show(); _fg = TFT_WHITE; _bg = TFT_BLACK; TFT_setFont(DEJAVU24_FONT, NULL); TFT_print("Jodium test?", CENTER, 127); SoundPlay(SOUND_Prompt); beeped = false; TimerSet(config.IodineTime * 60); MashState = MASH_IODINE; ESP_LOGI(TAG, "Mash iodine test prompt"); break; } if ((Main_Screen == MAIN_AUTO_MASH_OUT) && config.AskRemove) { /* * Mash remove prompt. */ log_annotation(ANNOTATION_EVENT, "Mout verwijderen"); TFT_fillRect(0, 120, 320, 50, TFT_BLACK); Buttons_Clear(); Buttons_Add( 5,120, 60, 40, "Halt", 0); Buttons[0].dark = true; Buttons_Add(255,120, 60, 40, "Ok", 1); Buttons_Show(); _fg = TFT_WHITE; _bg = TFT_BLACK; TFT_setFont(DEJAVU18_FONT, NULL); TFT_print("Mout verwijderen?", CENTER, 135); SoundPlay(SOUND_Prompt); MashState = MASH_REMOVE; ESP_LOGI(TAG, "Mash remove prompt"); break; } if (Main_Screen != MAIN_AUTO_ABORT) Main_Screen++; TempReached = false; } else { TimerShow(TimeLeft, 65, 122); if (NewMinute) { runtime.StageTimeLeft = TimeLeft / 60; updateRuntime = true; } } } else if (MashState == MASH_ADD) { switch (Buttons_Scan()) { case 0: Main_Screen = MAIN_AUTO_ABORT; break; case 1: Main_Screen++; break; default: break; } } else if (MashState == MASH_IODINE) { if (TimeSpent % 45 == 0) { if (! beeped) { SoundPlay(SOUND_Warn); beeped = true; } } else { beeped = false; } switch (Buttons_Scan()) { case 0: Main_Screen = MAIN_AUTO_ABORT; break; case 1: Main_Screen++; break; default: break; } if (TimeLeft == 0) { Main_Screen++; } } else if (MashState == MASH_REMOVE) { switch (Buttons_Scan()) { case 0: Main_Screen = MAIN_AUTO_ABORT; break; case 1: Main_Screen++; break; default: break; } } /* MashState */ MLT_info(71, 26, true); if (_UseHLT) { HLT_info(71, 170, true, true); } break; case MAIN_AUTO_TOBOIL: Output = 255; /* * Go to the boil temperature and wait until it is steady. */ if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (driver_state->mlt_pv < stageTemp) { Steady = 0; } else { if (Steady > 10) { Main_Screen = MAIN_AUTO_BOILING; TempReached = true; } } driver_state->pump_run = (equipment.PumpOnBoil && (driver_state->mlt_pv < equipment.PumpMaxTemp)) ? 1 : 0; xSemaphoreGive(xSemaphoreDriver); } MLT_info(71, 26, true); switch (Buttons_Scan()) { case 0: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp += 0.25; xSemaphoreGive(xSemaphoreDriver); } break; case 1: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp -= 0.25; xSemaphoreGive(xSemaphoreDriver); } break; default: break; } if (Resume) Resume = false; break; case MAIN_AUTO_BOILING: if (Resume) Resume = false; if (NewMinute) { if ((runtime.HopAddition < recipe.Additions) && (TimeLeft <= ((recipe.Addition[runtime.HopAddition].Time * 60) + 60))) { ESP_LOGI(TAG, "Hop addition %d at %d minutes", runtime.HopAddition + 1, recipe.Addition[runtime.HopAddition].Time); TopMessage(recipe.Addition[runtime.HopAddition].Name); sprintf(logline, "Hopgift %d %s", runtime.HopAddition + 1, recipe.Addition[runtime.HopAddition].Name); log_annotation(ANNOTATION_EVENT, logline); SoundPlay(SOUND_AddHop); runtime.HopAddition++; } else { TopMessage("Koken"); } runtime.StageTimeLeft = TimeLeft / 60; updateRuntime = true; } if (TimeLeft < 60) { if (Output) { log_annotation(ANNOTATION_STAGE, "Vlamuit"); } // Flameout Output = 0; } else if (driver_state->mlt_pv >= stageTemp) { Output = (int)((BoilPower * 255.0) / 100.0); if (Buttons[4].x == -1) { Buttons_Add( 3,110, 60, 40, "+%", 4); Buttons_Add(257,110, 60, 40, "-%", 5); Buttons_Show(); } } else { Output = 255; if (Buttons[4].x != -1) { Buttons[4].x = Buttons[5].x = -1; Buttons_Show(); TFT_fillRect( 3,110, 60, 40, TFT_BLACK); TFT_fillRect(257,110, 60, 40, TFT_BLACK); } } MLT_info(71, 26, true); TimerShow(TimeLeft, 65, 190); switch (Buttons_Scan()) { case 0: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp += 0.25; xSemaphoreGive(xSemaphoreDriver); } break; case 1: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp -= 0.25; xSemaphoreGive(xSemaphoreDriver); } break; case 2: TimeLeft += 60; break; case 3: if (TimeLeft > 60) TimeLeft -= 60; else TimeLeft = 0; break; case 4: if (BoilPower < 100) BoilPower++; break; case 5: if (BoilPower > 0) BoilPower--; break; default: break; } if (TimeLeft == 0) { Main_Screen = MAIN_AUTO_WHIRLPOOL9; ESP_LOGI(TAG, "Boil is ready"); } break; case MAIN_AUTO_COOLING_H: case MAIN_AUTO_COOLING_M: case MAIN_AUTO_COOLING_C: if (_Prompt) { /* * Prompt mode */ switch (Buttons_Scan()) { case 0: _Prompt = false; Buttons_Clear(); break; case 1: Main_Screen = MAIN_AUTO_DONE; Buttons_Clear(); return true; //goto startover; break; default: break; } if (! _Prompt) { /* * Starting cooling, setup the screen. */ Buttons_Clear(); TFT_fillScreen(_bg); if (Main_Screen == MAIN_AUTO_COOLING_H) { stageTemp = 77.0; } else if (Main_Screen == MAIN_AUTO_COOLING_M) { stageTemp = 66.0; } else { stageTemp = recipe.CoolTemp; } CoolBeep = false; ESP_LOGI(TAG, "Start cooling from %6.2f to %4.1f", ds18b20_state->mlt_temperature, stageTemp); if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_mode = MLT_MODE_OFF; driver_state->mlt_sp = stageTemp; xSemaphoreGive(xSemaphoreDriver); } log_annotation(ANNOTATION_STAGE, "Koelen"); TopMessage("Koelen"); MLT_info(71, 26, false); Buttons_Add( 5, 200, 60, 40, "Stop", 0); Buttons[0].dark = true; Buttons_Add( 5, 26, 60, 40, "+1", 1); Buttons_Add(255, 26, 60, 40, "-1", 2); /* * The next key is not a mistake, but we need a key entry which * will later become the pump key. The keyscan routine will find * the original key if pressed. */ Buttons_Add(255, 26, 60, 40, "-1", 3); Buttons_Show(); } } else { /* * Not in prompt mode. */ #ifdef CONFIG_TEMP_SENSORS_SIMULATOR if (Fake_MLT > 12.0) { if (driver_state->pump_run) Fake_MLT -= 0.00025 * (Fake_MLT - 12.0); else Fake_MLT -= 0.00015 * (Fake_MLT - 12.0); } if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) { ds18b20_state->mlt_temperature = ((int)(Fake_MLT * 16)) / 16.0; xSemaphoreGive(xSemaphoreDS18B20); } #endif MLT_info(71, 26, true); if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { /* * If the pump safe temperature is reached, add the control button. * Redefine key number 3 if it is at the position of key 2. */ if ((driver_state->mlt_pv < equipment.PumpMaxTemp) && (Buttons[3].x == Buttons[2].x) &&(Buttons[3].y == Buttons[2].y)) { Buttons_Add(255, 200, 60, 40, "Pomp", 3); Buttons_Show(); } xSemaphoreGive(xSemaphoreDriver); } switch (Buttons_Scan()) { case 1: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (Main_Screen == MAIN_AUTO_COOLING_H) { if (driver_state->mlt_sp < 77.0) driver_state->mlt_sp += 1.0; } else if (Main_Screen == MAIN_AUTO_COOLING_M) { if (driver_state->mlt_sp < 66.0) driver_state->mlt_sp += 1.0; } else if (Main_Screen == MAIN_AUTO_COOLING_C) { if (driver_state->mlt_sp < 30.0) driver_state->mlt_sp += 1.0; } xSemaphoreGive(xSemaphoreDriver); } break; case 0: Buttons_Add( 60, 150, 90, 40, "Stoppen", 4); Buttons[4].dark = true; Buttons_Add(170, 150, 90, 40, "Sorry", 5); Buttons_Show(); break; case 2: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (Main_Screen == MAIN_AUTO_COOLING_H) { if (driver_state->mlt_sp > 71.0) driver_state->mlt_sp -= 1.0; } else if (Main_Screen == MAIN_AUTO_COOLING_M) { if (driver_state->mlt_sp > 60.0) driver_state->mlt_sp -= 1.0; } else if (Main_Screen == MAIN_AUTO_COOLING_C) { if (driver_state->mlt_sp > 10.0) driver_state->mlt_sp -= 1.0; } xSemaphoreGive(xSemaphoreDriver); } break; case 3: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (driver_state->mlt_pv < equipment.PumpMaxTemp) { if (driver_state->pump_run) driver_state->pump_run = 0; else driver_state->pump_run = 1; } else { driver_state->pump_run = 0; } runtime.PumpCooling = driver_state->pump_run; updateRuntime = true; xSemaphoreGive(xSemaphoreDriver); } break; case 4: Main_Screen++; break; case 5: Buttons[4].x = Buttons[5].x = -1; TFT_fillRect(60, 150, 200, 40, TFT_BLACK); break; default: break; } if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (! CoolBeep && (driver_state->mlt_pv < (driver_state->mlt_sp + 2.0))) { SoundPlay(SOUND_Warn); CoolBeep = true; } if (driver_state->mlt_pv <= driver_state->mlt_sp) { SoundPlay(SOUND_TempReached); Main_Screen++; } xSemaphoreGive(xSemaphoreDriver); } } break; case MAIN_AUTO_WHIRLPOOL9: case MAIN_AUTO_WHIRLPOOL7: case MAIN_AUTO_WHIRLPOOL6: case MAIN_AUTO_WHIRLPOOL2: if (_Prompt) { switch (Buttons_Scan()) { case 0: _Prompt = false; break; case 1: if (Main_Screen == MAIN_AUTO_WHIRLPOOL2) { Main_Screen = MAIN_AUTO_DONE; } else { Main_Screen++; } Buttons_Clear(); return true; //goto startover; break; default: break; } if (! _Prompt) { /* * Prepare the screen for the actual whirpool. */ Buttons_Clear(); TFT_fillScreen(_bg); if (Main_Screen == MAIN_AUTO_WHIRLPOOL9) { TimeWhirlPool = recipe.Whirlpool9; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = 93.0; driver_state->mlt_mode = MLT_MODE_PID; xSemaphoreGive(xSemaphoreDriver); } TopMessage("Whirlpool 88..100"); } else if (Main_Screen == MAIN_AUTO_WHIRLPOOL7) { TimeWhirlPool = recipe.Whirlpool7; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = 74.0; driver_state->mlt_mode = MLT_MODE_PID; xSemaphoreGive(xSemaphoreDriver); } TopMessage("Whirlpool 71..77"); } else if (Main_Screen == MAIN_AUTO_WHIRLPOOL6) { TimeWhirlPool = recipe.Whirlpool6; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_sp = 63.0; driver_state->mlt_mode = MLT_MODE_PID; xSemaphoreGive(xSemaphoreDriver); } TopMessage("Whirlpool 60..66"); } else { TimeWhirlPool = recipe.Whirlpool2; TopMessage("Koude whirlpool"); } if (Resume) { TimeWhirlPool = runtime.StageTimeLeft; } /* * If the pump is allowed at the current temperature, turn it on. */ if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->pump_run = (driver_state->mlt_pv < equipment.PumpMaxTemp) ? 1 : 0; xSemaphoreGive(xSemaphoreDriver); } log_annotation(ANNOTATION_STAGE, "Whirlpool"); TimerSet(TimeWhirlPool * 60); runtime.StageTimeLeft = TimeWhirlPool; updateRuntime = true; MLT_info(71, 26, false); ESP_LOGI(TAG, "Whirlpool %d minutes, sp %4.1f", TimeWhirlPool, driver_state->mlt_sp); Buttons_Add(255, 120, 60, 40, "+1m", 0); Buttons_Add( 5, 120, 60, 40, "-1m", 1); Buttons_Add(130, 200, 60, 40, "Pomp", 2); Buttons_Show(); } } else { /* * Not running in prompt mode, do the whirlpool. */ if (TimeLeft == 120) { /* * Drop the temperature when whirlpool is almost ready. * If we are lucky the heater element will cool down so * the next cooling stage will not wast too much energy. */ if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (Main_Screen == MAIN_AUTO_WHIRLPOOL9) driver_state->mlt_sp = 88.0; if (Main_Screen == MAIN_AUTO_WHIRLPOOL7) driver_state->mlt_sp = 71.0; if (Main_Screen == MAIN_AUTO_WHIRLPOOL6) driver_state->mlt_sp = 60.0; xSemaphoreGive(xSemaphoreDriver); } } MLT_info(71, 26, true); TimerShow(TimeLeft, 65, 122); switch (Buttons_Scan()) { case 0: TimeLeft += 60; break; case 1: if (TimeLeft > 60) TimeLeft -= 60; else TimeLeft = 0; break; case 2: if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { if (driver_state->mlt_pv < equipment.PumpMaxTemp) { if (driver_state->pump_run) driver_state->pump_run = 0; else driver_state->pump_run = 1; } else { driver_state->pump_run = 0; } xSemaphoreGive(xSemaphoreDriver); } } if (NewMinute) { runtime.StageTimeLeft = TimeLeft / 60; updateRuntime = true; } if ((TimeLeft == 0)) { if (Main_Screen == MAIN_AUTO_WHIRLPOOL9) { Main_Screen = MAIN_AUTO_COOLING_H; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->pump_run = runtime.PumpCooling; xSemaphoreGive(xSemaphoreDriver); } } else if (Main_Screen == MAIN_AUTO_WHIRLPOOL7) { Main_Screen = MAIN_AUTO_COOLING_M; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->pump_run = runtime.PumpCooling; xSemaphoreGive(xSemaphoreDriver); } } else if (Main_Screen == MAIN_AUTO_WHIRLPOOL6) { Main_Screen = MAIN_AUTO_COOLING_C; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->pump_run = runtime.PumpCooling; xSemaphoreGive(xSemaphoreDriver); } } else if (Main_Screen == MAIN_AUTO_WHIRLPOOL2) { Main_Screen = MAIN_AUTO_DONE; } } } break; case MAIN_AUTO_DONE: case MAIN_AUTO_ABORT: if (Buttons_Scan() == 0) Main_Screen = MAIN_MODE_FREE; break; default: break; } return false; }