Thu, 12 Oct 2023 14:19:46 +0200
Version 0.3.44. Moved iSpindel Plato calculation from the php script to bmsd. This uses calibration data in the mon_ispindels table. Setup of this data will be done by the bmsapp applications. Default settings are stored in MySQL. From now on you don't need to store calibration data in the iSpindel.
/***************************************************************************** * Copyright (C) 2014-2023 * * Michiel Broek <mbroek at mbse dot eu> * * This file is part of BMS * * 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. * * BrewCloud 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. *****************************************************************************/ // dropdownlists arrays var StageData = [ { id: 0, en: 'Plan', nl: 'Plan' }, { id: 1, en: 'Wait', nl: 'Wacht' }, { id: 2, en: 'Brew', nl: 'Brouwen' }, { id: 3, en: 'Primary', nl: 'Hoofdgisting' }, { id: 4, en: 'Secondary', nl: 'Nagisting' }, { id: 5, en: 'Tertiary', nl: 'Lagering' }, { id: 6, en: 'Package', nl: 'Afvullen' }, { id: 7, en: 'Carbonation', nl: 'Hergisten' }, { id: 8, en: 'Mature', nl: 'Rijpen' }, { id: 9, en: 'Taste', nl: 'Proeven' }, { id: 10, en: 'Ready', nl: 'Gereed' }, { id: 11, en: 'Closed', nl: 'Afgesloten' } ], StageSource = { localdata: StageData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, StageAdapter = new $.jqx.dataAdapter(StageSource), SplitData = [ { id: 0, en: 'Not divided', nl: 'Niet gesplitst', ok: 100 }, { id: 1, en: 'After mash', nl: 'Na maischen', ok: 2 }, { id: 2, en: 'After boil', nl: 'Na koken', ok: 2 }, { id: 3, en: 'After cooling', nl: 'Na koelen', ok: 2 }, { id: 4, en: 'After primary', nl: 'Na hoofdgisting', ok: 3 }, { id: 5, en: 'After secondary', nl: 'Na nagisting', ok: 4 }, { id: 6, en: 'After tertiary', nl: 'Na lageren', ok: 5 } ], SplitSource = { localdata: SplitData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }, { name: 'ok' }] }, SplitAdapter = new $.jqx.dataAdapter(SplitSource), MaterialData = [ { id: 0, en: 'Stainless Steel', nl: 'RVS', sh: 0.11 }, { id: 1, en: 'Aluminium', nl: 'Aluminium', sh: 0.22 }, { id: 2, en: 'Plastics', nl: 'Kunststof', sh: 0.46 }, { id: 3, en: 'Copper', nl: 'Koper', sh: 0.092 } ], MaterialSource = { localdata: MaterialData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }, { name: 'sh' }] }, MaterialAdapter = new $.jqx.dataAdapter(MaterialSource), FermentableTypeData = [ { id: 0, en: 'Grain', nl: 'Mout' }, { id: 1, en: 'Sugar', nl: 'Suiker' }, { id: 2, en: 'Extract', nl: 'Vloeibaar extract' }, { id: 3, en: 'Dry extract', nl: 'Droog extract' }, { id: 4, en: 'Adjunct', nl: 'Ongemout graan' } ], FermentableTypeSource = { localdata: FermentableTypeData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, FermentableTypeAdapter = new $.jqx.dataAdapter(FermentableTypeSource), GrainTypeData = [ { id: 0, en: 'Base', nl: 'Basismout' }, { id: 1, en: 'Roast', nl: 'Geroosterde mout' }, { id: 2, en: 'Crystal', nl: 'Cara- of crystalmout' }, { id: 3, en: 'Kilned', nl: 'Geƫeste mout'}, { id: 4, en: 'Sour malt', nl: 'Zuurmout' }, { id: 5, en: 'Special', nl: 'Speciale mout' }, { id: 6, en: 'No malt', nl: 'Geen mout' } ], GrainTypeSource = { localdata: GrainTypeData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, GrainTypeAdapter = new $.jqx.dataAdapter(GrainTypeSource), AddedData = [ { id: 0, en: 'Mash', nl: 'Maischen' }, { id: 1, en: 'Boil', nl: 'Koken' }, { id: 2, en: 'Fermentation', nl: 'Vergisten' }, { id: 3, en: 'Lagering', nl: 'Nagisten/lageren' }, { id: 4, en: 'Bottle', nl: 'Bottelen' }, { id: 5, en: 'Kegs', nl: 'Fust' } ], AddedSource = { localdata: AddedData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, AddedAdapter = new $.jqx.dataAdapter(AddedSource), HopTypeData = [ { id: 0, en: 'Bittering', nl: 'Bitterhop' }, { id: 1, en: 'Aroma', nl: 'Aromahop' }, { id: 2, en: 'Both', nl: 'Beide' } ], HopTypeSource = { localdata: HopTypeData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, HopTypeAdapter = new $.jqx.dataAdapter(HopTypeSource), HopFormData = [ { id: 0, en: 'Pellet', nl: 'Pellets' }, { id: 1, en: 'Plug', nl: 'Plugs' }, { id: 2, en: 'Leaf', nl: 'Bloemen' }, { id: 3, en: 'Leaf wet', nl: 'Hop nat' }, { id: 4, en: 'Cryo', nl: 'Cryo' }, { id: 5, en: 'CO2 extract', nl: 'CO2 extract' }, { id: 6, en: 'Iso extract', nl: 'Iso extract' } ], HopFormSource = { localdata: HopFormData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, HopFormAdapter = new $.jqx.dataAdapter(HopFormSource), HopUseData = [ { id: 0, en: 'Mash', nl: 'Maischhop' }, { id: 1, en: 'First wort', nl: 'First wort' }, { id: 2, en: 'Boil', nl: 'Koken' }, { id: 3, en: 'Aroma', nl: 'Vlamuit' }, { id: 4, en: 'Whirlpool', nl: 'Whirlpool' }, { id: 5, en: 'Dry hop', nl: 'Koudhop' }, { id: 6, en: 'Bottling', nl: 'Bottelen' } ], HopUseSource = { localdata: HopUseData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, HopUseAdapter = new $.jqx.dataAdapter(HopUseSource), YeastTypeData = [ { id: 0, en: 'Lager', nl: 'Ondergist' }, { id: 1, en: 'Ale', nl: 'Bovengist' }, { id: 2, en: 'Wheat', nl: 'Tarwegist' }, { id: 3, en: 'Wine', nl: 'Wijngist' }, { id: 4, en: 'Champagne', nl: 'Champagnegist' }, { id: 5, en: 'Brett', nl: 'Brett' }, { id: 6, en: 'Kveik', nl: 'Kveik' }, { id: 7, en: 'Hybrid', nl: 'Hybride' } // { id: 8, en: 'Mixed', nl: 'Mixed' }, // { id: 9, en: 'Spontaneous', nl: 'Spontaan' }, // { id: 10, en: 'Other', nl: 'Overig' } ], YeastTypeSource = { localdata: YeastTypeData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, YeastTypeAdapter = new $.jqx.dataAdapter(YeastTypeSource), YeastFormData = [ { id: 0, en: 'Liquid', nl: 'Vloeibaar', cells: 100000000000 }, { id: 1, en: 'Dry', nl: 'Droog', cells: 15000000000 }, { id: 2, en: 'Slant', nl: 'Schuine buis', cells: 1700000000 }, { id: 3, en: 'Culture', nl: 'Slurry', cells: 1700000000 }, { id: 4, en: 'Frozen', nl: 'Ingevroren', cells: 1700000000 }, { id: 5, en: 'Bottle', nl: 'Depot', cells: 1700000000 }, { id: 6, en: 'Dried', nl: 'Gedroogd', cells: 9000000000 } /* 9..18 billion MTF */ ], YeastFormSource = { localdata: YeastFormData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }, { name: 'cells' }] }, YeastFormAdapter = new $.jqx.dataAdapter(YeastFormSource), YeastUseData = [ { id: 0, en: 'Primary', nl: 'Hoofdgisting' }, { id: 1, en: 'Secondary', nl: 'Nagisting' }, { id: 2, en: 'Tertiary', nl: 'Lagering' }, { id: 3, en: 'Bottle', nl: 'Bottelen' } ], YeastUseSource = { localdata: YeastUseData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }, { name: 'cells' }] }, YeastUseAdapter = new $.jqx.dataAdapter(YeastUseSource), FlocculationData = [ { id: 0, en: 'Low', nl: 'Laag' }, { id: 1, en: 'Medium', nl: 'Medium' }, { id: 2, en: 'High', nl: 'Hoog' }, { id: 3, en: 'Very high', nl: 'Zeer hoog' } ], FlocculationSource = { localdata: FlocculationData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, FlocculationAdapter = new $.jqx.dataAdapter(FlocculationSource), ZymocideData = [ { id: 0, en: 'None', nl: 'Niet' }, { id: 1, en: 'K1', nl: 'K1' }, { id: 2, en: 'K2', nl: 'K2' }, { id: 3, en: 'K28', nl: 'K28' }, { id: 4, en: 'Klus', nl: 'Klus' } ], ZymocideSource = { localdata: ZymocideData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, ZymocideAdapter = new $.jqx.dataAdapter(ZymocideSource), StarterTypeData = [ { id: 0, en: 'Stirred', nl: 'Geroerd' }, { id: 1, en: 'Shaken', nl: 'Geschud' }, { id: 2, en: 'Simple', nl: 'Simpel' } ], StarterTypeSource = { localdata: StarterTypeData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, StarterTypeAdapter = new $.jqx.dataAdapter(StarterTypeSource), PitchrateData = [ { rate: 0, nl: ' - Kies factor - ' }, { rate: 0.75, nl: 'Ale, 0.75' }, { rate: 1.0, nl: 'Ale, 1.00 zwaar > 1.060' }, { rate: 1.25, nl: 'Ale, 1.25 zwaar > 1.076' }, { rate: 1.5, nl: 'Lager 1.50' }, { rate: 2.0, nl: 'Lager, 2.00 zwaar > 1.060' }, { rate: 0.075, nl: 'Kveik, 0.075 origineel' } ], PitchrateSource = { localdata: PitchrateData, datatype: 'array', datafields: [{ name: 'rate' }, { name: 'nl' }] }, PitchrateAdapter = new $.jqx.dataAdapter(PitchrateSource), MiscTypeData = [ { id: 0, en: 'Spice', nl: 'Specerij' }, { id: 1, en: 'Herb', nl: 'Kruid' }, { id: 2, en: 'Flavor', nl: 'Smaakstof' }, { id: 3, en: 'Fining', nl: 'Klaringsmiddel' }, { id: 4, en: 'Water agent', nl: 'Brouwzout' }, { id: 5, en: 'Yeast nutrient', nl: 'Gistvoeding' }, { id: 6, en: 'Other', nl: 'Anders' } ], MiscTypeSource = { localdata: MiscTypeData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, MiscTypeAdapter = new $.jqx.dataAdapter(MiscTypeSource), MiscUseData = [ { id: 0, en: 'Starter', nl: 'Starter' }, { id: 1, en: 'Mash', nl: 'Maischen' }, { id: 2, en: 'Boil', nl: 'Koken' }, { id: 3, en: 'Primary', nl: 'Hoofdvergisting' }, { id: 4, en: 'Secondary', nl: 'Nagisting/lagering' }, { id: 5, en: 'Bottling', nl: 'Bottelen' }, { id: 6, en: 'Sparge', nl: 'Spoelen' } ], MiscUseSource = { localdata: MiscUseData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, MiscUseAdapter = new $.jqx.dataAdapter(MiscUseSource), StyleTypeData = [ { id: 0, en: 'Lager', nl: 'Ondergistend bier' }, { id: 1, en: 'Ale', nl: 'Bovengistend bier' }, { id: 2, en: 'Mead', nl: 'Mede' }, { id: 3, en: 'Wheat', nl: 'Tarwebier' }, { id: 4, en: 'Mixed', nl: 'Gemengd' }, { id: 5, en: 'Cider', nl: 'Cider' } ], StyleTypeSource = { localdata: StyleTypeData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, StyleTypeAdapter = new $.jqx.dataAdapter(StyleTypeSource), MashStepTypeData = [ { id: 0, en: 'Infusion', nl: 'Infusie' }, { id: 1, en: 'Temperature', nl: 'Verwarming' }, { id: 2, en: 'Decoction', nl: 'Decoctie' } ], MashStepTypeSource = { localdata: MashStepTypeData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, MashStepTypeAdapter = new $.jqx.dataAdapter(MashStepTypeSource), RecipeTypeData = [ { id: 0, en: 'Extract', nl: 'Extract' }, { id: 1, en: 'Partial Mash', nl: 'Deelmaisch' }, { id: 2, en: 'All Grain', nl: 'Mout' } ], RecipeTypeSource = { localdata: RecipeTypeData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, RecipeTypeAdapter = new $.jqx.dataAdapter(RecipeTypeSource), IBUmethodData = [ { id: 0, en: 'Tinseth', nl: 'Tinseth' }, { id: 1, en: 'Tinseth++', nl: 'Tinseth++' }, { id: 2, en: 'Daniels', nl: 'Daniels' } ], IBUmethodSource = { localdata: IBUmethodData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, IBUmethodAdapter = new $.jqx.dataAdapter(IBUmethodSource), ColorMethodData = [ { id: 0, en: 'Morey', nl: 'Morey' }, { id: 1, en: 'Mosher', nl: 'Mosher' }, { id: 2, en: 'Daniels', nl: 'Daniels' }, { id: 3, en: 'Halberstadt', nl: 'Halberstadt' }, { id: 4, en: 'Naudts', nl: 'Naudts' } ], ColorMethodSource = { localdata: ColorMethodData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, ColorMethodAdapter = new $.jqx.dataAdapter(ColorMethodSource), CoolingTypeData = [ { id: 0, en: '-', nl: '-' }, { id: 1, en: 'Immersion chiller', nl: 'Dompelkoeler' }, { id: 2, en: 'Counterflow chiller', nl: 'Tegenstroomkoeler' }, { id: 3, en: 'Au bain marie', nl: 'Au bain marie' }, { id: 4, en: 'No-chill', nl: 'Laten afkoelen' } ], CoolingTypeSource = { localdata: CoolingTypeData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, CoolingTypeAdapter = new $.jqx.dataAdapter(CoolingTypeSource), AerationTypeData = [ { id: 0, en: 'None', nl: 'Geen' }, { id: 1, en: 'Air', nl: 'Lucht' }, { id: 2, en: 'Oxygen', nl: 'Zuurstof' } ], AerationTypeSource = { localdata: AerationTypeData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, AerationTypeAdapter = new $.jqx.dataAdapter(AerationTypeSource), AcidTypeData = [ /* Note: AcidSG is for 100% use. AcidPrc is the default setting. */ { id: 0, en: 'Lactic', nl: 'Melkzuur', pK1: 3.86, pK2: 20, pK3: 20, MolWt: 90.08, AcidSG: 1238, AcidPrc: 80 }, { id: 1, en: 'Hydrochloric', nl: 'Zoutzuur', pK1: -7, pK2: 20, pK3: 20, MolWt: 36.46, AcidSG: 1497, AcidPrc: 28 }, { id: 2, en: 'Phosphoric', nl: 'Fosforzuur', pK1: 2.12, pK2: 7.20, pK3: 12.44, MolWt: 98.00, AcidSG: 1773, AcidPrc: 75 }, { id: 3, en: 'Sulfuric', nl: 'Zwavelzuur', pK1: -1, pK2: 1.92, pK3: 20, MolWt: 98.07, AcidSG: 1884, AcidPrc: 93 } ], AcidTypeSource = { localdata: AcidTypeData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, AcidTypeAdapter = new $.jqx.dataAdapter(AcidTypeSource), SpargeSourceData = [ { id: 0, en: 'Source 1', nl: 'Bron 1' }, { id: 1, en: 'Source 2', nl: 'Bron 2' }, { id: 2, en: 'Mixed', nl: 'Gemengd' } ], SpargeSourceSource = { localdata: SpargeSourceData, datatype: 'array', datafields: [{ name: 'id' }, { name: 'en' }, { name: 'nl' }] }, SpargeSourceAdapter = new $.jqx.dataAdapter(SpargeSourceSource), // options for editors Show1wat = { inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true }, Show2wat = { inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 2, readOnly: true }, Show3wat = { inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 3, readOnly: true }, Smal0dec = { inputMode: 'simple', theme: theme, width: 50, height: 23, decimalDigits: 0, readOnly: true }, Smal1dec = { inputMode: 'simple', theme: theme, width: 50, height: 23, decimalDigits: 1, readOnly: true }, Smal2dec = { inputMode: 'simple', theme: theme, width: 50, height: 23, decimalDigits: 2, readOnly: true }, Show0dec = { inputMode: 'simple', theme: theme, width: 90, height: 23, readOnly: true, decimalDigits: 0 }, Show1dec = { inputMode: 'simple', theme: theme, width: 90, height: 23, readOnly: true, decimalDigits: 1 }, Show2dec = { inputMode: 'simple', theme: theme, width: 90, height: 23, readOnly: true, decimalDigits: 2 }, Show3dec = { inputMode: 'simple', theme: theme, width: 90, height: 23, readOnly: true, decimalDigits: 3 }, Show4dec = { inputMode: 'simple', theme: theme, width: 90, height: 23, readOnly: true, decimalDigits: 4 }, Show5dec = { inputMode: 'simple', theme: theme, width: 90, height: 23, readOnly: true, decimalDigits: 5 }, SGopts = { inputMode: 'simple', theme: theme, width: 110, height: 23, min: 0.990, max: 1.199, decimalDigits: 3, spinButtons: true }, Spin1dec = { inputMode: 'simple', theme: theme, width: 110, height: 23, min: 0, decimalDigits: 1, spinButtons: true }, Spin2dec = { inputMode: 'simple', theme: theme, width: 110, height: 23, min: 0, decimalDigits: 2, spinButtons: true }, Spin3dec = { inputMode: 'simple', theme: theme, width: 110, height: 23, min: 0, decimalDigits: 3, spinButtons: true }, Spin4dec = { inputMode: 'simple', theme: theme, width: 110, height: 23, min: 0, decimalDigits: 4, spinButtons: true }, SpinpH = { inputMode: 'simple', theme: theme, width: 110, height: 23, min: 1, max: 14, decimalDigits: 1, spinButtons: true }, Spin2pH = { inputMode: 'simple', theme: theme, width: 110, height: 23, min: 1, max: 14, decimalDigits: 2, spinButtons: true }, YeastT = { inputMode: 'simple', theme: theme, width: 110, height: 23, min: 0, max: 45, decimalDigits: 1, spinButtons: true }, PosInt = { inputMode: 'simple', theme: theme, width: 110, height: 23, min: 0, decimalDigits: 0, spinButtons: true }, Perc1dec = { inputMode: 'simple', theme: theme, width: 110, height: 23, min: 0, max: 100, decimalDigits: 1, spinButtons: true }, Perc0 = { inputMode: 'simple', theme: theme, width: 110, height: 23, min: 0, max: 100, decimalDigits: 0, spinButtons: true }, Dateopts = { theme: theme, width: 150, height: 23, allowNullDate: true, todayString: 'Vandaag', clearString: 'Wissen', showFooter: true, formatString: 'yyyy-MM-dd', enableBrowserBoundsDetection: true }, DateTimeopts = { theme: theme, width: 230, height: 23, allowNullDate: true, todayString: 'Vandaag', clearString: 'Wissen', showFooter: true, formatString: 'yyyy-MM-dd HH:mm:ss', enableBrowserBoundsDetection: true, showTimeButton: true }, sugardensity = 1.611, //kg/l in solution // Styles dropdown list stylesSource = { datatype: 'json', datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'category', type: 'string' }, { name: 'category_number', type: 'number' }, { name: 'style_letter', type: 'string' }, { name: 'style_guide', type: 'string' }, { name: 'type', type: 'int' }, { name: 'og_min', type: 'float' }, { name: 'og_max', type: 'float' }, { name: 'fg_min', type: 'float' }, { name: 'fg_max', type: 'float' }, { name: 'ibu_min', type: 'float' }, { name: 'ibu_max', type: 'float' }, { name: 'color_min', type: 'float' }, { name: 'color_max', type: 'float' }, { name: 'carb_min', type: 'float' }, { name: 'carb_max', type: 'float' }, { name: 'abv_min', type: 'float' }, { name: 'abv_max', type: 'float' }, { name: 'notes', type: 'string' }, { name: 'profile', type: 'string' }, { name: 'ingredients', type: 'string' }, { name: 'examples', type: 'string' } ], url: 'includes/db_profile_styles.php' }, styleslist = new $.jqx.dataAdapter(stylesSource), // Equipemnt dropdown list equipmentSource = { datatype: 'json', datafields: [ { name: 'name', type: 'string' }, { name: 'boil_size', type: 'float' }, { name: 'batch_size', type: 'float' }, { name: 'tun_volume', type: 'float' }, { name: 'tun_weight', type: 'float' }, { name: 'tun_specific_heat', type: 'float' }, { name: 'tun_material', type: 'int' }, { name: 'tun_height', type: 'float' }, { name: 'top_up_water', type: 'float' }, { name: 'trub_loss', type: 'float' }, { name: 'evap_rate', type: 'float' }, { name: 'boil_time', type: 'float' }, { name: 'calc_boil_volume', type: 'int' }, { name: 'top_up_kettle', type: 'float' }, { name: 'notes', type: 'string' }, { name: 'lauter_volume', type: 'float' }, { name: 'lauter_height', type: 'float' }, { name: 'lauter_deadspace', type: 'float' }, { name: 'kettle_volume', type: 'float' }, { name: 'kettle_height', type: 'float' }, { name: 'mash_volume', type: 'float' }, { name: 'mash_max', type: 'float' }, { name: 'efficiency', type: 'float' } ], url: 'includes/db_inventory_equipments.php' }, equipmentlist = new $.jqx.dataAdapter(equipmentSource), // dropdownlist datasource from inventory_fermentables fermentableInvSource = { datatype: 'json', datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'type', type: 'int' }, { name: 'yield', type: 'float' }, { name: 'color', type: 'float' }, { name: 'add_after_boil', type: 'int' }, { name: 'origin', type: 'string' }, { name: 'supplier', type: 'string' }, { name: 'coarse_fine_diff', type: 'float' }, { name: 'moisture', type: 'float' }, { name: 'diastatic_power', type: 'float' }, { name: 'protein', type: 'float' }, { name: 'dissolved_protein', type: 'float' }, { name: 'max_in_batch', type: 'float' }, { name: 'recommend_mash', type: 'int' }, { name: 'graintype', type: 'int' }, { name: 'di_ph', type: 'float' }, { name: 'acid_to_ph_57', type: 'float' }, { name: 'inventory', type: 'float' }, { name: 'cost', type: 'float' } ], url: 'getfermentablesources.php' }, fermentableinstock = false, fermentablelist = new $.jqx.dataAdapter(fermentableInvSource, { beforeLoadComplete: function(records) { var row, i, data = new Array(); for (i = 0; i < records.length; i++) { row = records[i]; if (row.inventory || ! fermentableinstock) data.push(row); } return data; }, loadError: function(jqXHR, status, error) { console.log(status + ' ' + error); }, }), fermentablesugars = new $.jqx.dataAdapter(fermentableInvSource, { beforeLoadComplete: function(records) { var row, i, data = new Array(); for (i = 0; i < records.length; i++) { row = records[i]; if (row.type == 1 || row.type == 3) // Sugars or dry extract data.push(row); } return data; }, loadError: function(jqXHR, status, error) { console.log(status + ' ' + error); }, }), // dropdownlist datasource from inventory_hops hopInvSource = { datatype: 'json', datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'origin', type: 'string' }, { name: 'type', type: 'int' }, { name: 'alpha', type: 'float' }, { name: 'beta', type: 'float' }, { name: 'humulene', type: 'float' }, { name: 'caryophyllene', type: 'float' }, { name: 'cohumulone', type: 'float' }, { name: 'myrcene', type: 'float' }, { name: 'hsi', type: 'float' }, { name: 'useat', type: 'int' }, { name: 'form', type: 'int' }, { name: 'total_oil', type: 'float' }, { name: 'inventory', type: 'float' }, { name: 'cost', type: 'float' } ], url: 'gethopsources.php' }, hopinstock = false, hoplist = new $.jqx.dataAdapter(hopInvSource, { beforeLoadComplete: function(records) { var row, i, data = new Array(); for (i = 0; i < records.length; i++) { row = records[i]; if (row.inventory || ! hopinstock) data.push(row); } return data; }, loadError: function(jqXHR, status, error) { console.log(status + ' ' + error); }, }), // dropdownlist datasource from inventory_miscs miscInvSource = { datatype: 'json', datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'type', type: 'int' }, { name: 'use_use', type: 'int' }, { name: 'amount_is_weight', type: 'int' }, { name: 'time', type: 'float' }, { name: 'inventory', type: 'float' }, { name: 'cost', type: 'float' } ], url: 'getmiscsources.php' }, miscinstock = false, misclist = new $.jqx.dataAdapter(miscInvSource, { beforeLoadComplete: function(records) { var row, i, data = new Array(); for (i = 0; i < records.length; i++) { row = records[i]; if (row.inventory || ! miscinstock) data.push(row); } return data; }, loadError: function(jqXHR, status, error) { console.log(status + ' ' + error); }, }), // dropdownlist datasource from inventory_yeasts yeastInvSource = { datatype: 'json', datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'type', type: 'int' }, { name: 'form', type: 'int' }, { name: 'laboratory', type: 'string' }, { name: 'product_id', type: 'string' }, { name: 'min_temperature', type: 'float' }, { name: 'max_temperature', type: 'float' }, { name: 'flocculation', type: 'int' }, { name: 'attenuation', type: 'float' }, { name: 'cells', type: 'float' }, { name: 'inventory', type: 'float' }, { name: 'cost', type: 'float' }, { name: 'tolerance', type: 'float' }, { name: 'sta1', type: 'int' }, { name: 'bacteria', type: 'int' }, { name: 'harvest_top', type: 'int' }, { name: 'harvest_time', type: 'int' }, { name: 'pitch_temperature', type: 'float' }, { name: 'pofpos', type: 'int' }, { name: 'zymocide', type: 'int' }, { name: 'gr_hl_lo', type: 'int' }, { name: 'sg_lo', type: 'float' }, { name: 'gr_hl_hi', type: 'int' }, { name: 'sg_hi', type: 'float' } ], url: 'getyeastsources.php' }, yeastinstock = false, yeastlist = new $.jqx.dataAdapter(yeastInvSource, { beforeLoadComplete: function(records) { var row, i, data = new Array(); for (i = 0; i < records.length; i++) { row = records[i]; if (row.inventory || ! yeastinstock) data.push(row); } return data; }, loadError: function(jqXHR, status, error) { console.log(status + ' ' + error); }, }), yeastlablist = new $.jqx.dataAdapter(yeastInvSource, { autoBind: false, async: false, uniqueDataFields: ['laboratory'] }), // dropdownlist datasource from inventory_waters waterInvSource = { datatype: 'json', datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'unlimited_stock', type: 'int' }, { name: 'calcium', type: 'float' }, { name: 'sulfate', type: 'float' }, { name: 'chloride', type: 'float' }, { name: 'sodium', type: 'float' }, { name: 'magnesium', type: 'float' }, { name: 'ph', type: 'float' }, { name: 'total_alkalinity', type: 'float' }, { name: 'inventory', type: 'float' }, { name: 'cost', type: 'float' }, ], url: 'getwatersources.php' }, waterinstock = false, waterlist = new $.jqx.dataAdapter(waterInvSource, { beforeLoadComplete: function(records) { var data, i, row; data = new Array(); for (i = 0; i < records.length; i++) { row = records[i]; if (row.inventory || row.unlimited_stock || ! waterinstock) data.push(row); } return data; }, loadError: function(jqXHR, status, error) { console.log(status + ' ' + error); }, }), water2list = new $.jqx.dataAdapter(waterInvSource, { beforeLoadComplete: function(records) { var data, i, row, none = {}; data = new Array(); none['name'] = 'Geen mengwater'; /* Put empty water on top */ none['unlimited_stock'] = 0; none['calcium'] = 0; none['sulfate'] = 0; none['chloride'] = 0; none['sodium'] = 0; none['magnesium'] = 0; none['ph'] = 0; none['total_alkalinity'] = 0; none['inventory'] = 0; none['cost'] = 0; data.push(none); for (i = 0; i < records.length; i++) { row = records[i]; if (row.inventory || row.unlimited_stock || ! waterinstock) data.push(row); } return data; }, loadError: function(jqXHR, status, error) { console.log(status + ' ' + error); }, }), // dropdownlist datasource from profile_water waterProfileSource = { datatype: 'json', datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'calcium', type: 'float' }, { name: 'bicarbonate', type: 'float' }, { name: 'sulfate', type: 'float' }, { name: 'chloride', type: 'float' }, { name: 'sodium', type: 'float' }, { name: 'magnesium', type: 'float' }, { name: 'ph', type: 'float' }, { name: 'total_alkalinity', type: 'float' }, ], url: 'includes/db_profile_water.php' }, waterprofiles = new $.jqx.dataAdapter(waterProfileSource), // dropdownlist datasource from profile_mash mashProfileSource = { datatype: 'json', datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'steps', type: 'array' } ], url: 'includes/db_profile_mash.php' }, mashlist = new $.jqx.dataAdapter(mashProfileSource); /* Websocket interface. Place "websocket.onmessage = function(evt) {}" in the user script. */ var websocket = new ReconnectingWebSocket('ws://'+location.hostname+'/ws'); websocket.onerror = function(event) { console.log('Websocket error: ' + event.data); } $(document).ready(function() { $('#jqxMenu').jqxMenu({ width: 1280, height: '30px', autoOpen: false, clickToOpen: true, theme: theme }); $('#jqxWidget').css('visibility', 'visible'); }); function Round(n, d) { for (var i = 0, m = 1; i < d; i++, m *= 10); return Math.round(n * m) / m; } function ebc_to_srm(ebc) { var srm = -1.32303E-12 * Math.pow(ebc, 4) - 0.00000000291515 * Math.pow(ebc, 3) + 0.00000818515 * Math.pow(ebc, 2) + 0.372038 * ebc + 0.596351; if (srm < 0) srm = 0; return srm; } function srm_to_ebc(srm) { var ebc = Math.round(0.000000000176506 * Math.pow(srm, 4) + 0.000000154529 * Math.pow(srm, 3) - 0.000159428 * Math.pow(srm, 2) + 2.68837 * srm - 1.6004); if (ebc < 0) ebc = 0; return ebc; } /* Return incremented color by the boil and yeast. * https://www.hobbybrouwen.nl/forum/index.php/topic,19020.msg281132.html#msg281132 */ function get_kt(ebc) { var kt = 1; if (ebc < 3) kt = 3.5; else if (ebc < 6) kt = 3; else if (ebc < 8) kt = 2.75; else if (ebc < 10) kt = 2.5; else if (ebc < 20) kt = 1.8; else if (ebc < 30) kt = 1.6; else if (ebc < 60) kt = 1.3; else if (ebc < 100) kt = 1.2; else if (ebc < 300) kt = 1.1; return kt; } function abvol(og, fg) { if (((og - fg) < 0) || (fg < 0.9)) return 0; var factor = og * 3157 * Math.pow(10, -5) + 9.716 * Math.pow(10, -2); return (og * 1000 - fg * 1000) * factor; } /* Kleurwerking naar SRM. Niet voor Halberstadt, Naudts */ function kw_to_srm(colormethod, c) { if (colormethod == 0) return 1.4922 * Math.pow(c, 0.6859); //Morey if (colormethod == 1) return 0.3 * c + 4.7; //Mosher if (colormethod == 2) return 0.2 * c + 8.4; //Daniels return 0; //Halberstadt,Naudts } function kw_to_ebc(colormethod, c) { return srm_to_ebc(kw_to_srm(colormethod, c)); } /* * Berekeningen uit https://www.hobbybrouwen.nl/forum/index.php/topic,6079.msg69464.html#msg69464 */ function toIBU(Use, Form, SG, Volume, Amount, Boiltime, Alpha, Method, Whirlpool9, Whirlpool7, Whirlpool6) { var gravity, liters, alpha, mass, time, fmoment = 1.0, pfactor = 1.0, ibu = 0, boilfactor, sgfactor, AddedAlphaAcids, Bigness_factor, BoilTime_factor, utiisation; gravity = parseFloat(SG); liters = parseFloat(Volume); alpha = parseFloat(Alpha) / 100; mass = parseFloat(Amount) * 1000; time = parseFloat(Boiltime); if ((Use == 3) || (Use == 4) || (Use == 5)) { // Aroma, Whirlpool or Dry hop. fmoment = 0.0; } else if (Use == 0) { // Mash fmoment += my_factor_mashhop / 100; // Brouwhulp } else if (Use == 1) { // First wort fmoment += my_factor_fwh / 100; // Brouwhulp, Louis, Ozzie } if (Form == 0) { // Pellet pfactor += my_factor_pellet / 100; } else if (Form == 1) { // Plug pfactor += my_factor_plug / 100; } else if (Form == 3) { // Wet leaf pfactor += my_factor_wethop / 100; // From https://github.com/chrisgilmerproj/brewday/blob/master/brew/constants.py } else if (Form == 4) { // Cryo hop pfactor += my_factor_cryohop / 100; } // Ideas from Zymurgy March-April 2018. These are not exact formulas! whirlibus = 0; if (Use == 3 || Use == 4) { // Flameout or any whirlpool if (Whirlpool9) { // 20 mg/l/50 min whirlibus += (alpha * mass * 20) / liters * (Whirlpool9 / 50); //console.log('Whirlpool9:' + alpha * mass * 20 + ' liter:' + liters + ' time:' + Whirlpool9 + ' ibu' + (alpha * mass * 20) / liters * (Whirlpool9 / 50)); } else { if (Use == 3) // Flameout hops are 2 minutes in this range. whirlibus += (alpha * mass * 20) / liters * (2 / 50); } if (Whirlpool7) { // 6 mg/l/50 min whirlibus += (alpha * mass * 6) / liters * (Whirlpool7 / 50); //console.log('Whirlpool7:' + alpha * mass * 6 + ' liter:' + liters + ' time:' + Whirlpool7 + ' ibu' + (alpha * mass * 6) / liters * (Whirlpool7 / 50)); } else { if (Use == 3) // Flameout hops are 4 minutes in this range. whirlibus += (alpha * mass * 6) / liters * (4 / 50); } if (Whirlpool6) { // 2 mg/l/50 min whirlibus += (alpha * mass * 2) / liters * (Whirlpool6 / 50); //console.log('Whirlpool6:' + alpha * mass * 2 + ' liter:' + liters + ' time:' + Whirlpool6 + ' ibu' + (alpha * mass * 2) / liters * (Whirlpool6 / 50)); } } if (Method == 0) { // Tinseth /* http://realbeer.com/hops/research.html */ AddedAlphaAcids = (alpha * mass * 1000) / liters; Bigness_factor = 1.65 * Math.pow(0.000125, gravity - 1); BoilTime_factor = ((1 - Math.exp(-0.04 * time)) / 4.15); utiisation = Bigness_factor * BoilTime_factor; ibu = Round(utiisation * AddedAlphaAcids * fmoment * pfactor + whirlibus, 1); } if (Method == 2) { // Daniels if (Form == 2) // Leaf boilfactor = -(0.0041 * time * time) + (0.6162 * time) + 1.5779; else boilfactor = -(0.0051 * time * time) + (0.7835 * time) + 1.9348; if (gravity < 1050) sgfactor = 0; else sgfactor = (gravity - 1050) / 200; ibu = Round(fmoment * ((mass * (alpha * 100) * boilfactor * 0.1) / (liters * (1 + sgfactor))) + whirlibus, 1); } if (Method == 1) { // Rager boilfactor = fmoment * 18.11 + 13.86 * Math.tanh((time * 31.32) / 18.27); if (gravity < 1050) sgfactor = 0; else sgfactor = (gravity - 1050) / 200; ibu = Round((mass * (alpha * 100) * boilfactor * 0.1) / (liters * (1 + sgfactor)) + whirlibus, 1); } // console.log('toIBU(' + Use + ',' + Form + ',' + SG + ',' + Volume + ',' + Amount + ',' + Boiltime + ',' + // Alpha + ',' + Method + ',' + Whirlpool9 + ',' + Whirlpool7 + ',' + Whirlpool6 + '):' + ibu + ' fm:' + fmoment + ' pf:' + pfactor); return ibu; } function ebc_to_color(ebc) { return srm_to_color(ebc_to_srm(ebc)); } function srm_to_color(srm) { var i, R, G, B, color, result; i = Math.round(srm * 10); if (i < 0) i = 0; if (i > 299) i = 299; /* Table copied from Brouwhulp/BrewBuddy */ R = [ 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, //0 250, 250, 250, 250, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 239, 238, 237, 236, 235, //2 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224, 223, 222, 221, 220, 219, 218, 217, 216, 215, //4 214, 213, 212, 211, 210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 200, 199, 199, 198, 198, //6 197, 197, 196, 196, 195, 195, 194, 194, 193, 193, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, //8 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, //10 192, 192, 192, 192, 192, 192, 192, 192, 191, 190, 189, 188, 187, 186, 185, 184, 183, 182, 181, 180, //12 179, 178, 177, 175, 174, 172, 171, 169, 168, 167, 195, 164, 162, 161, 159, 158, 157, 155, 154, 152, //14 151, 149, 148, 147, 145, 144, 142, 141, 139, 138, 137, 135, 134, 132, 131, 129, 128, 127, 125, 124, //16 122, 121, 119, 118, 117, 115, 114, 112, 111, 109, 108, 107, 105, 104, 102, 101, 99, 98, 97, 95, //18 94, 92, 91, 89, 88, 87, 85, 84, 82, 81, 79, 78, 77, 75, 74, 72, 71, 69, 68, 67, //20 65, 64, 62, 61, 59, 58, 57, 55, 54, 52, 51, 49, 48, 47, 45, 44, 43, 41, 39, 38, //22 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 32, 32, 31, 31, 30, 30, 29, 29, 28, 28, //24 27, 27, 26, 26, 25, 25, 24, 24, 23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 18, 18, //26 17, 17, 16, 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 11, 10, 10, 9, 9, 8, 8]; G = [ 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 249, 248, 247, 246, 245, 244, 242, 240, 238, 236, 234, 232, 230, 228, 226, 224, 222, 220, 218, 216, 214, 212, 210, 208, 206, 204, 202, 200, 198, 196, 194, 192, 190, 188, 186, 184, 182, 180, 178, 176, 174, 172, 170, 168, 166, 164, 162, 160, 158, 156, 154, 152, 150, 148, 146, 144, 142, 141, 140, 139, 139, 138, 137, 136, 136, 135, 134, 133, 133, 132, 131, 130, 130, 129, 128, 127, 127, 126, 125, 124, 124, 123, 122, 121, 121, 120, 119, 118, 118, 117, 116, 115, 115, 114, 113, 112, 112, 111, 110, 109, 109, 108, 107, 106, 106, 105, 104, 103, 103, 102, 101, 100, 100, 99, 98, 97, 97, 96, 95, 94, 94, 93, 92, 91, 91, 90, 89, 88, 88, 87, 86, 85, 85, 84, 83, 82, 82, 81, 80, 79, 78, 77, 76, 75, 75, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67, 66, 66, 65, 64, 63, 63, 62, 61, 60, 60, 59, 58, 57, 57, 56, 55, 54, 54, 53, 52, 51, 51, 50, 49, 48, 48, 47, 46, 45, 45, 44, 43, 42, 42, 41, 40, 39, 39, 38, 37, 36, 36, 35, 34, 33, 33, 32, 31, 30, 30, 29, 28, 27, 27, 26, 25, 24, 24, 23, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3]; B = [ 210, 204, 199, 193, 188, 182, 177, 171, 166, 160, 155, 149, 144, 138, 133, 127, 122, 116, 111, 105, 100, 94, 89, 83, 78, 72, 67, 61, 56, 50, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 55, 55, 55, 55, 54, 54, 54, 54, 53, 53, 53, 53, 52, 52, 52, 52, 51, 51, 51, 51, 50, 50, 50, 50, 49, 49, 48, 47, 47, 46, 45, 45, 44, 43, 43, 42, 41, 41, 40, 39, 39, 38, 37, 37, 36, 35, 34, 33, 32, 31, 29, 28, 27, 26, 25, 24, 23, 21, 20, 19, 18, 17, 16, 15, 13, 12, 11, 10, 9, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2]; color = R[i] * 65536 + G[i] * 256 + B[i]; result = color.toString(16).toUpperCase(); if (result.length < 6) result = '0' + result; return '#' + result; } function sg_to_plato(sg) { // return ((135.997 * sg - 630.272) * sg + 1111.14) * sg - 616.868; return -668.962 + (1262.45 * sg) - (776.43 * sg * sg) + (182.94 * sg * sg * sg); } function plato_to_sg(plato) { // return 1 + (plato / (258.6 - ((plato / 258.2) * 227.1))); return 1.00001 + (0.0038661 * plato) + (1.3488e-5 * plato * plato) + (4.3074e-8 * plato * plato * plato); } function calc_svg(og, fg) { var oe = sg_to_plato(og); var ae = sg_to_plato(fg); return (oe - ae) / oe * 100; } function brix_to_sg(brix) { if (my_brix_correction > 0) return plato_to_sg(brix / my_brix_correction); else return plato_to_sg(brix); } function sg_to_brix(sg) { return sg_to_plato(sg) * my_brix_correction; } function brix_to_fg(OPt, FBrix) { // OPt = Original Plato // FBrix = Refractometer reading. // Brouwhulp var FGbh = Round(1.0031 - 0.002318474 * OPt - 0.000007775 * (OPt * OPt) - 0.000000034 * Math.pow(OPt, 3) + 0.00574 * (FBrix) + 0.00003344 * (FBrix * FBrix) + 0.000000086 * Math.pow(FBrix, 3), 4); // from http://seanterrill.com FGnc = new cubic var FBc = FBrix / my_brix_correction; // New cubic by Sean Terrill. Not good enough. //var FGnc=Round(1-0.0044993*(OPt)+0.0117741*(FBc)+0.000275806*(OPt*OPt)-0.00127169*(FBc*FBc)-0.00000727999*Math.pow(OPt,3)+0.0000632929*Math.pow(FBc,3),4); // Petr Novotny, Zymurgy July/August 2017. Used by Brewers Friend. var FGbf = Round(1 + 0.006276 * FBc - 0.002349 * OPt, 4); // The real battle is now between Brouwhulp and Petr Novotny. console.log('brix_to_fg(' + Round(OPt, 2) + ', ' + FBrix + ') FGbh:' + FGbh + ' FGbf:' + FGbf); return FGbf; } function estimate_sg(sugars, batch_size) { var plato, sg, i; plato = 100 * sugars / batch_size; sg = plato_to_sg(plato); for (i = 0; i < 20; i++) { if (sg > 0) plato = 100 * sugars / (batch_size * sg); sg = plato_to_sg(plato); } return Round(sg, 4); } function estimate_fg(percSugar, percCara, WGratio, TotTme, Temp, attenuation, og) { var BD, AttBeer, fg; if (percSugar > 40) percSugar = 0; if (percCara > 50) percCara = 0; if ((WGratio > 0) && (TotTme > 0)) { BD = WGratio; if (BD < 2) BD = 2; if (BD > 5.5) BD = 5.5; if (Temp < 60) Temp = 60; if (Temp > 72) Temp = 72; } else { BD = 3.5; Temp = 67; TotTme = 75; } if (attenuation < 30) attenuation = 77; // 0.00825 Attenuation factor yeast // 0.00817 Attenuation factor water/grain ration // -0.00684 Attenuation factor mash temperature // 0.00026 Attenuation factor total mash time (at some places this is 0.0026 this is wrong!) // -0.00356 Attenuation factor percentage crystal malt // 0.00553 Attenuation factor percentage simple sugars // 0.547 Attenuation factor constant AttBeer = 0.00825 * attenuation + 0.00817 * BD - 0.00684 * Temp + 0.00026 * TotTme - 0.00356 * percCara + 0.00553 * percSugar + 0.547; fg = Round(1 + (1 - AttBeer) * (og - 1), 4); //console.log('estimate_fg(' + percSugar + ',' + percCara + ',' + BD + ',' + TotTme + ',' + // Temp + ',' + attenuation + ',' + og + ') AttBeer:' + AttBeer + ' fg:' + fg); return fg; } function CalcFrac(TpH, pK1, pK2, pK3) { var r1d, r2d, r3d, dd, f2d, f3d, f4d; r1d = Math.pow(10, TpH - pK1); r2d = Math.pow(10, TpH - pK2); r3d = Math.pow(10, TpH - pK3); dd = 1 / (1 + r1d + r1d * r2d + r1d * r2d * r3d); f2d = r1d * dd; f3d = r1d * r2d * dd; f4d = r1d * r2d * r3d * dd; return f2d + 2 * f3d + 3 * f4d; } function lintner_to_kolbach(lintner) { var wk = (3.5 * lintner) - 16; if (wk < 0) return 0; return (3.5 * lintner) - 16; } function kolbach_to_lintner(kolbach) { return Round((kolbach + 16) / 3.5, 3); } function kettle_cm(vol, kettle_vol, kettle_height) { if ((vol > 0) && (kettle_vol > 0) && (vol <= kettle_vol)) return Round(100 * ((1 - vol / kettle_vol) * kettle_height), 1); return 0; } function kettle_vol(cm, kettle_vol, kettle_height) { if ((cm >= 0) && (kettle_vol > 0) && (cm <= (kettle_height * 100))) return Round(((kettle_height - (cm / 100)) / kettle_height) * kettle_vol, 1); return 0; } function Hardness(calcium, magnesium) { return 2.497 * calcium + 4.164 * magnesium; } function Bicarbonate(total_alkalinity, ph) { return (total_alkalinity / (1 + 2*Math.pow(10, ph - 10.33)) * MMHCO3 /*61.016*/ / (MMCaCO3 / 2) /*50.043*/); } function RA_CaCO3(bicarbonate, carbonate, calcium, magnesium) { return ((bicarbonate / MMHCO3) + (2*carbonate / MMCO3) - (2*calcium / MMCa)/3.5 - (2*magnesium / MMMg)/7) * 50; } function ResidualAlkalinity(total_alkalinity, calcium, magnesium) { return total_alkalinity - (calcium / 1.4 + magnesium / 1.7); }