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(capacity * 35.239).toLocaleString() +
(lidarBushels ? "(" + Math.round(lidarBushels * 35.239).toLocaleString() + " est)" : "") +
" L"
);
}
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && bin.bushelsPerTonne() > 1) {
return (
bin.grainTonnes().toLocaleString() +
(lidarBushels
? "(" + Math.round(lidarBushels / bin.bushelsPerTonne()).toLocaleString() + " est)"
: "") +
" mT " +
bin.fillPercent() +
"%"
@ -438,7 +434,6 @@ export default function BinCard(props: Props) {
current.toLocaleString() +
"/" +
capacity.toLocaleString() +
(lidarBushels ? "(" + lidarBushels.toLocaleString() + " est)" : "") +
" bu"
);
};
@ -469,10 +464,10 @@ export default function BinCard(props: Props) {
const info = () => {
const inv = bin.settings.inventory;
let bushelAmount = inv?.grainBushels;
let bushelAmount = bin.bushels();
let bushelCapacity = bin.settings.specs?.bushelCapacity;
const empty =
!inv || inv.empty || !bushelCapacity || bushelCapacity <= 0 || inv.grainBushels <= 0;
!inv || inv.empty || !bushelCapacity || bushelCapacity <= 0 || bushelAmount <= 0;
return (
<Box width={1} marginTop={0}>
<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;
if (bin.key() !== "" && !loadingTrend) {
setLoadingTrend(true);
// binAPI
// .getBinsTrendData([bin.key()], 7)
// .then(resp => {
// let binTrend = resp.data.trendData[bin.key()];
// //the variables to use to build the average conditions
// let temps: number[] = [];
// let humids: number[] = [];
// let emcs: number[] = [];
// let tempTrends: number[] = [];
// let humidTrends: number[] = [];
// let emcTrends: number[] = [];
binAPI
.getBinsTrendData([bin.key()], 7)
.then(resp => {
let binTrend = resp.data.trendData[bin.key()];
//the variables to use to build the average conditions
let temps: number[] = [];
let humids: number[] = [];
let emcs: number[] = [];
let tempTrends: number[] = [];
let humidTrends: number[] = [];
let emcTrends: number[] = [];
// let lowNodeConditions: GrainConditions | undefined = undefined;
// let highNodeConditions: GrainConditions | undefined = undefined;
// let avgConditions: GrainConditions | undefined = undefined;
// trendCables.forEach(cable => {
// //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
// //also reverse it so that the node 1 (end of the cable) is in the first position
// let tempClone = cloneDeep(cable.temperatures).reverse();
// let humClone = cloneDeep(cable.humidities).reverse();
// let emcClone = cloneDeep(cable.grainMoistures).reverse();
// //add the cable data to the proper arrays
// if (cable.topNode > 0) {
// temps.push(...tempClone.splice(0, cable.topNode));
// humids.push(...humClone.splice(0, cable.topNode));
// emcs.push(...emcClone.splice(0, cable.topNode));
// } else {
// //if the cable has no fill set (top node) then use all of the nodes
// temps = tempClone;
// humids = humClone;
// emcs = emcClone;
// }
// //add the trend data to the proper arrays if the top node is set
// let cableTrendData = binTrend.cableTrend[cable.key()];
// tempTrends.push(cableTrendData.grainCelciusTrend);
// humidTrends.push(cableTrendData.grainHumidityTrend);
// emcTrends.push(cableTrendData.grainEmcTrend);
let lowNodeConditions: GrainConditions | undefined = undefined;
let highNodeConditions: GrainConditions | undefined = undefined;
let avgConditions: GrainConditions | undefined = undefined;
trendCables.forEach(cable => {
//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
//also reverse it so that the node 1 (end of the cable) is in the first position
let tempClone = cloneDeep(cable.temperatures).reverse();
let humClone = cloneDeep(cable.humidities).reverse();
let emcClone = cloneDeep(cable.grainMoistures).reverse();
//add the cable data to the proper arrays
if (cable.topNode > 0) {
temps.push(...tempClone.splice(0, cable.topNode));
humids.push(...humClone.splice(0, cable.topNode));
emcs.push(...emcClone.splice(0, cable.topNode));
} else {
//if the cable has no fill set (top node) then use all of the nodes
temps = tempClone;
humids = humClone;
emcs = emcClone;
}
//add the trend data to the proper arrays if the top node is set
let cableTrendData = binTrend.cableTrend[cable.key()];
tempTrends.push(cableTrendData.grainCelciusTrend);
humidTrends.push(cableTrendData.grainHumidityTrend);
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
// //use this cables lowest node and trend data in the condition
// if (!lowNodeConditions || Math.min(...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));
// lowNodeConditions = {
// tempC: temps[lowTempIndex],
// humidity: humids[lowTempIndex],
// emc: emcs[lowTempIndex],
// tempCTrend: cableTrendData.coldNodeTrend?.tempTrend ?? 0,
// humidityTrend: cableTrendData.coldNodeTrend?.humidityTrend ?? 0,
// emcTrend: cableTrendData.coldNodeTrend?.emcTrend ?? 0
// };
// }
//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) {
//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));
lowNodeConditions = {
tempC: temps[lowTempIndex],
humidity: humids[lowTempIndex],
emc: emcs[lowTempIndex],
tempCTrend: cableTrendData.coldNodeTrend?.tempTrend ?? 0,
humidityTrend: cableTrendData.coldNodeTrend?.humidityTrend ?? 0,
emcTrend: cableTrendData.coldNodeTrend?.emcTrend ?? 0
};
}
// //do the same for the high node
// if (!highNodeConditions || cable.maxTemp() > highNodeConditions.tempC) {
// let highTempIndex =
// temps.indexOf(Math.max(...temps)) === -1 ? 0 : temps.indexOf(Math.max(...temps));
// highNodeConditions = {
// tempC: temps[highTempIndex],
// humidity: humids[highTempIndex],
// emc: emcs[highTempIndex],
// tempCTrend: cableTrendData.hotNodeTrend?.tempTrend ?? 0,
// humidityTrend: cableTrendData.hotNodeTrend?.humidityTrend ?? 0,
// emcTrend: cableTrendData.hotNodeTrend?.emcTrend ?? 0
// };
// }
// });
// //set the average conditions
// avgConditions = {
// tempC: average(temps),
// humidity: average(humids),
// emc: average(emcs),
// tempCTrend: average(tempTrends),
// humidityTrend: average(humidTrends),
// emcTrend: average(emcTrends)
// };
// setAverageConditions(avgConditions);
// setLowNodeConditions(lowNodeConditions);
// setHighNodeConditions(highNodeConditions);
// })
// .catch(err => {
// console.log(err);
// openSnack("Failed to retrieve trend data");
// })
// .finally(() => {
// setLoadingTrend(false);
// });
//do the same for the high node
if (!highNodeConditions || cable.maxTemp() > highNodeConditions.tempC) {
let highTempIndex =
temps.indexOf(Math.max(...temps)) === -1 ? 0 : temps.indexOf(Math.max(...temps));
highNodeConditions = {
tempC: temps[highTempIndex],
humidity: humids[highTempIndex],
emc: emcs[highTempIndex],
tempCTrend: cableTrendData.hotNodeTrend?.tempTrend ?? 0,
humidityTrend: cableTrendData.hotNodeTrend?.humidityTrend ?? 0,
emcTrend: cableTrendData.hotNodeTrend?.emcTrend ?? 0
};
}
});
//set the average conditions
avgConditions = {
tempC: average(temps),
humidity: average(humids),
emc: average(emcs),
tempCTrend: average(tempTrends),
humidityTrend: average(humidTrends),
emcTrend: average(emcTrends)
};
setAverageConditions(avgConditions);
setLowNodeConditions(lowNodeConditions);
setHighNodeConditions(highNodeConditions);
})
.catch(err => {
console.log(err);
openSnack("Failed to retrieve trend data");
})
.finally(() => {
setLoadingTrend(false);
});
}
}
}, [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) {
const isEmpty = bin.settings.inventory?.empty === true;
const capacity = bin.settings.specs.bushelCapacity;
const grainBushels = bin.settings.inventory.grainBushels;
const grainBushels = bin.bushels();
setSliderCoulour("gold");
if (!capacity) {
setFillPercentage(null);
@ -535,7 +535,7 @@ export default function BinVisualizer(props: Props) {
const inventoryOverview = () => {
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 grainType = bin.settings.inventory?.grainType;
const grainTypeName = isEmpty || !grainType ? "" : GrainDescriber(grainType).name;
@ -1481,9 +1481,7 @@ export default function BinVisualizer(props: Props) {
onChange={(_, value) => {
setFillPercentage(value as number);
const capacity = bin.settings.specs ? bin.settings.specs.bushelCapacity : 0;
const current = bin.settings.inventory
? bin.settings.inventory.grainBushels
: 0;
const current = bin.bushels()
let grainAmount = ((value as number) / 100) * capacity;
if (grainAmount < current) {

View file

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

View file

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

View file

@ -125,11 +125,23 @@ export class Bin {
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 {
let fill = 0;
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(
(this.settings.inventory.grainBushels / this.settings.specs.bushelCapacity) * 100
(bushels / this.settings.specs.bushelCapacity) * 100
);
}
return fill;

View file

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

View file

@ -1,7 +1,7 @@
import {
GraphComponent,
GraphOrientation,
GraphPoint,
// GraphComponent,
// GraphOrientation,
// GraphPoint,
GraphType,
InteractionLine
} from "common/Graph";
@ -41,15 +41,15 @@ import {
Weight,
CapacitorCable
} from "pbHelpers/ComponentTypes";
import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable";
import { multilineCapCableData } from "./ComponentTypes/CapacitorCable";
//import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable";
//import { multilineCapCableData } from "./ComponentTypes/CapacitorCable";
import { findInteractionsAsSource } from "pbHelpers/Interaction";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { notNull, or } from "utils/types";
import { emptyComponentId } from "./Component";
import { multilinePressureCableData } from "./ComponentTypes/PressureCable";
//import { emptyComponentId } from "./Component";
//import { multilinePressureCableData } from "./ComponentTypes/PressureCable";
import { describeMeasurement, MeasurementDescriber } from "./MeasurementDescriber";
import { convertedUnitMeasurement, MeasurementsFor, UnitMeasurement } from "models/UnitMeasurement";
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)
export function getGraphProps(
componentType: quack.ComponentType,
subtype: number,
measurements: pond.Measurement[],
interactions: Interaction[],
overlays: pond.ComponentOverlays[],
orientation: GraphOrientation,
filters?: GraphFilters
): any {
const componentID: quack.IComponentID = quack.ComponentID.fromObject(
or(measurements[0].measurement, {
id: emptyComponentId()
}).id as any
);
let graphProps = {
orientation: orientation
} as any;
let ext = extension(componentType, subtype);
ext.measurements.forEach((componentMeasurement: ComponentMeasurement, index: number) => {
let describer = describeMeasurement(
componentMeasurement.measurementType,
componentType,
subtype
);
let range =
filters && filters.ranges && filters.ranges[index] ? filters.ranges[index] : undefined;
let selectedInteractions =
filters && filters.selectedInteractions ? filters.selectedInteractions : [];
let interactionLines = getInteractionLines(
componentType,
subtype,
componentMeasurement.measurementType,
componentID,
interactions,
selectedInteractions
);
let selectedOverlays = filters?.selectedOverlays ?? [];
let overlayAreas = getOverlayAreas(
componentMeasurement.measurementType,
overlays,
selectedOverlays
);
graphProps["graphComponent" + (index + 1).toString()] = {
label: componentMeasurement.label,
data: [],
tick: describer.unit(),
colour: componentMeasurement.colour,
type: componentMeasurement.graphType,
range: range,
isBoolean:
componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN,
isPercent:
componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT ||
(componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_ANALOG &&
subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL),
interactionLines: interactionLines,
states: ext.states,
decimals: describer.decimals(),
overlays: overlayAreas
} as GraphComponent;
});
// export function getGraphProps(
// componentType: quack.ComponentType,
// subtype: number,
// measurements: pond.Measurement[],
// interactions: Interaction[],
// overlays: pond.ComponentOverlays[],
// orientation: GraphOrientation,
// filters?: GraphFilters
// ): any {
// const componentID: quack.IComponentID = quack.ComponentID.fromObject(
// or(measurements[0].measurement, {
// id: emptyComponentId()
// }).id as any
// );
// let graphProps = {
// orientation: orientation
// } as any;
// let ext = extension(componentType, subtype);
// ext.measurements.forEach((componentMeasurement: ComponentMeasurement, index: number) => {
// let describer = describeMeasurement(
// componentMeasurement.measurementType,
// componentType,
// subtype
// );
// let range =
// filters && filters.ranges && filters.ranges[index] ? filters.ranges[index] : undefined;
// let selectedInteractions =
// filters && filters.selectedInteractions ? filters.selectedInteractions : [];
// let interactionLines = getInteractionLines(
// componentType,
// subtype,
// componentMeasurement.measurementType,
// componentID,
// interactions,
// selectedInteractions
// );
// let selectedOverlays = filters?.selectedOverlays ?? [];
// let overlayAreas = getOverlayAreas(
// componentMeasurement.measurementType,
// overlays,
// selectedOverlays
// );
// graphProps["graphComponent" + (index + 1).toString()] = {
// label: componentMeasurement.label,
// data: [],
// tick: describer.unit(),
// colour: componentMeasurement.colour,
// type: componentMeasurement.graphType,
// range: range,
// isBoolean:
// componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN,
// isPercent:
// componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT ||
// (componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_ANALOG &&
// subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL),
// interactionLines: interactionLines,
// states: ext.states,
// decimals: describer.decimals(),
// overlays: overlayAreas
// } as GraphComponent;
// });
//for new cables add here to get the data for multiline
if (showMultilineCable(componentType, filters)) {
let cableData: any = {};
if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
cableData = multilineGrainCableData(measurements, filters);
} else if (componentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE) {
cableData = multilinePressureCableData(measurements, filters);
} else if (componentType === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE) {
cableData = multilineCapCableData(measurements, filters);
}
//will loop through the keys in cableData
//IMPORTANT keys must be data1, data2, etc in the multiline function in the component
for (let i = 1; i <= Object.keys(cableData).length; i++) {
let gc = "graphComponent" + i;
if (graphProps[gc]) {
graphProps[gc].data = cableData["data" + i];
graphProps[gc].type = GraphType.MULTILINE;
}
}
} else {
measurements.forEach((reading: pond.Measurement, readIndex) => {
if (notNull(reading) && notNull(reading.timestamp) && notNull(reading.measurement)) {
let date = new Date(Date.parse(reading.timestamp));
for (let index = 0; index < ext.measurements.length; index++) {
let componentMeasurement: ComponentMeasurement = ext.measurements[index];
if (componentMeasurement.isErrorMeasurement(reading.measurement)) {
break;
}
let dataPoint = null;
switch (componentMeasurement.graphType) {
case GraphType.AREA:
let avg = componentMeasurement.extract(
or(reading.measurement, {} as quack.Measurement),
filters
);
if (avg && avg.low !== null && avg.high !== null) {
dataPoint = {
x: date,
y: avg.low,
y0: avg.high
} as GraphPoint;
}
break;
case GraphType.SCATTER:
let scatter = componentMeasurement.extract(
or(reading.measurement, {} as quack.Measurement),
filters
);
if (scatter && scatter.value !== undefined && scatter.value !== null) {
dataPoint = {
x: date,
y: Number(scatter.value),
bubble: or(scatter.bubble, 0)
} as GraphPoint;
}
break;
case GraphType.RADAR:
if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_WIND_DIRECTION) {
let value = componentMeasurement.extract(
or(reading.measurement, {} as quack.Measurement),
filters
);
// //for new cables add here to get the data for multiline
// if (showMultilineCable(componentType, filters)) {
// let cableData: any = {};
// if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
// cableData = multilineGrainCableData(measurements, filters);
// } else if (componentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE) {
// cableData = multilinePressureCableData(measurements, filters);
// } else if (componentType === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE) {
// cableData = multilineCapCableData(measurements, filters);
// }
// //will loop through the keys in cableData
// //IMPORTANT keys must be data1, data2, etc in the multiline function in the component
// for (let i = 1; i <= Object.keys(cableData).length; i++) {
// let gc = "graphComponent" + i;
// if (graphProps[gc]) {
// graphProps[gc].data = cableData["data" + i];
// graphProps[gc].type = GraphType.MULTILINE;
// }
// }
// } else {
// measurements.forEach((reading: pond.Measurement, readIndex) => {
// if (notNull(reading) && notNull(reading.timestamp) && notNull(reading.measurement)) {
// let date = new Date(Date.parse(reading.timestamp));
// for (let index = 0; index < ext.measurements.length; index++) {
// let componentMeasurement: ComponentMeasurement = ext.measurements[index];
// if (componentMeasurement.isErrorMeasurement(reading.measurement)) {
// break;
// }
// let dataPoint = null;
// switch (componentMeasurement.graphType) {
// case GraphType.AREA:
// let avg = componentMeasurement.extract(
// or(reading.measurement, {} as quack.Measurement),
// filters
// );
// if (avg && avg.low !== null && avg.high !== null) {
// dataPoint = {
// x: date,
// y: avg.low,
// y0: avg.high
// } as GraphPoint;
// }
// break;
// case GraphType.SCATTER:
// let scatter = componentMeasurement.extract(
// or(reading.measurement, {} as quack.Measurement),
// filters
// );
// if (scatter && scatter.value !== undefined && scatter.value !== null) {
// dataPoint = {
// x: date,
// y: Number(scatter.value),
// bubble: or(scatter.bubble, 0)
// } as GraphPoint;
// }
// break;
// case GraphType.RADAR:
// if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_WIND_DIRECTION) {
// let value = componentMeasurement.extract(
// or(reading.measurement, {} as quack.Measurement),
// filters
// );
dataPoint = {
x: getWindDirection(value).dString,
y: getWindDirection(value).direction
};
}
break;
default:
//linear and bar
let value = componentMeasurement.extract(
or(reading.measurement, {} as quack.Measurement),
filters
);
// dataPoint = {
// x: getWindDirection(value).dString,
// y: getWindDirection(value).direction
// };
// }
// break;
// default:
// //linear and bar
// let value = componentMeasurement.extract(
// or(reading.measurement, {} as quack.Measurement),
// filters
// );
if (value !== undefined && value !== null) {
if (componentType === quack.ComponentType.COMPONENT_TYPE_ANALOG_INPUT) {
if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL) {
if (value > 100) {
value = 0;
}
}
}
if (componentType === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) {
if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_WIND_SPEED) {
if (readIndex < measurements.length - 1) {
//do math
//get the time of both the current reading and the reading before it
let currentTime = new Date(
Date.parse(measurements[readIndex].timestamp)
).valueOf();
let prevTime = new Date(
Date.parse(measurements[readIndex + 1].timestamp)
).valueOf();
// if (value !== undefined && value !== null) {
// if (componentType === quack.ComponentType.COMPONENT_TYPE_ANALOG_INPUT) {
// if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL) {
// if (value > 100) {
// value = 0;
// }
// }
// }
// if (componentType === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) {
// if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_WIND_SPEED) {
// if (readIndex < measurements.length - 1) {
// //do math
// //get the time of both the current reading and the reading before it
// let currentTime = new Date(
// Date.parse(measurements[readIndex].timestamp)
// ).valueOf();
// let prevTime = new Date(
// Date.parse(measurements[readIndex + 1].timestamp)
// ).valueOf();
//get the difference in seconds between the two
let deltaTime = (currentTime - prevTime) / 1000;
// //get the difference in seconds between the two
// let deltaTime = (currentTime - prevTime) / 1000;
//divide the ticks by the time between intervals for the average ticks per second and multiply
//by 2.4 to convert to km/h
value = (Number(value) / deltaTime) * 2.4;
} else {
value = 0;
}
}
if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_RAIN) {
value = Math.round(value * 0.2794);
}
}
dataPoint = {
x: date,
y: Number(value)
} as GraphPoint;
}
break;
}
if (dataPoint !== null) {
graphProps["graphComponent" + (index + 1).toString()].data.push(dataPoint);
}
}
}
});
}
return graphProps;
}
// //divide the ticks by the time between intervals for the average ticks per second and multiply
// //by 2.4 to convert to km/h
// value = (Number(value) / deltaTime) * 2.4;
// } else {
// value = 0;
// }
// }
// if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_RAIN) {
// value = Math.round(value * 0.2794);
// }
// }
// dataPoint = {
// x: date,
// y: Number(value)
// } as GraphPoint;
// }
// break;
// }
// if (dataPoint !== null) {
// graphProps["graphComponent" + (index + 1).toString()].data.push(dataPoint);
// }
// }
// }
// });
// }
// return graphProps;
// }
function getWindDirection(value: number) {
//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 TemperatureHumidityLightIcon from "assets/components/temperatureHumidityLight.png";
import { ExtractMoisture, grainName } from "grain";
import { GraphPoint } from "common/Graph";
import moment from "moment";
//import { GraphPoint } from "common/Graph";
//import moment from "moment";
import {
AreaChartData,
ComponentMeasurement,
@ -418,38 +418,38 @@ export function binSplitAt(filters?: GraphFilters): number {
}
//TODO: deprecated function with new measurements
export function multilineGrainCableData(
measurements: Array<pond.Measurement>,
filters?: GraphFilters
) {
let temperature: Array<Array<GraphPoint>> = [];
let humidity: Array<Array<GraphPoint>> = []; //humidity is not being used by the graphs
let moisture: Array<Array<GraphPoint>> = [];
measurements.forEach((measurement: pond.Measurement, i) => {
let nodes = extractNodes(measurement.measurement);
let selectedNodes = filters ? or(filters.selectedNodes, []) : [];
for (let j = 0; j < selectedNodes.length; j++) {
if (i === 0) {
temperature[j] = [];
humidity[j] = [];
moisture[j] = [];
}
let nodeIndex = selectedNodes[j];
if (nodeIndex < nodes.length) {
let node = nodes[nodeIndex];
let ts = moment(measurement.timestamp);
temperature[j].push({ x: ts, y: node.temperature });
humidity[j].push({ x: ts, y: node.humidity });
let grain =
isBinSplit(filters) && nodeIndex >= binSplitAt(filters)
? pond.Grain.GRAIN_NONE
: or(filters, { grainType: pond.Grain.GRAIN_NONE }).grainType;
moisture[j].push({
x: ts,
y: ExtractMoisture(grain, node.temperature, node.humidity)
});
}
}
});
return { data1: temperature, data2: moisture, data3: humidity };
}
// export function multilineGrainCableData(
// measurements: Array<pond.Measurement>,
// filters?: GraphFilters
// ) {
// let temperature: Array<Array<GraphPoint>> = [];
// let humidity: Array<Array<GraphPoint>> = []; //humidity is not being used by the graphs
// let moisture: Array<Array<GraphPoint>> = [];
// measurements.forEach((measurement: pond.Measurement, i) => {
// let nodes = extractNodes(measurement.measurement);
// let selectedNodes = filters ? or(filters.selectedNodes, []) : [];
// for (let j = 0; j < selectedNodes.length; j++) {
// if (i === 0) {
// temperature[j] = [];
// humidity[j] = [];
// moisture[j] = [];
// }
// let nodeIndex = selectedNodes[j];
// if (nodeIndex < nodes.length) {
// let node = nodes[nodeIndex];
// let ts = moment(measurement.timestamp);
// temperature[j].push({ x: ts, y: node.temperature });
// humidity[j].push({ x: ts, y: node.humidity });
// let grain =
// isBinSplit(filters) && nodeIndex >= binSplitAt(filters)
// ? pond.Grain.GRAIN_NONE
// : or(filters, { grainType: pond.Grain.GRAIN_NONE }).grainType;
// moisture[j].push({
// x: ts,
// y: ExtractMoisture(grain, node.temperature, node.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) {
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) {
this.details.path = "vpd.celciusTimes10";
@ -194,6 +198,10 @@ export class MeasurementDescriber {
if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
this.details.label = "Humidity";
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
// this.details.nodeDetails = {
// colours: ["white", "black"],

View file

@ -126,6 +126,10 @@ export interface IBinAPIContext {
showErrors?: boolean,
otherTeam?: string
) => Promise<AxiosResponse<pond.ListObjectMeasurementsResponse>>;
getBinsTrendData: (
bins: string[],
days: number
) => Promise<AxiosResponse<pond.GetBinsTrendDataResponse>>;
}
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 (
<BinAPIContext.Provider
value={{
@ -655,7 +665,8 @@ export default function BinProvider(props: PropsWithChildren<Props>) {
listHistoryBetween,
listBinPrefs,
updateTopNodes,
listBinMeasurements
listBinMeasurements,
getBinsTrendData
}}>
{children}
</BinAPIContext.Provider>

View file

@ -88,7 +88,7 @@ export default function GroupProvider(props: PropsWithChildren<Props>) {
const addDevice = (group: number, device: number) => {
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) => {
post(pondURL(url), group).then(resp => {
return resolve(resp)
@ -100,7 +100,7 @@ export default function GroupProvider(props: PropsWithChildren<Props>) {
const removeDevice = (group: number, device: number) => {
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) => {
post(pondURL(url), group).then(resp => {
return resolve(resp)