Tue, 23 Oct 2018 20:39:40 +0200
Updated README
/** * @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_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 /** * @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) { printf("Addition_Add(%s, %d, %d)\n", Name, Type, Time); if (! recipe.Additions) { // No entries yet, add the first one. sprintf(recipe.Addition[recipe.Additions].Name, "%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. snprintf(recipe.Addition[i].Name, 63, "%s, %s", recipe.Addition[i].Name, 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) { printf("Insert at %d\n", i); // Nake 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; } sprintf(recipe.Addition[i].Name, "%s", Name); recipe.Addition[i].Type = Type; recipe.Addition[i].Time = Time; recipe.Additions++; return; } } // Just append. sprintf(recipe.Addition[recipe.Additions].Name, "%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)) { snprintf(recipe.Name, 127, "%s", char_data_buffer); } 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) { snprintf(_xml_add_name, 63, "%s", char_data_buffer); } 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) { snprintf(_xml_add_name, 63, "%s", char_data_buffer); } 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) { snprintf(_xml_add_name, 63, "%s", char_data_buffer); } 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) { snprintf(_xml_add_name, 31, "%s", char_data_buffer); } else if (strcmp("TYPE", _xml_element[5]) == 0) { // Temperature Infusion Decoction _xml_add_valid = (strcmp("Temperature", char_data_buffer) == 0); } 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 ((_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)) { // printf("Flush End MASH_STEP %d %s\n", _xml_depth, _xml_add_name); _xml_mashsteps++; sprintf(recipe.MashStep[_xml_mashsteps].Name, "%s", _xml_add_name); recipe.MashStep[_xml_mashsteps].Temperature = _xml_add_temp; recipe.MashStep[_xml_mashsteps].Resttime = _xml_add_time; recipe.MashStep[_xml_mashsteps].Steptime = _xml_add_ramp; } _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 (_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 = 1; 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].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].Steptime = recipe.MashStep[i].Steptime; recipe.MashStep[i].Steptime = 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 temp time ramp\n"); printf("- ------------------------------ ----- ---- ----\n"); for (int i = 0; i < 8; i++) { if (recipe.MashStep[i].Resttime) { printf("%d %-31s %5.2f %4d %4d\n", i, recipe.MashStep[i].Name, recipe.MashStep[i].Temperature, recipe.MashStep[i].Resttime, recipe.MashStep[i].Steptime); } } 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[256], newname[256]; int rc; switch (Main_Screen) { case MAIN_TOOLS_RECIPE: TopMessage("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, 255, "/sdcard/recipe/%s", de->d_name); snprintf(newname, 255, "/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(" Ok", LASTX, y); } else { _fg = TFT_RED; TFT_print(" 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[32]; float mintemp; switch (Main_Screen) { case MAIN_TOOLS_RECIPE: if (r_Imported) { Buttons_Clear(); Buttons_Add(135, 210, 50, 30, "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("Recepten"); r_UpdateRec = false; read_recipe(r_CurrentRec); TFT_setFont(DEFAULT_FONT, NULL); ShowText(2, 28, "Naam", recipe.Name); ShowText(2, 44, "Code", recipe.Code); ShowInteger(162, 44, "Record", NULL, r_CurrentRec); ShowInteger(2, 60, "Kooktijd", " min", recipe.BoilTime); ShowFloat(162, 60, "Koel tot", " C", recipe.CoolTemp, 2); ShowFloat(2, 76, "Maisch in", " C", recipe.MashStep[0].Temperature, 2); ShowFloat(162, 76, "Spoelwater", " C", recipe.SpargeTemp, 2); y = 92; _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; } } 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; if (recipe.Whirlpool9 || recipe.Whirlpool7 || recipe.Whirlpool6 || recipe.Whirlpool2) { _fg = TFT_WHITE; TFT_print("Whirlpool ", 2, y); _fg = TFT_YELLOW; if (recipe.Whirlpool9) TFT_print("88+ ", LASTX, y); if (recipe.Whirlpool7) TFT_print("71+ ", LASTX, y); if (recipe.Whirlpool6) TFT_print("60+ ", LASTX, y); if (recipe.Whirlpool2) TFT_print("koud", LASTX, y); } Buttons_Clear(); Buttons_Add( 0, 210, 45, 30, "Ok" , 0); Buttons_Add( 46, 210, 45, 30, "+" , 1); if (r_CurrentRec != config.RecipeRec) Buttons_Add( 92, 210, 45, 30, "-", 2); else Buttons_Add( 92, 210, 45, 30, "" , 2); if (r_CurrentRec > 1) Buttons_Add(138, 210, 45, 30, "<", 3); else Buttons_Add(138, 210, 45, 30, "", 3); if (r_CurrentRec < r_Records) Buttons_Add(184, 210, 45, 30, ">", 4); else Buttons_Add(184, 210, 45, 30, "", 4); if (r_CurrentRec != config.RecipeRec) Buttons_Add(230, 210, 45, 30, "Std", 5); else Buttons_Add(230, 210, 45, 30, "", 5); Buttons_Add(276, 210, 45, 30, "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; 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].Temperature = 67.5; recipe.MashStep[0].Resttime = 1; sprintf(recipe.MashStep[1].Name, "Mash"); recipe.MashStep[1].Temperature = 67.0; recipe.MashStep[1].Resttime = 75; sprintf(recipe.MashStep[7].Name, "Mash-out"); recipe.MashStep[7].Temperature = 78.0; recipe.MashStep[7].Resttime = 5; recipe.BoilTime = 60; recipe.Additions = 2; sprintf(recipe.Addition[0].Name, "Hop"); recipe.Addition[0].Time = 60; recipe.Addition[0].Type = ADDITION_HOP; sprintf(recipe.Addition[1].Name, "Hop"); 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("Naam", recipe.Name, 31); EditText("Code", recipe.Code, 31); mashsteps = 0; for (int i = 1; i < 7; i++) { if (recipe.MashStep[i].Resttime) mashsteps++; } EditInt("Maisch stappen", &mashsteps, 1, 6); EditFloat("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); 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].Steptime = 1; } else { recipe.MashStep[i].Steptime = (int)(recipe.MashStep[i].Temperature - recipe.MashStep[i - 1].Temperature); } } mintemp = recipe.MashStep[mashsteps].Temperature; EditFloat("Uitmaischen temperatuur", &recipe.MashStep[7].Temperature, mintemp, 80, 2); recipe.MashStep[7].Temperature = ((int)(recipe.MashStep[7].Temperature * 4)) / 4.0; EditUint16("Uitmaischen tijd in minuten", &recipe.MashStep[7].Resttime, 1, 15); recipe.MashStep[7].Steptime = (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("Kook tijd in minuten", &recipe.BoilTime, 3, 480); EditUint8("Hop/kruiden toevoegingen", &recipe.Additions, 1, 10); for (uint8_t i = 0; i < recipe.Additions; i++) { sprintf(tmp, "Toevoeging %d maam", 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("Koel temperatuur", &recipe.CoolTemp, 10, 30, 2); recipe.CoolTemp = ((int)(recipe.CoolTemp * 4)) / 4.0; EditFloat("Spoelwater temperatuur", &recipe.SpargeTemp, 75, 98, 2); recipe.SpargeTemp = ((int)(recipe.SpargeTemp * 4)) / 4.0; EditUint16("Whirlpool 88..100 graden, 0 = niet", &recipe.Whirlpool9, 0, 120); EditUint16("Whirlpool 71..77 graden, 0 = niet", &recipe.Whirlpool7, 0, 120); EditUint16("Whirlpool 60..66 graden, 0 = niet", &recipe.Whirlpool6, 0, 120); EditUint16("Whirlpool koud, 0 = niet", &recipe.Whirlpool2, 0, 120); crc2 = crc32_le(0, dst, sizeof(recipe)); if ((crc1 != crc2) && Confirm("Gewijzigd, opslaan?", "Ja", "Nee")) { write_recipe(r_CurrentRec); } Main_Screen = MAIN_TOOLS_RECIPE; break; default: break; } }