merged staging and updated staging proto

This commit is contained in:
Carter 2026-02-23 13:40:17 -06:00
commit 8e4dc16947
56 changed files with 1929 additions and 349 deletions

View file

@ -200,25 +200,27 @@ export default function BinCard(props: Props) {
const typeDisplay = () => {
let grainType = bin.settings.inventory?.grainType ?? pond.Grain.GRAIN_NONE;
if (bin.storage() === pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN) {
return (
<Box>
<Typography variant="subtitle2" style={{ fontSize: "0.7rem", fontWeight: 700 }}>
{GrainDescriber(grainType).name}
</Typography>
<Typography variant="subtitle2" style={{ fontSize: "0.7rem", fontWeight: 700 }}>
{bin.settings.inventory?.grainSubtype}
</Typography>
</Box>
switch (bin.storage()){
case pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN:
return (
<Box>
<Typography variant="subtitle2" style={{ fontSize: "0.7rem", fontWeight: 700 }}>
{GrainDescriber(grainType).name}
</Typography>
<Typography variant="subtitle2" style={{ fontSize: "0.7rem", fontWeight: 700 }}>
{bin.settings.inventory?.grainSubtype}
</Typography>
</Box>
)
default:
return (
<Box>
<Typography variant="subtitle2" style={{ fontSize: "0.7rem", fontWeight: 700 }}>
{bin.grainName()}
</Typography>
</Box>
);
}
return (
<Box>
<Typography variant="subtitle2" style={{ fontSize: "0.7rem", fontWeight: 700 }}>
{bin.settings.inventory?.customTypeName}
</Typography>
</Box>
);
};
const tempDisplay = () => {
@ -425,6 +427,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 CustomGrainSelector from "grain/CustomGrainSelector";
// 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) {
@ -444,6 +449,11 @@ export default function BinSettings(props: Props) {
form.inventory.inventoryControl = inventoryControl
form.inventory.autoThreshold = autoFillThreshold
form.inventory.lidarDropCm = getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? lidarDropDistance * 30.48 : lidarDropDistance
if(form.inventory.grainType !== pond.Grain.GRAIN_CUSTOM){
form.inventory.customGrain = undefined
}else{
form.inventory.customGrain = customGrain
}
}
binAPI
@ -493,6 +503,11 @@ 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
if(form.inventory.grainType !== pond.Grain.GRAIN_CUSTOM){
form.inventory.customGrain = undefined
}else{
form.inventory.customGrain = customGrain
}
}
binAPI
.updateBin(bin.key(), form, as)
@ -1014,7 +1029,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}}>
<CustomGrainSelector initialGrain={customGrain} onGrainSettingsChange={(newGrainSettings) => {setCustomGrain(newGrainSettings)}}/>
</Box>
}
</React.Fragment>
:
<React.Fragment>
<Box className={classes.bottomSpacing}>
<SearchSelect
label="Type"
@ -1040,49 +1085,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 +1104,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

@ -70,6 +70,7 @@ import Edit from "@mui/icons-material/Edit";
import { makeStyles } from "@mui/styles";
import ButtonGroup from "common/ButtonGroup";
import ModeChangeDialog from "./conditioning/modeChangeDialog";
import CustomGrainSelector from "grain/CustomGrainSelector";
const useStyles = makeStyles((theme: Theme) => {
return ({
@ -286,6 +287,7 @@ export default function BinVisualizer(props: Props) {
const [openModeChange, setOpenModeChange] = useState(false)
const [modeChangeInProgress, setModeChangeInProgress] = useState(false)
const [newGrainSettings, setNewGrainSettings] = useState<pond.GrainSettings>()
useEffect(() => {
setModeTime(moment(bin.status.lastModeChange));
@ -327,16 +329,26 @@ export default function BinVisualizer(props: Props) {
setBushPerTonne(bin.settings.inventory.bushelsPerTonne.toFixed(2));
setGrainSubtype(bin.subtype());
setNewBinMode(bin.settings.mode);
setNewGrainSettings(bin.customGrain());
}
if (bin.settings) {
let t = bin.settings.outdoorTemp;
let t = 0
let h = 0
if(ambient){
t = ambient.temperature
h = ambient.humidity
}else{
t = bin.settings.outdoorTemp;//use what is already set in the bin settings
h = bin.settings.outdoorHumidity
}
if (user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
t = CtoF(t);
}
setOutdoorTemp(t);
setOutdoorHumidity(h)
}
if (bin.settings) setOutdoorHumidity(bin.settings.outdoorHumidity);
}, [bin, user]);
}, [bin, user, ambient]);
useEffect(() => {
//if(loadingTrend) return
@ -515,18 +527,13 @@ export default function BinVisualizer(props: Props) {
const capacity = bin.settings.specs?.bushelCapacity ?? 0;
const grainBushels = bin.bushels();
const isEmpty = bin.settings.inventory?.empty === true || !grainBushels || grainBushels <= 0;
const grainType = bin.settings.inventory?.grainType;
const grainTypeName = isEmpty || !grainType ? "" : GrainDescriber(grainType).name;
const customTypeName = bin.settings.inventory?.customTypeName;
const grainSubtype = bin.settings.inventory?.grainSubtype;
return (
<Box>
{/* grain display */}
<Box display="flex" justifyContent="space-between">
<Typography variant="subtitle2" color="textPrimary" style={{ fontWeight: 800 }}>
{bin.storage() === pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN
? grainTypeName
: customTypeName}
{isEmpty ? "" : bin.grainName()}
{grainSubtype !== "" ? " - " + grainSubtype : ""}
</Typography>
<Box
@ -768,6 +775,7 @@ export default function BinVisualizer(props: Props) {
setGrainType(bin.settings.inventory.grainType);
setGrainOption(ToGrainOption(bin.settings.inventory.grainType));
setBushPerTonne(bin.settings.inventory.bushelsPerTonne.toFixed(2));
setNewGrainSettings(bin.customGrain())
setGrainSubtype(bin.subtype());
}
setGrainChangeDialog(false);
@ -799,11 +807,13 @@ export default function BinVisualizer(props: Props) {
setCustomTypeName("");
setGrainOption(ToGrainOption(pond.Grain.GRAIN_NONE));
if (checked) {
setStorageType(pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN);
setGrainType(pond.Grain.GRAIN_CUSTOM);
} else {
setStorageType(pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN);
setGrainType(pond.Grain.GRAIN_CUSTOM);
setNewGrainSettings(bin.customGrain())
} else {
setStorageType(pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN);
setGrainType(pond.Grain.GRAIN_NONE);
setNewGrainSettings(undefined)
}
}}
name="storage"
@ -834,7 +844,7 @@ export default function BinVisualizer(props: Props) {
)}
{isCustomInventory ? (
<React.Fragment>
<TextField
{/* <TextField
label="Type"
value={customTypeName}
type="text"
@ -859,7 +869,8 @@ export default function BinVisualizer(props: Props) {
variant="outlined"
className={classes.bottomSpacing}
/>
)}
)} */}
<CustomGrainSelector initialGrain={bin.customGrain()} onGrainSettingsChange={settings => {setNewGrainSettings(settings)}}/>
</React.Fragment>
) : (
<TextField
@ -1404,6 +1415,7 @@ export default function BinVisualizer(props: Props) {
}
}}
inventoryControl={bin.inventoryControl()}
customGrain={bin.customGrain()}
/>
</Box>
</Box>
@ -1574,6 +1586,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 +1634,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 +1654,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 +1699,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>
@ -1968,6 +1988,7 @@ export default function BinVisualizer(props: Props) {
? 0
: parseFloat(bushPerTonne);
b.settings.inventory.grainSubtype = grainSubtype;
b.settings.inventory.customGrain = newGrainSettings
}
if (b.settings.outdoorTemp !== undefined) {
@ -1986,7 +2007,7 @@ export default function BinVisualizer(props: Props) {
}
b.settings.mode = newBinMode;
}
console.log(b.settings)
binAPI.updateBin(bin.key(), b.settings, as).then(resp => {
refresh();
});
@ -2013,8 +2034,8 @@ export default function BinVisualizer(props: Props) {
if(refresh) updateBin();
}}
defaultTargetMoisture={bin.settings.inventory?.initialMoisture}
// defaultOutdoorTemp={get this from any ambient sensors}
// defaultOutdoorHumidity={get this from any ambient sensors}
defaultOutdoorTemp={outdoorTemp}
defaultOutdoorHumidity={outdoorHumidity}
changeTargetMoisture={newMoisture => {
setTargetMoisture(newMoisture)
}}

View file

@ -108,9 +108,9 @@ export default function ModeChangeDialog(props: Props){
const [deviceOption, setDeviceOption] = useState<Option>()
useEffect(()=>{
setMoistureInput(defaultTargetMoisture?.toFixed(2) ?? "0")
setOutdoorTempInput(defaultOutdoorTemp?.toFixed(2) ?? "0")
setOutdoorHumidityInput(defaultOutdoorHumidity?.toFixed(2) ?? "0")
setMoistureInput(defaultTargetMoisture?.toString() ?? "0")
setOutdoorTempInput(defaultOutdoorTemp?.toString() ?? "0")
setOutdoorHumidityInput(defaultOutdoorHumidity?.toString() ?? "0")
},[defaultTargetMoisture, defaultOutdoorTemp, defaultOutdoorHumidity])
//get the interactions and components for the device when it is selected from the dropdown
@ -436,7 +436,9 @@ if (!selectedDevice) return;
}
stage1.steps.push(newStep);
});
stages.push(stage1)
if(stage1.steps.length > 0){
stages.push(stage1)
}
//stage two is to add the new interactions
let stage2: Stage = {
title: "Add New Interactions",
@ -447,7 +449,9 @@ if (!selectedDevice) return;
}
]
}
stages.push(stage2)
if(stage2.steps.length > 0){
stages.push(stage2)
}
//stage three is to update the controller components
let stage3: Stage = {
@ -463,7 +467,9 @@ if (!selectedDevice) return;
stage3.steps.push(newStep)
})
})
stages.push(stage3)
if(stage3.steps.length > 0){
stages.push(stage3)
}
//set those stages to a state variable
setPromiseStages(stages)
}
@ -618,11 +624,7 @@ if (!selectedDevice) return;
onChange={event => {
setOutdoorTempInput(event.target.value)
if(changeOutdoorTemp && !isNaN(parseFloat(event.target.value))){
//if the users prefs are F will need to convert it to C when sending it back
let val = parseFloat(event.target.value)
if(getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
val = fahrenheitToCelsius(val)
}
changeOutdoorTemp(val)
}
}}
@ -721,7 +723,7 @@ if (!selectedDevice) return;
<Box>
<PromiseProgress
stages={promiseStages}
automaticStart
description="These are the changes that will occur in order to change your bin mode with the given options. Press start to begin."
failFast
onStart={() => {
startChange()

View file

@ -170,18 +170,18 @@ export default function BinGraphs(props: Props) {
setEndDate(newEndDate);
};
const determineGrainColour = () => {
let col = "yellow";
let binInv = bin.settings.inventory;
if (binInv) {
if (binInv.grainType === pond.Grain.GRAIN_CUSTOM) {
col = stringToMaterialColour(binInv.customTypeName);
} else if (binInv.grainType !== pond.Grain.GRAIN_NONE) {
col = GrainDescriber(binInv.grainType).colour;
}
}
return col;
};
// const determineGrainColour = () => {
// let col = "yellow";
// let binInv = bin.settings.inventory;
// if (binInv) {
// if (binInv.grainType === pond.Grain.GRAIN_CUSTOM) {
// col = stringToMaterialColour(binInv.customTypeName);
// } else if (binInv.grainType !== pond.Grain.GRAIN_NONE) {
// col = GrainDescriber(binInv.grainType).colour;
// }
// }
// return col;
// };
const inventoryGraph = () => {
return (
@ -193,7 +193,7 @@ export default function BinGraphs(props: Props) {
endDate={endDate}
binLoading={binLoading}
bin={bin}
colour={determineGrainColour()}
colour={bin.grainColour()}
fertilizerBin={bin.settings.storage === pond.BinStorage.BIN_STORAGE_FERTILIZER}
/>
</Grid>

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);