2
* equipment.cpp is part of Brewtarget, and is Copyright Philip G. Lee
3
* (rocketman768@gmail.com), 2009.
5
* Brewtarget is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation, either version 3 of the License, or
8
* (at your option) any later version.
10
* Brewtarget is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program. If not, see <http://www.gnu.org/licenses/>.
23
#include "stringparsing.h"
24
#include "equipment.h"
25
#include "brewtarget.h"
26
#include <QDomElement>
29
bool operator<(Equipment &e1, Equipment &e2)
31
return e1.name < e2.name;
34
bool operator==(Equipment &e1, Equipment &e2)
36
return e1.name == e2.name;
39
void Equipment::toXml(QDomDocument& doc, QDomNode& parent)
41
QDomElement equipNode;
45
equipNode = doc.createElement("EQUIPMENT");
47
tmpNode = doc.createElement("NAME");
48
tmpText = doc.createTextNode(name.c_str());
49
tmpNode.appendChild(tmpText);
50
equipNode.appendChild(tmpNode);
52
tmpNode = doc.createElement("VERSION");
53
tmpText = doc.createTextNode(text(version));
54
tmpNode.appendChild(tmpText);
55
equipNode.appendChild(tmpNode);
57
tmpNode = doc.createElement("BOIL_SIZE");
58
tmpText = doc.createTextNode(text(boilSize_l));
59
tmpNode.appendChild(tmpText);
60
equipNode.appendChild(tmpNode);
62
tmpNode = doc.createElement("BATCH_SIZE");
63
tmpText = doc.createTextNode(text(batchSize_l));
64
tmpNode.appendChild(tmpText);
65
equipNode.appendChild(tmpNode);
67
tmpNode = doc.createElement("TUN_VOLUME");
68
tmpText = doc.createTextNode(text(tunVolume_l));
69
tmpNode.appendChild(tmpText);
70
equipNode.appendChild(tmpNode);
72
tmpNode = doc.createElement("TUN_WEIGHT");
73
tmpText = doc.createTextNode(text(tunWeight_kg));
74
tmpNode.appendChild(tmpText);
75
equipNode.appendChild(tmpNode);
77
tmpNode = doc.createElement("TUN_SPECIFIC_HEAT");
78
tmpText = doc.createTextNode(text(tunSpecificHeat_calGC));
79
tmpNode.appendChild(tmpText);
80
equipNode.appendChild(tmpNode);
82
tmpNode = doc.createElement("TOP_UP_WATER");
83
tmpText = doc.createTextNode(text(topUpWater_l));
84
tmpNode.appendChild(tmpText);
85
equipNode.appendChild(tmpNode);
87
tmpNode = doc.createElement("TRUB_CHILLER_LOSS");
88
tmpText = doc.createTextNode(text(trubChillerLoss_l));
89
tmpNode.appendChild(tmpText);
90
equipNode.appendChild(tmpNode);
92
tmpNode = doc.createElement("EVAP_RATE");
93
tmpText = doc.createTextNode(text(evapRate_pctHr));
94
tmpNode.appendChild(tmpText);
95
equipNode.appendChild(tmpNode);
97
tmpNode = doc.createElement("BOIL_TIME");
98
tmpText = doc.createTextNode(text(boilTime_min));
99
tmpNode.appendChild(tmpText);
100
equipNode.appendChild(tmpNode);
102
tmpNode = doc.createElement("CALC_BOIL_VOLUME");
103
tmpText = doc.createTextNode(text(calcBoilVolume));
104
tmpNode.appendChild(tmpText);
105
equipNode.appendChild(tmpNode);
107
tmpNode = doc.createElement("LAUTER_DEADSPACE");
108
tmpText = doc.createTextNode(text(lauterDeadspace_l));
109
tmpNode.appendChild(tmpText);
110
equipNode.appendChild(tmpNode);
112
tmpNode = doc.createElement("TOP_UP_KETTLE");
113
tmpText = doc.createTextNode(text(topUpKettle_l));
114
tmpNode.appendChild(tmpText);
115
equipNode.appendChild(tmpNode);
117
tmpNode = doc.createElement("HOP_UTILIZATION");
118
tmpText = doc.createTextNode(text(hopUtilization_pct));
119
tmpNode.appendChild(tmpText);
120
equipNode.appendChild(tmpNode);
122
tmpNode = doc.createElement("NOTES");
123
tmpText = doc.createTextNode(notes.c_str());
124
tmpNode.appendChild(tmpText);
125
equipNode.appendChild(tmpNode);
127
parent.appendChild(equipNode);
130
//=============================CONSTRUCTORS=====================================
132
void Equipment::setDefaults()
139
tunSpecificHeat_calGC = 0.0;
141
trubChillerLoss_l = 0.0;
142
evapRate_pctHr = 0.0;
144
calcBoilVolume = false;
145
lauterDeadspace_l = 0.0;
147
hopUtilization_pct = 0.0;
151
Equipment::Equipment()
156
Equipment::Equipment(XmlNode *node)
158
std::vector<XmlNode *> children;
159
std::vector<XmlNode *> tmpVec;
161
std::string leafText;
163
unsigned int i, childrenSize;
164
bool hasName=false, hasVersion=false, hasBoilSize=false, hasBatchSize=false;
168
if( node->getTag() != "EQUIPMENT" )
169
throw EquipmentException("initializer not passed an EQUIPMENT node.");
171
node->getChildren( children );
172
childrenSize = children.size();
174
for( i = 0; i < childrenSize; ++i )
176
tag = children[i]->getTag();
177
children[i]->getChildren( tmpVec );
179
// All valid children of EQUIPMENT only have one or fewer children.
180
if( tmpVec.size() > 1 )
181
throw EquipmentException("Tag \""+tag+"\" has more than one child.");
184
// It must be a leaf if it is a valid BeerXML entry.
185
if( ! leaf->isLeaf() )
186
throw EquipmentException("Should have been a leaf but is not.");
188
leafText = leaf->getLeafText();
190
// TODO: in all elements, make sure that an exception thrown by a parse
191
// method gets handled when the element is not required.
197
else if( tag == "VERSION" )
199
if( parseInt(leafText) != version )
200
std::cerr << "Warning: XML EQUIPMENT version is not " << version << std::endl;
203
else if( tag == "BOIL_SIZE" )
205
setBoilSize_l(parseDouble(leafText));
208
else if( tag == "BATCH_SIZE" )
210
setBatchSize_l(parseDouble(leafText));
213
else if( tag == "TUN_VOLUME" )
214
setTunVolume_l(parseDouble(leafText));
215
else if( tag == "TUN_WEIGHT" )
216
setTunWeight_kg(parseDouble(leafText));
217
else if( tag == "TUN_SPECIFIC_HEAT" )
218
setTunSpecificHeat_calGC(parseDouble(leafText));
219
else if( tag == "TOP_UP_WATER" )
220
setTopUpWater_l(parseDouble(leafText));
221
else if( tag == "TRUB_CHILLER_LOSS" )
222
setTrubChillerLoss_l(parseDouble(leafText));
223
else if( tag == "EVAP_RATE" )
224
setEvapRate_pctHr(parseDouble(leafText));
225
else if( tag == "BOIL_TIME" )
226
setBoilTime_min(parseDouble(leafText));
227
else if( tag == "CALC_BOIL_VOLUME" )
228
setCalcBoilVolume(parseBool(leafText));
229
else if( tag == "LAUTER_DEADSPACE" )
230
setLauterDeadspace_l(parseDouble(leafText));
231
else if( tag == "TOP_UP_KETTLE" )
232
setTopUpKettle_l(parseDouble(leafText));
233
else if( tag == "HOP_UTILIZATION" )
234
setHopUtilization_pct(parseDouble(leafText));
235
else if( tag == "NOTES" )
238
std::cerr << "Warning: Unsupported EQUIPMENT tag: " << tag << std::endl;
241
if( !hasName || !hasVersion || !hasBoilSize || !hasBatchSize )
242
throw EquipmentException("missing required tag.");
245
Equipment::Equipment(const QDomNode& equipmentNode)
247
QDomNode node, child;
249
QString property, value;
253
for( node = equipmentNode.firstChild(); ! node.isNull(); node = node.nextSibling() )
255
if( ! node.isElement() )
257
Brewtarget::log(Brewtarget::WARNING, QString("Node at line %1 is not an element.").arg(textNode.lineNumber()) );
261
child = node.firstChild();
262
if( child.isNull() || ! child.isText() )
265
property = node.nodeName();
266
textNode = child.toText();
267
value = textNode.nodeValue();
269
if( property == "NAME" )
271
name = value.toStdString();
273
else if( property == "VERSION" )
275
if( version != getInt(textNode) )
276
Brewtarget::log(Brewtarget::ERROR, QString("EQUIPMENT says it is not version %1. Line %2").arg(version).arg(textNode.lineNumber()) );
278
else if( property == "BOIL_SIZE" )
280
setBoilSize_l(getDouble(textNode));
282
else if( property == "BATCH_SIZE" )
284
setBatchSize_l(getDouble(textNode));
286
else if( property == "TUN_VOLUME" )
288
setTunVolume_l(getDouble(textNode));
290
else if( property == "TUN_WEIGHT" )
292
setTunWeight_kg(getDouble(textNode));
294
else if( property == "TUN_SPECIFIC_HEAT" )
296
setTunSpecificHeat_calGC(getDouble(textNode));
298
else if( property == "TOP_UP_WATER" )
300
setTopUpWater_l(getDouble(textNode));
302
else if( property == "TRUB_CHILLER_LOSS" )
304
setTrubChillerLoss_l(getDouble(textNode));
306
else if( property == "EVAP_RATE" )
308
setEvapRate_pctHr(getDouble(textNode));
310
else if( property == "BOIL_TIME" )
312
setBoilTime_min(getDouble(textNode));
314
else if( property == "CALC_BOIL_VOLUME" )
316
setCalcBoilVolume(getBool(textNode));
318
else if( property == "LAUTER_DEADSPACE" )
320
setLauterDeadspace_l(getDouble(textNode));
322
else if( property == "TOP_UP_KETTLE" )
324
setTopUpKettle_l(getDouble(textNode));
326
else if( property == "HOP_UTILIZATION" )
328
setHopUtilization_pct(getDouble(textNode));
330
else if( property == "NOTES" )
332
setNotes(value.toStdString());
335
Brewtarget::log(Brewtarget::WARNING, QString("Unsupported EQUIPMENT property: %1. Line %2").arg(property).arg(node.lineNumber()) );
339
//============================"SET" METHODS=====================================
341
void Equipment::setName( const std::string &var )
343
name = std::string(var);
347
void Equipment::setBoilSize_l( double var )
350
throw EquipmentException("boil size cannot be negative: " + doubleToString(var) );
358
void Equipment::setBatchSize_l( double var )
361
throw EquipmentException( "batch size cannot be negative: " + doubleToString(var) );
370
void Equipment::setTunVolume_l( double var )
373
throw EquipmentException( "tun volume cannot be negative: " + doubleToString(var) );
381
void Equipment::setTunWeight_kg( double var )
384
throw EquipmentException( "tun weight cannot be negative: " + doubleToString(var) );
392
void Equipment::setTunSpecificHeat_calGC( double var )
395
throw EquipmentException( "tun specific heat cannot be negative: " + doubleToString(var) );
398
tunSpecificHeat_calGC = var;
403
void Equipment::setTopUpWater_l( double var )
406
throw EquipmentException( "top up water cannot be negative: " + doubleToString(var) );
415
void Equipment::setTrubChillerLoss_l( double var )
418
throw EquipmentException( "trub chiller loss cannot be negative: " + doubleToString(var) );
421
trubChillerLoss_l = var;
427
void Equipment::setEvapRate_pctHr( double var )
429
if( var < 0.0 || var > 100.0)
430
throw EquipmentException( "evap rate must be a percent: " + doubleToString(var) );
433
evapRate_pctHr = var;
439
void Equipment::setBoilTime_min( double var )
442
throw EquipmentException( "boil time cannot be negative: " + doubleToString(var) );
451
void Equipment::setCalcBoilVolume( bool var )
453
calcBoilVolume = var;
459
void Equipment::setLauterDeadspace_l( double var )
462
throw EquipmentException( "lauter deadspace cannot be negative: " + doubleToString(var) );
465
lauterDeadspace_l = var;
470
void Equipment::setTopUpKettle_l( double var )
473
throw EquipmentException( "top up kettle cannot be negative: " + doubleToString(var) );
481
void Equipment::setHopUtilization_pct( double var )
483
if( var < 0.0 || var > 100.0 )
484
throw EquipmentException( "hop utilization must be a percentage: " + doubleToString(var) );
487
hopUtilization_pct = var;
492
void Equipment::setNotes( const std::string &var )
494
notes = std::string(var);
498
//============================"GET" METHODS=====================================
500
std::string Equipment::getName() const
505
double Equipment::getBoilSize_l() const
510
double Equipment::getBatchSize_l() const
515
double Equipment::getTunVolume_l() const
520
double Equipment::getTunWeight_kg() const
525
double Equipment::getTunSpecificHeat_calGC() const
527
return tunSpecificHeat_calGC;
530
double Equipment::getTopUpWater_l() const
535
double Equipment::getTrubChillerLoss_l() const
537
return trubChillerLoss_l;
540
double Equipment::getEvapRate_pctHr() const
542
return evapRate_pctHr;
545
double Equipment::getBoilTime_min() const
550
bool Equipment::getCalcBoilVolume() const
552
return calcBoilVolume;
555
double Equipment::getLauterDeadspace_l() const
557
return lauterDeadspace_l;
560
double Equipment::getTopUpKettle_l() const
562
return topUpKettle_l;
565
double Equipment::getHopUtilization_pct() const
567
return hopUtilization_pct;
570
std::string Equipment::getNotes() const
575
//TODO: take a look at evapRate_pctHr.
576
void Equipment::doCalculations()
578
// Only do the calculation if we're asked to.
579
if( ! calcBoilVolume )
582
/* The equation given the BeerXML 1.0 spec was way wrong. */
584
(batchSize_l - topUpWater_l + trubChillerLoss_l)
585
/ (1 - (boilTime_min/(double)60) * (evapRate_pctHr/(double)100) );
590
double Equipment::wortEndOfBoil_l( double kettleWort_l ) const
592
return kettleWort_l * (1 - (boilTime_min/(double)60) * (evapRate_pctHr/(double)100) );