brewco/brewco.c

Sun, 27 Dec 2015 17:52:26 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Sun, 27 Dec 2015 17:52:26 +0100
changeset 477
9167ad4c2e77
parent 476
8f159cd4f5fc
child 478
fe8bf61cde06
permissions
-rw-r--r--

Renamed Mash-in step to Prepare on the display. Don't run the pump when the mash is added. When preparing the mash, first heat the HLT, and then the MLT so that both have the chance to reach their target temperatures.

/*****************************************************************************
 * 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 "rdconfig.h"
#include "rdsession.h"
#include "rdrecipes.h"
#include "util.h"
#include "xutil.h"
#include "lcd-pcf8574.h"
#include "slcd.h"
#include "lock.h"
#include "devices.h"
#include "keyboard.h"
#include "simulator.h"
#include "prompt.h"
#include "setup.h"
#include "logger.h"



int			my_shutdown = FALSE;
int			mlt_pump_state = 0;

double			hltInput;			/* HLT PID variables		*/
double			hltOutput;
double			hltSetpoint;
double			mltInput;			/* MLT PID variables		*/
double			mltOutput;
double			mltSetpoint;

extern int              debug;
extern sys_config       Config;
extern a_recipe		*recipes;
extern int              lcdHandle;
extern int		slcdHandle;
extern int		sock;

#ifdef USE_SIMULATOR
extern int		SIM_cooler;
#endif
char			*etcpath = NULL;
char			*varpath = NULL;



#ifndef HAVE_WIRINGPI_H
pthread_t               threads[5];
#endif


#define	MANUAL_NONE	0
#define	MANUAL_SELHLT	1
#define	MANUAL_SELMLT	2
#define	MANUAL_HLT	11
#define	MANUAL_MLT	12

#define	PERC_INIT	0
#define	PERC_MLT	1
#define	PERC_HLT	2
#define	PERC_REST	3


int manual		= MANUAL_NONE;



/*
 * CGRAM characters
 */
unsigned char	degC[8] 	= { 0b01000, 0b10100, 0b01000, 0b00111, 0b01000, 0b01000, 0b01000, 0b00111 };
unsigned char	degF[8] 	= { 0b01000, 0b10100, 0b01000, 0b00111, 0b00100, 0b00110, 0b00100, 0b00100 };
unsigned char	SP_Symbol[8]	= { 0b11100, 0b10000, 0b11100, 0b00111, 0b11101, 0b00111, 0b00100, 0b00100 };
unsigned char	PumpONOFF[8]	= { 0b00000, 0b01110, 0b01010, 0b01110, 0b01000, 0b01000, 0b01000, 0b00000 };
unsigned char	RevPumpONOFF[8]	= { 0b11111, 0b10001, 0b10101, 0b10001, 0b10111, 0b10111, 0b10111, 0b11111 };
unsigned char	HeatONOFF[8]	= { 0b00000, 0b01010, 0b01010, 0b01110, 0b01110, 0b01010, 0b01010, 0b00000 };
unsigned char	RevHeatONOFF[8]	= { 0b11111, 0b10101, 0b10101, 0b10001, 0b10001, 0b10101, 0b10101, 0b11111 };
unsigned char	Language[8]	= { 0b11111, 0b00010, 0b01000, 0b11111, 0b00000, 0b10001, 0b10101, 0b11111 };


void help(void);
void die(int);



void help(void)
{
    fprintf(stdout, "mbsePi-apps brewco v%s starting\n\n", VERSION);
    fprintf(stdout, "Usage: brewco [-d] [-h]\n");
    fprintf(stdout, "  -d --debug              Debug and run in foreground\n");
    fprintf(stdout, "  -h --help               Display this help\n");
}



void die(int onsig)
{
    switch (onsig) {
#ifdef USE_SIMULATOR
	case SIGUSR1:	syslog(LOG_NOTICE, "Got SIGUSR1, start cooler");
			SIM_cooler = TRUE;
			return;
	case SIGUSR2:	syslog(LOG_NOTICE, "Got SIGUSR2, stop cooler");
			SIM_cooler = FALSE;
			return;
#endif
	case SIGHUP:    syslog(LOG_NOTICE, "Got SIGHUP, shutting down");
			break;
	case SIGINT:    syslog(LOG_NOTICE, "Keyboard interrupt, shutting down");
			break;
	case SIGTERM:   syslog(LOG_NOTICE, "Got SIGTERM, shutting down");
			break;
	case SIGSEGV:   syslog(LOG_NOTICE, "Got SIGSEGV, shutting down");
			my_shutdown = TRUE;
			exit(SIGSEGV);
			break;
	default:        syslog(LOG_NOTICE, "die() on signal %d", onsig);
    }

    my_shutdown = TRUE;
}



void tempstatus(void)
{
    char	text[81];

    snprintf(text, 8, "%6.2f\001", hltInput);
#ifdef HAVE_WIRINGPI_H
    piLock(LOCK_LCD);
    lcdPosition(lcdHandle, 1, 1);
    lcdPuts(lcdHandle, text);
#endif
    slcdPosition(slcdHandle, 1, 1);
    slcdPuts(slcdHandle, text);

    snprintf(text, 8, "%6.2f\001", mltInput);
#ifdef HAVE_WIRINGPI_H
    piLock(LOCK_LCD);
    lcdPosition(lcdHandle, 10, 1);
    lcdPuts(lcdHandle, text);
#endif
    slcdPosition(slcdHandle, 10, 1);
    slcdPuts(slcdHandle, text);
}



/*
 * Third line, show setPoints or heater percentage.
 */
void percstatus(int which)
{
    char	text[21];

    if (which)
	snprintf(text, 8, "%6.2f\002", hltSetpoint);
    else
    	snprintf(text, 8, "HLT%3d%%", (int)hltOutput);
#ifdef HAVE_WIRINGPI_H
    piLock(LOCK_LCD);
    lcdPosition(lcdHandle, 1, 2);
    lcdPuts(lcdHandle, text);
#endif
    slcdPosition(slcdHandle, 1, 2);
    slcdPuts(slcdHandle, text);

    if (which)
	snprintf(text, 8, "%6.2f\002", mltSetpoint);
    else
    	snprintf(text, 8, "MLT%3d%%", (int)mltOutput);
#ifdef HAVE_WIRINGPI_H
    piLock(LOCK_LCD);
    lcdPosition(lcdHandle, 10, 2);
    lcdPuts(lcdHandle, text);
#endif
    slcdPosition(slcdHandle, 10, 2);
    slcdPuts(slcdHandle, text);
}



/*
 * 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)
{
    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 (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 = STEP_NA, last_fase = -1, oldsec = 75, startdelay = 0;

    if (brew->brewstep != last_step) {
	snprintf(data, 40, brewstep_name(last_step));
	syslog(LOG_NOTICE, "AUTO: brewstep %s to %s", data, brewstep_name(brew->brewstep));
    }

    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(407, NULL);          /* "---  ---   No   Yes " */
				    last_step = brew->brewstep;
				}
				slcdDummy(slcdHandle);
				key = keycheck();
				if (key == KEY_ENTER) {
				    brew->brewstep = STEP_PUMPPRIME;
				    syslog(LOG_NOTICE, "AUTO: confirmed water added");
				}
				if (key == KEY_RETURN) {
				    syslog(LOG_NOTICE, "AUTO: aborted water added");
				    brew->brewstep = STEP_CLEANUP;
				}
				break;

	case STEP_PUMPPRIME:	if (brew->brewstep != last_step) {
				    prompt(100, NULL);		/* "                    " */
				    prompt(210, NULL);		/* "     Pump Prime     " */
				    prompt(300, NULL);		/* "                    " */
				    prompt(400, NULL);		/* "                    " */
				    hlt_status(0);
				    mlt_status(0);
				    last_step = brew->brewstep;
				}
				for (i = 1; i < 6; i++) {
				    if (set_MLT_pump(unit, 1))
					syslog(LOG_NOTICE, "AUTO: pump prime %d turn %s MLT pump", i, mlt_pump_state ? "on":"off");
				    usleep(750000 + (i * 250000));	/* 250 + i * 250 mSec */
				    if (set_MLT_pump(unit, 0))
					syslog(LOG_NOTICE, "AUTO: pump prime %d turn %s MLT pump", i, mlt_pump_state ? "on":"off");
				    usleep(350000);			/* 350 mSec */
				}
				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, NULL);     	/* "AUTO --> Prepare    " */
				    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, 10.0))
				    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");
				if (hltInput >= 85.0) {
				    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);

				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;
							    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(407, NULL);              	/* "---  ---   No   Yes " */
							    last_fase = mash_fase;
							}
							if (set_MLT_pump(unit, 0))	/* Off during mash add */
							    syslog(LOG_NOTICE, "AUTO: mash turn %s MLT pump", mlt_pump_state ? "on":"off");
							slcdDummy(slcdHandle);
							key = keycheck();
							if (key == KEY_ENTER) {
							    mash_fase = MASH_NA;
							    brew->mashstep++;
							    syslog(LOG_NOTICE, "AUTO: confirmed mash added");
							}
							if (key == KEY_RETURN) {
							    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 (set_MLT_pump(unit, unit->pump_onmash))
							    syslog(LOG_NOTICE, "AUTO: mash turn %s MLT pump", mlt_pump_state ? "on":"off");
							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 (set_MLT_pump(unit, unit->pump_onmash))
							    syslog(LOG_NOTICE, "AUTO: mash turn %s MLT pump", mlt_pump_state ? "on":"off");
							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(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();
							    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 == 0) && ! unit->skip_add) {
								    mash_fase = MASH_PROMPT;
								} else 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_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;
				}
				syslog(LOG_NOTICE, "AUTO: brew done");
				brew->brewstep = -1;
				brew->endtime = time(NULL);
				save = TRUE;
				do {
				    key = keywait();
				} while (key != KEY_RETURN);
				/*
				 * Rewrite the display
				 */
				prompt(101, NULL);      	/* "    Brewco x.x.x    " */
				tempstatus();
				prompt(300, NULL);      	/* "                    " */
				prompt(401, NULL);      	/* "---  MAN  AUTO SETUP" */
				break;
    }

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



void manual_prompt(void)
{
    switch (manual) {
	case MANUAL_SELHLT:	prompt(104, NULL);      /* "    MANUAL MODE     " */
				prompt(303, NULL);      /* "     Manual HLT     " */
				prompt(402, NULL);      /* "---  dwn  quit  ok  " */
				break;
	case MANUAL_SELMLT:	prompt(104, NULL);      /* "    MANUAL MODE     " */
				prompt(304, NULL);      /* "     Manual MLT     " */
				prompt(404, NULL);      /* " up  ---  quit  ok  " */
				break;
	case MANUAL_HLT:	prompt(104, NULL);      /* "    MANUAL MODE     " */
				prompt(300, NULL);	/* "                    " */
				prompt(413, NULL);      /* "UP* *DWN  heat  --- " */
				break;
	case MANUAL_MLT:	prompt(104, NULL);      /* "    MANUAL MODE     " */
				prompt(300, NULL);	/* "                    " */
				prompt(406, NULL);      /* "UP* *DWN  heat  pmp " */
				break;
    }
}



/*
 * Manual menu for testing your equipment.
 */
int manual_menu(units_list *, int);
int manual_menu(units_list *unit, int seconds)
{
    int		key;

    switch (manual) {
        case MANUAL_SELHLT:     slcdDummy(slcdHandle);
				key = keycheck();
                                if (key == KEY_DOWN) {
                                    manual = MANUAL_SELMLT;
				    manual_prompt();
				}
                                if (key == KEY_RETURN) {
                                    manual = MANUAL_NONE;
				    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, mlt_pump_state);
				}
                                if (key == KEY_ENTER) {
                                                // TODO: prompt for water
                                    manual = MANUAL_HLT;
				    manual_prompt();
                                }
                                break;
        case MANUAL_SELMLT:     slcdDummy(slcdHandle);
				key = keycheck();
                                if (key == KEY_UP) {
                                    manual = MANUAL_SELHLT;
				    manual_prompt();
				}
                                if (key == KEY_RETURN) {
                                    manual = MANUAL_NONE;
				    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, mlt_pump_state);
				}
                                if (key == KEY_ENTER) {
                                                // TODO: prompt for water
                                    manual = MANUAL_MLT;
				    manual_prompt();
                                }
                                break;
        case MANUAL_HLT:        tempstatus();
				percstatus((seconds / 2) % 4);

				slcdDummy(slcdHandle);
                                key = keycheck();
                                if (key == KEY_RETURN) {
				    if (PID_getMode(unit->PID_hlt) == P_MANUAL) {
					PID_setMode(unit->PID_hlt, P_AUTOMATIC);
					hlt_status(1);
				    } else {
					PID_setMode(unit->PID_hlt, P_MANUAL);
					hlt_status(0);
				    }
                                }
				if ((key == KEY_DOWN) && (hltSetpoint > 10))
				    hltSetpoint -= 1.0;
				if ((key == KEY_UP) && (hltSetpoint < 100))
				    hltSetpoint += 1.0;
                                if (key == KEY_ESCAPE) {
                                    manual = MANUAL_SELHLT;
				    manual_prompt();
                                }
                                break;
        case MANUAL_MLT:        tempstatus();
				percstatus((seconds / 2) % 4);

				slcdDummy(slcdHandle);
				key = keycheck();
                                if (key == KEY_RETURN) {
				    if (PID_getMode(unit->PID_mlt) == P_MANUAL) {
					PID_setMode(unit->PID_mlt, P_AUTOMATIC);
					mlt_status(1);
				    } else {
					PID_setMode(unit->PID_mlt, P_MANUAL);
					mlt_status(0);
				    }
                                }
				if ((key == KEY_DOWN) && (mltSetpoint > 10))
				    mltSetpoint -= 1.0;
				if ((key == KEY_UP) && (mltSetpoint < 100))
				    mltSetpoint += 1.0;
                                if (key == KEY_ENTER) {
                                    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, mlt_pump_state);
                                break;
    }

    return 0;
}



char *choose_recipe(void);
char *choose_recipe(void)
{
    int			total, i, key, choice = 1;
    static char		uuid[37];
    a_recipe		*recipe;
    char		pmpt[81];

    strcpy(uuid, (char *)"00000000-0000-0000-0000-000000000000");
    for (;;) {
	total = 0;
	for (recipe = recipes; recipe; recipe = recipe->next)
	    total++;

	if (total == 0)
	    return uuid;

	i = 0;
	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 %s                   ", recipe->code, recipe->name);
	    prompt(300, pmpt);
	}
	if (total == 1)
	    prompt(405, NULL);      /* "---  ---  quit  ok  " */
	else if (choice == 1)
	    prompt(402, NULL);      /* "---  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 uuid;
	if (key == KEY_ENTER) {
	    strcpy(uuid, recipe->uuid);
	    return uuid;
	}
	if ((key == KEY_UP) && (total > 1) && (choice > 1)) {
	    choice--;
	}
	if ((key == KEY_DOWN) && (total > 1) && (choice < total))
	    choice++;
    }
}



int server(void);
int server(void)
{
    int 		rc = 0, run = 1, dosave, key, temp, MLTp, HLTp, percslot, percfase = PERC_INIT;
    int			do_init = TRUE, seconds = 0, minutes = 0;
    units_list		*unit;
    a_recipe		*recipe = NULL;
    brew_session	*brew = NULL;
#ifndef HAVE_WIRINGPI_H
    long		t = 0;
#endif
    time_t		now, last = (time_t)0;
    long		nowmillis, perctimer;
    struct tm		*tm;

    prompt(101, NULL);

    /*
     * Define special characters in the display CGRAM
     */
    if (Config.tempFormat == 'C')
    	slcdCharDef(slcdHandle, 1, degC);
    else
	slcdCharDef(slcdHandle, 1, degF);
    slcdCharDef(slcdHandle, 2, SP_Symbol);
    slcdCharDef(slcdHandle, 3, PumpONOFF);
    slcdCharDef(slcdHandle, 4, RevPumpONOFF);
    slcdCharDef(slcdHandle, 5, HeatONOFF);
    slcdCharDef(slcdHandle, 6, RevHeatONOFF);
    slcdCharDef(slcdHandle, 7, Language);

    if (lockprog((char *)"brewco")) {
	syslog(LOG_NOTICE, "Can't lock");
	return 1;
    }

    if (debug)
	fprintf(stdout, "Begin server()\n");

    if ((rc = devices_detect())) {
	syslog(LOG_NOTICE, "Detected %d new devices", rc);
	if (debug)
	    fprintf(stdout, "Detected %d new devices\n", rc);
	wrconfig();
    }

#ifdef HAVE_WIRINGPI_H
    rc = piThreadCreate(my_devices_loop);
#else
    rc = pthread_create(&threads[t], NULL, my_devices_loop, (void *)t );
#endif
    if (rc) {
	fprintf(stderr, "my_devices_loop thread didn't start rc=%d\n", rc);
	syslog(LOG_NOTICE, "my_devices_loop thread didn't start rc=%d", rc);
#ifndef HAVE_WIRINGPI_H
    } else {
	t++;
#endif
    }

#ifdef HAVE_WIRINGPI_H
    rc = piThreadCreate(my_keyboard_loop);
#else
    rc = pthread_create(&threads[t], NULL, my_keyboard_loop, (void *)t );
#endif
    if (rc) {
	fprintf(stderr, "my_keyboard_loop thread didn't start rc=%d\n", rc);
	syslog(LOG_NOTICE, "my_keyboard_loop thread didn't start rc=%d", rc);
#ifndef HAVE_WIRINGPI_H
    } else {
	t++;
#endif
    }

#ifdef USE_SIMULATOR
#ifdef HAVE_WIRINGPI_H
    rc = piThreadCreate(my_simulator_loop);
#else
    rc = pthread_create(&threads[t], NULL, my_simulator_loop, (void *)t );
#endif
    if (rc) {
	fprintf(stderr, "my_simulator_loop thread didn't start rc=%d\n", rc);
	syslog(LOG_NOTICE, "my_simulator_loop thread didn't start rc=%d", rc);
#ifndef HAVE_WIRINGPI_H
    } else {
	t++;
#endif
    }
#endif

    if (! Config.units) {
	/*
	 * No brewsystems defined, add the first
	 */
	prompt(218, NULL);	/*   Add Brewsystem?   */
	prompt(407, NULL);	/* ---  ---   Ok   --- */

	do {
	    key = keywait();
	} while (key != KEY_RETURN);

	if (key == KEY_RETURN) {
	    addUnit(1);
	}
    }

    /*
     * Initialize units for processing
     */
    for (unit = Config.units; unit; unit = unit->next) {
	if (unit->active)
	    break;
    }

    if (! unit->active) {
	fprintf(stdout, "No active units found\n");
    }

    /*
     * Safety, turn everything off
     */
    if (unit->active) {
	if (debug)
	    fprintf(stdout, "Starting brewsystem %d `%s'\n", unit->number, unit->name);
	syslog(LOG_NOTICE, "Starting brewsystem %d `%s'", unit->number, unit->name);
    }

    /*
     * During automation there will be a state file:
     * ~/.brewco/var/brewing.xml
     * If this file is present, there has been a crash.
     */
    brew = (brew_session *)malloc(sizeof(brew_session));
    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) {
	    run = 0;
	    unit->hlt_heater.value = 0;
	    unit->mlt_heater.value = 0;
	    unit->mlt_pump.value = 0;
	    device_out(unit->hlt_heater.uuid, 0);
	    device_out(unit->mlt_heater.uuid, 0);
	    device_out(unit->mlt_pump.uuid, 0);
	    hlt_status(0);
	    mlt_status(0);
	    break;
	}

	/*
	 * Do we need to initialize this unit?
	 */
	if (do_init) {
	    if (debug)
		fprintf(stdout, "Initialize brewsystem %d `%s'\n", unit->number, unit->name);
	    syslog(LOG_NOTICE, "Initialize brewsystem %d `%s'", unit->number, unit->name);

	    prompt(0, NULL);
	    prompt(101, NULL);		/* "    Brewco x.x.x    " */
	    prompt(401, NULL);		/* "---  MAN  AUTO SETUP" */

	    /*
	     * Turn everything off
	     */
	    unit->hlt_heater.value = 0;
	    unit->mlt_heater.value = 0;
	    unit->mlt_pump.value = 0;
	    device_out(unit->hlt_heater.uuid, 0);
	    device_out(unit->mlt_heater.uuid, 0);
	    device_out(unit->mlt_pump.uuid, 0);

	    /*
	     * Initialize PID's
	     */
	    hltInput = hltSetpoint = mltInput = mltSetpoint = 20.0;
	    hltOutput = mltOutput = 0;
	    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->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);
	    mlt_status(0);

	    manual = MANUAL_NONE;

	    do_init = FALSE;
	    nowmillis = perctimer = millis();
	    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 */

	/*
	 * Update PID's, even if they are off. Both PID's will do
	 * the scheduling by themselves.
	 */
	rc = PID_compute(unit->PID_hlt);
	rc = PID_compute(unit->PID_mlt);

	/*
	 * This is the serial heaters schedule loop. The total
	 * loop time is 5 seconds, heating is 0..100% so we
	 * have 5 mSeconds timeslots. The MLT has priority over
	 * the HLT heater, so the HLT heater can get too little
	 * power. TODO: simultaneous use must be implemented.
	 * In sequentiel mode, the total drawn power is the same
	 * as the power used by the largest heater element.
	 */
	nowmillis = millis();
	if (nowmillis > (perctimer + 50)) {
	    percslot++;
	    if (percfase == PERC_INIT) {
		HLTp = (int)hltOutput;
		MLTp = (int)mltOutput;
		if ((MLTp + HLTp) > 100)
		    HLTp = 100 - MLTp;		/* The HLT has lower priority	*/
		if (MLTp) {
		    percfase = PERC_MLT;
		    device_out(unit->hlt_heater.uuid, 0);
		    device_out(unit->mlt_heater.uuid, 1);
		} else if (HLTp) {
		    percfase = PERC_HLT;
		    device_out(unit->hlt_heater.uuid, 1);
		    device_out(unit->mlt_heater.uuid, 0);
		} else { 
		    percfase = PERC_REST;
		    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);
	    } else if (percfase == PERC_MLT) {
		if (percslot > MLTp) {
		    device_out(unit->mlt_heater.uuid, 0);
		    if (HLTp) {
			device_out(unit->hlt_heater.uuid, 1);
			percfase = PERC_HLT;
		    } else {
			percfase = PERC_REST;
		    }
		}
	    } else if (percfase == PERC_HLT) {
		if (percslot > (MLTp + HLTp)) {
		    device_out(unit->hlt_heater.uuid, 0);
		    percfase = PERC_REST;
		}
	    }
	    if (percslot == 100) {	/* End of the loop, start over */
		percslot = 0;
		percfase = PERC_INIT;
	    }
	    perctimer = nowmillis;
	}

	now = time(NULL);
	if (now != last) {
	    /*
	     * Each second
	     */
	    last = now;
	    seconds++;

	    if (seconds > 59) {
		seconds = 0;
		minutes++;
		dosave = 1;
	    }

	    rc = device_in(unit->hlt_sensor.uuid, &temp);
	    if (rc == DEVPRESENT_YES) {
		hltInput = temp / 1000.0;
		unit->hlt_sensor.state = 0;
	    } else if (rc == DEVPRESENT_ERROR) {
		unit->hlt_sensor.state = 1;
	    } else {
		unit->hlt_sensor.state = 2;
	    }
	    rc = device_in(unit->mlt_sensor.uuid, &temp);
	    if (rc == DEVPRESENT_YES) {
		mltInput = temp / 1000.0;
		unit->mlt_sensor.state = 0;
	    } else if (rc == DEVPRESENT_ERROR) {
		unit->mlt_sensor.state = 1;
	    } else {
		unit->mlt_sensor.state = 2;
	    }

	    if (debug && ((seconds % 10) == 1)) {
		fprintf(stdout, "MLT: In=%.2lf Out=%.2lf Set=%.2lf Stat=%d  HLT: In=%.2lf Out=%.2lf Set=%.2lf Stat=%d\n",
				mltInput, mltOutput, mltSetpoint, PID_getMode(unit->PID_mlt), 
				hltInput, hltOutput, hltSetpoint, PID_getMode(unit->PID_hlt));
	    }

	}

	if (brew) {
	    /*
	     * Automate mode
	     */
	    automatic_brew(unit, brew, recipe, dosave, seconds);
	    dosave = 0;
	    if (brew->brewstep == -1) {
		/*
		 * Save session and move it
		 */
		wrsession(brew);
		char	*fpath, *tpath;
		fpath = xstrcpy(etcpath);
		fpath = xstrcat(fpath, (char *)"brewing.xml");
		tpath = xstrcpy(varpath);
		tpath = xstrcat(tpath, (char *)"old/brewing.xml.");
		mkdirs(tpath, 0755);
		tpath = xstrcat(tpath, brew->name);
		if (debug)
		    fprintf(stdout, "auto: saving as %s\n", tpath);
		file_cp(fpath, tpath);
		unlink(fpath);
		free(fpath);
		free(tpath);

		/*
		 * Free memory, session is over.
		 */
		if (brew->name)
		    free(brew->name);
		if (brew->uuid_recipe)
		    free(brew->uuid_recipe);
		if (brew->uuid_unit)
		    free(brew->uuid_unit);
		free(brew);
		brew = NULL;
	    }

	} else if (manual != MANUAL_NONE) {
	    /*
	     * Manual mode
	     */
	    manual_menu(unit, seconds);
	    if (manual == MANUAL_NONE) {
		/*
		 * Rewrite the display
		 */
		prompt(101, NULL);	/* "    Brewco x.x.x    " */
		prompt(300, NULL);	/* "                    " */
		prompt(401, NULL);	/* "---  MAN  AUTO SETUP" */
	    }
	} else {
	    /*
	     * Not running.
	     */
	    tempstatus();
	    key = keycheck();
	    if (key == KEY_ENTER) {
		setup();
		prompt(101, NULL);	/* "    Brewco x.x.x    " */
		prompt(200, NULL);
		prompt(300, NULL);
		hlt_status(0);
		mlt_status(0);
		pump_status(0);
		prompt(401, NULL);	/* "---  MAN  AUTO SETUP" */
	    } else if (key == KEY_RETURN && ! brew) {
		int     i, isOk = TRUE;
		char    message[41];

		tm = localtime(&now);
		snprintf(message, 40, "%04d%02d%02d-%02d%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min);
		brew = (brew_session *)malloc(sizeof(brew_session));
		brew->uuid_recipe = xstrcpy(choose_recipe());
		brew->uuid_unit = xstrcpy(unit->uuid);
		brew->name = xstrcpy(message);
		brew->brewstep = STEP_NA;
		brew->mashstep = MASH_NA;
		brew->timeout = brew->boiltimer = 0;
		brew->starttime = brew->endtime = (time_t)0;
		/*
		 * Now check if everything is sane
		 */
		if (strcmp(brew->uuid_recipe, (char *)"00000000-0000-0000-0000-000000000000") == 0) {
		    isOk = FALSE;
		    snprintf(message, Config.lcd_cols + 1, " No recipe selected ");
		    if (debug)
			fprintf(stdout, "brew init: No recipe selected\n");
		}
		if (isOk) {
		    isOk = FALSE;
		    for (recipe = recipes; recipe; recipe = recipe->next) {
			if (strcmp(recipe->uuid, brew->uuid_recipe) == 0) {
			    isOk = TRUE;
			    if (! recipe->boiltime)
				isOk = FALSE;
			    for (i = 0; i < 8; i++) {
				if (! recipe->mash[i].skip && ! recipe->mash[i].setpoint)
				    isOk = FALSE;
			    }
			    for (i = 0; i < 3; i++) {
				if (! recipe->hopstand[i].skip && recipe->hopstand[i].hold && (recipe->hopstand[i].setpoint == 0))
				    isOk = FALSE;
			    }
			    break;
			}
		    }
		    if (isOk == FALSE) {
			snprintf(message, Config.lcd_cols + 1, "    Recipe error    ");
			if (debug)
			    fprintf(stdout, "brew init: recipe error\n");
		    }
		}

		if (debug)
		    fprintf(stdout, "init brew: %s\n", isOk ? (char *)"Ok":(char *)"Error");

		if (isOk) {
		    wrsession(brew);
		} else {
		    prompt(300, message);
		    prompt(408, NULL);		/* "---  ---   Ok   --- " */
		    key = keywait();
		    if (brew->name)
			free(brew->name);
		    if (brew->uuid_recipe)
			free(brew->uuid_recipe);
		    if (brew->uuid_unit)
			free(brew->uuid_unit);
		    free(brew);
		    brew = NULL;
		    /*
		     * Rewrite the display
		     */
		    prompt(101, NULL);      /* "    Brewco x.x.x    " */
		    prompt(300, NULL);      /* "                    " */
		    prompt(401, NULL);      /* "---  MAN  AUTO SETUP" */
		}

	    } else if (key == KEY_DOWN) {
		manual = MANUAL_SELHLT;
		manual_prompt();
	    }
	}

	usleep(5000);	/* 5 mSec */

    } while (run);

    syslog(LOG_NOTICE, "Out of loop");
    if (debug)
	fprintf(stdout, (char *)"Out of loop\n");

    prompt(0, NULL);
    prompt(101, NULL);		/* "    Brewco x.x.x    " */
    prompt(302, NULL);		/* "   Shutting down    " */

    /*
     * Give threads time to cleanup
     */
    usleep(1500000);
    prompt(0, NULL);
    setBacklight(0);

    if (sock != -1) {
	if (shutdown(sock, SHUT_RDWR)) {
	    syslog(LOG_NOTICE, "Can't shutdown socket: %s", strerror(errno));
	}
	sock = -1;
    }

    wrrecipes();
    wrconfig();
    ulockprog((char *)"brewco");
    return 0;
}



int main(int argc, char *argv[])
{
    int		rc = 0, c, i;
    char	*homepath = NULL;

    while (1) {
	int option_index = 0;
	static struct option long_options[] = {
	    {"debug", 0, 0, 'c'},
	    {"help", 0, 0, 'h'},
	    {0, 0, 0, 0}
	};

	c = getopt_long(argc, argv, "dh", long_options, &option_index);
	if (c == -1)
	    break;

	switch (c) {
	    case 'd':   debug = TRUE;
			break;
	    case 'h':   help();
			return 1;
	}
    }

    openlog("brewco", LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_USER);
    syslog(LOG_NOTICE, "mbsePi-apps brewco v%s starting", VERSION);
    if (debug)
	fprintf(stdout, "mbsePi-apps brewco v%s starting\n", VERSION);

    /*
     * Set home directory path
     */
    if (getenv((char *)"USER") == NULL) {
	homepath = xstrcpy((char *)"/root");
    } else {
	homepath = xstrcpy(getenv((char *)"HOME"));
    }
    varpath = xstrcpy(homepath);
    varpath = xstrcat(varpath, (char *)"/.brewco/var/");
    etcpath = xstrcpy(homepath);
    etcpath = xstrcat(etcpath, (char *)"/.brewco/etc/");
    mkdirs(varpath, 0755);
    mkdirs(etcpath, 0755);

    if (rdconfig()) {
	fprintf(stderr, "Error reading configuration\n");
	syslog(LOG_NOTICE, "Error reading configuration: halted");
	return 1;
    }
    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.
     *  Don't catch SIGCHLD.
     */
    for (i = 0; i < NSIG; i++) {
	if ((i != SIGCHLD) && (i != SIGKILL) && (i != SIGSTOP))
	    signal(i, (void (*))die);
    }

#ifdef HAVE_WIRINGPI_H
    if (wiringPiSetup () )
	return 1;
#endif

    if ((rc = initLCD (Config.lcd_cols, Config.lcd_rows))) {
	fprintf(stderr, "Cannot initialize LCD display, rc=%d\n", rc);
	return 1;
    }

    rc = server();

    syslog(LOG_NOTICE, "Finished, rc=%d", rc);
    if (debug)
	fprintf(stdout, "Finished, rc=%d\n", rc);
    return rc;
}

mercurial