# HG changeset patch # User Michiel Broek # Date 1546190302 -3600 # Node ID 35860890224c9c28aa1d52bdf13b49d45365647d # Parent 041af8a82d778f801987f2d3cb89537fdd843faf Added sparge water acid calculation. Added residual alkalinity calculation. diff -r 041af8a82d77 -r 35860890224c www/includes/db_recipes.php --- a/www/includes/db_recipes.php Fri Dec 28 23:18:42 2018 +0100 +++ b/www/includes/db_recipes.php Sun Dec 30 18:18:22 2018 +0100 @@ -62,12 +62,13 @@ $sql .= "', sparge_temp='" . $_POST['sparge_temp']; $sql .= "', sparge_ph='" . $_POST['sparge_ph']; $sql .= "', sparge_volume='" . $_POST['sparge_volume']; -// $sql .= "', sparge_acid_type='" . $_POST['sparge_acid_type']; -// $sql .= "', sparge_acid_perc='" . $_POST['sparge_acid_perc']; -// $sql .= "', sparge_acid_amount='" . $_POST['sparge_acid_amount']; + $sql .= "', sparge_source='" . $_POST['sparge_source']; + $sql .= "', sparge_acid_type='" . $_POST['sparge_acid_type']; + $sql .= "', sparge_acid_perc='" . $_POST['sparge_acid_perc']; + $sql .= "', sparge_acid_amount='" . $_POST['sparge_acid_amount']; $sql .= "', mash_ph='" . $_POST['mash_ph']; $sql .= "', mash_name='" . $_POST['mash_name']; - $sql .= "', calc_acid='" . $_POST['calc_acid']; + ($_POST['calc_acid'] == 'true') ? $sql .= "', calc_acid='1" : $sql .= "', calc_acid='0"; if (isset($_POST['w1_name'])) { $sql .= "', w1_name='" . mysqli_real_escape_string($connect, $_POST['w1_name']); $sql .= "', w1_amount='" . $_POST['w1_amount']; @@ -235,7 +236,8 @@ $recipes .= '","sparge_temp":' . $row['sparge_temp']; $recipes .= ',"sparge_ph":' . $row['sparge_ph']; $recipes .= ',"sparge_volume":' . $row['sparge_volume']; - $recipes .= ',"sparge_acid_type":"' . $row['sparge_acid_type']; + $recipes .= ',"sparge_source":"' . $row['sparge_source']; + $recipes .= '","sparge_acid_type":"' . $row['sparge_acid_type']; $recipes .= '","sparge_acid_perc":' . $row['sparge_acid_perc']; $recipes .= ',"sparge_acid_amount":' . $row['sparge_acid_amount']; $recipes .= ',"mash_ph":' . $row['mash_ph']; diff -r 041af8a82d77 -r 35860890224c www/js/rec_edit.js --- a/www/js/rec_edit.js Fri Dec 28 23:18:42 2018 +0100 +++ b/www/js/rec_edit.js Sun Dec 30 18:18:22 2018 +0100 @@ -306,6 +306,11 @@ return 0; } + // mg/l as CaCO3 + function ResidualAlkalinity(total_alkalinity, calcium, magnesium) { + return total_alkalinity - (calcium / 1.4 + magnesium / 1.7); + } + var Ka1 = 0.0000004445; var Ka2 = 0.0000000000468; @@ -324,9 +329,10 @@ } //Z alkalinity is the amount of acid (in mEq/l) needed to bring water to the target pH (Z pH) - function ZAlkalinity(pHZ, WpH) { + function ZAlkalinity(pHZ) { var C43 = Charge(4.3); - var Cw = Charge(WpH); + //var Cw = Charge(parseFloat($("#wg_ph").jqxNumberInput('decimal'))); + var Cw = Charge(7.0); var Cz = Charge(pHZ); var DeltaCNaught = -C43+Cw; var CT = parseFloat($("#wg_total_alkalinity").jqxNumberInput('decimal')) / 50 / DeltaCNaught; @@ -335,17 +341,18 @@ } //Z Residual alkalinity is the amount of acid (in mEq/l) needed to bring the water in the mash to the target pH (Z pH) - function ZRA(pHZ, WpH) { + function ZRA(pHZ) { var Calc = parseFloat($("#wg_calcium").jqxNumberInput('decimal')) / (MMCa / 2); var Magn = parseFloat($("#wg_magnesium").jqxNumberInput('decimal')) / (MMMg / 2); - var Z = ZAlkalinity(pHZ, WpH); + var Z = ZAlkalinity(pHZ); return Z - (Calc / 3.5 + Magn / 7); } function ProtonDeficit(pHZ) { - var Result = ZRA(pHZ, parseFloat($("#wg_ph").jqxNumberInput('decimal'))) * parseFloat($("#wg_amount").jqxNumberInput('decimal')); + var Result = ZRA(pHZ) * parseFloat($("#wg_amount").jqxNumberInput('decimal')); + // proton deficit for the grist var rows = $('#fermentableGrid').jqxGrid('getrows'); for (var i = 0; i < rows.length; i++) { var row = rows[i]; @@ -354,9 +361,8 @@ var C1 = 0; if ((row.f_di_ph != 5.7) && ((row.f_acid_to_ph_57 < - 0.1) || (row.f_acid_to_ph_57 > 0.1))) { C1 = row.f_acid_to_ph_57 / (row.f_di_ph - 5.7); - // console.log("formula C1: "+C1); } else { - // If the acid_to_ph_5.7 is unknown from the malter, guess the required acid. + // If the acid_to_ph_5.7 is unknown from the maltster, guess the required acid. var ebc = row.f_color; switch (row.f_graintype) { case 'Base': @@ -370,7 +376,6 @@ case 'Sour': C1 = -149; break; } - // console.log("Logic C1: "+C1); } x = C1 * (pHZ - row.f_di_ph); // AcidRequired(ZpH) // console.log(row.f_name+" C1: "+C1+" ZpH: "+pHZ+" di_ph: "+row.f_di_ph+" acid rquired: "+x); @@ -502,7 +507,6 @@ // Note: brouwhulp has the malts included here in the result. var wg_ph = ph; $('#wg_ph').val(Math.round(ph * 10) / 10); -// $('#wb_ph').val(Math.round(ph * 10) / 10); $('#wb_ph').val(Math.round(MashpH() * 10) / 10); bicarbonate = total_alkalinity * 1.22; var wg_bicarbonate = bicarbonate; @@ -562,10 +566,8 @@ setWaterAgent(last_base, 0); frac = CalcFrac(TpH, pK1, pK2, pK3); Acid = protonDeficit / frac; - // console.log("Required moles: "+Acid); Acid *= MolWt; // mg Acidmg = Acid; - // console.log("Required mg: "+Acidmg); Acid = Acid / AcidSG; // ml if (parseFloat($("#wa_acid_perc").jqxNumberInput('decimal')) == 0) @@ -601,6 +603,7 @@ RA = 1000 * RA / liters; bicarbonate = wg_bicarbonate + RA; total_alkalinity = bicarbonate * 50 / 61; + RA = ResidualAlkalinity(wb_total_alkalinity, wb_calcium, wb_magnesium); } break; case 'Na2CO3': base = -protonDeficit / (2 * f1d + f2d); //mmol totaal @@ -617,6 +620,7 @@ RA = 1000 * RA / liters; bicarbonate = wg_bicarbonate + RA; total_alkalinity = bicarbonate * 50 / 61; + RA = ResidualAlkalinity(wb_total_alkalinity, wb_calcium, wb_magnesium); } break; case 'CaCO3': base = -protonDeficit * (f1d - f3d); //mmol totaal @@ -637,6 +641,7 @@ parseFloat($("#wa_base").jqxNumberInput('decimal')) * MMCa / MMCaCO3; RA = 1000 * RA / liters; calcium = wg_calcium + RA; + RA = ResidualAlkalinity(wb_total_alkalinity, wb_calcium, wb_magnesium); } break; case 'Ca(OH)2': base = -protonDeficit / 19.3; // g @@ -653,6 +658,7 @@ parseFloat($("#wa_base").jqxNumberInput('decimal')) * MMCa / MMCaOH2; RA = 1000 * RA / liters; calcium = wg_calcium + RA; + RA = ResidualAlkalinity(wb_total_alkalinity, wb_calcium, wb_magnesium); } break; } @@ -663,7 +669,46 @@ console.log("calc_acid no"); // First add base salts if (parseFloat($("#wa_base").jqxNumberInput('decimal')) > 0) { - + if (liters > 0) { + switch (BT) { + case 'NaHCO3': // Na + RA = parseFloat($("#wa_nacl").jqxNumberInput('decimal')) * MMNa / MMNaCl + + parseFloat($("#wa_base").jqxNumberInput('decimal')) * MMNa / MMNaHCO3; + RA = 1000 * RA / liters; + sodium = wg_sodium + RA; + // HCO3 + RA = parseFloat($("#wa_base").jqxNumberInput('decimal')) * MMHCO3 / MMNaHCO3; + RA = 1000 * RA / liters; + bicarbonate = wg_bicarbonate + RA; + total_alkalinity = bicarbonate * 50 / 61; + RA = ResidualAlkalinity(wb_total_alkalinity, wb_calcium, wb_magnesium); + break; + case 'Na2CO3': RA = parseFloat($("#wa_nacl").jqxNumberInput('decimal')) * MMNa / MMNaCl + + parseFloat($("#wa_base").jqxNumberInput('decimal')) * 2 * MMNa / MMNa2CO3; + RA = 1000 * RA / liters; + sodium = wg_sodium + RA; + // HCO3 + RA = parseFloat($("#wa_base").jqxNumberInput('decimal')) * MMHCO3 / MMNa2CO3; + RA = 1000 * RA / liters; + bicarbonate = wg_bicarbonate + RA; + total_alkalinity = bicarbonate * 50 / 61; + RA = ResidualAlkalinity(wb_total_alkalinity, wb_calcium, wb_magnesium); + break; + case 'CaCO3': // Bicarbonate + RA = parseFloat($("#wa_base").jqxNumberInput('decimal')) / 3 * MMHCO3 / MMCaCO3; + RA = 1000 * RA / liters; + bicarbonate = wg_bicarbonate + RA; + total_alkalinity = bicarbonate * 50 / 61; + RA = ResidualAlkalinity(wb_total_alkalinity, wb_calcium, wb_magnesium); + // Ca + RA = parseFloat($("#wa_cacl2").jqxNumberInput('decimal')) * MMCa / MMCaCl2 + + parseFloat($("#wa_caso4").jqxNumberInput('decimal')) * MMCa / MMCaSO4 + + parseFloat($("#wa_base").jqxNumberInput('decimal')) * MMCa / MMCaCO3; + RA = 1000 * RA / liters; + calcium = wg_calcium + RA; + break; + } + } } TpH = parseFloat(dataRecord.mash_ph); @@ -724,8 +769,18 @@ chloride = wg_chloride + RA; } + // 2:1 Sulfate to Chroride IPA's, Pale Ales. + // 1:1 Sulfate to Chloride Balanced + // 1:2 Sulfate to Chloride Malty $('#tgt_bu').val(Math.round(GetBUGU() * 100) / 100); $('#tgt_cl_so4').val(Math.round(GetOptClSO4ratio() * 10) / 10); // Show real value too + if (sulfate > 0) + RA = chloride / sulfate; + else + RA = 10; + var piCLSO4_low = 0.8 * GetOptClSO4ratio(); + var piCLSO4_high = 1.2 * GetOptClSO4ratio(); + console.log("low: "+piCLSO4_low+" val: "+RA+" high: "+piCLSO4_high); $('#wb_calcium').val(Math.round(calcium * 10) / 10); $('#wb_magnesium').val(Math.round(magnesium * 10) / 10); @@ -768,6 +823,98 @@ } else { setRangeIndicator("ph", "normal"); } + calcSparge(); + } + + function calcSparge() { + + // Code from BrewBuddy/Brouwhulp, who got it from http://www.brewery.org/brewery/library/Acidi0,00fWaterAJD0497.html + var TargetpH = dataRecord.sparge_ph; + var Source_pH = dataRecord.w1_ph; + var Source_alkalinity = dataRecord.w1_total_alkalinity; + if (dataRecord.sparge_source == 'Bron 2') { + if (dataRecord.w2_ph > 0.0) { + Source_pH = dataRecord.w2_ph; + Source_alkalinity = dataRecord.w2_total_alkalinity; + } else { + dataRecord.sparge_source = 'Bron 1'; + $("#sparge_source").val('Bron 1'); + } + } else if (dataRecord.sparge_source == 'Gemengd') { + if (dataRecord.w2_ph > 0.0) { + Source_pH = parseFloat($("#wg_ph").jqxNumberInput('decimal')); + Source_alkalinity = parseFloat($("#wg_total_alkalinity").jqxNumberInput('decimal')); + } else { + dataRecord.sparge_source = 'Bron 1'; + $("#sparge_source").val('Bron 1'); + } + } + + console.log("calcSparge() target pH: "+TargetpH+" Source: "+Source_pH+" alkalinity: "+Source_alkalinity); + + // Step 1: Compute the mole fractions of carbonic (f1o), bicarbonate (f2o) and carbonate(f3o) at the water pH + r1 = Math.pow(10, Source_pH - 6.38); + r2 = Math.pow(10, Source_pH - 10.33); + d = 1 + r1 + r1*r2; + f1 = 1/d; + f2 = r1/d; + f3 = r1 * r2 / d; + + //Step 2. Compute the mole fractions at pH = 4.3 (the pH which defines alkalinity) + r143 = Math.pow(10, 4.3 - 6.38); + r243 = Math.pow(10, 4.3 - 10.33); + d43 = 1 + r143 + r143*r243; + f143 = 1/d43; + f243 = r143 / d43; + f343 = r143 * r243 / d43; + + //Step 3. Convert the sample alkalinity to milliequivalents/L + alkalinity = Source_alkalinity / 50; + //Step 4. Solve + alkalinity = alkalinity / ((f143-f1)+(f3-f343)); + + //Step 5. Compute mole fractions at desired pH + r1g = Math.pow(10, TargetpH - 6.38); + r2g = Math.pow(10, TargetpH - 10.33); + dg = 1 + r1g + r1g*r2g; + f1g = 1/dg; + f2g = r1g / dg; + f3g = r1g * r2g / dg; + + //Step 6. Use these to compute the milliequivalents acid required per liter (mEq/L) + Acid = alkalinity * ((f1g-f1)+(f3-f3g)) + Math.pow(10, -TargetpH) - Math.pow(10, -Source_pH); //mEq/l + + if ($("#sparge_acid_type").val() == "") { + $("#sparge_acid_type").val('Melkzuur'); + dataRecord.sparge_acid_type = 'Melkzuur'; + } + AT = dataRecord.sparge_acid_type; + var result = GetAcidSpecs(AT); + pK1 = result.pK1; + pK2 = result.pK2; + pK3 = result.pK3; + MolWt = result.MolWt; + AcidSG = result.AcidSG; + AcidPrc = result.AcidPrc; + fract = CalcFrac(TargetpH, pK1, pK2, pK3); + + //Step 9. Now divide the mEq required by the "fraction". This is the required number of moles of acid. + Acid /= fract; + + //Step 10. Multiply by molecular weight of the acid + Acid *= MolWt; //mg + + Acid = Acid / AcidSG; //ml ; 88% lactic solution + f1 = dataRecord.sparge_acid_perc; + if (f1 <= 0.1) + f1 = AcidPrc; + Acid = Acid * AcidPrc / (f1 / 100); + + Acid *= dataRecord.sparge_volume; //ml lactic acid total + Acid = Math.round(Acid * 100) / 100; + dataRecord.sparge_acid_amount = Acid / 1000; + $("#sparge_acid_amount").val(Acid); + console.log("acid needed: "+Acid); } function calcFermentablesFromOG(OG) { @@ -929,6 +1076,28 @@ dataRecord.mash_ph = parseFloat(event.args.value); calcWater(); }); + + $('#sparge_ph').on('change', function (event) { + dataRecord.sparge_ph = parseFloat(event.args.value); + calcSparge(); + }); + $('#sparge_volume').on('change', function (event) { + dataRecord.sparge_volume = parseFloat(event.args.value); + calcSparge(); + }); + $('#sparge_source').on('change', function (event) { + dataRecord.sparge_source= event.args.item.value; + calcSparge(); + }); + $('#sparge_acid_type').on('change', function (event) { + dataRecord.sparge_acid_type = event.args.item.value; + console.log("new sparge_acid_type: "+dataRecord.sparge_acid_type); + calcSparge(); + }); + $('#sparge_acid_perc').on('change', function (event) { + dataRecord.sparge_acid_perc = parseFloat(event.args.value); + calcSparge(); + }); }; $("#styleSelect").jqxDropDownList({ @@ -1064,6 +1233,7 @@ { name: 'sparge_temp', type: 'float' }, { name: 'sparge_ph', type: 'float' }, { name: 'sparge_volume', type: 'float' }, + { name: 'sparge_source', type: 'string' }, { name: 'sparge_acid_type', type: 'string' }, { name: 'sparge_acid_perc', type: 'float' }, { name: 'sparge_acid_amount', type: 'float' }, @@ -1145,9 +1315,10 @@ $("#sparge_temp").val(dataRecord.sparge_temp); $("#sparge_ph").val(dataRecord.sparge_ph); $("#sparge_volume").val(dataRecord.sparge_volume); + $("#sparge_source").val(dataRecord.sparge_source); $("#sparge_acid_type").val(dataRecord.sparge_acid_type); $("#sparge_acid_perc").val(dataRecord.sparge_acid_perc); - $("#sparge_acid_amount").val(dataRecord.sparge_acid_amount); + $("#sparge_acid_amount").val(dataRecord.sparge_acid_amount * 1000); $("#calc_acid").val(dataRecord.calc_acid); $("#w1_name").val(dataRecord.w1_name); $("#w1_amount").val(dataRecord.w1_amount); @@ -2262,6 +2433,7 @@ var srcIBU = [ "Tinseth", "Rager", "Daniels" ]; // Only these are supported at this time. var srcBase = [ "NaHCO3", "Na2CO3", "CaCO3", "Ca(OH)2" ]; var srcAcid = [ "Melkzuur", "Zoutzuur", "Fosforzuur", "Zwavelzuur" ]; + var srcSource = [ "Bron 1", "Bron 2", "Gemengd" ]; $("#name").jqxInput({ theme: theme, width: 640, height: 23 }); $("#notes").jqxInput({ theme: theme, width: 960, height: 200 }); $("#st_name").jqxInput({ theme: theme, width: 250, height: 23 }); @@ -2306,7 +2478,6 @@ $("#mash_name").jqxInput({ theme: theme, width: 320, height: 23 }); $("#mash_ph").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 4, max: 8, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1 }); - $("#sparge_temp").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 70, max: 98, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.5 }); // Several gauges $("#hop_flavour").jqxProgressBar({ width: 300, height: 23, theme: theme, showText: true }); $("#hop_aroma").jqxProgressBar({ width: 300, height: 23, theme: theme, showText: true }); @@ -2457,15 +2628,19 @@ $("#wa_nacl").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 0, max: 1000, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1, symbol: ' gr', symbolPosition: 'right' }); $("#calc_acid").jqxCheckBox({ theme: theme, width: 120, height: 23 }); - $("#wa_base_name").jqxDropDownList({ theme: theme, source: srcBase, width: 125, height: 23, dropDownHeight: 128 }); + $("#wa_base_name").jqxDropDownList({ theme: theme, source: srcBase, width: 100, height: 23, dropDownHeight: 128 }); $("#wa_base").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 0, decimalDigits: 2, spinButtons: true, spinButtonsStep: 0.05, symbol: ' gr', symbolPosition: 'right' }); - $("#wa_acid_name").jqxDropDownList({ theme: theme, source: srcAcid, width: 125, height: 23, dropDownHeight: 128 }) + $("#wa_acid_name").jqxDropDownList({ theme: theme, source: srcAcid, width: 100, height: 23, dropDownHeight: 128 }); $("#wa_acid").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 0, decimalDigits: 2, spinButtons: true, spinButtonsStep: 0.05, symbol: ' ml', symbolPosition: 'right' }); $("#wa_acid_perc").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 80, height: 23, min: 0, max: 100, decimalDigits: 0, spinButtons: true, symbol: '%', symbolPosition: 'right' }); + $("#sparge_temp").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 70, max: 98, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.5 }); $("#sparge_volume").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1 }); $("#sparge_ph").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1 }); - $("#sparge_acid_amount").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 100, height: 23, decimalDigits: 5, readOnly: true }); + $("#sparge_source").jqxDropDownList({ theme: theme, source: srcSource, width: 100, height: 23, dropDownHeight: 95 }); + $("#sparge_acid_amount").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 100, height: 23, decimalDigits: 2, readOnly: true, symbol: ' ml', symbolPosition: 'right' }); + $("#sparge_acid_type").jqxDropDownList({ theme: theme, source: srcAcid, width: 100, height: 23, dropDownHeight: 128 }); + $("#sparge_acid_perc").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, spinButtons: true, decimalDigits: 0, symbol: '%', symbolPosition: 'right' }); // Tabs inside the popup window. $('#jqxTabs').jqxTabs({ @@ -2557,9 +2732,10 @@ sparge_temp: parseFloat($("#sparge_temp").jqxNumberInput('decimal')), sparge_ph: parseFloat($("#sparge_ph").jqxNumberInput('decimal')), sparge_volume: parseFloat($("#sparge_volume").jqxNumberInput('decimal')), - // sparge_acid_type: $("#sparge_acid_type").val(), - // sparge_acid_perc: parseFloat($("#sparge_acid_perc").jqxNumberInput('decimal')), - // sparge_acid_amount: parseFloat($("#sparge_acid_amount").jqxNumberInput('decimal')), + sparge_source: $("#sparge_source").val(), + sparge_acid_type: $("#sparge_acid_type").val(), + sparge_acid_perc: parseFloat($("#sparge_acid_perc").jqxNumberInput('decimal')), + sparge_acid_amount: dataRecord.sparge_acid_amount, calc_acid: $("#calc_acid").val(), w1_name: $("#w1_name").val(), w1_amount: parseFloat($("#w1_amount").jqxNumberInput('decimal')), diff -r 041af8a82d77 -r 35860890224c www/rec_edit.php --- a/www/rec_edit.php Fri Dec 28 23:18:42 2018 +0100 +++ b/www/rec_edit.php Sun Dec 30 18:18:22 2018 +0100 @@ -314,10 +314,16 @@ Aanzuren met: -
+
+ - Aanzuren hoeveelheid: + Percentage: +
+ + + + Hoeveelheid: