main/recipes.c

changeset 0
b74b0e4902c3
child 1
ad2c8b13eb88
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/recipes.c	Sat Oct 20 13:23:15 2018 +0200
@@ -0,0 +1,699 @@
+/**
+ * @file recipes.c
+ * @brief Recipes management.
+ */
+
+#include "config.h"
+
+
+extern sButton			Buttons[MAXBUTTONS];
+extern int			Main_Screen;
+
+bool				r_UpdateRec = false;
+int				r_CurrentRec = 1;
+int				r_Records = 1;
+int				r_Imported = 0;
+char                            _xml_element[10][32];
+int                             _xml_depth;
+int				_xml_mashsteps;
+char                            _xml_add_name[64];
+int                             _xml_add_type;
+int                             _xml_add_time;
+int				_xml_add_ramp;
+float				_xml_add_temp;
+float				_xml_tun_temp;
+bool                            _xml_add_valid;
+
+static const char               *TAG = "recipes";
+
+char				char_data_buffer[1024];
+size_t				offs;
+bool				overflow;
+
+
+
+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++;
+}
+
+
+
+void reset_char_data_buffer (void)
+{
+    offs = 0;
+    overflow = false;
+}
+
+
+
+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;
+        }
+    }
+}
+
+
+
+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);
+		}
+            }
+        }
+    }
+}
+
+
+
+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();
+}
+
+
+
+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;
+	    }
+	}
+    }
+
+    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);
+
+    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, recipe.Record);
+			    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;
+					recipe.Record = r_Records + 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", recipe.Record);
+					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("Inmaisch temperatuur", &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(recipe.Record);
+			}
+			Main_Screen = MAIN_TOOLS_RECIPE;
+			break;
+
+	default:	break;
+    }
+}
+
+

mercurial