main/recipes.c

Mon, 01 Jun 2020 20:27:00 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Mon, 01 Jun 2020 20:27:00 +0200
changeset 76
3ff381bfa469
parent 75
224851e81117
child 77
66c77497d86d
permissions
-rw-r--r--

Version 0.3.10 Updated the webui. The recipe mash infusiont temperature rounding from 0.25 to 0.0625 degrees.

/**
 * @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.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));
					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.0625 values
			recipe.MashStep[0].Temperature = ((int)(recipe.MashStep[0].Temperature * 16)) / 16.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);
			    // Round to 0.25 values
			    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, 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;
			    }
			}

			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;
    }
}

mercurial