Added the beginning of the recipes editor. Added load and save of recipes file.

Tue, 15 Dec 2015 23:11:42 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Tue, 15 Dec 2015 23:11:42 +0100
changeset 459
1f88be70f253
parent 458
43a8ecb53637
child 460
0b2ea0ec165c

Added the beginning of the recipes editor. Added load and save of recipes file.

brewco/Makefile file | annotate | diff | comparison | revisions
brewco/README file | annotate | diff | comparison | revisions
brewco/brewco.c file | annotate | diff | comparison | revisions
brewco/brewco.h file | annotate | diff | comparison | revisions
brewco/prompt.c file | annotate | diff | comparison | revisions
brewco/rdrecipes.c file | annotate | diff | comparison | revisions
brewco/rdrecipes.h file | annotate | diff | comparison | revisions
brewco/setup.c file | annotate | diff | comparison | revisions
brewco/simulator.c file | annotate | diff | comparison | revisions
--- a/brewco/Makefile	Sat Dec 12 22:17:25 2015 +0100
+++ b/brewco/Makefile	Tue Dec 15 23:11:42 2015 +0100
@@ -54,10 +54,11 @@
 
 # DO NOT DELETE THIS LINE - MAKE DEPEND RELIES ON IT
 # Dependencies generated by make depend
-setup.o: brewco.h slcd.h setup.h prompt.h xutil.h keyboard.h rdconfig.h
+rdrecipes.o: brewco.h rdrecipes.h util.h xutil.h
+setup.o: brewco.h slcd.h setup.h prompt.h xutil.h keyboard.h rdconfig.h rdrecipes.h
 devices.o: brewco.h devices.h xutil.h keyboard.h slcd.h
 xutil.o: brewco.h xutil.h
-brewco.o: brewco.h rdconfig.h rdsession.h util.h xutil.h lcd-pcf8574.h slcd.h lock.h devices.h keyboard.h simulator.h prompt.h setup.h
+brewco.o: brewco.h rdconfig.h rdsession.h rdrecipes.h util.h xutil.h lcd-pcf8574.h slcd.h lock.h devices.h keyboard.h simulator.h prompt.h setup.h
 lock.o: lock.h brewco.h
 lcd-pcf8574.o: brewco.h lcd-pcf8574.h slcd.h
 pid.o: brewco.h pid.h util.h
--- a/brewco/README	Sat Dec 12 22:17:25 2015 +0100
+++ b/brewco/README	Tue Dec 15 23:11:42 2015 +0100
@@ -13,7 +13,7 @@
 
 Extra t.o.v. ArdBir: hopstands.
 1. 88..100 graden, 93 graden hold.
-2. 71..77 graden
+2. 72..77 graden
 3. 60..66 graden
 Inbouwn in Cooling. Timings: 1..4 uur.
 
--- a/brewco/brewco.c	Sat Dec 12 22:17:25 2015 +0100
+++ b/brewco/brewco.c	Tue Dec 15 23:11:42 2015 +0100
@@ -23,6 +23,7 @@
 #include "brewco.h"
 #include "rdconfig.h"
 #include "rdsession.h"
+#include "rdrecipes.h"
 #include "util.h"
 #include "xutil.h"
 #include "lcd-pcf8574.h"
@@ -457,7 +458,7 @@
 		minutes++;
 	    }
 
-fprintf(stdout, "%d seconds %d minutes %ld millis\n", seconds, minutes, millis());
+//fprintf(stdout, "%d seconds %d minutes %ld millis\n", seconds, minutes, millis());
 
 	    rc = device_in(unit->hlt_sensor.uuid, &temp);
 	    if (rc == DEVPRESENT_YES) {
@@ -551,7 +552,9 @@
 	}
 	sock = -1;
     }
+    wrrecipes();
     wrconfig();
+
 //    ulockprog((char *)"brewco");
     return 0;
 }
@@ -595,6 +598,14 @@
     if (debug)
 	fprintf(stdout, "configuration loaded\n");
 
+    if (rdrecipes()) {
+	fprintf(stderr, "Error reading recipes\n");
+    	syslog(LOG_NOTICE, "Error reading recipes: halted");
+	return 1;
+    }
+    if (debug)
+	fprintf(stdout, "recipes loaded\n");
+
     /*
      *  Catch all the signals we can, and ignore the rest. Note that SIGKILL can't be ignored
      *  but that's live. This daemon should only be stopped by SIGTERM.
--- a/brewco/brewco.h	Sat Dec 12 22:17:25 2015 +0100
+++ b/brewco/brewco.h	Tue Dec 15 23:11:42 2015 +0100
@@ -195,7 +195,7 @@
  * Recipes.
  */
 typedef struct _mash_step {
-    char		name[21];		/* Step name			*/
+    char		*name;			/* Step name			*/
     int			min;			/* Minimum temperature		*/
     int			max;			/* Maximum temperature		*/
     int			canskip;		/* 0/1 can we skip this step	*/
@@ -214,30 +214,31 @@
 // mash-out	75	80	noskip
 
 typedef struct _hop_stand {
-    char		name[21];		/* Hop stand name		*/
+    char		*name;			/* Hop stand name		*/
     int			min;			/* Minimum temperature		*/
     int			max;			/* Maximum temperature		*/
-    int			holdtemp;		/* Hold temperature at		*/
+    int			hold;			/* Hold temperature at		*/
     float		setpoint;		/* Hold temperature setpoint	*/
     int			skip;			/* Skip this one		*/
     int			duration;		/* Hold time			*/
 } hop_stand;
 
 typedef struct _hop_addition {
-    char		name[21];		/* Hop name			*/
-    int			boil_duration;		/* -1=fwh, 0..boiltime		*/
+    char		*name;			/* Hop name			*/
+    int			boiltime;		/* -1=fwh, 0..boiltime		*/
 } hop_addition;
 
 typedef struct _a_recipe {
-    char		uuid[37];		/* Recipe uuid			*/
-    char		code[21];		/* Recipe code			*/
-    char		name[21];		/* Recipe name			*/
+    struct _a_recipe	*next;
+    char		*uuid;			/* Recipe uuid			*/
+    char		*code;			/* Recipe code			*/
+    char		*name;			/* Recipe name			*/
+    int                 boiltime;               /* 1..240 minutes               */
+    time_t              starttime;		/* Start time                   */
+    time_t              endtime;                /* Ending time                  */
     mash_step		mash[8];		/* 8 mash steps			*/
     hop_addition	hops[10];		/* 10 hop additions		*/
-    int			boiltime;		/* 1..240 minutes		*/
     hop_stand		hopstand[3];		/* 3 hopstand slots		*/
-    time_t		started;		/* Start time			*/
-    time_t		endtime;		/* Ending time			*/
 } a_recipe;
 
 
--- a/brewco/prompt.c	Sat Dec 12 22:17:25 2015 +0100
+++ b/brewco/prompt.c	Tue Dec 15 23:11:42 2015 +0100
@@ -96,6 +96,8 @@
 			break;
 	case 138:	snprintf(message, Config.lcd_cols + 1, "  Change parameter  ");
 			break;
+	case 139:       snprintf(message, Config.lcd_cols + 1, "    Edit recipe     ");
+			break;
 	case 200:	snprintf(message, Config.lcd_cols + 1, text);
 			break;
 	case 202:	snprintf(message, Config.lcd_cols + 1, "   Manage Recipes   ");
@@ -136,6 +138,12 @@
 			break;
 	case 220:	snprintf(message, Config.lcd_cols + 1, "     Manual MLT     ");
 			break;
+	case 221:       snprintf(message, Config.lcd_cols + 1, "   Select Recipe    ");
+			break;
+	case 222:       snprintf(message, Config.lcd_cols + 1, " Select Brewsystem  ");
+			break;
+	case 223:       snprintf(message, Config.lcd_cols + 1, "   Select Device    ");
+			break;
 	case 300:	snprintf(message, Config.lcd_cols + 1, text);
 			break;
 	case 301:	snprintf(message, Config.lcd_cols + 1, "      Finished      ");
@@ -170,6 +178,12 @@
 			break;
 	case 413:	snprintf(message, Config.lcd_cols + 1, "UP* *DWN  heat  --- ");
 			break;
+	case 414:       snprintf(message, Config.lcd_cols + 1, "add  dwn  quit  ok  ");
+			break;
+	case 415:	snprintf(message, Config.lcd_cols + 1, "add  ---  quit  ok  ");
+			break;
+	case 416:	snprintf(message, Config.lcd_cols + 1, "add  ---  quit  --- ");
+			break;
 			//                                      12345678901234567890
 	default:	snprintf(message, Config.lcd_cols + 1, "   N/A        N/A");
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/brewco/rdrecipes.c	Tue Dec 15 23:11:42 2015 +0100
@@ -0,0 +1,655 @@
+/*****************************************************************************
+ * Copyright (C) 2015
+ *   
+ * Michiel Broek <mbroek at mbse dot eu>
+ *
+ * This file is part of the mbsePi-apps
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * mbsePi-apps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with ThermFerm; see the file COPYING.  If not, write to the Free
+ * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *****************************************************************************/
+
+#include "brewco.h"
+#include "rdrecipes.h"
+#include "util.h"
+#include "xutil.h"
+
+extern int	debug;
+
+a_recipe	*recipes = NULL;
+
+
+const char	SKIPYN[2][4] = { "NO", "YES" };
+
+
+#define MY_ENCODING "utf-8"
+
+
+int do_wrrecipes(void);
+int do_wrrecipes(void)
+{
+    int			i, rc = 0;
+    FILE		*fp;
+    char		*mypath = NULL;
+    a_recipe		*recipe;
+    xmlTextWriterPtr	writer;
+    xmlBufferPtr	buf;
+
+    /* 
+     * Create a new XML buffer, to which the XML document will be written
+     */
+    if ((buf = xmlBufferCreate()) == NULL) {
+	syslog(LOG_NOTICE, "wrrecipes: error creating the xml buffer");
+	return 1;
+    }
+
+    /* 
+     * Create a new XmlWriter for memory, with no compression.
+     */
+    if ((writer = xmlNewTextWriterMemory(buf, 0)) == NULL) {
+	syslog(LOG_NOTICE, "wrrecipes: error creating the xml writer");
+	return 1;
+    }
+
+    /*
+     * Use indentation instead of one long line
+     */
+    if ((rc = xmlTextWriterSetIndent(writer, 2)) < 0) {
+	syslog(LOG_NOTICE, "wrrecipes: error setting Indent");
+	return 1;
+    }
+
+    /* 
+     * Start the document with the xml default for the version,
+     * encoding ISO 8859-1 and the default for the standalone
+     * declaration. 
+     */
+    if ((rc = xmlTextWriterStartDocument(writer, NULL, MY_ENCODING, NULL)) < 0) {
+	syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartDocument");
+	return 1;
+    }
+
+    /* 
+     * Start an element named "BREWCO". Since thist is the first
+     * element, this will be the root element of the document.
+     */
+    if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "BREWCO")) < 0) {
+	syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement");
+	return 1;
+    }
+
+    if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "RECIPES")) < 0) {
+	syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement");
+	return 1;
+    }
+
+    for (recipe = recipes; recipe; recipe = recipe->next) {
+
+	if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "RECIPE")) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement");
+	    return 1;
+	}
+
+	if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "UUID", "%s", recipe->uuid)) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+	    return 1;
+	}
+	if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "CODE", "%s", recipe->code)) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+	    return 1;
+	}
+	if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "NAME", "%s", recipe->name)) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+	    return 1;
+	}
+	if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "BOILTIME", "%d", recipe->boiltime)) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+	    return 1;
+	}
+	if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "STARTTIME", "%d", (int)recipe->starttime)) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+	    return 1;
+	}
+	if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "ENDTIME", "%d", (int)recipe->endtime)) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+	    return 1;
+	}
+
+	/*
+	 * 8 Mash steps
+	 */
+	if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "MASHSTEPS")) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement");
+	    return 1;
+	}
+	for (i =0; i < 8; i++) {
+	    if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "MASHSTEP")) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "NAME", "%s", recipe->mash[i].name)) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "MIN", "%d", recipe->mash[i].min)) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "MAX", "%d", recipe->mash[i].max)) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "CANSKIP", "%s", recipe->mash[i].canskip ? "YES":"NO")) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "SETPOINT", "%f", recipe->mash[i].setpoint)) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "SKIP", "%s", recipe->mash[i].skip ? "YES":"NO")) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "DURATION", "%d", recipe->mash[i].duration)) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterEndElement(writer)) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement");
+		return 1;
+	    }
+	}
+	if ((rc = xmlTextWriterEndElement(writer)) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement");
+	    return 1;
+	}
+
+	/*
+	 * 10 Hop additions
+	 */
+	if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "HOPADDITIONS")) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement");
+	    return 1;
+	}
+	for (i = 0; i < 10; i++) {
+	    if (recipe->hops[i].name) {
+	        if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "HOPADDITION")) < 0) {
+		    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement");
+		    return 1;
+	    	}
+	    	if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "NAME", "%s", recipe->hops[i].name)) < 0) {
+		    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		    return 1;
+	    	}
+	    	if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "BOILTIME", "%d", recipe->hops[i].boiltime)) < 0) {
+		    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		    return 1;
+	    	}
+	    	if ((rc = xmlTextWriterEndElement(writer)) < 0) {
+		    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement");
+		    return 1;
+	    	}
+	    }
+	}
+	if ((rc = xmlTextWriterEndElement(writer)) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement");
+	    return 1;
+	}
+
+	/*
+	 * 3 Hop stands
+	 */
+	if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "HOPSTANDS")) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement");
+	    return 1;
+	}                                       
+	for (i = 0; i < 3; i++) {
+	    if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "HOPSTAND")) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "NAME", "%s", recipe->hopstand[i].name)) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "MIN", "%d", recipe->hopstand[i].min)) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "MAX", "%d", recipe->hopstand[i].max)) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "HOLD", "%s", recipe->hopstand[i].hold ? "YES":"NO")) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "SETPOINT", "%f", recipe->hopstand[i].setpoint)) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "SKIP", "%s", recipe->hopstand[i].skip ? "YES":"NO")) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "DURATION", "%d", recipe->hopstand[i].duration)) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
+		return 1;
+	    }
+	    if ((rc = xmlTextWriterEndElement(writer)) < 0) {
+		syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement");
+		return 1;
+	    }
+	}
+	if ((rc = xmlTextWriterEndElement(writer)) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement");
+	    return 1;
+	}
+
+	/* 
+	 * Close the element named RECIPE.
+	 */
+	if ((rc = xmlTextWriterEndElement(writer)) < 0) {
+	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement");
+	    return 1;
+	}
+    }
+    /* 
+     * Close the element named RECIPES.
+     */
+    if ((rc = xmlTextWriterEndElement(writer)) < 0) {
+	syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement");
+	return 1;
+    }
+
+    /*
+     * All done, close any open elements
+     */
+    if ((rc = xmlTextWriterEndDocument(writer)) < 0) {
+	syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndDocument");
+	return 1;
+    }
+    xmlFreeTextWriter(writer);
+
+    /*
+     * Now write the XML recipes
+     */
+    if (getenv((char *)"USER") == NULL) {
+	mypath = xstrcpy((char *)"/root");
+    } else {
+	mypath = xstrcpy(getenv((char *)"HOME"));
+    }
+    mypath = xstrcat(mypath, (char *)"/.brewco/etc/");
+    mkdirs(mypath, 0755);
+    mypath = xstrcat(mypath, (char *)"recipes.xml");
+
+    if (debug)
+	fprintf(stdout, "Writing %s\n", mypath);
+
+    if ((fp = fopen(mypath, "w")) == NULL) {
+	syslog(LOG_NOTICE, "could not rewrite %s", mypath);
+	free(mypath);
+	return 1;
+    }
+    free(mypath);
+
+    fprintf(fp, "%s", (const char *) buf->content);
+    fclose(fp);
+    xmlBufferFree(buf);
+
+    return 0;
+}
+
+
+
+int wrrecipes(void)
+{
+    int		rc;
+
+    rc = do_wrrecipes();
+    syslog(LOG_NOTICE, "Rewritten recipes, rc=%d", rc);
+    return rc;
+}
+
+
+
+int parseRecipe(xmlDocPtr doc, xmlNodePtr cur)
+{
+    xmlChar             *key;
+    xmlNodePtr		s1cur, s2cur;
+    a_recipe		*recipe, *tmp;
+    int                 i, j, ival;
+    float		fval;
+
+    recipe = (a_recipe *)malloc(sizeof(a_recipe));
+    memset(recipe, 0, sizeof(a_recipe));
+    recipe->next = NULL;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"UUID"))) {
+	    recipe->uuid = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CODE"))) {
+	    recipe->code = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    recipe->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BOILTIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->boiltime = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STARTTIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->starttime = (time_t)ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ENDTIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->endtime = (time_t)ival;
+	    xmlFree(key);
+	}
+fprintf(stdout, "cur %s\n", cur->name);
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MASHSTEPS"))) {
+	    s1cur = cur->xmlChildrenNode;
+	    i = 0;
+	    while (s1cur != NULL) {
+fprintf(stdout, "  s1cur %s\n", s1cur->name);
+
+		if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"MASHSTEP"))) {
+		    s2cur = s1cur->xmlChildrenNode;
+		    while (s2cur != NULL) {
+fprintf(stdout, "    s2cur %s\n", s2cur->name);
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"NAME"))) {
+			    recipe->mash[i].name = (char *)xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"MIN"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    if (sscanf((const char *)key, "%d", &ival) == 1)
+				recipe->mash[i].min = ival;
+			    xmlFree(key);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"MAX"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    if (sscanf((const char *)key, "%d", &ival) == 1)
+				recipe->mash[i].max = ival;
+			    xmlFree(key);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"CANSKIP"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    for (j = 0; j < 2; j++) {
+				if (! xmlStrcmp(key, (const xmlChar *)SKIPYN[j])) {
+				    recipe->mash[i].canskip = j;
+				    break;
+				}
+			    }
+			    xmlFree(key);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"SETPOINT"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    if (sscanf((const char *)key, "%f", &fval) == 1)
+				recipe->mash[i].setpoint = fval;
+			    xmlFree(key);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"SKIP"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    for (j = 0; j < 2; j++) {
+				if (! xmlStrcmp(key, (const xmlChar *)SKIPYN[j])) {
+				    recipe->mash[i].skip = j;
+				    break;
+				}
+			    }
+			    xmlFree(key);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"DURATION"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    if (sscanf((const char *)key, "%d", &ival) == 1)
+				recipe->mash[i].duration = ival;
+			    xmlFree(key);
+			}
+			s2cur = s2cur->next;
+		    }
+		    i++;
+		}
+		s1cur = s1cur->next;
+	    }
+	}
+
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HOPADDITIONS"))) {
+	    s1cur = cur->xmlChildrenNode;
+	    i = 0;
+	    while (s1cur != NULL) {
+fprintf(stdout, "  s1cur %s\n", s1cur->name);
+
+		if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"HOPADDITION"))) {
+		    s2cur = s1cur->xmlChildrenNode;
+		    while (s2cur != NULL) {
+fprintf(stdout, "    s2cur %s\n", s2cur->name);
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"NAME"))) {
+			    recipe->hops[i].name = (char *)xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"BOILTIME"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    if (sscanf((const char *)key, "%d", &ival) == 1)
+				recipe->hops[i].boiltime = ival;
+			    xmlFree(key);
+			}
+			s2cur = s2cur->next;
+		    }
+		    i++;
+		}
+		s1cur = s1cur->next;
+	    }
+	}
+
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HOPSTANDS"))) {
+	    s1cur = cur->xmlChildrenNode;
+	    i = 0;
+	    while (s1cur != NULL) {
+fprintf(stdout, "  s1cur %s\n", s1cur->name);
+
+		if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"HOPSTAND"))) {
+		    s2cur = s1cur->xmlChildrenNode;
+		    while (s2cur != NULL) {
+fprintf(stdout, "    s2cur %s\n", s2cur->name);
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"NAME"))) {
+			    recipe->hopstand[i].name = (char *)xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"MIN"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    if (sscanf((const char *)key, "%d", &ival) == 1)
+				recipe->hopstand[i].min = ival;
+			    xmlFree(key);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"MAX"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    if (sscanf((const char *)key, "%d", &ival) == 1)
+				recipe->hopstand[i].max = ival;
+			    xmlFree(key);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"HOLD"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    for (j = 0; j < 2; j++) {
+				if (! xmlStrcmp(key, (const xmlChar *)SKIPYN[j])) {
+				    recipe->hopstand[i].hold = j;
+				    break;
+				}
+			    }
+			    xmlFree(key);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"SETPOINT"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    if (sscanf((const char *)key, "%f", &fval) == 1)
+				recipe->hopstand[i].setpoint = fval;
+			    xmlFree(key);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"SKIP"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    for (j = 0; j < 2; j++) {
+				if (! xmlStrcmp(key, (const xmlChar *)SKIPYN[j])) {
+				    recipe->hopstand[i].skip = j;
+				    break;
+				}
+			    }
+			    xmlFree(key);
+			}
+			if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"DURATION"))) {
+			    key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1);
+			    if (sscanf((const char *)key, "%d", &ival) == 1)
+				recipe->hopstand[i].duration = ival;
+			    xmlFree(key);
+			}
+			s2cur = s2cur->next;
+		    }
+		    i++;
+		}
+		s1cur = s1cur->next;
+	    }
+	}
+	cur = cur->next;
+    }
+
+    if (recipes == NULL) {
+	recipes = recipe;
+    } else {
+	for (tmp = recipes; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = recipe;
+		break;
+	    }
+	}
+    }
+
+    return 0;
+}
+
+
+
+int parseRecipes(xmlDocPtr doc, xmlNodePtr cur)
+{
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"RECIPE"))) {
+	    parseRecipe(doc, cur);
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Returns:
+ *  0 - All is well, recipes loaded.
+ *  1 - Something went wrong
+ */
+int rdrecipes(void) 
+{
+    int		i;
+    char	*mypath;
+    xmlDocPtr	doc;
+    xmlNodePtr	cur;
+    a_recipe	*recipe;
+
+    syslog(LOG_NOTICE, "HOME='%s' USER='%s' LOGNAME='%s'", MBSE_SS(getenv((char *)"HOME")), MBSE_SS(getenv((char *)"USER")), MBSE_SS(getenv((char *)"LOGNAME")));
+
+    /*
+     * Search config file
+     */
+    if (getenv((char *)"USER") == NULL) {
+	mypath = xstrcpy((char *)"/root");
+    } else {
+    	mypath = xstrcpy(getenv((char *)"HOME"));
+    }
+    mypath = xstrcat(mypath, (char *)"/.brewco/etc/");
+    mkdirs(mypath, 0755);
+    mypath = xstrcat(mypath, (char *)"recipes.xml");
+
+    /*
+     * Free possible old recipes list
+     */
+    for (recipe = recipes; recipe; recipe = recipe->next) {
+	if (recipe->uuid)
+	    free(recipe->uuid);
+	if (recipe->code)
+	    free(recipe->code);
+	if (recipe->name)
+	    free(recipe->name);
+	for (i = 0; i < 8; i++)
+	    if (recipe->mash[i].name)
+		free(recipe->mash[i].name);
+	for (i = 0; i < 10; i++)
+	    if (recipe->hops[i].name)
+		free(recipe->hops[i].name);
+	for (i = 0; i < 3; i++)
+	    if (recipe->hopstand[i].name)
+		free(recipe->hopstand[i].name);
+	free(recipe);
+    }
+    recipes = NULL;
+
+    /*
+     * See if we have a recipes file.
+     */
+    if (file_exist(mypath, W_OK)) {
+	syslog(LOG_NOTICE, "rdrecipes: %s not found, good.", mypath);
+	free(mypath);
+	return 0;
+    }
+
+    if ((doc = xmlParseFile(mypath)) == NULL) {
+	syslog(LOG_NOTICE, "rdrecipes: %s not found, we may need some.", mypath);
+	free(mypath);
+	return 0;
+    }
+    syslog(LOG_NOTICE, "rdrecipes: using %s", mypath);
+
+    if ((cur = xmlDocGetRootElement(doc)) == NULL) {
+	syslog(LOG_NOTICE, "XML file %s empty.", mypath);
+	xmlFreeDoc(doc);
+	return 1;
+    }
+    if (xmlStrcmp(cur->name, (const xmlChar*)"BREWCO")) {
+	syslog(LOG_NOTICE, "XML file %s is not a valid configuration file.", mypath);
+	xmlFreeDoc(doc);
+	return 1;
+    }
+
+    /*
+     * Parse recipes
+     */
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"RECIPES"))) {
+	    parseRecipes(doc, cur);
+	}
+	cur = cur->next;
+    }
+    xmlFreeDoc(doc);
+
+    free(mypath);
+    mypath = NULL;
+
+    return 0;
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/brewco/rdrecipes.h	Tue Dec 15 23:11:42 2015 +0100
@@ -0,0 +1,9 @@
+#ifndef	_RDRECIPES_H
+#define	_RDRECIPES_H
+
+
+int  rdrecipes(void);
+int  wrrecipes(void);
+
+
+#endif
--- a/brewco/setup.c	Sat Dec 12 22:17:25 2015 +0100
+++ b/brewco/setup.c	Tue Dec 15 23:11:42 2015 +0100
@@ -27,6 +27,7 @@
 #include "xutil.h"
 #include "keyboard.h"
 #include "rdconfig.h"
+#include "rdrecipes.h"
 
 
 extern int		my_shutdown;
@@ -34,6 +35,7 @@
 extern sys_config       Config;
 extern int              lcdHandle;
 extern int		slcdHandle;
+extern a_recipe		*recipes;
 
 
 
@@ -755,6 +757,341 @@
 
 
 
+/*
+ * Edit a single recipe
+ */
+void editRecipe(a_recipe *recipe)
+{
+    int         idx = 1, key;
+    char        pmpt[81];
+    uLong       ocrc, ncrc;
+
+    if (debug)
+        fprintf(stdout, "Start edit recipe `%s' %s\n", recipe->name, recipe->uuid);
+
+    prompt(0, NULL);
+    ncrc = ocrc = crc32(0L, Z_NULL, 0);
+    ocrc = crc32(ocrc, (const Bytef *) recipe, sizeof(a_recipe));
+
+    for (;;) {
+
+        prompt(0, NULL);
+        prompt(139, NULL);
+
+        if (idx == 1)
+            prompt(402, NULL);
+        else if (idx == 21)
+            prompt(404, NULL);
+        else
+            prompt(403, NULL);
+
+        switch (idx) {                          //   12345678901234567890
+            case 1:     snprintf(pmpt, Config.lcd_cols + 1, "Recipe name:");
+                        prompt(200, pmpt);
+                        snprintf(pmpt, Config.lcd_cols + 1, "%s", recipe->name);
+                        prompt(300, pmpt);
+                        break;
+	    case 2:	snprintf(pmpt, Config.lcd_cols + 1, "Recipe code:");
+			prompt(200, pmpt);
+			snprintf(pmpt, Config.lcd_cols + 1, "%s", recipe->code);
+			prompt(300, pmpt);
+			break;
+	    case 3:     snprintf(pmpt, Config.lcd_cols + 1, "Boil time: %3d mins", recipe->boiltime);
+			prompt(200, pmpt);
+			break;
+	}
+
+        key = keywait();
+        if ((key == KEY_RETURN) || my_shutdown) {
+            ncrc = crc32(ncrc, (const Bytef *)recipe, sizeof(a_recipe));
+            if (ocrc != ncrc)
+                wrrecipes();
+            if (debug)
+                fprintf(stdout, "End edit recipe `%s' %s\n", recipe->name, recipe->uuid);
+            return;
+        }
+
+        if ((key == KEY_UP) && (idx > 1))
+            idx--;
+        if ((key == KEY_DOWN) && (idx < 6))
+            idx++;
+
+        if (key == KEY_ENTER) {
+            switch(idx) {
+                case 1:         // name
+                                break;
+		case 2:		// code
+				break;
+		case 3:		editInteger(&recipe->boiltime, 60, 240, 5, (char *)"Boil time:", (char *)"mins");
+				break;
+	    }
+    	}
+    }
+}
+
+
+
+void addRecipe(int number)
+{
+    a_recipe	*tmpu, *recipe = (a_recipe *)malloc(sizeof(a_recipe));
+    char        name[81];
+    uuid_t      uu;
+    int		i;
+
+    if (debug)
+        fprintf(stdout, "Adding new recipe %d\n", number);
+    recipe->next = NULL;
+    recipe->uuid = malloc(37);
+    uuid_generate(uu);
+    uuid_unparse(uu, recipe->uuid);
+    snprintf(name, 21, "New recipe %d", number);
+    recipe->name = xstrcpy(name);
+    snprintf(name, 21, "%04d", number);
+    recipe->code = xstrcpy(name);
+    recipe->boiltime = 90;
+    recipe->starttime = recipe->endtime = (time_t)0;
+    /*
+     * Initial mash schedule, set a single-step 67 degr. mash
+     */
+    recipe->mash[0].name = xstrcpy((char *)"Mash-in");
+    recipe->mash[0].min = 20;
+    recipe->mash[0].max = 80;
+    recipe->mash[0].canskip = 0;
+    recipe->mash[0].setpoint = 68.0;
+    recipe->mash[0].skip = 0;
+    recipe->mash[0].duration = 1;
+    recipe->mash[1].name = xstrcpy((char *)"Phytase");
+    recipe->mash[1].min = 25;
+    recipe->mash[1].max = 55;
+    recipe->mash[1].canskip = 1;
+    recipe->mash[1].setpoint = 40.0;
+    recipe->mash[1].skip = 1;
+    recipe->mash[1].duration = 10;
+    recipe->mash[2].name = xstrcpy((char *)"Glucanase");
+    recipe->mash[2].min = 35; 
+    recipe->mash[2].max = 50;
+    recipe->mash[2].canskip = 1;
+    recipe->mash[2].setpoint = 45.0;
+    recipe->mash[2].skip = 1;
+    recipe->mash[2].duration = 10;
+    recipe->mash[3].name = xstrcpy((char *)"Protease");
+    recipe->mash[3].min = 45; 
+    recipe->mash[3].max = 60;
+    recipe->mash[3].canskip = 1;
+    recipe->mash[3].setpoint = 55.0;
+    recipe->mash[3].skip = 1;
+    recipe->mash[3].duration = 10;
+    recipe->mash[4].name = xstrcpy((char *)"B-Amylase");
+    recipe->mash[4].min = 50; 
+    recipe->mash[4].max = 70;
+    recipe->mash[4].canskip = 1;
+    recipe->mash[4].setpoint = 62.0;
+    recipe->mash[4].skip = 1;
+    recipe->mash[4].duration = 30;
+    recipe->mash[5].name = xstrcpy((char *)"A-Amylase 1");
+    recipe->mash[5].min = 60; 
+    recipe->mash[5].max = 76;
+    recipe->mash[5].canskip = 1;
+    recipe->mash[5].setpoint = 67.0;
+    recipe->mash[5].skip = 1;
+    recipe->mash[5].duration = 30;
+    recipe->mash[6].name = xstrcpy((char *)"A-Amylase 2");
+    recipe->mash[6].min = 60; 
+    recipe->mash[6].max = 76;
+    recipe->mash[6].canskip = 0;
+    recipe->mash[6].setpoint = 67.0;
+    recipe->mash[6].skip = 0;
+    recipe->mash[6].duration = 60;
+    recipe->mash[7].name = xstrcpy((char *)"Mash-out");
+    recipe->mash[7].min = 75;
+    recipe->mash[7].max = 80;
+    recipe->mash[7].canskip = 0;
+    recipe->mash[7].setpoint = 78.0;
+    recipe->mash[7].skip = 0;
+    recipe->mash[7].duration = 10;
+
+    /*
+     * Add 2 hop additions, maximum 10
+     */
+    recipe->hops[0].name = xstrcpy((char *)"Hops 1");
+    recipe->hops[0].boiltime = 90;
+    recipe->hops[1].name = xstrcpy((char *)"Hops 2");
+    recipe->hops[1].boiltime = 5;
+    for (i = 2; i < 10; i++) {
+	recipe->hops[i].name = NULL;
+	recipe->hops[i].boiltime = 0;
+    }
+
+
+    /*
+     * Add 3 hopstands, disabled by default.
+     */
+    recipe->hopstand[0].name = xstrcpy((char *)"Hopstand hot");
+    recipe->hopstand[0].min = 88;
+    recipe->hopstand[0].max = 100;
+    recipe->hopstand[0].hold = 0;
+    recipe->hopstand[0].setpoint = 93.0;
+    recipe->hopstand[0].skip = 1;
+    recipe->hopstand[0].duration = 30;
+    recipe->hopstand[1].name = xstrcpy((char *)"Hopstand default");
+    recipe->hopstand[1].min = 72;
+    recipe->hopstand[1].max = 77;
+    recipe->hopstand[1].hold = 0;
+    recipe->hopstand[1].setpoint = 75.0;
+    recipe->hopstand[1].skip = 1;
+    recipe->hopstand[1].duration = 60;
+    recipe->hopstand[2].name = xstrcpy((char *)"Hopstand cool");
+    recipe->hopstand[2].min = 60;
+    recipe->hopstand[2].max = 66;
+    recipe->hopstand[2].hold = 0;
+    recipe->hopstand[2].setpoint = 63.0;
+    recipe->hopstand[2].skip = 1;
+    recipe->hopstand[2].duration = 60;
+
+    editRecipe(recipe);
+
+    if (recipes == NULL) {
+        recipes = recipe;
+    } else {
+        for (tmpu = recipes; tmpu; tmpu = tmpu->next) {
+            if (tmpu->next == NULL) {
+                tmpu->next = recipe;
+                break;
+            }
+        }
+    }
+    syslog(LOG_NOTICE, "Recipe %d added", number);
+    if (debug)
+        fprintf(stdout, "Recipe %d added\n", number);
+}
+
+
+
+void editRecipes(void)
+{
+    int         total, i, key, choice = 1;;
+    a_recipe	*recipe;
+    char        pmpt[81];
+
+    prompt(0, NULL);
+    for (;;) {
+	total = 0;
+	for (recipe = recipes; recipe; recipe = recipe->next) {
+	    total++;
+	}
+
+	if (debug)
+	    fprintf(stdout, "editRecipes total=%d choice=%d\n", total, choice);
+
+	i = 0;
+	if (total) {
+	    for (recipe = recipes; recipe; recipe = recipe->next) {
+	    	i++;
+	    	if (i == choice)
+		    break;
+	    }
+	}
+
+	prompt(102, NULL);          /* "     SETUP MENU     " */
+	prompt(221, NULL);          /* "   Select Recipe    " */
+	if (total) {
+	    snprintf(pmpt, Config.lcd_cols + 1, "%s", recipe->name);
+	    prompt(300, pmpt);
+	}
+	if (total == 0)
+	    prompt(416, NULL);      /* "add  ---  quit  --- " */
+	else if (total == 1)
+	    prompt(415, NULL);      /* "add  ---  quit  ok  " */
+	else if (total && (choice == 1))
+	    prompt(414, NULL);      /* "add  dwn  quit  ok  " */
+	else if (total && (choice == total))
+	    prompt(404, NULL);      /* " up  ---  quit  ok  " */
+	else
+	    prompt(403, NULL);      /* " up  dwn  quit  ok  " */
+
+	key = keywait();
+	if ((key == KEY_RETURN) || my_shutdown)
+	    return;
+	if (total && (key == KEY_ENTER)) {
+	    editRecipe(recipe);
+	    prompt(0, NULL);
+	}
+	if (key == KEY_UP) {
+	    if ((total == 1) || (choice == 1))
+		addRecipe(total + 1);
+	    else if (choice > 1)
+	    	choice--;
+	}
+	if ((key == KEY_DOWN) && (total > 1) && (choice < total))
+	    choice++;
+    }
+}
+
+
+
+void editUnits(void)
+{
+    int		total, i, key, choice = 1;;
+    units_list	*unit;
+    char	pmpt[81];
+
+    prompt(0, NULL);
+    for (;;) {
+    	total = 0;
+        for (unit = Config.units; unit; unit = unit->next) {
+	    total++;
+    	}
+
+	if (debug)
+	    fprintf(stdout, "editUnits total=%d choice=%d\n", total, choice);
+
+	if (total == 0) {
+	    /*
+	     * Impossible unless first setup was skipped
+	     */
+	    addUnit(1);
+	    total = 1;
+	} else {
+	    i = 0;
+	    for (unit = Config.units; unit; unit = unit->next) {
+		i++;
+		if (i == choice)
+		    break;
+	    }
+	    prompt(102, NULL);		/* "     SETUP MENU     " */
+	    prompt(222, NULL);		/* " Select Brewsystem  " */
+	    snprintf(pmpt, Config.lcd_cols + 1, "%s", unit->name);
+	    prompt(300, pmpt);
+	    if (total == 1)
+		prompt(415, NULL);	/* "add  ---  quit  ok  " */
+	    else if (choice == 1)
+		prompt(414, NULL);	/* "add  dwn  quit  ok  " */
+	    else if (choice == total)
+		prompt(404, NULL);	/* " up  ---  quit  ok  " */
+	    else
+		prompt(403, NULL);	/* " up  dwn  quit  ok  " */
+
+	    key = keywait();
+	    if ((key == KEY_RETURN) || my_shutdown)
+		return;
+	    if (key == KEY_ENTER) {
+		editUnit(unit);
+		prompt(0, NULL);
+	    }
+	    if (key == KEY_UP) {
+		if ((total == 1) || (choice == 1))
+		    addUnit(total + 1);
+		else if (choice > 1)
+		    choice--;
+	    }
+	    if ((key == KEY_DOWN) && (total > 1) && (choice < total))
+		choice++;
+	}
+    }
+}
+
+
+
 void setup(void)
 {
     int		key, option = 202;
@@ -763,18 +1100,18 @@
 	if (debug)
 	    fprintf(stdout, "setup() option=%d\n", option);
 	prompt(0, NULL);
-	prompt(102, NULL);
+	prompt(102, NULL);		/* "     SETUP MENU     " */
 	prompt(option, NULL);
 	if (option == 202)
-	    prompt(402, NULL);
+	    prompt(402, NULL);		/* "---  dwn  quit  ok  " */
 #ifdef USE_SIMULATOR
 	else if (option == 205)
 #else
 	else if (option == 204)
 #endif
-	    prompt(404, NULL);
+	    prompt(404, NULL);		/* " up  ---  quit  ok  " */
 	else
-	    prompt(403, NULL);
+	    prompt(403, NULL);		/* " up  dwn  quit  ok  " */
 
 	key = keywait();
 
@@ -793,9 +1130,9 @@
 
 	if (key == KEY_ENTER) {
 	    switch(option) {
-		case 202:	// recipes
+		case 202:	editRecipes();
 				break;
-		case 203:	editUnit(Config.units);	/* If more units, via a selector */
+		case 203:	editUnits();
 				break;
 		case 204:	// devices
 				break;
--- a/brewco/simulator.c	Sat Dec 12 22:17:25 2015 +0100
+++ b/brewco/simulator.c	Tue Dec 15 23:11:42 2015 +0100
@@ -115,11 +115,11 @@
 	    /*
 	     * Each second
 	     */
-	    if (debug)
-		fprintf(stdout, "HLT temp=%f plate=%f val=%d   MLT temp=%f plate=%f val=%d   Room %.1f loops=%d  cool=%s\n",
-			Config.simulator->hlt_temperature, Config.simulator->hlt_heater_temp, Config.simulator->hlt_heater_state,
-			Config.simulator->mlt_temperature, Config.simulator->mlt_heater_temp, Config.simulator->mlt_heater_state,
-			Config.simulator->room_temperature, loops, SIM_cooler ? "Yes":"No");
+//	    if (debug)
+//		fprintf(stdout, "HLT temp=%f plate=%f val=%d   MLT temp=%f plate=%f val=%d   Room %.1f loops=%d  cool=%s\n",
+//			Config.simulator->hlt_temperature, Config.simulator->hlt_heater_temp, Config.simulator->hlt_heater_state,
+//			Config.simulator->mlt_temperature, Config.simulator->mlt_heater_temp, Config.simulator->mlt_heater_state,
+//			Config.simulator->room_temperature, loops, SIM_cooler ? "Yes":"No");
 	    loops = 0;
 	}
 	usleep(50000);

mercurial