The brew state machine is complete, works but is not bugfree

Sat, 26 Dec 2015 21:45:44 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Sat, 26 Dec 2015 21:45:44 +0100
changeset 473
fdd30e935079
parent 472
55bcbf92ecab
child 474
fe1c3e3e90dc

The brew state machine is complete, works but is not bugfree

brewco/Makefile file | annotate | diff | comparison | revisions
brewco/brewco.c file | annotate | diff | comparison | revisions
brewco/brewco.h file | annotate | diff | comparison | revisions
brewco/devices.c file | annotate | diff | comparison | revisions
brewco/logger.c file | annotate | diff | comparison | revisions
brewco/logger.h file | annotate | diff | comparison | revisions
brewco/pid.c file | annotate | diff | comparison | revisions
brewco/prompt.c file | annotate | diff | comparison | revisions
brewco/rdconfig.c file | annotate | diff | comparison | revisions
brewco/rdrecipes.c file | annotate | diff | comparison | revisions
brewco/rdsession.c file | annotate | diff | comparison | revisions
brewco/setup.c file | annotate | diff | comparison | revisions
brewco/setup.h file | annotate | diff | comparison | revisions
brewco/simulator.c file | annotate | diff | comparison | revisions
--- a/brewco/Makefile	Tue Dec 22 21:07:14 2015 +0100
+++ b/brewco/Makefile	Sat Dec 26 21:45:44 2015 +0100
@@ -58,8 +58,9 @@
 setup.o: brewco.h slcd.h setup.h prompt.h xutil.h keyboard.h rdconfig.h rdrecipes.h
 devices.o: brewco.h devices.h util.h xutil.h keyboard.h slcd.h
 xutil.o: brewco.h xutil.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
+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 logger.h
 lock.o: lock.h brewco.h
+logger.o: logger.h brewco.h util.h xutil.h
 lcd-pcf8574.o: brewco.h lcd-pcf8574.h slcd.h
 pid.o: brewco.h pid.h util.h
 util.o: brewco.h util.h slcd.h
--- a/brewco/brewco.c	Tue Dec 22 21:07:14 2015 +0100
+++ b/brewco/brewco.c	Sat Dec 26 21:45:44 2015 +0100
@@ -34,11 +34,12 @@
 #include "simulator.h"
 #include "prompt.h"
 #include "setup.h"
+#include "logger.h"
 
 
 
 int			my_shutdown = FALSE;
-int			man_mlt_pump = 0;
+int			mlt_pump_state = 0;
 
 double			hltInput;			/* HLT PID variables		*/
 double			hltOutput;
@@ -139,7 +140,7 @@
 
 
 
-void tempstatus(double hlttemp, double mlttemp)
+void tempstatus(void)
 {
     char	text[81];
 
@@ -198,107 +199,608 @@
 
 
 
-void automatic_brew(units_list *, brew_session *, a_recipe *, int);
-void automatic_brew(units_list *unit, brew_session *brew, a_recipe *recipe, int dosave)
+/*
+ * Third line during boil, only MLT status
+ */
+void mltstatus(void)
+{
+    char        text[21];
+
+    snprintf(text, 20, "MLT %3d%%  %6.2f\002      ", (int)mltOutput, mltSetpoint);
+#ifdef HAVE_WIRINGPI_H
+    piLock(LOCK_LCD);
+    lcdPosition(lcdHandle, 0, 2);
+    lcdPuts(lcdHandle, text);
+#endif
+    slcdPosition(slcdHandle, 0, 2);
+    slcdPuts(slcdHandle, text);
+}
+
+
+
+void timestatus(int row, int timeval)
+{
+    char	text[21];
+    int		hours, mins, val = timeval;
+
+    hours = val / 3600;
+    val -= (hours * 3600);
+    mins = val / 60;
+    val -= (mins * 60);
+ 
+    snprintf(text, 20, "      %02d:%02d:%02d      ", hours, mins, val);
+#ifdef HAVE_WIRINGPI_H
+    piLock(LOCK_LCD);
+    lcdPosition(lcdHandle, 0, row);
+    lcdPuts(lcdHandle, text);
+#endif
+    slcdPosition(slcdHandle, 0, row);
+    slcdPuts(slcdHandle, text);
+}
+
+
+
+int set_HLT_heater(units_list *unit, int state, double val)
 {
-    int		key, save = dosave;
+    if (strcmp(unit->hlt_heater.uuid, (char *)"00000000-0000-0000-0000-000000000000") == 0)
+	return 0;
+
+    hltSetpoint = val;
+
+    if (state && (PID_getMode(unit->PID_hlt) == P_MANUAL)) {
+	hlt_status(1);
+	PID_setMode(unit->PID_hlt, P_AUTOMATIC);
+	return 1;
+    }
+    if (! state && (PID_getMode(unit->PID_hlt) == P_AUTOMATIC)) {
+	hlt_status(0);
+	PID_setMode(unit->PID_hlt, P_MANUAL);
+	return 1;
+    }
+
+    return 0;
+}
+
+
+
+int set_MLT_heater(units_list *unit, int state, double val)
+{
+    if (strcmp(unit->mlt_heater.uuid, (char *)"00000000-0000-0000-0000-000000000000") == 0)
+	return 0;
+
+    mltSetpoint = val;
 
-//    if (debug)
-//	fprintf(stdout, "auto: step %d\n", brew->brewstep);
+    if (state && (PID_getMode(unit->PID_mlt) == P_MANUAL)) {
+	mlt_status(1);
+	PID_setMode(unit->PID_mlt, P_AUTOMATIC);
+	return 1;
+    }
+    if (! state && (PID_getMode(unit->PID_mlt) == P_AUTOMATIC)) {
+	mlt_status(0);
+	PID_setMode(unit->PID_mlt, P_MANUAL);
+	return 1;
+    }
+
+    return 0;
+}
+
+
+
+int set_MLT_pump(units_list *unit, int state)
+{
+    if (strcmp(unit->mlt_pump.uuid, (char *)"00000000-0000-0000-0000-000000000000") == 0)
+	return 0;
+
+    if (state && ! mlt_pump_state) {
+	device_out(unit->mlt_pump.uuid, 1);
+	mlt_pump_state = 1;
+	return 1;
+    }
+    if (! state && mlt_pump_state) {
+	device_out(unit->mlt_pump.uuid, 0);
+	mlt_pump_state = 0;
+	return 1;
+    }
+
+    return 0;
+}
+
+
+
+void automatic_brew(units_list *, brew_session *, a_recipe *, int, int);
+void automatic_brew(units_list *unit, brew_session *brew, a_recipe *recipe, int dosave, int seconds)
+{
+    int		key, save = dosave, i;
+    char	data[128];
+    static int	mash_fase = MASH_NA, hopstand = 0, last_step = -1, last_fase = -1, oldsec = 75, startdelay = 0;
 
     switch (brew->brewstep) {
 	case STEP_NA:		if (debug)
 				    fprintf(stdout, "auto: init recipe: %s-%s  unit: %s\n", recipe->code, recipe->name, unit->name);
 				syslog(LOG_NOTICE, "AUTO: starting new brew, recipe: %s-%s  unit: %s", recipe->code, recipe->name, unit->name);
+				brew->brewstep = STEP_BREWINIT;
+				break;
+
+	case STEP_BREWINIT:	prompt(103, NULL);		/* "   AUTOMATIC MODE   " */
+				prompt(207, NULL);		/* "    Delay start?    " */
+				prompt(300, NULL);		/* "                    " */
+				prompt(407, NULL);		/* "---  ---   No   Yes " */
+				if (debug)
+					fprintf(stdout, "step brewinit\n");
+				key = keywait();
+				if (key == KEY_RETURN) {
+				    brew->brewstep = STEP_WATERCHECK;
+				    startdelay = 0;
+				    syslog(LOG_NOTICE, "AUTO: brew initialize, direct start selected");
+				    break;
+				}
+				if (key == KEY_ENTER) {
+				    startdelay = 30;
+				    editInteger(&startdelay, 10, 960, 10, (char *)"Start delay", (char *)"mins");
+				}
+				syslog(LOG_NOTICE, "AUTO: brew initialize");
+				brew->brewstep = STEP_WATERCHECK;
+				break;
+
+	case STEP_WATERCHECK:	if (brew->brewstep != last_step) {
+				    prompt(111, NULL);		/* "AUTO --> Mash In    " */
+				    prompt(209, NULL);		/* "    Water Added?    " */
+				    prompt(300, NULL);
+				    prompt(410, NULL);          /* " Continue: Yes  No  " */
+				    last_step = brew->brewstep;
+				}
+				slcdDummy(slcdHandle);
+				key = keycheck();
+				if (key == KEY_RETURN) {
+				    brew->brewstep = STEP_PUMPPRIME;
+				    syslog(LOG_NOTICE, "AUTO: confirmed water added");
+				}
+				if (key == KEY_ENTER) {
+				    syslog(LOG_NOTICE, "AUTO: aborted water added");
+				    brew->brewstep = STEP_CLEANUP;
+				}
+				break;
+
+	case STEP_PUMPPRIME:	if (brew->brewstep != last_step) {
+				    prompt(111, NULL);		/* "AUTO --> Mash In    " */
+				    prompt(210, NULL);		/* "     Pump Prime     " */
+				    prompt(300, NULL);		/* "                    " */
+				    prompt(400, NULL);		/* "                    " */
+				    last_step = brew->brewstep;
+				}
+				brew->brewstep = STEP_WAITSTART;
+				break;
+
+	case STEP_WAITSTART:	if (startdelay == 0) {
+				    brew->brewstep = STEP_PREMASH;
+				    break;
+				}
+				if (brew->brewstep != last_step) {
+				    brew->timeout = startdelay * 60;
+				    prompt(111, NULL);          /* "AUTO --> Mash In    " */
+				    prompt(212, NULL);          /* "  To be started in  " */
+				    prompt(410, NULL);          /* " Continue: Yes  No  " */
+				    last_step = brew->brewstep;
+				}
+				if (oldsec != seconds) {
+				    timestatus(2, brew->timeout);
+				    brew->timeout--;
+				    if (brew->timeout <= 0) {
+					syslog(LOG_NOTICE, "AUTO: delayed start time reached");
+					brew->brewstep = STEP_PREMASH;
+				    }
+				    oldsec = seconds;
+				}
+				slcdDummy(slcdHandle);
+				key = keycheck();
+				if (key == KEY_RETURN) {
+				    syslog(LOG_NOTICE, "AUTO: delayed start skipped by user");
+				    brew->brewstep = STEP_PREMASH;
+				}
+				break;
+
+	case STEP_PREMASH:	if (brew->brewstep != last_step) {
+				    prompt(111 + brew->mashstep, NULL);     /* "AUTO --> [mashname] " */
+				    prompt(300, NULL);                      /* "                    " */
+				    prompt(418, NULL);                      /* "---  ---  Pause --- " */
+				    tempstatus();
+				    hlt_status(1);
+				    mlt_status(1);
+				    pump_status(unit->pump_premash);
+				    last_step = brew->brewstep;
+				}
+				initlog(brew->name);
+				if (set_HLT_heater(unit, 1, 85.0))
+				    syslog(LOG_NOTICE, "AUTO: premash turn on HLT at %6.2f", hltSetpoint);
+				if (set_MLT_heater(unit, 1, recipe->mash[0].setpoint))
+				    syslog(LOG_NOTICE, "AUTO: premash turn on MLT at %6.2f", mltSetpoint);
+				if (set_MLT_pump(unit, unit->pump_premash))
+				    syslog(LOG_NOTICE, "AUTO: premash turn %s MLT pump", mlt_pump_state ? "on":"off");
+				brew->brewstep = STEP_MASHING;
+				brew->mashstep = 0;
+				mash_fase = MASH_NA;
+				save = TRUE;
+				break;
+
+	case STEP_MASHING:	if (brew->brewstep != last_step) {
+				    prompt(111 + brew->mashstep, NULL);     /* "AUTO --> [mashname] " */
+				    prompt(300, NULL);                      /* "                    " */
+				    prompt(418, NULL);                      /* "---  ---  Pause --- " */
+				    tempstatus();
+				    hlt_status(1);
+				    mlt_status(1);
+				    pump_status(unit->pump_onmash);
+				    last_step = brew->brewstep;
+				}
+				if (set_HLT_heater(unit, 1, 85.0))
+				    syslog(LOG_NOTICE, "AUTO: mash turn on HLT at %6.2f", hltSetpoint);
+				if (set_MLT_heater(unit, 1, recipe->mash[brew->mashstep].setpoint))
+				    syslog(LOG_NOTICE, "AUTO: mash turn on MLT at %6.2f", mltSetpoint);
+				if ((mash_fase != MASH_REST) && set_MLT_pump(unit, unit->pump_onmash))
+				    syslog(LOG_NOTICE, "AUTO: mash turn %s MLT pump", mlt_pump_state ? "on":"off");
+
+				switch (mash_fase) {
+				    case MASH_NA:	if (recipe->mash[brew->mashstep].skip) {
+							    syslog(LOG_NOTICE, "AUTO: skipping mash step %d", brew->mashstep);
+							    brew->mashstep++;
+							} else {
+					    		    mltSetpoint = recipe->mash[brew->mashstep].setpoint;
+							    brew->timeout = recipe->mash[brew->mashstep].duration * 60;
+					    		    if ((brew->mashstep == 0) && ! unit->skip_add) {
+							    	mash_fase = MASH_PROMPT;
+							    } else
+							    	mash_fase = MASH_HEATING;
+							    syslog(LOG_NOTICE, "AUTO: mash step %d fase NA, setpoint %6.2f, duration %d", 
+									brew->mashstep, mltSetpoint, brew->timeout);
+							}
+							if (brew->mashstep == 0) {
+							    brew->starttime = time(NULL);
+							    save = TRUE;
+							}
+					    		break;
+
+				    case MASH_PROMPT:	if (last_fase != mash_fase) {
+							    prompt(111 + brew->mashstep, NULL);	/* "AUTO --> [mashname] " */
+							    prompt(219, NULL);              	/* "    Mash added?     " */
+							    prompt(300, NULL);
+							    prompt(410, NULL);              	/* " Continue: Yes  No  " */
+							    last_fase = mash_fase;
+							}
+							slcdDummy(slcdHandle);
+							key = keycheck();
+							if (key == KEY_RETURN) {
+							    mash_fase = MASH_HEATING;
+							    syslog(LOG_NOTICE, "AUTO: confirmed mash added");
+							}
+							if (key == KEY_ENTER) {
+							    syslog(LOG_NOTICE, "AUTO: aborted mash added");
+							    brew->brewstep = STEP_CLEANUP;
+							    brew->mashstep = 0;
+							}
+							break;
+
+				    case MASH_IODINE:	if (last_fase != mash_fase) {
+							    prompt(118, NULL);          /* "AUTO --> Mash Out   " */
+							    prompt(213, NULL);          /* "    Iodine test     " */
+							    timestatus(2, brew->timeout);
+							    prompt(410, NULL);          /* " Continue: Yes  No  " */
+							    last_fase = mash_fase;
+							}
+							if (oldsec != seconds) {
+							    brew->timeout--;
+							    timestatus(2, brew->timeout);
+							    if (brew->timeout <= 0) {
+								syslog(LOG_NOTICE, "AUTO: mash IODINE test timeout");
+								mash_fase = MASH_NA;
+								brew->mashstep++;
+							    }
+							    oldsec = seconds;
+							}
+							slcdDummy(slcdHandle);
+							key = keycheck();
+							if (key == KEY_RETURN) {
+							    syslog(LOG_NOTICE, "AUTO: mash IODINE test confirmed");
+							    mash_fase = MASH_NA;
+							    brew->mashstep++;
+							}
+							if (key == KEY_ENTER) {
+							    syslog(LOG_NOTICE, "AUTO: mash IODINE test declined");
+							    mash_fase = MASH_REST;
+							    brew->timeout = 600;	/* Add 10 more minutes */
+							}
+							break;
+
+				    case MASH_HEATING:	if (last_fase != mash_fase) {
+							    prompt(111 + brew->mashstep, NULL);     /* "AUTO --> [mashname] " */
+							    prompt(200, NULL);
+							    prompt(300, NULL);
+							    prompt(418, NULL);                      /* "---  ---  Pause --- " */
+							    hlt_status(1);
+							    mlt_status(1);
+							    pump_status(unit->pump_onmash);
+							    last_fase = mash_fase;
+							}
+							if (oldsec != seconds) {
+							    tempstatus();
+							    percstatus((seconds / 2) % 4);
+							    oldsec = seconds;
+							}
+							if (mltInput > mltSetpoint) {
+							    syslog(LOG_NOTICE, "AUTO: mash step %d fase HEATING reached %6.2f", brew->mashstep, mltSetpoint);
+							    mash_fase = MASH_REST;
+							}
+							break;
+
+				    case MASH_REST:	if (last_fase != mash_fase) {
+							    prompt(111 + brew->mashstep, NULL);     /* "AUTO --> [mashname] " */
+							    prompt(300, NULL);
+							    prompt(418, NULL);                      /* "---  ---  Pause --- " */
+							    hlt_status(1);
+							    mlt_status(1);
+							    pump_status(unit->pump_onmash);
+							    last_fase = mash_fase;
+							}
+							if (oldsec != seconds) {
+							    tempstatus();
+							    timestatus(2, brew->timeout);
+							    if (brew->mashstep == 7) {
+								/*
+								 * During mash-out rest, allow the grain to sink
+								 */
+								if (set_MLT_pump(unit, unit->pump_mashout))
+								    syslog(LOG_NOTICE, "AUTO: mash-out turn %s MLT pump", mlt_pump_state ? "on":"off");
+							    } else {
+								if (set_MLT_pump(unit, unit->pump_onmash))
+								    syslog(LOG_NOTICE, "AUTO: mash turn %s MLT pump", mlt_pump_state ? "on":"off");
+							    }
+							    brew->timeout--;
+							    if (brew->timeout <= 0) {
+								syslog(LOG_NOTICE, "AUTO: mash step %d fase REST done", brew->mashstep);
+								if ((brew->mashstep == 6) && ! unit->skip_iodine) {
+								    mash_fase = MASH_IODINE;
+								    brew->timeout = unit->iodine_time * 60;
+								} else if (brew->mashstep == 7) {
+								    mash_fase = MASH_DONE;
+								} else {
+								    mash_fase = MASH_NA;
+								    brew->mashstep++;
+								}
+							    }
+							    oldsec = seconds;
+							}
+							break;
+
+				    case MASH_DONE:	syslog(LOG_NOTICE, "AUTO: mash step %d fase DONE", brew->mashstep);
+							if (set_MLT_heater(unit, 1, recipe->mash[7].setpoint))
+							    syslog(LOG_NOTICE, "AUTO: mash done turn MLT off");
+							if (set_MLT_pump(unit, 0))
+							    syslog(LOG_NOTICE, "AUTO: mash done turn %s MLT pump", mlt_pump_state ? "on":"off");
+							brew->mashstep = 0;
+							brew->brewstep = STEP_MASHREMOVE;
+							break;
+				}
+				break;
+
+	case STEP_MASHREMOVE:	if (unit->skip_remove) {
+				    syslog(LOG_NOTICE, "AUTO: skipping Mash remove");
+				    brew->brewstep = STEP_PREBOIL;
+				} else {
+				    if (brew->brewstep != last_step) {
+					prompt(118, NULL);          /* "AUTO --> Mash Out   " */
+					prompt(220, NULL);          /* "   Mash Removed?    " */
+					prompt(300, NULL);
+					prompt(410, NULL);          /* " Continue: Yes  No  " */
+					last_step = brew->brewstep;
+				    }
+				    slcdDummy(slcdHandle);
+				    key = keycheck();
+				    if (key == KEY_RETURN) {
+					syslog(LOG_NOTICE, "AUTO: Confirmed Mash removed");
+					brew->brewstep = STEP_PREBOIL;
+				    }
+				}
+				break;
+
+	case STEP_PREBOIL:	if (brew->brewstep != last_step) {
+				    prompt(119, NULL);          /* "AUTO --> Boil       " */
+				    prompt(200, NULL);
+				    prompt(300, NULL);
+				    tempstatus();
+				    mltstatus();
+				    prompt(418, NULL);          /* "---  ---  Pause --- " */
+				    hlt_status(0);
+				    mlt_status(1);
+				    pump_status(unit->pump_onboil && (mltInput < unit->pump_stop));
+				    last_step = brew->brewstep;
+				}
+				if (oldsec != seconds) {
+				    tempstatus();
+				    mltstatus();
+				    oldsec = seconds;
+				}
+				if (set_HLT_heater(unit, 0, 10.0))
+				    syslog(LOG_NOTICE, "AUTO: preboil turn off HLT");
+				if (set_MLT_heater(unit, 1, 100.0))
+				    syslog(LOG_NOTICE, "AUTO: preboil turn on MLT to boil");
+				if (set_MLT_pump(unit, unit->pump_onboil && (mltInput < unit->pump_stop)))
+				    syslog(LOG_NOTICE, "AUTO: preboil turn %s MLT pump", mlt_pump_state ? "on":"off");
+				if (mltInput > 99.2) {
+				    syslog(LOG_NOTICE, "AUTO: reached boil temperature %.2f, start %d minutes boil", mltInput, recipe->boiltime);
+				    brew->brewstep = STEP_BOILING;
+				    brew->boiltimer = recipe->boiltime * 60;
+				}
+				break;
+
+	case STEP_BOILING:	if (set_HLT_heater(unit, 0, 10.0))
+				    syslog(LOG_NOTICE, "AUTO: boil turn off HLT");
+				if (set_MLT_heater(unit, 1, 100.0))
+				    syslog(LOG_NOTICE, "AUTO: boil turn on MLT to boil");
+				if (set_MLT_pump(unit, unit->pump_onboil && (mltInput < unit->pump_stop)))
+				    syslog(LOG_NOTICE, "AUTO: boil turn %s MLT pump", mlt_pump_state ? "on":"off");
+				if (brew->brewstep != last_step) {
+				    prompt(119, NULL);          /* "AUTO --> Boil       " */
+				    prompt(200, NULL);
+				    prompt(300, NULL);
+				    tempstatus();
+				    mltstatus();
+				    prompt(418, NULL);          /* "---  ---  Pause --- " */
+				    hlt_status(0);
+				    mlt_status(1);
+				    pump_status(unit->pump_onboil && (mltInput < unit->pump_stop));
+				    last_step = brew->brewstep;
+				}
+				if (oldsec != seconds) {
+				    tempstatus();
+				    if ((seconds / 2) % 4) {
+				        timestatus(2, brew->boiltimer);
+				    } else {
+					mltstatus();
+				    }
+				    if (brew->boiltimer >= 0) {
+				    	brew->boiltimer--;
+				    } else {
+					brew->brewstep = STEP_BOILDONE;
+				        syslog(LOG_NOTICE, "AUTO: boil is done");
+				    }
+				    oldsec = seconds;
+				}
+				break;
+
+	case STEP_BOILDONE:	if (set_MLT_heater(unit, 0, 10.0))
+				    syslog(LOG_NOTICE, "AUTO: after boil turn off MLT heater");
+				if (set_MLT_pump(unit, 0))
+				    syslog(LOG_NOTICE, "AUTO: after boil turn %s MLT pump", mlt_pump_state ? "on":"off");
+				if (brew->brewstep != last_step) {
+				    prompt(120, NULL);          /* "AUTO --> Cooling    " */
+				    prompt(214, NULL);          /* "   START COOLING    " */
+				    prompt(300, NULL);
+				    prompt(410, NULL);		/* " Continue: Yes  No  " */
+				    last_step = brew->brewstep;
+				}
+				slcdDummy(slcdHandle);
+				key = keycheck();
+				if (key == KEY_ENTER) {
+				    brew->brewstep = STEP_CLEANUP;
+				    syslog(LOG_NOTICE, "AUTO: user skipped cooling");
+				}
+				if (key == KEY_RETURN) {
+				    brew->brewstep = STEP_COOLING;
+				    syslog(LOG_NOTICE, "AUTO: user started cooling");
+				}
+				break;
+
+	case STEP_COOLING:	for (i = 0; i < 3; i++) {
+				    if ((recipe->hopstand[i].skip == 0) && (mltInput <= recipe->hopstand[i].max) && (mltInput >= recipe->hopstand[i].min)) {
+				        brew->brewstep = STEP_HOPSTAND;
+					hopstand = i;
+					syslog(LOG_NOTICE, "AUTO: starting hopstand %d", i+1);
+				    }
+				}
+				if (brew->brewstep == STEP_HOPSTAND)
+				    break;
+				// hot whirlpool start at 85 degrees
+				// cold whirlpool start at 30 degrees
+
+				if (brew->brewstep != last_step) {
+				    prompt(120, NULL);          /* "AUTO --> Cooling    " */
+				    prompt(200, NULL);
+				    tempstatus();
+				    prompt(300, NULL);
+				    prompt(418, NULL);          /* "---  ---  Pause --- " */
+				    hlt_status(0);
+				    mlt_status(0);
+				    pump_status(0);
+				    last_step = brew->brewstep;
+				}
+				if (oldsec != seconds) {
+				    tempstatus();
+				    oldsec = seconds;
+				}
+				if (mltInput <= recipe->coolto) {
+				    syslog(LOG_NOTICE, "AUTO: cool temperture %.2f reached", recipe->coolto);
+				    brew->brewstep = STEP_CLEANUP;
+				}
+				break;
+
+	case STEP_HOPSTAND:	if (brew->brewstep != last_step) {
+				    prompt(122 + hopstand, NULL);	/* "AUTO --> Hopstand n " */
+				    tempstatus();
+				    timestatus(2, brew->timeout);
+				    prompt(418, NULL);			/* "---  ---  Pause --- " */
+				    brew->boiltimer = recipe->hopstand[hopstand].duration * 60;
+				    last_step = brew->brewstep;
+				}
+
+				if (recipe->hopstand[hopstand].hold) {
+				    if (set_MLT_heater(unit, 1, recipe->hopstand[hopstand].setpoint))
+					syslog(LOG_NOTICE, "AUTO: hopstand 1 turn on MLT at %6.2f", mltSetpoint);
+				}
+				if (set_MLT_pump(unit, 1))
+				    syslog(LOG_NOTICE, "AUTO: hopstand 1 turn %s MLT pump", mlt_pump_state ? "on":"off");
+				if (oldsec != seconds) {
+				    tempstatus();
+				    timestatus(2, brew->timeout);
+				    brew->boiltimer--;
+				    if (brew->boiltimer <= 0) {
+					syslog(LOG_NOTICE, "AUTO: hopstand %d done", hopstand+1);
+					if (set_MLT_heater(unit, 0, 10.0))
+					    syslog(LOG_NOTICE, "AUTO: hopstand 1 turn off MLT at %6.2f", mltSetpoint);
+					brew->brewstep = STEP_COOLING;
+				    }
+				    oldsec = seconds;
+				}
+				break;
+
+	case STEP_WHIRLPOOL:	prompt(121, NULL);		/* "AUTO --> Whirlpool  " */
+				break;
+
+	case STEP_CLEANUP:	if (brew->brewstep != last_step) {
+				    prompt(101, NULL);              /* "    Brewco x.x.x    " */
+				    prompt(200, NULL);              /* "                    " */
+				    prompt(300, NULL);              /* "                    " */
+				    prompt(400, NULL);              /* "                    " */
+				    last_step = brew->brewstep;
+				}
+				syslog(LOG_NOTICE, "AUTO: cleanup");
+				if (set_HLT_heater(unit, 0, 10.0))
+				    syslog(LOG_NOTICE, "AUTO: cleanup turn on HLT at %6.2f", hltSetpoint);
+				if (set_MLT_heater(unit, 0, 10.0))
+				    syslog(LOG_NOTICE, "AUTO: cleanup turn on MLT at %6.2f", mltSetpoint);
+				if (set_MLT_pump(unit, 0))
+				    syslog(LOG_NOTICE, "AUTO: cleanup turn %s MLT pump", mlt_pump_state ? "on":"off");
 				brew->brewstep = STEP_BREWDONE;
 				break;
 
-	case STEP_BREWINIT:
-				break;
-	case STEP_WATERCHECK:	prompt(111, NULL);		/* "AUTO --> Mash In    " */
-				prompt(209, NULL);		/* "    Water Added?    " */
-				break;
-	case STEP_PUMPPRIME:	prompt(111, NULL);		/* "AUTO --> Mash In    " */
-				prompt(210, NULL);		/* "     Pump Prime     " */
-				break;
-	case STEP_WAITSTART:	prompt(111, NULL);		/* "AUTO --> Mash In    " */
-				prompt(212, NULL);		/* "  To be started in  " */
-				break;
-	case STEP_PREMASH:	prompt(111, NULL);		/* "AUTO --> Mash In    " */
-				/* Heatup until strike temp reached */
-				break;
-	case STEP_MASHING:	switch (brew->mashstep) {
-				    case 0:	prompt(111, NULL);
-					    	break;
-				    case 1:	prompt(112, NULL);
-						break;
-				    case 2:	prompt(113, NULL);
-						break;
-				    case 3:	prompt(114, NULL);
-						break;
-				    case 4:	prompt(115, NULL);
-						break;
-				    case 5:	prompt(116, NULL);
-						break;
-				    case 6:	prompt(117, NULL);
-						break;
-				    case 7:	prompt(118, NULL);
-						break;
+	case STEP_BREWDONE:	if (brew->brewstep != last_step) {
+				    prompt(101, NULL);              /* "    Brewco x.x.x    " */
+				    prompt(200, NULL);              /* "                    " */
+				    prompt(301, NULL);              /* "      Finished      " */
+				    prompt(408, NULL);              /* "---  ---   Ok   --- " */
+				    last_step = brew->brewstep;
 				}
-				break;
-	case STEP_IODINE:	prompt(118, NULL);		/* "AUTO --> Mash Out   " */
-				prompt(213, NULL);		/* "    Iodine test     " */
-				break;
-	case STEP_MASHREMOVE:	prompt(118, NULL);		/* "AUTO --> Mash Out   " */
-				break;
-	case STEP_PREBOIL:
-				break;
-	case STEP_BOILING:	prompt(119, NULL);		/* "AUTO --> Boil       " */
-				break;
-	case STEP_BOILDONE:
-				break;
-	case STEP_HOPSTAND1:
-				break;
-	case STEP_COOLING1:	prompt(120, NULL);		/* "AUTO --> Cooling    " */
-				prompt(214, NULL);		/* "   START COOLING    " */
-				break;
-	case STEP_WHIRLPOOL1:	prompt(121, NULL);		/* "AUTO --> Whirlpool  " */
-				break;
-	case STEP_COOLING2:	prompt(120, NULL);		/* "AUTO --> Cooling    " */
-				break;
-	case STEP_HOPSTAND2:
-				break;
-	case STEP_COOLING3:	prompt(120, NULL);		/* "AUTO --> Cooling    " */
-				break;
-	case STEP_HOPSTAND3:
-				break;
-	case STEP_COOLING:	prompt(120, NULL);		/* "AUTO --> Cooling    " */
-				break;
-	case STEP_WHIRLPOOL:	prompt(121, NULL);		/* "AUTO --> Whirlpool  " */
-				break;
-	case STEP_CLEANUP:
-				break;
-	case STEP_BREWDONE:	syslog(LOG_NOTICE, "AUTO: brew done");
+				syslog(LOG_NOTICE, "AUTO: brew done");
 				brew->brewstep = -1;
 				brew->endtime = time(NULL);
-				prompt(101, NULL);      	/* "    Brewco x.x.x    " */
-				prompt(200, NULL);		/* "                    " */
-				prompt(301, NULL);		/* "      Finished      " */
-				prompt(408, NULL);		/* "---  ---   Ok   --- " */
+				save = TRUE;
 				do {
 				    key = keywait();
 				} while (key != KEY_RETURN);
 				/*
 				 * Rewrite the display
 				 */
-				prompt(101, NULL);      /* "    Brewco x.x.x    " */
-				prompt(300, NULL);      /* "                    " */
-				prompt(401, NULL);      /* "---  MAN  AUTO SETUP" */
+				prompt(101, NULL);      	/* "    Brewco x.x.x    " */
+				tempstatus();
+				prompt(300, NULL);      	/* "                    " */
+				prompt(401, NULL);      	/* "---  MAN  AUTO SETUP" */
 				break;
     }
 
-    if (save)
+    if (save) {
+	snprintf(data, 127, "%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f", brew->brewstep,
+			hltInput, hltOutput, hltSetpoint, mltInput, mltOutput, mltSetpoint);
+	logger(brew->name, data);
 	wrsession(brew);
+    }
 }
 
 
@@ -344,12 +846,12 @@
 				}
                                 if (key == KEY_RETURN) {
                                     manual = MANUAL_NONE;
-				    man_mlt_pump = 0;
+				    mlt_pump_state = 0;
 				    PID_setMode(unit->PID_mlt, P_MANUAL);
 				    PID_setMode(unit->PID_hlt, P_MANUAL);
 				    hlt_status(0);
 				    mlt_status(0);
-				    device_out(unit->mlt_pump.uuid, man_mlt_pump);
+				    device_out(unit->mlt_pump.uuid, mlt_pump_state);
 				}
                                 if (key == KEY_ENTER) {
                                                 // TODO: prompt for water
@@ -365,12 +867,12 @@
 				}
                                 if (key == KEY_RETURN) {
                                     manual = MANUAL_NONE;
-				    man_mlt_pump = 0;
+				    mlt_pump_state = 0;
 				    PID_setMode(unit->PID_mlt, P_MANUAL);
 				    PID_setMode(unit->PID_hlt, P_MANUAL);
 				    hlt_status(0);
 				    mlt_status(0);
-				    device_out(unit->mlt_pump.uuid, man_mlt_pump);
+				    device_out(unit->mlt_pump.uuid, mlt_pump_state);
 				}
                                 if (key == KEY_ENTER) {
                                                 // TODO: prompt for water
@@ -378,7 +880,7 @@
 				    manual_prompt();
                                 }
                                 break;
-        case MANUAL_HLT:        tempstatus(*unit->PID_hlt->myInput, *unit->PID_mlt->myInput);
+        case MANUAL_HLT:        tempstatus();
 				percstatus((seconds / 2) % 4);
 
 				slcdDummy(slcdHandle);
@@ -401,7 +903,7 @@
 				    manual_prompt();
                                 }
                                 break;
-        case MANUAL_MLT:        tempstatus(*unit->PID_hlt->myInput, *unit->PID_mlt->myInput);
+        case MANUAL_MLT:        tempstatus();
 				percstatus((seconds / 2) % 4);
 
 				slcdDummy(slcdHandle);
@@ -420,16 +922,16 @@
 				if ((key == KEY_UP) && (mltSetpoint < 100))
 				    mltSetpoint += 1.0;
                                 if (key == KEY_ENTER) {
-                                    if (man_mlt_pump)
-                                        man_mlt_pump = 0;
-                                    else
-                                        man_mlt_pump = 1;
+                                    if (mlt_pump_state)
+					set_MLT_pump(unit, 0);
+				    else
+					set_MLT_pump(unit, 1);
                                 }
                                 if (key == KEY_ESCAPE) {
                                     manual = MANUAL_SELMLT;
 				    manual_prompt();
                                 }
-                                device_out(unit->mlt_pump.uuid, man_mlt_pump);
+                                device_out(unit->mlt_pump.uuid, mlt_pump_state);
                                 break;
     }
 
@@ -627,14 +1129,30 @@
      * If this file is present, there has been a crash.
      */
     brew = (brew_session *)malloc(sizeof(brew_session));
-    if (rdsession(brew) == 0) {
-    } else {
-	/*
-	 * No active brew session, make that permanent.
-	 */
+    rc = rdsession(brew);
+    syslog(LOG_NOTICE, "rdsession: rc=%d", rc);
+    if (debug)
+	fprintf(stdout, "rdsession: rc=%d\n", rc);
+
+    char *mypath = xstrcpy(etcpath);
+    mypath = xstrcat(mypath, (char *)"brewing.xml");
+    if (rc == 0) {
+	if (debug)
+	    fprintf(stdout, "Active brew session found\n");
+    } else if (rc == -1) {
 	free(brew);
 	brew = NULL;
+	if (debug)
+	    fprintf(stdout, "No active brew session found\n");
+    } else {
+	unlink(mypath);
+	free(brew);
+	brew = NULL;
+	if (debug)
+	    fprintf(stdout, "Error brew session found\n");
     }
+    free(mypath);
+    mypath = NULL;
 
     do {
 	if (my_shutdown) {
@@ -677,10 +1195,10 @@
 	     */
 	    hltInput = hltSetpoint = mltInput = mltSetpoint = 20.0;
 	    hltOutput = mltOutput = 0;
-	    PID_init(unit->PID_hlt, &hltInput, &hltOutput, &hltSetpoint, unit->PID_hlt->dispKd, unit->PID_hlt->dispKi, unit->PID_hlt->dispKd, unit->PID_hlt->Direction);
+	    PID_init(unit->PID_hlt, &hltInput, &hltOutput, &hltSetpoint, unit->PID_hlt->dispKp, unit->PID_hlt->dispKi, unit->PID_hlt->dispKd, unit->PID_hlt->Direction);
 	    PID_setOutputLimits(unit->PID_hlt, 0, 100);
 	    PID_setSampleTime(unit->PID_hlt, unit->PID_hlt->SampleTime);
-	    PID_init(unit->PID_mlt, &mltInput, &mltOutput, &mltSetpoint, unit->PID_mlt->dispKd, unit->PID_mlt->dispKi, unit->PID_mlt->dispKd, unit->PID_mlt->Direction);
+	    PID_init(unit->PID_mlt, &mltInput, &mltOutput, &mltSetpoint, unit->PID_mlt->dispKp, unit->PID_mlt->dispKi, unit->PID_mlt->dispKd, unit->PID_mlt->Direction);
 	    PID_setOutputLimits(unit->PID_mlt, 0, 100);
 	    PID_setSampleTime(unit->PID_mlt, unit->PID_mlt->SampleTime);
 	    hlt_status(0);
@@ -693,6 +1211,21 @@
 	    percslot = 0;
 	    percfase = PERC_INIT;
 	    dosave = 0;
+
+	    if (brew) {
+		/*
+		 * Restore session
+		 */
+		if (debug)
+		    fprintf(stdout, "loop_init: restoring brew session\n");
+	    	for (recipe = recipes; recipe; recipe = recipe->next) {
+		    if (strcmp(recipe->uuid, brew->uuid_recipe) == 0) {
+			break;
+		    }
+		}
+		if (debug)
+		    fprintf(stdout, "loop_init: brewstep=%d mashstep=%d recipe=%s\n", brew->brewstep, brew->mashstep, recipe->code);
+	    }
 	}
 
 	/* run_pause code here */
@@ -734,8 +1267,8 @@
 		    device_out(unit->hlt_heater.uuid, 0);
 		    device_out(unit->mlt_heater.uuid, 0);
 		}
-		if (debug)
-		    fprintf(stdout, "  perslot=%d MLT=%d%% HLT=%d%% fase=%d\n", percslot, MLTp, HLTp, percfase);
+//		if (debug)
+//		    fprintf(stdout, "  perslot=%d MLT=%d%% HLT=%d%% fase=%d\n", percslot, MLTp, HLTp, percfase);
 	    } else if (percfase == PERC_MLT) {
 		if (percslot > MLTp) {
 		    device_out(unit->mlt_heater.uuid, 0);
@@ -773,8 +1306,6 @@
 		dosave = 1;
 	    }
 
-//fprintf(stdout, "%d seconds %d minutes %ld millis\n", seconds, minutes, millis());
-
 	    rc = device_in(unit->hlt_sensor.uuid, &temp);
 	    if (rc == DEVPRESENT_YES) {
 		hltInput = temp / 1000.0;
@@ -806,7 +1337,7 @@
 	    /*
 	     * Automate mode
 	     */
-	    automatic_brew(unit, brew, recipe, dosave);
+	    automatic_brew(unit, brew, recipe, dosave, seconds);
 	    dosave = 0;
 	    if (brew->brewstep == -1) {
 		/*
@@ -857,7 +1388,7 @@
 	    /*
 	     * Not running.
 	     */
-	    tempstatus(hltInput, mltInput);
+	    tempstatus();
 	    key = keycheck();
 	    if (key == KEY_ENTER) {
 		setup();
--- a/brewco/brewco.h	Tue Dec 22 21:07:14 2015 +0100
+++ b/brewco/brewco.h	Sat Dec 26 21:45:44 2015 +0100
@@ -227,6 +227,7 @@
     char		*code;			/* Recipe code			*/
     char		*name;			/* Recipe name			*/
     int                 boiltime;               /* 1..240 minutes               */
+    float		coolto;			/* Cooling target		*/
     time_t              starttime;		/* Start time                   */
     time_t              endtime;                /* Ending time                  */
     mash_step		mash[8];		/* 8 mash steps			*/
@@ -288,28 +289,22 @@
 #define	STEP_WAITSTART	4			/* Wait for starttime		*/
 #define	STEP_PREMASH	5			/* Pre-mash			*/
 #define	STEP_MASHING	6			/* Mash steps			*/
-#define	STEP_IODINE	7			/* Iodine test			*/
-#define	STEP_MASHREMOVE	8			/* Remove mash			*/
-#define	STEP_PREBOIL	9			/* Heating before boil		*/
-#define	STEP_BOILING	10			/* Boil				*/
-#define	STEP_BOILDONE	11			/* Boil done, hopstand		*/
-#define STEP_HOPSTAND1	12			/* Hopstand 88..100		*/
-#define STEP_COOLING1   13                      /* Cooling to whirlpool hot	*/
-#define STEP_WHIRLPOOL1 14                      /* Whirlpool hot                */
-#define	STEP_COOLING2	15			/* Cooling to hopstand 2	*/
-#define STEP_HOPSTAND2	16			/* Hopstand 71..77		*/
-#define STEP_COOLING3	17			/* Cooling to hopstand 3	*/
-#define STEP_HOPSTAND3	18			/* Hopstand 60..66		*/
-#define STEP_COOLING	19			/* Final cooling		*/
-#define STEP_WHIRLPOOL	20			/* Final whirlpool		*/
-#define	STEP_CLEANUP	21			/* Cleanup			*/
-#define	STEP_BREWDONE	22			/* Final step			*/
+#define	STEP_MASHREMOVE	7			/* Remove mash			*/
+#define	STEP_PREBOIL	8			/* Heating before boil		*/
+#define	STEP_BOILING	9			/* Boil				*/
+#define	STEP_BOILDONE	10			/* Boil done, hopstand		*/
+#define	STEP_COOLING	11			/* Cooling			*/
+#define STEP_HOPSTAND	12			/* Hopstand			*/
+#define STEP_WHIRLPOOL	13			/* Final whirlpool		*/
+#define	STEP_CLEANUP	14			/* Cleanup			*/
+#define	STEP_BREWDONE	15			/* Final step			*/
 
 #define	MASH_NA		0			/* Not in any state yet		*/
-#define	MASH_PROMPT	1			/* Prompt the user		*/
-#define	MASH_HEATING	2			/* Heating phase		*/
-#define	MASH_REST	3			/* Rest phase			*/
-#define	MASH_DONE	4			/* This step is done		*/
+#define	MASH_PROMPT	1			/* Prompt for mash		*/
+#define	MASH_IODINE	2			/* Prompt for iodine test	*/
+#define	MASH_HEATING	3			/* Heating phase		*/
+#define	MASH_REST	4			/* Rest phase			*/
+#define	MASH_DONE	5			/* This step is done		*/
 
 
 
--- a/brewco/devices.c	Tue Dec 22 21:07:14 2015 +0100
+++ b/brewco/devices.c	Sat Dec 26 21:45:44 2015 +0100
@@ -251,11 +251,6 @@
 		    if ((strcmp((char *)"SimHLTheater", device->address) == 0) || 
 			(strcmp((char *)"SimMLTheater", device->address) == 0) || 
 		        (strcmp((char *)"SimMLTpump", device->address) == 0)) {
-			if (value != device->value) {
-			    syslog(LOG_NOTICE, "SIM %s value=%d", device->address, value);
-			    if (debug)
-			    	fprintf(stdout, "SIM %s value=%d\n", device->address, value);
-			}
 			device->value = value;
 			if (strcmp((char *)"SimHLTheater", device->address) == 0) {
 			    SIM_hlt_value = value;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/brewco/logger.c	Sat Dec 26 21:45:44 2015 +0100
@@ -0,0 +1,72 @@
+/*****************************************************************************
+ * Copyright (C) 2014-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 "logger.h"
+#include "brewco.h"
+#include "util.h"
+#include "xutil.h"
+
+extern char	*varpath;
+
+
+void initlog(char *name)
+{
+    char	buf[128];
+
+    snprintf(buf, 127, "Fase,hltInput,hltOutput,hltSetpoint,mltInput,mltOutput,mltSetpoint");
+    logger(name, buf);
+}
+
+
+
+void logger(char *name, char *data)
+{
+    struct timeval	now;
+    struct tm		ptm;
+    char		*outstr = NULL, *logfile = NULL;
+    FILE		*fp;
+
+    logfile = xstrcpy(varpath);
+    logfile = xstrcat(logfile, (char *)"log/brew-");
+    logfile = xstrcat(logfile, name);
+    mkdirs(logfile, 0755);
+    logfile = xstrcat(logfile, (char *)".log");
+
+    gettimeofday(&now, NULL);
+    localtime_r(&now.tv_sec, &ptm);
+    outstr = calloc(10240, sizeof(char));
+    snprintf(outstr, 10239, "%04d-%02d-%02d %02d:%02d,%s\n", ptm.tm_year + 1900, ptm.tm_mon + 1, ptm.tm_mday, ptm.tm_hour, ptm.tm_min, data);
+
+    if ((fp = fopen(logfile, "a+"))) {
+	fprintf(fp, outstr);
+	fclose(fp);
+    } else {
+	syslog(LOG_NOTICE, "logger: cannot open %s for writing", logfile);
+    }
+
+    free(outstr);
+    outstr = NULL;
+    free(logfile);
+    logfile = NULL;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/brewco/logger.h	Sat Dec 26 21:45:44 2015 +0100
@@ -0,0 +1,9 @@
+#ifndef	LOGGER_H
+#define	LOGGER_H
+
+
+void logger(char *, char *);
+void initlog(char *);
+
+
+#endif
--- a/brewco/pid.c	Tue Dec 22 21:07:14 2015 +0100
+++ b/brewco/pid.c	Sat Dec 26 21:45:44 2015 +0100
@@ -66,6 +66,11 @@
 	    pid->ITerm = pid->outMax;
 	else if (pid->ITerm < pid->outMin)
 	    pid->ITerm = pid->outMin;
+	/*
+	 * If turned to manual, turn output off.
+	 */
+	if (Mode == P_MANUAL)
+	    *pid->myOutput = pid->outMin;
     }
     pid->inAuto = newAuto;
 }
--- a/brewco/prompt.c	Tue Dec 22 21:07:14 2015 +0100
+++ b/brewco/prompt.c	Sat Dec 26 21:45:44 2015 +0100
@@ -156,7 +156,10 @@
 			break;
 	case 218:	snprintf(message, Config.lcd_cols + 1, "  Add Brewsystem?   ");
 			break;
-
+	case 219:	snprintf(message, Config.lcd_cols + 1, "    Mash added?     ");
+			break;
+	case 220:	snprintf(message, Config.lcd_cols + 1, "   Mash Removed?    ");
+			break;
 	case 221:       snprintf(message, Config.lcd_cols + 1, "   Select Recipe    ");
 			break;
 	case 222:       snprintf(message, Config.lcd_cols + 1, " Select Brewsystem  ");
@@ -215,6 +218,11 @@
 			break;
 	case 417:	snprintf(message, Config.lcd_cols + 1, " up  dwn  next  ok  ");
 			break;
+	case 418:	snprintf(message, Config.lcd_cols + 1, "---  ---  Pause --- ");
+			break;
+	case 419:	snprintf(message, Config.lcd_cols + 1, "--*  *--  ---   pmp ");
+			break;
+
 			//                                      12345678901234567890
 	default:	snprintf(message, Config.lcd_cols + 1, "   N/A        N/A");
     }
--- a/brewco/rdconfig.c	Tue Dec 22 21:07:14 2015 +0100
+++ b/brewco/rdconfig.c	Sat Dec 26 21:45:44 2015 +0100
@@ -395,46 +395,46 @@
 	    }
 
             if (unit->PID_hlt) {
-                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_HLT_KP", "%.2lf", unit->PID_hlt->dispKp)) < 0) {
+                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_HLT_KP", "%lf", PID_getKp(unit->PID_hlt))) < 0) {
                     syslog(LOG_NOTICE, "wrconfig: error at xmlTextWriterWriteFormatElement");
                     return 1;
                 }
-                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_HLT_KI", "%.2lf", unit->PID_hlt->dispKi)) < 0) {
+                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_HLT_KI", "%lf", PID_getKi(unit->PID_hlt))) < 0) {
                     syslog(LOG_NOTICE, "wrconfig: error at xmlTextWriterWriteFormatElement");
                     return 1;
                 }
-                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_HLT_KD", "%.2lf", unit->PID_hlt->dispKd)) < 0) {
+                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_HLT_KD", "%lf", PID_getKd(unit->PID_hlt))) < 0) {
                     syslog(LOG_NOTICE, "wrconfig: error at xmlTextWriterWriteFormatElement");
                     return 1;
                 }
-		if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_HLT_SAMPLETIIME", "%ld", unit->PID_hlt->SampleTime)) < 0) {
+		if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_HLT_SAMPLETIME", "%d", PID_getSampleTime(unit->PID_hlt))) < 0) {
 		    syslog(LOG_NOTICE, "wrconfig: error at xmlTextWriterWriteFormatElement");
 		    return 1;
 		}
-                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_HLT_DIRECTION", "%s", PIDDIRECTION[unit->PID_hlt->Direction])) < 0) {
+                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_HLT_DIRECTION", "%s", PIDDIRECTION[PID_getDirection(unit->PID_hlt)])) < 0) {
                     syslog(LOG_NOTICE, "wrconfig: error at xmlTextWriterWriteFormatElement");
                     return 1;
                 }
             }
 
             if (unit->PID_mlt) {
-                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_MLT_KP", "%.2lf", unit->PID_mlt->dispKp)) < 0) {
+                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_MLT_KP", "%lf", PID_getKp(unit->PID_mlt))) < 0) {
                     syslog(LOG_NOTICE, "wrconfig: error at xmlTextWriterWriteFormatElement");
                     return 1;
                 }
-                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_MLT_KI", "%.2lf", unit->PID_mlt->dispKi)) < 0) {
+                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_MLT_KI", "%lf", PID_getKi(unit->PID_mlt))) < 0) {
                     syslog(LOG_NOTICE, "wrconfig: error at xmlTextWriterWriteFormatElement");
                     return 1;
                 }
-                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_MLT_KD", "%.2lf", unit->PID_mlt->dispKd)) < 0) {
+                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_MLT_KD", "%lf", PID_getKd(unit->PID_mlt))) < 0) {
                     syslog(LOG_NOTICE, "wrconfig: error at xmlTextWriterWriteFormatElement");
                     return 1;
                 }
-		if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_MLT_SAMPLETIIME", "%ld", unit->PID_mlt->SampleTime)) < 0) {
+		if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_MLT_SAMPLETIME", "%d", PID_getSampleTime(unit->PID_mlt))) < 0) {
 		    syslog(LOG_NOTICE, "wrconfig: error at xmlTextWriterWriteFormatElement");
 		    return 1;
 		}
-                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_MLT_DIRECTION", "%s", PIDDIRECTION[unit->PID_mlt->Direction])) < 0) {
+                if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "PID_MLT_DIRECTION", "%s", PIDDIRECTION[PID_getDirection(unit->PID_mlt)])) < 0) {
                     syslog(LOG_NOTICE, "wrconfig: error at xmlTextWriterWriteFormatElement");
                     return 1;
                 }
@@ -609,9 +609,6 @@
     mypath = xstrcpy(etcpath);
     mypath = xstrcat(mypath, (char *)"brewco.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);
--- a/brewco/rdrecipes.c	Tue Dec 22 21:07:14 2015 +0100
+++ b/brewco/rdrecipes.c	Sat Dec 26 21:45:44 2015 +0100
@@ -118,6 +118,10 @@
 	    syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement");
 	    return 1;
 	}
+	if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "COOLTO", "%f", recipe->coolto)) < 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;
@@ -333,6 +337,7 @@
     recipe = (a_recipe *)malloc(sizeof(a_recipe));
     memset(recipe, 0, sizeof(a_recipe));
     recipe->next = NULL;
+    recipe->coolto = 20.0;
 
     cur = cur->xmlChildrenNode;
     while (cur != NULL) {
@@ -351,6 +356,12 @@
 		recipe->boiltime = ival;
 	    xmlFree(key);
 	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COOLTO"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &fval) == 1)
+		recipe->coolto = fval;
+	    xmlFree(key);
+	}
 	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STARTTIME"))) {
 	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
 	    if (sscanf((const char *)key, "%d", &ival) == 1)
--- a/brewco/rdsession.c	Tue Dec 22 21:07:14 2015 +0100
+++ b/brewco/rdsession.c	Sat Dec 26 21:45:44 2015 +0100
@@ -29,12 +29,11 @@
 extern char	*etcpath;
 
 
-const char      BREWSTEP[23][11] = { 	"NA", "INIT", "WATEROK", "PRIME", "WAITSTART",
-					"PREMASH", "MASHING", "IODINE", "REMOVE", "PREBOIL",
-					"BOIL", "BOILDONE", "HOPSTAND1", "COOLING1", "WHIRLPOOL1",
-					"COOLING2", "HOPSTAND2", "COOLING3", "HOPSTAND3", "COOLING",
-					"WHIRLPOOL", "CLEANUP", "DONE" };
-const char	MASHSTEP[5][8] = { "NA", "PROMPT", "HEATING", "REST", "DONE" };
+const char      BREWSTEP[16][10] = { 	"NA", "INIT", "WATEROK", "PRIME", "WAITSTART",
+					"PREMASH", "MASHING", "REMOVE", "PREBOIL", "BOIL",
+					"BOILDONE", "COOLING", "HOPSTAND", "WHIRLPOOL", "CLEANUP",
+					"DONE" };
+const char	MASHSTEP[8][11] = {	"MASH-IN", "PHYTASE", "GLUCANASE", "PROTEASE", "B-AMYLASE", "A-AMYLASE1", "A-AMYLASE2", "MASH-OUT" };
 
 #define MY_ENCODING "utf-8"
 
@@ -86,7 +85,11 @@
      * 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_BREW")) < 0) {
+    if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "BREWCO")) < 0) {
+	syslog(LOG_NOTICE, "wrsession: error at xmlTextWriterStartElement");
+	return 1;
+    }
+    if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "BREW")) < 0) { 
 	syslog(LOG_NOTICE, "wrsession: error at xmlTextWriterStartElement");
 	return 1;
     }
@@ -135,6 +138,14 @@
         return 1;
     }
 
+    /* 
+     * Close the element named BREW.
+     */
+    if ((rc = xmlTextWriterEndElement(writer)) < 0) {
+	syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement");
+	return 1;
+    }
+
     /*
      * All done, close any open elements
      */
@@ -191,7 +202,7 @@
     int		i, ival;
     char	*mypath;
     xmlDocPtr	doc;
-    xmlNodePtr	cur;
+    xmlNodePtr	cur, s1cur;
     xmlChar	*key;
 
     /*
@@ -232,58 +243,66 @@
      */
     cur = cur->xmlChildrenNode;
     while (cur != NULL) {
-	if ((!xmlStrcmp(cur->name, (const xmlChar *)"UUID_RECIPE"))) {
-	    brew->uuid_recipe = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
-	}
-	if ((!xmlStrcmp(cur->name, (const xmlChar *)"UUID_UNIT"))) {
-	    brew->uuid_unit = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
-	}
-	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
-	    brew->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
-	}
-	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BREWSTEP"))) {
-	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
-	    for (i = 0; i < 23; i++) {
-		if (! xmlStrcmp(key, (const xmlChar *)BREWSTEP[i])) {
-		    brew->brewstep = i;
-		    break;
+
+	if ((! xmlStrcmp(cur->name, (const xmlChar*)"BREW"))) {
+	    s1cur = cur->xmlChildrenNode;
+	    while (s1cur != NULL) {
+
+		if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"UUID_RECIPE"))) {
+		    brew->uuid_recipe = (char *)xmlNodeListGetString(doc, s1cur->xmlChildrenNode, 1);
+		}
+		if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"UUID_UNIT"))) {
+		    brew->uuid_unit = (char *)xmlNodeListGetString(doc, s1cur->xmlChildrenNode, 1);
 		}
-	    }
-	    xmlFree(key);
-	}
-	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MASHSTEP"))) {
-	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
-	    for (i = 0; i < 5; i++) {
-		if (! xmlStrcmp(key, (const xmlChar *)MASHSTEP[i])) {
-		    brew->mashstep = i;
-		    break;
+		if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"NAME"))) {
+		    brew->name = (char *)xmlNodeListGetString(doc, s1cur->xmlChildrenNode, 1);
+		}
+		if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"BREWSTEP"))) {
+		    key = xmlNodeListGetString(doc, s1cur->xmlChildrenNode, 1);
+		    for (i = 0; i < 15; i++) {
+			if (! xmlStrcmp(key, (const xmlChar *)BREWSTEP[i])) {
+			    brew->brewstep = i;
+			    break;
+			}
+		    }
+		    xmlFree(key);
 		}
+		if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"MASHSTEP"))) {
+		    key = xmlNodeListGetString(doc, s1cur->xmlChildrenNode, 1);
+		    for (i = 0; i < 8; i++) {
+			if (! xmlStrcmp(key, (const xmlChar *)MASHSTEP[i])) {
+			    brew->mashstep = i;
+			    break;
+			}
+		    }
+		    xmlFree(key);
+		}
+		if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"TIMEOUT"))) {
+		    key = xmlNodeListGetString(doc, s1cur->xmlChildrenNode, 1);
+		    if (sscanf((const char *)key, "%d", &ival) == 1)
+			brew->timeout = ival;
+		    xmlFree(key);
+		}
+		if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"BOILTIMER"))) {
+		    key = xmlNodeListGetString(doc, s1cur->xmlChildrenNode, 1);
+		    if (sscanf((const char *)key, "%d", &ival) == 1)
+			brew->boiltimer = ival;
+		    xmlFree(key);
+		}
+		if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"STARTTIME"))) {
+		    key = xmlNodeListGetString(doc, s1cur->xmlChildrenNode, 1);
+		    if (sscanf((const char *)key, "%d", &ival) == 1)
+			brew->starttime = (time_t)ival;
+		    xmlFree(key);
+		}
+		if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"ENDTIME"))) {
+		    key = xmlNodeListGetString(doc, s1cur->xmlChildrenNode, 1);
+		    if (sscanf((const char *)key, "%d", &ival) == 1)
+			brew->endtime = (time_t)ival;
+		    xmlFree(key);
+		}
+		s1cur = s1cur->next;
 	    }
-	    xmlFree(key);
-	}
-	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TIMEOUT"))) {
-	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
-	    if (sscanf((const char *)key, "%d", &ival) == 1)
-		brew->timeout = ival;
-	    xmlFree(key);
-	}
-	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BOILTIMER"))) {
-	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
-	    if (sscanf((const char *)key, "%d", &ival) == 1)
-		brew->boiltimer = ival;
-	    xmlFree(key);
-	}
-	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STARTTIME"))) {
-	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
-	    if (sscanf((const char *)key, "%d", &ival) == 1)
-		brew->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)
-		brew->endtime = (time_t)ival;
-	    xmlFree(key);
 	}
 	cur = cur->next;
     }
--- a/brewco/setup.c	Tue Dec 22 21:07:14 2015 +0100
+++ b/brewco/setup.c	Sat Dec 26 21:45:44 2015 +0100
@@ -1055,22 +1055,24 @@
 	    case 3:     snprintf(pmpt, Config.lcd_cols + 1, "Boil time: %3d mins", recipe->boiltime);
 			prompt(200, pmpt);
 			break;
-	    case 4:
+	    case 4:	snprintf(pmpt, Config.lcd_cols + 1, "  Cool to: %5.1f\001", recipe->coolto);
+			prompt(200, pmpt);
+			break;
 	    case 5:
 	    case 6:
 	    case 7:
 	    case 8:
 	    case 9:
 	    case 10:
-	    case 11:	snprintf(pmpt, Config.lcd_cols + 1, "Mash: %s", recipe->mash[idx - 4].name);
+	    case 11:
+	    case 12:	snprintf(pmpt, Config.lcd_cols + 1, "Mash: %s", recipe->mash[idx - 5].name);
 			prompt(200, pmpt);
-			if (recipe->mash[idx - 4].skip)
+			if (recipe->mash[idx - 5].skip)
 			    snprintf(pmpt, Config.lcd_cols + 1, "      Skipped");
 			else
-			    snprintf(pmpt, Config.lcd_cols + 1, " Sv %4.1f\001 %3d mins", recipe->mash[idx - 4].setpoint, recipe->mash[idx - 4].duration);
+			    snprintf(pmpt, Config.lcd_cols + 1, " Sv %4.1f\001 %3d mins", recipe->mash[idx - 5].setpoint, recipe->mash[idx - 5].duration);
 			prompt(300, pmpt);
 			break;
-	    case 12:
 	    case 13:
 	    case 14:
 	    case 15:
@@ -1079,28 +1081,29 @@
 	    case 18:
 	    case 19:
 	    case 20:
-	    case 21:	snprintf(pmpt, Config.lcd_cols + 1, "Add: %s", recipe->hops[idx - 12].name);
+	    case 21:
+	    case 22:	snprintf(pmpt, Config.lcd_cols + 1, "Add: %s", recipe->hops[idx - 13].name);
 			prompt(200, pmpt);
-			if (recipe->hops[idx - 12].skip)
+			if (recipe->hops[idx - 13].skip)
 			    snprintf(pmpt, Config.lcd_cols + 1, "      Skipped");
 			else {
-			    if (recipe->hops[idx - 12].boiltime == -1)
+			    if (recipe->hops[idx - 13].boiltime == -1)
 				snprintf(pmpt, Config.lcd_cols + 1, "   First Wort Hop");
 			    else
-			        snprintf(pmpt, Config.lcd_cols + 1, " Boil for %3d mins", recipe->hops[idx - 12].boiltime);
+			        snprintf(pmpt, Config.lcd_cols + 1, " Boil for %3d mins", recipe->hops[idx - 13].boiltime);
 			}
 			prompt(300, pmpt);
 			break;
-	    case 22:    
-	    case 23:
-	    case 24:	snprintf(pmpt, Config.lcd_cols + 1, "Hopstand: %s", recipe->hopstand[idx - 22].name);
+	    case 23:    
+	    case 24:
+	    case 25:	snprintf(pmpt, Config.lcd_cols + 1, "Hopstand: %s", recipe->hopstand[idx - 23].name);
 			prompt(200, pmpt);
-			if (recipe->hopstand[idx - 22].skip)
+			if (recipe->hopstand[idx - 23].skip)
 			    snprintf(pmpt, Config.lcd_cols + 1, "      Skipped");
-			else if (recipe->hopstand[idx - 22].hold)
-			    snprintf(pmpt, Config.lcd_cols + 1, "Hold at %4.1f %3d mins", recipe->hopstand[idx - 22].setpoint, recipe->hopstand[idx - 22].duration);
+			else if (recipe->hopstand[idx - 23].hold)
+			    snprintf(pmpt, Config.lcd_cols + 1, "Hold at %4.1f %3d mins", recipe->hopstand[idx - 23].setpoint, recipe->hopstand[idx - 23].duration);
 			else
-			    snprintf(pmpt, Config.lcd_cols + 1, "Hold for %3d mins", recipe->hopstand[idx - 22].duration);
+			    snprintf(pmpt, Config.lcd_cols + 1, "Hold for %3d mins", recipe->hopstand[idx - 23].duration);
 			prompt(300, pmpt);
 			break;
 	}
@@ -1147,16 +1150,17 @@
 				break;
 		case 3:		editInteger(&recipe->boiltime, 60, 240, 5, (char *)"Boil time:", (char *)"mins");
 				break;
-		case 4:
+		case 4:		editFloat(&recipe->coolto, 10.0, 30.0, (char *)"  Cool to");
+
 		case 5:
 		case 6:
 		case 7:
 		case 8:
 		case 9:
 		case 10:
-		case 11:	editMash(&recipe->mash[idx-4]);
+		case 11:
+		case 12:	editMash(&recipe->mash[idx-5]);
 				break;
-		case 12:
 		case 13:
 		case 14:
 		case 15:
@@ -1165,11 +1169,12 @@
 		case 18:
 		case 19:
 		case 20:
-		case 21:	editHopaddition(&recipe->hops[idx-12], recipe->boiltime);
+		case 21:
+		case 22:	editHopaddition(&recipe->hops[idx-13], recipe->boiltime);
 				break;
-		case 22:
 		case 23:
-		case 24:	editHopstand(&recipe->hopstand[idx-22]);
+		case 24:
+		case 25:	editHopstand(&recipe->hopstand[idx-23]);
 				break;
 	    }
     	}
@@ -1195,6 +1200,7 @@
     snprintf(name, 21, "%04d", number);
     recipe->code = xstrcpy(name);
     recipe->boiltime = 90;
+    recipe->coolto = 20.0;
     recipe->starttime = recipe->endtime = (time_t)0;
     /*
      * Initial mash schedule, set a single-step 67 degr. mash
--- a/brewco/setup.h	Tue Dec 22 21:07:14 2015 +0100
+++ b/brewco/setup.h	Sat Dec 26 21:45:44 2015 +0100
@@ -2,6 +2,7 @@
 #define	_SETUP_H
 
 
+void editInteger(int *, int, int, int, char *, char *);
 void editUnit(units_list *);
 void addUnit(int);
 void setup(void);
--- a/brewco/simulator.c	Tue Dec 22 21:07:14 2015 +0100
+++ b/brewco/simulator.c	Sat Dec 26 21:45:44 2015 +0100
@@ -81,20 +81,20 @@
 	if (SIM_hlt_value) {
 	    Config.simulator->hlt_heater_temp += (250 - Config.simulator->hlt_heater_temp) / 2000;
 	} else {
-	    Config.simulator->hlt_heater_temp -= (Config.simulator->hlt_heater_temp - Config.simulator->hlt_temperature) / 500;
+	    Config.simulator->hlt_heater_temp -= (Config.simulator->hlt_heater_temp - Config.simulator->hlt_temperature) / 250;
 	}
 	Config.simulator->mlt_heater_state = SIM_mlt_value;
 	if (SIM_mlt_value) {
 	    Config.simulator->mlt_heater_temp += (250 - Config.simulator->mlt_heater_temp) / 2000;
 	} else {
-	    Config.simulator->mlt_heater_temp -= (Config.simulator->mlt_heater_temp - Config.simulator->mlt_temperature) / 500;
+	    Config.simulator->mlt_heater_temp -= (Config.simulator->mlt_heater_temp - Config.simulator->mlt_temperature) / 250;
 	}
 
 	/*
 	 * If cooling, bring down the MLT temperature. Assume 14 degrees coolwater.
 	 */
 	if (SIM_cooler) {
-	    Config.simulator->mlt_temperature -= (Config.simulator->mlt_temperature - 14) / (250 * Config.simulator->hlt_heater_volume);
+	    Config.simulator->mlt_temperature -= (Config.simulator->mlt_temperature - 14) / (175 * Config.simulator->hlt_heater_volume);
 	}
 
 	/*

mercurial