Added the mash sourcecode, this does nothing useful yet.

Sat, 12 Jul 2014 21:59:19 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Sat, 12 Jul 2014 21:59:19 +0200
changeset 103
99c47a8a61cb
parent 102
e37b9c571a56
child 104
5e538c4e1ecb

Added the mash sourcecode, this does nothing useful yet.

.hgignore file | annotate | diff | comparison | revisions
mash/Makefile file | annotate | diff | comparison | revisions
mash/beerxml.c file | annotate | diff | comparison | revisions
mash/beerxml.h file | annotate | diff | comparison | revisions
mash/labview-lvm.c file | annotate | diff | comparison | revisions
mash/labview-lvm.h file | annotate | diff | comparison | revisions
mash/lcd-pcf8574.c file | annotate | diff | comparison | revisions
mash/lcd-pcf8574.h file | annotate | diff | comparison | revisions
mash/lock.c file | annotate | diff | comparison | revisions
mash/lock.h file | annotate | diff | comparison | revisions
mash/logger.c file | annotate | diff | comparison | revisions
mash/logger.h file | annotate | diff | comparison | revisions
mash/mash.c file | annotate | diff | comparison | revisions
mash/mash.h file | annotate | diff | comparison | revisions
mash/rdconfig.c file | annotate | diff | comparison | revisions
mash/sensors.c file | annotate | diff | comparison | revisions
mash/xutil.c file | annotate | diff | comparison | revisions
mash/xutil.h file | annotate | diff | comparison | revisions
--- a/.hgignore	Sat Jul 12 19:22:13 2014 +0200
+++ b/.hgignore	Sat Jul 12 21:59:19 2014 +0200
@@ -11,6 +11,7 @@
 rc433/sniffer
 thermferm/thermferm
 dht11/dht11
+mash/mash
 
 syntax: glob
 *.o
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/Makefile	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,65 @@
+# Makefile for the mbsePi-apps/thermferm.
+
+include ../Makefile.global
+
+SRCS		= $(wildcard *.c)
+HDRS		= $(wildcard *.h)
+OBJS		= $(SRCS:.c=.o)
+SLIBS		= -lpthread
+TARGET		= mash
+OTHER		= Makefile
+
+#############################################################################
+
+.c.o:
+		${CC} ${CFLAGS} ${INCLUDES} ${DEFINES} -c $<
+
+all:		${TARGET}
+
+mash:		${OBJS} ${SLIBS}
+		${CC} -o mash ${OBJS} ${LDFLAGS} ${LIBS} ${SLIBS}
+
+clean:
+		rm -f ${TARGET} *.o *.h~ *.c~ core filelist Makefile.bak
+
+install:	all
+		${INSTALL} -c -s -g root -o root -m 0755 mash ${BINDIR}
+
+filelist:	Makefile
+		BASE=`pwd`; \
+		BASE=`basename $${BASE}`; \
+		(for f in ${SRCS} ${HDRS} ${OTHER} ;do echo ${PACKAGE}-${VERSION}/$${BASE}/$$f; done) >filelist
+
+depend:
+	@rm -f Makefile.bak; \
+	mv Makefile Makefile.bak; \
+	sed -e '/^# DO NOT DELETE/,$$d' Makefile.bak >Makefile; \
+	${ECHO} '# DO NOT DELETE THIS LINE - MAKE DEPEND RELIES ON IT' \
+		>>Makefile; \
+	${ECHO} '# Dependencies generated by make depend' >>Makefile; \
+	for f in ${SRCS}; \
+	do \
+		${ECHO} "Dependencies for $$f:\c"; \
+		${ECHO} "`basename $$f .c`.o:\c" >>Makefile; \
+		for h in `sed -n -e \
+			's/^#[ 	]*include[ 	]*"\([^"]*\)".*/\1/p' $$f`; \
+		do \
+			${ECHO} " $$h\c"; \
+			${ECHO} " $$h\c" >>Makefile; \
+		done; \
+		${ECHO} " done."; \
+		${ECHO} "" >>Makefile; \
+	done; \
+	${ECHO} '# End of generated dependencies' >>Makefile
+
+# DO NOT DELETE THIS LINE - MAKE DEPEND RELIES ON IT
+# Dependencies generated by make depend
+sensors.o: mash.h
+lock.o: mash.h lock.h
+beerxml.o: mash.h beerxml.h
+lcd-pcf8574.o: mash.h lcd-pcf8574.h
+mash.o: mash.h lock.h logger.h xutil.h beerxml.h
+xutil.o: mash.h xutil.h
+rdconfig.o: mash.h xutil.h
+logger.o: mash.h xutil.h logger.h
+# End of generated dependencies
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/beerxml.c	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,1531 @@
+/*****************************************************************************
+ * Copyright (C) 2014
+ *   
+ * 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 EC-65K; see the file COPYING.  If not, write to the Free
+ * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *****************************************************************************/
+
+#include "mash.h"
+#include "beerxml.h"
+
+
+recipe_rec		*recipes = NULL;
+
+
+int parseStyle(xmlDocPtr, xmlNodePtr, style_rec **);
+int parseEquipment(xmlDocPtr, xmlNodePtr, equipment_rec **);
+int parseMashStep(xmlDocPtr, xmlNodePtr, mash_step **);
+int parseMashSteps(xmlDocPtr, xmlNodePtr, mash_step **);
+int parseMash(xmlDocPtr, xmlNodePtr, mash_profile **);
+int parseFermentable(xmlDocPtr, xmlNodePtr, fermentable_rec **);
+int parseFermentables(xmlDocPtr, xmlNodePtr, fermentable_rec **);
+
+
+
+
+/*
+ * Parse style
+ */
+int parseStyle(xmlDocPtr doc, xmlNodePtr cur, style_rec **style)
+{
+    xmlChar     *key;
+    style_rec   *item;
+    float       val;
+
+    item = (style_rec *)malloc(sizeof(style_rec));
+    item->name = item->category = item->category_number = item->style_letter = item-> style_guide =
+	    item->type = item->notes = item->profile = item->ingredients = item->examples = NULL;
+    item->og_min = item->og_max = item->fg_min = item->fg_max = item->ibu_min = item->ibu_max =
+	    item->color_min = item->color_max = item->carb_min = item->carb_max = 
+	    item->abv_min = item->abv_max = 0.0;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    item->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CATEGORY"))) {
+	    item->category = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CATEGORY_NUMBER"))) {
+	    item->category_number = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STYLE_LETTER"))) {
+	    item->style_letter = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STYLE_GUIDE"))) {
+	    item->style_guide = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    item->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"OG_MIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->og_min = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"OG_MAX"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->og_max = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FG_MIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->fg_min = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FG_MAX"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->fg_max = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"IBU_MIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->ibu_min = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"IBU_MAX"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->ibu_max = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COLOR_MIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->color_min = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COLOR_MAX"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->color_max = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CARB_MIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->carb_min = val;
+	    xmlFree(key);
+	}               
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CARB_MAX"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->carb_max = val;
+	    xmlFree(key); 
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ABV_MIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->abv_min = val;
+	    xmlFree(key);
+	}              
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ABV_MAX"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->abv_max = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROFILE"))) {
+	    item->profile = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"INGREDIENTS"))) {
+	    item->ingredients = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"EXAMPLES"))) {
+	    item->examples = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	cur = cur->next;
+    }
+
+    *style = item;
+    return 0;
+}
+
+
+
+/*
+ * Parse equipment
+ */
+int parseEquipment(xmlDocPtr doc, xmlNodePtr cur, equipment_rec **equipment)
+{
+    xmlChar     	*key;
+    equipment_rec	*item;
+    float       	val;
+
+    item = (equipment_rec *)malloc(sizeof(equipment_rec));
+    item->name = item->notes = NULL;
+    item->version = 0;
+    item->boil_size = item->batch_size = item->tun_volume = item->tun_weight = 
+	    item->tun_specific_heat = item->top_up_water = item->trub_chiller_loss = 
+	    item->evap_rate = item->boil_time = item->lauter_deadspace =
+	    item->top_up_kettle = item->hop_utilization = 0.0;
+    item->calc_boil_volume = FALSE;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    xmlFree(key);
+	    item->version = 1;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BOIL_SIZE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->boil_size = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BATCH_SIZE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->batch_size = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TUN_VOLUME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->tun_volume = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TUN_WEIGHT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->tun_weight = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TUN_SPECIFIC_HEAT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->tun_specific_heat = val;    
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TOP_UP_WATER"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->top_up_water = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TRUB_CHILLER_LOSS"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->trub_chiller_loss = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"EVAP_RATE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->evap_rate = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BOIL_TIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->boil_time = val;
+	    xmlFree(key);        
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CALC_BOIL_VOLUME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		item->calc_boil_volume = TRUE;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"LAUTER_DEADSPACE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->lauter_deadspace = val;
+	    xmlFree(key);        
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HOP_UTILIZATION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->hop_utilization = val;
+	    xmlFree(key);        
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	cur = cur->next;
+    }
+
+    *equipment = item;
+    return 0;
+}
+
+
+
+/*
+ * Parse a Mash step and add it to the linked list
+ */
+int parseMashStep(xmlDocPtr doc, xmlNodePtr cur, mash_step **mashstep)
+{
+    xmlChar     *key;
+    mash_step	*step, *tmp;
+    float	val;
+
+    cur = cur->xmlChildrenNode;
+
+    step = (mash_step *)malloc(sizeof(mash_step));
+    step->next = NULL;
+    step->name = NULL;
+    step->type = NULL;
+    step->infuse_amount = 0.0;
+    step->step_temp = 0.0;
+    step->end_temp = 0.0;
+    step->step_time = 0.0;
+    step->ramp_time = 0.0;
+    step->version = 0;
+
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    step->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    step->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    step->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"INFUSE_AMOUNT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		step->infuse_amount = val;
+	    xmlFree(key);                           
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STEP_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		step->step_temp = val;
+	    xmlFree(key);                           
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STEP_TIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		step->step_time = val;
+	    xmlFree(key);                           
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"END_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		step->end_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"RAMP_TIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		step->ramp_time = val;
+	    xmlFree(key);
+	}
+
+	cur = cur->next;
+    }
+	
+    if (*mashstep == NULL) {
+	*mashstep = step;
+    } else {
+	for (tmp = *mashstep; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = step;
+		break;
+	    }
+	}
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse all Mash steps
+ */
+int parseMashSteps(xmlDocPtr doc, xmlNodePtr cur, mash_step **step)
+{
+    int		rc;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MASH_STEP"))) {
+	    if ((rc = parseMashStep(doc, cur, step)))
+		return 1;
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse Mash profile
+ */
+int parseMash(xmlDocPtr doc, xmlNodePtr cur, mash_profile **mash)
+{
+    xmlChar     *key;
+    mash_profile	*mashprofile;
+    float	val;
+
+    mashprofile = (mash_profile *)malloc(sizeof(mash_profile));
+    mashprofile->name = NULL;
+    mashprofile->version = 0;
+    mashprofile->notes = NULL;
+    mashprofile->grain_temp = 0.0;
+    mashprofile->mash_steps = NULL;
+    mashprofile->tun_temp = 0.0;
+    mashprofile->sparge_temp = 0.0;
+    mashprofile->ph = 0.0;
+    mashprofile->tun_weight = 0.0;
+    mashprofile->tun_specific_heat = 0.0;
+    mashprofile->equip_adjust = FALSE;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    mashprofile->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    mashprofile->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    mashprofile->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"GRAIN_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		mashprofile->grain_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TUN_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		mashprofile->tun_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SPARGE_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		mashprofile->sparge_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PH"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		mashprofile->ph = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TUN_WEIGHT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		mashprofile->tun_weight = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TUN_SPECIFIC_HEAT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		mashprofile->tun_specific_heat = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"EQUIP_ADJUST"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		mashprofile->equip_adjust = TRUE;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MASH_STEPS"))) {
+	    parseMashSteps(doc, cur, &(mashprofile)->mash_steps);
+	}
+	cur = cur->next;
+    }
+
+    *mash = mashprofile;
+    return 0;
+}
+
+
+
+/*
+ * Parse fermentable
+ */
+int parseFermentable(xmlDocPtr doc, xmlNodePtr cur, fermentable_rec **fr)
+{
+    xmlChar     	*key;
+    fermentable_rec	*item, *tmp;
+    float               val;
+
+    item = (fermentable_rec *)malloc(sizeof(fermentable_rec));
+    item->next = NULL;
+    item->name = item->type = item->notes = item->origin = item->supplier = NULL;
+    item->version = 0;
+    item->amount = item->yield = item->color = item->coarse_fine_diff = item->moisture = 
+	    item->diastatic_power = item->protein = item->max_in_batch = 0.0;
+    item->add_after_boil = item->recommend_mash = FALSE;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    item->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    item->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ORIGIN"))) {
+	    item->origin = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SUPPLIER"))) {
+	    item->supplier = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->amount = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"YIELD"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->yield = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COLOR"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->color = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ADD_AFTER_BOIL"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		item->add_after_boil = TRUE;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COARSE_FINE_DIFF"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->coarse_fine_diff = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MOISTURE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->moisture = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"DIASTATIC_POWER"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->diastatic_power = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROTEIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->protein = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MAX_IN_BATCH"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->max_in_batch = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"RECOMMEND_MASH"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		item->recommend_mash = TRUE;
+	}
+	cur = cur->next;
+    }
+
+    if (*fr == NULL) {
+	*fr = item;
+    } else {
+	for (tmp = *fr; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = item;
+		break;
+	    }
+	}
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Parse fermentables
+ */
+int parseFermentables(xmlDocPtr doc, xmlNodePtr cur, fermentable_rec **fr)
+{
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FERMENTABLE"))) {
+	    parseFermentable(doc, cur, fr);
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse hop
+ */
+int parseHop(xmlDocPtr doc, xmlNodePtr cur, hop_rec **hr)
+{
+    xmlChar     *key;
+    hop_rec	*item, *tmp;
+    float       val;
+
+    item = (hop_rec *)malloc(sizeof(hop_rec));
+    item->next = NULL;
+    item->name = item->use = item->notes = item->type = item->form = 
+	    item->origin = item->substitutes = NULL;
+    item->version = 0;
+    item->alpha = item->amount = item->time = item->beta = item->hsi = 
+	    item->humulene = item->caryophyllene = item->cohumulone = item->myrcene = 0.0;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    item->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ALPHA"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->alpha = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->amount = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"USE"))) {
+	    item->use = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->time = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    item->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FORM"))) {
+	    item->form = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BETA"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->beta = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HSI"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->hsi = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ORIGIN"))) {
+	    item->origin = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SUBSTITITES"))) {
+	    item->substitutes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HUMULENE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->humulene = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CARYOPHYLLENE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->caryophyllene = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COHUMULONE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->cohumulone = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MYRCENE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->myrcene = val;
+	    xmlFree(key);
+	}
+	cur = cur->next;
+    }
+
+    if (*hr == NULL) {
+	*hr = item;
+    } else {
+	for (tmp = *hr; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = item;
+		break;
+	    }
+	}
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Parse hops
+ */
+int parseHops(xmlDocPtr doc, xmlNodePtr cur, hop_rec **hr)
+{
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HOP"))) {
+	    parseHop(doc, cur, hr);
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse Yeast
+ */
+int parseYeast(xmlDocPtr doc, xmlNodePtr cur, yeast_rec **yr)
+{
+    xmlChar     *key;
+    yeast_rec	*item, *tmp;
+    float       val;
+    int		ival;
+
+    item = (yeast_rec *)malloc(sizeof(yeast_rec));
+    item->next = NULL;
+    item->name = item->type = item->form = item->laboratory = item->product_id = item->flocculation = item->notes = item->best_for = NULL;
+    item->amount = item->min_temperature = item->max_temperature = item->attenuation = 0.0;
+    item->amount_is_weight = item->add_to_secondary = FALSE;
+    item->version = item->times_cultured = item->max_reuse = 0;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    item->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    item->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FORM"))) {
+	    item->form = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->amount = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT_IS_WEIGHT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		item->amount_is_weight = TRUE;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"LABORATORY"))) {
+	    item->laboratory = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRODUCT_ID"))) {
+	    item->product_id = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MIN_TEMPERATURE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->min_temperature = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MAX_TEMPERATURE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->max_temperature = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FLOCCULATION"))) {
+	    item->flocculation = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ATTENUATION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->attenuation = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BEST_FOR"))) {
+	    item->best_for = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TIMES_CULTURED"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		item->times_cultured = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MAX_REUSE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		item->max_reuse = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ADD_TO_SECONDARY"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		item->add_to_secondary = TRUE;
+	}
+	cur = cur->next;
+    }
+
+    if (*yr == NULL) {
+	*yr = item;
+    } else {
+	for (tmp = *yr; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = item;
+		break;
+	    }
+	}
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Parse Yeasts
+ */
+int parseYeasts(xmlDocPtr doc, xmlNodePtr cur, yeast_rec **yr)
+{
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"YEAST"))) {
+	    parseYeast(doc, cur, yr);
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse Misc
+ */
+int parseMisc(xmlDocPtr doc, xmlNodePtr cur, misc_rec **mr)
+{
+    xmlChar     *key;
+    misc_rec   	*item, *tmp;
+    float       val;
+
+    item = (misc_rec *)malloc(sizeof(misc_rec));
+    item->next = NULL;
+    item->name = item->type = item->use = item->use_for = item->notes = NULL;
+    item->amount = item->time = 0.0;
+    item->amount_is_weight = FALSE;
+    item->version = 0;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    item->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    item->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"USE"))) {
+	    item->use = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->time = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->amount = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT_IS_WEIGHT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		item->amount_is_weight = TRUE;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"USE_FOR"))) {
+	    item->use_for = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	cur = cur->next;
+    }
+
+    if (*mr == NULL) {
+	*mr = item;
+    } else {
+	for (tmp = *mr; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = item;
+		break;
+	    }
+	}
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Parse Miscs
+ */
+int parseMiscs(xmlDocPtr doc, xmlNodePtr cur, misc_rec **mr)
+{
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MISC"))) {
+	    parseMisc(doc, cur, mr);
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse Water
+ */
+int parseWater(xmlDocPtr doc, xmlNodePtr cur, water_rec **wr)
+{
+    xmlChar     *key;
+    water_rec   *item, *tmp;
+    float       val;
+
+    item = (water_rec *)malloc(sizeof(water_rec));
+    item->next = NULL;
+    item->name = item->notes = NULL;
+    item->amount = item->calcium = item->bicarbonate = item->sulfate = item->chloride = item->sodium = item->magnesium = item->ph = 0.0;
+    item->version = 0;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    item->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->amount = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CALCIUM"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->calcium = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BICARBONATE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->bicarbonate = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SULFATE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->sulfate = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CHLORIDE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->chloride = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SODIUM"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->sodium = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MAGNESIUM"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->magnesium = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PH"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->ph = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	cur = cur->next;
+    }
+
+    if (*wr == NULL) {
+	*wr = item;
+    } else {
+	for (tmp = *wr; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = item;
+		break;
+	    }
+	}
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Parse Waters
+ */
+int parseWaters(xmlDocPtr doc, xmlNodePtr cur, water_rec **wr)
+{
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"WATER"))) {
+	    parseWater(doc, cur, wr);
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse a recipe
+ */
+void parseRecipe(xmlDocPtr doc, xmlNodePtr cur)
+{
+    xmlChar	*key;
+    recipe_rec	*recipe, *tmp;
+    float	val;
+    int		ival;
+
+    recipe = (recipe_rec *)malloc(sizeof(recipe_rec));
+    recipe->next = NULL;
+    recipe->name = NULL;
+    recipe->brewer = NULL;
+    recipe->asst_brewer = NULL;
+    recipe->type = NULL;
+    recipe->style = NULL;
+    recipe->equipment = NULL;
+    recipe->batch_size = 0.0;
+    recipe->boil_time = 0.0;
+    recipe->efficiency = 0.0;
+    recipe->hops = NULL;
+    recipe->fermentables = NULL;
+    recipe->miscs = NULL;
+    recipe->yeasts = NULL;
+    recipe->waters = NULL;
+    recipe->notes = NULL;
+    recipe->taste_notes = NULL;
+    recipe->taste_rating = 0.0;
+    recipe->og = 0.0;
+    recipe->fg = 0.0;
+    recipe->fermentation_stages = 0;
+    recipe->primary_age = recipe->secondary_age = recipe->tertiary_age = recipe->age = 0;
+    recipe->primary_temp = recipe->secondary_temp = recipe->tertiary_temp = recipe->age_temp = 0.0;
+    recipe->date = NULL;
+    recipe->carbonation = 0.0;
+    recipe->forced_carbonation = FALSE;
+    recipe->priming_sugar_name = NULL;
+    recipe->carbonation_temp = 0.0;
+    recipe->priming_sugar_equiv = recipe->keg_priming_factor = 0.0;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    recipe->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    recipe->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STYLE"))) {
+	    parseStyle(doc, cur, &(recipe)->style);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"EQUIPMENT"))) {
+	    parseEquipment(doc, cur, &(recipe)->equipment);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BREWER"))) {
+	    recipe->brewer = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ASST_BREWER"))) {
+	    recipe->asst_brewer = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BATCH_SIZE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->batch_size = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BOIL_SIZE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->boil_size = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BOIL_TIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->boil_time = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"EFFICIENCY"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->efficiency = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HOPS"))) {
+	    parseHops(doc, cur, &(recipe)->hops);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FERMENTABLES"))) {
+	    parseFermentables(doc, cur, &(recipe)->fermentables);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MISCS"))) {
+	    parseMiscs(doc, cur, &(recipe)->miscs);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"YEASTS"))) {
+	    parseYeasts(doc, cur, &(recipe)->yeasts);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"WATERS"))) {
+	    parseWaters(doc, cur, &(recipe)->waters);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MASH"))) {
+	    parseMash(doc, cur, &(recipe)->mash);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    recipe->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TASTE_NOTES"))) {
+	    recipe->taste_notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TASTE_RATING"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->taste_rating = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"OG"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->og = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FG"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->fg = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FERMENTATION_STAGES"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->fermentation_stages = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRIMARY_AGE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->primary_age = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRIMARY_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->primary_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SECONDARY_AGE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->secondary_age = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SECONDARY_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->secondary_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TERTIARY_AGE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->tertiary_age = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TERTIARY_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->tertiary_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AGE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->age = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AGE_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->age_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"DATE"))) {
+	    recipe->date = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CARBONATION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->carbonation = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FORCED_CARBONATION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		recipe->forced_carbonation = TRUE;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRIMING_SUGAR_NAME"))) {
+	    recipe->priming_sugar_name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CARBONATION_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->carbonation_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRIMING_SUGAR_EQUIV"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->priming_sugar_equiv = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"KEG_PRIMING_FACTOR"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->keg_priming_factor = val;
+	    xmlFree(key);
+	}
+	cur = cur->next;
+    }
+
+    if (recipes == NULL) {
+	recipes = recipe;
+    } else {
+	for (tmp = recipes; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = recipe;
+		break;
+	    }
+	}
+    }
+}
+
+
+
+int parseBeerXML(char *docname)
+{
+    xmlDocPtr		doc;
+    xmlNodePtr		cur;
+
+    if ((doc = xmlParseFile(docname)) == NULL) {
+	fprintf(stderr, "XML file %s errors.\n", docname);
+	return 1;
+    }
+
+    if ((cur = xmlDocGetRootElement(doc)) == NULL) {
+	fprintf(stderr, "XML file %s empty.\n", docname);
+	xmlFreeDoc(doc);
+	return 1;
+    }
+
+    if (xmlStrcmp(cur->name, (const xmlChar*)"RECIPES")) {
+	fprintf(stderr, "XML file %s is not a BeerXML file.\n", docname);
+	xmlFreeDoc(doc);
+	return 1;
+    }
+
+    /*
+     * Parse all recipes
+     */
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"RECIPE"))) {
+	    parseRecipe(doc, cur);
+	}
+	cur = cur->next;
+    }
+
+    xmlFreeDoc(doc);
+    return 0;
+}
+
+
+
+/*
+ * For debugging purposes, dump the recipes
+ */
+void printBeerXML(void)
+{
+    recipe_rec		*tmp0;
+    mash_step           *tmp1;
+    fermentable_rec     *tmp2;
+    equipment_rec	*tmp3;
+    style_rec		*tmp4;
+    hop_rec		*tmp5;
+    yeast_rec		*tmp6;
+    misc_rec		*tmp7;
+    water_rec		*tmp8;
+    int			mashstep = 0;
+    float               grain_weight = 0.0, water_grain_ratio, tun_water = 0.0, temp;
+
+    for (tmp0 = recipes; tmp0; tmp0 = tmp0->next) {
+	printf("--------------------------------------------------------------------------------\n");
+    	printf("Recipe:     %s\n", tmp0->name);
+    	printf("Type:       %s\n", tmp0->type);
+	if (tmp0->notes)
+	    printf("Notes:      %s\n", tmp0->notes);
+	if (tmp0->brewer || tmp0->asst_brewer)
+    	    printf("Brewer:     %s  Assistent brewer: %s\n", tmp0->brewer, tmp0->asst_brewer);
+	printf("Batch size: %5.1f  Boil time:  %5.1f  Efficiency: %5.1f\n", tmp0->batch_size, tmp0->boil_time, tmp0->efficiency);
+	printf("Taste rate: %5.1f  Taste notes: %s\n", tmp0->taste_rating, tmp0->taste_notes);
+	printf("Original G: %5.3f  Final G:    %5.3f\n", tmp0->og, tmp0->fg);
+    	printf("\n");
+
+	if (tmp0->equipment) {
+	    tmp3 = tmp0->equipment;
+	    printf("Equipment:  %s\n", tmp3->name);
+	    if (tmp3->notes)
+	    	printf("  Notes:     %s\n", tmp3->notes);
+	    printf("  Boil size:  %5.1f  Batch size:   %5.1f  Boil time:        %5.1f\n", tmp3->boil_size, tmp3->batch_size, tmp3->boil_time);
+	    printf("  Tun volume: %5.1f  Tun weight:   %5.1f  Specific heat:    %5.1f\n", tmp3->tun_volume, tmp3->tun_weight, tmp3->tun_specific_heat);
+	    printf("  TopUp:      %5.1f  Chiller loss: %5.1f  Evaporation rate: %5.1f\n", tmp3->top_up_water, tmp3->trub_chiller_loss, tmp3->evap_rate);
+	    printf("  Tun loss:   %5.1f  TopUp kettle: %5.1f  Hop utilization:  %5.1f\n", tmp3->lauter_deadspace, tmp3->top_up_kettle, tmp3->hop_utilization);
+	    printf("  CalcBoilVol:  %s\n", tmp3->calc_boil_volume ? "YES":"NO");
+	    printf("\n");
+	}
+
+	if (tmp0->style) {
+	    tmp4 = tmp0->style;
+	    printf("Style:      %s-%s Type:   %s\n", tmp4->style_letter, tmp4->name, tmp4->type); 
+	    if (tmp4->category || tmp4->category_number || tmp4->style_guide)
+	    	printf(" Category:  %s    Cat nr: %s  Guide: %s\n", tmp4->category, tmp4->category_number, tmp4->style_guide);
+	    printf("  OG:        %5.3f - %5.3f\n", tmp4->og_min, tmp4->og_max);
+	    printf("  FG:        %5.3f - %5.3f\n", tmp4->fg_min, tmp4->fg_max);
+	    printf("  Bittern:   %5.1f - %5.1f IBU\n", tmp4->ibu_min, tmp4->ibu_max);
+	    printf("  Color:     %5.1f - %5.1f SRM\n", tmp4->color_min, tmp4->color_max);
+	    printf("  Carb:      %5.1f - %5.1f V-CO2\n", tmp4->carb_min, tmp4->carb_max);
+	    printf("  ABV:       %5.1f - %5.1f %%\n", tmp4->abv_min, tmp4->abv_max);
+	    if (tmp4->notes)
+	    	printf("  Notes:     %s\n", tmp4->notes);
+	    if (tmp4->profile)
+	    	printf("  Profile:   %s\n", tmp4->profile);
+	    if (tmp4->examples)
+	    	printf("  Examples:  %s\n", tmp4->examples);
+	    printf("\n");
+	}
+
+    	printf("Fermentables:\n");
+    	printf("  Type         Name                       Kg    Mash  Moist\n");
+    	for (tmp2 = tmp0->fermentables; tmp2; tmp2 = tmp2->next) {
+	    printf("  %-12s %-25s %6.3f %-5s %5.1f\n", tmp2->type, tmp2->name, tmp2->amount,
+			tmp2->recommend_mash ? "TRUE":"FALSE", tmp2->moisture);
+	    if (tmp2->recommend_mash)
+	    	grain_weight += tmp2->amount;
+        }
+    	printf("\n");
+
+	if (tmp0->hops) {
+	    printf("Hops:\n");
+	    //        123456789 1234567890 1234567890123456789012345 123456 12345 12345
+	    printf("  Type      Use        Name                          Kg  Time Alpha\n");
+	    for (tmp5 = tmp0->hops; tmp5; tmp5 = tmp5->next) {
+	    	printf("  %-9s %-10s %-25s %6.3f %5.1f %5.1f\n", tmp5->type, tmp5->use, tmp5->name, tmp5->amount, tmp5->time, tmp5->alpha);
+	    }
+	    printf("\n");
+	}
+
+	if (tmp0->miscs) {
+	    printf("Miscs:\n");
+	    //        12345678901 123456789 1234567890123456789012345 123456 12345 12345
+	    printf("  Type        Use       Name                          Kg  Time\n");
+	    for (tmp7 = tmp0->miscs; tmp7; tmp7 = tmp7->next) {
+		printf("  %-11s %-9s %-25s %6.3f %5.1f\n", tmp7->type, tmp7->use, tmp7->name, tmp7->amount, tmp7->time);
+	    }
+	    printf("\n");
+	}
+
+	if (tmp0->waters) {
+	    printf("Wateren:\n");
+	    //        1234567890123456789012345 12345 12345 1234...
+	    printf("  Name                       Ltrs    PH Notes\n");
+	    for (tmp8 = tmp0->waters; tmp8; tmp8 = tmp8->next) {
+		printf("  %-25s %5.1f %5.1f %s\n", tmp8->name, tmp8->amount, tmp8->ph, tmp8->notes);
+	    }
+	    printf("\n");
+	}
+
+    	printf("Mash profile:   %s\n", tmp0->mash->name);
+	if (tmp0->mash->notes)
+    	    printf("  Notes:        %s\n", tmp0->mash->notes);
+    	printf("  Grain temp:  %5.1f   Tun temp:   %5.1f    Sparge temp:       %5.1f\n", tmp0->mash->grain_temp, tmp0->mash->tun_temp, tmp0->mash->sparge_temp);
+    	printf("  PH:          %5.1f   Tun weight: %6.2f   Tun specific heat: %6.2f\n", tmp0->mash->ph, tmp0->mash->tun_weight, tmp0->mash->tun_specific_heat);
+    	printf("  Equip adjust: %s\n", tmp0->mash->equip_adjust ? "TRUE":"FALSE");
+    	printf("\n");
+
+	if (tmp0->mash->mash_steps) {
+    	    mashstep = 0;
+    	    printf("st Name             Type          Temp  Endtmp Dur Rmp Inf/Dec  L/Kg\n");
+    	    for (tmp1 = tmp0->mash->mash_steps; tmp1; tmp1 = tmp1->next) {
+	    	mashstep++;
+	    	tun_water += tmp1->infuse_amount;
+	    	water_grain_ratio = tun_water / grain_weight;
+	    	temp = tmp1->infuse_amount;
+//	    	if ((tmp1->infuse_amount == 0.0) && (tmp1->decotion_amt > 0.0))
+//		    temp = tmp1->decotion_amt;
+	    	printf("%2d %-16s %-12s %6.1f %6.1f %3.0f %3.0f %6.1f %6.2f\n", mashstep, tmp1->name, tmp1->type, 
+			tmp1->step_temp, tmp1->end_temp, tmp1->step_time, tmp1->ramp_time, temp, water_grain_ratio);
+            }
+	    printf("\n");
+	}
+
+	if (tmp0->yeasts) {
+	    for (tmp6 = tmp0->yeasts; tmp6; tmp6 = tmp6->next) {
+		printf("Yeast:       %s\n", tmp6->name);
+		printf("  Type:      %-20s Form:       %-20s Amount:      %7.4f %s\n", 
+				tmp6->type, tmp6->form, tmp6->amount,  tmp6->amount_is_weight ? "Kg":"L");
+		printf("  Lab:       %-20s Product id: %-20s Flocculation: %s\n", tmp6->laboratory, tmp6->product_id, tmp6->flocculation);
+		printf("  Min temp: %5.1f                 Max temp:  %5.1f                 Attenuation: %5.1f\n", 
+				tmp6->min_temperature, tmp6->max_temperature, tmp6->attenuation);
+		if (tmp6->notes)
+		    printf("  Notes:     %s\n", tmp6->notes);
+		printf("\n");
+	    }
+	}
+
+	printf("\n");
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/beerxml.h	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,262 @@
+#ifndef	_BEERXML_H
+#define	_BEERXML_H
+
+
+/*
+ * See http://www.beerxml.com/beerxml.htm for more information.
+ * This standard isn't perfect, but it works. Note that some
+ * fields are not well defined. Some precentages are safe if you
+ * declare them as float instead of intergers.
+ *
+ * The function parseBeerXML() reads the file and puts the
+ * contents in the following tree:
+ *
+ * recipes recipe hops hop
+ *                     hop
+ *                fermentables fermentable
+ *                             fermentable
+ *                yeasts yeast
+ *                       yeast
+ *                miscs misc
+ *                      misc
+ *                waters water
+ *                       water
+ *                style
+ *                mash mash_step
+ *                     mash_step
+ *                equipment
+ *         recipe hops hop 
+ *           and so on.
+ */
+
+typedef struct _hop_rec {
+    struct _hop_rec	*next;
+    char		*name;			/* Name of the hops				*/
+    int			version;		/* Should be 1 for this version of the standard	*/
+    float		alpha;			/* Percent alpha of hops			*/
+    float		amount;			/* Weight in Kilograms of the hops used.	*/
+    char		*use;			/* "Boil", "Dry Hop", "Mash", "First Wort" or "Aroma"	*/
+    float		time;			/* The time as measured in minutes. 		*/
+    char		*notes;			/* Textual notes about the hops.		*/
+    char		*type;			/* "Bittering", "Aroma" or "Both"		*/
+    char		*form;			/* "Pellet", "Plug" or "Leaf"			*/
+    float		beta;			/* Hop beta percentage.				*/
+    float		hsi;			/* Hop Stability Index				*/
+    char		*origin;		/* Place of origin for the hops			*/
+    char		*substitutes;		/* Substitutes that can be used for this hops	*/
+    float		humulene;		/* Humulene level in percent.			*/
+    float		caryophyllene;		/* Caryophyllene level in percent.		*/
+    float		cohumulone;		/* Cohumulone level in percent			*/
+    float		myrcene;		/* Myrcene level in percent			*/
+} hop_rec;
+
+typedef struct _fermentable_rec {
+    struct _fermentable_rec	*next;
+    char		*name;			/* Name						*/
+    int			version;		/* Record version				*/
+    char		*type;			/* "Grain", "Sugar", "Extract", "Dry Extract" or "Adjunct"	*/
+    char		*notes;			/* Notes					*/
+    float		amount;			/* Weight in KG					*/
+    float		yield;			/* Percent dry yield (fine grain)		*/
+    float		color;			/* The color of the item in Lovibond		*/
+    bool		add_after_boil;		/* May be TRUE.					*/
+    char		*origin;		/* Country or place of origin			*/
+    char		*supplier;		/* Supplier of the grain/extract/sugar		*/
+    float		coarse_fine_diff;	/* Percent difference between the coarse grain yield and fine grain yield.	*/
+    float		moisture;		/* Percent moisture in the grain.		*/
+    float		diastatic_power;	/* The diastatic power of the grain		*/
+    float		protein;		/* The percent protein in the grain.		*/
+    float		max_in_batch;		/* The recommended maximum percentage		*/
+    bool		recommend_mash;		/* TRUE if it is recommended to mash		*/
+} fermentable_rec;
+
+typedef struct _equipment_rec {
+    char		*name;			/* Name of the equipment profile		*/
+    int			version;		/* Version of the equipment record.		*/
+    float		boil_size;		/* The pre-boil volume used.			*/
+    float		batch_size;		/* The target volume of the batch.		*/
+    float		tun_volume;		/* Volume of the mash tun in liters.		*/
+    float		tun_weight;		/* Weight of the mash tun in kilograms.		*/
+    float		tun_specific_heat;	/* The specific heat of the mash tun.		*/
+    float		top_up_water;		/* The amount of top up water normally added.	*/
+    float		trub_chiller_loss;	/* The amount of wort normally lost.		*/
+    float		evap_rate;		/* Perc of wort lost to evaporation per hour.	*/
+    float		boil_time;		/* The normal amount of time one boils		*/
+    bool		calc_boil_volume;	/* Flag denoting to calculate the boil size.	*/
+    float		lauter_deadspace;	/* Amount lost to the lauter tun.		*/
+    float		top_up_kettle;		/* Amount normally added to the boil 		*/
+    float		hop_utilization;	/* Large batch hop utilization.			*/
+    char		*notes;			/* Notes associated with the equipment.		*/
+} equipment_rec;
+
+typedef struct _yeast_rec {
+    struct _yeast_rec	*next;
+    char		*name;			/* Name of the yeast.				*/
+    int			version;		/* Version of the standard.			*/
+    char		*type;			/* “Ale”, “Lager”, “Wheat”, “Wine”, “Champagne”	*/
+    char		*form;			/* “Liquid”, “Dry”, “Slant” or “Culture”	*/
+    float		amount;			/* The amount of yeast, measured in liters.	*/
+    bool		amount_is_weight;	/* TRUE if the amount measurement is a weight	*/
+    char		*laboratory;		/* The laboratory that produced the yeast.	*/
+    char		*product_id;		/* The manufacturer’s product ID label		*/
+    float		min_temperature;	/* The minimum recommended temperature		*/
+    float		max_temperature;	/* The maximum recommended temperature		*/
+    char		*flocculation;		/* “Low”, “Medium”, “High” or “Very High”	*/
+    float		attenuation;		/* Average attenuation for this yeast strain.	*/
+    char		*notes;			/* Notes on this yeast strain.			*/
+    char		*best_for;		/* Beerstyle this yeast is best suited for.	*/
+    int			times_cultured;		/* Number of times this yeast has been reused	*/
+    int			max_reuse;		/* Recommended maximum reuse times		*/
+    bool		add_to_secondary;	/* Flag this yeast was added for a secondary	*/
+} yeast_rec;
+
+typedef struct _misc_rec {
+    struct _misc_rec	*next;
+    char		*name;			/* Name of the misc item.			*/
+    int			version;		/* Version number of this element.		*/
+    char		*type;			/* “Spice”, “Fining”, “Water Agent”, “Herb”, “Flavor”, “Other”	*/
+    char		*use;			/* “Boil”, “Mash”, “Primary”, “Secondary”, “Bottling”	*/
+    float		time;			/* Time the misc was boiled, steeped, mashed, etc in minutes.	*/
+    float		amount;			/* Amount of item used. Liters or Weight	*/
+    bool		amount_is_weight;	/* TRUE if amount is weight			*/
+    char		*use_for;		/* Desc. of what the ingredient is used for	*/
+    char		*notes;			/* Detailed notes on the item including usage.	*/
+} misc_rec;
+
+typedef struct _water_rec {
+    struct _water_rec	*next;
+    char		*name;			/* Name of the water profile			*/
+    int			version;		/* Version of the water record.			*/
+    float		amount;			/* Volume of water to use in a recipe in liters	*/
+    float		calcium;		/* The amount of calcium (Ca) in ppm		*/
+    float		bicarbonate;		/* The amount of bicarbonate (HCO3) in ppm.	*/
+    float		sulfate;		/* The amount of Sulfate (SO4) in ppm.		*/
+    float		chloride;		/* The amount of Chloride (Cl) in ppm.		*/
+    float		sodium;			/* The amount of Sodium (Na) in ppm.		*/
+    float		magnesium;		/* The amount of Magnesium (Mg) in ppm.		*/
+    float		ph;			/* The PH of the water.				*/
+    char		*notes;			/* Notes about the water profile.		*/
+} water_rec;
+
+typedef struct _style_rec {
+    char		*name;			/* Name of the style profile			*/
+    char		*category;		/* Category that this style belongs to		*/
+    int			version;		/* Version of the style record.			*/
+    char		*category_number;	/* Number or identifier for this style category	*/
+    char		*style_letter;		/* The specific style number or subcategory	*/
+    char		*style_guide;		/* The name of the style guide			*/
+    char		*type;			/* “Lager”, “Ale”, “Mead”, “Wheat”, “Mixed” or “Cider”	*/
+    float		og_min;			/* Original gracity				*/
+    float		og_max;
+    float		fg_min;			/* Final gravity				*/
+    float		fg_max;
+    float		ibu_min;		/* Bitterness for this style as measured	*/
+    float		ibu_max;		/* in International Bitterness Units (IBUs)	*/
+    float		color_min;		/* The recommended color in SRM			*/
+    float		color_max;
+    float		carb_min;		/* The recommended carbonation for this style	*/
+    float		carb_max;		/* in volumes of CO2				*/
+    float		abv_min;		/* Alcohol by volume as a percentage.		*/
+    float		abv_max;
+    char		*notes;			/* Description of the style, history		*/
+    char		*profile;		/* Flavor and aroma profile for this style	*/
+    char		*ingredients;		/* Suggested ingredients for this style		*/
+    char		*examples;		/* Example beers of this style.			*/
+} style_rec;
+
+typedef struct _mash_step {
+    struct _mash_step	*next;
+    char		*name;			/* Name of this step				*/
+    int			version;		/* Record version				*/
+    char		*type;			/* “Infusion”, “Temperature” or “Decoction”	*/
+    float		infuse_amount;		/* Wateri vol. in liters to infuse in this step	*/
+    float		infuse_temp;		/* niet officieel - berekenen	*/
+    float		step_temp;		/* The target temperature for this step		*/
+    float		end_temp;		/* The final temperature you can expect		*/
+    float		step_time;		/* The number of minutes to spend		*/
+    float		ramp_time;		/* Time to achieve the desired step temperature	*/
+    float		water_grain_ratio;	/* niet officieel - berekenen	*/
+    float		decotion_amt;		/* niet officieel	*/
+} mash_step;
+
+typedef struct _mash_profile {
+    char                *name;                  /* Name of mash method                          */
+    int			version;		/* Record version				*/
+    char                *notes;                 /* Additional notes                             */
+    float               grain_temp;             /* The temperature of the grain                 */
+    mash_step           *mash_steps;            /* List of mash steps                           */
+    float               tun_temp;               /* Grain tun temperature                        */
+    float               sparge_temp;            /* Temperature of the sparge water              */
+    float               ph;                     /* PH of the sparge.                            */
+    float               tun_weight;             /* Weight of the mash tun in kilograms          */
+    float               tun_specific_heat;      /* Specific heat of the tun material            */
+    bool                equip_adjust;		/* Adjust for tun heat loss			*/
+} mash_profile;
+
+typedef struct _brew_equipment {
+    char 		*name;			/* Name of the equipment profile		*/
+    float		boil_size;		/* The pre-boil volume for this equipment setup	*/
+    float		batch_size;		/* The target volume of the batch		*/
+    float		tun_volume;		/* Volume of the mash tun in liters.		*/
+    float		tun_weight;		/* Weight of the mash tun in kilograms.		*/
+    float		tun_specific_heat;	/* The specific heat of the mash tun.		*/
+    float		top_up_water;		/* The amount of top up water b4 fermenting	*/
+    float		trub_chiller_loss;	/* Amount of wort lost during cooling		*/
+    float		evap_rate;		/* Perc of wort lost to evaporation per hour.	*/
+    float		boil_time;		/* The normal amount of boil time for this eq.	*/
+    bool		calc_boil_volume;	/* Should the program calculate the boil size.	*/
+    float		lauter_dead_space;	/* Amount lost to the lauter tun.		*/
+    float		top_up_kettle;		/* Amount added to the boil kettle b4 the boil.	*/
+    float		hop_utilization;	/* Large batch hop utilization.			*/
+    char		*notes;			/* Notes for this equipment.			*/
+} brew_equipment;
+
+typedef struct _recipe_rec {
+    struct _recipe_rec 	*next;
+    int			version;		/* Current version is 1				*/
+    char                *name;                  /* Name of the recipe                           */
+    char                *type;                  /* “Extract”, “Partial Mash” or “All Grain”     */
+    style_rec		*style;			/* style record					*/
+    equipment_rec	*equipment;		/* equipemnt record				*/
+    char                *brewer;                /* Name of the brewer                           */
+    char		*asst_brewer;		/* Name of the assistent brewer			*/
+    float               batch_size;             /* Target size of the finished batch in liters. */
+    float               boil_size;              /* Starting size for the main boil in liters    */
+    float               boil_time;              /* The total time to boil the wort in minutes.  */
+    float               efficiency;             /* The percent brewhouse efficiency             */
+    hop_rec		*hops;			/* hops						*/
+    fermentable_rec	*fermentables;		/* Fermentables list				*/
+    misc_rec		*miscs;			/* Misc ingredients				*/
+    yeast_rec		*yeasts;		/* Yeasts					*/
+    water_rec		*waters;		/* Water profiles				*/
+    mash_profile	*mash;			/* Mash profile					*/
+    char		*notes;			/* Notes associated with this recipe		*/
+    char		*taste_notes;		/* Tasting notes – may be multiline.		*/
+    float		taste_rating;		/* Number between zero and 50.0 (BJCP system)	*/
+    float		og;			/* The measured original specific gravity.	*/
+    float		fg;			/* The measured final gravity of the beer.	*/
+    int			fermentation_stages;	/* The number of fermentation stages used	*/
+    int			primary_age;		/* Time spent in the primary in days		*/
+    float		primary_temp;		/* Temp in Celsius for the primary fermentation	*/
+    int			secondary_age;		/* Time spent in the secondary in days.		*/
+    float		secondary_temp;		/* Temp in C for the secondary fermentation.	*/
+    int			tertiary_age;		/* Time spent in the third fermenter in days.	*/
+    float		tertiary_temp;		/* Temperature in the tertiary fermenter.	*/
+    int			age;			/* Time to age the beer in days after bottling.	*/
+    float		age_temp;		/* Temperature for aging after bottling.	*/
+    char		*date;			/* Date brewed in a easily recognizable format.	*/
+    float		carbonation;		/* Volume of CO2 used to carbonate this beer.	*/
+    bool		forced_carbonation;	/* TRUE if forced carbonated using CO2 pressure	*/
+    char		*priming_sugar_name;	/* Text describing the priming agent.		*/
+    float		carbonation_temp;	/* Temp for either bottling/forced carbonation.	*/
+    float		priming_sugar_equiv;	/* Factor used to convert this priming agent	*/
+    float		keg_priming_factor;	/* Used 2 factor amount of sugar for containers	*/
+} recipe_rec;
+
+
+int parseBeerXML(char *);
+void printBeerXML(void);
+
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/labview-lvm.c	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,93 @@
+/*****************************************************************************
+ * Copyright (C) 2008-2014
+ *   
+ * 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 EC-65K; see the file COPYING.  If not, write to the Free
+ * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *****************************************************************************/
+
+#include "mash.h"
+#include "xutil.h"
+#include "labview-lvm.h"
+
+
+char		lvm_name[PATH_MAX] = "";
+
+
+
+void create_lvm(char *filename, char *progname, char *data)
+{
+    FILE		*lvm_file;
+    struct timeval	now;
+    struct tm		ptm;
+    int			i;
+
+    /* NOTE: in het voorbeeld wordt een , als separator gebruikt, hollandse locale ? */
+
+    snprintf(lvm_name, PATH_MAX, "/var/local/log/%s/%s", progname, filename);
+
+    /* Rename excisting ... */
+
+    if (access(lvm_name, F_OK)) {
+	/*
+	 * 2 options:
+	 *   Append, so return here.
+	 *   Rename file and create a new one
+	 */
+    }
+
+    gettimeofday(&now, NULL);
+    localtime_r(&now.tv_sec, &ptm);
+
+    if ((lvm_file = fopen(lvm_name, "w"))) {
+
+	fprintf(lvm_file, "LabVIEW Measurement\r\n");
+	fprintf(lvm_file, "Separator	Tab\r\n");
+	fprintf(lvm_file, "Decimal_Separator	.\r\n");
+	fprintf(lvm_file, "Writer_Version	2\r\n");
+	fprintf(lvm_file, "Reader_Version	2\r\n");
+	fprintf(lvm_file, "Multi_Headings	No\r\n");
+	fprintf(lvm_file, "Date	%04d/%02d/%02d\r\n", ptm.tm_year + 1900, ptm.tm_mon + 1, ptm.tm_mday);
+	fprintf(lvm_file, "Time	%02d:%02d:%02d.000\r\n", ptm.tm_hour, ptm.tm_min, ptm.tm_sec);
+	fprintf(lvm_file, "X_Columns	One\r\n");
+	fprintf(lvm_file, "Description	Tcontrol Logfile\r\n");
+	fprintf(lvm_file, "Time_Pref	Absolute\r\n");
+	fprintf(lvm_file, "Operator	harrie\r\n");
+	fprintf(lvm_file, "***End_of_Header***\r\n");
+	fprintf(lvm_file, "\r\n");
+	fprintf(lvm_file, "Channels	9\r\n");
+	fprintf(lvm_file, "Samples	1	1	1	1	1	1	1	1	1\r\n");
+	fprintf(lvm_file, "Date");
+	for (i = 1; i <= 9; i++)
+	    fprintf(lvm_file, "	%04d/%02d/%02d", ptm.tm_year + 1900, ptm.tm_mon + 1, ptm.tm_mday);
+	fprintf(lvm_file, "\r\n");
+	fprintf(lvm_file, "Time");
+	for (i = 1; i <= 9; i++)
+	    fprintf(lvm_file, "	%02d:%02d:%02d.000", ptm.tm_hour, ptm.tm_min, ptm.tm_sec);
+	fprintf(lvm_file, "\r\n");
+	fprintf(lvm_file, "X_Dimension	Time	Time	Time	Time	Time	Time	Time	Time	Time\r\n");
+	fprintf(lvm_file, "X0	0.0000000000000000E+0	0.0000000000000000E+0	0.0000000000000000E+0	0.0000000000000000E+0	0.0000000000000000E+0	0.0000000000000000E+0	0.0000000000000000E+0	0.0000000000000000E+0	0.0000000000000000E+0\r\n");
+	fprintf(lvm_file, "Delta_X	1.000000	1.000000	1.000000	1.000000	1.000000	1.000000	1.000000	1.000000	1.000000\r\n");
+	fprintf(lvm_file, "***End_of_Header***\r\n");
+	fprintf(lvm_file, "X_Value	Untitled	Untitled 1	Untitled 2	Untitled 3	Untitled 4	Untitled 5	Untitled 6	Untitled 7	Untitled 8	Comment\r\n");
+    }
+}
+
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/labview-lvm.h	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,7 @@
+#ifndef _LABVIEW_LVM_H
+#define	_LABVIEW_LVM_H
+
+
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/lcd-pcf8574.c	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,144 @@
+/*
+ * lcd-pcf8574.c:
+ *	Text-based LCD driver library code
+ *	This is designed to drive the HD44780U LCD display connected via
+ *	a "LCM1602 IIC  A0 A1 A2" board with a PCF8574 I2C controller.
+ *
+ * Copyright (c) 2012-2013 Gordon Henderson.
+ * Copyright (c) 2014 Michiel Broek.
+ ***********************************************************************
+ *
+ *    mbsePi is free software: you can redistribute it and/or modify
+ *    it under the terms of the GNU Lesser General Public License as published by
+ *    the Free Software Foundation, either version 3 of the License, or
+ *    (at your option) any later version.
+ *
+ *    mbsePi 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 Lesser General Public License for more details.
+ *
+ *    You should have received a copy of the GNU Lesser General Public License
+ *    along with wiringPi.  If not, see <http://www.gnu.org/licenses/>.
+ ***********************************************************************
+ */
+
+#include "mash.h"
+#include "lcd-pcf8574.h"
+
+
+#ifdef HAVE_WIRINGPI_H
+
+int			lcdHandle;
+unsigned char		lcdbuf[MAX_LCDS][20][4];
+
+struct lcdDataStruct
+{
+    int bits, rows, cols ;
+    int rsPin, strbPin ;
+    int dataPins [8] ;
+    int cx, cy ;
+};
+extern struct lcdDataStruct	*lcds [MAX_LCDS];
+
+
+/*
+ * Some LCD functions are extended shadow copies of the wiringPi functions.
+ * The difference is that the lcdbuf will be updated with the contents on
+ * the hardware display. This copy can then be used for a remote display
+ */
+
+
+/*
+ * setBacklight:
+ *********************************************************************************
+ */
+
+void setBacklight (int value)
+{
+  pinMode (AF_BACKLIGHT, OUTPUT) ;
+  digitalWrite (AF_BACKLIGHT, (value & 1)) ;
+}
+
+
+
+/*
+ * initLCD:
+ *********************************************************************************
+ */
+
+int initLCD (int cols, int rows)
+{
+    int	x, y;
+
+    if (!((rows == 1) || (rows == 2) || (rows == 4))) {
+    	fprintf (stderr, "rows must be 1, 2 or 4\n") ;
+    	return EXIT_FAILURE ;
+    }
+
+    if (!((cols == 16) || (cols == 20))) {
+    	fprintf (stderr, "cols must be 16 or 20\n") ;
+    	return EXIT_FAILURE ;
+    }
+
+    pcf8574Setup(AF_BASE, 0x27) ;
+    pinMode (AF_RW, OUTPUT) ;
+    digitalWrite (AF_RW, LOW) ;        // Not used with wiringPi - always in write mode
+
+    /*
+     * The other control pins are initialised with lcdInit ()
+     */
+    lcdHandle = lcdInit (rows, cols, 4, AF_RS, AF_E, AF_DB4, AF_DB5, AF_DB6, AF_DB7, 0, 0, 0, 0) ;
+    if (lcdHandle < 0) {
+    	fprintf (stderr, "lcdInit failed\n") ;
+    	return -1 ;
+    }
+
+    lcdClear (lcdHandle) ;
+    for (x = 0; x < 20; x++)
+	for (y = 0; y < 4; y++)
+	    lcdbuf[lcdHandle][x][y] = ' ';
+
+    setBacklight (1) ;
+
+    return 0 ;
+}
+
+
+
+void mb_lcdPutchar(const int fd, unsigned char data)
+{
+    struct lcdDataStruct *lcd = lcds[fd];
+
+    /*
+     * Write to our buffer first, then to the wiringPi driver.
+     * Writing to wiringPi updates the cursor position.
+     */
+    lcdbuf[fd][lcd->cx][lcd->cy] = data;
+    lcdPutchar(fd, data);
+}
+
+
+
+void mb_lcdPuts(const int fd, const char *string)
+{
+    while (*string)
+	mb_lcdPutchar (fd, *string++);
+}
+
+
+
+void mb_lcdClear(const int fd)
+{
+    int	x, y;
+
+    lcdClear(fd);
+    for (x = 0; x < 20; x++)
+	for (y = 0; y < 4; y++)
+	    lcdbuf[fd][x][y] = ' ';
+}
+
+
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/lcd-pcf8574.h	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,33 @@
+#ifndef	_LCD_PCF8574_H
+#define	_LCD_PCF8574_H
+
+
+#ifdef HAVE_WIRINGPI_H
+/* wiringPi */
+#include <wiringPi.h>
+#include <pcf8574.h>
+#include <lcd.h>
+
+
+// Defines for the pcf8574 Pi LCD interface board
+#define AF_BASE         100
+
+#define AF_RS           (AF_BASE + 0)
+#define AF_RW           (AF_BASE + 1)
+#define AF_E            (AF_BASE + 2)
+#define AF_BACKLIGHT    (AF_BASE + 3)
+#define AF_DB4          (AF_BASE + 4)
+#define AF_DB5          (AF_BASE + 5)
+#define AF_DB6          (AF_BASE + 6)
+#define AF_DB7          (AF_BASE + 7)
+
+
+void setBacklight (int);
+int  initLCD (int, int);
+void mb_lcdPutchar(const int, unsigned char);
+void mb_lcdPuts(const int, const char *);
+void mb_lcdClear(const int);
+
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/lock.c	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,134 @@
+/*****************************************************************************
+ * Copyright (C) 2014
+ *   
+ * 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 EC-65K; see the file COPYING.  If not, write to the Free
+ * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *****************************************************************************/
+
+#include "mash.h"
+#include "lock.h"
+
+
+/*
+ *  Put a lock on this program.
+ */
+int lockprog(char *name)
+{
+    char    *tempfile, *lockfile;
+    FILE    *fp;
+    pid_t   oldpid;
+
+    tempfile = calloc(PATH_MAX, sizeof(char));
+    lockfile = calloc(PATH_MAX, sizeof(char));
+
+    snprintf(tempfile, PATH_MAX, "/var/run/%s.tmp", name);
+    snprintf(lockfile, PATH_MAX, "/var/run/%s.pid", name);
+
+    if ((fp = fopen(tempfile, "w")) == NULL) {
+	perror(name);
+	printf("Can't create lockfile \"%s\"\n", tempfile);
+	free(tempfile);
+	free(lockfile);
+	return 1;
+    }
+    fprintf(fp, "%10u\n", getpid());
+    fclose(fp);
+
+    while (TRUE) {
+	if (link(tempfile, lockfile) == 0) {
+	    unlink(tempfile);
+	    free(tempfile);
+	    free(lockfile);
+	    return 0;
+	}
+	if ((fp = fopen(lockfile, "r")) == NULL) {
+	    perror(name);
+	    printf("Can't open lockfile \"%s\"\n", tempfile);
+	    unlink(tempfile);
+	    free(tempfile);
+	    free(lockfile);
+	    return 1;
+	}
+	if (fscanf(fp, "%u", &oldpid) != 1) {
+	    perror(name);
+	    printf("Can't read old pid from \"%s\"\n", tempfile);
+	    fclose(fp);
+	    unlink(tempfile);
+	    free(tempfile);
+	    free(lockfile);
+	    return 1;
+	}
+	fclose(fp);
+	if (kill(oldpid,0) == -1) {
+	    if (errno == ESRCH) {
+		printf("Stale lock found for pid %u\n", oldpid);
+		unlink(lockfile);
+		/* no return, try lock again */  
+	    } else {
+		perror(name);
+		printf("Kill for %u failed\n",oldpid);
+		unlink(tempfile);
+		free(tempfile);
+		free(lockfile);
+		return 1;
+	    }
+	} else {
+	    printf("Another %s is already running, pid=%u\n", name, oldpid);
+	    unlink(tempfile);
+	    free(tempfile);
+	    free(lockfile);
+	    return 1;
+	}
+    }
+}
+
+
+
+void ulockprog(char *name)
+{
+    char	    *lockfile;
+    pid_t	    oldpid;
+    FILE	    *fp;
+
+    lockfile = calloc(PATH_MAX, sizeof(char));
+    snprintf(lockfile, PATH_MAX, "/var/run/%s.pid", name);
+
+    if ((fp = fopen(lockfile, "r")) == NULL) {
+	syslog(LOG_NOTICE, "Can't open lockfile \"%s\"", lockfile);
+	free(lockfile);
+	return;
+    }
+
+    if (fscanf(fp, "%u", &oldpid) != 1) {
+	syslog(LOG_NOTICE, "Can't read old pid from \"%s\"", lockfile);
+	fclose(fp);
+	unlink(lockfile);
+	free(lockfile);
+	return;
+    }
+
+    fclose(fp);
+
+    if (oldpid == getpid()) {
+	(void)unlink(lockfile);
+    }
+
+    free(lockfile);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/lock.h	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,8 @@
+#ifndef	_LOCK_H
+#define	_LOCK_H
+
+
+int  lockprog(char *);
+void ulockprog(char *);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/logger.c	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,58 @@
+/*****************************************************************************
+ * Copyright (C) 2014
+ *   
+ * 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 EC-65K; see the file COPYING.  If not, write to the Free
+ * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *****************************************************************************/
+
+#include "mash.h"
+#include "xutil.h"
+#include "logger.h"
+
+
+void logger(char *filename, char *progname, char *data)
+{
+    struct timeval	now;
+    struct tm		ptm;
+    char		*outstr = NULL, *name = NULL;
+    FILE		*logfile;
+
+    name = xstrcpy((char *)"/var/local/log/");
+    name = xstrcat(name, progname);
+    name = xstrcat(name, (char *)"/");
+    name = xstrcat(name, filename);
+
+    gettimeofday(&now, NULL);
+    localtime_r(&now.tv_sec, &ptm);
+
+    if ((logfile = fopen(name, "a+"))) {
+	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);
+	fprintf(logfile, outstr);
+	fclose(logfile);
+	free(outstr);
+	outstr = NULL;
+    } else {
+	    syslog(LOG_NOTICE, "logger: cannot open %s for writing", name);
+    }
+
+    free(name);
+    name = NULL;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/logger.h	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,6 @@
+#ifndef	_LOGGER_H
+#define	_LOGGER_H
+
+void logger(char *, char *, char *);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/mash.c	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,330 @@
+/*****************************************************************************
+ * Copyright (C) 2014
+ *   
+ * 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 EC-65K; see the file COPYING.  If not, write to the Free
+ * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *****************************************************************************/
+
+#include "mash.h"
+#include "lock.h"
+#include "logger.h"
+#include "xutil.h"
+#include "beerxml.h"
+
+#ifdef HAVE_WIRINGPI_H
+
+
+int			tempA = 80;
+int			tempB = 80;
+int			coolerA = 0;
+int			coolerB = 0;
+
+bool			my_shutdown = false;
+static pid_t		pgrp, mypid;
+
+extern bool		debug;
+extern sys_config	Config;
+extern int		lcdHandle;
+extern unsigned char	lcdbuf[MAX_LCDS][20][4];
+int			lcdupdate;
+
+
+void help(void);
+void die(int);
+void stopLCD(void);
+int  server(void);
+
+
+void help(void)
+{
+    fprintf(stdout, "mbsePi-apps mash v%s starting\n\n", VERSION);
+    fprintf(stdout, "Usage: mash [-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) {
+	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;
+	default:	syslog(LOG_NOTICE, "die() on signal %d", onsig);
+    }
+
+    my_shutdown = true;
+}
+
+
+
+void stopLCD(void)
+{
+    mb_lcdClear(lcdHandle);
+    setBacklight(0);
+}
+
+
+
+int main(int argc, char *argv[])
+{
+    int		rc, c, i;
+    pid_t	frk;
+    char	buf[80];
+
+    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;
+	}
+    }
+
+
+    parseBeerXML((char *)"/home/mbroek/Downloads/SalmoGold.xml");
+
+    return 0;
+
+    openlog("mash", LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_USER);
+    syslog(LOG_NOTICE, "mbsePi-apps mash v%s starting", VERSION);
+    if (debug)
+	fprintf(stdout, "mbsePi-apps mash v%s starting\n", VERSION);
+
+    if (rdconfig((char *)"mash.conf")) {
+	fprintf(stderr, "Error reading configuration\n");
+	syslog(LOG_NOTICE, "halted");
+	return 1;
+    }
+
+    /*
+     *  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);
+    }
+
+    if (wiringPiSetup () )
+	return 1;
+
+    if ((rc = initLCD (16, 2))) {
+	fprintf(stderr, "Cannot initialize LCD display, rc=%d\n", rc);
+	return 1;
+    }
+
+    lcdPosition(lcdHandle, 0, 0);
+    sprintf(buf, "      Mash");
+    mb_lcdPuts(lcdHandle, buf);
+    lcdPosition(lcdHandle, 0, 1);
+    sprintf(buf, "  Version %s", VERSION);
+    mb_lcdPuts(lcdHandle, buf);
+
+    if (debug) {
+	/*
+	 * For debugging run in foreground.
+	 */
+	rc = server();
+    } else {
+	/*
+	 * Server initialization is complete. Now we can fork the 
+	 * daemon and return to the user. We need to do a setpgrp
+	 * so that the daemon will no longer be assosiated with the
+	 * users control terminal. This is done before the fork, so
+	 * that the child will not be a process group leader. Otherwise,
+	 * if the child were to open a terminal, it would become
+	 * associated with that terminal as its control terminal.
+	 */
+	if ((pgrp = setpgid(0, 0)) == -1) {
+	    syslog(LOG_NOTICE, "setpgpid failed");
+	}
+
+	frk = fork();
+	switch (frk) {
+	    case -1:	
+		    	syslog(LOG_NOTICE, "Daemon fork failed: %s", strerror(errno));
+			syslog(LOG_NOTICE, "Finished, rc=1");
+			stopLCD();
+			exit(1);
+	    case 0:	/*
+			 * Run the daemon
+			 */
+			fclose(stdin);
+			if (open("/dev/null", O_RDONLY) != 0) {
+			    syslog(LOG_NOTICE, "Reopen of stdin to /dev/null failed");
+			    _exit(2);
+			}
+			fclose(stdout);
+			if (open("/dev/null", O_WRONLY | O_APPEND | O_CREAT,0600) != 1) {
+			    syslog(LOG_NOTICE, "Reopen of stdout to /dev/null failed");
+			    _exit(2);
+			}
+			fclose(stderr);
+			if (open("/dev/null", O_WRONLY | O_APPEND | O_CREAT,0600) != 2) {
+			    syslog(LOG_NOTICE, "Reopen of stderr to /dev/null failed");
+			    _exit(2);
+			}
+			mypid = getpid();
+			rc = server();
+			break;
+			/* Not reached */
+	    default:
+			/*
+			 * Here we detach this process and let the child
+			 * run the deamon process.
+			 */
+			syslog(LOG_NOTICE, "Starting daemon with pid %d", frk);
+			exit(0);
+	}
+    }
+
+    syslog(LOG_NOTICE, "Finished, rc=%d", rc);
+    return rc;
+}
+
+
+
+int server(void)
+{
+    char                buf[1024];
+    w1_therm		*tmp1;
+    int			rc, run = 1, temp;
+
+    if (lockprog((char *)"mash")) {
+	syslog(LOG_NOTICE, "Can't lock");
+	return 1;
+    }
+
+    rc = piThreadCreate(my_sensors_loop);
+    if (rc) {
+	fprintf(stderr, "my_sensors_loop thread didn't start rc=%d\n", rc);
+	syslog(LOG_NOTICE, "my_sensors_loop thread didn't start rc=%d", rc);
+    }
+
+//    rc = piThreadCreate(my_server_loop);
+//    if (rc) {
+//	fprintf(stderr, "my_server_loop thread didn't start rc=%d\n", rc);
+//	syslog(LOG_NOTICE, "my_server_loop thread didn't start rc=%d", rc);
+//    }
+
+    snprintf(buf, 1023, "temp,step,description");
+    logger((char *)"mash.log", (char *)"mash", buf);
+
+    do {
+	lcdupdate = FALSE;
+
+	if (my_shutdown)
+	    run = 0;
+
+	tmp1 = Config.w1therms;
+//	if (((tmp1->lastval / 100) < (tempA - 5)) && (coolerA == 1)) {
+//	    syslog(LOG_NOTICE, "Temperature A is %.1f, switched cooler off", (tmp1->lastval / 1000.0));
+//	    lcdupdate = TRUE;
+//	}
+//	if (((tmp1->lastval / 100) > (tempA + 5)) && (coolerA == 0)) {
+//	    syslog(LOG_NOTICE, "Temperature A is %.1f, switched cooler on", (tmp1->lastval / 1000.0));
+//	    lcdupdate = TRUE;
+//	}
+	if (tmp1->update) {
+	    tmp1->update = FALSE;
+	    lcdupdate = TRUE;
+	}
+//	old1 = tmp1->next;
+//	tmp1 = old1;
+//	if (((tmp1->lastval / 100) < (tempB - 5)) && (coolerB == 1)) {
+//	    syslog(LOG_NOTICE, "Temperature B is %.1f, switched cooler off", (tmp1->lastval / 1000.0));
+//	    lcdupdate = TRUE;
+//	}
+//	if (((tmp1->lastval / 100) > (tempB + 5)) && (coolerB == 0)) {
+//	    syslog(LOG_NOTICE, "Temperature B is %.1f, switched cooler on", (tmp1->lastval / 1000.0));
+//	    lcdupdate = TRUE;
+//	}
+//	if (tmp1->update) {
+//	    tmp1->update = FALSE;
+//	    lcdupdate = TRUE;
+//	}
+
+	if (run && lcdupdate) {
+	    lcdPosition(lcdHandle, 0, 0);
+	    tmp1 = Config.w1therms;
+	    snprintf(buf, 16, "%4.1f %cC %s               ", tmp1->lastval / 1000.0, 0xdf, tmp1->alias);
+	    mb_lcdPuts(lcdHandle, buf);
+	    temp = tmp1->lastval;
+//	    old1 = tmp1->next;
+//	    tmp1 = old1;
+	    lcdPosition(lcdHandle, 0, 1);
+	    snprintf(buf, 16, "                    ");
+	    mb_lcdPuts(lcdHandle, buf);
+	    snprintf(buf, 1023, "%.1f,%d,%s", temp / 1000.0, 1, (char *)"step description");
+	    logger((char *)"mash.log", (char *)"mash", buf);
+	}
+	usleep(100000);
+
+    } while (run);
+
+    if (debug)
+	fprintf(stdout, (char *)"Out of loop\n");
+
+    /*
+     * Give threads time to cleanup
+     */
+    usleep(1500000);
+
+    stopLCD();
+
+//    wrconfig((char *)"mash.conf");
+
+    ulockprog((char *)"mash");
+
+    if (debug)
+	fprintf(stdout, "Goodbye\n");
+
+    return 0;
+}
+
+#else
+
+
+int main(int argc, char *argv[])
+{
+	parseBeerXML((char *)"/home/mbroek/Downloads/beerxml.xml");
+	printBeerXML();
+//    fprintf(stderr, "Compiled on a system without a wiringPi library.\n");
+//    fprintf(stderr, "This program is useless and will do nothing.\n");
+    return 0;
+}
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/mash.h	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,82 @@
+#ifndef	_MASH_H
+#define	_MASH_H
+
+
+#define TRUE 1
+#define FALSE 0
+
+#include "../config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <time.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <poll.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/parser.h>
+
+
+/* mosquitto */
+#include <mosquitto.h>
+
+#define MBSE_SS(x) (x)?(x):"(null)"
+
+
+/* rdconfig.c */
+typedef struct _key_list {
+    char		*key;
+    int			(*prc)(char **);
+    char		**dest;
+} key_list;
+
+typedef struct _w1_therm {
+    struct _w1_therm    *next;
+    char                *master;                /* Master for this device       */
+    int                 bus;                    /* Reserved for ds2482-800      */
+    char                *name;                  /* Name of this device          */
+    char                *alias;                 /* Friendly name                */
+    int                 present;                /* 1=present, 0=absent          */
+    int                 lastval;                /* Last valid value             */
+    int			update;			/* Value updated		*/
+} w1_therm;
+
+typedef struct _sys_config {
+    char		*name;			/* Configuration name		*/
+    int			my_port;		/* my client/server port	*/
+    w1_therm		*w1therms;		/* 1-wire temp sensors		*/
+    int			lcd_cols;		/* LCD display columns		*/
+    int			lcd_rows;		/* LCD display rows		*/
+} sys_config;
+
+
+void killconfig(void);
+int  rdconfig(char *);
+int  wrconfig(char *);
+
+
+
+#ifdef HAVE_WIRINGPI_H
+
+PI_THREAD (my_sensors_loop);
+
+#endif
+
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/rdconfig.c	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,368 @@
+/*****************************************************************************
+ * Copyright (C) 2014
+ *   
+ * 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 EC-65K; see the file COPYING.  If not, write to the Free
+ * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *****************************************************************************/
+
+#include "mash.h"
+#include "xutil.h"
+
+
+bool		debug = FALSE;
+static char	*mypath;
+static char	*k, *v;
+static int	linecnt = 0;
+sys_config	Config;			/* System configuration		*/
+
+
+
+//static int getstr(char **);
+static int getint(char **);
+static int getw1(char **);
+//static int getuch(char **);
+//static int getfloat(char **);
+//static int getbyt(char **);
+//static int gethex(char **);
+
+#define XSTR(x) #x
+#define STR(x) XSTR(x)
+
+/*
+ * System configuration table
+ */
+key_list keytab[] = {
+    {(char *)"w1therm",			getw1,		(char **)&Config.w1therms},
+    {(char *)"lcd_cols",		getint,		(char **)&Config.lcd_cols},
+    {(char *)"lcd_rows",		getint,		(char **)&Config.lcd_rows},
+    {NULL,				NULL,		NULL}
+};
+
+
+
+void killconfig(void)
+{
+    w1_therm	*tmp1, *old1;
+
+    if (Config.name)
+	free(Config.name);
+    Config.name = NULL;
+
+    for (tmp1 = Config.w1therms; tmp1; tmp1 = old1) {
+	old1 = tmp1->next;
+	if (tmp1->master)
+	    free(tmp1->master);
+	if (tmp1->name)
+	    free(tmp1->name);
+	if (tmp1->alias)
+	    free(tmp1->alias);
+	free(tmp1);
+    }
+    Config.w1therms = NULL;
+    Config.my_port = 6554;
+
+    Config.lcd_cols = 16;
+    Config.lcd_rows = 2;
+}
+
+
+
+int wrconfig(char *config)
+{
+    int		rc = 0;
+    FILE	*fp;
+    w1_therm    *tmp1;
+
+    if (getenv((char *)"USER") == NULL) {
+	mypath = xstrcpy((char *)"/root");
+    } else {
+    	mypath = xstrcpy(getenv((char *)"HOME"));
+    }
+    mypath = xstrcat(mypath, (char *)"/mbsepi-apps/");
+    mypath = xstrcat(mypath, config);
+
+    if (debug)
+	fprintf(stdout, "Writing %s\n", mypath);
+
+    if ((fp = fopen(mypath, "w")) == NULL) {
+	syslog(LOG_NOTICE, "could not rewrite %s", mypath);
+	return 1;
+    }
+
+    fprintf(fp, "# Configuration file for thermferm %s\n", VERSION);
+    fprintf(fp, "\n");
+
+    fprintf(fp, "# LCD display\n");
+    fprintf(fp, "#\n");
+    fprintf(fp, "lcd_cols	%d\n", Config.lcd_cols);
+    fprintf(fp, "lcd_rows	%d\n", Config.lcd_rows);
+    fprintf(fp, "\n");
+
+    fprintf(fp, "# DS18B20 temperature sensors on the 1-wire bus.\n");
+    fprintf(fp, "#\n");
+    fprintf(fp, "# kwd		master		bus	name		alias\n");
+    for (tmp1 = Config.w1therms; tmp1; tmp1 = tmp1->next) {
+	fprintf(fp, "w1therm		%s	%d	%s	%s\n", tmp1->master, tmp1->bus, tmp1->name, tmp1->alias);
+    }
+    fprintf(fp, "\n");
+
+    fprintf(fp, "# End of generated configuration\n");
+    fclose(fp);
+    syslog(LOG_NOTICE, "Written %s rc=%d", mypath, rc);
+    free(mypath);
+    mypath = NULL;
+
+    return rc;
+}
+
+
+
+int rdconfig(char *config) 
+{
+    char	buf[256], *p;
+    FILE	*fp;
+    int		i, rc = 0;
+
+    killconfig();
+
+    syslog(LOG_NOTICE, "HOME='%s' USER='%s' LOGNAME='%s'", MBSE_SS(getenv((char *)"HOME")), MBSE_SS(getenv((char *)"USER")), MBSE_SS(getenv((char *)"LOGNAME")));
+
+    /*
+     * Search config file
+     */
+    if (getenv((char *)"USER") == NULL) {
+	mypath = xstrcpy((char *)"/root");
+    } else {
+    	mypath = xstrcpy(getenv((char *)"HOME"));
+    }
+    mypath = xstrcat(mypath, (char *)"/mbsepi-apps/");
+    mypath = xstrcat(mypath, config);
+    if ((fp = fopen(mypath, "r")) == NULL) {
+	/*
+	 * Not in the users home directory
+	 */
+	free(mypath);
+	mypath = xstrcpy((char *)"/etc/mbsepi-apps/");
+	mypath = xstrcat(mypath, config);
+	if ((fp = fopen(mypath, "r")) == NULL) {
+	    /*
+	     * Try /usr/local/etc
+	     */
+	    free(mypath);
+	    mypath = xstrcpy((char *)"/usr/local/etc/mbsepi-apps/");
+	    mypath = xstrcat(mypath, config);
+	    if ((fp = fopen(mypath, "r")) == NULL) {
+		syslog(LOG_NOTICE, "rdconfig: could find %s", config);
+		return 1;
+	    }
+	}
+    }
+    syslog(LOG_NOTICE, "rdconfig: using %s", mypath);
+
+    linecnt = 0;
+    while (fgets(buf, sizeof(buf) -1, fp)) {
+	linecnt++;
+	if (*(p = buf + strlen(buf) -1) != '\n') {
+	    syslog(LOG_NOTICE, "rdconfig: %s(%d): \"%s\" - line too long", mypath, linecnt, buf);
+	    rc = 1;
+	    break;
+	}
+	*p-- = '\0';
+	while ((p >= buf) && isspace(*p))
+	    *p-- = '\0';
+	k = buf;
+	while (*k && isspace(*k))
+	    k++;
+	p = k;
+	while (*p && !isspace(*p))
+	    p++;
+	*p++='\0';
+	v = p;
+	while (*v && isspace(*v)) 
+	    v++;
+
+	if ((*k == '\0') || (*k == '#')) {
+	    continue;
+	}
+
+	for (i = 0; keytab[i].key; i++)
+	    if (strcasecmp(k,keytab[i].key) == 0)
+		break;
+
+	if (keytab[i].key == NULL) {
+	    syslog(LOG_NOTICE, "rdconfig: %s(%d): %s %s - unknown keyword", mypath, linecnt, MBSE_SS(k), MBSE_SS(v));
+	    rc = 1;
+	    break;
+	} else if ((keytab[i].prc(keytab[i].dest))) {
+	    rc = 1;
+	    break;
+	}
+
+    }
+    fclose(fp);
+
+    free(mypath);
+    mypath = NULL;
+
+    return rc;
+}
+
+
+/*
+static int getstr(char **dest)
+{
+    if (debug)
+	syslog(LOG_NOTICE, "rdconfig: getstr: %s(%d): %s %s", mypath, linecnt, MBSE_SS(k), MBSE_SS(v));
+
+    *dest = xstrcpy(v);
+    return 0;
+}
+*/
+
+
+static int getint(char **dest)
+{
+    if (debug)
+	syslog(LOG_NOTICE, "rdconfig: getint: %s(%d): %s %s", mypath, linecnt, MBSE_SS(k), MBSE_SS(v));
+
+    if (strspn(v,"0123456789") != strlen(v)) 
+	syslog(LOG_NOTICE, "rdconfig: %s(%d): %s %s - bad numeric", mypath, linecnt, MBSE_SS(k), MBSE_SS(v));
+    else 
+	*((int*)dest)=atoi(v);
+    return 0;
+}
+
+
+
+static int getw1(char **dest)
+{
+    char	*p, *q = NULL, *r = NULL;
+    w1_therm	**tmpm;
+    int		rc = 0, tmpp;
+
+    for (p = v; *p && !isspace(*p); p++);
+    if (*p)
+	*p++ = '\0';
+    while (*p && isspace(*p))
+	p++;
+    if (*p == '\0') {
+	syslog(LOG_NOTICE, "rdconfig: %s(%d): less then two tokens", mypath, linecnt);
+	return 1;
+    }
+
+    for (q = p; *q && !isspace(*q); q++);
+    if (*q && isspace(*q)) {
+	if (*q)
+	    *q++ = '\0';
+	while (*q && isspace(*q))
+	    q++;
+
+	for (r = q; *r && !isspace(*r); r++);
+	if (*r)
+	    *r++ = '\0';
+	rc = sscanf(p, "%d", &tmpp);
+	if (rc != 1) {
+	    syslog(LOG_NOTICE, "rdconfig: getw1: %s(%d): %s is not a integer value", mypath, linecnt, p);
+	    return 1;
+	}
+	if (debug)
+	    syslog(LOG_NOTICE, "rdconfig: getw1: %s(%d): %s %d %s %s", mypath, linecnt, v, tmpp, q, r);
+    }
+
+    for (tmpm = (w1_therm**)dest; *tmpm; tmpm=&((*tmpm)->next));
+    (*tmpm) = (w1_therm *) xmalloc(sizeof(w1_therm));
+    (*tmpm)->next = NULL;
+    (*tmpm)->master = xstrcpy(v);
+    (*tmpm)->bus = tmpp;
+    (*tmpm)->name = xstrcpy(q);
+    (*tmpm)->alias = xstrcpy(r);
+    (*tmpm)->present = 0;
+    (*tmpm)->lastval = 0;
+    (*tmpm)->update = 0;
+
+    return 0;
+}
+
+
+/*
+static int getuch(char **dest)
+{
+    if (debug)
+	syslog(LOG_NOTICE, "rdconfig: getuch: %s(%d): %s %s", mypath, linecnt, MBSE_SS(k), MBSE_SS(v));
+
+    if (isalnum(v[0])) {
+	*((unsigned char*)dest) = v[0];
+    } else {
+	syslog(LOG_NOTICE, "rdconfig: %s(%d): %s %s - bad character", mypath, linecnt, MBSE_SS(k), MBSE_SS(v));
+    }
+    return 0;
+}
+
+
+
+static int getfloat(char **dest)
+{
+    float	val = 0.0;
+    int		rc;
+
+    if (debug)
+	syslog(LOG_NOTICE, "rdconfig: getfloat: %s(%d): %s %s", mypath, linecnt, MBSE_SS(k), MBSE_SS(v));
+
+    rc = sscanf(v, "%f", &val);
+    if (rc != 1) {
+	syslog(LOG_NOTICE, "rdconfig: %s(%d): %s %s - bad float value", mypath, linecnt, MBSE_SS(k), MBSE_SS(v));
+	return 1;
+    }
+    *((float*)dest) = val;
+
+    return 0;
+}
+*/
+
+/*
+static int getbyt(char **dest)
+{
+    Log_Msg("[rdconfig] getbyt: %s(%d): %s %s", mypath, linecnt, k, v);
+    if (strspn(v,"0123456789") != strlen(v))
+	Log_Msg("[rdconfig] %s(%d): %s %s - bad numeric", mypath, linecnt, S(k), S(v));
+    else
+	*((Uint8*)dest)=atoi(v);
+    return 0;
+}
+*/
+
+
+/*
+static int gethex(char **dest)
+{
+    unsigned int    val = 0;
+    int		    rc;
+
+    Log_Msg("[rdconfig] gethex: %s(%d): %s %s", mypath, linecnt, k, v);
+    rc = sscanf(v, "%08x", &val);
+    if (rc != 1) {
+	Log_Msg("[rdconfig] %s(%d): %s %s - bad hex value", mypath, linecnt, S(k), S(v));
+	return 1;
+    }
+    *((int*)dest) = val;
+
+    return 0;
+}
+*/
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/sensors.c	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,134 @@
+/*****************************************************************************
+ * Copyright (C) 2014
+ *   
+ * 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 EC-65K; see the file COPYING.  If not, write to the Free
+ * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *****************************************************************************/
+
+#include "mash.h"
+
+#ifdef HAVE_WIRINGPI_H
+
+
+extern bool		debug;
+extern sys_config	Config;
+extern int		my_shutdown;
+
+
+
+PI_THREAD (my_sensors_loop)
+{
+    w1_therm		*tmp1, *old1;
+    char		*device, line[60], *p = NULL;
+    FILE		*fp;
+    int			temp, rc, deviation;
+
+    syslog(LOG_NOTICE, "Thread my_sensors_loop started");
+    if (debug)
+	fprintf(stdout, "Thread my_sensors_loop started\n");
+
+    /*
+     * Loop forever until the external shutdown variable is set.
+     */
+    for (;;) {
+    	/*
+    	 * Here send our 1-wire sensors values
+    	 */
+    	for (tmp1 = Config.w1therms; tmp1; tmp1 = old1) {
+	    old1 = tmp1->next;
+
+	    if (my_shutdown) {
+		syslog(LOG_NOTICE, "Thread my_sensors_loop stopped");
+		if (debug)
+		    fprintf(stdout, "Thread my_sensors_loop stopped\n");
+	    	return 0;
+	    }
+
+	    /*
+	     * Build path to the on-wire sensor
+	     */
+	    device = xstrcpy((char *)"/sys/bus/w1/devices/");
+	    device = xstrcat(device, tmp1->master);
+	    device = xstrcat(device, (char *)"/");
+	    device = xstrcat(device, tmp1->name);
+	    device = xstrcat(device, (char *)"/w1_slave");
+
+	    /*
+	     * Read sensor data
+	     */
+	    if ((fp = fopen(device, "r"))) {
+		/*
+		 * The output looks like:
+		 * 72 01 4b 46 7f ff 0e 10 57 : crc=57 YES
+		 * 72 01 4b 46 7f ff 0e 10 57 t=23125
+		 */
+		fgets(line, 50, fp);
+		line[strlen(line)-1] = '\0';
+		if ((line[36] == 'Y') && (line[37] == 'E')) {
+		    /*
+		     * CRC is Ok, continue
+		     */
+		    fgets(line, 50, fp);
+		    line[strlen(line)-1] = '\0';
+		    strtok(line, (char *)"=");
+		    p = strtok(NULL, (char *)"=");
+		    rc = sscanf(p, "%d", &temp);
+		    if ((rc == 1) && (tmp1->lastval != temp)) {
+			/*
+			 * It is possible to have read errors or extreme values.
+			 * This can happen with bad connections so we compare the
+			 * value with the previous one. If the difference is too
+			 * much, we don't send that value. That also means that if
+			 * the next value is ok again, it will be marked invalid too.
+			 * Maximum error is 20 degrees for now.
+			 */
+			deviation = 20000;
+			if ( (tmp1->lastval == 0) ||
+			     (tmp1->lastval && (temp > (tmp1->lastval - deviation)) && (temp < (tmp1->lastval + deviation))) ) {
+			    /*
+			     * Temperature is changed and valid, set flag.
+			     */
+			    tmp1->update = TRUE;
+			} else {
+			    syslog(LOG_NOTICE, "deviation error deviation=%d, old=%d new=%d", deviation, tmp1->lastval, temp);
+			    if (debug) {
+				fprintf(stdout, "deviation error deviation=%d, old=%d new=%d\n", deviation, tmp1->lastval, temp);
+			    }
+			}
+			tmp1->lastval = temp;
+		    }
+		} else {
+		    syslog(LOG_NOTICE, "sensor %s/%s CRC error", tmp1->master, tmp1->name);
+		}
+		fclose(fp);
+		tmp1->present = 1;
+	    } else {
+		tmp1->present = 0;
+		if (debug)
+		    printf("sensor %s is missing\n", tmp1->name);
+	    }
+
+	    free(device);
+	    device = NULL;
+    	}
+    }
+}
+
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/xutil.c	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,73 @@
+/*****************************************************************************
+ * Copyright (C) 2008-2014
+ *   
+ * 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 EC-65K; see the file COPYING.  If not, write to the Free
+ * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *****************************************************************************/
+
+#include "mash.h"
+#include "xutil.h"
+
+
+char *xmalloc(size_t size)
+{
+    char *tmp;
+
+    tmp = malloc(size);
+    if (!tmp) 
+	abort();
+			            
+    return tmp;
+}
+
+
+
+char *xstrcpy(char *src)
+{
+    char    *tmp;
+
+    if (src == NULL) 
+	return(NULL);
+    tmp = xmalloc(strlen(src)+1);
+    strcpy(tmp, src);
+    return tmp;
+}
+
+
+
+char *xstrcat(char *src, char *add)
+{
+    char    *tmp;
+    size_t  size = 0;
+
+    if ((add == NULL) || (strlen(add) == 0))
+	return src;
+    if (src)
+	size = strlen(src);
+    size += strlen(add);
+    tmp = xmalloc(size + 1);
+    *tmp = '\0';
+    if (src) {
+	strcpy(tmp, src);
+	free(src);
+    }
+    strcat(tmp, add);
+    return tmp;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/xutil.h	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,8 @@
+#ifndef	_XUTIL_H
+#define	_XUTIL_H
+
+char *xmalloc(size_t);
+char *xstrcpy(char *);
+char *xstrcat(char *, char *);
+
+#endif

mercurial