QString Mash::toXml() { calcMashSchedule(); QString sb; sb.append(" <MASH>\n"); sb.append(SBStringUtils::xmlElement("NAME", name, 4)); sb.append(SBStringUtils::xmlElement("MASH_VOLUME", SBStringUtils::format(Quantity::convertUnit(CONVERTER_QT, volUnits, volQts), 2) , 4)); sb.append(SBStringUtils::xmlElement("MASH_VOL_U", volUnits, 4)); sb.append(SBStringUtils::xmlElement("MASH_RATIO", mashRatio, 4)); sb.append(SBStringUtils::xmlElement("MASH_RATIO_U", mashRatioU, 4)); sb.append(SBStringUtils::xmlElement("MASH_TIME", totalTime, 4)); sb.append(SBStringUtils::xmlElement("MASH_TMP_U", tempUnits, 4)); sb.append(SBStringUtils::xmlElement("THICK_DECOCT_RATIO", thickDecoctRatio, 4)); sb.append(SBStringUtils::xmlElement("THIN_DECOCT_RATIO", thinDecoctRatio, 4)); if (tempUnits == "C"){ sb.append(SBStringUtils::xmlElement("MASH_TUNLOSS_TEMP", (tunLossF/1.8), 4)); sb.append(SBStringUtils::xmlElement("GRAIN_TEMP", BrewCalcs::fToC(grainTempF), 4)); sb.append(SBStringUtils::xmlElement("BOIL_TEMP", BrewCalcs::fToC(boilTempF), 4)); } else { sb.append(SBStringUtils::xmlElement("MASH_TUNLOSS_TEMP", tunLossF, 4)); sb.append(SBStringUtils::xmlElement("GRAIN_TEMP", grainTempF, 4)); sb.append(SBStringUtils::xmlElement("BOIL_TEMP", boilTempF, 4)); } for (int i = 0; i < steps.size(); i++) { MashStep st = steps.at(i); sb.append(" <ITEM>\n"); sb.append(SBStringUtils::xmlElement("TYPE", st.getType(), 6)); sb.append(SBStringUtils::xmlElement("TEMP", st.getStartTemp(), 6)); double sTemp; double eTemp; if (tempUnits == "C") { sTemp = BrewCalcs::fToC(st.getStartTemp()); eTemp = BrewCalcs::fToC(st.getEndTemp()); } else { sTemp = st.getStartTemp(); eTemp = st.getEndTemp(); } sb.append(SBStringUtils::xmlElement("DISPL_TEMP", SBStringUtils::format(sTemp, 1), 6)); sb.append(SBStringUtils::xmlElement("END_TEMP", st.getEndTemp(), 6)); sb.append(SBStringUtils::xmlElement("DISPL_END_TEMP", SBStringUtils::format(eTemp, 1), 6)); sb.append(SBStringUtils::xmlElement("MIN", st.getMinutes(), 6)); sb.append(SBStringUtils::xmlElement("RAMP_MIN", st.getRampMin(), 6)); sb.append(SBStringUtils::xmlElement("METHOD", st.getMethod(), 6)); sb.append(SBStringUtils::xmlElement("WEIGHT_LBS", st.getWeightLbs(), 6)); sb.append(SBStringUtils::xmlElement("DIRECTIONS", st.getDirections(), 6)); sb.append(" </ITEM>\n"); } sb.append(" </MASH>\n"); return sb; }
int Mash::addStep(){ MashStep step; // calcStepType(temp); if (!steps.empty()) { MashStep *lastStep = &steps[steps.size() -1]; step.setStartTemp(lastStep->getEndTemp() + 1); step.setEndTemp(step.getStartTemp()); step.setType(calcStepType(step.getStartTemp())); } steps.append(step); int i = steps.size(); calcMashSchedule(); // return the index of the last added step: return i-1; }
void Mash::calcMashSchedule() { // Method to run through the mash table and calculate values if (!this->allowRecalcs) { return; } double strikeTemp = 0; double targetTemp = 0; double waterAddedQTS = 0; double waterEquiv = 0; double mr = mashRatio; double currentTemp = getGrainTemp(); double displTemp = 0; double tunLoss; // figure out a better way to do this, eg: themal mass double decoct = 0; int totalMashTime = 0; int totalSpargeTime = 0; double mashWaterQTS = 0; double mashVolQTS = 0; int numSparge = 0; double totalWeightLbs = 0; double totalCerealLbs = 0; // convert mash ratio to qts/lb if in l/kg if (mashRatioU.compare(L_PER_KG) == 0) { mr *= 0.479325; } // convert CurrentTemp to F if (tempUnits == "C") { currentTemp = BrewCalcs::cToF(currentTemp); tunLoss = tunLossF * 1.8; } else tunLoss = tunLossF; // perform calcs on first record if (steps.empty()) return; // sort the list //Collections.sort(steps); // add up the cereal mash lbs for (int i = 1; i < steps.size(); i++) { MashStep *stp = &steps[i]; if (stp->getMethod() == "cereal mash"){ totalCerealLbs += stp->getWeightLbs(); } } totalWeightLbs = maltWeightLbs - totalCerealLbs; // the first step is always an infusion MashStep *stp = &steps[0]; targetTemp = stp->getStartTemp(); strikeTemp = calcStrikeTemp(targetTemp, currentTemp, mr, tunLoss); waterAddedQTS = mr * totalWeightLbs; waterEquiv = totalWeightLbs * (0.192 + mr); mashVolQTS = calcMashVol(totalWeightLbs, mr); totalMashTime += stp->getMinutes(); mashWaterQTS += waterAddedQTS; stp->getInVol()->setUnits(CONVERTER_QT); stp->getInVol()->setAmount(waterAddedQTS); stp->setStrikeTemp(strikeTemp); stp->setMethod(INFUSION); stp->setWeightLbs(totalWeightLbs); qDebug() << "Subtracting cereal " << totalCerealLbs << " from " <<maltWeightLbs; totalWeightLbs = maltWeightLbs - totalCerealLbs; // subtract the water added from the Water Equiv so that they are correct when added in the next part of the loop waterEquiv -= waterAddedQTS; // Updated the water added if (tempUnits == "C") { strikeTemp = BrewCalcs::fToC(strikeTemp); } stp->setDirections("Mash in with " + SBStringUtils::format(stp->getInVol()->getValueAs(volUnits),1 ) + " " + volUnits + " of water at " + SBStringUtils::format(strikeTemp, 1) + " " + tempUnits); // set TargetTemp to the end temp targetTemp = stp->getEndTemp(); for (int i = 1; i < steps.size(); i++) { stp = &steps[i]; currentTemp = targetTemp; // switch targetTemp = stp->getStartTemp(); // if this is a former sparge step that's been changed, change // the method to infusion qDebug() << "Mash step Type: " << stp->getType() << " Method: " << stp->getMethod(); if (stp->getType() != SPARGE && ( stp->getMethod() == FLY || stp->getMethod() == BATCH)) stp->setMethod(INFUSION); // do calcs if (stp->getMethod() == INFUSION) { // calculate an infusion step decoct = 0; waterEquiv += waterAddedQTS; // add previous addition to get WE strikeTemp = boilTempF; // boiling water // Updated the water added waterAddedQTS = calcWaterAddition(targetTemp, currentTemp, waterEquiv, boilTempF); stp->getOutVol()->setAmount(0); stp->getInVol()->setUnits(CONVERTER_QT); stp->getInVol()->setAmount(waterAddedQTS); stp->setStrikeTemp(strikeTemp); stp->setWeightLbs(totalWeightLbs); if (tempUnits == "C") strikeTemp = 100; stp->setDirections("Add " + SBStringUtils::format(stp->getInVol()->getValueAs(volUnits), 1) + " " + volUnits + " of water at " + SBStringUtils::format(strikeTemp, 1) + " " + tempUnits); mashWaterQTS += waterAddedQTS; mashVolQTS += waterAddedQTS; } else if (stp->getMethod() == DECOCTION) { // calculate a decoction step waterEquiv += waterAddedQTS; // add previous addition to get WE waterAddedQTS = 0; strikeTemp = boilTempF; // boiling water double ratio=0.75; if (stp->getMethod() == DECOCTION_THICK) ratio = thickDecoctRatio; else if (stp->getMethod() == DECOCTION_THIN) ratio = thinDecoctRatio; // Calculate volume (qts) of mash to remove decoct = calcDecoction2(targetTemp, currentTemp, mashWaterQTS, ratio, totalWeightLbs); stp->getOutVol()->setUnits(CONVERTER_QT); stp->getOutVol()->setAmount(decoct); stp->getInVol()->setAmount(0); stp->setStrikeTemp(boilTempF); stp->setWeightLbs(totalWeightLbs); // Updated the decoction, convert to right units & make directions stp->setDirections("Remove " + SBStringUtils::format(stp->getOutVol()->getValueAs(volUnits), 1) + " " + volUnits + " of mash, boil, and return to mash."); } else if (stp->getMethod() == DIRECT) { // calculate a direct heat step decoct = 0; waterEquiv += waterAddedQTS; // add previous addition to get WE waterAddedQTS = 0; displTemp = stp->getStartTemp(); if (tempUnits == "C") displTemp = BrewCalcs::fToC(displTemp); stp->setDirections("Add direct heat until mash reaches " + QString::number(displTemp) + " " + tempUnits + "."); stp->getInVol()->setAmount(0); stp->getOutVol()->setAmount(0); stp->setStrikeTemp(0); stp->setWeightLbs(totalWeightLbs); } else if (stp->getMethod() == CEREAL_MASH) { // calculate a cereal mash step waterEquiv += waterAddedQTS; // add previous addition to get WE waterAddedQTS = 0; targetTemp = stp->getStartTemp(); double extraWaterQTS = 0; double cerealTemp = boilTempF; double cerealTargTemp = cerealMashTemp; QString addStr = ""; /* * 1. check the temp of the mash when you add the boiling cereal mash @ default ratio back * 2. if it's > than the step temp, adjust the step temp * 3. if it's < than the step temp, add extra water to increase the "heat equivalencey" of the cereal mash */ double cerealWaterEquiv = stp->getWeightLbs() * (0.192 + mr); waterAddedQTS = mr * stp->getWeightLbs(); strikeTemp = calcStrikeTemp(cerealMashTemp, grainTempF, mr, 0); double newTemp = ((waterEquiv * currentTemp) + (cerealWaterEquiv * cerealTemp)) / (waterEquiv + cerealWaterEquiv); if (newTemp > targetTemp){ stp->setStartTemp(newTemp); } if (newTemp < targetTemp){ double addQts = ((waterEquiv * (targetTemp - currentTemp)) / (cerealTemp - targetTemp)) - 0.192; extraWaterQTS = addQts - waterAddedQTS; addStr = " Add " + SBStringUtils::format(Quantity::convertUnit(CONVERTER_QT, volUnits, extraWaterQTS), 1) + " " + volUnits + " water to the cereal mash."; } // Calculate final temp of cereal mash // cerealTemp = (targetTemp * (waterEquiv + cerealWaterEquiv) - (waterEquiv * currentTemp)) / cerealWaterEquiv; totalMashTime += stp->getMinutes(); mashWaterQTS += waterAddedQTS + extraWaterQTS; stp->getInVol()->setUnits(CONVERTER_QT); stp->getInVol()->setAmount(waterAddedQTS); stp->getOutVol()->setAmount(0); stp->setStrikeTemp(strikeTemp); // make directions QString weightStr = SBStringUtils::format(Quantity::convertUnit(CONVERTER_LB, this->maltUnits, stp->getWeightLbs()), 1) + " " + this->maltUnits; QString volStr = SBStringUtils::format(Quantity::convertUnit(CONVERTER_QT, volUnits, waterAddedQTS), 1) + " " + volUnits; if (tempUnits == "C"){ strikeTemp = BrewCalcs::fToC(strikeTemp); cerealTemp = BrewCalcs::fToC(cerealTemp); targetTemp = BrewCalcs::fToC(targetTemp); cerealTargTemp = BrewCalcs::fToC(cerealTargTemp); } QString tempStr = SBStringUtils::format(strikeTemp, 1) + tempUnits; QString tempStr2 = SBStringUtils::format(cerealTemp, 1) + tempUnits; QString tempStr3 = SBStringUtils::format(targetTemp, 1) + tempUnits; QString tempStr4 = SBStringUtils::format(cerealTargTemp, 1) + tempUnits; QString newDirection = "Cereal mash: mash " + weightStr + " grain with " + volStr + " water at " + tempStr + " to hit " + tempStr4 + " and rest."; newDirection.append(addStr); newDirection.append(" Raise to " + tempStr2 + " and add to the main mash to reach " + tempStr3); stp->setDirections(newDirection); // add cereal mash to total weight totalWeightLbs += stp->getWeightLbs(); } else { qDebug() << "Unrecognised mash step: " << stp->getMethod(); } if (stp->getType() == SPARGE) numSparge++; else { totalMashTime += stp->getMinutes(); } // set target temp to end temp for next step targetTemp = stp->getEndTemp(); } // for steps.size() waterEquiv += waterAddedQTS; // add previous addition to get WE totalTime = totalMashTime; volQts = mashVolQTS; // water use stats: qDebug() << "Total Weight " << totalWeightLbs; absorbedQTS = totalWeightLbs * 0.52; // figure from HBD // spargeTotalQTS = (myRecipe.getPreBoilVol("qt")) - (mashWaterQTS - absorbedQTS); totalWaterQTS = mashWaterQTS; spargeQTS = this->preBoilQts - (mashWaterQTS - absorbedQTS - deadSpace.getValueAs(CONVERTER_QT)); qDebug() << "Sparge QTs: " << spargeQTS; // Now let's figure out the sparging: if (numSparge == 0) return; // Amount to collect per sparge double col = this->preBoilQts / numSparge; QList<double> charge = QList<double>(); QList<double> collect = QList<double>(); for (int i = 0; i < numSparge; i++) { charge.append(0.0); collect.append(0.0); } double totalCollectQts = this->preBoilQts; // do we need to add more water to charge up // is the amount we need to collect less than the initial mash volume - absorbption if (col <= (mashWaterQTS - absorbedQTS)) { charge[0] = 0; collect[0] = mashWaterQTS - absorbedQTS; // how much is left over from the mash totalCollectQts = totalCollectQts - collect[0]; } else { charge[0] = col - (mashWaterQTS - absorbedQTS); // add the additional water to get out the desired first collection amount PER sparge collect[0] = col; totalCollectQts = totalCollectQts - collect[0]; } // do we need any more steps? if(numSparge > 1) { /* batch_1_sparge_liters = (boil_size_l/<total number of steps> ) - mash_water_l + grain_wt_kg * 0.625) batch_2_liters = boil_size_l / <total number of steps> */ for (int i = 1; i < numSparge; i++) { charge[i] = col; collect[i] = col; } } int j=0; for (int i = 1; i < steps.size(); i++) { stp = &steps[i]; if (stp->getType() == SPARGE) { stp->getInVol()->setUnits(CONVERTER_QT); stp->getInVol()->setAmount(charge[j]); stp->getOutVol()->setUnits(CONVERTER_QT); stp->getOutVol()->setAmount(collect[j]); stp->setStrikeTemp(SPARGETMPF); totalSpargeTime += stp->getMinutes(); QString collectStr = SBStringUtils::format(Quantity::convertUnit(CONVERTER_QT, volUnits, collect[j]), 2) + " " + volUnits; QString tempStr = ""; if (tempUnits == "F"){ tempStr = "" + SBStringUtils::format(SPARGETMPF, 1) + "F"; } else { tempStr = SBStringUtils::format(BrewCalcs::fToC(SPARGETMPF), 1) + "C"; } if (numSparge > 1){ stp->setMethod(BATCH); QString add = SBStringUtils::format(Quantity::convertUnit(CONVERTER_QT, volUnits, charge[j]), 2) + " " + volUnits; stp->setDirections("Add " + add + " at " + tempStr + " to collect " + collectStr); } else { stp->getInVol()->setUnits(CONVERTER_QT); stp->getInVol()->setAmount(spargeQTS); stp->getOutVol()->setUnits(CONVERTER_QT); stp->getOutVol()->setAmount(collect[j]); stp->setMethod(FLY); stp->setDirections("Sparge with " + SBStringUtils::format(Quantity::convertUnit(CONVERTER_QT, volUnits, spargeQTS), 1) + " " + volUnits + " at " + tempStr + " to collect " + collectStr); } j++; } } }