Sun, 24 Nov 2019 16:44:00 +0100
Version 0.3.7. The WiFi task uses the new event handlers. Cooling temperature top is now 45 instead of 30 degreees for pitching Kveik. One extra cosmetic message during OTA update.
/** * @file recipes.c * @brief Recipes management. If new beerxml recipes are detected in * the /recipe directory on the SD card they will be imported * when you enter the recipes menu. After successfull import the * xml file extensions will be changed to xok to prevent that * the recipe is imported again. After the import the recipe * editor is shown. */ #include "config.h" extern sButton Buttons[MAXBUTTONS]; ///< Buttons definitions extern int Main_Screen; ///< Current screen bool r_UpdateRec = false; ///< Update record flag int r_CurrentRec = 1; ///< Current record int r_Records = 1; ///< Total records int r_Imported = 0; ///< Total imported char _xml_element[10][32]; ///< XML element array int _xml_depth; ///< Current depths int _xml_mashsteps; ///< Mash steps char _xml_add_name[64]; ///< Mash name int _xml_add_type; ///< Mash type int _xml_add_time; ///< Mash rest time int _xml_add_ramp; ///< Mash ramp time float _xml_add_temp; ///< Mash temperature float _xml_add_amount; ///< Mash infusion amount float _xml_add_infusion; ///< Mash infusion temperature float _xml_tun_temp; ///< TUN temperature bool _xml_add_valid; ///< Add is valid static const char *TAG = "recipes"; char char_data_buffer[1024]; ///< Data buffer size_t offs; ///< Offset in buffer bool overflow; ///< Overflow in buffer extern const char *mashTypes[]; /** * @brief Add addition to the recipe. * @param Name The addition name. * @param Type The addition type. * @param Time The addition time to add. */ void Addition_Add(char *Name, uint8_t Type, uint16_t Time) { if (! recipe.Additions) { // No entries yet, add the first one. snprintf(recipe.Addition[recipe.Additions].Name, 63, "%s", Name); // recipe.Addition[recipe.Additions].Name = ""; // strncat(recipe.Addition[recipe.Additions].Name, Name, 63); recipe.Addition[recipe.Additions].Type = Type; recipe.Addition[recipe.Additions].Time = Time; recipe.Additions++; return; } // See if we already got one with the same time. for (int i = 0; i < recipe.Additions; i++) { if (recipe.Addition[i].Time == Time) { // Yes, update the name. //snprintf(recipe.Addition[i].Name, 63, "%s, %s", recipe.Addition[i].Name, Name); strncat(recipe.Addition[i].Name, ", ", 63 - strlen(recipe.Addition[i].Name)); strncat(recipe.Addition[i].Name, Name, 63 - strlen(recipe.Addition[i].Name)); return; } } // A new entry and we already have some. Add it and keep the list sorted. for (int i = 0; i < recipe.Additions; i++) { if (Time > recipe.Addition[i].Time) { // Make room for (int j = i; j < recipe.Additions; j++) { sprintf(recipe.Addition[j+1].Name, "%s", recipe.Addition[j].Name); recipe.Addition[j+1].Type = recipe.Addition[j].Type; recipe.Addition[j+1].Time = recipe.Addition[j].Time; } snprintf(recipe.Addition[i].Name, 63, "%s", Name); recipe.Addition[i].Type = Type; recipe.Addition[i].Time = Time; recipe.Additions++; return; } } // Just append. snprintf(recipe.Addition[recipe.Additions].Name, 63, "%s", Name); recipe.Addition[recipe.Additions].Type = Type; recipe.Addition[recipe.Additions].Time = Time; recipe.Additions++; } /** * @brief Reset the parser buffer. */ void reset_char_data_buffer (void) { offs = 0; overflow = false; } /** * @brief Datahandler get bytes into the data buffer. */ void char_data(void *userData, const XML_Char *s, int len) { if (!overflow) { if (len + offs >= sizeof(char_data_buffer) ) { overflow = true; } else { memcpy(char_data_buffer + offs, s, len); offs += len; } } } /** * @brief Process the XML parser data buffer. */ void process_char_data_buffer (void) { bool allspace = true; if (offs > 0) { char_data_buffer[ offs ] = '\0'; /* * If all spaces and control characters, the data is invalid. */ for (int i = 0; i < strlen(char_data_buffer); i++) { if (char_data_buffer[i] > ' ') allspace = false; } if (allspace) return; if ((_xml_depth > 2) && (strcmp(_xml_element[0], "RECIPES") == 0) && (strcmp(_xml_element[1], "RECIPE") == 0)) { /* * We are in a recipe */ if ((_xml_depth == 3) && (strcmp("NAME", _xml_element[2]) == 0)) { recipe.Name[0] = '\0'; strncat(recipe.Name, char_data_buffer, 127); } else if ((_xml_depth == 3) && (strcmp("BOIL_TIME", _xml_element[2]) == 0)) { recipe.BoilTime = atoi(char_data_buffer); } else if ((_xml_depth == 5) && (strcmp("HOPS", _xml_element[2]) == 0) && (strcmp("HOP", _xml_element[3]) == 0)) { /* * Hops that are added during the boil. * But check for whirlpool hops too. */ if (strcmp("NAME", _xml_element[4]) == 0) { _xml_add_name[0] = '\0'; strncat(_xml_add_name, char_data_buffer, 63); } else if (strcmp("USE", _xml_element[4]) == 0) { _xml_add_valid = (strcmp("Boil", char_data_buffer) == 0); // Only "Boil" is a valid hop if (strcmp("Aroma", char_data_buffer) == 0) { recipe.Whirlpool7 = 30; } } else if (strcmp("TIME", _xml_element[4]) == 0) { _xml_add_time = atoi(char_data_buffer); } } else if ((_xml_depth >= 3) && (strcmp("STYLE", _xml_element[2]) == 0)) { // Ignore } else if ((_xml_depth >= 3) && (strcmp("EQUIPMENT", _xml_element[2]) == 0)) { // Ignora } else if ((_xml_depth >= 3) && (strcmp("YEASTS", _xml_element[2]) == 0)) { // Ignore } else if ((_xml_depth >= 3) && (strcmp("WATERS", _xml_element[2]) == 0)) { // Ignore } else if ((_xml_depth == 5) && (strcmp("FERMENTABLES", _xml_element[2]) == 0) && (strcmp("FERMENTABLE", _xml_element[3]) == 0)) { /* * Fermentabes that must be added during the boil. */ if (strcmp("NAME", _xml_element[4]) == 0) { _xml_add_name[0] = '\0'; strncat(_xml_add_name, char_data_buffer, 63); } else if (strcmp("TYPE", _xml_element[4]) == 0) { if ((strcmp("Sugar", char_data_buffer) == 0)/* || (strcmp("Adjunct", char_data_buffer) == 0)*/) { _xml_add_type = ADDITION_FERMENTABLE; _xml_add_time = 10; } else { _xml_add_type = -1; } } else if (strcmp("ADD_AFTER_BOIL", _xml_element[4]) == 0) { _xml_add_valid = (strcmp("FALSE", char_data_buffer) == 0); } } else if ((_xml_depth == 5) && (strcmp("MISCS", _xml_element[2]) == 0) && (strcmp("MISC", _xml_element[3]) == 0)) { /* * Check for Misc additions to add during the boil. */ if (strcmp("NAME", _xml_element[4]) == 0) { _xml_add_name[0] = '\0'; strncat(_xml_add_name, char_data_buffer, 63); } else if (strcmp("USE", _xml_element[4]) == 0) { _xml_add_valid = (strcmp("Boil", char_data_buffer) == 0); // Only "Boil" is a valid hop. } else if (strcmp("TYPE", _xml_element[4]) == 0) { if (strcmp("Spice", char_data_buffer) == 0) _xml_add_type = ADDITION_SPICE; if (strcmp("Fining", char_data_buffer) == 0) _xml_add_type = ADDITION_FINING; if (strcmp("Herb", char_data_buffer) == 0) _xml_add_type = ADDITION_HERB; if (strcmp("Flavor", char_data_buffer) == 0) _xml_add_type = ADDITION_FLAVOR; if (strcmp("Other", char_data_buffer) == 0) _xml_add_type = ADDITION_OTHER; } else if (strcmp("TIME", _xml_element[4]) == 0) { _xml_add_time = atoi(char_data_buffer); } } else if ((_xml_depth >= 4) && (strcmp("MASH", _xml_element[2]) == 0)) { if ((_xml_depth >= 6) && (strcmp("MASH_STEP", _xml_element[4]) == 0)) { if (strcmp("NAME", _xml_element[5]) == 0) { _xml_add_name[0] = '\0'; strncat(_xml_add_name, char_data_buffer, 63); } else if (strcmp("TYPE", _xml_element[5]) == 0) { // Temperature Infusion Decoction _xml_add_valid = (strcmp("Temperature", char_data_buffer) == 0); if (strcmp("Infusion", char_data_buffer) == 0) _xml_add_type = MASHTYPE_INFUSION; else if (strcmp("Temperature", char_data_buffer) == 0) _xml_add_type = MASHTYPE_TEMPERATURE; else if (strcmp("Decoction", char_data_buffer) == 0) _xml_add_type = MASHTYPE_DECOCTION; } else if (strcmp("STEP_TEMP", _xml_element[5]) == 0) { _xml_add_temp = atof(char_data_buffer); } else if (strcmp("STEP_TIME", _xml_element[5]) == 0) { _xml_add_time = atoi(char_data_buffer); } else if (strcmp("RAMP_TIME", _xml_element[5]) == 0) { _xml_add_ramp = atoi(char_data_buffer); } else if (strcmp("INFUSE_AMOUNT", _xml_element[5]) == 0) { _xml_add_amount = atof(char_data_buffer); } else if (strcmp("INFUSE_TEMP", _xml_element[5]) == 0) { _xml_add_infusion = atof(char_data_buffer); } } else if ((_xml_depth >= 4) && (strcmp("TUN_TEMP", _xml_element[3]) == 0)) { // Save this and check later if this is the strike temperature. _xml_tun_temp = atof(char_data_buffer); } else if ((_xml_depth >= 4) && (strcmp("SPARGE_TEMP", _xml_element[3]) == 0)) { recipe.SpargeTemp = atof(char_data_buffer); } } } } } /** * @brief Start new XML element. * @param userData * @param name XML element name * @param attr XML element attribute */ void startElement(void *userData, const char *name, const char **attr) { sprintf(_xml_element[_xml_depth], "%s", name); _xml_depth++; process_char_data_buffer(); reset_char_data_buffer(); } /** * @brief End XML element * @param userData * @param name XML element name */ void endElement(void *userData, const char *name) { process_char_data_buffer(); reset_char_data_buffer(); if ((_xml_depth == 4) && (strcmp("HOP", _xml_element[3]) == 0)) { if (_xml_add_valid) { Addition_Add(_xml_add_name, ADDITION_HOP, _xml_add_time); } } if ((_xml_depth == 4) && (strcmp("FERMENTABLE", _xml_element[3]) == 0)) { if (_xml_add_valid && (_xml_add_type == ADDITION_FERMENTABLE)) { Addition_Add(_xml_add_name, _xml_add_type, _xml_add_time); } } if ((_xml_depth == 4) && (strcmp("MISC", _xml_element[3]) == 0)) { if (_xml_add_valid) { Addition_Add(_xml_add_name, _xml_add_type, _xml_add_time); } } if ((_xml_depth == 5) && (strcmp("MASH_STEP", _xml_element[4]) == 0)) { _xml_mashsteps++; recipe.MashStep[_xml_mashsteps].Name[0] = '\0'; strncat(recipe.MashStep[_xml_mashsteps].Name, _xml_add_name, 31); recipe.MashStep[_xml_mashsteps].Type = _xml_add_type; recipe.MashStep[_xml_mashsteps].Temperature = _xml_add_temp; recipe.MashStep[_xml_mashsteps].Resttime = _xml_add_time; recipe.MashStep[_xml_mashsteps].Ramptime = _xml_add_ramp; if (_xml_add_type == MASHTYPE_INFUSION) { recipe.MashStep[_xml_mashsteps].Infusion_temp = _xml_add_infusion; recipe.MashStep[_xml_mashsteps].Infusion_amount = _xml_add_amount; } else { recipe.MashStep[_xml_mashsteps].Infusion_temp = 0.0; recipe.MashStep[_xml_mashsteps].Infusion_amount = 0.0; } } _xml_depth--; } int ParseRecipe(char *fn, char *code) { char buf[512]; memset(&recipe, 0, sizeof(recipe)); XML_Parser parser = XML_ParserCreate(NULL); int done; _xml_depth = 0; _xml_mashsteps = 0; _xml_tun_temp = 0.0; XML_SetElementHandler(parser, startElement, endElement); XML_SetCharacterDataHandler(parser, char_data); FILE *fp = fopen(fn, "r"); do { int len = (int)fread(buf, 1, sizeof(buf), fp); done = len < sizeof(buf); if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) { printf( "%s at line %5lu\n", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); return 1; } vTaskDelay(2 / portTICK_PERIOD_MS); } while (!done); XML_ParserFree(parser); fclose(fp); /* * Fix missing things. */ snprintf(recipe.Code, 31, "%s", code); // The code is created from the recipe filename. for (int i = 0; i < strlen(recipe.Code); i++) { if ((recipe.Code[i] == ' ') || (recipe.Code[i] == '.')) { recipe.Code[i] = '\0'; break; } } recipe.CoolTemp = 20.0; sprintf(recipe.MashStep[0].Name, "Mash-in"); if ((recipe.MashStep[1].Type == MASHTYPE_INFUSION) && (recipe.MashStep[1].Infusion_temp > 0.0)) { /* If next (first original) step is infusion, take that temperature. */ recipe.MashStep[0].Temperature = recipe.MashStep[1].Infusion_temp; } else if (_xml_tun_temp > recipe.MashStep[1].Temperature) { recipe.MashStep[0].Temperature = _xml_tun_temp; } else { recipe.MashStep[0].Temperature = recipe.MashStep[1].Temperature + 1.25; } recipe.MashStep[0].Resttime = recipe.MashStep[0].Ramptime = 1; recipe.MashStep[0].Type = MASHTYPE_INFUSION; /* * Because we inserted the first infusion step and the next (original first) * step is infusion, change that one into temperature. */ if (recipe.MashStep[1].Type == MASHTYPE_INFUSION) { recipe.MashStep[1].Type = MASHTYPE_TEMPERATURE; recipe.MashStep[1].Infusion_temp = recipe.MashStep[1].Infusion_amount = 0.0; } if (! recipe.MashStep[7].Resttime) { // Move last mash step to position 7. for (int i = 6; i > 1; i--) { if (recipe.MashStep[i].Resttime) { // Got it, move. sprintf(recipe.MashStep[7].Name, "%s", recipe.MashStep[i].Name); recipe.MashStep[i].Name[0] = '\0'; recipe.MashStep[7].Type = recipe.MashStep[i].Type; recipe.MashStep[i].Type = 0; recipe.MashStep[7].Temperature = recipe.MashStep[i].Temperature; recipe.MashStep[i].Temperature = 0.0; recipe.MashStep[7].Resttime = recipe.MashStep[i].Resttime; recipe.MashStep[i].Resttime = 0; recipe.MashStep[7].Ramptime = recipe.MashStep[i].Ramptime; recipe.MashStep[i].Ramptime = 0; recipe.MashStep[7].Infusion_temp = recipe.MashStep[i].Infusion_temp; recipe.MashStep[i].Infusion_temp = 0.0; recipe.MashStep[7].Infusion_amount = recipe.MashStep[i].Infusion_amount; recipe.MashStep[i].Infusion_amount = 0.0; break; } } } #if 0 printf("Recipe: %s\n", recipe.Name); printf("Code : %s\n", recipe.Code); printf("Boil time %d minutes\n", recipe.BoilTime); printf("n Stepname T temp time ramp inft infa\n"); printf("- ------------------------------ - ----- ---- ---- ----- -----\n"); for (int i = 0; i < 8; i++) { if (recipe.MashStep[i].Resttime) { printf("%d %-30s %d %5.2f %4d %4d %5.2f %5.2f\n", i, recipe.MashStep[i].Name, recipe.MashStep[i].Type, recipe.MashStep[i].Temperature, recipe.MashStep[i].Resttime, recipe.MashStep[i].Ramptime, recipe.MashStep[i].Infusion_temp, recipe.MashStep[i].Infusion_amount); } } printf("%d additions\n", recipe.Additions); printf("n Addition name t Tim\n"); printf("- --------------------------------------------------------------- - ---\n"); for (int i = 0; i < recipe.Additions; i++) { printf("%d %-63s %d %3d\n", i, recipe.Addition[i].Name, recipe.Addition[i].Type, recipe.Addition[i].Time); } printf("Cooltemp %5.2f\n", recipe.CoolTemp); if (recipe.Whirlpool9) printf("Whirlpool9 %d\n", recipe.Whirlpool9); if (recipe.Whirlpool7) printf("Whirlpool7 %d\n", recipe.Whirlpool7); if (recipe.Whirlpool6) printf("Whirlpool6 %d\n", recipe.Whirlpool6); if (recipe.Whirlpool2) printf("Whirlpool2 %d\n", recipe.Whirlpool2); printf("SpargeTemp %5.2f\n", recipe.SpargeTemp); #endif return 0; } /* * Recipes init function, only runs once a new screen is entered. */ void Recipes_Init(void) { FILE *f; DIR *dir; struct dirent *de; size_t bytes; uint8_t *dst; uint16_t y; char filename[288], newname[288]; int rc; switch (Main_Screen) { case MAIN_TOOLS_RECIPE: TopMessage((char *)"Recepten importeren"); r_Imported = 0; TFT_setFont(DEFAULT_FONT, NULL); y = 28; if ((dir = opendir("/sdcard/recipe"))) { de = readdir(dir); while (de) { if (strstr(de->d_name, ".xml") || strstr(de->d_name, ".XML")) { _fg = TFT_YELLOW; TFT_print(de->d_name, 2, y); snprintf(filename, 287, "/sdcard/recipe/%s", de->d_name); snprintf(newname, 287, "/sdcard/recipe/%s", de->d_name); newname[strlen(newname) -2] = 'o'; newname[strlen(newname) -1] = 'k'; rc = ParseRecipe(filename, de->d_name); ESP_LOGI(TAG, "Recipe %s parsed, rc=%d", filename, rc); if (rc == 0) { append_recipe(); rename(filename, newname); _fg = TFT_GREEN; TFT_print((char *)" Ok", LASTX, y); } else { _fg = TFT_RED; TFT_print((char *)" Fout", LASTX, y); } r_Imported++; // Count them all y += 16; } de = readdir(dir); } closedir(dir); } f = fopen("/spiffs/etc/recipe.conf", "r"); dst = (uint8_t*)&recipe; r_Records = 0; while ((bytes = fread(dst, 1, sizeof(recipe), f))) { r_Records++; } fclose(f); // Load the default record. r_CurrentRec = config.RecipeRec; r_UpdateRec = true; break; case MAIN_TOOLS_RECIPE_EDIT: break; default: break; } } /* * Recipes management loop, non-blocking. */ void Recipes_Loop(void) { uint32_t crc1, crc2; uint8_t *dst; int mashsteps; uint16_t y; char tmp[64]; float mintemp; switch (Main_Screen) { case MAIN_TOOLS_RECIPE: if (r_Imported) { Buttons_Clear(); Buttons_Add(135, 210, 50, 30, (char *)"Ok" , 0); Buttons_Show(); while (true) { if (Buttons_Scan() == 0) break; vTaskDelay(20 / portTICK_PERIOD_MS); } Buttons_Clear(); r_Imported = 0; } if (r_UpdateRec) { _bg = TFT_BLACK; TFT_fillScreen(_bg); TFT_resetclipwin(); TopMessage((char *)"Recepten"); r_UpdateRec = false; read_recipe(r_CurrentRec); TFT_setFont(DEFAULT_FONT, NULL); ShowText(2, 28, (char *)"Naam", recipe.Name); ShowText(2, 44, (char *)"Code", recipe.Code); ShowInteger(162, 44, (char *)"Record", NULL, r_CurrentRec); ShowInteger(2, 60, (char *)"Kooktijd", (char *)" min", recipe.BoilTime); ShowFloat(162, 60, (char *)"Koel tot", (char *)" C", recipe.CoolTemp, 2); ShowFloat(2, 76, (char *)"Maisch in", (char *)" C", recipe.MashStep[0].Temperature, 2); ShowFloat(162, 76, (char *)"Spoelwater", (char *)" C", recipe.SpargeTemp, 2); y = 92; _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 = 1; i < 8; i++) { if (recipe.MashStep[i].Resttime) { TFT_print(recipe.MashStep[i].Name, 2, y); strcpy(tmp, mashTypes[recipe.MashStep[i].Type]); tmp[1] = '\0'; TFT_print(tmp, 200, y); sprintf(tmp, "%.2f", recipe.MashStep[i].Temperature); TFT_print(tmp, 220, y); sprintf(tmp, "%2d m", recipe.MashStep[i].Resttime); TFT_print(tmp, 280, y); 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 || recipe.Whirlpool7 || recipe.Whirlpool6 || recipe.Whirlpool2) { _fg = TFT_WHITE; TFT_print((char *)"Whirlpool ", 2, y); _fg = TFT_YELLOW; if (recipe.Whirlpool9) TFT_print((char *)"88+ ", LASTX, y); if (recipe.Whirlpool7) TFT_print((char *)"71+ ", LASTX, y); if (recipe.Whirlpool6) TFT_print((char *)"60+ ", LASTX, y); if (recipe.Whirlpool2) TFT_print((char *)"koud", LASTX, y); } Buttons_Clear(); Buttons_Add( 0, 210, 45, 30, (char *)"Ok" , 0); Buttons_Add( 46, 210, 45, 30, (char *)"+" , 1); if (r_CurrentRec != config.RecipeRec) Buttons_Add( 92, 210, 45, 30, (char *)"-", 2); else Buttons_Add( 92, 210, 45, 30, (char *)"" , 2); if (r_CurrentRec > 1) Buttons_Add(138, 210, 45, 30, (char *)"<", 3); else Buttons_Add(138, 210, 45, 30, (char *)"", 3); if (r_CurrentRec < r_Records) Buttons_Add(184, 210, 45, 30, (char *)">", 4); else Buttons_Add(184, 210, 45, 30, (char *)"", 4); if (r_CurrentRec != config.RecipeRec) Buttons_Add(230, 210, 45, 30, (char *)"Std", 5); else Buttons_Add(230, 210, 45, 30, (char *)"", 5); Buttons_Add(276, 210, 45, 30, (char *)"Ed" , 6); Buttons[0].dark = true; Buttons_Show(); } /* if (r_UpdateRec) */ switch (Buttons_Scan()) { case 0: Main_Screen = MAIN_TOOLS; break; case 1: memset(&recipe, 0, sizeof(recipe)); recipe.Version = 1; for (int i = 1; i < 8; i++) recipe.MashStep[i].Type = MASHTYPE_TEMPERATURE; sprintf(recipe.Name, "Recipe %d", r_Records + 1); sprintf(recipe.Code, "00%d", r_Records + 1); sprintf(recipe.MashStep[0].Name, "Mash-in"); recipe.MashStep[0].Type = MASHTYPE_INFUSION; recipe.MashStep[0].Temperature = recipe.MashStep[0].Infusion_temp = 67.5; recipe.MashStep[0].Infusion_amount = 15.0; recipe.MashStep[0].Resttime = 1; recipe.MashStep[0].Ramptime = 1; sprintf(recipe.MashStep[1].Name, "Mash"); recipe.MashStep[1].Temperature = 67.0; recipe.MashStep[1].Resttime = 75; recipe.MashStep[1].Ramptime = 1; sprintf(recipe.MashStep[7].Name, "Mash-out"); recipe.MashStep[7].Temperature = 78.0; recipe.MashStep[7].Resttime = 5; recipe.MashStep[7].Ramptime = 11; recipe.BoilTime = 60; recipe.Additions = 2; sprintf(recipe.Addition[0].Name, "Bitterhop"); recipe.Addition[0].Time = 60; recipe.Addition[0].Type = ADDITION_HOP; sprintf(recipe.Addition[1].Name, "Aromahop"); recipe.Addition[1].Time = 10; recipe.Addition[1].Type = ADDITION_HOP; recipe.CoolTemp = 20.0; recipe.Whirlpool9 = 0; recipe.Whirlpool7 = 0; recipe.Whirlpool6 = 0; recipe.Whirlpool2 = 0; recipe.SpargeTemp = 85.0; append_recipe(); r_Records++; r_CurrentRec = r_Records; r_UpdateRec = true; ESP_LOGI(TAG, "New recipe record %d", r_CurrentRec); break; case 2: if ((r_CurrentRec != config.RecipeRec) && (r_Records > 1)) { delete_recipe(r_CurrentRec); r_Records--; if (r_CurrentRec > r_Records) r_CurrentRec = r_Records; r_UpdateRec = true; } break; case 3: if (r_CurrentRec > 1) { r_CurrentRec--; r_UpdateRec = true; } break; case 4: if (r_CurrentRec < r_Records) { r_CurrentRec++; r_UpdateRec = true; } break; case 5: if (r_CurrentRec != config.RecipeRec) { config.RecipeRec = r_CurrentRec; write_config(); r_UpdateRec = true; ESP_LOGI(TAG, "Recipe %d `%s' set as default", r_CurrentRec, recipe.Name); } break; case 6: Main_Screen = MAIN_TOOLS_RECIPE_EDIT; break; default: break; } break; case MAIN_TOOLS_RECIPE_EDIT: dst = (uint8_t*)&recipe; crc1 = crc32_le(0, dst, sizeof(recipe)); EditText((char *)"Naam", recipe.Name, 31); EditText((char *)"Code", recipe.Code, 31); mashsteps = 0; for (int i = 1; i < 7; i++) { if (recipe.MashStep[i].Resttime) mashsteps++; } EditInt((char *)"Maisch stappen", &mashsteps, 1, 6); EditFloat((char *)"Inmaisch temperatuur", &recipe.MashStep[0].Temperature, 38, 74, 2); // Round to 0.25 values recipe.MashStep[0].Temperature = ((int)(recipe.MashStep[0].Temperature * 4)) / 4.0; for (int i = 1; i <= mashsteps; i++) { sprintf(tmp, "Maisch stap %d naam", i); EditText(tmp, recipe.MashStep[i].Name, 31); EditMashType(&recipe.MashStep[i].Type); sprintf(tmp, "Maisch stap %d temperatuur", i); if (i == 1) mintemp = recipe.MashStep[0].Temperature - 5; else mintemp = recipe.MashStep[i - 1].Temperature; EditFloat(tmp, &recipe.MashStep[i].Temperature, mintemp, 74, 2); recipe.MashStep[i].Temperature = ((int)(recipe.MashStep[i].Temperature * 4)) / 4.0; sprintf(tmp, "Maisch stap %d rusttijd in minuten", i); EditUint16(tmp, &recipe.MashStep[i].Resttime, 1, 480); if (i == 1) { recipe.MashStep[i].Ramptime = 1; } else { recipe.MashStep[i].Ramptime = (int)(recipe.MashStep[i].Temperature - recipe.MashStep[i - 1].Temperature); } if (recipe.MashStep[i].Type == MASHTYPE_INFUSION) { sprintf(tmp, "Stap %d infusie temperatuur", i); mintemp = recipe.MashStep[i].Temperature; EditFloat(tmp, &recipe.MashStep[i].Infusion_temp, mintemp, 100, 2); sprintf(tmp, "Stap %d infusie volume", i); EditFloat(tmp, &recipe.MashStep[i].Infusion_amount, 0.5, 1000.0, 2); } else { recipe.MashStep[i].Infusion_temp = recipe.MashStep[i].Infusion_amount = 0.0; } } mintemp = recipe.MashStep[mashsteps].Temperature; EditFloat((char *)"Uitmaischen temperatuur", &recipe.MashStep[7].Temperature, mintemp, 80, 2); recipe.MashStep[7].Temperature = ((int)(recipe.MashStep[7].Temperature * 4)) / 4.0; EditUint16((char *)"Uitmaischen tijd in minuten", &recipe.MashStep[7].Resttime, 1, 15); recipe.MashStep[7].Ramptime = (int)(recipe.MashStep[7].Temperature - recipe.MashStep[mashsteps].Temperature); // Zero any higher steps to diable them. for (int i = (mashsteps + 1); i < 7; i++) recipe.MashStep[i].Resttime = 0; EditUint16((char *)"Kook tijd in minuten", &recipe.BoilTime, 3, 480); EditUint8((char *)"Hop/kruiden toevoegingen", &recipe.Additions, 1, 10); for (uint8_t i = 0; i < recipe.Additions; i++) { sprintf(tmp, "Toevoeging %d naam", i+1); if (strlen(recipe.Addition[i].Name) == 00) { sprintf(recipe.Addition[i].Name, "Hop %d", (int)i+1); } EditText(tmp, recipe.Addition[i].Name, 40); sprintf(tmp, "Toevoeging %d tijd", i+1); if (i == 0) { EditUint16(tmp, &recipe.Addition[i].Time, 0, recipe.BoilTime); } else { EditUint16(tmp, &recipe.Addition[i].Time, 0, recipe.Addition[i-1].Time - 1); } recipe.Addition[i].Type = ADDITION_HOP; } EditFloat((char *)"Koel temperatuur", &recipe.CoolTemp, 10, 45, 2); recipe.CoolTemp = ((int)(recipe.CoolTemp * 4)) / 4.0; EditFloat((char *)"Spoelwater temperatuur", &recipe.SpargeTemp, 75, 98, 2); recipe.SpargeTemp = ((int)(recipe.SpargeTemp * 4)) / 4.0; EditUint16((char *)"Whirlpool 88..100 graden, 0 = niet", &recipe.Whirlpool9, 0, 120); EditUint16((char *)"Whirlpool 71..77 graden, 0 = niet", &recipe.Whirlpool7, 0, 120); EditUint16((char *)"Whirlpool 60..66 graden, 0 = niet", &recipe.Whirlpool6, 0, 120); EditUint16((char *)"Whirlpool koud, 0 = niet", &recipe.Whirlpool2, 0, 120); crc2 = crc32_le(0, dst, sizeof(recipe)); if ((crc1 != crc2) && Confirm((char *)"Gewijzigd, opslaan?", (char *)"Ja", (char *)"Nee")) { write_recipe(r_CurrentRec); } Main_Screen = MAIN_TOOLS_RECIPE; break; default: break; } }