Merge branch 'i2c_detect' into dev_environment

This commit is contained in:
csawatzky 2025-10-07 12:44:07 -06:00
commit 37b1a1fade
30 changed files with 332 additions and 141 deletions

View file

@ -103,8 +103,10 @@ export default function BinCard(props: Props) {
let filteredNodes = c.filteredNodes(true)
allTemps.push(...filteredNodes.temps);
allHums.push(...filteredNodes.humids);
allMoistures.push(...filteredNodes.moistures);
if(avg(c.humidities) !== 0){ //if the average of the humidities is 0 that means that all the readings are 0 and it is a temp only cable
allHums.push(...filteredNodes.humids);
allMoistures.push(...filteredNodes.moistures);
}
//set the hottest and coldest nodes
if (coldestNode === undefined || Math.min(...filteredNodes.temps) < coldestNode.temp) {
@ -490,7 +492,7 @@ export default function BinCard(props: Props) {
return (
<Card style={{ cursor: "pointer" }}>
{user.hasFeature("admin") && (
{user.hasFeature("installer") && (
<IconButton
style={{
height: 35,

View file

@ -33,7 +33,7 @@ const useStyles = makeStyles((theme: Theme) => {
interface Props {
interactions: Interaction[];
interactionDevices: Map<string, number>;
mode: pond.BinMode.BIN_MODE_DRYING | pond.BinMode.BIN_MODE_HYDRATING;
mode: pond.BinMode;
plenums?: Plenum[];
ambients?: Ambient[];
heaters?: Controller[];
@ -94,10 +94,21 @@ export default function BinConditioningCard(props: Props) {
return conditioningInteractions;
};
const modeDisplay = () => {
switch(mode){
case pond.BinMode.BIN_MODE_DRYING:
return "Drying"
case pond.BinMode.BIN_MODE_HYDRATING:
return "Hydrating"
case pond.BinMode.BIN_MODE_COOLDOWN:
return "Cooldown"
}
}
return (
<Card raised className={classes.card}>
<Typography style={{ fontWeight: 650 }}>
{mode === pond.BinMode.BIN_MODE_DRYING ? "Drying" : "Hydrating"} Conditions
{modeDisplay()} Conditions
</Typography>
{conditionsDisplay()}
</Card>

View file

@ -47,8 +47,6 @@ const useStyles = makeStyles((theme: Theme) => {
sliderThumb: {
height: 15,
width: 15,
marginTop: -6,
marginLeft: -7.5,
backgroundColor: "yellow"
},
sliderTrack: {
@ -60,10 +58,10 @@ const useStyles = makeStyles((theme: Theme) => {
backgroundColor: "white"
},
sliderValLabel: {
left: "calc(-50%)",
top: 22,
left: -20,
top: 40,
background: "transparent",
"& *": {
background: "transparent",
color: "#fff"
}
},

View file

@ -476,11 +476,11 @@ export default function BinSVGV2(props: Props) {
return cableGrainPath;
};
const nodeClass = (nodeTemp: number) => {
if (hottestNodeTemp && hottestNodeTemp.toFixed(2) === nodeTemp.toFixed(2)) {
const nodeClass = (nodeTemp: number, underTop: boolean) => {
if (hottestNodeTemp && hottestNodeTemp.toFixed(2) === nodeTemp.toFixed(2) && underTop) {
return classes.hotNode;
}
if (coldestNodeTemp && coldestNodeTemp.toFixed(2) === nodeTemp.toFixed(2)) {
if (coldestNodeTemp && coldestNodeTemp.toFixed(2) === nodeTemp.toFixed(2) && underTop) {
return classes.coldNode;
}
return classes.cableNode;
@ -507,12 +507,12 @@ export default function BinSVGV2(props: Props) {
//if the cables have the same number of nodes
if (b.temperatures.length === a.temperatures.length) {
//sort by temp only last and any type that has humidity first
if (
a.settings.subtype === quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP &&
b.settings.subtype !== quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP
) {
if ((avg(a.humidities) === 0) && (avg(b.humidities) !== 0)) {
return 1;
} else {
} else if ((avg(b.humidities) === 0) && (avg(a.humidities) !== 0)) {
return -1;
}
else {
return a.key() > b.key() ? 1 : -1;
}
}
@ -759,7 +759,7 @@ export default function BinSVGV2(props: Props) {
return (
<g key={e.key}>
{e.nodeNumber === topNode && !e.excluded && topNodeHat(cablePos, e.y)}
<circle cx={cablePos} cy={e.y} className={e.excluded ? classes.excludedNode : nodeClass(e.nodeTemp)} r="16"></circle>
<circle cx={cablePos} cy={e.y} className={e.excluded ? classes.excludedNode : nodeClass(e.nodeTemp, e.nodeNumber <= topNode)} r="16"></circle>
</g>
)
}

View file

@ -485,6 +485,7 @@ export default function BinSettings(props: Props) {
form.inventory.inventoryControl = inventoryControl
form.inventory.autoThreshold = autoFillThreshold
}
console.log(form)
binAPI
.updateBin(bin.key(), form, as)
.then(response => {
@ -741,7 +742,7 @@ export default function BinSettings(props: Props) {
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR:
return "Hybrid (lidar)"
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART:
return "Auto (LibraCart)"
return "Auto (Libra Cart)"
default:
return "Manual"
}
@ -905,7 +906,7 @@ export default function BinSettings(props: Props) {
<FormControlLabel
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART}
control={<Radio />}
label={"Auto (LibraCart)"}
label={"Auto (Libra Cart)"}
/>
}
</RadioGroup>
@ -951,7 +952,7 @@ export default function BinSettings(props: Props) {
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART &&
<Box width="100%" padding={2}>
<SearchSelect
label="LibraCart Destination"
label="Libra Cart Destination"
selected={lcDestination}
changeSelection={option => {
let newForm = form;
@ -963,7 +964,7 @@ export default function BinSettings(props: Props) {
options={lcDestinationOptions}
/>
<Typography variant="caption">
The linked bins inventory will be adjusted When the LibraCart Destination data is synced every 6 hours
The linked bins inventory will be adjusted When the Libra Cart Destination data is synced every 6 hours
</Typography>
</Box>
}

View file

@ -21,7 +21,7 @@ import { Bin, Component } from "models";
import { GrainCable } from "models/GrainCable";
import { UnitMeasurement } from "models/UnitMeasurement";
import Co2Icon from "products/CommonIcons/co2Icon";
import { pond } from "protobuf-ts/pond";
import { pond, quack } from "protobuf-ts/pond";
import { useBinAPI, useGlobalState, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
import { avg, getTemperatureUnit } from "utils";
@ -200,33 +200,32 @@ export default function BinStorageConditions(props: Props) {
// }
});
let nodeEMCs: number[] = [];
filteredNodes.moistures.forEach((emc, i) => {
if(cable.subType() !== quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP){
let nodeEMCs: number[] = [];
filteredNodes.moistures.forEach((emc, i) => {
if (bin.settings.inventory?.targetMoisture) {
// if (i < cable.topNode) {
nodeEMCs.push(emc);
nodeEMCs.push(emc);
emcCounts.total++;
if (
emc >
bin.settings.inventory.targetMoisture +
bin.settings.inventory.moistureTargetDeviation
bin.settings.inventory.moistureTargetDeviation
) {
emcCounts.above++;
} else if (
emc <
bin.settings.inventory.targetMoisture -
bin.settings.inventory.moistureTargetDeviation
bin.settings.inventory.moistureTargetDeviation
) {
emcCounts.below++;
} else {
emcCounts.onTarget++;
}
// }
}
});
}
});
cableEMCAvgs.push(avg(nodeEMCs));
}
cableTempAvgs.push(avg(nodeTemps));
cableEMCAvgs.push(avg(nodeEMCs));
}
});
if (cableTempAvgs.length > 0) {

View file

@ -250,6 +250,7 @@ export default function BinVisualizer(props: Props) {
const [cfmHighOpen, setCFMHighOpen] = useState(false);
const [{ user }] = useGlobalState();
const [newPreset, setNewPreset] = useState<pond.BinMode>(pond.BinMode.BIN_MODE_NONE);
const [newBinMode, setNewBinMode] = useState<pond.BinMode>(pond.BinMode.BIN_MODE_NONE);
const [selectedCable, setSelectedCable] = useState<GrainCable>();
const [openNodeDialog, setOpenNodeDialog] = useState(false);
const [cableDevice, setCableDevice] = useState<Device | undefined>();
@ -345,6 +346,7 @@ export default function BinVisualizer(props: Props) {
setGrainOption(ToGrainOption(bin.settings.inventory.grainType));
setBushPerTonne(bin.settings.inventory.bushelsPerTonne.toFixed(2));
setGrainSubtype(bin.subtype());
setNewBinMode(bin.settings.mode);
}
if (bin.settings) {
let t = bin.settings.outdoorTemp;
@ -384,8 +386,11 @@ export default function BinVisualizer(props: Props) {
//also reverse it so that the node 1 (end of the cable) is in the first position
let filteredNodes = cable.filteredNodes(true)
temps.push(...filteredNodes.temps)
humids.push(...filteredNodes.humids)
emcs.push(...filteredNodes.moistures)
//only push to the humidity values if the cable reads humidities
if(cable.subType() !== quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP){
humids.push(...filteredNodes.humids)
emcs.push(...filteredNodes.moistures)
}
// let tempClone = cloneDeep(cable.temperatures).reverse();
// let humClone = cloneDeep(cable.humidities).reverse();
// let emcClone = cloneDeep(cable.grainMoistures).reverse();
@ -409,14 +414,15 @@ export default function BinVisualizer(props: Props) {
//if the lowest temp for this cable is less than the temp in the low node conditions or the low node conditions are not set
//use this cables lowest node and trend data in the condition
if (!lowNodeConditions || Math.min(...temps) < lowNodeConditions.tempC) {
if (!lowNodeConditions || Math.min(...filteredNodes.temps) < lowNodeConditions.tempC) {
//determine which node is the coldest so that the data displayed is for the same node
let lowTempIndex =
temps.indexOf(Math.min(...temps)) === -1 ? 0 : temps.indexOf(Math.min(...temps));
filteredNodes.temps.indexOf(Math.min(...filteredNodes.temps)) === -1 ? 0 : filteredNodes.temps.indexOf(Math.min(...filteredNodes.temps));
console.log(lowTempIndex)
lowNodeConditions = {
tempC: temps[lowTempIndex],
humidity: humids[lowTempIndex],
emc: emcs[lowTempIndex],
tempC: filteredNodes.temps[lowTempIndex],
humidity: filteredNodes.humids[lowTempIndex],
emc: filteredNodes.moistures[lowTempIndex],
tempCTrend: cableTrendData.coldNodeTrend?.tempTrend ?? 0,
humidityTrend: cableTrendData.coldNodeTrend?.humidityTrend ?? 0,
emcTrend: cableTrendData.coldNodeTrend?.emcTrend ?? 0
@ -424,13 +430,13 @@ export default function BinVisualizer(props: Props) {
}
//do the same for the high node
if (!highNodeConditions || cable.maxTemp() > highNodeConditions.tempC) {
if (!highNodeConditions || Math.max(...filteredNodes.temps) > highNodeConditions.tempC) {
let highTempIndex =
temps.indexOf(Math.max(...temps)) === -1 ? 0 : temps.indexOf(Math.max(...temps));
filteredNodes.temps.indexOf(Math.max(...filteredNodes.temps)) === -1 ? 0 : filteredNodes.temps.indexOf(Math.max(...filteredNodes.temps));
highNodeConditions = {
tempC: temps[highTempIndex],
humidity: humids[highTempIndex],
emc: emcs[highTempIndex],
tempC: filteredNodes.temps[highTempIndex],
humidity: filteredNodes.humids[highTempIndex],
emc: filteredNodes.moistures[highTempIndex],
tempCTrend: cableTrendData.hotNodeTrend?.tempTrend ?? 0,
humidityTrend: cableTrendData.hotNodeTrend?.humidityTrend ?? 0,
emcTrend: cableTrendData.hotNodeTrend?.emcTrend ?? 0
@ -1967,6 +1973,7 @@ export default function BinVisualizer(props: Props) {
const setModeStorage = () => {
setNewPreset(pond.BinMode.BIN_MODE_STORAGE);
setNewBinMode(pond.BinMode.BIN_MODE_STORAGE);
};
const setModeCooldown = () => {
@ -1974,6 +1981,7 @@ export default function BinVisualizer(props: Props) {
return;
}
setNewPreset(pond.BinMode.BIN_MODE_COOLDOWN);
setNewBinMode(pond.BinMode.BIN_MODE_COOLDOWN);
};
const setModeDrying = () => {
@ -1982,13 +1990,16 @@ export default function BinVisualizer(props: Props) {
bin.settings.inventory?.initialMoisture < bin.settings.inventory?.targetMoisture
) {
setNewPreset(pond.BinMode.BIN_MODE_HYDRATING);
setNewBinMode(pond.BinMode.BIN_MODE_HYDRATING);
} else {
setNewPreset(pond.BinMode.BIN_MODE_DRYING);
setNewBinMode(pond.BinMode.BIN_MODE_DRYING);
}
};
const closeMoistureDialog = () => {
setNewPreset(0);
setNewBinMode(bin.settings.mode);
setShowInputMoisture(false);
};
@ -2049,6 +2060,7 @@ export default function BinVisualizer(props: Props) {
<Button
onClick={() => {
setNewPreset(0);
setNewBinMode(bin.settings.mode);
setShowInputMoisture(false);
}}
color="primary">
@ -2090,7 +2102,13 @@ export default function BinVisualizer(props: Props) {
}
if (b.settings.outdoorHumidity !== undefined)
b.settings.outdoorHumidity = Number(outdoorHumidityInput);
b.settings.mode = newPreset;
if (b.settings.mode != newBinMode){
if(b.settings.inventory){
b.settings.inventory.initialTimestamp = moment().format();
}
b.settings.mode = newBinMode;
}
binAPI.updateBin(bin.key(), b.settings, as).then(resp => {
refresh();

File diff suppressed because one or more lines are too long

View file

@ -36,6 +36,7 @@ import {
} from "pbHelpers/AddressType";
import {
extension,
FilterSubtypeI2C,
getAddressTypes,
GetComponentTypeOptions,
getFriendlyName,
@ -240,7 +241,8 @@ export default function ComponentSettings(props: Props) {
const getAvailablePositions = (
addressType: quack.AddressType,
componentType: quack.ComponentType
componentType: quack.ComponentType,
subtype: number
): number[] => {
let positions = availablePositions.get(addressType);
if (!positions) return [];
@ -249,7 +251,8 @@ export default function ComponentSettings(props: Props) {
case quack.AddressType.ADDRESS_TYPE_SPI:
let componentPositions;
if (positions) {
componentPositions = (positions as ComponentAvailabilityMap).get(componentType);
componentPositions = (positions as ComponentAvailabilityMap).get(componentType); //gets the addresses that are not claimed yet
componentPositions = FilterSubtypeI2C(componentType, subtype, componentPositions as number[]) //filters the I2C addresses for valid ones for the subtype
}
return componentPositions ? (componentPositions as number[]) : [];
case quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY:
@ -264,7 +267,8 @@ export default function ComponentSettings(props: Props) {
if (mode === "add") {
let positions = getAvailablePositions(
component.settings.addressType,
component.settings.type
component.settings.type,
component.settings.subtype
);
availablePosition = positions ? positions.length > 0 : false;
}
@ -285,7 +289,7 @@ export default function ComponentSettings(props: Props) {
component.settings.subtype
);
for (let i = 0; i < addressTypes.length; i++) {
if (getAvailablePositions(addressTypes[i], component.settings.type).length > 0) {
if (getAvailablePositions(addressTypes[i], component.settings.type, component.settings.subtype).length > 0) {
return true;
}
}
@ -383,7 +387,7 @@ export default function ComponentSettings(props: Props) {
formComponent.settings.subtype
)[0]);
formComponent.settings.address = or(
getAvailablePositions(formComponent.settings.addressType, formComponent.settings.type)[0],
getAvailablePositions(formComponent.settings.addressType, formComponent.settings.type, formComponent.settings.subtype)[0],
Component.create().settings.address
);
formComponent.settings.name = option.label;
@ -426,7 +430,7 @@ export default function ComponentSettings(props: Props) {
let availableMenuItemPositions = [];
for (let i = 0; i < addressTypes.length; i++) {
let addressType = addressTypes[i];
let availablePositions = getAvailablePositions(addressType, type);
let availablePositions = getAvailablePositions(addressType, type, subtype);
availableMenuItemPositions.push(
<MenuItem key={addressType} divider disabled>

View file

@ -40,7 +40,7 @@ export default function DeviceScannedComponents(props: Props){
const [openDialog, setOpenDialog] = useState(false);
useEffect(()=>{
console.log(scannedComponents)
//console.log(scannedComponents)
if (scannedComponents.i2c) setScannedI2C(scannedComponents.i2c)
//makes the array empty for testing
// if (scannedComponents.i2c) {
@ -226,7 +226,9 @@ export default function DeviceScannedComponents(props: Props){
<Button variant="contained" color="primary" onClick={()=>{removeScan(scannedI2C.key)}}>Clear Scan</Button>
</Box>
{validCompAddresses.length > 0 ? validCompAddresses.map((addr, index) => {
return <ScannedI2C key={index} sensorNum={index+1} addressData={addr} deviceProduct={device.settings.product} componentSelectionCallback={componentSelection} availablePositions={availablePositions.get(quack.AddressType.ADDRESS_TYPE_I2C) ?? new Map<quack.ComponentType, number[]>()}/>
return (
<ScannedI2C key={index} sensorNum={index+1} addressData={addr} deviceProduct={device.settings.product} componentSelectionCallback={componentSelection} availablePositions={availablePositions.get(quack.AddressType.ADDRESS_TYPE_I2C) ?? new Map<quack.ComponentType, number[]>()}/>
)
}) :
<Box sx={{
margin: 1,

View file

@ -107,6 +107,7 @@ export default function ScannedI2C(props: Props){
return (
<Box sx={{marginY: 1}}>
{sensorNum && <Typography>Sensor {sensorNum}</Typography>}
<Typography variant="caption">{addressData.expansionLine !== 0 && "Expansion Line: " + addressData.expansionLine}</Typography>
{types.length > 0 ?
<React.Fragment>
<Grid2 container direction="row" alignItems="center" justifyContent="space-between">
@ -145,7 +146,7 @@ export default function ScannedI2C(props: Props){
}}
select>
{subtypeOptions.map(s => (
<MenuItem value={s.key}>{s.friendlyName}</MenuItem>
<MenuItem key={s.key} value={s.key}>{s.friendlyName}</MenuItem>
))}
</TextField>
}

View file

@ -338,14 +338,12 @@ export default function GateDevice(props: Props) {
return (
<Grid
container
direction="row"
alignContent="center"
direction={(isMobile) ? "column" : "row"}
width="100%"
//alignItems="center"
alignItems="center"
>
<Grid size={{ sm: 12, lg: isMobile || drawerView ? 12 : 4}} style={{ padding: 10 }}>
<Box
paddingLeft={1}
display="flex"
justifyContent="space-between"
alignItems="center"
@ -364,20 +362,6 @@ export default function GateDevice(props: Props) {
}
]}
/>
{/* <ToggleButtonGroup value={detail} exclusive size="small" aria-label="detail">
<ToggleButton
onClick={() => setDetail("analytics")}
value={"analytics"}
aria-label="Analysis Graphs">
Analysis
</ToggleButton>
<ToggleButton
onClick={() => setDetail("sensors")}
value={"sensors"}
aria-label="Sensor Graphs">
Sensors
</ToggleButton>
</ToggleButtonGroup> */}
</Box>
<Card raised>
<GateSVG

View file

@ -258,9 +258,9 @@ export default function GateFlowGraph(props: Props) {
<React.Fragment>
{flowEvents.length > 0 ? (
<Grid container direction="row" spacing={2} className={classes.eventGrid}>
<Grid size={3}>
<Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.eventCard}>
<Grid container alignItems="center">
<Grid container width="100%" direction="column" alignItems="center">
<Grid size={9}>
<Box display="flex" justifyContent="space-between">
<Typography>Total Events:</Typography>
@ -271,7 +271,7 @@ export default function GateFlowGraph(props: Props) {
</Grid>
<Grid size={9}>
<Box display="flex" justifyContent="space-between">
<Typography>Total Time:</Typography>
<Typography>Time:</Typography>
<Typography style={{ fontWeight: 650, fontSize: 15 }}>
{moment.duration(totalTimeS, "s").humanize()}
</Typography>
@ -280,9 +280,9 @@ export default function GateFlowGraph(props: Props) {
</Grid>
</Card>
</Grid>
<Grid size={3}>
<Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.eventCard}>
<Grid container alignItems="center">
<Grid container width="100%" direction="column" alignItems="center">
<Grid size={9}>
<Box display="flex" justifyContent="space-between">
<Typography>Events Inside:</Typography>
@ -293,7 +293,7 @@ export default function GateFlowGraph(props: Props) {
</Grid>
<Grid size={9}>
<Box display="flex" justifyContent="space-between">
<Typography>Event Time:</Typography>
<Typography>Time:</Typography>
<Typography style={{ fontWeight: 650, fontSize: 15 }}>
{moment.duration(timeSInside, "s").humanize()}
</Typography>
@ -302,9 +302,9 @@ export default function GateFlowGraph(props: Props) {
</Grid>
</Card>
</Grid>
<Grid size={3}>
<Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.eventCard}>
<Grid container alignItems="center">
<Grid container width="100%" direction="column" alignItems="center">
<Grid size={9}>
<Box display="flex" justifyContent="space-between">
<Typography>Events Outside:</Typography>
@ -315,7 +315,7 @@ export default function GateFlowGraph(props: Props) {
</Grid>
<Grid size={9}>
<Box display="flex" justifyContent="space-between">
<Typography>Event Time:</Typography>
<Typography>Time:</Typography>
<Typography style={{ fontWeight: 650, fontSize: 15 }}>
{moment.duration(timeSOutside, "s").humanize()}
</Typography>
@ -324,15 +324,15 @@ export default function GateFlowGraph(props: Props) {
</Grid>
</Card>
</Grid>
<Grid size={3}>
<Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.eventCard}>
<Grid container direction="column" alignItems="center">
<Grid container width="100%" direction="column" alignItems="center">
<Grid>
<Typography>Performance:</Typography>
</Grid>
<Grid>
<Typography style={{ fontWeight: 650, fontSize: 15 }}>
{(eventsInside / totalEvents) * 100}%
{((eventsInside / totalEvents) * 100).toFixed(2)}%
</Typography>
</Grid>
</Grid>
@ -367,7 +367,7 @@ export default function GateFlowGraph(props: Props) {
<React.Fragment>
{runtime && (
<Grid container direction="row" spacing={2} className={classes.runtimeGrid}>
<Grid size={3}>
<Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.calcCard}>
<Grid container width="100%" direction="column" alignItems="center">
<Grid>Approximate Runtime:</Grid>
@ -379,7 +379,7 @@ export default function GateFlowGraph(props: Props) {
</Grid>
</Card>
</Grid>
<Grid size={3}>
<Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.calcCard}>
<Grid container width="100%" direction="column" alignItems="center">
<Grid>Cost to run PCA:</Grid>
@ -394,7 +394,7 @@ export default function GateFlowGraph(props: Props) {
</Grid>
</Card>
</Grid>
<Grid size={3}>
<Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.calcCard}>
<Grid container width="100%" direction="column" alignItems="center">
<Grid>Cost to run APU:</Grid>
@ -409,7 +409,7 @@ export default function GateFlowGraph(props: Props) {
</Grid>
</Card>
</Grid>
<Grid size={3}>
<Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.calcCard}>
<Grid container width="100%" direction="column" alignItems="center">
<Grid>Total Cost:</Grid>

View file

@ -190,7 +190,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
[
pond.Grain.GRAIN_OATS,
{
name: "Oats",
name: "Oats a",
group: "Oats",
equation: Equation.henderson,
a: 0.000085511,
@ -203,6 +203,40 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
weightConversionKg: 15.4222988297197,
bushelsPerTonne: 64.842
}
],
[
pond.Grain.GRAIN_OATS_B,
{
name: "Oats b",
group: "Oats",
equation: Equation.chungPfost,
a: 442.85,
b: 0.21228,
c: 35.803,
setTempC: 32.0,
targetMC: 13.6,
img: OatImg,
colour: "#79955a",
weightConversionKg: 15.4222988297197,
bushelsPerTonne: 64.842
}
],
[
pond.Grain.GRAIN_OATS_C,
{
name: "Oats c",
group: "Oats",
equation: Equation.oswin,
a: 12.412,
b: -0.060707,
c: 2.9397,
setTempC: 32.0,
targetMC: 13.6,
img: OatImg,
colour: "#79955a",
weightConversionKg: 15.4222988297197,
bushelsPerTonne: 64.842
}
],
[
pond.Grain.GRAIN_PEANUTS,
@ -506,6 +540,66 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
weightConversionKg: 27.2155,
bushelsPerTonne: 36.744
}
],[
pond.Grain.GRAIN_RYE_A,
{
name: "Rye a",
group: "Rye",
equation: Equation.henderson,
a: 0.00006343,
b: 2.2060,
c: 13.1810,
setTempC: defaultSetTemp,
targetMC: 15.0,
colour: "grey",
weightConversionKg: 25.4,
bushelsPerTonne: 39.368
}
],[
pond.Grain.GRAIN_RYE_B,
{
name: "Rye b",
group: "Rye",
equation: Equation.chungPfost,
a: 461.0230,
b: 0.1840,
c: 36.7410,
setTempC: defaultSetTemp,
targetMC: 15.0,
colour: "grey",
weightConversionKg: 25.4,
bushelsPerTonne: 39.368
},
],[
pond.Grain.GRAIN_RYE_C,
{
name: "Rye c",
group: "Rye",
equation: Equation.halsey,
a: 4.2970,
b: 0.380,
c: 2.2710,
setTempC: defaultSetTemp,
targetMC: 15.0,
colour: "grey",
weightConversionKg: 25.4,
bushelsPerTonne: 39.368
},
],[
pond.Grain.GRAIN_RYE_D,
{
name: "Rye d",
group: "Rye",
equation: Equation.oswin,
a: 11.8870,
b: 0.0210,
c: 3.2620,
setTempC: defaultSetTemp,
targetMC: 15.0,
colour: "grey",
weightConversionKg: 25.4,
bushelsPerTonne: 39.368
}
]
]);

View file

@ -182,7 +182,7 @@ export default function LibraCartAccess() {
color="primary"
onClick={e => {
e.preventDefault();
window.open(`https://staging.cloud.agrimatics.com/grain/integrations`, "_blank");
window.open(`https://cloud.agrimatics.com/grain/integrations`, "_blank");
}}>
Link Libra Cart Account
</Button>
@ -196,7 +196,7 @@ export default function LibraCartAccess() {
</Grid>
</Grid>
<Grid>
<Tooltip title={"This will Sync the data for all LibraCart accounts linked to " + (as ? "this team" : "your account")}>
<Tooltip title={"This will Sync the data for all Libra Cart accounts linked to " + (as ? "this team" : "your account")}>
<Button
variant="contained"
color="primary"
@ -210,7 +210,7 @@ export default function LibraCartAccess() {
</Grid>
<Box paddingTop={2}>
<Typography>
LibraCart data will sync every 6 hours, if the data is needed immediately you can select the Sync button. It may take up to 15 minutes for the data to appear in our platform.
Libra Cart data will sync every 6 hours, if the data is needed immediately you can select the Sync button. It may take up to 15 minutes for the data to appear in our platform.
</Typography>
</Box>

View file

@ -96,7 +96,7 @@ export class Bin {
}
public empty(): boolean {
return this.settings.inventory?.empty || (this.settings.inventory !== undefined && this.settings.inventory !== null && this.settings.inventory.grainBushels < 5);
return this.settings.inventory?.empty || (this.settings.inventory !== undefined && this.settings.inventory !== null && this.bushels() < 5);
}
public objectType(): pond.ObjectType {

View file

@ -273,7 +273,7 @@ export class GrainCable {
let filtered: number[] = []
values.forEach((val, index) => {
//if the node is not excluded AND is either under the top node OR top node is not set
if(!this.excludedNodes.includes(index) && (index <= this.topNode || this.topNode === 0)){
if(!this.excludedNodes.includes(index) && (index < this.topNode || this.topNode === 0)){
filtered.push(val)
}
})

View file

@ -70,19 +70,26 @@ const useStyles = makeStyles((theme: Theme) => ({
})
},
sideMenuOnClosed: {
transition: theme.transitions.create(["width", "z-index", "opacity"], {
transition: theme.transitions.create(["width"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
overflowX: "hidden",
width: theme.spacing(7),
zIndex: theme.zIndex.drawer,
opacity: 0,
// overflowX: "hidden",
width: theme.spacing(0),
// zIndex: theme.zIndex.drawer,
// opacity: 0,
[theme.breakpoints.up("md")]: {
width: theme.spacing(9),
width: theme.spacing(9.25),
opacity: 1
}
},
sideMenuOnClosedMobile: {
transition: theme.transitions.create(["width"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
width: theme.spacing(0),
},
list: {
paddingTop: 0
},
@ -480,7 +487,7 @@ export default function SideNavigator(props: Props) {
</Tooltip>
}
{user.hasFeature("libra-cart") &&
<Tooltip title="LibraCart" placement="right">
<Tooltip title="Libra Cart" placement="right">
<ListItemButton
id="tour-libraCart"
onClick={() => goTo("/libracart")}
@ -489,7 +496,7 @@ export default function SideNavigator(props: Props) {
<ListItemIcon>
<LibraCartIcon />
</ListItemIcon>
{open && <ListItemText primary="LibraCart" />}
{open && <ListItemText primary="Libra Cart" />}
</ListItemButton>
</Tooltip>
}
@ -504,7 +511,7 @@ export default function SideNavigator(props: Props) {
classes={{
paper: classNames(
classes.sideMenu,
open ? classes.sideMenuOpened : classes.sideMenuOnClosed
open ? classes.sideMenuOpened : (isMobile ? classes.sideMenuOnClosedMobile : classes.sideMenuOnClosed)
)
}}
anchor="left"

View file

@ -810,7 +810,8 @@ export default function Bin(props: Props) {
</Grid>
<Grid size={{ xs: 12, sm: 12, md: 12, lg: 2.6 }}>
{(bin.settings.mode === pond.BinMode.BIN_MODE_DRYING ||
bin.settings.mode === pond.BinMode.BIN_MODE_HYDRATING) && (
bin.settings.mode === pond.BinMode.BIN_MODE_HYDRATING ||
bin.settings.mode === pond.BinMode.BIN_MODE_COOLDOWN) && (
<BinConditioningCard
mode={bin.settings.mode}
interactions={interactions}

View file

@ -472,7 +472,7 @@ export default function Devices() {
]
if (hasPlenums) {
columns.push({
title: "Plenum",
title: "Temp/Humidity",
// sortKey: "hi",
// disableSort: true,
render: (device: Device) => {

View file

@ -103,7 +103,7 @@ export default function LibraCart() {
<Grid>
<Select
//style={{ maxWidth: 110 }}
title="LibraCart Account"
title="Libra Cart Account"
displayEmpty
disableUnderline={true}
value={currentOrg}

View file

@ -129,7 +129,7 @@ export default function Users() {
"marketplace",
"installer",
"cnhi",
"libracart"
"libra-cart"
].sort();
const [rows, setRows] = useState<User[]>([])

View file

@ -164,6 +164,10 @@ export interface ComponentTypeExtension {
filters?: GraphFilters
) => LineChartData;
secondaryComponentSettings?: () => pond.ComponentSettings[]
//(only for I2C) possibly add an optional map here that has a number (subtype) as the key and the addresses available for that subtype
//if the map does not exist then just use the array from the device availability,
//if the map does exist filter the availability after claims to find matching address between them and use that
subtypeI2CMap?: Map<number, number[]>
}
export interface Summary {
@ -1090,3 +1094,31 @@ export const GetNumNodesFromUnitMeasurement = (unitMeasurement: UnitMeasurement)
}
return nodes;
};
export const FilterSubtypeI2C = (
componentType: quack.ComponentType,
subtype: number,
positions: number[]
) => {
console.log("Filter I2C")
console.log(positions)
let filtered: number[] = []
//create an extension using the type and subtype
let ext = extension(componentType, subtype)
//if the extension has a subtype map
if (ext.subtypeI2CMap !== undefined){
//check if any of the position passed in match to the map values according to the subtype
let subtypePositions = ext.subtypeI2CMap.get(subtype)
if(subtypePositions !== undefined){
positions.forEach(position => {
if(subtypePositions.includes(position)){
filtered.push(position)
}
})
}
}else{
filtered = positions
}
//return any matches between the arrays
return filtered
}

View file

@ -95,6 +95,11 @@ export function Lidar(subtype: number = 0): ComponentTypeExtension {
minMeasurementPeriodMs: 1000,
icon: (theme?: "light" | "dark"): string | undefined => {
return theme === "light" ? LidarSensorDarkIcon : LidarSensorLightIcon;
}
},
subtypeI2CMap: new Map<number, number[]>([
[quack.LidarSubtype.LIDAR_SUBTYPE_NONE, [0x62]],
[quack.LidarSubtype.LIDAR_SUBTYPE_LONG_DISTANCE, [0x66]],
[quack.LidarSubtype.LIDAR_SUBTYPE_BENEWAKE_TF02W_MID_RANGE, [0x10]],
])
};
}

View file

@ -5,6 +5,7 @@ import { GetProductAvailability } from "products/DeviceProduct";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { or } from "utils/types";
import { FilterSubtypeI2C } from "./ComponentType";
export type ComponentAvailabilityMap = Map<quack.ComponentType, number[]>;
export type DevicePositions = number[] | ComponentAvailabilityMap | ConfigurablePin[];
@ -47,16 +48,17 @@ const DefaultAvailability: DeviceAvailabilityMap = new Map<quack.AddressType, De
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, DefaultPinArray],
[
quack.AddressType.ADDRESS_TYPE_I2C,
//this will contain all the possible addresses for a component type, if subtypes use specific addresses add a filter to the type describer, see lidar.ts for an example
new Map<quack.ComponentType, number[]>([
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18, 0x19, 0x1a]],
[quack.ComponentType.COMPONENT_TYPE_LIGHT, [0x29]],
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
[quack.ComponentType.COMPONENT_TYPE_STEPPER_MOTOR, [0x14, 0x15, 0x16, 0x17]],
[quack.ComponentType.COMPONENT_TYPE_PH, [0x40]],
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40, 0x61]], //TODO temp fix, needs to be fixed to use channels similar to PH
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40, 0x61]],
// [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated?
[quack.ComponentType.COMPONENT_TYPE_AIR_QUALITY, [0x58]],
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62, 0x66, 0x10]],
[quack.ComponentType.COMPONENT_TYPE_CAPACITANCE, [0x50]],
[quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE, [0x50]],
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],

View file

@ -71,16 +71,19 @@ export function describePower(power?: pond.DevicePower | null): PowerDescriber {
}
} else {
let thresholds = notChargingThresholds;
let suffix = "not charging";
if (power.inputVoltage >= 4) {
thresholds = chargingThresholds;
if (power.chargePercent === 100) {
suffix = "charged";
} else {
suffix = "charging";
}
}
result.description = power.chargePercent.toString() + "%, " + suffix;
// we would not actually know if it is charging here since it is just looking at the voltage now and not comparing to previous voltage
// let suffix = "not charging";
// if (power.inputVoltage >= 4) {
// thresholds = chargingThresholds;
// if (power.chargePercent === 100) {
// suffix = "charged";
// } else {
// suffix = "charging";
// }
// }
// result.description = power.chargePercent.toString() + "%, " + suffix;
// TODO: there is a plan in place to add a boolean value to power in the backend to have it check and set the boolean, we can then use that to know if it is charging or not
result.description = power.chargePercent.toString() + "%"
for (let i = 0; i < thresholds.length; i++) {
if (power.chargePercent >= thresholds[i].threshold) {
result.icon = thresholds[i].icon;

View file

@ -249,7 +249,7 @@ export const BindaptV2MonitorAvailability: DeviceAvailabilityMap = new Map<
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
// [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated?
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62, 0x66, 0x10]],
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]]
@ -282,7 +282,7 @@ export const BindaptV2AutomateAvailability: DeviceAvailabilityMap = new Map<
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
// [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated?
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62, 0x66, 0x10]],
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]]

View file

@ -10,7 +10,7 @@ import {
Textsms as TextIcon,
Contrast,
} from "@mui/icons-material";
import { AppBar, Box, Button, Checkbox, CircularProgress, Collapse, Divider, FormControl, FormControlLabel, FormGroup, FormHelperText, FormLabel, Grid2, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText, MenuItem, Radio, RadioGroup, TextField, Theme, ToggleButton, ToggleButtonGroup, Toolbar, Tooltip, Typography } from "@mui/material";
import { AppBar, Box, Button, Checkbox, CircularProgress, Collapse, Divider, FormControl, FormControlLabel, FormGroup, FormHelperText, FormLabel, Grid2, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText, MenuItem, Radio, RadioGroup, Slider, TextField, Theme, ToggleButton, ToggleButtonGroup, Toolbar, Tooltip, Typography } from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog";
import UserAvatar from "./UserAvatar";
import { useGlobalState, useSnackbar, useUserAPI } from "providers";
@ -31,6 +31,7 @@ import UnitsIcon from "@mui/icons-material/Category";
import { setThemeType } from "theme";
import { IsAdaptiveAgriculture } from "services/whiteLabel";
import { setDistanceUnit, setGrainUnit, setPressureUnit, setTemperatureUnit } from "utils";
import FieldsIcon from "products/AgIcons/FieldsIcon";
const useStyles = makeStyles((theme: Theme) => {
return ({
@ -82,7 +83,9 @@ export default function UserSettings(props: Props) {
const [avatarExpanded, setAvatarExpanded] = useState<boolean>(false);
const [notificationsExpanded, setNotificationsExpanded] = useState<boolean>(false);
const [unitsExpanded, setUnitsExpanded] = useState<boolean>(false);
const [mapsExpanded, setMapsExpanded] = useState<boolean>(false);
const [avatarUrl, setAvatarUrl] = useState<string>("");
const [sliderVal, setSliderVal] = useState(globalState.user.settings.mapZoom);
// const [themeValue, setThemeValue] = useState<"light" | "dark" | "system">(themeMode.mode)
useEffect(() => {
@ -151,6 +154,30 @@ export default function UserSettings(props: Props) {
setUser(updatedUser);
};
const changeDefaultZoom = (value: number) => {
setSliderVal(value);
let updatedUser = User.clone(user);
updatedUser.settings.mapZoom = value;
setUser(updatedUser)
};
const mapSettings = () => {
return (
<Box display="flex">
<Typography>Map Zoom Level</Typography>
<Slider
valueLabelDisplay="auto"
value={sliderVal}
min={1}
max={16}
onChange={(_, val) => {
changeDefaultZoom(val as number);
}}
/>
</Box>
);
};
const generalSettings = () => {
const { name, email, phoneNumber, timezone } = user.settings;
@ -631,15 +658,15 @@ export default function UserSettings(props: Props) {
</List>
</Collapse>
{/* <Divider />
<Divider />
<ListItem button onClick={() => setMapsExpanded(!mapsExpanded)}>
<ListItemButton onClick={() => setMapsExpanded(!mapsExpanded)}>
<ListItemIcon>
<FieldsIcon />
</ListItemIcon>
<ListItemText primary={"Map Settings"} />
{mapsExpanded ? <ExpandLess /> : <ExpandMore />}
</ListItem>
</ListItemButton>
<Collapse in={mapsExpanded} timeout="auto" unmountOnExit>
<List component="div">
<ListItem>
@ -648,7 +675,7 @@ export default function UserSettings(props: Props) {
</ListItemText>
</ListItem>
</List>
</Collapse> */}
</Collapse>
</List>
);
};

View file

@ -62,14 +62,14 @@ const agFeatureList: ProductDetails[] = [
// bulletPoints: ["bullet one", "bullet two"],
// questions: [],
},
{
featureName: "libra-cart",
featureLogo: <LibraCartIcon />,
featureCost: 0,
cardImage: FeatureImageTest,
featureTitle: "Libra Cart",
longDescription: "Integrate with Libra Cart to bring your data into our platform"
}
// {
// featureName: "libra-cart",
// featureLogo: <LibraCartIcon />,
// featureCost: 0,
// cardImage: FeatureImageTest,
// featureTitle: "Libra Cart",
// longDescription: "Integrate with Libra Cart to bring your data into our platform"
// }
];
// const constructionFeatureList: ProductDetails[] = []