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