Merge branch 'dev_environment' into staging_environment

This commit is contained in:
csawatzky 2025-05-08 15:19:54 -06:00
commit 1c41157281
11 changed files with 486 additions and 364 deletions

View file

@ -419,16 +419,12 @@ export default function BinCard(props: Props) {
Math.round(current * 35.239).toLocaleString() + Math.round(current * 35.239).toLocaleString() +
"/" + "/" +
Math.round(capacity * 35.239).toLocaleString() + Math.round(capacity * 35.239).toLocaleString() +
(lidarBushels ? "(" + Math.round(lidarBushels * 35.239).toLocaleString() + " est)" : "") +
" L" " L"
); );
} }
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && bin.bushelsPerTonne() > 1) { if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && bin.bushelsPerTonne() > 1) {
return ( return (
bin.grainTonnes().toLocaleString() + bin.grainTonnes().toLocaleString() +
(lidarBushels
? "(" + Math.round(lidarBushels / bin.bushelsPerTonne()).toLocaleString() + " est)"
: "") +
" mT " + " mT " +
bin.fillPercent() + bin.fillPercent() +
"%" "%"
@ -438,7 +434,6 @@ export default function BinCard(props: Props) {
current.toLocaleString() + current.toLocaleString() +
"/" + "/" +
capacity.toLocaleString() + capacity.toLocaleString() +
(lidarBushels ? "(" + lidarBushels.toLocaleString() + " est)" : "") +
" bu" " bu"
); );
}; };
@ -469,10 +464,10 @@ export default function BinCard(props: Props) {
const info = () => { const info = () => {
const inv = bin.settings.inventory; const inv = bin.settings.inventory;
let bushelAmount = inv?.grainBushels; let bushelAmount = bin.bushels();
let bushelCapacity = bin.settings.specs?.bushelCapacity; let bushelCapacity = bin.settings.specs?.bushelCapacity;
const empty = const empty =
!inv || inv.empty || !bushelCapacity || bushelCapacity <= 0 || inv.grainBushels <= 0; !inv || inv.empty || !bushelCapacity || bushelCapacity <= 0 || bushelAmount <= 0;
return ( return (
<Box width={1} marginTop={0}> <Box width={1} marginTop={0}>
<Typography align="center" color="textSecondary" noWrap style={{ fontSize: "0.65rem" }}> <Typography align="center" color="textSecondary" noWrap style={{ fontSize: "0.65rem" }}>

View file

@ -362,95 +362,95 @@ export default function BinVisualizer(props: Props) {
let trendCables = cables; let trendCables = cables;
if (bin.key() !== "" && !loadingTrend) { if (bin.key() !== "" && !loadingTrend) {
setLoadingTrend(true); setLoadingTrend(true);
// binAPI binAPI
// .getBinsTrendData([bin.key()], 7) .getBinsTrendData([bin.key()], 7)
// .then(resp => { .then(resp => {
// let binTrend = resp.data.trendData[bin.key()]; let binTrend = resp.data.trendData[bin.key()];
// //the variables to use to build the average conditions //the variables to use to build the average conditions
// let temps: number[] = []; let temps: number[] = [];
// let humids: number[] = []; let humids: number[] = [];
// let emcs: number[] = []; let emcs: number[] = [];
// let tempTrends: number[] = []; let tempTrends: number[] = [];
// let humidTrends: number[] = []; let humidTrends: number[] = [];
// let emcTrends: number[] = []; let emcTrends: number[] = [];
// let lowNodeConditions: GrainConditions | undefined = undefined; let lowNodeConditions: GrainConditions | undefined = undefined;
// let highNodeConditions: GrainConditions | undefined = undefined; let highNodeConditions: GrainConditions | undefined = undefined;
// let avgConditions: GrainConditions | undefined = undefined; let avgConditions: GrainConditions | undefined = undefined;
// trendCables.forEach(cable => { trendCables.forEach(cable => {
// //only use the values below the top node //only use the values below the top node
// //because splice mutates the original array make a clone in the cables so the svg still has all of them for display //because splice mutates the original array make a clone in the cables so the svg still has all of them for display
// //also reverse it so that the node 1 (end of the cable) is in the first position //also reverse it so that the node 1 (end of the cable) is in the first position
// let tempClone = cloneDeep(cable.temperatures).reverse(); let tempClone = cloneDeep(cable.temperatures).reverse();
// let humClone = cloneDeep(cable.humidities).reverse(); let humClone = cloneDeep(cable.humidities).reverse();
// let emcClone = cloneDeep(cable.grainMoistures).reverse(); let emcClone = cloneDeep(cable.grainMoistures).reverse();
// //add the cable data to the proper arrays //add the cable data to the proper arrays
// if (cable.topNode > 0) { if (cable.topNode > 0) {
// temps.push(...tempClone.splice(0, cable.topNode)); temps.push(...tempClone.splice(0, cable.topNode));
// humids.push(...humClone.splice(0, cable.topNode)); humids.push(...humClone.splice(0, cable.topNode));
// emcs.push(...emcClone.splice(0, cable.topNode)); emcs.push(...emcClone.splice(0, cable.topNode));
// } else { } else {
// //if the cable has no fill set (top node) then use all of the nodes //if the cable has no fill set (top node) then use all of the nodes
// temps = tempClone; temps = tempClone;
// humids = humClone; humids = humClone;
// emcs = emcClone; emcs = emcClone;
// } }
// //add the trend data to the proper arrays if the top node is set //add the trend data to the proper arrays if the top node is set
// let cableTrendData = binTrend.cableTrend[cable.key()]; let cableTrendData = binTrend.cableTrend[cable.key()];
// tempTrends.push(cableTrendData.grainCelciusTrend); tempTrends.push(cableTrendData.grainCelciusTrend);
// humidTrends.push(cableTrendData.grainHumidityTrend); humidTrends.push(cableTrendData.grainHumidityTrend);
// emcTrends.push(cableTrendData.grainEmcTrend); emcTrends.push(cableTrendData.grainEmcTrend);
// //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 //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 //use this cables lowest node and trend data in the condition
// if (!lowNodeConditions || Math.min(...temps) < lowNodeConditions.tempC) { if (!lowNodeConditions || Math.min(...temps) < lowNodeConditions.tempC) {
// //determine which node is the coldest so that the data displayed is for the same node //determine which node is the coldest so that the data displayed is for the same node
// let lowTempIndex = let lowTempIndex =
// temps.indexOf(Math.min(...temps)) === -1 ? 0 : temps.indexOf(Math.min(...temps)); temps.indexOf(Math.min(...temps)) === -1 ? 0 : temps.indexOf(Math.min(...temps));
// lowNodeConditions = { lowNodeConditions = {
// tempC: temps[lowTempIndex], tempC: temps[lowTempIndex],
// humidity: humids[lowTempIndex], humidity: humids[lowTempIndex],
// emc: emcs[lowTempIndex], emc: emcs[lowTempIndex],
// tempCTrend: cableTrendData.coldNodeTrend?.tempTrend ?? 0, tempCTrend: cableTrendData.coldNodeTrend?.tempTrend ?? 0,
// humidityTrend: cableTrendData.coldNodeTrend?.humidityTrend ?? 0, humidityTrend: cableTrendData.coldNodeTrend?.humidityTrend ?? 0,
// emcTrend: cableTrendData.coldNodeTrend?.emcTrend ?? 0 emcTrend: cableTrendData.coldNodeTrend?.emcTrend ?? 0
// }; };
// } }
// //do the same for the high node //do the same for the high node
// if (!highNodeConditions || cable.maxTemp() > highNodeConditions.tempC) { if (!highNodeConditions || cable.maxTemp() > highNodeConditions.tempC) {
// let highTempIndex = let highTempIndex =
// temps.indexOf(Math.max(...temps)) === -1 ? 0 : temps.indexOf(Math.max(...temps)); temps.indexOf(Math.max(...temps)) === -1 ? 0 : temps.indexOf(Math.max(...temps));
// highNodeConditions = { highNodeConditions = {
// tempC: temps[highTempIndex], tempC: temps[highTempIndex],
// humidity: humids[highTempIndex], humidity: humids[highTempIndex],
// emc: emcs[highTempIndex], emc: emcs[highTempIndex],
// tempCTrend: cableTrendData.hotNodeTrend?.tempTrend ?? 0, tempCTrend: cableTrendData.hotNodeTrend?.tempTrend ?? 0,
// humidityTrend: cableTrendData.hotNodeTrend?.humidityTrend ?? 0, humidityTrend: cableTrendData.hotNodeTrend?.humidityTrend ?? 0,
// emcTrend: cableTrendData.hotNodeTrend?.emcTrend ?? 0 emcTrend: cableTrendData.hotNodeTrend?.emcTrend ?? 0
// }; };
// } }
// }); });
// //set the average conditions //set the average conditions
// avgConditions = { avgConditions = {
// tempC: average(temps), tempC: average(temps),
// humidity: average(humids), humidity: average(humids),
// emc: average(emcs), emc: average(emcs),
// tempCTrend: average(tempTrends), tempCTrend: average(tempTrends),
// humidityTrend: average(humidTrends), humidityTrend: average(humidTrends),
// emcTrend: average(emcTrends) emcTrend: average(emcTrends)
// }; };
// setAverageConditions(avgConditions); setAverageConditions(avgConditions);
// setLowNodeConditions(lowNodeConditions); setLowNodeConditions(lowNodeConditions);
// setHighNodeConditions(highNodeConditions); setHighNodeConditions(highNodeConditions);
// }) })
// .catch(err => { .catch(err => {
// console.log(err); console.log(err);
// openSnack("Failed to retrieve trend data"); openSnack("Failed to retrieve trend data");
// }) })
// .finally(() => { .finally(() => {
// setLoadingTrend(false); setLoadingTrend(false);
// }); });
} }
} }
}, [binAPI, bin, cables, openSnack]); //eslint-disable-line react-hooks/exhaustive-deps }, [binAPI, bin, cables, openSnack]); //eslint-disable-line react-hooks/exhaustive-deps
@ -485,7 +485,7 @@ export default function BinVisualizer(props: Props) {
if (bin.settings.inventory && bin.settings.specs) { if (bin.settings.inventory && bin.settings.specs) {
const isEmpty = bin.settings.inventory?.empty === true; const isEmpty = bin.settings.inventory?.empty === true;
const capacity = bin.settings.specs.bushelCapacity; const capacity = bin.settings.specs.bushelCapacity;
const grainBushels = bin.settings.inventory.grainBushels; const grainBushels = bin.bushels();
setSliderCoulour("gold"); setSliderCoulour("gold");
if (!capacity) { if (!capacity) {
setFillPercentage(null); setFillPercentage(null);
@ -535,7 +535,7 @@ export default function BinVisualizer(props: Props) {
const inventoryOverview = () => { const inventoryOverview = () => {
const capacity = bin.settings.specs?.bushelCapacity ?? 0; const capacity = bin.settings.specs?.bushelCapacity ?? 0;
const grainBushels = bin.settings.inventory?.grainBushels ?? 0; const grainBushels = bin.bushels();
const isEmpty = bin.settings.inventory?.empty === true || !grainBushels || grainBushels <= 0; const isEmpty = bin.settings.inventory?.empty === true || !grainBushels || grainBushels <= 0;
const grainType = bin.settings.inventory?.grainType; const grainType = bin.settings.inventory?.grainType;
const grainTypeName = isEmpty || !grainType ? "" : GrainDescriber(grainType).name; const grainTypeName = isEmpty || !grainType ? "" : GrainDescriber(grainType).name;
@ -1481,9 +1481,7 @@ export default function BinVisualizer(props: Props) {
onChange={(_, value) => { onChange={(_, value) => {
setFillPercentage(value as number); setFillPercentage(value as number);
const capacity = bin.settings.specs ? bin.settings.specs.bushelCapacity : 0; const capacity = bin.settings.specs ? bin.settings.specs.bushelCapacity : 0;
const current = bin.settings.inventory const current = bin.bushels()
? bin.settings.inventory.grainBushels
: 0;
let grainAmount = ((value as number) / 100) * capacity; let grainAmount = ((value as number) / 100) * capacity;
if (grainAmount < current) { if (grainAmount < current) {

View file

@ -7,6 +7,7 @@ import {
DialogTitle, DialogTitle,
Divider, Divider,
Grid2, Grid2,
IconButton,
lighten, lighten,
List, List,
ListItem, ListItem,
@ -36,12 +37,23 @@ import { useGlobalState } from "providers";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import RemoveGroup from "./RemoveGroup"; import RemoveGroup from "./RemoveGroup";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { Add, Remove } from "@mui/icons-material";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
return ({ return ({
deviceListContainer: { deviceListContainer: {
marginBottom: theme.spacing(1) marginBottom: theme.spacing(1)
}, },
listTitle: {
fontSize: 20,
fontWeight: 650
},
listButton: {
cursor: "pointer",
"&:hover": {
backgroundColor: lighten(theme.palette.background.default, 0.25)
}
},
devicesList: { devicesList: {
overflow: "auto", overflow: "auto",
minHeight: "25vh", minHeight: "25vh",
@ -87,6 +99,7 @@ export default function GroupSettings(props: Props) {
const deviceAPI = useDeviceAPI(); const deviceAPI = useDeviceAPI();
const snackbar = useSnackbar() const snackbar = useSnackbar()
const [devices, setDevices] = useState<Device[]>([]); const [devices, setDevices] = useState<Device[]>([]);
const [nonGroupDevices, setNonGroupDevices] = useState<Device[]>([]);
const [group, setGroup] = useState<Group>(initialGroup ? Group.clone(initialGroup) : new Group()); const [group, setGroup] = useState<Group>(initialGroup ? Group.clone(initialGroup) : new Group());
const [isRemoveGroupOpen, setIsRemoveGroupOpen] = useState<boolean>( const [isRemoveGroupOpen, setIsRemoveGroupOpen] = useState<boolean>(
mode === "remove" ? true : false mode === "remove" ? true : false
@ -109,13 +122,14 @@ export default function GroupSettings(props: Props) {
const loadDevices = useCallback(() => { const loadDevices = useCallback(() => {
setLoadingDevices(true); setLoadingDevices(true);
//this lists the devices for the user/team viewing
deviceAPI deviceAPI
.list(1000000, 0, "asc") .list(1000000, 0, "asc")
.then((response: any) => { .then((response: any) => {
let rDevices: Device[] = response.data.devices let rDevices: Device[] = response.data.devices
? response.data.devices.map((device: any) => Device.any(device)) ? response.data.devices.map((device: any) => Device.any(device))
: []; : [];
//remove duplicates from the options
setDevices(rDevices); setDevices(rDevices);
}) })
.catch((_error: any) => { .catch((_error: any) => {
@ -124,6 +138,16 @@ export default function GroupSettings(props: Props) {
.finally(() => setLoadingDevices(false)); .finally(() => setLoadingDevices(false));
}, [deviceAPI, as]); }, [deviceAPI, as]);
useEffect(()=>{
let ungrouped: Device[] = []
devices.forEach(device => {
if(!groupDeviceNumbers.includes(device.id())){
ungrouped.push(device)
}
})
setNonGroupDevices(ungrouped)
},[groupDeviceNumbers, devices])
useEffect(() => { useEffect(() => {
if (prevInitialGroup !== initialGroup) { if (prevInitialGroup !== initialGroup) {
setGroup(initialGroup ? Group.clone(initialGroup) : new Group()); setGroup(initialGroup ? Group.clone(initialGroup) : new Group());
@ -317,27 +341,58 @@ export default function GroupSettings(props: Props) {
</ListItem> </ListItem>
</List> </List>
) : ( ) : (
<List dense className={classes.devicesList}> <React.Fragment>
{filterDevices(deviceSearch, devices, []) {groupDevices &&
.sort((a, b: Device) => a.name().localeCompare(b.name())) <List dense className={classes.devicesList}>
.map(device => { {filterDevices(deviceSearch, groupDevices, [])
const label = `checkbox-list-secondary-label-${device.id()}`; .sort((a, b: Device) => a.name().localeCompare(b.name()))
return ( .map(device => {
<ListItem key={device.id()} onClick={() => changeDevices(device.id())}> const label = `checkbox-list-secondary-label-${device.id()}`;
<ListItemText id={label} primary={device.name()} /> return (
<ListItemSecondaryAction> <ListItem className={classes.listButton} key={device.id()} onClick={() => {
<Checkbox removeDevice(device.id())
edge="end" }}>
onChange={() => changeDevices(device.id())} <ListItemText id={label} primary={device.name()} />
checked={group.settings.devices.includes(device.id())} <ListItemSecondaryAction>
inputProps={{ "aria-labelledby": label }} {/* <Checkbox
disabled={!canEdit} edge="end"
/> onChange={() => changeDevices(device.id())}
</ListItemSecondaryAction> checked={group.settings.devices.includes(device.id())}
</ListItem> inputProps={{ "aria-labelledby": label }}
); disabled={!canEdit}
})} /> */}
</List> -
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
}
<List dense className={classes.devicesList}>
{filterDevices(deviceSearch, nonGroupDevices, [])
.sort((a, b: Device) => a.name().localeCompare(b.name()))
.map(device => {
const label = `checkbox-list-secondary-label-${device.id()}`;
return (
<ListItem className={classes.listButton} key={device.id()} onClick={() => {
addDevice(device.id())
}}>
<ListItemText id={label} primary={device.name()} />
<ListItemSecondaryAction>
{/* <Checkbox
edge="end"
onChange={() => changeDevices(device.id())}
checked={group.settings.devices.includes(device.id())}
inputProps={{ "aria-labelledby": label }}
disabled={!canEdit}
/> */}
+
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</React.Fragment>
)} )}
</Paper> </Paper>
); );
@ -383,16 +438,49 @@ export default function GroupSettings(props: Props) {
</ListItem> </ListItem>
</List> </List>
) : ( ) : (
<React.Fragment>
{groupDevices &&
<React.Fragment>
<Typography className={classes.listTitle}>Grouped Devices</Typography>
<List dense className={classes.devicesList}>
{filterDevices(deviceSearch, groupDevices, [])
.sort((a, b: Device) => a.name().localeCompare(b.name()))
.map(device => {
const label = `checkbox-list-secondary-label-${device.id()}`;
return (
<ListItem className={classes.listButton} key={device.id()} onClick={() => {
removeDevice(device.id())
}}>
<ListItemText id={label} primary={device.name()} />
<ListItemSecondaryAction>
{/* <Checkbox
edge="end"
onChange={() => changeDevices(device.id())}
checked={group.settings.devices.includes(device.id())}
inputProps={{ "aria-labelledby": label }}
disabled={!canEdit}
/> */}
<Remove />
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</React.Fragment>
}
<Typography className={classes.listTitle}>Devices You Can Add</Typography>
<List dense className={classes.devicesList}> <List dense className={classes.devicesList}>
{filterDevices(deviceSearch, devices, []) {filterDevices(deviceSearch, nonGroupDevices, [])
.sort((a, b: Device) => a.name().localeCompare(b.name())) .sort((a, b: Device) => a.name().localeCompare(b.name()))
.map(device => { .map(device => {
const label = `checkbox-list-secondary-label-${device.id()}`; const label = `checkbox-list-secondary-label-${device.id()}`;
return ( return (
<ListItem key={device.id()} onClick={() => changeDevices(device.id())}> <ListItem className={classes.listButton} key={device.id()} onClick={() => {
addDevice(device.id())
}}>
<ListItemText id={label} primary={device.name()} /> <ListItemText id={label} primary={device.name()} />
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Checkbox {/* <Checkbox
edge="end" edge="end"
onChange={(_, checked) => { onChange={(_, checked) => {
if (checked) addDevice(device.id()); if (checked) addDevice(device.id());
@ -402,12 +490,14 @@ export default function GroupSettings(props: Props) {
checked={groupDeviceNumbers.includes(device.id())} checked={groupDeviceNumbers.includes(device.id())}
inputProps={{ "aria-labelledby": label }} inputProps={{ "aria-labelledby": label }}
disabled={!canEdit} disabled={!canEdit}
/> /> */}
<Add />
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>
); );
})} })}
</List> </List>
</React.Fragment>
)} )}
</Paper> </Paper>
); );

View file

@ -185,6 +185,8 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
const [measurement, setMeasurement] = useState(false); const [measurement, setMeasurement] = useState(false);
const [measurementData, setMeasurementData] = useState<Map<string, MeasurementData>>(new Map()); const [measurementData, setMeasurementData] = useState<Map<string, MeasurementData>>(new Map());
const measurementRef = useRef(measurementData); const measurementRef = useRef(measurementData);
const zoomRef = useRef(15)
const fieldMarkerZoom = 15
//watches for changes to the viewing as and sets the load boolean to trigger a load //watches for changes to the viewing as and sets the load boolean to trigger a load
useEffect(() => { useEffect(() => {
@ -591,12 +593,13 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
} }
let newFM = fieldMarkers.get(newKey); let newFM = fieldMarkers.get(newKey);
if (newFM) { if (newFM) {
setCurrentView({ let view = {
latitude: newFM.lat(), latitude: newFM.lat(),
longitude: newFM.long(), longitude: newFM.long(),
zoom: 12, zoom: currentView.zoom,
transitionDuration: 1000 transitionDuration: 1000
}); }
setCurrentView(view);
} }
setObjectKey(newKey); setObjectKey(newKey);
setFMIndexKey(newID); setFMIndexKey(newID);
@ -621,12 +624,13 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
} }
let newFM = fieldMarkers.get(newKey); let newFM = fieldMarkers.get(newKey);
if (newFM) { if (newFM) {
setCurrentView({ let view = {
latitude: newFM.lat(), latitude: newFM.lat(),
longitude: newFM.long(), longitude: newFM.long(),
zoom: 12, zoom: currentView.zoom,
transitionDuration: 1000 transitionDuration: 1000
}); }
setCurrentView(view);
} }
setObjectKey(newKey); setObjectKey(newKey);
setFMIndexKey(newID); setFMIndexKey(newID);
@ -728,13 +732,14 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
subtype: fm.type(), subtype: fm.type(),
mini: true, mini: true,
clickFunc: (event, index) => { clickFunc: (event, index) => {
console.log(zoomRef)
clickDelay(); clickDelay();
closeDrawers(); closeDrawers();
setIgnoreFeatures(true); setIgnoreFeatures(true);
setCurrentView({ setCurrentView({
latitude: fm.lat(), latitude: fm.lat(),
longitude: fm.long(), longitude: fm.long(),
zoom: 12, zoom: zoomRef.current,
transitionDuration: 0 transitionDuration: 0
//transitionInterpolator: new FlyToInterpolator() //transitionInterpolator: new FlyToInterpolator()
}); });
@ -1115,14 +1120,16 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
let xOff = !mobileOffset ? -250 : undefined; let xOff = !mobileOffset ? -250 : undefined;
let yOff = mobileOffset ? -150 : undefined; let yOff = mobileOffset ? -150 : undefined;
setCurrentView({ let view = {
latitude: lat, latitude: lat,
longitude: long, longitude: long,
zoom: zoom, zoom: zoom,
transitionDuration: transDuration, transitionDuration: transDuration,
xOffset: center ? 0 : xOff, xOffset: center ? 0 : xOff,
yOffset: center ? 0 : yOff yOffset: center ? 0 : yOff
}); }
setCurrentView(view);
zoomRef.current = zoom
}; };
const markerDialog = () => { const markerDialog = () => {

View file

@ -125,11 +125,23 @@ export class Bin {
return colour; return colour;
} }
public bushels(): number {
let bushels = this.settings.inventory?.grainBushels || 0
if (this.settings.inventory?.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR){
bushels = this.status.grainBushels
}
return bushels
}
public fillPercent(): number { public fillPercent(): number {
let fill = 0; let fill = 0;
if (this.settings.inventory && this.settings.specs) { if (this.settings.inventory && this.settings.specs) {
let bushels = this.settings.inventory.grainBushels
if (this.settings.inventory.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR){
bushels = this.status.grainBushels
}
fill = Math.round( fill = Math.round(
(this.settings.inventory.grainBushels / this.settings.specs.bushelCapacity) * 100 (bushels / this.settings.specs.bushelCapacity) * 100
); );
} }
return fill; return fill;

View file

@ -96,6 +96,7 @@ export default function DevicePage() {
} }
} }
}); });
setDiagnosticComponents(diagComponents)
setComponents(rComponents) setComponents(rComponents)
let interactions: Interaction[] = []; let interactions: Interaction[] = [];
resp.data.interactions.forEach((interaction: pond.Interaction) => { resp.data.interactions.forEach((interaction: pond.Interaction) => {

View file

@ -1,7 +1,7 @@
import { import {
GraphComponent, // GraphComponent,
GraphOrientation, // GraphOrientation,
GraphPoint, // GraphPoint,
GraphType, GraphType,
InteractionLine InteractionLine
} from "common/Graph"; } from "common/Graph";
@ -41,15 +41,15 @@ import {
Weight, Weight,
CapacitorCable CapacitorCable
} from "pbHelpers/ComponentTypes"; } from "pbHelpers/ComponentTypes";
import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable"; //import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable";
import { multilineCapCableData } from "./ComponentTypes/CapacitorCable"; //import { multilineCapCableData } from "./ComponentTypes/CapacitorCable";
import { findInteractionsAsSource } from "pbHelpers/Interaction"; import { findInteractionsAsSource } from "pbHelpers/Interaction";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack"; import { quack } from "protobuf-ts/quack";
import { notNull, or } from "utils/types"; import { notNull, or } from "utils/types";
import { emptyComponentId } from "./Component"; //import { emptyComponentId } from "./Component";
import { multilinePressureCableData } from "./ComponentTypes/PressureCable"; //import { multilinePressureCableData } from "./ComponentTypes/PressureCable";
import { describeMeasurement, MeasurementDescriber } from "./MeasurementDescriber"; import { describeMeasurement, MeasurementDescriber } from "./MeasurementDescriber";
import { convertedUnitMeasurement, MeasurementsFor, UnitMeasurement } from "models/UnitMeasurement"; import { convertedUnitMeasurement, MeasurementsFor, UnitMeasurement } from "models/UnitMeasurement";
import { Sen5x } from "./ComponentTypes/Sen5x"; import { Sen5x } from "./ComponentTypes/Sen5x";
@ -622,194 +622,194 @@ export function GetComponentIcon(
} }
//TODO: deprecated function, new graphs are handled in measurementChart (using reCharts instead of victory) //TODO: deprecated function, new graphs are handled in measurementChart (using reCharts instead of victory)
export function getGraphProps( // export function getGraphProps(
componentType: quack.ComponentType, // componentType: quack.ComponentType,
subtype: number, // subtype: number,
measurements: pond.Measurement[], // measurements: pond.Measurement[],
interactions: Interaction[], // interactions: Interaction[],
overlays: pond.ComponentOverlays[], // overlays: pond.ComponentOverlays[],
orientation: GraphOrientation, // orientation: GraphOrientation,
filters?: GraphFilters // filters?: GraphFilters
): any { // ): any {
const componentID: quack.IComponentID = quack.ComponentID.fromObject( // const componentID: quack.IComponentID = quack.ComponentID.fromObject(
or(measurements[0].measurement, { // or(measurements[0].measurement, {
id: emptyComponentId() // id: emptyComponentId()
}).id as any // }).id as any
); // );
let graphProps = { // let graphProps = {
orientation: orientation // orientation: orientation
} as any; // } as any;
let ext = extension(componentType, subtype); // let ext = extension(componentType, subtype);
ext.measurements.forEach((componentMeasurement: ComponentMeasurement, index: number) => { // ext.measurements.forEach((componentMeasurement: ComponentMeasurement, index: number) => {
let describer = describeMeasurement( // let describer = describeMeasurement(
componentMeasurement.measurementType, // componentMeasurement.measurementType,
componentType, // componentType,
subtype // subtype
); // );
let range = // let range =
filters && filters.ranges && filters.ranges[index] ? filters.ranges[index] : undefined; // filters && filters.ranges && filters.ranges[index] ? filters.ranges[index] : undefined;
let selectedInteractions = // let selectedInteractions =
filters && filters.selectedInteractions ? filters.selectedInteractions : []; // filters && filters.selectedInteractions ? filters.selectedInteractions : [];
let interactionLines = getInteractionLines( // let interactionLines = getInteractionLines(
componentType, // componentType,
subtype, // subtype,
componentMeasurement.measurementType, // componentMeasurement.measurementType,
componentID, // componentID,
interactions, // interactions,
selectedInteractions // selectedInteractions
); // );
let selectedOverlays = filters?.selectedOverlays ?? []; // let selectedOverlays = filters?.selectedOverlays ?? [];
let overlayAreas = getOverlayAreas( // let overlayAreas = getOverlayAreas(
componentMeasurement.measurementType, // componentMeasurement.measurementType,
overlays, // overlays,
selectedOverlays // selectedOverlays
); // );
graphProps["graphComponent" + (index + 1).toString()] = { // graphProps["graphComponent" + (index + 1).toString()] = {
label: componentMeasurement.label, // label: componentMeasurement.label,
data: [], // data: [],
tick: describer.unit(), // tick: describer.unit(),
colour: componentMeasurement.colour, // colour: componentMeasurement.colour,
type: componentMeasurement.graphType, // type: componentMeasurement.graphType,
range: range, // range: range,
isBoolean: // isBoolean:
componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN, // componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN,
isPercent: // isPercent:
componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT || // componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT ||
(componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_ANALOG && // (componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_ANALOG &&
subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL), // subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL),
interactionLines: interactionLines, // interactionLines: interactionLines,
states: ext.states, // states: ext.states,
decimals: describer.decimals(), // decimals: describer.decimals(),
overlays: overlayAreas // overlays: overlayAreas
} as GraphComponent; // } as GraphComponent;
}); // });
//for new cables add here to get the data for multiline // //for new cables add here to get the data for multiline
if (showMultilineCable(componentType, filters)) { // if (showMultilineCable(componentType, filters)) {
let cableData: any = {}; // let cableData: any = {};
if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) { // if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
cableData = multilineGrainCableData(measurements, filters); // cableData = multilineGrainCableData(measurements, filters);
} else if (componentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE) { // } else if (componentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE) {
cableData = multilinePressureCableData(measurements, filters); // cableData = multilinePressureCableData(measurements, filters);
} else if (componentType === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE) { // } else if (componentType === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE) {
cableData = multilineCapCableData(measurements, filters); // cableData = multilineCapCableData(measurements, filters);
} // }
//will loop through the keys in cableData // //will loop through the keys in cableData
//IMPORTANT keys must be data1, data2, etc in the multiline function in the component // //IMPORTANT keys must be data1, data2, etc in the multiline function in the component
for (let i = 1; i <= Object.keys(cableData).length; i++) { // for (let i = 1; i <= Object.keys(cableData).length; i++) {
let gc = "graphComponent" + i; // let gc = "graphComponent" + i;
if (graphProps[gc]) { // if (graphProps[gc]) {
graphProps[gc].data = cableData["data" + i]; // graphProps[gc].data = cableData["data" + i];
graphProps[gc].type = GraphType.MULTILINE; // graphProps[gc].type = GraphType.MULTILINE;
} // }
} // }
} else { // } else {
measurements.forEach((reading: pond.Measurement, readIndex) => { // measurements.forEach((reading: pond.Measurement, readIndex) => {
if (notNull(reading) && notNull(reading.timestamp) && notNull(reading.measurement)) { // if (notNull(reading) && notNull(reading.timestamp) && notNull(reading.measurement)) {
let date = new Date(Date.parse(reading.timestamp)); // let date = new Date(Date.parse(reading.timestamp));
for (let index = 0; index < ext.measurements.length; index++) { // for (let index = 0; index < ext.measurements.length; index++) {
let componentMeasurement: ComponentMeasurement = ext.measurements[index]; // let componentMeasurement: ComponentMeasurement = ext.measurements[index];
if (componentMeasurement.isErrorMeasurement(reading.measurement)) { // if (componentMeasurement.isErrorMeasurement(reading.measurement)) {
break; // break;
} // }
let dataPoint = null; // let dataPoint = null;
switch (componentMeasurement.graphType) { // switch (componentMeasurement.graphType) {
case GraphType.AREA: // case GraphType.AREA:
let avg = componentMeasurement.extract( // let avg = componentMeasurement.extract(
or(reading.measurement, {} as quack.Measurement), // or(reading.measurement, {} as quack.Measurement),
filters // filters
); // );
if (avg && avg.low !== null && avg.high !== null) { // if (avg && avg.low !== null && avg.high !== null) {
dataPoint = { // dataPoint = {
x: date, // x: date,
y: avg.low, // y: avg.low,
y0: avg.high // y0: avg.high
} as GraphPoint; // } as GraphPoint;
} // }
break; // break;
case GraphType.SCATTER: // case GraphType.SCATTER:
let scatter = componentMeasurement.extract( // let scatter = componentMeasurement.extract(
or(reading.measurement, {} as quack.Measurement), // or(reading.measurement, {} as quack.Measurement),
filters // filters
); // );
if (scatter && scatter.value !== undefined && scatter.value !== null) { // if (scatter && scatter.value !== undefined && scatter.value !== null) {
dataPoint = { // dataPoint = {
x: date, // x: date,
y: Number(scatter.value), // y: Number(scatter.value),
bubble: or(scatter.bubble, 0) // bubble: or(scatter.bubble, 0)
} as GraphPoint; // } as GraphPoint;
} // }
break; // break;
case GraphType.RADAR: // case GraphType.RADAR:
if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_WIND_DIRECTION) { // if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_WIND_DIRECTION) {
let value = componentMeasurement.extract( // let value = componentMeasurement.extract(
or(reading.measurement, {} as quack.Measurement), // or(reading.measurement, {} as quack.Measurement),
filters // filters
); // );
dataPoint = { // dataPoint = {
x: getWindDirection(value).dString, // x: getWindDirection(value).dString,
y: getWindDirection(value).direction // y: getWindDirection(value).direction
}; // };
} // }
break; // break;
default: // default:
//linear and bar // //linear and bar
let value = componentMeasurement.extract( // let value = componentMeasurement.extract(
or(reading.measurement, {} as quack.Measurement), // or(reading.measurement, {} as quack.Measurement),
filters // filters
); // );
if (value !== undefined && value !== null) { // if (value !== undefined && value !== null) {
if (componentType === quack.ComponentType.COMPONENT_TYPE_ANALOG_INPUT) { // if (componentType === quack.ComponentType.COMPONENT_TYPE_ANALOG_INPUT) {
if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL) { // if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL) {
if (value > 100) { // if (value > 100) {
value = 0; // value = 0;
} // }
} // }
} // }
if (componentType === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) { // if (componentType === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) {
if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_WIND_SPEED) { // if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_WIND_SPEED) {
if (readIndex < measurements.length - 1) { // if (readIndex < measurements.length - 1) {
//do math // //do math
//get the time of both the current reading and the reading before it // //get the time of both the current reading and the reading before it
let currentTime = new Date( // let currentTime = new Date(
Date.parse(measurements[readIndex].timestamp) // Date.parse(measurements[readIndex].timestamp)
).valueOf(); // ).valueOf();
let prevTime = new Date( // let prevTime = new Date(
Date.parse(measurements[readIndex + 1].timestamp) // Date.parse(measurements[readIndex + 1].timestamp)
).valueOf(); // ).valueOf();
//get the difference in seconds between the two // //get the difference in seconds between the two
let deltaTime = (currentTime - prevTime) / 1000; // let deltaTime = (currentTime - prevTime) / 1000;
//divide the ticks by the time between intervals for the average ticks per second and multiply // //divide the ticks by the time between intervals for the average ticks per second and multiply
//by 2.4 to convert to km/h // //by 2.4 to convert to km/h
value = (Number(value) / deltaTime) * 2.4; // value = (Number(value) / deltaTime) * 2.4;
} else { // } else {
value = 0; // value = 0;
} // }
} // }
if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_RAIN) { // if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_RAIN) {
value = Math.round(value * 0.2794); // value = Math.round(value * 0.2794);
} // }
} // }
dataPoint = { // dataPoint = {
x: date, // x: date,
y: Number(value) // y: Number(value)
} as GraphPoint; // } as GraphPoint;
} // }
break; // break;
} // }
if (dataPoint !== null) { // if (dataPoint !== null) {
graphProps["graphComponent" + (index + 1).toString()].data.push(dataPoint); // graphProps["graphComponent" + (index + 1).toString()].data.push(dataPoint);
} // }
} // }
} // }
}); // });
} // }
return graphProps; // return graphProps;
} // }
function getWindDirection(value: number) { function getWindDirection(value: number) {
//determine the general direction based on the value //determine the general direction based on the value

View file

@ -4,8 +4,8 @@ import GrainCableLightIcon from "assets/components/grainCableLight.png";
import TemperatureHumidityDarkIcon from "assets/components/temperatureHumidityDark.png"; import TemperatureHumidityDarkIcon from "assets/components/temperatureHumidityDark.png";
import TemperatureHumidityLightIcon from "assets/components/temperatureHumidityLight.png"; import TemperatureHumidityLightIcon from "assets/components/temperatureHumidityLight.png";
import { ExtractMoisture, grainName } from "grain"; import { ExtractMoisture, grainName } from "grain";
import { GraphPoint } from "common/Graph"; //import { GraphPoint } from "common/Graph";
import moment from "moment"; //import moment from "moment";
import { import {
AreaChartData, AreaChartData,
ComponentMeasurement, ComponentMeasurement,
@ -418,38 +418,38 @@ export function binSplitAt(filters?: GraphFilters): number {
} }
//TODO: deprecated function with new measurements //TODO: deprecated function with new measurements
export function multilineGrainCableData( // export function multilineGrainCableData(
measurements: Array<pond.Measurement>, // measurements: Array<pond.Measurement>,
filters?: GraphFilters // filters?: GraphFilters
) { // ) {
let temperature: Array<Array<GraphPoint>> = []; // let temperature: Array<Array<GraphPoint>> = [];
let humidity: Array<Array<GraphPoint>> = []; //humidity is not being used by the graphs // let humidity: Array<Array<GraphPoint>> = []; //humidity is not being used by the graphs
let moisture: Array<Array<GraphPoint>> = []; // let moisture: Array<Array<GraphPoint>> = [];
measurements.forEach((measurement: pond.Measurement, i) => { // measurements.forEach((measurement: pond.Measurement, i) => {
let nodes = extractNodes(measurement.measurement); // let nodes = extractNodes(measurement.measurement);
let selectedNodes = filters ? or(filters.selectedNodes, []) : []; // let selectedNodes = filters ? or(filters.selectedNodes, []) : [];
for (let j = 0; j < selectedNodes.length; j++) { // for (let j = 0; j < selectedNodes.length; j++) {
if (i === 0) { // if (i === 0) {
temperature[j] = []; // temperature[j] = [];
humidity[j] = []; // humidity[j] = [];
moisture[j] = []; // moisture[j] = [];
} // }
let nodeIndex = selectedNodes[j]; // let nodeIndex = selectedNodes[j];
if (nodeIndex < nodes.length) { // if (nodeIndex < nodes.length) {
let node = nodes[nodeIndex]; // let node = nodes[nodeIndex];
let ts = moment(measurement.timestamp); // let ts = moment(measurement.timestamp);
temperature[j].push({ x: ts, y: node.temperature }); // temperature[j].push({ x: ts, y: node.temperature });
humidity[j].push({ x: ts, y: node.humidity }); // humidity[j].push({ x: ts, y: node.humidity });
let grain = // let grain =
isBinSplit(filters) && nodeIndex >= binSplitAt(filters) // isBinSplit(filters) && nodeIndex >= binSplitAt(filters)
? pond.Grain.GRAIN_NONE // ? pond.Grain.GRAIN_NONE
: or(filters, { grainType: pond.Grain.GRAIN_NONE }).grainType; // : or(filters, { grainType: pond.Grain.GRAIN_NONE }).grainType;
moisture[j].push({ // moisture[j].push({
x: ts, // x: ts,
y: ExtractMoisture(grain, node.temperature, node.humidity) // y: ExtractMoisture(grain, node.temperature, node.humidity)
}); // });
} // }
} // }
}); // });
return { data1: temperature, data2: moisture, data3: humidity }; // return { data1: temperature, data2: moisture, data3: humidity };
} // }

View file

@ -110,6 +110,10 @@ export class MeasurementDescriber {
} }
if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) { if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
this.details.graph = GraphType.AREA; this.details.graph = GraphType.AREA;
if(componentSubtype === quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_DIAG){
this.details.label = "Cable ID"
this.details.unit = ""
}
} }
if (componentType === quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT) { if (componentType === quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT) {
this.details.path = "vpd.celciusTimes10"; this.details.path = "vpd.celciusTimes10";
@ -194,6 +198,10 @@ export class MeasurementDescriber {
if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) { if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
this.details.label = "Humidity"; this.details.label = "Humidity";
this.details.graph = GraphType.AREA; this.details.graph = GraphType.AREA;
if(componentSubtype === quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_DIAG){
this.details.label = "Cable Type"
this.details.unit = ""
}
// can be used for testing node splitting on a cable if you dont have a component that uses it // can be used for testing node splitting on a cable if you dont have a component that uses it
// this.details.nodeDetails = { // this.details.nodeDetails = {
// colours: ["white", "black"], // colours: ["white", "black"],

View file

@ -126,6 +126,10 @@ export interface IBinAPIContext {
showErrors?: boolean, showErrors?: boolean,
otherTeam?: string otherTeam?: string
) => Promise<AxiosResponse<pond.ListObjectMeasurementsResponse>>; ) => Promise<AxiosResponse<pond.ListObjectMeasurementsResponse>>;
getBinsTrendData: (
bins: string[],
days: number
) => Promise<AxiosResponse<pond.GetBinsTrendDataResponse>>;
} }
export const BinAPIContext = createContext<IBinAPIContext>({} as IBinAPIContext); export const BinAPIContext = createContext<IBinAPIContext>({} as IBinAPIContext);
@ -629,6 +633,12 @@ export default function BinProvider(props: PropsWithChildren<Props>) {
}) })
}; };
const getBinsTrendData = (bins: string[], days: number) => {
return get<pond.GetBinsTrendDataResponse>(
pondURL("/binTrends?bins=" + bins.toString() + "&days=" + days + (as ? "&as=" + as : ""))
);
};
return ( return (
<BinAPIContext.Provider <BinAPIContext.Provider
value={{ value={{
@ -655,7 +665,8 @@ export default function BinProvider(props: PropsWithChildren<Props>) {
listHistoryBetween, listHistoryBetween,
listBinPrefs, listBinPrefs,
updateTopNodes, updateTopNodes,
listBinMeasurements listBinMeasurements,
getBinsTrendData
}}> }}>
{children} {children}
</BinAPIContext.Provider> </BinAPIContext.Provider>

View file

@ -88,7 +88,7 @@ export default function GroupProvider(props: PropsWithChildren<Props>) {
const addDevice = (group: number, device: number) => { const addDevice = (group: number, device: number) => {
let url = "/groups/" + group + "/devices/" + device + "/add"; let url = "/groups/" + group + "/devices/" + device + "/add";
if (as) url = pondURL(url + as) if (as) url = url + "?as=" + as
return new Promise<AxiosResponse>((resolve, reject) => { return new Promise<AxiosResponse>((resolve, reject) => {
post(pondURL(url), group).then(resp => { post(pondURL(url), group).then(resp => {
return resolve(resp) return resolve(resp)
@ -100,7 +100,7 @@ export default function GroupProvider(props: PropsWithChildren<Props>) {
const removeDevice = (group: number, device: number) => { const removeDevice = (group: number, device: number) => {
let url = "/groups/" + group + "/devices/" + device + "/remove"; let url = "/groups/" + group + "/devices/" + device + "/remove";
if (as) url = pondURL(url + as) if (as) url = url + "?as=" + as
return new Promise<AxiosResponse>((resolve, reject) => { return new Promise<AxiosResponse>((resolve, reject) => {
post(pondURL(url), group).then(resp => { post(pondURL(url), group).then(resp => {
return resolve(resp) return resolve(resp)