--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/www/js/fermenter.js Mon Apr 15 17:04:57 2024 +0200 @@ -0,0 +1,494 @@ +/***************************************************************************** + * Copyright (C) 2024 + * + * Michiel Broek <mbroek at mbse dot eu> + * + * This file is part of mbsePi-apps thermferm + * + * 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. + * + * Brewery Management System istributed 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. + *****************************************************************************/ + +function createAbortElements() { + $('#eventWindow').jqxWindow({ + theme: theme, + position: { x: 440, y: 210 }, + width: 400, + height: 200, + resizable: false, + isModal: true, + modalOpacity: 0.4, + okButton: $('#delOk'), + cancelButton: $('#delCancel'), + initContent: function() { + $('#delOk').jqxButton({ template: 'danger', width: '65px', theme: theme }); + $('#delCancel').jqxButton({ template: 'success', width: '65px', theme: theme }); + $('#delCancel').focus(); + } + }); + $('#eventWindow').jqxWindow('hide'); +} + + +$(document).ready(function() { + + var record = {}, + global = {}, + blank = {}, + ppayload = '', + yl = 12, // Normal yeast temp range + yh = 24, + + gaugeoptions = { + min: 0, max: 45, width: 375, height: 375, + ranges: [{ startValue: 0, endValue: yl, style: { fill: '#3399FF', stroke: '#3399FF' }, endWidth: 10, startWidth: 10 }, + { startValue: yl, endValue: yh, style: { fill: '#00CC33', stroke: '#00CC33' }, endWidth: 10, startWidth: 10 }, + { startValue: yh, endValue: 45, style: { fill: '#FC6A6A', stroke: '#FC6A6A' }, endWidth: 10, startWidth: 10 }], + ticksMinor: { interval: 1, size: '5%' }, + ticksMajor: { interval: 5, size: '9%' }, + labels: { interval: 5 }, + style: { fill: '#eeeeee', stroke: '#666666' }, + value: 0, + colorScheme: 'scheme05' + }, + gaugeSmalloptions = { + min: -15, max: 25, width: 190, height: 190, + ranges: [{ startValue: -15, endValue: 0, startWidth: 5, endWidth: 5, style: { fill: '#3399FF', stroke: '#3399FF' }}, + { startValue: 0, endValue: 10, startWidth: 5, endWidth: 5, style: { fill: '#00CC33', stroke: '#00CC33' }}, + { startValue: 10, endValue: 25, startWidth: 5, endWidth: 5, style: { fill: '#FC6A6A', stroke: '#FC6A6A' }}], + ticksMinor: { interval: 1, size: '5%' }, + ticksMajor: { interval: 5, size: '9%' }, + labels: { interval: 5 }, + style: { fill: '#eeeeee', stroke: '#666666' }, + value: 0, + colorScheme: 'scheme05', + caption: { value: 'Chiller', position: 'bottom', offset: [0, 10] } + }, + switchoptions = { + height: 68, + width: 35, + onLabel: 'ON', + offLabel: 'OFF', + theme: theme, + thumbSize: '50%', + orientation: 'vertical' + }, + targetoptions = { inputMode: 'simple', theme: theme, width: 70, min: 0, max: 45, decimalDigits: 1, spinButtons: true }, + + globalSource = { + datatype: 'json', + cache: false, + datafields: [ + { name: 'type' }, + { name: 'name' }, + { name: 'node' }, + { name: 'os' }, + { name: 'os_version' }, + { name: 'FW' }, + { name: 'room_temp', map: 'THB>temperature', type: 'float' }, + { name: 'room_hum', map: 'THB>humidity', type: 'float' } + ], + id: 'name', + url: 'getglobal.php' + }, + globalData = new $.jqx.dataAdapter(globalSource, { + loadComplete: function(records) { + global = globalData.records[0]; + updateScreen(); + } + }), + url = 'getfermenter.php?uuid=' + my_uuid, + source = { + datatype: 'json', + datafields: [ + { name: 'type' }, + { name: 'unit' }, + { name: 'beercode', map: 'metric>product>code' }, + { name: 'beername', map: 'metric>product>name' }, + { name: 'yeast_lo', map: 'metric>product>yeast_lo' }, + { name: 'yeast_hi', map: 'metric>product>yeast_hi' }, + { name: 'air_state', map: 'metric>air>state' }, + { name: 'air_temperature', map: 'metric>air>temperature' }, + { name: 'beer_state', map: 'metric>beer>state' }, + { name: 'beer_temperature', map: 'metric>beer>temperature' }, + { name: 'chiller_state', map: 'metric>chiller>state' }, + { name: 'chiller_temperature', map: 'metric>chiller>temperature' }, + { name: 'heater_state', map: 'metric>heater>state' }, + { name: 'heater_usage', map: 'metric>heater>usage' }, + { name: 'cooler_state', map: 'metric>cooler>state' }, + { name: 'cooler_usage', map: 'metric>cooler>usage' }, + { name: 'fan_state', map: 'metric>fan>state' }, + { name: 'fan_usage', map: 'metric>fan>usage' }, + { name: 'light_address', map: 'metric>light>address' }, + { name: 'light_state', map: 'metric>light>state' }, + { name: 'light_usage', map: 'metric>light>usage' }, + { name: 'door_address', map: 'metric>door>address' }, + { name: 'door_state', map: 'metric>door>state' }, + { name: 'psu_address', map: 'metric>psu>address' }, + { name: 'psu_state', map: 'metric>psu>state' }, + { name: 'mode', map: 'metric>mode' }, + { name: 'alarm', map: 'metric>alarm', type: 'int' }, + { name: 'setpoint_high', map: 'metric>setpoint>high' }, + { name: 'setpoint_low', map: 'metric>setpoint>low' }, + { name: 'profile_uuid', type: 'string' }, + { name: 'profile_name', type: 'string' }, + { name: 'profile_state', type: 'string' }, + { name: 'profile_percent', type: 'int' }, + { name: 'profile_inittemp_high', type: 'float' }, + { name: 'profile_inittemp_low', type: 'float' }, + { name: 'profile_steps', type: 'string' }, + { name: 'stage', map: 'metric>stage', type: 'string' }, + { name: 'beeruuid', map: 'metric>product>uuid' } + ], + id: 'alias', + url: url + }, + dataAdapter = new $.jqx.dataAdapter(source, { + loadComplete: function(records) { + record = dataAdapter.records[0]; + updateScreen(); + } + }); + + function updateScreen() { + $('#room_thb').html(global.room_temp + '°C ' + global.room_hum + '% humidity'); + $('#info_system').html(record.unit); + $('#info_beer').html(record.beercode + ' - ' + record.beername); + $('#info_mode').jqxDropDownList('selectItem', record.mode); + $('#info_stage').jqxDropDownList('selectItem', record.stage); + if (record.door_address) { + if (record.door_state != '0') { + $('#fermenter_doorled').html('<div class="LEDyellow_on"></div>Door'); + } else { + $('#fermenter_doorled').html('<div class="LEDyellow_off"></div>Door'); + } + } + if (record.light_address) { + if (record.light_state != '0') { + $('#fermenter_lightled').html('<div class="LEDyellow_on"></div>Light'); + } else { + $('#fermenter_lightled').html('<div class="LEDyellow_off"></div>Light'); + } + } + if (record.mode != 'OFF') { + $('#fermenter_powerled').html('<div class="LEDblue_on"></div>Power'); + } else { + $('#fermenter_powerled').html('<div class="LEDblue_off"></div>Power'); + } + if (record.alarm != '0') { + $('#fermenter_alarmled').html('<div class="LEDred_on"></div>Alarm'); + } else { + $('#fermenter_alarmled').html('<div class="LEDred_off"></div>Alarm'); + } + + $('#target_lo').val(record.setpoint_low); + $('#target_hi').val(record.setpoint_high); + if ((record.mode == 'FRIDGE') || (record.mode == 'BEER')) { + $('#target_lo').jqxNumberInput({ readOnly: false, Width: 70, spinButtons: true }); + $('#target_hi').jqxNumberInput({ readOnly: false, Width: 70, spinButtons: true }); + } else { + $('#target_lo').jqxNumberInput({ readOnly: true, Width: 50, spinButtons: false }); + $('#target_hi').jqxNumberInput({ readOnly: true, Width: 50, spinButtons: false }); + } + + $('.f_control_leds').show(); + if (record.heater_state != '0') { + $('#fermenter_led1').html('<div class="LEDgreen_on"></div>Heat'); + } else { + $('#fermenter_led1').html('<div class="LEDgreen_off"></div>Heat'); + } + if (record.cooler_state != '0') { + $('#fermenter_led2').html('<div class="LEDgreen_on"></div>Cool'); + } else { + $('#fermenter_led2').html('<div class="LEDgreen_off"></div>Cool'); + } + if (record.fan_state != '0') { + $('#fermenter_led3').html('<div class="LEDgreen_on"></div>Fan'); + } else { + $('#fermenter_led3').html('<div class="LEDgreen_off"></div>Fan'); + } + + if (record.mode == 'NONE') { + $('.f_control_switches').show(); + } else { + $('.f_control_switches').hide(); + } + if ((record.heater_state != '0') != $('#fermenter_toggle1').jqxSwitchButton('val')) + $('#fermenter_toggle1').val(record.heater_state != '0'); + if ((record.cooler_state != '0') != $('#fermenter_toggle2').jqxSwitchButton('val')) + $('#fermenter_toggle2').val(record.cooler_state != '0'); + if ((record.fan_state != '0') != $('#fermenter_toggle3').jqxSwitchButton('val')) + $('#fermenter_toggle3').val(record.fan_state != '0'); + + $('#info_profile').html(record.profile_name); + if (record.profile_name == '') + $('#info_mode').jqxDropDownList('disableItem', 'PROFILE'); + else + $('#info_mode').jqxDropDownList('enableItem', 'PROFILE'); + + if (record.mode == 'PROFILE') { + if (record.profile_state == 'OFF') { + $('#info_mode').jqxDropDownList({ disabled: false }); + $('#Profile1').jqxButton({ template: 'success', value: 'Starten' }); + $('#Profile1').show(); + $('#Profile2').hide(); + $('#status_profile').html(''); + } else if (record.profile_state == 'RUN') { + $('#info_mode').jqxDropDownList({ disabled: true }); + $('#Profile1').jqxButton({ template: 'danger', value: 'Afbreken' }); + $('#Profile2').jqxButton({ template: 'primary', value: 'Pauze' }); + $('#Profile1').show(); + $('#Profile2').show(); + $('#status_profile').html('Profiel actief, ' + record.profile_percent + '% gereed'); + } else if (record.profile_state == 'PAUSE') { + $('#info_mode').jqxDropDownList({ disabled: true }); + $('#Profile1').jqxButton({ template: 'danger', value: 'Afbreken' }); + $('#Profile2').jqxButton({ template: 'success', value: 'Doorgaan' }); + $('#Profile1').show(); + $('#Profile2').show(); + $('#status_profile').html('Profiel pauze, ' + record.profile_percent + '% gereed'); + } else if (record.profile_state == 'DONE') { + $('#info_mode').jqxDropDownList({ disabled: true }); + $('#Profile1').jqxButton({ template: 'primary', value: 'Profiel Ok' }); + $('#Profile1').show(); + $('#Profile2').hide(); + $('#status_profile').html('Profiel is gereed'); + } + } else { + $('#info_mode').jqxDropDownList({ disabled: false }); + $('#Profile1').hide(); + $('#Profile2').hide(); + $('#status_profile').html(''); + } + + var yl = record.yeast_lo; + var yh = record.yeast_hi; + var range = { ranges: [{ startValue: 0, endValue: yl, style: { fill: '#3399FF', stroke: '#3399FF' }, endWidth: 10, startWidth: 10 }, + { startValue: yl, endValue: yh, style: { fill: '#00CC33', stroke: '#00CC33' }, endWidth: 10, startWidth: 10 }, + { startValue: yh, endValue: 45, style: { fill: '#FC6A6A', stroke: '#FC6A6A' }, endWidth: 10, startWidth: 10 }]}; + $('#gaugeContainer_air').jqxGauge(range); + $('#gaugeContainer_beer').jqxGauge(range); + + if (record.air_temperature !== undefined) { + $('#gaugeContainer_air').jqxGauge({ caption: { value: 'Air: ' + record.air_temperature.toFixed(3) }}); + $('#gaugeContainer_air').jqxGauge({ value: record.air_temperature }); + } + if (record.air_state == 'OK') { + $('#gaugeContainer_air').jqxGauge({ disabled: false }); + } else { + $('#gaugeContainer_air').jqxGauge({ disabled: true }); + } + if (record.beer_temperature !== undefined) { + $('#gaugeContainer_beer').jqxGauge({ caption: { value: 'Beer: ' + record.beer_temperature.toFixed(3) }}); + $('#gaugeContainer_beer').jqxGauge({ value: record.beer_temperature }); + } + if (record.beer_state == 'OK') { + $('#gaugeContainer_beer').jqxGauge({ disabled: false }); + } else { + $('#gaugeContainer_beer').jqxGauge({ disabled: true }); + } + if (record.chiller_temperature !== undefined) { + $('#gaugeContainer_chiller').jqxGauge({ value: record.chiller_temperature }); + } + if (record.chiller_state == 'OK') { + $('#gaugeContainer_chiller').jqxGauge({ disabled: false }); + } else { + $('#gaugeContainer_chiller').jqxGauge({ disabled: true }); + } + } + + $('#gaugeContainer_air').jqxGauge(gaugeoptions); + $('#gaugeContainer_air').jqxGauge({ caption: { value: 'Air: 00.000' }}); + $('#gaugeContainer_beer').jqxGauge(gaugeoptions); + $('#gaugeContainer_beer').jqxGauge({ caption: { value: 'Beer: 00.000' }}); + $('#gaugeContainer_chiller').jqxGauge(gaugeSmalloptions); + + $('#fermenter_toggle1').jqxSwitchButton(switchoptions); + $('#fermenter_toggle2').jqxSwitchButton(switchoptions); + $('#fermenter_toggle3').jqxSwitchButton(switchoptions); + + srcMode = ['OFF', 'NONE', 'FRIDGE', 'BEER', 'PROFILE']; + srcStage = ['PRIMARY', 'SECONDARY', 'TERTIARY', 'CARBONATION']; + $('#info_mode').jqxDropDownList({ theme: theme, source: srcMode, width: 100, height: 24, dropDownHeight: 156 }); + $('#info_stage').jqxDropDownList({ theme: theme, source: srcStage, width: 150, height: 24, dropDownHeight: 125 }); + + $('#target_lo').jqxNumberInput(targetoptions); + $('#target_hi').jqxNumberInput(targetoptions); + + $('#Profile1').jqxButton({ template: 'info', width: '150px', height: 24, theme: theme }); + $('#Profile2').jqxButton({ template: 'info', width: '150px', height: 24, theme: theme }); + $('#Profile1').hide(); // Hide these until they are needed. + $('#Profile2').hide(); + + // Get the data immediatly and then at regular intervals to refresh. + dataAdapter.dataBind(); + globalData.dataBind(); + + $('#info_mode').on('select', function(event) { + if (event.args && event.args.item.value != record.mode) { + record.mode = event.args.item.value; + console.log('set mode ' + record.mode); + var msg = '{"type":"fermenter","unit":"' + record.unit + '","mode":"' + record.mode + '"}'; + websocket.send(msg); + } + }); + $('#info_stage').on('select', function(event) { + if (event.args && event.args.item.value != record.stage) { + record.stage = event.args.item.value; + console.log('set stage ' + record.stage); + var msg = '{"type":"fermenter","unit":"' + record.unit + '","stage":"' + record.stage + '"}'; + websocket.send(msg); + } + }); + + $('#target_lo').on('change', function(event) { + record.setpoint_low = parseFloat(event.args.value); + // Keep the high target above the low. + if (record.setpoint_low > record.setpoint_high) { + record.setpoint_high = record.setpoint_low; + $('#target_hi').val(record.setpoint_high); + } + console.log('set setpoints ' + record.setpoint_low + ' ' + record.setpoint_high); + websocket.send('{"type":"fermenter","unit":"' + record.unit + + '","setpoint_low":' + record.setpoint_low + ',"setpoint_high":' + record.setpoint_high + '}'); + }); + $('#target_hi').on('change', function(event) { + record.setpoint_high = parseFloat(event.args.value); + // Keep the low target below the high. + if (record.setpoint_high < record.setpoint_low) { + record.setpoint_low = record.setpoint_high; + $('#target_lo').val(record.setpoint_low); + } + console.log('set setpoints ' + record.setpoint_low + ' ' + record.setpoint_high); + websocket.send('{"type":"fermenter","unit":"' + record.unit + + '","setpoint_low":' + record.setpoint_low + ',"setpoint_high":' + record.setpoint_high + '}'); + }); + + $('#fermenter_toggle1').on('checked', function(event) { + if (record.mode == 'NONE' && record.heater_state != 0) { + console.log('set heater ' + $("#fermenter_toggle1").jqxSwitchButton('val')); + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","heater_state":0}'); + } + }); + $('#fermenter_toggle1').on('unchecked', function(event) { + if (record.mode == 'NONE' && record.heater_state == 0) { + console.log('set heater ' + $("#fermenter_toggle1").jqxSwitchButton('val')); + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","heater_state":100,"cooler_state":0}'); + } + }); + $('#fermenter_toggle2').on('checked', function(event) { + if (record.mode == 'NONE' && record.cooler_state != 0) { + console.log('set cooler ' + $("#fermenter_toggle2").jqxSwitchButton('val')); + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","cooler_state":0}'); + } + }); + $('#fermenter_toggle2').on('unchecked', function(event) { + if (record.mode == 'NONE' & record.cooler_state == 0) { + console.log('set cooler ' + $("#fermenter_toggle2").jqxSwitchButton('val')); + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","cooler_state":100,"heater_state":0}'); + } + }); + $('#fermenter_toggle3').on('checked', function(event) { + if (record.mode == 'NONE' && record.fan_state != 0) { + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","fan_state":0}'); + } + }); + $('#fermenter_toggle3').on('unchecked', function(event) { + if (record.mode == 'NONE' && record.fan_state == 0) { + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","fan_state":100}'); + } + }); + $('#Profile1').click(function() { + if (record.mode == 'PROFILE') { + if (record.profile_state == 'OFF') { + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","profile":{"command":"start"}}'); + } else if ((record.profile_state == 'RUN') || (record.profile_state == 'PAUSE')) { + // Open a popup to confirm this action. + $('#eventWindow').jqxWindow('open'); + $('#delOk').click(function() { + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","profile":{"command":"abort"}}'); + }); + } else if (record.profile_state == 'DONE') { + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","profile":{"command":"done"}}'); + } + } + }); + $('#Profile2').click(function() { + if (record.mode == 'PROFILE') { + if ((record.profile_state == 'RUN') || (record.profile_state == 'PAUSE')) { + websocket.send('{"type":"fermenter","unit":"' + record.unit + '","profile":{"command":"pause"}}'); + } + } + }); + + createAbortElements(); + + websocket.onmessage = function(evt) { + var msg = evt.data; + var obj = JSON.parse(msg); + + console.log('ws got ' + msg); + + if (obj.ping == 1) { + console.log('ws got ping'); + websocket.send('{"pong":1}'); + } + + if (obj.type == 'fermenter' && obj.unit == record.unit) { + console.log('ws got this device ' + msg); + record.beeruuid = obj.metric.product.uuid; + record.beercode = obj.metric.product.code; + record.beername = obj.metric.product.name; + record.yeast_lo = obj.metric.product.yeast_lo; + record.yeast_hi = obj.metric.product.yeast_hi; + record.air_state = obj.metric.air.state; + record.air_temperature = obj.metric.air.temperature; + record.beer_state = obj.metric.beer.state; + record.beer_temperature = obj.metric.beer.temperature; + record.chiller_state = obj.metric.chiller.state; + record.chiller_temperature = obj.metric.chiller.temperature; + if (obj.metric.heater.state !== undefined) + record.heater_state = obj.metric.heater.state; + if (obj.metric.cooler.state !== undefined) + record.cooler_state = obj.metric.cooler.state; + if (obj.metric.fan.state !== undefined) + record.fan_state = obj.metric.fan.state; + if (obj.metric.door) + record.door_state = obj.metric.door.state; + if (obj.metric.light) + record.light_state = obj.metric.light.state; + if (obj.metric.psu) + record.psu_state = obj.metric.psu.state; + record.mode = obj.metric.mode; + record.stage = obj.metric.stage; + record.alarm = obj.metric.alarm; + record.setpoint_low = obj.metric.setpoint.low; + record.setpoint_high = obj.metric.setpoint.high; + if (obj.profile) { + record.profile_uuid = obj.profile_uuid; + record.profile_name = obj.profile_name; + record.profile_state = obj.profile_state; + record.profile_percent = obj.profile_percent; + record.profile_inittemp_high = obj.profile_inittemp_high; + record.profile_inittemp_low = obj.profile_inittemp_low; + } else { + record.profile_uuid = ''; + record.profile_name = ''; + record.profile_state = ''; + record.profile_percent = 0; + } + updateScreen(); + } + } +});