Sat, 06 Jun 2020 22:51:44 +0200
Added directory sorting for the logfiles. Sort in reverse order on the timestamp so the last files will be on top.
/** * @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 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 start temperature float _xml_add_end; ///< Mash end 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].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. 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) { // Step_temp Infusion Decoction _xml_add_valid = (strcmp("Step_temp", 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 (strcmp("DECOCTION_AMT", _xml_element[5]) == 0) { _xml_add_amount = atof(char_data_buffer); } else if (strcmp("END_TEMP", _xml_element[5]) == 0) { _xml_add_end = 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)) { recipe.MashStep[recipe.Mashsteps].Name[0] = '\0'; strncat(recipe.MashStep[recipe.Mashsteps].Name, _xml_add_name, 31); recipe.MashStep[recipe.Mashsteps].Type = _xml_add_type; recipe.MashStep[recipe.Mashsteps].Step_temp = _xml_add_temp; recipe.MashStep[recipe.Mashsteps].Step_time = _xml_add_time; recipe.MashStep[recipe.Mashsteps].Ramp_time = _xml_add_ramp; recipe.MashStep[recipe.Mashsteps].End_temp = _xml_add_end; if (_xml_add_type == MASHTYPE_INFUSION) { recipe.MashStep[recipe.Mashsteps].Infuse_temp = _xml_add_infusion; recipe.MashStep[recipe.Mashsteps].Infuse_amount = _xml_add_amount; } else if (_xml_add_type == MASHTYPE_DECOCTION) { recipe.MashStep[recipe.Mashsteps].Infuse_temp = 0.0; recipe.MashStep[recipe.Mashsteps].Infuse_amount = _xml_add_amount; } else { recipe.MashStep[recipe.Mashsteps].Infuse_temp = 0.0; recipe.MashStep[recipe.Mashsteps].Infuse_amount = 0.0; } recipe.Mashsteps++; } _xml_depth--; } int ParseRecipe(char *fn, char *code) { char buf[512]; memset(&recipe, 0, recipe_hdr.recsize); for (int i = 0; i < recipe_hdr.mashmax; i++) recipe.MashStep[i].Type = 255; XML_Parser parser = XML_ParserCreate(NULL); int done; _xml_depth = 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) { ESP_LOGE(TAG, "%s at line %5lu", 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; #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].Step_time) { 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].Step_temp, recipe.MashStep[i].Step_time, recipe.MashStep[i].Ramp_time, recipe.MashStep[i].Infuse_temp, recipe.MashStep[i].Infuse_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"); fseek(f, recipe_hdr.hdrsize, SEEK_SET); dst = (uint8_t*)&recipe; r_Records = 0; while ((bytes = fread(dst, 1, recipe_hdr.recsize, 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; uint16_t y; char tmp[64]; float mintemp, maxtemp; int i; 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].Infuse_temp, 3); 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", 180, y); TFT_print((char *)"Temp.", 200, y); TFT_print((char *)"Rust", 280, y); _fg = TFT_YELLOW; y += 16; for (i = 0; i < recipe_hdr.mashmax; i++) { ESP_LOGI(TAG, "%2d %-31s %3d %5.2f %5.2f %2d %2d %7.4f %6.3f", i, recipe.MashStep[i].Name, recipe.MashStep[i].Type, recipe.MashStep[i].Step_temp, recipe.MashStep[i].End_temp, recipe.MashStep[i].Step_time, recipe.MashStep[i].Ramp_time, recipe.MashStep[i].Infuse_temp, recipe.MashStep[i].Infuse_amount); if (recipe.MashStep[i].Type != 255) { TFT_print(recipe.MashStep[i].Name, 2, y); strcpy(tmp, mashTypes[recipe.MashStep[i].Type]); tmp[1] = '\0'; TFT_print(tmp, 180, y); sprintf(tmp, "%.1f %.1f", recipe.MashStep[i].Step_temp, recipe.MashStep[i].End_temp); TFT_print(tmp, 200, y); sprintf(tmp, "%2d m", recipe.MashStep[i].Step_time); TFT_print(tmp, 280, y); y += 16; } } if (recipe.BoilTime) { 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)); // new recipe for (i = 1; i < recipe_hdr.mashmax; i++) recipe.MashStep[i].Type = 255; sprintf(recipe.Name, "Recept %d", r_Records + 1); sprintf(recipe.Code, "00%d", r_Records + 1); sprintf(recipe.MashStep[0].Name, "Maischen"); recipe.MashStep[0].Type = MASHTYPE_INFUSION; recipe.MashStep[0].Step_temp = recipe.MashStep[0].End_temp = 67.0; recipe.MashStep[0].Infuse_temp = 67.5; recipe.MashStep[0].Infuse_amount = 15.0; recipe.MashStep[0].Step_time = 75; recipe.MashStep[0].Ramp_time = 1; sprintf(recipe.MashStep[1].Name, "Mash-out"); recipe.MashStep[1].Type = MASHTYPE_TEMPERATURE; recipe.MashStep[1].Step_temp = recipe.MashStep[1].End_temp = 78.0; recipe.MashStep[1].Step_time = 10; recipe.MashStep[1].Ramp_time = 13; recipe.Mashsteps = 2; 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)) { _bg = TFT_BLACK; TFT_fillScreen(_bg); TFT_resetclipwin(); TopMessage((char *)"Recept verwijderen"); 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); EditUint8((char *)"Maisch stappen", &recipe.Mashsteps, 1, 6); for (i = 0; i < recipe.Mashsteps; i++) { int step = i + 1; if (i == (recipe.Mashsteps - 1)) { mintemp = 75.0; maxtemp = 80.0; } else { mintemp = 35.0; maxtemp = 74.0; } if (recipe.MashStep[i].Type == 255) { // A new step, set default values. recipe.MashStep[i].Type = MASHTYPE_TEMPERATURE; recipe.MashStep[i].Step_time = 20; if (i == 0) { recipe.MashStep[i].Type = MASHTYPE_INFUSION; recipe.MashStep[i].Step_temp = recipe.MashStep[i].End_temp = 62.0; recipe.MashStep[i].Infuse_temp = 62.5; recipe.MashStep[i].Infuse_amount = 15; recipe.MashStep[i].Ramp_time = 1; } else if (i == (recipe.Mashsteps - 1)) { recipe.MashStep[i].Step_temp = recipe.MashStep[i].End_temp = 78.0; recipe.MashStep[i].Step_time = 10; recipe.MashStep[i].Ramp_time = (int)(recipe.MashStep[i].Step_temp - recipe.MashStep[i - 1].Step_temp + 2); } else { recipe.MashStep[i].Step_temp = recipe.MashStep[i].End_temp = recipe.MashStep[i - 1].Step_temp + 2.0; recipe.MashStep[i].Ramp_time = (int)(recipe.MashStep[i].Step_temp - recipe.MashStep[i - 1].Step_temp + 2); } } sprintf(tmp, "Maisch stap %d naam", step); EditText(tmp, recipe.MashStep[i].Name, 31); EditMashType(&recipe.MashStep[i].Type); sprintf(tmp, "Maisch stap %d start temperatuur", step); EditFloat(tmp, &recipe.MashStep[i].Step_temp, mintemp, maxtemp, 2); // Round to 0.25 values recipe.MashStep[i].Step_temp = ((int)(recipe.MashStep[i].Step_temp * 4)) / 4.0; sprintf(tmp, "Maisch stap %d eind temperatuur", step); EditFloat(tmp, &recipe.MashStep[i].End_temp, mintemp, maxtemp, 2); recipe.MashStep[i].End_temp = ((int)(recipe.MashStep[i].End_temp * 4)) / 4.0; sprintf(tmp, "Maisch stap %d rusttijd in minuten", step); EditUint16(tmp, &recipe.MashStep[i].Step_time, 1, 480); if (recipe.MashStep[i].Type == MASHTYPE_INFUSION) { sprintf(tmp, "Stap %d infusie temperatuur", step); EditFloat(tmp, &recipe.MashStep[i].Infuse_temp, 10, 99, 3); recipe.MashStep[i].Infuse_temp = ((int)(recipe.MashStep[i].Infuse_temp * 16)) / 16.0; sprintf(tmp, "Stap %d infusie volume", step); EditFloat(tmp, &recipe.MashStep[i].Infuse_amount, 0.5, 1000.0, 3); recipe.MashStep[i].Ramp_time = 2; } else if (recipe.MashStep[i].Type == MASHTYPE_DECOCTION) { recipe.MashStep[i].Infuse_temp = 0.0; recipe.MashStep[i].Ramp_time = 2; sprintf(tmp, "Stap %d decoctie volume", step); EditFloat(tmp, &recipe.MashStep[i].Infuse_amount, 0.5, 1000.0, 3); } else { recipe.MashStep[i].Infuse_temp = recipe.MashStep[i].Infuse_amount = 0.0; sprintf(tmp, "Stap %d opwarm tijd", step); EditUint16(tmp, &recipe.MashStep[i].Ramp_time, 1, 480); } } for (i = recipe.Mashsteps; i < recipe_hdr.mashmax; i++) { recipe.MashStep[i].Type = 255; recipe.MashStep[i].Step_temp = recipe.MashStep[i].End_temp = recipe.MashStep[i].Infuse_temp = recipe.MashStep[i].Infuse_amount = 0.0; recipe.MashStep[i].Step_time = recipe.MashStep[i].Ramp_time = 0; recipe.MashStep[i].Name[0] = '\0'; } EditUint16((char *)"Kook tijd in minuten", &recipe.BoilTime, 0, 480); if (recipe.BoilTime) { 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; } } else { recipe.Additions = 0; } 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; } }