Sun, 27 Dec 2015 17:52:26 +0100
Renamed Mash-in step to Prepare on the display. Don't run the pump when the mash is added. When preparing the mash, first heat the HLT, and then the MLT so that both have the chance to reach their target temperatures.
/***************************************************************************** * Copyright (C) 2015 * * Michiel Broek <mbroek at mbse dot eu> * * This file is part of the mbsePi-apps * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * mbsePi-apps is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ThermFerm; see the file COPYING. If not, write to the Free * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *****************************************************************************/ #include "brewco.h" #include "rdrecipes.h" #include "util.h" #include "xutil.h" extern int debug; extern char *etcpath; a_recipe *recipes = NULL; const char SKIPYN[2][4] = { "NO", "YES" }; #define MY_ENCODING "utf-8" int do_wrrecipes(void); int do_wrrecipes(void) { int i, rc = 0; FILE *fp; char *mypath = NULL; a_recipe *recipe; xmlTextWriterPtr writer; xmlBufferPtr buf; /* * Create a new XML buffer, to which the XML document will be written */ if ((buf = xmlBufferCreate()) == NULL) { syslog(LOG_NOTICE, "wrrecipes: error creating the xml buffer"); return 1; } /* * Create a new XmlWriter for memory, with no compression. */ if ((writer = xmlNewTextWriterMemory(buf, 0)) == NULL) { syslog(LOG_NOTICE, "wrrecipes: error creating the xml writer"); return 1; } /* * Use indentation instead of one long line */ if ((rc = xmlTextWriterSetIndent(writer, 2)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error setting Indent"); return 1; } /* * Start the document with the xml default for the version, * encoding ISO 8859-1 and the default for the standalone * declaration. */ if ((rc = xmlTextWriterStartDocument(writer, NULL, MY_ENCODING, NULL)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartDocument"); return 1; } /* * Start an element named "BREWCO". Since thist is the first * element, this will be the root element of the document. */ if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "BREWCO")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement"); return 1; } if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "RECIPES")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement"); return 1; } for (recipe = recipes; recipe; recipe = recipe->next) { if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "RECIPE")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "UUID", "%s", recipe->uuid)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "CODE", "%s", recipe->code)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "NAME", "%s", recipe->name)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "BOILTIME", "%d", recipe->boiltime)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "COOLTO", "%f", recipe->coolto)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "STARTTIME", "%d", (int)recipe->starttime)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "ENDTIME", "%d", (int)recipe->endtime)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } /* * 8 Mash steps */ if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "MASHSTEPS")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement"); return 1; } for (i =0; i < 8; i++) { if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "MASHSTEP")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "NAME", "%s", recipe->mash[i].name)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "MIN", "%d", recipe->mash[i].min)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "MAX", "%d", recipe->mash[i].max)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "CANSKIP", "%s", recipe->mash[i].canskip ? "YES":"NO")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "SETPOINT", "%f", recipe->mash[i].setpoint)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "SKIP", "%s", recipe->mash[i].skip ? "YES":"NO")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "DURATION", "%d", recipe->mash[i].duration)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterEndElement(writer)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement"); return 1; } } if ((rc = xmlTextWriterEndElement(writer)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement"); return 1; } /* * 10 Hop additions */ if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "HOPADDITIONS")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement"); return 1; } for (i = 0; i < 10; i++) { if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "HOPADDITION")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "NAME", "%s", recipe->hops[i].name)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "SKIP", "%s", recipe->hops[i].skip ? "YES":"NO")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "BOILTIME", "%d", recipe->hops[i].boiltime)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterEndElement(writer)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement"); return 1; } } if ((rc = xmlTextWriterEndElement(writer)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement"); return 1; } /* * 3 Hop stands */ if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "HOPSTANDS")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement"); return 1; } for (i = 0; i < 3; i++) { if ((rc = xmlTextWriterStartElement(writer, BAD_CAST "HOPSTAND")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterStartElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "NAME", "%s", recipe->hopstand[i].name)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "MIN", "%d", recipe->hopstand[i].min)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "MAX", "%d", recipe->hopstand[i].max)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "HOLD", "%s", recipe->hopstand[i].hold ? "YES":"NO")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "SETPOINT", "%f", recipe->hopstand[i].setpoint)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "SKIP", "%s", recipe->hopstand[i].skip ? "YES":"NO")) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterWriteFormatElement(writer, BAD_CAST "DURATION", "%d", recipe->hopstand[i].duration)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterWriteFormatElement"); return 1; } if ((rc = xmlTextWriterEndElement(writer)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement"); return 1; } } if ((rc = xmlTextWriterEndElement(writer)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement"); return 1; } /* * Close the element named RECIPE. */ if ((rc = xmlTextWriterEndElement(writer)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement"); return 1; } } /* * Close the element named RECIPES. */ if ((rc = xmlTextWriterEndElement(writer)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndElement"); return 1; } /* * All done, close any open elements */ if ((rc = xmlTextWriterEndDocument(writer)) < 0) { syslog(LOG_NOTICE, "wrrecipes: error at xmlTextWriterEndDocument"); return 1; } xmlFreeTextWriter(writer); /* * Now write the XML recipes */ mypath = xstrcpy(etcpath); mypath = xstrcat(mypath, (char *)"recipes.xml"); if (debug) fprintf(stdout, "Writing %s\n", mypath); if ((fp = fopen(mypath, "w")) == NULL) { syslog(LOG_NOTICE, "could not rewrite %s", mypath); free(mypath); return 1; } free(mypath); fprintf(fp, "%s", (const char *) buf->content); fclose(fp); xmlBufferFree(buf); return 0; } int wrrecipes(void) { int rc; rc = do_wrrecipes(); syslog(LOG_NOTICE, "Rewritten recipes, rc=%d", rc); return rc; } int parseRecipe(xmlDocPtr doc, xmlNodePtr cur) { xmlChar *key; xmlNodePtr s1cur, s2cur; a_recipe *recipe, *tmp; int i, j, ival; float fval; recipe = (a_recipe *)malloc(sizeof(a_recipe)); memset(recipe, 0, sizeof(a_recipe)); recipe->next = NULL; recipe->coolto = 20.0; cur = cur->xmlChildrenNode; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar *)"UUID"))) { recipe->uuid = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); } if ((!xmlStrcmp(cur->name, (const xmlChar *)"CODE"))) { recipe->code = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); } if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) { recipe->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); } if ((!xmlStrcmp(cur->name, (const xmlChar *)"BOILTIME"))) { key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%d", &ival) == 1) recipe->boiltime = ival; xmlFree(key); } if ((!xmlStrcmp(cur->name, (const xmlChar *)"COOLTO"))) { key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%f", &fval) == 1) recipe->coolto = fval; xmlFree(key); } if ((!xmlStrcmp(cur->name, (const xmlChar *)"STARTTIME"))) { key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%d", &ival) == 1) recipe->starttime = (time_t)ival; xmlFree(key); } if ((!xmlStrcmp(cur->name, (const xmlChar *)"ENDTIME"))) { key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%d", &ival) == 1) recipe->endtime = (time_t)ival; xmlFree(key); } if ((!xmlStrcmp(cur->name, (const xmlChar *)"MASHSTEPS"))) { s1cur = cur->xmlChildrenNode; i = 0; while (s1cur != NULL) { if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"MASHSTEP"))) { s2cur = s1cur->xmlChildrenNode; while (s2cur != NULL) { if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"NAME"))) { recipe->mash[i].name = (char *)xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"MIN"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%d", &ival) == 1) recipe->mash[i].min = ival; xmlFree(key); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"MAX"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%d", &ival) == 1) recipe->mash[i].max = ival; xmlFree(key); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"CANSKIP"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); for (j = 0; j < 2; j++) { if (! xmlStrcmp(key, (const xmlChar *)SKIPYN[j])) { recipe->mash[i].canskip = j; break; } } xmlFree(key); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"SETPOINT"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%f", &fval) == 1) recipe->mash[i].setpoint = fval; xmlFree(key); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"SKIP"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); for (j = 0; j < 2; j++) { if (! xmlStrcmp(key, (const xmlChar *)SKIPYN[j])) { recipe->mash[i].skip = j; break; } } xmlFree(key); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"DURATION"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%d", &ival) == 1) recipe->mash[i].duration = ival; xmlFree(key); } s2cur = s2cur->next; } i++; } s1cur = s1cur->next; } } if ((!xmlStrcmp(cur->name, (const xmlChar *)"HOPADDITIONS"))) { s1cur = cur->xmlChildrenNode; i = 0; while (s1cur != NULL) { if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"HOPADDITION"))) { s2cur = s1cur->xmlChildrenNode; while (s2cur != NULL) { if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"NAME"))) { recipe->hops[i].name = (char *)xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"SKIP"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); for (j = 0; j < 2; j++) { if (! xmlStrcmp(key, (const xmlChar *)SKIPYN[j])) { recipe->hops[i].skip = j; break; } } xmlFree(key); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"BOILTIME"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%d", &ival) == 1) recipe->hops[i].boiltime = ival; xmlFree(key); } s2cur = s2cur->next; } i++; } s1cur = s1cur->next; } } if ((!xmlStrcmp(cur->name, (const xmlChar *)"HOPSTANDS"))) { s1cur = cur->xmlChildrenNode; i = 0; while (s1cur != NULL) { if ((!xmlStrcmp(s1cur->name, (const xmlChar *)"HOPSTAND"))) { s2cur = s1cur->xmlChildrenNode; while (s2cur != NULL) { if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"NAME"))) { recipe->hopstand[i].name = (char *)xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"MIN"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%d", &ival) == 1) recipe->hopstand[i].min = ival; xmlFree(key); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"MAX"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%d", &ival) == 1) recipe->hopstand[i].max = ival; xmlFree(key); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"HOLD"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); for (j = 0; j < 2; j++) { if (! xmlStrcmp(key, (const xmlChar *)SKIPYN[j])) { recipe->hopstand[i].hold = j; break; } } xmlFree(key); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"SETPOINT"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%f", &fval) == 1) recipe->hopstand[i].setpoint = fval; xmlFree(key); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"SKIP"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); for (j = 0; j < 2; j++) { if (! xmlStrcmp(key, (const xmlChar *)SKIPYN[j])) { recipe->hopstand[i].skip = j; break; } } xmlFree(key); } if ((!xmlStrcmp(s2cur->name, (const xmlChar *)"DURATION"))) { key = xmlNodeListGetString(doc, s2cur->xmlChildrenNode, 1); if (sscanf((const char *)key, "%d", &ival) == 1) recipe->hopstand[i].duration = ival; xmlFree(key); } s2cur = s2cur->next; } i++; } s1cur = s1cur->next; } } cur = cur->next; } if (recipes == NULL) { recipes = recipe; } else { for (tmp = recipes; tmp; tmp = tmp->next) { if (tmp->next == NULL) { tmp->next = recipe; break; } } } return 0; } int parseRecipes(xmlDocPtr doc, xmlNodePtr cur) { cur = cur->xmlChildrenNode; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar *)"RECIPE"))) { parseRecipe(doc, cur); } cur = cur->next; } return 0; } /* * Returns: * 0 - All is well, recipes loaded. * 1 - Something went wrong */ int rdrecipes(void) { int i; char *mypath; xmlDocPtr doc; xmlNodePtr cur; a_recipe *recipe; /* * Search config file */ mypath = xstrcpy(etcpath); mypath = xstrcat(mypath, (char *)"recipes.xml"); /* * Free possible old recipes list */ for (recipe = recipes; recipe; recipe = recipe->next) { if (recipe->uuid) free(recipe->uuid); if (recipe->code) free(recipe->code); if (recipe->name) free(recipe->name); for (i = 0; i < 8; i++) if (recipe->mash[i].name) free(recipe->mash[i].name); for (i = 0; i < 10; i++) if (recipe->hops[i].name) free(recipe->hops[i].name); for (i = 0; i < 3; i++) if (recipe->hopstand[i].name) free(recipe->hopstand[i].name); free(recipe); } recipes = NULL; /* * See if we have a recipes file. */ if (file_exist(mypath, W_OK)) { syslog(LOG_NOTICE, "rdrecipes: %s not found, good.", mypath); free(mypath); return 0; } if ((doc = xmlParseFile(mypath)) == NULL) { syslog(LOG_NOTICE, "rdrecipes: %s not found, we may need some.", mypath); free(mypath); return 0; } syslog(LOG_NOTICE, "rdrecipes: using %s", mypath); if ((cur = xmlDocGetRootElement(doc)) == NULL) { syslog(LOG_NOTICE, "XML file %s empty.", mypath); xmlFreeDoc(doc); return 1; } if (xmlStrcmp(cur->name, (const xmlChar*)"BREWCO")) { syslog(LOG_NOTICE, "XML file %s is not a valid configuration file.", mypath); xmlFreeDoc(doc); return 1; } /* * Parse recipes */ cur = cur->xmlChildrenNode; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar *)"RECIPES"))) { parseRecipes(doc, cur); } cur = cur->next; } xmlFreeDoc(doc); free(mypath); mypath = NULL; return 0; }