345 lines
12 KiB
TypeScript
345 lines
12 KiB
TypeScript
import {
|
|
Accordion,
|
|
AccordionDetails,
|
|
AccordionSummary,
|
|
Box,
|
|
Button,
|
|
darken,
|
|
Grid,
|
|
Slider,
|
|
Theme,
|
|
Typography
|
|
} from "@mui/material";
|
|
import { ExpandMore } from "@mui/icons-material";
|
|
import UnitMeasurementSummary from "component/UnitMeasurementSummary";
|
|
import { ExtractMoisture } from "grain";
|
|
import { cloneDeep } from "lodash";
|
|
import { Component, Interaction } from "models";
|
|
import { UnitMeasurement } from "models/UnitMeasurement";
|
|
import { interactionConditionText, interactionResultText } from "pbHelpers/Interaction";
|
|
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
|
import { pond, quack } from "protobuf-ts/pond";
|
|
import { useGlobalState, useInteractionsAPI, useSnackbar } from "providers";
|
|
import React, { useEffect, useState } from "react";
|
|
import { avg, fahrenheitToCelsius } from "utils";
|
|
import { makeStyles } from "@mui/styles";
|
|
import { Mark } from "@mui/material/Slider/useSlider.types";
|
|
|
|
const useStyles = makeStyles((theme: Theme) => {
|
|
return ({
|
|
displayBG: {
|
|
marginTop: 10,
|
|
background: darken(theme.palette.background.default, 0.05),
|
|
borderRadius: 5,
|
|
padding: 10
|
|
},
|
|
markContainer: {
|
|
zIndex: 2
|
|
},
|
|
arrowDown: {
|
|
width: 0,
|
|
height: 0,
|
|
borderLeft: "10px solid transparent",
|
|
borderRight: "10px solid transparent"
|
|
//borderTop: "10px solid yellow"
|
|
},
|
|
sliderRoot: {},
|
|
sliderThumb: {
|
|
height: 15,
|
|
width: 15,
|
|
backgroundColor: "yellow"
|
|
},
|
|
sliderTrack: {
|
|
height: 3,
|
|
backgroundColor: "white"
|
|
},
|
|
sliderRail: {
|
|
height: 3,
|
|
backgroundColor: "white"
|
|
},
|
|
sliderValLabel: {
|
|
left: -20,
|
|
top: 40,
|
|
background: "transparent",
|
|
"& *": {
|
|
color: "#fff"
|
|
}
|
|
},
|
|
sliderMark: {
|
|
visibility: "hidden"
|
|
},
|
|
sliderMarked: {
|
|
marginTop: 25,
|
|
marginBottom: 0
|
|
},
|
|
sliderMarkLabel: {
|
|
top: -25
|
|
}
|
|
});
|
|
});
|
|
|
|
interface Props {
|
|
deviceId: number;
|
|
interaction: Interaction;
|
|
source: Component;
|
|
sink: Component;
|
|
grain?: pond.Grain;
|
|
customGrain?: pond.GrainSettings
|
|
}
|
|
|
|
export default function BinConditioningInteraction(props: Props) {
|
|
const { interaction, source, sink, grain, deviceId, customGrain } = props;
|
|
const [sliderVals, setSliderVals] = useState<Map<quack.MeasurementType, number>>(new Map());
|
|
const [sliderMarks, setSliderMarks] = useState<Map<quack.MeasurementType, number>>(new Map());
|
|
//this is the emc value calculated from the interactions temp and humidity conditions
|
|
const [baseEMC, setBaseEMC] = useState<number | undefined>();
|
|
const [{ user, as }] = useGlobalState();
|
|
const classes = useStyles();
|
|
const interactionAPI = useInteractionsAPI();
|
|
const { openSnack } = useSnackbar();
|
|
|
|
useEffect(() => {
|
|
let passedInteraction = interaction;
|
|
let sliderVals: Map<quack.MeasurementType, number> = new Map();
|
|
let sliderMarks: Map<quack.MeasurementType, number> = new Map();
|
|
passedInteraction.conditions().forEach(condition => {
|
|
let describer = describeMeasurement(condition.measurementType, source.type(), undefined, undefined, user);
|
|
//NOTE: toDisplay will convert the temp value to fahrenheit
|
|
sliderVals.set(condition.measurementType, describer.toDisplay(condition.value));
|
|
});
|
|
|
|
source.status.lastGoodMeasurement.forEach(measurement => {
|
|
let m = pond.UnitMeasurementsForComponent.fromObject(measurement);
|
|
if (m.values[0]) {
|
|
let markVal = avg(m.values[0].values);
|
|
//do this since this is how interactions handle the values so that the slider can use the toDisplay method of the describer for the marks
|
|
if (m.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) markVal = markVal * 10;
|
|
if (m.type === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) markVal = markVal * 100;
|
|
sliderMarks.set(m.type, markVal);
|
|
}
|
|
});
|
|
setSliderVals(sliderVals);
|
|
setSliderMarks(sliderMarks);
|
|
}, [interaction, source]);
|
|
|
|
useEffect(() => {
|
|
let temp = sliderVals.get(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE);
|
|
let hum = sliderVals.get(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT);
|
|
if (
|
|
grain !== undefined &&
|
|
grain !== pond.Grain.GRAIN_INVALID &&
|
|
temp &&
|
|
hum
|
|
) {
|
|
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
|
//the emc calc needs the temp to be in celsius
|
|
temp = fahrenheitToCelsius(temp);
|
|
}
|
|
let emc = ExtractMoisture(grain, temp, hum, customGrain)
|
|
setBaseEMC(emc === hum ? undefined : emc);
|
|
|
|
}
|
|
}, [sliderVals, grain, user]);
|
|
|
|
const updateInteraction = () => {
|
|
interactionAPI
|
|
.updateInteraction(deviceId, interaction.settings, as)
|
|
.then(resp => {
|
|
openSnack("Updated Interaction Conditions");
|
|
})
|
|
.catch(err => {
|
|
openSnack("Failed to Update Interaction Conditions");
|
|
});
|
|
};
|
|
|
|
const customMark = (val: string, arrowColor: string) => {
|
|
return (
|
|
<Box className={classes.markContainer}>
|
|
<Grid container direction="column" alignContent="center" alignItems="center" spacing={1}>
|
|
<Grid item>
|
|
<Typography style={{ color: "white", fontSize: 15, fontWeight: 650, lineHeight: 1 }}>
|
|
{val}
|
|
</Typography>
|
|
</Grid>
|
|
<Grid item>
|
|
<Box className={classes.arrowDown} style={{ borderTop: "10px solid " + arrowColor }} />
|
|
</Grid>
|
|
</Grid>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const conditionDisplay = () => {
|
|
return (
|
|
<Grid container>
|
|
{interaction.conditions().map((condition, i) => {
|
|
let describer = describeMeasurement(condition.measurementType, source?.type(), source.subType(), undefined, user);
|
|
let labelTail = describer.unit();
|
|
let marks: Mark[] = [];
|
|
|
|
// if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
|
|
// if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
|
// labelTail = "°F";
|
|
// } else {
|
|
// labelTail = "°C";
|
|
// }
|
|
// }
|
|
if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) {
|
|
labelTail = "%";
|
|
}
|
|
|
|
let mark = sliderMarks.get(condition.measurementType);
|
|
if (mark !== undefined) {
|
|
marks.push({
|
|
label: customMark(describer.toDisplay(mark) + labelTail, describer.colour()),
|
|
value: describer.toDisplay(mark)
|
|
});
|
|
}
|
|
return (
|
|
<Grid key={i} container item xs={12} alignContent="center" alignItems="center">
|
|
<Grid item xs={12}>
|
|
{interactionConditionText(source, condition, false, user)}
|
|
</Grid>
|
|
<Grid item xs={12} style={{ marginBottom: 20 }}>
|
|
<Slider
|
|
classes={{
|
|
root: classes.sliderRoot,
|
|
rail: classes.sliderRail,
|
|
track: classes.sliderTrack,
|
|
thumb: classes.sliderThumb,
|
|
valueLabel: classes.sliderValLabel,
|
|
marked: classes.sliderMarked,
|
|
mark: classes.sliderMark,
|
|
markLabel: classes.sliderMarkLabel
|
|
}}
|
|
step={0.1}
|
|
valueLabelDisplay="on"
|
|
valueLabelFormat={value => {
|
|
if (
|
|
condition.measurementType ===
|
|
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE
|
|
) {
|
|
if (
|
|
user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
|
) {
|
|
return value.toFixed(1) + "°F";
|
|
} else {
|
|
return value.toFixed(1) + "°C";
|
|
}
|
|
}
|
|
if (
|
|
condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT
|
|
) {
|
|
return value.toFixed(1) + "%";
|
|
}
|
|
}}
|
|
marks={marks}
|
|
min={describer.min()}
|
|
max={describer.max()}
|
|
value={sliderVals.get(condition.measurementType) ?? describer.min()}
|
|
onChange={(_, val) => {
|
|
condition.value = Math.round(describer.toStored(val as number));
|
|
let sliders = cloneDeep(sliderVals);
|
|
sliders.set(condition.measurementType, val as number);
|
|
setSliderVals(sliders);
|
|
}}
|
|
/>
|
|
</Grid>
|
|
</Grid>
|
|
);
|
|
})}
|
|
<Grid item xs={12}>
|
|
<Button
|
|
variant="contained"
|
|
color="primary"
|
|
onClick={() => {
|
|
updateInteraction();
|
|
}}>
|
|
Update Conditions
|
|
</Button>
|
|
</Grid>
|
|
</Grid>
|
|
);
|
|
};
|
|
|
|
const determineEMCRelation = () => {
|
|
let relation = "Approximately:";
|
|
let tempRelation: quack.RelationalOperator | undefined = undefined;
|
|
let humidRelation: quack.RelationalOperator | undefined = undefined;
|
|
interaction.conditions().forEach(condition => {
|
|
if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE)
|
|
tempRelation = condition.comparison;
|
|
if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT)
|
|
humidRelation = condition.comparison;
|
|
});
|
|
|
|
if (
|
|
(tempRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN ||
|
|
tempRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO) &&
|
|
(humidRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN ||
|
|
humidRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO)
|
|
) {
|
|
relation = "Less Than: ";
|
|
}
|
|
if (
|
|
(tempRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN ||
|
|
tempRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO) &&
|
|
(humidRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN ||
|
|
humidRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO)
|
|
) {
|
|
relation = "Greater Than: ";
|
|
}
|
|
|
|
return relation;
|
|
};
|
|
|
|
return (
|
|
<Grid key={interaction.key()} container className={classes.displayBG}>
|
|
<Grid item xs={6}>
|
|
<Typography style={{ fontWeight: 650 }}>{source.name()}</Typography>
|
|
<UnitMeasurementSummary
|
|
component={source}
|
|
reading={UnitMeasurement.convertLastMeasurement(
|
|
source.status.lastGoodMeasurement.map(um => UnitMeasurement.any(um, user))
|
|
)}
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={6}>
|
|
<Typography style={{ fontWeight: 650 }}>{sink.name()}</Typography>
|
|
<UnitMeasurementSummary
|
|
component={sink}
|
|
reading={UnitMeasurement.convertLastMeasurement(
|
|
sink.status.lastGoodMeasurement.map(um => UnitMeasurement.any(um, user))
|
|
)}
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12}>
|
|
<Typography align="center" style={{ margin: 5, marginBottom: 10, fontWeight: 650 }}>
|
|
{interactionResultText(interaction, sink)}
|
|
</Typography>
|
|
{baseEMC !== undefined ? (
|
|
<Accordion>
|
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
|
<Box display="flex">
|
|
<Typography style={{ fontWeight: 650 }}>EMC {determineEMCRelation()}</Typography>
|
|
<Typography
|
|
style={{
|
|
marginLeft: 5,
|
|
fontWeight: 650,
|
|
color: describeMeasurement(
|
|
quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC
|
|
).colour()
|
|
}}>
|
|
{baseEMC.toFixed(1)}%
|
|
</Typography>
|
|
</Box>
|
|
</AccordionSummary>
|
|
<AccordionDetails>{conditionDisplay()}</AccordionDetails>
|
|
</Accordion>
|
|
) : (
|
|
<React.Fragment>{conditionDisplay()}</React.Fragment>
|
|
)}
|
|
</Grid>
|
|
</Grid>
|
|
);
|
|
}
|