Added f_acid_to_ph_57 to the fermentables json in the recipes. The protonDeficit now processes the grist. Water pH calculations are partly in place. Added simple BU and Cl/So4 indicators. Redesign of the water screen.

Thu, 27 Dec 2018 22:30:26 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Thu, 27 Dec 2018 22:30:26 +0100
changeset 154
ef298b5aa994
parent 153
15fe253ffa83
child 155
041af8a82d77

Added f_acid_to_ph_57 to the fermentables json in the recipes. The protonDeficit now processes the grist. Water pH calculations are partly in place. Added simple BU and Cl/So4 indicators. Redesign of the water screen.

www/import/from_brouwhulp.php file | annotate | diff | comparison | revisions
www/includes/db_recipes.php file | annotate | diff | comparison | revisions
www/js/rec_edit.js file | annotate | diff | comparison | revisions
www/rec_edit.php file | annotate | diff | comparison | revisions
--- a/www/import/from_brouwhulp.php	Tue Dec 25 15:06:39 2018 +0100
+++ b/www/import/from_brouwhulp.php	Thu Dec 27 22:30:26 2018 +0100
@@ -583,6 +583,10 @@
 			$fermentables .= ',"f_di_ph":' . floatval($fermentable->DI_pH);
 		else
 			$fermentables .= ',"f_di_ph":0.0';
+		if ($fermentable->{'ACID_TO_pH_5.7'})
+			$fermentables .= ',"f_acid_to_ph_57":' . floatval($fermentable->{'ACID_TO_pH_5.7'});
+		else
+			$fermentables .= ',"f_acid_to_ph_57":0.0';
 		$fermentables .= "}";
 		/* Sugars */
 		$d = $famount * ($fyield / 100) * (1 - $fmoisture / 100);
--- a/www/includes/db_recipes.php	Tue Dec 25 15:06:39 2018 +0100
+++ b/www/includes/db_recipes.php	Thu Dec 27 22:30:26 2018 +0100
@@ -62,9 +62,9 @@
 	$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_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'];
--- a/www/js/rec_edit.js	Tue Dec 25 15:06:39 2018 +0100
+++ b/www/js/rec_edit.js	Thu Dec 27 22:30:26 2018 +0100
@@ -198,6 +198,52 @@
 		}
 	}
 
+/*	function GetBUGUMin() {
+
+		var Result = 0;
+
+		if (((dataRecord.st_og_max + dataRecord.st_og_min) > 0) && ((dataRecord.st_ibu_max + dataRecord.st_ibu_min) > 0)) {
+			var G = (dataRecord.st_og_max - dataRecord.st_og_min) / ((dataRecord.st_og_max + dataRecord.st_og_min) / 2);
+			var B = (dataRecord.st_ibu_max - dataRecord.st_ibu_min) / ((dataRecord.st_ibu_max + dataRecord.st_ibu_min) / 2);
+			if (G > B)
+				Result = ((dataRecord.st_ibu_max + dataRecord.st_ibu_min) / 2) / (1000 * (dataRecord.st_og_max - 1));
+			else
+				Result = dataRecord.st_ibu_min / (1000 * (((dataRecord.st_og_max + dataRecord.st_og_min) / 2) - 1));
+		}
+		console.log("GetBUGUMin(): "+Result);
+		return Result;
+	}
+
+	function GetBUGUMax() {
+
+		var Result = 0;
+
+		if (((dataRecord.st_og_max + dataRecord.st_og_min) > 0) && ((dataRecord.st_ibu_max + dataRecord.st_ibu_min) > 0) && (dataRecord.st_og_min > 0)) {
+			var G = (dataRecord.st_og_max - dataRecord.st_og_min) / ((dataRecord.st_og_max + dataRecord.st_og_min) / 2);
+			var B = (dataRecord.st_ibu_max - dataRecord.st_ibu_min) / ((dataRecord.st_ibu_max + dataRecord.st_ibu_min) / 2);
+			if (G > B)
+				Result = ((dataRecord.st_ibu_min + dataRecord.st_ibu_max) / 2) / (1000 * (dataRecord.st_og_min - 1));
+			else
+				Result = dataRecord.st_ibu_max / (1000 * (((dataRecord.st_og_max + dataRecord.st_og_min) / 2) - 1));
+
+		}
+		console.log("GetBUGUMax(): "+Result);
+		return Result;
+	} */
+
+	function GetBUGU() {
+		var gu = (dataRecord.est_og - 1) * 1000;
+		if (gu > 0)
+			return dataRecord.est_ibu / gu;
+		else
+			return 0.5;
+	}
+
+	function GetOptClSO4ratio() {
+		var BUGU = GetBUGU();
+		return (-1.2 * BUGU + 1.4);
+	}
+
 	function setWaterAgent(name, amount) {
 		console.log("setWaterAgent(" + name + ", " + amount + ")");
 		var rows = $('#miscGrid').jqxGrid('getrows');
@@ -281,9 +327,9 @@
 	}
 
 	//Z alkalinity is the amount of acid (in mEq/l) needed to bring water to the target pH (Z pH)
-	function ZAlkalinity(pHZ) {
+	function ZAlkalinity(pHZ, WpH) {
 		var C43 = Charge(4.3);
-		var Cw = Charge(parseFloat(dataRecord.mash_ph));
+		var Cw = Charge(WpH);
 		var Cz = Charge(pHZ);
 		var DeltaCNaught = -C43+Cw;
 		var CT = parseFloat($("#wg_total_alkalinity").jqxNumberInput('decimal')) / 50 / DeltaCNaught;
@@ -291,21 +337,108 @@
 		return CT * DeltaCZ;
 	}
 
-	function ZRA(pHZ) {
+	//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) {
 
 		var Calc = parseFloat($("#wg_calcium").jqxNumberInput('decimal')) / (MMCa / 2);
 		var Magn = parseFloat($("#wg_magnesium").jqxNumberInput('decimal')) / (MMMg / 2);
-		var Z = ZAlkalinity(pHZ);
+		var Z = ZAlkalinity(pHZ, WpH);
 		return Z - (Calc / 3.5 + Magn / 7);
 	}
 
 	function ProtonDeficit(pHZ) {
 
-		var Result = ZRA(pHZ) * parseFloat($("#wg_amount").jqxNumberInput('decimal'));
-
+		var Result = ZRA(pHZ, parseFloat($("#wg_ph").jqxNumberInput('decimal'))) * parseFloat($("#wg_amount").jqxNumberInput('decimal'));
+		var rows = $('#fermentableGrid').jqxGrid('getrows');
+		for (var i = 0; i < rows.length; i++) {
+			var row = rows[i];
+			if (row.f_added == 'Mash' && row.f_graintype != 'No malt') {
+				// Check if acid is required
+				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.
+					var ebc = row.f_color;
+					switch (row.f_graintype) {
+						case 'Base':
+						case 'Special':
+						case 'Kilned':	C1 = 0.014 * ebc - 34.192;
+								break;
+						case 'Crystal':	C1 = -0.0597 * ebc - 32.457;
+								break;
+						case 'Roast':	C1 = 0.0107 * ebc - 54.768;
+								break;
+						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);
+				Result += x * row.f_amount;
+			}
+		}
+	//	console.log("Final: "+Result);
 		return Result;
 	}
 
+	function MashpH() {
+		var n = 0;
+		var pH = 5.4;
+		var deltapH = 0.001;
+		var deltapd = 0.1;
+		var pd = ProtonDeficit(pH);
+		while (((pd < -deltapd) || (pd > deltapd)) && (n < 1000)) {
+			n++;
+			if (pd < -deltapd)
+				pH -= deltapH;
+			else if (pd > deltapd)
+				pH += deltapH;
+			pd = ProtonDeficit(pH);
+		}
+		console.log("MashpH() n: "+n+" pH: "+pH);
+	}
+
+	function GetAcidSpecs(AT) {
+		switch(AT) {
+			case 'Melkzuur':	return {
+							pK1: 3.08,
+							pK2: 20,
+							pK3: 20,
+							MolWt: 90.08,
+							AcidSG: 1214,
+							AcidPrc: 0.88
+						};
+			case 'Zoutzuur':	return {
+							pK1: -10,
+							pK2: 20,
+							pK3: 20,
+							MolWt: 36.46,
+							AcidSG: 1142,
+							AcidPrc: 0.28
+						};
+			case 'Fosforzuur':	return {
+							pK1: 2.12,
+							pK2: 7.20,
+							pK3: 12.44,
+							MolWt: 98.00,
+							AcidSG: 1170,
+							AcidPrc: 0.25
+						};
+			case 'Zwavelzuur':	return {
+							pK1: -10,
+							pK2: 1.92,
+							pK3: 20,
+							MolWt: 98.07,
+							AcidSG: 1700,
+							AcidPrc: 0.93
+						};
+		}
+	}
+
+	// Procedure TFrmWaterAdjustment.CalcWater2;
 	function calcWater() {
 
 		console.log("calcWater()");
@@ -314,6 +447,7 @@
 		var magnesium = 0;
 		var sodium = 0;
 		var total_alkalinity = 0;
+		var bicarbonate = 0;
 		var chloride = 0;
 		var sulfate = 0;
 		var ph = 0;
@@ -329,27 +463,30 @@
 		var AcidPrc = 0;
 		var protonDeficit = 0;
 
-//		console.log((dataRecord.w1_name != "") + " " + (dataRecord.w2_name != ""));
-		if (dataRecord.w1_name != "") {
-			if (dataRecord.w2_name != "") {
-				liters = dataRecord.w1_amount + dataRecord.w2_amount;
-				calcium = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_calcium, dataRecord.w2_calcium);
-				magnesium = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_magnesium, dataRecord.w2_magnesium);
-				sodium = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_sodium, dataRecord.w2_sodium);
-				chloride = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_chloride, dataRecord.w2_chloride);
-				sulfate = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_sulfate, dataRecord.w2_sulfate);
-				total_alkalinity = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_total_alkalinity, dataRecord.w2_total_alkalinity);
-				ph = -Math.log10(((Math.pow(10, -dataRecord.w1_ph) * dataRecord.w1_amount) + (Math.pow(10, -dataRecord.w2_ph) * dataRecord.w2_amount))  / liters);
-			} else {
-				liters = dataRecord.w1_amount;
-				calcium = dataRecord.w1_calcium;
-				magnesium = dataRecord.w1_magnesium;
-				sodium = dataRecord.w1_sodium;
-				chloride = dataRecord.w1_chloride;
-				sulfate = dataRecord.w1_sulfate;
-				total_alkalinity = dataRecord.w1_total_alkalinity;
-				ph = dataRecord.w1_ph;
-			}
+		if (dataRecord.w1_name == "") {
+			return;
+		}
+		// Check for a recipe too. Fermentables, Mash schedule?
+
+		// If there is a dillute water source, mix the waters.
+		if (dataRecord.w2_name != "") {
+			liters = dataRecord.w1_amount + dataRecord.w2_amount;
+			calcium = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_calcium, dataRecord.w2_calcium);
+			magnesium = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_magnesium, dataRecord.w2_magnesium);
+			sodium = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_sodium, dataRecord.w2_sodium);
+			chloride = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_chloride, dataRecord.w2_chloride);
+			sulfate = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_sulfate, dataRecord.w2_sulfate);
+			total_alkalinity = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_total_alkalinity, dataRecord.w2_total_alkalinity);
+			ph = -Math.log10(((Math.pow(10, -dataRecord.w1_ph) * dataRecord.w1_amount) + (Math.pow(10, -dataRecord.w2_ph) * dataRecord.w2_amount))  / liters);
+		} else {
+			liters = dataRecord.w1_amount;
+			calcium = dataRecord.w1_calcium;
+			magnesium = dataRecord.w1_magnesium;
+			sodium = dataRecord.w1_sodium;
+			chloride = dataRecord.w1_chloride;
+			sulfate = dataRecord.w1_sulfate;
+			total_alkalinity = dataRecord.w1_total_alkalinity;
+			ph = dataRecord.w1_ph;
 		}
 		$('#wg_amount').val(liters);
 		$('#wg_calcium').val(Math.round(calcium * 10) / 10);
@@ -360,39 +497,211 @@
 		$('#wg_sulfate').val(Math.round(sulfate * 10) / 10);
 		// Note: brouwhulp has the malts included here in the result.
 		$('#wg_ph').val(Math.round(ph * 10) / 10);
+		$('#wb_ph').val(Math.round(ph * 10) / 10);
+		bicarbonate = total_alkalinity * 1.22;
 
 		// Noot: de volgende berekeningen geven bijna gelijke resultaten in Brun'water.
 		// Calculate Ca
 		RA = parseFloat($("#wa_cacl2").jqxNumberInput('decimal')) * MMCa / MMCaCl2 +
 		     parseFloat($("#wa_caso4").jqxNumberInput('decimal')) * MMCa / MMCaSO4;
-		calcium += 1000 * RA / parseFloat($("#wg_amount").jqxNumberInput('decimal'));
+		calcium += 1000 * RA / liters;
 
 		// Calculate Mg
 		RA = parseFloat($("#wa_mgso4").jqxNumberInput('decimal')) * MMMg / MMMgSO4;
-		magnesium += 1000 * RA / parseFloat($("#wg_amount").jqxNumberInput('decimal'));
+		magnesium += 1000 * RA / liters;
 
 		// Calculate Na
-		RA = parseFloat($("#wa_nacl").jqxNumberInput('decimal')) * MMNa / MMNaCl +
-		     parseFloat($("#wa_base").jqxNumberInput('decimal')) * MMNa / MMNaHCO3;
-		sodium += 1000 * RA / parseFloat($("#wg_amount").jqxNumberInput('decimal'));
+		RA = parseFloat($("#wa_nacl").jqxNumberInput('decimal')) * MMNa / MMNaCl;
+		sodium += 1000 * RA / liters;
 
 		// Calculate SO4
 		RA = parseFloat($("#wa_caso4").jqxNumberInput('decimal')) * MMSO4 / MMCaSO4 +
 		     parseFloat($("#wa_mgso4").jqxNumberInput('decimal')) * MMSO4 / MMMgSO4;
-		sulfate += 1000 * RA / parseFloat($("#wg_amount").jqxNumberInput('decimal'));
+		sulfate += 1000 * RA / liters;
 
 		// Calculate Cl
 		RA = 2 * parseFloat($("#wa_cacl2").jqxNumberInput('decimal')) * MMCl / MMCaCl2 +
 		     parseFloat($("#wa_nacl").jqxNumberInput('decimal')) * MMCl / MMNaCl;
-		chloride += 1000 * RA / parseFloat($("#wg_amount").jqxNumberInput('decimal'));
+		chloride += 1000 * RA / liters;
 		// Einde noot.
 
+		var AT = $("#wa_acid_name").val();
+		var BT = $("#wa_base_name").val();
+
+		var result = GetAcidSpecs(AT);
+		pK1 = result.pK1;
+		pK2 = result.pK2;
+		pK3 = result.pK3;
+		MolWt = result.MolWt;
+		AcidSG = result.AcidSG;
+		AcidPrc = result.AcidPrc;
+		console.log(AT+" pK1: "+pK1+" pK2: "+pK2+" pK3: "+pK3+" MolWt: "+MolWt+" AcidSG: "+AcidSG+" AcidPrc: "+AcidPrc);
+
+		if (dataRecord.calc_acid) {
+			TpH = parseFloat(dataRecord.mash_ph);
+			protonDeficit = ProtonDeficit(TpH);
+			console.log("calc_acid tgt: "+TpH+" protonDeficit: "+protonDeficit);
+			if (protonDeficit > 0) { // Add acid
+				$("#wa_base").val(0);
+				setWaterAgent(last_base, 0);
+				if ($("#wa_acid_name").val() == "") {
+					$("#wa_acid_name").val('Melkzuur');
+					last_acid = 'Melkzuur';
+				}
+				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)
+					$("#wa_acid_perc").val(AcidPrc);
+				Acid = Acid * AcidPrc / (parseFloat($("#wa_acid_perc").jqxNumberInput('decimal')) / 100); // ml
+				console.log("Final ml: "+Acid);
+				$("#wa_acid").val(Math.round(Acid * 100) / 100);
+				setWaterAgent(AT, Math.round(Acid * 100) / 100);
+
+				bicarbonate = bicarbonate - protonDeficit * frac / liters;
+				total_alkalinity = bicarbonate * 50 / 61;
+			} else if (protonDeficit < 0) { //Add base
+				$("#wa_acid").val(0);
+				setWaterAgent(last_acid, 0);
+				if ($("#wa_base_name").val() == "") {
+					$("#wa_base_name").val('NaHCO3');
+					last_base = 'NaHCO3';
+				}
+				r1d = Math.pow(10, (TpH - 6.38));
+				r2d = Math.pow(10, (TpH - 10.38));
+				f1d = 1 / (1 + r1d + r1d * r2d);
+				f2d = f1d * r1d;
+				f3d = f2d * r2d;
+				switch (BT) {
+					case 'NaHCO3':	base = -protonDeficit / (f1d - f3d); //mmol totaal
+							base = base * MMNaHCO3/1000; //gram
+							$("#wa_base").val(Math.round(base * 100) / 100);
+							setWaterAgent(BT, Math.round(base * 100) / 100);
+							if (liters > 0) {
+								// Na
+								RA = parseFloat($("#wa_nacl").jqxNumberInput('decimal')) * MMNa / MMNaCl +
+								     parseFloat($("#wa_base").jqxNumberInput('decimal')) * MMNa / MMNaHCO3;
+								RA = 1000 * RA / liters;
+								sodium = parseFloat($('#wg_sodium').jqxNumberInput('decimal')) + RA;
+								// HCO3
+								RA = parseFloat($("#wa_base").jqxNumberInput('decimal')) * MMHCO3 / MMNaHCO3;
+								RA = 1000 * RA / liters;
+								bicarbonate = (parseFloat($('#wg_total_alkalinity').jqxNumberInput('decimal')) * 1.22) + RA;
+								total_alkalinity = bicarbonate * 50 / 61;
+							}
+							break;
+					case 'Na2CO3':	base = -protonDeficit / (2 * f1d + f2d); //mmol totaal
+							base = base * MMNa2CO3/1000; //gram
+							$("#wa_base").val(Math.round(base * 100) / 100);
+							setWaterAgent(BT, Math.round(base * 100) / 100);
+							if (liters > 0) {
+								RA = parseFloat($("#wa_nacl").jqxNumberInput('decimal')) * MMNa / MMNaCl +
+								     parseFloat($("#wa_base").jqxNumberInput('decimal')) * 2 * MMNa / MMNa2CO3;
+								RA = 1000 * RA / liters;
+								sodium = parseFloat($('#wg_sodium').jqxNumberInput('decimal')) + RA;
+								// HCO3
+								RA = parseFloat($("#wa_base").jqxNumberInput('decimal')) * MMHCO3 / MMNa2CO3;
+								RA = 1000 * RA / liters;
+								bicarbonate = (parseFloat($('#wg_total_alkalinity').jqxNumberInput('decimal')) * 1.22) + RA;
+								total_alkalinity = bicarbonate * 50 / 61;
+							}
+							break;
+					case 'CaCO3':	base = -protonDeficit * (f1d - f3d); //mmol totaal
+							base = base * MMCaCO3/1000; //gram
+							//but only 1/3 is effective, so add 3 times as much
+							base = 3 * base;
+							$("#wa_base").val(Math.round(base * 100) / 100);
+							setWaterAgent(BT, Math.round(base * 100) / 100);
+							if (liters > 0) {
+								//Bicarbonate
+								RA = parseFloat($("#wa_base").jqxNumberInput('decimal')) / 3 * MMHCO3 / MMCaCO3;
+								RA = 1000 * RA / liters;
+								bicarbonate = (parseFloat($('#wg_total_alkalinity').jqxNumberInput('decimal')) * 1.22) + RA;
+								total_alkalinity = bicarbonate * 50 / 61;
+								//Ca precipitates out as Ca10(PO4)6(OH)2
+								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 = parseFloat($('#wg_calcium').jqxNumberInput('decimal')) + RA;
+							}
+							break;
+					case 'Ca(OH)2':	base = -protonDeficit / 19.3; // g
+							$("#wa_base").val(Math.round(base * 100) / 100);
+							setWaterAgent(BT, Math.round(base * 100) / 100);
+							if (liters > 0) {
+								// Bicarbonate
+								RA = -protonDeficit / liters;
+								total_alkalinity = parseFloat($('#wg_total_alkalinity').jqxNumberInput('decimal')) + RA;
+								bicarbonate = total_alkalinity * 61 / 50;
+								// Calcium
+								RA = parseFloat($("#wa_cacl2").jqxNumberInput('decimal')) * MMCa / MMCaCl2 +
+								     parseFloat($("#wa_caso4").jqxNumberInput('decimal')) * MMCa / MMCaSO4 +
+								     parseFloat($("#wa_base").jqxNumberInput('decimal')) * MMCa / MMCaOH2;
+								RA = 1000 * RA / liters;
+								calcium = parseFloat($('#wg_calcium').jqxNumberInput('decimal')) + RA;
+							}
+							break;
+				}
+			}
+			ph = TpH;
+			$('#wb_ph').val(Math.round(ph * 10) / 10);
+		} else { // Manual
+			console.log("calc_acid no");
+			// First add base salts
+			if (parseFloat($("#wa_base").jqxNumberInput('decimal')) > 0) {
+
+			}
+
+			TpH = parseFloat(dataRecord.mash_ph);
+			pHa = parseFloat($('#wg_ph').jqxNumberInput('decimal'));
+			//pHa = parseFloat(dataRecord.mash_ph);
+			// Then calculate the new pH with added acids
+			if (parseFloat($("#wa_acid").jqxNumberInput('decimal')) > 0) {
+				console.log("TpH: "+TpH+" water: "+pHa);
+				Acid = parseFloat($("#wa_acid").jqxNumberInput('decimal'));
+				if (parseFloat($("#wa_acid_perc").jqxNumberInput('decimal')) == 0)
+					$("#wa_acid_perc").val(AcidPrc);
+				Acid = Acid / AcidPrc * (parseFloat($("#wa_acid_perc").jqxNumberInput('decimal')) / 100); // ml
+				Acid = Acid * AcidSG; // ml
+				Acid = Acid / MolWt;  // mg
+
+				//find the pH where the protondeficit = protondeficit by the acid
+				frac = CalcFrac(pHa, pK1, pK2, pK3);
+				protonDeficit = Acid * frac;
+
+				deltapH = 0.01;
+				deltapd = 0.1;
+				pd = ProtonDeficit(TpH);
+				n = 0;
+				console.log("n: "+n+" pd: "+pd+" protonDeficit: "+protonDeficit+" frac: "+frac+" pHa: "+pHa);
+
+				while (((pd < (protonDeficit - deltapd)) || (pd > (protonDeficit + deltapd))) && (n < 1000)) {
+					n++;
+					if (pd < (protonDeficit-deltapd))
+						pHa = pHa - deltapH;
+					else if (pd > (protonDeficit+deltapd))
+						pHa = pHa + deltapH;
+					frac = CalcFrac(pHa, pK1, pK2, pK3);
+					protonDeficit = Acid * frac;
+					pd = ProtonDeficit(pHa);
+			//		console.log("n: "+n+" pd: "+pd+" protonDeficit: "+protonDeficit+" frac: "+frac+" pHa: "+pHa);
+				}
+				console.log("n: "+n+" pd: "+pd+" protonDeficit: "+protonDeficit+" frac: "+frac+" pHa: "+pHa);
+
+			}
+		} 
+/*
 		TpH = parseFloat(dataRecord.mash_ph);
 		if (TpH < 5.0 || TpH > 6.0) {
 			TpH = 5.4;
 			dataRecord.mash_ph = 5.4;
 			$("#mash_ph").val(5.4);
-			$("#tgt_mash_ph").val(5.4);
 		}
 		var acid_amount = parseFloat($("#wa_acid").jqxNumberInput('decimal'));
 		var acid_perc = parseFloat($("#wa_acid_perc").jqxNumberInput('decimal'));
@@ -443,9 +752,58 @@
 						sulfate += Acidmg / 1000 * MMSO4 / (MMSO4 + 2);
 						break;
 		}
-		protonDeficit = ProtonDeficit(TpH);
-		console.log("frac: "+frac+"  acid: "+acid+"  protonDeficit: "+protonDeficit);
+
+		if (dataRecord.calc_acid) {
+		} else if (liters > 0) {  // not calc_acid
+			// First add base salts
+			if (parseFloat($("#wa_base").jqxNumberInput('decimal')) > 0) {
+
+			}
+
+			pHa = parseFloat($("#wb_ph").jqxNumberInput('decimal'));
+			console.log("Adjusted water mash pH: "+pHa);
+			// Then calculate the new pH with added acids
+			if (parseFloat($("#wa_acid").jqxNumberInput('decimal')) > 0) {
+				acid = parseFloat($("#wa_acid").jqxNumberInput('decimal'));
+				if (parseFloat($("#wa_acid_perc").jqxNumberInput('decimal')) == 0)
+					$("#wa_acid_perc").val(AcidPrc);
+				console.log("screen value: "+acid);
+				acid = acid / AcidPrc * (parseFloat($("#wa_acid_perc").jqxNumberInput('decimal')) / 100); // ml
+				console.log("acid ml: "+acid);
+				acid = acid * AcidSG // ml
+				console.log("acid ml: "+acid);
+				acid = acid / MolWt;  // mg
+				console.log("acid mg: "+acid);
+				var Acidmg = acid;
+
+				frac = CalcFrac(pHa, pK1, pK2, pK3);
+				protonDeficit = acid * frac;
+
+				deltapH = 0.001;
+				deltapd = 0.1;
+				pd = ProtonDeficit(pHa);
+				n = 0;
+				console.log("n: "+n+" pd: "+pd+" protonDeficit: "+protonDeficit+" frac: "+frac+" pHa: "+pHa);
+
+				while (((pd < (protonDeficit - deltapd)) || (pd > (protonDeficit + deltapd))) && (n < 1000)) {
+					n++;
+					if (pd < (protonDeficit-deltapd))
+						pHa = pHa - deltapH;
+					else if (pd > (protonDeficit+deltapd))
+						pHa = pHa + deltapH;
+					frac = CalcFrac(pHa, pK1, pK2, pK3);
+					protonDeficit = acid * frac;
+					pd = ProtonDeficit(pHa);
+					console.log("n: "+n+" pd: "+pd+" protonDeficit: "+protonDeficit+" frac: "+frac+" pHa: "+pHa);
+				}
+				console.log("n: "+n+" pd: "+pd+" protonDeficit: "+protonDeficit+" frac: "+frac+" pHa: "+pHa);
+			}
+		}
 		total_alkalinity -= 50 / 61 * protonDeficit * frac / liters;
+		MashpH();
+*/
+		$('#tgt_bu').val(Math.round(GetBUGU() * 100) / 100);
+		$('#tgt_cl_so4').val(Math.round(GetOptClSO4ratio() * 10) / 10);
 
                 $('#wb_calcium').val(Math.round(calcium * 10) / 10);
                 $('#wb_magnesium').val(Math.round(magnesium * 10) / 10);
@@ -481,6 +839,13 @@
 		} else {
 			setRangeIndicator("sulfate", "high");
 		}
+		if (ph < 5.2) {
+			setRangeIndicator("ph", "low");
+		} else if (ph > 5.6) {
+			setRangeIndicator("ph", "high");
+		} else {
+			setRangeIndicator("ph", "normal");
+		}
 	}
 
 	function calcFermentablesFromOG(OG) {
@@ -520,6 +885,14 @@
 	function calcInit () {
 		console.log("calc.init()");
 
+		$("#calc_acid").on('checked', function (event) {
+			dataRecord.calc_acid = true;
+			calcWater();
+		});
+		$("#calc_acid").on('unchecked', function (event) {
+			dataRecord.calc_acid = false;
+			calcWater();
+		});
 		$("#w1_name").jqxDropDownList('selectItem', dataRecord.w1_name);
 		$("#w2_name").jqxDropDownList('selectItem', dataRecord.w2_name);
 		// Fix tap water if zero using mash infuse amount.
@@ -632,7 +1005,6 @@
 		});
 		$('#mash_ph').on('change', function (event) {
 			dataRecord.mash_ph = parseFloat(event.args.value);
-			$("#tgt_mash_ph").val(parseFloat(event.args.value));
 			calcWater();
 		});
 	};
@@ -715,6 +1087,7 @@
 	$("#st_abv_max").jqxTooltip({ content: 'Het maximum alcohol volume % voor deze bierstijl.'});
 	$("#st_carb_min").jqxTooltip({ content: 'Het minimum koolzuur volume voor deze bierstijl.'});
 	$("#st_carb_max").jqxTooltip({ content: 'Het maximum koolzuur volume voor deze bierstijl.'});
+	$("#mash_ph").jqxTooltip({ content: 'Maisch pH tussen 5.2 en 5.6. Gebruik 5.2 voor lichte en 5.5 voor donkere bieren.'});
 	$("#wa_cacl2").jqxTooltip({ content: 'Voor het maken van een ander waterprofiel. Voegt calcium en chloride toe. Voor het verbeteren van zoetere bieren.'});
 	$("#wa_caso4").jqxTooltip({ content: 'Gips. Voor het maken van een ander waterprofiel. Voegt calcium en sulfaat toe. Voor het verbeteren van bittere bieren.'});
 	$("#wa_mgso4").jqxTooltip({ content: 'Epsom zout. Voor het maken van een ander waterprofiel. Voegt magnesium en sulfaat toe. Gebruik spaarzaam!'});
@@ -847,7 +1220,6 @@
 			$("#st_carb_max").val(dataRecord.st_carb_max);
 			$("#mash_name").val(dataRecord.mash_name);
 			$("#mash_ph").val(dataRecord.mash_ph);
-			$("#tgt_mash_ph").val(dataRecord.mash_ph);
 			$("#sparge_temp").val(dataRecord.sparge_temp);
 			$("#sparge_ph").val(dataRecord.sparge_ph);
 			$("#sparge_volume").val(dataRecord.sparge_volume);
@@ -916,7 +1288,8 @@
 				{ name: 'f_add_after_boil', type: 'bool' },
 				{ name: 'f_adjust_to_total_100', type: 'bool' },
 				{ name: 'f_percentage', type: 'float' },
-				{ name: 'f_di_ph', type: 'float' }
+				{ name: 'f_di_ph', type: 'float' },
+				{ name: 'f_acid_to_ph_57', type: 'float' }
 			],
 			addrow: function (rowid, rowdata, position, commit) {
 				commit(true);
@@ -998,6 +1371,7 @@
 							row["f_percentage"] = 0;
 						}
 						row["f_di_ph"] = datarecord.di_ph;
+						row["f_acid_to_ph_57"] = datarecord.acid_to_ph_57;
 						var commit = $("#fermentableGrid").jqxGrid('addrow', null, row);
 					}
 				});
@@ -2010,7 +2384,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 });
-	$("#tgt_mash_ph").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 100, height: 23, decimalDigits: 1, readOnly: true });
 	$("#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 });
@@ -2153,10 +2526,13 @@
 	$("#pr_chloride").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true });
 	$("#pr_sulfate").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true });
 
-	$("#wa_cacl2").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 0, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1, symbol: ' gr', symbolPosition: 'right' });
-	$("#wa_caso4").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 0, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1, symbol: ' gr', symbolPosition: 'right' });
-	$("#wa_mgso4").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 0, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1, symbol: ' gr', symbolPosition: 'right' });
-	$("#wa_nacl").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 0, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1, symbol: ' gr', symbolPosition: 'right' });
+	$("#tgt_bu").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 2, readOnly: true });
+	$("#tgt_cl_so4").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true });
+
+	$("#wa_cacl2").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' });
+	$("#wa_caso4").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' });
+	$("#wa_mgso4").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' });
+	$("#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 });
--- a/www/rec_edit.php	Tue Dec 25 15:06:39 2018 +0100
+++ b/www/rec_edit.php	Thu Dec 27 22:30:26 2018 +0100
@@ -173,11 +173,7 @@
        <table style="width: 100%;">
         <tr>
          <td style="vertical-align: top; float: right; padding: 3px;">Maischchema:</td>
-         <td align="left" style="vertical-align: top; padding: 3px;"><input id="mash_name" /></td>
-         <td style="vertical-align: top; float: right; padding: 3px;">Maish pH:</td>
-         <td style="padding: 3px;"><div id="mash_ph"></div></td>
-         <td style="vertical-align: top; float: right; padding: 3px;">Spoelwater temp:</td>
-         <td style="padding: 3px;"><div id="sparge_temp"></div></td>
+         <td align="left" colspan="5" style="vertical-align: top; padding: 3px;"><input id="mash_name" /></td>
         </tr>
         <tr>
          <td align="right" style="vertical-align: top; padding: 3px;">Stappen:</td>
@@ -192,11 +188,9 @@
        <table style="width: 100%;">
         <tr>
          <td style="vertical-align: top; float: right; padding: 3px;">Bitterheidsindex:</td>
-         <td style="padding: 3px;"><div id="tgt_bu"></div></td>
+         <td style="padding: 3px;" colspan="2"><div id="tgt_bu"></div></td>
          <td style="vertical-align: top; float: right; padding: 3px;">Richtgetal Cl/SO4:</td>
-         <td style="padding: 3px;"><div id="tgt_cl_so4"></div></td>
-         <td style="vertical-align: top; float: right; padding: 3px;">Doel maisch pH:</td>
-         <td style="padding: 3px;"><div id="tgt_mash_ph"></div></td>
+         <td style="padding: 3px;" colspan="2"><div id="tgt_cl_so4"></div></td>
         </tr>
         <tr>
          <td></td>
@@ -287,35 +281,40 @@
 	</tr>
         <tr>
          <td style="vertical-align: top; float: right; padding: 3px;">Calciumchloride (CaCl2):</td>
-         <td style="padding: 3px;"><div id="wa_cacl2"></div></td>
-         <td style="vertical-align: top; float: right; padding: 3px;">Automatisch pH aanpassen:</td>
-         <td style="padding: 3px;"><div id="calc_acid"></div></td>
+	 <td style="padding: 3px;"><div id="wa_cacl2"></div></td>
+         <td style="vertical-align: top; float: right; padding: 3px;">Maish pH:</td>
+         <td style="padding: 3px;"><div id="mash_ph"></div></td>
          <td style="vertical-align: top; float: right; padding: 3px;">Spoelwater volume:</td>
          <td style="padding: 3px;"><div id="sparge_volume"></div></td>
         </tr>
         <tr>
          <td style="vertical-align: top; float: right; padding: 3px;">Gips (CaSO4):</td>
-         <td style="padding: 3px;"><div id="wa_caso4"></div></td>
+	 <td style="padding: 3px;"><div id="wa_caso4"></div></td>
+         <td style="vertical-align: top; float: right; padding: 3px;">pH Automatisch:</td>
+	 <td style="padding: 3px;"><div id="calc_acid"></div></td>
+         <td style="vertical-align: top; float: right; padding: 3px;">Spoelwater temp:</td>
+         <td style="padding: 3px;"><div id="sparge_temp"></div></td>
+        </tr>
+        <tr>
+         <td style="vertical-align: top; float: right; padding: 3px;">Epsom zout (MgSO4):</td>
+	 <td style="padding: 3px;"><div id="wa_mgso4"></div></td>
          <td style="vertical-align: top; float: right; padding: 3px;">Ontzuren met:</td>
          <td style="padding: 3px;"><div style="float: left;" id="wa_base_name"></div><div style="float: left; margin-left: 15px;" id="wa_base"></div></td>
          <td style="vertical-align: top; float: right; padding: 3px;">Spoelwater bron:</td>
          <td style="padding: 3px;"><div id="sparge_source"></div></td>
-        </tr>
-        <tr>
-         <td style="vertical-align: top; float: right; padding: 3px;">Epsom zout (MgSO4):</td>
-         <td style="padding: 3px;"><div id="wa_mgso4"></div></td>
-         <td style="vertical-align: top; float: right; padding: 3px;">Aanzuren met:</td>
-         <td style="padding: 3px;"><div style="float: left;" id="wa_acid_name"></div><div style="float: left; margin-left: 15px;" id="wa_acid"></div><div style="float: left; margin-left: 15px;" id="wa_acid_perc"></div></td>
-         <td style="vertical-align: top; float: right; padding: 3px;">Spoelwater pH:</td>
-         <td style="padding: 3px;"><div id="sparge_ph"></div></td>
-        </tr>
+	</tr>
         <tr>
          <td style="vertical-align: top; float: right; padding: 3px;">Keukenzout (NaCl):</td>
 	 <td style="padding: 3px;"><div id="wa_nacl"></div></td>
-         <td colspan="2"></td>
+         <td style="vertical-align: top; float: right; padding: 3px;">Aanzuren met:</td>
+	 <td style="padding: 3px;"><div style="float: left;" id="wa_acid_name"></div><div style="float: left; margin-left: 15px;" id="wa_acid"></div><div style="float: left; margin-left: 15px;" id="wa_acid_perc"></div></td>
+         <td style="vertical-align: top; float: right; padding: 3px;">Spoelwater pH:</td>
+         <td style="padding: 3px;"><div id="sparge_ph"></div></td>
+	</tr>
+        <tr>
+         <td colspan="4"></td>
          <td style="vertical-align: top; float: right; padding: 3px;">Aanzuren met:</td>
          <td style="padding: 3px;"><div id="sparge_acid_type"></div></td>
-	</tr>
         <tr>
          <td colspan="4"></td>
          <td style="vertical-align: top; float: right; padding: 3px;">Aanzuren hoeveelheid:</td>

mercurial