updated the grain describers to use the new enum in the pond for the moisture calculation, changed the extract moisture function to account for custom grain types, and if there is no equation set it will return the humidity, updated instances where the bin uses that function to pass in the grain settings in its inventory

This commit is contained in:
csawatzky 2025-12-12 15:17:37 -06:00
parent bdddcfc103
commit d6c670fb78
16 changed files with 596 additions and 140 deletions

View file

@ -417,6 +417,7 @@ export default function BinCard(props: Props) {
hottestNodeTemp={valDisplay === "high" ? hotNode?.temp : undefined}
coldestNodeTemp={valDisplay === "low" ? coldNode?.temp : undefined}
inventoryControl={bin.inventoryControl()}
customGrain={bin.customGrain()}
/>
</Box>
</Box>

View file

@ -39,11 +39,12 @@ interface Props {
heaters?: Controller[];
fans?: Controller[];
grain?: pond.Grain;
customGrain?: pond.GrainSettings
}
export default function BinConditioningCard(props: Props) {
const classes = useStyles();
const { interactions, interactionDevices, plenums, ambients, heaters, fans, grain, mode } = props;
const { interactions, interactionDevices, plenums, ambients, heaters, fans, grain, mode, customGrain } = props;
const [sourceMap, setSourceMap] = useState<Map<string, Component>>(new Map());
const [sinkMap, setSinkMap] = useState<Map<string, Component>>(new Map());
@ -87,6 +88,7 @@ export default function BinConditioningCard(props: Props) {
sink={sink}
source={source}
grain={grain}
customGrain={customGrain}
/>
);
}

View file

@ -84,10 +84,11 @@ interface Props {
source: Component;
sink: Component;
grain?: pond.Grain;
customGrain?: pond.GrainSettings
}
export default function BinConditioningInteraction(props: Props) {
const { interaction, source, sink, grain, deviceId } = 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
@ -127,7 +128,6 @@ export default function BinConditioningInteraction(props: Props) {
if (
grain !== undefined &&
grain !== pond.Grain.GRAIN_INVALID &&
grain !== pond.Grain.GRAIN_CUSTOM &&
temp &&
hum
) {
@ -135,8 +135,9 @@ export default function BinConditioningInteraction(props: Props) {
//the emc calc needs the temp to be in celsius
temp = fahrenheitToCelsius(temp);
}
let emc = ExtractMoisture(grain, temp, hum);
setBaseEMC(emc);
let emc = ExtractMoisture(grain, temp, hum, customGrain)
setBaseEMC(emc === hum ? undefined : emc);
}
}, [sliderVals, grain]);

View file

@ -147,6 +147,7 @@ interface Props {
hottestNodeTemp?: number;
coldestNodeTemp?: number;
inventoryControl?: pond.BinInventoryControl;
customGrain?: pond.GrainSettings;
}
interface GrainNodePoint {
@ -211,7 +212,8 @@ export default function BinSVGV2(props: Props) {
hottestNodeTemp,
coldestNodeTemp,
inventoryControl,
co2Sensors
co2Sensors,
customGrain
} = props;
const [{ user }] = useGlobalState();
const [extraDetails, setExtraDetails] = useState<"temps" | "hums">("temps");
@ -683,7 +685,7 @@ export default function BinSVGV2(props: Props) {
x: cablePos,
y: nodeY + 10,
stroke: "green",
value: ExtractMoisture(props.grainType, temp, cable.humidities[index] ?? 0).toFixed(1)+"%",
value: ExtractMoisture(props.grainType, temp, cable.humidities[index] ?? 0, customGrain).toFixed(1)+"%",
})
!showTempHum &&
nodeClickData.push({

View file

@ -65,6 +65,7 @@ import { getDistanceUnit, or, getTemperatureUnit, getGrainUnit } from "utils";
import { makeStyles } from "@mui/styles";
import { useNavigate } from "react-router-dom";
import BinSelector from "./BinSelector";
import CustomGrainForm from "grain/CustomGrainForm";
// import BinSelector from "./BinSelector";
const useStyles = makeStyles((theme: Theme) => {
@ -205,6 +206,7 @@ export default function BinSettings(props: Props) {
const libracartAPI = useLibraCartProxyAPI()
const [lcDestination, setlcDestination] = useState<Option | null>()
const [lcDestinationOptions, setlcDestinationOptions] = useState<Option[]>([])
const [customGrain, setCustomGrain] = useState<pond.GrainSettings>()
useEffect(() => {
if (open) {
@ -236,6 +238,9 @@ export default function BinSettings(props: Props) {
initForm.inventory.grainType
).bushelsPerTonne;
}
if (initForm.inventory.customGrain){
setCustomGrain(initForm.inventory.customGrain)
}
//if the target temp is not set (older bins) make it the midpoint of the high and low temps assuming they are set otherwise make it 15
if (!initForm.inventory.targetTemperature || initForm.inventory.targetTemperature) {
if (initForm.highTemp && initForm.lowTemp) {
@ -493,6 +498,7 @@ export default function BinSettings(props: Props) {
form.inventory.autoThreshold = autoFillThreshold
//if the users preferences are in feet convert the distance to cm otherwise it was entered as cm so use that
form.inventory.lidarDropCm = getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? lidarDropDistance * 30.48 : lidarDropDistance
form.inventory.customGrain = customGrain
}
binAPI
.updateBin(bin.key(), form, as)
@ -1014,7 +1020,37 @@ export default function BinSettings(props: Props) {
{isCustom ? "Custom" : "Grain"}
</Typography> */}
<Box>
{!isCustomInventory && (
{isCustomInventory ?
<React.Fragment>
{storageType === pond.BinStorage.BIN_STORAGE_FERTILIZER ?
<React.Fragment>
<TextField
label="Type"
value={customTypeName}
type="text"
onChange={event => {
updateForm(
"inventory",
pond.BinInventory.create({
...form.inventory,
customTypeName: event.target.value
})
);
updateFormExtension("customTypeName", event.target.value);
}}
fullWidth
variant="outlined"
className={classes.bottomSpacing}
/>
</React.Fragment>
:
<Box sx={{border: "1px solid white", borderRadius: 2, padding: 2, marginBottom: 2}}>
<CustomGrainForm grainSettings={customGrain} onGrainSettingsChange={(newGrainSettings) => {setCustomGrain(newGrainSettings)}}/>
</Box>
}
</React.Fragment>
:
<React.Fragment>
<Box className={classes.bottomSpacing}>
<SearchSelect
label="Type"
@ -1040,49 +1076,6 @@ export default function BinSettings(props: Props) {
options={grainOptions}
/>
</Box>
)}
{isCustomInventory ? (
<React.Fragment>
<TextField
label="Type"
value={customTypeName}
type="text"
onChange={event => {
updateForm(
"inventory",
pond.BinInventory.create({
...form.inventory,
customTypeName: event.target.value
})
);
updateFormExtension("customTypeName", event.target.value);
}}
fullWidth
variant="outlined"
className={classes.bottomSpacing}
/>
{storageType !== pond.BinStorage.BIN_STORAGE_FERTILIZER && (
<TextField
label="Bushels Per Tonne"
value={bushPerTonne}
type="number"
onChange={event => {
updateForm(
"inventory",
pond.BinInventory.create({
...form.inventory,
bushelsPerTonne: +event.target.value
})
);
updateFormExtension("bushelsPerTonne", event.target.value);
}}
fullWidth
variant="outlined"
className={classes.bottomSpacing}
/>
)}
</React.Fragment>
) : (
<TextField
label="Grain Variant"
value={grainSubtype}
@ -1102,7 +1095,8 @@ export default function BinSettings(props: Props) {
disabled={!grainType}
className={classes.bottomSpacing}
/>
)}
</React.Fragment>
}
{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_BUSHELS ||
storageType === pond.BinStorage.BIN_STORAGE_FERTILIZER ||
formExtension.bushelsPerTonne === "0" ||

View file

@ -1404,6 +1404,7 @@ export default function BinVisualizer(props: Props) {
}
}}
inventoryControl={bin.inventoryControl()}
customGrain={bin.customGrain()}
/>
</Box>
</Box>
@ -1574,6 +1575,12 @@ export default function BinVisualizer(props: Props) {
const fanPerformance = () => {
let totalCFM = bin.status.fanCfm;
let bushelCFM = bin.status.cfmPerBushel;
let emc = ExtractMoisture(
bin.grain(),
activePlenum?.tempHumidity?.temperature ?? 0,
activePlenum?.tempHumidity?.humidity ?? 0,
bin.customGrain()
)
return (
<Box>
<Typography style={{ fontSize: isMobile ? "0.85rem" : "1.0rem", fontWeight: 650 }}>
@ -1616,20 +1623,18 @@ export default function BinVisualizer(props: Props) {
{cfmDryWarning(bushelCFM)}
</Box>
</Grid>
<Grid >
<Box className={classes.lightBox}>
<Typography align="center" style={{ fontWeight: 650, color: emcColour }}>
{ExtractMoisture(
bin.grain(),
activePlenum?.tempHumidity?.temperature ?? 0,
activePlenum?.tempHumidity?.humidity ?? 0
).toFixed(2)}
%
</Typography>
<Typography align="center" style={{ fontWeight: 650 }}>
Plenum EMC
</Typography>
</Box>
<Grid>
{emc !== activePlenum?.tempHumidity?.humidity &&
<Box className={classes.lightBox}>
<Typography align="center" style={{ fontWeight: 650, color: emcColour }}>
{emc.toFixed(2)}
%
</Typography>
<Typography align="center" style={{ fontWeight: 650 }}>
Plenum EMC
</Typography>
</Box>
}
</Grid>
</Grid>
</Box>
@ -1638,6 +1643,12 @@ export default function BinVisualizer(props: Props) {
};
const ambientDisplay = () => {
let emc = ExtractMoisture(
bin.grain(),
ambient?.temperature ?? 0,
ambient?.humidity ?? 0,
bin.customGrain()
)
return (
<Box>
<Typography style={{ fontSize: isMobile ? "0.85rem" : "1.0rem", fontWeight: 650 }}>
@ -1677,19 +1688,17 @@ export default function BinVisualizer(props: Props) {
</Box>
</Grid>
<Grid >
<Box className={classes.lightBox}>
<Typography align="center" style={{ fontWeight: 650, color: emcColour }}>
{ExtractMoisture(
bin.grain(),
ambient?.temperature ?? 0,
ambient?.humidity ?? 0
).toFixed(2)}
%
</Typography>
<Typography align="center" style={{ fontWeight: 650 }}>
Ambient EMC
</Typography>
</Box>
{emc !== ambient?.humidity &&
<Box className={classes.lightBox}>
<Typography align="center" style={{ fontWeight: 650, color: emcColour }}>
{emc.toFixed(2)}
%
</Typography>
<Typography align="center" style={{ fontWeight: 650 }}>
Ambient EMC
</Typography>
</Box>
}
</Grid>
</Grid>
</Box>

View file

@ -92,7 +92,8 @@ export default function BinGraphsTrending(props: Props) {
trend: ExtractMoisture(
bin.grain(),
val.values[0],
plenumHumidity.values[i].values[0]
plenumHumidity.values[i].values[0],
bin.customGrain()
)
};
trendData.push(trendPoint);