import { Box, CircularProgress, MenuItem, TextField } from "@mui/material"; import GrainDryingChart, { GrainDryingPoint } from "charts/GrainDryingChart"; import { GrainCable } from "models/GrainCable"; import { Plenum } from "models/Plenum"; import { UnitMeasurement } from "models/UnitMeasurement"; import moment from "moment"; import { quack } from "protobuf-ts/quack"; import React, { useEffect, useState } from "react"; import { avg } from "utils"; interface Props { plenums: Plenum[]; cables: GrainCable[]; measurementMap: Map; returnLastVPD: (lastVPD: GrainDryingPoint | undefined) => void; newXDomain?: number[] | string[]; multiGraphZoom?: (domain: number[] | string[]) => void; multiGraphZoomOut?: boolean; //perBushelCFM?: number; } interface UnitMeasurementHelper { timestamp: string; compKey: string; measurementType: quack.MeasurementType; value: number; } interface NodeOption { name: string; number: number; } export default function BinGraphsVPD(props: Props) { const { plenums, cables, measurementMap, newXDomain, multiGraphZoom, multiGraphZoomOut, returnLastVPD //perBushelCFM } = props; const [vpdData, setVpdData] = useState([]); const [plenum, setPlenum] = useState(""); const [cable, setCable] = useState(""); const [cableOptions, setCableOptions] = useState([]); const [calculating, setCalculating] = useState(false); const [selectedNode, setSelectedNode] = useState(-1); const [nodeOps, setNodeOps] = useState([]); useEffect(() => { let plenum: string = ""; if (plenums[0]) { plenum = plenums[0].key(); } let ops: GrainCable[] = []; let cable: string = ""; cables.forEach(c => { if (c.humidities.length > 0) { ops.push(c); if (ops.length === 1) { cable = c.key(); let nodes: NodeOption[] = []; c.humidities.forEach((hum, i) => { nodes.push({ name: "Node" + (i + 1), number: i }); }); setNodeOps(nodes); } } }); setPlenum(plenum); setCable(cable); setCableOptions(ops); }, [plenums, cables]); const buildHelperArray = ( plenumUM: UnitMeasurement[], cableUM: UnitMeasurement[], node?: number ) => { let helper: UnitMeasurementHelper[] = []; //doing the plenum and the cable seperately because the plenum will always be done the same way however the cable will be done differently if there is a node selected plenumUM.forEach(unitM => { if ( unitM.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE || unitM.type === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT ) { unitM.values.forEach((valSet, i) => { helper.push({ timestamp: unitM.timestamps[i], measurementType: unitM.type, compKey: unitM.componentId, value: avg(valSet.values) }); }); } }); cableUM.forEach(unitM => { if ( unitM.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE || unitM.type === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT ) { unitM.values.forEach((valSet, i) => { let newHelper = { timestamp: unitM.timestamps[i], measurementType: unitM.type, compKey: unitM.componentId, value: node !== undefined ? valSet.values[node] : avg(valSet.values) }; helper.push(newHelper); }); } }); helper.sort((a, b) => { return moment(a.timestamp).valueOf() - moment(b.timestamp).valueOf(); }); return helper; }; // T = temperature im degrees Celsius // RH = relative humidity (%) const calculateVPD = (T: number, RH: number) => { const es = 0.6108 * Math.exp((17.27 * T) / (T + 237.3)); const ea = (RH / 100) * es; return ea - es; }; const buildData = (measurements: UnitMeasurementHelper[]) => { let data: GrainDryingPoint[] = []; let recent: GrainDryingPoint | undefined; let currentPlenumTemp: number; let currentPlenumHum: number; let currentCableTemp: number; let currentCableHum: number; if (measurements[0]) { let lastTime = moment(measurements[0].timestamp); measurements.forEach(m => { let thisTime = moment(m.timestamp); if (m.compKey === plenum) { if (m.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) { currentPlenumTemp = m.value; } else if (m.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) { currentPlenumHum = m.value; } } if (m.compKey === cable) { if (m.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) { currentCableTemp = m.value; } else if (m.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) { currentCableHum = m.value; } } if (currentPlenumTemp && currentPlenumHum && currentCableTemp && currentCableHum) { if (thisTime.diff(lastTime, "seconds") >= 2) { lastTime = thisTime; let plenumVPD = calculateVPD(currentPlenumTemp, currentPlenumHum); let cableVPD = calculateVPD(currentCableTemp, currentCableHum); let dryScore = cableVPD - plenumVPD; // if (perBushelCFM) { // dryScore = dryScore * perBushelCFM; // } let newPoint: GrainDryingPoint = { dryScore: dryScore, timestamp: moment(m.timestamp).valueOf() }; data.push(newPoint); if (!recent || recent.timestamp < newPoint.timestamp) { recent = newPoint; } } } }); } setVpdData(data); returnLastVPD(recent); }; //calculate the vpd using the average values of the nodes useEffect(() => { setSelectedNode(-1); setVpdData([]); if (plenum && cable) { let plenumUM = measurementMap.get(plenum); let cableUM = measurementMap.get(cable); if (plenumUM && cableUM) { setCalculating(true); let measurements = buildHelperArray(plenumUM, cableUM); buildData(measurements); setCalculating(false); } } }, [plenum, cable, measurementMap]); // eslint-disable-line react-hooks/exhaustive-deps //calculate the vpd using the specified node value const nodeVPD = (node: number) => { if (plenum && cable) { setVpdData([]); let plenumUM = measurementMap.get(plenum); let cableUM = measurementMap.get(cable); if (plenumUM && cableUM) { setCalculating(true); let measurements: UnitMeasurementHelper[] = []; if (node === -1) { measurements = buildHelperArray(plenumUM, cableUM); } else { measurements = buildHelperArray(plenumUM, cableUM, node); } buildData(measurements); setCalculating(false); } } }; return ( {plenums.length > 1 && ( { setPlenum(e.target.value as string); }}> {plenums.map(k => { return ( {k.name()} ); })} )} {cableOptions.length > 1 && ( { setCable(e.target.value as string); }}> {cableOptions.map(k => { return ( {k.name()} ); })} )} { setSelectedNode(+e.target.value); nodeVPD(+e.target.value); }}> Average Score {nodeOps.map(n => { return ( {n.name} ); })} {calculating ? ( ) : ( vpdData.length > 0 && ( ) )} ); }