finished the bin sensor view to display connected components for a bin
This commit is contained in:
parent
67b251d865
commit
b00f10b831
16 changed files with 983 additions and 232 deletions
105
src/bin/BinSensorCard.tsx
Normal file
105
src/bin/BinSensorCard.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import { AccessTime, MoreVert } from "@mui/icons-material";
|
||||
import { Avatar, Box, Card, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Typography, useTheme } from "@mui/material";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
|
||||
interface SensorRow {
|
||||
label: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
interface CableRow {
|
||||
label: string;
|
||||
min: string;
|
||||
avg: string;
|
||||
max: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
icon?: string;
|
||||
tag: string;
|
||||
lastReading: string;
|
||||
onMenuClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
rows?: SensorRow[];
|
||||
cableRows?: CableRow[];
|
||||
/**
|
||||
* the time in hours that a reading is considered 'good'
|
||||
* defaults to 6
|
||||
*/
|
||||
staleLimit?: number;
|
||||
}
|
||||
|
||||
|
||||
export default function BinSensorCard(props: Props) {
|
||||
const { name, icon, tag, lastReading, onMenuClick, rows, cableRows, staleLimit = 6 } = props
|
||||
const theme = useTheme()
|
||||
const isStale = moment().diff(moment(lastReading), "hours") > staleLimit
|
||||
|
||||
return (
|
||||
<Card raised sx={{ height: "100%" }}>
|
||||
<Box padding={1.5} height="100%" display="flex" flexDirection="column">
|
||||
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" marginBottom={1}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
{icon &&
|
||||
<Avatar
|
||||
variant="square"
|
||||
src={icon}
|
||||
alt={name + " icon"}
|
||||
style={{ width: theme.spacing(3), height: theme.spacing(3) }}
|
||||
/>
|
||||
}
|
||||
<Typography fontWeight={500}>{name}</Typography>
|
||||
<Typography variant="caption" color="textSecondary">- {tag}</Typography>
|
||||
</Box>
|
||||
<IconButton size="small" onClick={onMenuClick}>
|
||||
<MoreVert fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
<Table size="small">
|
||||
{cableRows ? (
|
||||
<>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell sx={{ color: "text.secondary", fontSize: 11, fontWeight: 500, border: 0, pb: 0.5, pl: 0 }} />
|
||||
<TableCell align="right" sx={{ color: "text.secondary", fontSize: 11, fontWeight: 500, border: 0, pb: 0.5 }}>Min</TableCell>
|
||||
<TableCell align="right" sx={{ color: "primary.main", fontSize: 11, fontWeight: 500, border: 0, pb: 0.5 }}>Avg</TableCell>
|
||||
<TableCell align="right" sx={{ color: "text.secondary", fontSize: 11, fontWeight: 500, border: 0, pb: 0.5, pr: 0 }}>Max</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{cableRows.map((row) => (
|
||||
<TableRow key={row.label}>
|
||||
<TableCell sx={{ color: "text.secondary", fontSize: 11, pl: 0, py: 0.5 }}>{row.label}</TableCell>
|
||||
<TableCell align="right" sx={{ fontSize: 12, py: 0.5 }}>{row.min}</TableCell>
|
||||
<TableCell align="right" sx={{ color: "primary.main", fontWeight: 500, fontSize: 12, py: 0.5 }}>{row.avg}</TableCell>
|
||||
<TableCell align="right" sx={{ fontSize: 12, py: 0.5, pr: 0 }}>{row.max}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</>
|
||||
) : (
|
||||
<TableBody>
|
||||
{rows?.map((row) => (
|
||||
<TableRow key={row.label}>
|
||||
<TableCell sx={{ color: "text.secondary", fontSize: 11, pl: 0, py: 0.5 }}>{row.label}</TableCell>
|
||||
<TableCell align="right" sx={{ fontSize: 12, py: 0.5 }}>{row.data}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
|
||||
<Box display="flex" alignItems="center" gap={0.5} marginTop="auto">
|
||||
<AccessTime sx={{ fontSize: 12, color: isStale ? "warning.main" : "text.disabled" }} />
|
||||
<Typography variant="caption" color={isStale ? "warning.main" : "text.secondary"}>
|
||||
{moment(lastReading).fromNow()}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
780
src/bin/binSensorsDisplay.tsx
Normal file
780
src/bin/binSensorsDisplay.tsx
Normal file
|
|
@ -0,0 +1,780 @@
|
|||
import { AccessTime, MoreVert } from "@mui/icons-material";
|
||||
import { Avatar, Box, Button, Card, Grid2, IconButton, Menu, MenuItem, Typography, useTheme } from "@mui/material";
|
||||
import BinSensorCard from "bin/BinSensorCard";
|
||||
import { ExtractMoisture } from "grain";
|
||||
import { useThemeType } from "hooks";
|
||||
import { Bin, Component } from "models";
|
||||
import { Ambient } from "models/Ambient";
|
||||
import { CO2 } from "models/CO2";
|
||||
import { Controller } from "models/Controller";
|
||||
import { GrainCable } from "models/GrainCable";
|
||||
import { Headspace } from "models/Headspace";
|
||||
import { Plenum } from "models/Plenum";
|
||||
import { Pressure } from "models/Pressure";
|
||||
import moment from "moment";
|
||||
import { GetComponentIcon } from "pbHelpers/ComponentType";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { quack } from "protobuf-ts/quack";
|
||||
import { useBinAPI, useComponentAPI, useGlobalState, useSnackbar } from "providers";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { avg } from "utils";
|
||||
|
||||
interface Props {
|
||||
bin: Bin
|
||||
components: Map<string, Component>;
|
||||
componentDevices: Map<string, number>;
|
||||
preferences?: Map<string, pond.BinComponentPreferences>;
|
||||
setPreferences: React.Dispatch<
|
||||
React.SetStateAction<Map<string, pond.BinComponentPreferences> | undefined>
|
||||
>;
|
||||
|
||||
}
|
||||
|
||||
export default function BinSensorsDisplay(props: Props){
|
||||
const {bin, components, componentDevices, preferences, setPreferences} = props
|
||||
const themeType = useThemeType()
|
||||
const [{user, as}] = useGlobalState()
|
||||
const binAPI = useBinAPI()
|
||||
const componentAPI = useComponentAPI()
|
||||
const theme = useTheme()
|
||||
const snackbar = useSnackbar()
|
||||
const [cables, setCables] = useState<GrainCable[]>([])
|
||||
const [plenums, setPlenums] = useState<Plenum[]>([])
|
||||
const [ambient, setAmbient] = useState<Ambient[]>([])
|
||||
const [pressures, setPressures] = useState<Pressure[]>([])
|
||||
const [fans, setFans] = useState<Controller[]>([])
|
||||
const [heaters, setHeaters] = useState<Controller[]>([])
|
||||
//the above component preferences all have only components that are one type of sensor, however headspace has components of three different sensors T/H, CO2, and Lidar
|
||||
const [headspaceTH, setHeadspaceTH] = useState<Headspace[]>([]) //the components in the headspace that measure temp/humidity
|
||||
const [headspaceCO2, setHeadspaceCO2] = useState<CO2[]>([]) //the CO2 components in the headspace
|
||||
const [headspaceLidar, setHeadspaceLidar] = useState<Component[]>([]) //the lidar components in the headspace
|
||||
|
||||
const [unassignedComponents, setUnassignedComponents] = useState<Component[]>([])
|
||||
|
||||
//using counts so i dont have to have to check multiple arrays for sensors and controllers
|
||||
const [sensorCount, setSensorCount] = useState(0)
|
||||
const [controllerCount, setControllerCount] = useState(0)
|
||||
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
//only used to determine what preferences are options for the selected component ie. DHT can be a plenum or a headspace
|
||||
const [selectedComponentType, setSelectedComponentType] = useState<
|
||||
quack.ComponentType | undefined
|
||||
>(undefined);
|
||||
const [componentUnassigned, setComponentUnnassigned] = useState(false);
|
||||
const [selectedComponentKey, setSelectedComponentKey] = useState("");
|
||||
const [selectedComponentSubtype, setSelectedComponentSubtype] = useState(0);
|
||||
const [selectedComponentTopNode, setSelectedComponentTopNode] = useState(0);
|
||||
const [showGraphs, setShowGraphs] = useState(false);
|
||||
|
||||
//organize the components into the correct 'positions' using the bins preferences
|
||||
useEffect(()=>{
|
||||
let unassigned: Component[] = []
|
||||
let cables: GrainCable[] = []
|
||||
let plenums: Plenum[] = []
|
||||
let ambients: Ambient[] = []
|
||||
let pressures: Pressure[] = []
|
||||
let fans: Controller[] = []
|
||||
let heaters: Controller[] = []
|
||||
let headspaceTH: Headspace[] = []
|
||||
let headspaceCO2: CO2[] = []
|
||||
let headspaceLidar: Component[] = []
|
||||
let sensorCount = 0
|
||||
let controllerCount = 0
|
||||
|
||||
components.forEach(comp => {
|
||||
if(preferences){
|
||||
let pref = preferences.get(comp.key())
|
||||
switch(pref?.type){
|
||||
case pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE:
|
||||
cables.push(GrainCable.create(comp))
|
||||
sensorCount++
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_PLENUM:
|
||||
plenums.push(Plenum.create(comp))
|
||||
sensorCount++
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_AMBIENT:
|
||||
ambients.push(Ambient.create(comp))
|
||||
sensorCount++
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_PRESSURE:
|
||||
pressures.push(Pressure.create(comp))
|
||||
sensorCount++
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_FAN:
|
||||
fans.push(Controller.create(comp))
|
||||
controllerCount++
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_HEATER:
|
||||
heaters.push(Controller.create(comp))
|
||||
controllerCount++
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_HEADSPACE:
|
||||
if (comp.type() === quack.ComponentType.COMPONENT_TYPE_DHT) {
|
||||
headspaceTH.push(Headspace.create(comp));
|
||||
} else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR) {
|
||||
headspaceLidar.push(comp);
|
||||
} else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_CO2) {
|
||||
let coComp = CO2.create(comp)
|
||||
headspaceCO2.push(coComp);
|
||||
}
|
||||
sensorCount++
|
||||
break;
|
||||
default:
|
||||
unassigned.push(comp)
|
||||
}
|
||||
}else{
|
||||
unassigned.push(comp)
|
||||
}
|
||||
})
|
||||
// console.log(cables)
|
||||
// console.log(unassigned)
|
||||
setSensorCount(sensorCount)
|
||||
setControllerCount(controllerCount)
|
||||
setCables(cables)
|
||||
setPlenums(plenums)
|
||||
setPressures(pressures)
|
||||
setAmbient(ambients)
|
||||
setFans(fans)
|
||||
setHeaters(heaters)
|
||||
setHeadspaceTH(headspaceTH)
|
||||
setHeadspaceCO2(headspaceCO2)
|
||||
setHeadspaceLidar(headspaceLidar)
|
||||
setUnassignedComponents(unassigned)
|
||||
},[components, preferences])
|
||||
|
||||
const selectType = (component: string, type: pond.BinComponent, topNode?: number) => {
|
||||
setAnchorEl(null);
|
||||
if (!preferences) return
|
||||
let p = preferences.get(component);
|
||||
if (p) {
|
||||
p.type = type;
|
||||
p.node = topNode ?? 0;
|
||||
binAPI
|
||||
.updateComponentPreferences(bin.key(), component, p, as)
|
||||
.then(() => {
|
||||
snackbar.success("Component preferences updated");
|
||||
preferences.set(component, p!);
|
||||
setPreferences(new Map(preferences));
|
||||
})
|
||||
.catch(() => {
|
||||
snackbar.error("Component preference update failed");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getOptions = () => {
|
||||
let options: JSX.Element[] = [];
|
||||
|
||||
if (!componentUnassigned) {
|
||||
options.push(
|
||||
<MenuItem
|
||||
key="remove"
|
||||
onClick={() => {
|
||||
selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_UNKNOWN);
|
||||
}}>
|
||||
Remove
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
if (
|
||||
selectedComponentType === quack.ComponentType.COMPONENT_TYPE_DHT ||
|
||||
selectedComponentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE
|
||||
) {
|
||||
options.push(
|
||||
<MenuItem
|
||||
key="ambientOption"
|
||||
onClick={() => {
|
||||
selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_AMBIENT);
|
||||
}}>
|
||||
Ambient
|
||||
</MenuItem>
|
||||
);
|
||||
options.push(
|
||||
<MenuItem
|
||||
key="plenumOption"
|
||||
onClick={() => {
|
||||
selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_PLENUM);
|
||||
}}>
|
||||
Plenum
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
if (
|
||||
selectedComponentType === quack.ComponentType.COMPONENT_TYPE_DHT ||
|
||||
selectedComponentType === quack.ComponentType.COMPONENT_TYPE_LIDAR ||
|
||||
selectedComponentType === quack.ComponentType.COMPONENT_TYPE_CO2
|
||||
) {
|
||||
options.push(
|
||||
<MenuItem
|
||||
key="headspaceOption"
|
||||
onClick={() => {
|
||||
selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_HEADSPACE);
|
||||
}}>
|
||||
Headspace
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
if (
|
||||
selectedComponentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE ||
|
||||
selectedComponentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE
|
||||
) {
|
||||
options.push(
|
||||
<MenuItem
|
||||
key="pressureOption"
|
||||
onClick={() => {
|
||||
selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_PRESSURE);
|
||||
}}>
|
||||
Plenum Pressure
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
if (selectedComponentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
|
||||
options.push(
|
||||
<MenuItem
|
||||
key="cableOption"
|
||||
onClick={() => {
|
||||
selectType(
|
||||
selectedComponentKey,
|
||||
pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE,
|
||||
selectedComponentTopNode
|
||||
);
|
||||
}}>
|
||||
Grain Cable
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
if (selectedComponentType === quack.ComponentType.COMPONENT_TYPE_BOOLEAN_OUTPUT) {
|
||||
options.push(
|
||||
<MenuItem
|
||||
key="controlOption"
|
||||
onClick={() => {
|
||||
if (
|
||||
selectedComponentSubtype === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER
|
||||
) {
|
||||
selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_HEATER);
|
||||
} else if (
|
||||
selectedComponentSubtype ===
|
||||
quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN
|
||||
) {
|
||||
selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_FAN);
|
||||
}
|
||||
}}>
|
||||
Heaters & Fans
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (options.length === 0) {
|
||||
return <MenuItem>No Available Options</MenuItem>;
|
||||
} else {
|
||||
return options;
|
||||
}
|
||||
};
|
||||
|
||||
const assignmentMenu = () => {
|
||||
return (
|
||||
<Menu
|
||||
id="groupMenu"
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={() => setAnchorEl(null)}
|
||||
keepMounted
|
||||
disableAutoFocusItem>
|
||||
{getOptions()}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
const cableCard = (cable: GrainCable) => {
|
||||
const component = cable.asComponent()
|
||||
let icon = GetComponentIcon(component.type(), component.subType(), themeType)
|
||||
let unit = user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C"
|
||||
|
||||
const hasHumidity = cable.aveHumidity() !== 0
|
||||
const hasMoisture = cable.grainMoistures.length > 0
|
||||
|
||||
const rows: { label: string; min: string; avg: string; max: string }[] = [
|
||||
{
|
||||
label: `Temp ${unit}`,
|
||||
min: cable.minTemp(user.tempUnit()).toFixed(2),
|
||||
avg: cable.aveTemp(user.tempUnit()).toFixed(2),
|
||||
max: cable.maxTemp(user.tempUnit()).toFixed(2),
|
||||
},
|
||||
...(hasHumidity ? [{
|
||||
label: "RH %",
|
||||
min: cable.minHumidity().toFixed(2),
|
||||
avg: cable.aveHumidity().toFixed(2),
|
||||
max: cable.maxHumidity().toFixed(2),
|
||||
}] : []),
|
||||
...(hasMoisture ? [{
|
||||
label: "EMC %",
|
||||
min: cable.minMoisture().toFixed(2),
|
||||
avg: cable.aveMoisture().toFixed(2),
|
||||
max: cable.maxMoisture().toFixed(2),
|
||||
}] : []),
|
||||
]
|
||||
|
||||
return (
|
||||
<BinSensorCard
|
||||
icon={icon}
|
||||
lastReading={cable.lastReading}
|
||||
name={cable.name()}
|
||||
tag={"cable"}
|
||||
cableRows={rows}
|
||||
onMenuClick={(event) => {
|
||||
setComponentUnnassigned(false);
|
||||
setSelectedComponentType(cable.type());
|
||||
setSelectedComponentKey(cable.key());
|
||||
setSelectedComponentTopNode(cable.settings.grainFilledTo);
|
||||
setAnchorEl(event.currentTarget);
|
||||
}}/>
|
||||
)
|
||||
}
|
||||
|
||||
const tempHumidCard = (sensor: Plenum | Ambient | Headspace, showEMC?: boolean) => {
|
||||
let icon = GetComponentIcon(sensor.settings.type, sensor.settings.subtype, themeType)
|
||||
let prefDisplay = ""
|
||||
switch (preferences?.get(sensor.key())?.type){
|
||||
case pond.BinComponent.BIN_COMPONENT_PLENUM:
|
||||
prefDisplay = "Plenum"
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_AMBIENT:
|
||||
prefDisplay = "Ambient"
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_HEADSPACE:
|
||||
prefDisplay = "Headspace"
|
||||
}
|
||||
|
||||
let rows: {label: string, data: string}[] = [
|
||||
// build temperature row data
|
||||
{
|
||||
label: "Temp " + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C"),
|
||||
data: (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? sensor.temperature * (9 / 5) + 32 : sensor.temperature).toFixed(2)
|
||||
},
|
||||
// build humidity row
|
||||
{
|
||||
label: "RH %",
|
||||
data: sensor.humidity.toFixed(2)
|
||||
}
|
||||
]
|
||||
// build moisture row assuming the bin has a grain type to use to calculatee the moisture
|
||||
if(showEMC && bin.grain() !== pond.Grain.GRAIN_INVALID && bin.grain() !== pond.Grain.GRAIN_NONE){
|
||||
let emc = ExtractMoisture(
|
||||
bin.grain(),
|
||||
sensor.temperature,
|
||||
sensor.humidity,
|
||||
bin.customGrain()
|
||||
)
|
||||
rows.push(
|
||||
{
|
||||
label: "EMC %",
|
||||
data: emc.toFixed(2)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<BinSensorCard
|
||||
lastReading={sensor.lastReading}
|
||||
name={sensor.name()}
|
||||
tag={prefDisplay}
|
||||
icon={icon}
|
||||
rows={rows}
|
||||
onMenuClick={(event) => {
|
||||
setComponentUnnassigned(false);
|
||||
setSelectedComponentType(sensor.type());
|
||||
setSelectedComponentKey(sensor.key());
|
||||
setAnchorEl(event.currentTarget);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const pressureCard = (pressure: Pressure) => {
|
||||
let icon = GetComponentIcon(pressure.settings.type, pressure.settings.subtype, themeType)
|
||||
let rows: {label: string, data: string}[] = [
|
||||
{
|
||||
label: "Pressure " + (user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? "iwg" : "kPa"),
|
||||
data: (user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? pressure.pascals/249 : pressure.pascals/1000).toFixed(2)
|
||||
}
|
||||
]
|
||||
|
||||
if(pressure.airflow){
|
||||
rows.push({
|
||||
label: "Airflow CFM",
|
||||
data: pressure.airflow.toFixed(2)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<BinSensorCard
|
||||
lastReading={pressure.lastReading}
|
||||
name={pressure.name()}
|
||||
tag={"Pressure"}
|
||||
icon={icon}
|
||||
rows={rows}
|
||||
onMenuClick={(event) => {
|
||||
setComponentUnnassigned(false);
|
||||
setSelectedComponentType(pressure.type());
|
||||
setSelectedComponentKey(pressure.key());
|
||||
setAnchorEl(event.currentTarget);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const co2Card = (sensor: CO2) => {
|
||||
let icon = GetComponentIcon(sensor.settings.type, sensor.settings.subtype, themeType)
|
||||
let rows: {label: string, data: string}[] = [
|
||||
{
|
||||
label: "CO2 ppm",
|
||||
data: sensor.ppm.toFixed(2)
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<BinSensorCard
|
||||
lastReading={sensor.lastReading}
|
||||
name={sensor.name()}
|
||||
tag={"Headspace"}
|
||||
icon={icon}
|
||||
rows={rows}
|
||||
onMenuClick={(event) => {
|
||||
setComponentUnnassigned(false);
|
||||
setSelectedComponentType(sensor.type());
|
||||
setSelectedComponentKey(sensor.key());
|
||||
setAnchorEl(event.currentTarget);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const lidarCard = (sensor: Component) => {
|
||||
let icon = GetComponentIcon(sensor.settings.type, sensor.settings.subtype, themeType)
|
||||
let rows: {label: string, data: string}[] = []
|
||||
let lastReading = moment.now().toString()
|
||||
sensor.lastMeasurement.forEach(unitMeasurement => {
|
||||
let label = ""
|
||||
let value = 0
|
||||
if(sensor.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR){
|
||||
if(unitMeasurement.type === quack.MeasurementType.MEASUREMENT_TYPE_DISTANCE_CM){
|
||||
label = "Lidar " + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? " ft" : " M")
|
||||
if(unitMeasurement.timestamps.length > 0){
|
||||
lastReading = unitMeasurement.timestamps[0]
|
||||
}
|
||||
if (unitMeasurement.values.length > 0){
|
||||
//distance is stored in cm so needs to be converted
|
||||
value = avg(unitMeasurement.values[0].values)
|
||||
value = user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? value/30.48 : value/100
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
rows.push({label: label, data: value.toFixed(2)})
|
||||
})
|
||||
|
||||
return (
|
||||
<BinSensorCard
|
||||
lastReading={lastReading}
|
||||
name={sensor.name()}
|
||||
tag={"Headspace"}
|
||||
icon={icon}
|
||||
rows={rows}
|
||||
onMenuClick={(event) => {
|
||||
setComponentUnnassigned(false);
|
||||
setSelectedComponentType(sensor.type());
|
||||
setSelectedComponentKey(sensor.key());
|
||||
setAnchorEl(event.currentTarget);
|
||||
}}
|
||||
/>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
const controllerCard = (controller: Controller) => {
|
||||
const component = controller.asComponent()
|
||||
let icon = GetComponentIcon(component.type(), component.subType(), themeType)
|
||||
const isStale = moment().diff(moment(controller.lastReading), "hours") > 6
|
||||
const isAuto = controller.mode === quack.OutputMode.OUTPUT_MODE_AUTO
|
||||
const isOn = controller.mode === quack.OutputMode.OUTPUT_MODE_ON
|
||||
const isOff = controller.mode === quack.OutputMode.OUTPUT_MODE_OFF
|
||||
|
||||
const prefType = preferences?.get(controller.key())?.type
|
||||
const tag = prefType === pond.BinComponent.BIN_COMPONENT_FAN ? "Fan" : "Heater"
|
||||
|
||||
const segStyle = (active: boolean, activeColor?: string): React.CSSProperties => ({
|
||||
flex: 1,
|
||||
padding: "5px 0",
|
||||
fontSize: 11,
|
||||
fontWeight: 500,
|
||||
textAlign: "center",
|
||||
cursor: "pointer",
|
||||
borderRadius: 0,
|
||||
backgroundColor: active ? (activeColor ?? theme.palette.success.main) : "transparent",
|
||||
color: active ? "#fff" : theme.palette.text.secondary,
|
||||
border: "none",
|
||||
transition: "background-color 0.15s",
|
||||
})
|
||||
|
||||
return (
|
||||
<Card raised sx={{ height: "100%" }}>
|
||||
<Box padding={1.5} height="100%" display="flex" flexDirection="column">
|
||||
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" marginBottom={1}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Avatar
|
||||
variant="square"
|
||||
src={icon}
|
||||
alt={controller.name() + " icon"}
|
||||
style={{ width: theme.spacing(3), height: theme.spacing(3) }}
|
||||
/>
|
||||
<Typography fontWeight={500}>{controller.name()}</Typography>
|
||||
<Typography variant="caption" color="textSecondary">- {tag}</Typography>
|
||||
</Box>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setComponentUnnassigned(false)
|
||||
setSelectedComponentType(controller.type())
|
||||
setSelectedComponentKey(controller.key())
|
||||
setAnchorEl(event.currentTarget)
|
||||
}}
|
||||
>
|
||||
<MoreVert fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" marginBottom={1.5}>
|
||||
<Typography variant="caption" color="textSecondary">Current state</Typography>
|
||||
<Box display="flex" alignItems="center" gap={0.5}>
|
||||
<Box
|
||||
sx={{
|
||||
width: 7,
|
||||
height: 7,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: controller.on
|
||||
? theme.palette.success.main
|
||||
: theme.palette.text.disabled,
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
variant="caption"
|
||||
fontWeight={500}
|
||||
color={controller.on ? "success.main" : "text.secondary"}
|
||||
>
|
||||
{controller.on ? "Running" : isAuto ? "Standby" : "Off"}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
display="flex"
|
||||
sx={{
|
||||
border: `0.5px solid ${theme.palette.divider}`,
|
||||
borderRadius: 1,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{[
|
||||
{ label: "Auto", mode: quack.OutputMode.OUTPUT_MODE_AUTO, active: isAuto },
|
||||
{ label: "On", mode: quack.OutputMode.OUTPUT_MODE_ON, active: isOn },
|
||||
{ label: "Off", mode: quack.OutputMode.OUTPUT_MODE_OFF, active: isOff },
|
||||
].map(({ label, mode, active }, i) => (
|
||||
<Box
|
||||
key={label}
|
||||
component="button"
|
||||
onClick={() => {
|
||||
//this will update the components settings here to change the mode
|
||||
console.log("changing mode: " + mode)
|
||||
controller.settings.defaultOutputState = mode
|
||||
let device = componentDevices.get(controller.key())
|
||||
if(device){
|
||||
componentAPI.update(device, controller.settings).then(resp => {
|
||||
console.log("updated controller")
|
||||
if (prefType === pond.BinComponent.BIN_COMPONENT_FAN) {
|
||||
setFans(prev => [...prev])
|
||||
} else {
|
||||
setHeaters(prev => [...prev])
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log("there was a problem updating the controller")
|
||||
})
|
||||
}
|
||||
|
||||
}}
|
||||
sx={{
|
||||
...segStyle(active, label === "Off" ? theme.palette.action.selected : undefined),
|
||||
borderLeft: i > 0 ? `0.5px solid ${theme.palette.divider}` : "none",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Box display="flex" alignItems="center" gap={0.5} marginTop="auto" paddingTop={1}>
|
||||
<AccessTime sx={{ fontSize: 12, color: isStale ? "warning.main" : "text.disabled" }} />
|
||||
<Typography variant="caption" color={isStale ? "warning.main" : "text.secondary"}>
|
||||
{moment(controller.lastReading).fromNow()}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const unassignedComponentCard = (component: Component) => {
|
||||
let icon = GetComponentIcon(component.type(), component.subType(), themeType)
|
||||
const lastReading = component.lastMeasurement.length > 0
|
||||
? component.lastMeasurement[0].timestamps[0] ?? ""
|
||||
: ""
|
||||
const isStale = moment().diff(moment(lastReading), "hours") > 6
|
||||
|
||||
return (
|
||||
<Card raised sx={{ height: "100%" }}>
|
||||
<Box padding={1.5} height="100%" display="flex" flexDirection="column">
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Avatar
|
||||
variant="square"
|
||||
src={icon}
|
||||
alt={component.name() + " icon"}
|
||||
style={{ width: theme.spacing(3), height: theme.spacing(3) }}
|
||||
/>
|
||||
<Typography fontWeight={500}>{component.name()}</Typography>
|
||||
</Box>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setComponentUnnassigned(true)
|
||||
setSelectedComponentType(component.type())
|
||||
setSelectedComponentKey(component.key())
|
||||
setSelectedComponentSubtype(component.subType())
|
||||
setSelectedComponentTopNode(0)
|
||||
setAnchorEl(event.currentTarget)
|
||||
}}
|
||||
>
|
||||
<MoreVert fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" alignItems="center" gap={0.5} marginTop="auto">
|
||||
<AccessTime sx={{ fontSize: 12, color: isStale ? "warning.main" : "text.disabled" }} />
|
||||
<Typography variant="caption" color={isStale ? "warning.main" : "text.secondary"}>
|
||||
{lastReading ? moment(lastReading).fromNow() : "No readings yet"}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box padding={1}>
|
||||
{assignmentMenu()}
|
||||
{sensorCount > 0 &&
|
||||
<Box>
|
||||
<Box display="flex" gap={2} alignItems="center">
|
||||
<Typography sx={{fontWeight: 650, fontSize: 25}}>Sensors</Typography>
|
||||
<Button variant="contained" color="primary" onClick={() => {
|
||||
setShowGraphs(true)
|
||||
}}>
|
||||
Show Graphs
|
||||
</Button>
|
||||
</Box>
|
||||
<Grid2 container spacing={2}>
|
||||
{cables.map(cable => {
|
||||
return(
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
{cableCard(cable)}
|
||||
</Grid2>
|
||||
)
|
||||
}
|
||||
)}
|
||||
{plenums.map(plenum => {
|
||||
return(
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
{tempHumidCard(plenum)}
|
||||
</Grid2>
|
||||
)
|
||||
}
|
||||
)}
|
||||
{pressures.map(pressure => {
|
||||
return(
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
{pressureCard(pressure)}
|
||||
</Grid2>
|
||||
)
|
||||
})}
|
||||
{ambient.map(ambient => {
|
||||
return(
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
{tempHumidCard(ambient)}
|
||||
</Grid2>
|
||||
)
|
||||
}
|
||||
)}
|
||||
{headspaceTH.map(h => {
|
||||
return(
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
{tempHumidCard(h)}
|
||||
</Grid2>
|
||||
)
|
||||
})}
|
||||
|
||||
{headspaceCO2.map(co2 => {
|
||||
return(
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
{co2Card(co2)}
|
||||
</Grid2>
|
||||
)
|
||||
})}
|
||||
{headspaceLidar.map(lidar => {
|
||||
return (
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
{lidarCard(lidar)}
|
||||
</Grid2>
|
||||
)
|
||||
})}
|
||||
</Grid2>
|
||||
</Box>
|
||||
}
|
||||
|
||||
{controllerCount > 0 &&
|
||||
<Box>
|
||||
<Typography sx={{fontWeight: 650, fontSize: 25}}>Controllers</Typography>
|
||||
<Grid2 container spacing={2}>
|
||||
{heaters.map(heater => {
|
||||
return (
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
{controllerCard(heater)}
|
||||
</Grid2>
|
||||
)
|
||||
})}
|
||||
{fans.map(fan => {
|
||||
return (
|
||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
||||
{controllerCard(fan)}
|
||||
</Grid2>
|
||||
)
|
||||
})}
|
||||
</Grid2>
|
||||
</Box>
|
||||
}
|
||||
{unassignedComponents.length > 0 &&
|
||||
<Box>
|
||||
<Typography sx={{ fontWeight: 650, fontSize: 25 }}>Unassigned Components</Typography>
|
||||
<Grid2 container spacing={2}>
|
||||
{unassignedComponents.map(comp => (
|
||||
<Grid2 size={{ xs: 6, sm: 6, md: 4, lg: 3 }}>
|
||||
{unassignedComponentCard(comp)}
|
||||
</Grid2>
|
||||
))}
|
||||
</Grid2>
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -2,18 +2,16 @@ import { Box, Card, Grid2, LinearProgress, Typography } from "@mui/material";
|
|||
import { useMobile } from "hooks";
|
||||
import { Bin, Component, Device } from "models";
|
||||
import Bin3dVisualizer from "./components/bin3dVisualizer";
|
||||
import BinTableView from "./components/binTableView";
|
||||
// import GrassIcon from "@mui/icons-material/Grass"
|
||||
import { grey, orange, red } from "@mui/material/colors";
|
||||
import { AccessTime, Spa } from "@mui/icons-material";
|
||||
import moment from "moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller } from "models/Controller";
|
||||
import { GrainCable } from "models/GrainCable";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import BinDetails from "./components/binDetails";
|
||||
import { Plenum } from "models/Plenum";
|
||||
import BinSensors from "./components/binSensors";
|
||||
import BinSensorsDisplay from "../binSensorsDisplay";
|
||||
|
||||
interface Props {
|
||||
bin: Bin
|
||||
|
|
@ -73,20 +71,6 @@ export default function BinSummary(props: Props){
|
|||
)
|
||||
}
|
||||
|
||||
const sensorData = () => {
|
||||
return (
|
||||
<Box>sensor data</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const controllerStatus = () => {
|
||||
return (
|
||||
<Box>controller status</Box>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Box padding={2}>
|
||||
<Card raised sx={{marginBottom: 2}}>
|
||||
|
|
@ -191,7 +175,7 @@ export default function BinSummary(props: Props){
|
|||
</Grid2>
|
||||
<Grid2 size={12}>
|
||||
<Card>
|
||||
<BinSensors components={componentMap} bin={bin} preferences={binPrefs} setPreferences={setPreferences}/>
|
||||
<BinSensorsDisplay components={componentMap} bin={bin} preferences={binPrefs} setPreferences={setPreferences} componentDevices={componentDevices}/>
|
||||
</Card>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Box, Tab, Tabs } from "@mui/material";
|
||||
import React from "react";
|
||||
import { useState } from "react";
|
||||
import BinTableView from "./binTableView";
|
||||
import BinTableView from "../../binTableView";
|
||||
import { Bin, Component, Device } from "models";
|
||||
import Alerts from "objects/objectInteractions/Alerts";
|
||||
import BinAlerts from "./binAlerts";
|
||||
|
|
|
|||
|
|
@ -1,156 +0,0 @@
|
|||
import { Box, Grid2, Typography } from "@mui/material";
|
||||
import { Bin, Component } from "models";
|
||||
import { Ambient } from "models/Ambient";
|
||||
import { CO2 } from "models/CO2";
|
||||
import { Controller } from "models/Controller";
|
||||
import { GrainCable } from "models/GrainCable";
|
||||
import { Headspace } from "models/Headspace";
|
||||
import { Plenum } from "models/Plenum";
|
||||
import { Pressure } from "models/Pressure";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { quack } from "protobuf-ts/quack";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
interface Props {
|
||||
bin: Bin
|
||||
components: Map<string, Component>;
|
||||
preferences?: Map<string, pond.BinComponentPreferences>;
|
||||
setPreferences: React.Dispatch<
|
||||
React.SetStateAction<Map<string, pond.BinComponentPreferences> | undefined>
|
||||
>;
|
||||
}
|
||||
|
||||
export default function BinSensors(props: Props){
|
||||
const {bin, components, preferences, setPreferences} = props
|
||||
const [cables, setCables] = useState<GrainCable[]>([])
|
||||
const [plenums, setPlenums] = useState<Plenum[]>([])
|
||||
const [ambient, setAmbient] = useState<Ambient[]>([])
|
||||
const [pressures, setPressures] = useState<Pressure[]>([])
|
||||
const [fans, setFans] = useState<Controller[]>([])
|
||||
const [heaters, setHeaters] = useState<Controller[]>([])
|
||||
//the above component preferences all have only components that are one type of sensor, however headspace has components of three different sensors T/H, CO2, and Lidar
|
||||
const [headspaceTH, setHeadspaceTH] = useState<Headspace[]>([]) //the components in the headspace that measure temp/humidity
|
||||
const [headspaceCO2, setHeadspaceCO2] = useState<CO2[]>([]) //the CO2 components in the headspace
|
||||
const [headspaceLidar, setHeadspaceLidar] = useState<Component[]>([]) //the lidar components in the headspace
|
||||
|
||||
const [unassignedComponents, setUnassignedComponents] = useState<Component[]>([])
|
||||
|
||||
//using counts so i dont have to have to check multiple arrays for sensors and controllers
|
||||
const [sensorCount, setSensorCount] = useState(0)
|
||||
const [controllerCount, setControllerCount] = useState(0)
|
||||
|
||||
|
||||
//organize the components into the correct 'positions' using the bins preferences
|
||||
useEffect(()=>{
|
||||
let unassigned: Component[] = []
|
||||
let cables: GrainCable[] = []
|
||||
let plenums: Plenum[] = []
|
||||
let ambients: Ambient[] = []
|
||||
let pressures: Pressure[] = []
|
||||
let fans: Controller[] = []
|
||||
let heaters: Controller[] = []
|
||||
let headspaceTH: Headspace[] = []
|
||||
let headspaceCO2: CO2[] = []
|
||||
let headspaceLidar: Component[] = []
|
||||
let sensorCount = 0
|
||||
let controllerCount = 0
|
||||
|
||||
components.forEach(comp => {
|
||||
if(preferences){
|
||||
let pref = preferences.get(comp.key())
|
||||
switch(pref?.type){
|
||||
case pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE:
|
||||
cables.push(GrainCable.create(comp))
|
||||
sensorCount++
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_PLENUM:
|
||||
plenums.push(Plenum.create(comp))
|
||||
sensorCount++
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_AMBIENT:
|
||||
ambients.push(Ambient.create(comp))
|
||||
sensorCount++
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_PRESSURE:
|
||||
pressures.push(Pressure.create(comp))
|
||||
sensorCount++
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_FAN:
|
||||
fans.push(Controller.create(comp))
|
||||
controllerCount++
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_HEATER:
|
||||
heaters.push(Controller.create(comp))
|
||||
controllerCount++
|
||||
break;
|
||||
case pond.BinComponent.BIN_COMPONENT_HEADSPACE:
|
||||
if (comp.type() === quack.ComponentType.COMPONENT_TYPE_DHT) {
|
||||
headspaceTH.push(Headspace.create(comp));
|
||||
} else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR) {
|
||||
headspaceLidar.push(comp);
|
||||
} else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_CO2) {
|
||||
let coComp = CO2.create(comp)
|
||||
headspaceCO2.push(coComp);
|
||||
}
|
||||
sensorCount++
|
||||
break;
|
||||
default:
|
||||
unassigned.push(comp)
|
||||
}
|
||||
}else{
|
||||
unassigned.push(comp)
|
||||
}
|
||||
})
|
||||
// console.log(cables)
|
||||
// console.log(unassigned)
|
||||
setCables(cables)
|
||||
setPlenums(plenums)
|
||||
setPressures(pressures)
|
||||
setAmbient(ambients)
|
||||
setFans(fans)
|
||||
setHeaters(heaters)
|
||||
setHeadspaceTH(headspaceTH)
|
||||
setHeadspaceCO2(headspaceCO2)
|
||||
setHeadspaceLidar(headspaceLidar)
|
||||
setUnassignedComponents(unassigned)
|
||||
},[components, preferences])
|
||||
|
||||
const cableDisplay = (cable: GrainCable) => {
|
||||
return (
|
||||
<Box>
|
||||
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{sensorCount > 0 &&
|
||||
<React.Fragment>
|
||||
<Typography>Sensors</Typography>
|
||||
{cables.length > 0 &&
|
||||
<React.Fragment>
|
||||
<Typography>Cables</Typography>
|
||||
<Grid2 container>
|
||||
{cables.map(cable => {
|
||||
return(
|
||||
<Grid2 size={{xs: 6, xl: 4}}>
|
||||
{cableDisplay(cable)}
|
||||
</Grid2>
|
||||
)
|
||||
})}
|
||||
</Grid2>
|
||||
</React.Fragment>
|
||||
}
|
||||
{plenums.length }
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
<Typography>Controllers</Typography>
|
||||
{/* do all of the fans and heaters here */}
|
||||
|
||||
<Typography>Unassigned Components</Typography>
|
||||
{/* list the components that have no assignment on the bin */}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -14,12 +14,11 @@ import WheatImg from "assets/grain/wheat.jpg";
|
|||
import { Option } from "common/SearchSelect";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { Equation } from "./GrainMoisture";
|
||||
|
||||
export interface GrainExtension {
|
||||
name: string;
|
||||
group: string;
|
||||
equation: Equation;
|
||||
equation: pond.MoistureEquation;
|
||||
a: number;
|
||||
b: number;
|
||||
c: number;
|
||||
|
|
@ -38,7 +37,7 @@ const defaultSetTemp = 30.0;
|
|||
const defaultGrain: GrainExtension = {
|
||||
name: "None",
|
||||
group: "",
|
||||
equation: Equation.none,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_NONE,
|
||||
a: 0,
|
||||
b: 0,
|
||||
c: 0,
|
||||
|
|
@ -58,7 +57,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Custom Type",
|
||||
group: "",
|
||||
equation: Equation.none,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_NONE,
|
||||
a: 0,
|
||||
b: 0,
|
||||
c: 0,
|
||||
|
|
@ -75,7 +74,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Barley",
|
||||
group: "Barley",
|
||||
equation: Equation.chungPfost,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
||||
a: 475.12,
|
||||
b: 0.14843,
|
||||
c: 71.996,
|
||||
|
|
@ -93,7 +92,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Buckwheat",
|
||||
group: "Buckwheat",
|
||||
equation: Equation.chungPfost,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
||||
a: 103540000,
|
||||
b: 0.1646,
|
||||
c: 15853000,
|
||||
|
|
@ -111,7 +110,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Canola",
|
||||
group: "Canola",
|
||||
equation: Equation.halsey,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
||||
a: 3.489,
|
||||
b: -0.010553,
|
||||
c: 1.86,
|
||||
|
|
@ -130,7 +129,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Rapeseed",
|
||||
group: "Canola",
|
||||
equation: Equation.halsey,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
||||
a: 3.0026,
|
||||
b: -0.0048967,
|
||||
c: 1.7607,
|
||||
|
|
@ -148,7 +147,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Corn (Henderson)",
|
||||
group: "Corn",
|
||||
equation: Equation.henderson,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
||||
a: 0.000066612,
|
||||
b: 1.9677,
|
||||
c: 42.143,
|
||||
|
|
@ -166,7 +165,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Corn (Chung-Pfost)",
|
||||
group: "Corn",
|
||||
equation: Equation.chungPfost,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
||||
a: 374.34,
|
||||
b: 0.18662,
|
||||
c: 31.696,
|
||||
|
|
@ -184,7 +183,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Corn (Oswin)",
|
||||
group: "Corn",
|
||||
equation: Equation.oswin,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
||||
a: 15.303,
|
||||
b: -0.10164,
|
||||
c: 3.0358,
|
||||
|
|
@ -202,7 +201,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Maize White",
|
||||
group: "Corn",
|
||||
equation: Equation.henderson,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
||||
a: 0.000066612,
|
||||
b: 1.9677,
|
||||
c: 70.143,
|
||||
|
|
@ -220,7 +219,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Maize Yellow",
|
||||
group: "Corn",
|
||||
equation: Equation.henderson,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
||||
a: 0.000066612,
|
||||
b: 1.9677,
|
||||
c: 65.143,
|
||||
|
|
@ -238,7 +237,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Oats (Henderson)",
|
||||
group: "Oats",
|
||||
equation: Equation.henderson,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
||||
a: 0.000085511,
|
||||
b: 2.0087,
|
||||
c: 37.811,
|
||||
|
|
@ -256,7 +255,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Oats (Chung-Pfost)",
|
||||
group: "Oats",
|
||||
equation: Equation.chungPfost,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
||||
a: 442.85,
|
||||
b: 0.21228,
|
||||
c: 35.803,
|
||||
|
|
@ -274,7 +273,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Oats (Oswin)",
|
||||
group: "Oats",
|
||||
equation: Equation.oswin,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
||||
a: 12.412,
|
||||
b: -0.060707,
|
||||
c: 2.9397,
|
||||
|
|
@ -292,7 +291,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Peanuts",
|
||||
group: "Peanuts",
|
||||
equation: Equation.oswin,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
||||
a: 8.6588,
|
||||
b: -0.057904,
|
||||
c: 2.6204,
|
||||
|
|
@ -310,7 +309,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Long Grain Rice",
|
||||
group: "Rice",
|
||||
equation: Equation.henderson,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
||||
a: 0.000041276,
|
||||
b: 2.1191,
|
||||
c: 49.828,
|
||||
|
|
@ -328,7 +327,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Medium Grain Rice",
|
||||
group: "Rice",
|
||||
equation: Equation.henderson,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
||||
a: 0.000035502,
|
||||
b: 2.31,
|
||||
c: 27.396,
|
||||
|
|
@ -346,7 +345,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Short Grain Rice",
|
||||
group: "Rice",
|
||||
equation: Equation.henderson,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
||||
a: 0.000048524,
|
||||
b: 2.0794,
|
||||
c: 45.646,
|
||||
|
|
@ -364,7 +363,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Sorghum",
|
||||
group: "Sorghum",
|
||||
equation: Equation.chungPfost,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
||||
a: 797.33,
|
||||
b: 0.18159,
|
||||
c: 52.238,
|
||||
|
|
@ -382,7 +381,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Soybeans",
|
||||
group: "Soybeans",
|
||||
equation: Equation.chungPfost,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
||||
a: 228.2,
|
||||
b: 0.2072,
|
||||
c: 30,
|
||||
|
|
@ -400,7 +399,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Sunflower",
|
||||
group: "Sunflower",
|
||||
equation: Equation.henderson,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
||||
a: 0.00031,
|
||||
b: 1.7459,
|
||||
c: 66.603,
|
||||
|
|
@ -418,7 +417,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Durum Wheat",
|
||||
group: "Wheat",
|
||||
equation: Equation.oswin,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
||||
a: 13.101,
|
||||
b: -0.052626,
|
||||
c: 2.9987,
|
||||
|
|
@ -436,7 +435,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Hard Red Wheat",
|
||||
group: "Wheat",
|
||||
equation: Equation.chungPfost,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
||||
a: 610.34,
|
||||
b: 0.15526,
|
||||
c: 93.213,
|
||||
|
|
@ -454,7 +453,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Wheat SAWOS",
|
||||
group: "Wheat",
|
||||
equation: Equation.chungPfost,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
||||
a: 610.34,
|
||||
b: 0.15526,
|
||||
c: 93.213,
|
||||
|
|
@ -472,7 +471,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Un-retted Flax",
|
||||
group: "Flax",
|
||||
equation: Equation.halsey,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
||||
a: 5.11,
|
||||
b: Math.pow(-8.46 * 10, -3),
|
||||
c: 2.26,
|
||||
|
|
@ -490,7 +489,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Dew-retted Flax",
|
||||
group: "Flax",
|
||||
equation: Equation.oswin,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
||||
a: 6.5,
|
||||
b: Math.pow(-1.68 * 10, -2),
|
||||
c: 3.2,
|
||||
|
|
@ -508,7 +507,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Yellow Peas",
|
||||
group: "Peas",
|
||||
equation: Equation.oswin,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
||||
a: 14.81,
|
||||
b: -0.109,
|
||||
c: 3.019,
|
||||
|
|
@ -526,7 +525,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Blaze Lentils",
|
||||
group: "Lentils",
|
||||
equation: Equation.halsey,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
||||
a: 5.39,
|
||||
b: -0.015,
|
||||
c: 2.273,
|
||||
|
|
@ -544,7 +543,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Redberry Lentils",
|
||||
group: "Lentils",
|
||||
equation: Equation.halsey,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
||||
a: 4.749,
|
||||
b: -0.0116,
|
||||
c: 2.066,
|
||||
|
|
@ -562,7 +561,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Robin Lentils",
|
||||
group: "Lentils",
|
||||
equation: Equation.halsey,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
||||
a: 5.176,
|
||||
b: -0.0065,
|
||||
c: 2.337,
|
||||
|
|
@ -580,7 +579,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Dry Beans Red",
|
||||
group: "Dry Beans",
|
||||
equation: Equation.halsey,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
||||
a: 4.2669,
|
||||
b: -0.013382,
|
||||
c: 1.6933,
|
||||
|
|
@ -596,7 +595,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Dry Beans Black",
|
||||
group: "Dry Beans",
|
||||
equation: Equation.halsey,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
||||
a: 5.2003,
|
||||
b: -0.022685,
|
||||
c: 1.9656,
|
||||
|
|
@ -612,7 +611,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Rye (Henderson)",
|
||||
group: "Rye",
|
||||
equation: Equation.henderson,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
||||
a: 0.00006343,
|
||||
b: 2.2060,
|
||||
c: 13.1810,
|
||||
|
|
@ -628,7 +627,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Rye (Chung-Pfost)",
|
||||
group: "Rye",
|
||||
equation: Equation.chungPfost,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
||||
a: 461.0230,
|
||||
b: 0.1840,
|
||||
c: 36.7410,
|
||||
|
|
@ -644,7 +643,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Rye (Halsey)",
|
||||
group: "Rye",
|
||||
equation: Equation.halsey,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
||||
a: 4.2970,
|
||||
b: 0.380,
|
||||
c: 2.2710,
|
||||
|
|
@ -660,7 +659,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
{
|
||||
name: "Rye (Oswin)",
|
||||
group: "Rye",
|
||||
equation: Equation.oswin,
|
||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
||||
a: 11.8870,
|
||||
b: 0.0210,
|
||||
c: 3.2620,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,6 @@
|
|||
import { pond } from "protobuf-ts/pond";
|
||||
import GrainDescriber from "./GrainDescriber";
|
||||
|
||||
export enum Equation {
|
||||
"none",
|
||||
"oswin",
|
||||
"halsey",
|
||||
"henderson",
|
||||
"chungPfost"
|
||||
}
|
||||
|
||||
const toERH = (humidity: number): number => {
|
||||
return humidity >= 100 ? 0.99999 : humidity / 100;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export class Ambient {
|
|||
public status: pond.ComponentStatus = pond.ComponentStatus.create();
|
||||
public temperature: number = -127;
|
||||
public humidity: number = 200;
|
||||
public lastReading: string = ""
|
||||
|
||||
public static create(comp: Component): Ambient {
|
||||
let my = new Ambient();
|
||||
|
|
@ -18,6 +19,9 @@ export class Ambient {
|
|||
|
||||
if (comp.status.measurement.length > 0) {
|
||||
comp.status.measurement.forEach(um => {
|
||||
if (um.timestamps[0]){
|
||||
my.lastReading = um.timestamps[0]
|
||||
}
|
||||
if (um.values[0]) {
|
||||
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
|
||||
my.temperature = or(um.values[0].values[0], -127);
|
||||
|
|
@ -39,6 +43,7 @@ export class Ambient {
|
|||
hum = humAndTemp["relativeHumidityTimes100"];
|
||||
}
|
||||
}
|
||||
my.lastReading = comp.status.lastMeasurement?.timestamp ?? ""
|
||||
my.temperature = or(temp, -1270) / 10;
|
||||
my.humidity = or(hum, 20000) / 100;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,30 @@ export class Controller {
|
|||
public settings: pond.ComponentSettings = pond.ComponentSettings.create();
|
||||
public status: pond.ComponentStatus = pond.ComponentStatus.create();
|
||||
public on = false;
|
||||
public mode: quack.OutputMode | undefined
|
||||
public lastReading: string = "";
|
||||
|
||||
public static create(comp: Component): Controller {
|
||||
let my = new Controller();
|
||||
my.settings = comp.settings;
|
||||
my.status = comp.status;
|
||||
my.mode = comp.settings.defaultOutputState
|
||||
|
||||
my.on = Boolean(my.status.lastMeasurement?.measurement?.booleanOutput?.value);
|
||||
//this is deprecated because it is our old measurement structure
|
||||
// my.on = Boolean(my.status.lastMeasurement?.measurement?.booleanOutput?.value);
|
||||
|
||||
if (comp.status.lastGoodMeasurement.length > 0) {
|
||||
comp.status.measurement.forEach(um => {
|
||||
if (um.timestamps[0]){
|
||||
my.lastReading = um.timestamps[0]
|
||||
}
|
||||
if (um.values[0]) {
|
||||
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN) {
|
||||
my.on = Boolean(um.values[0].values[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return my;
|
||||
}
|
||||
|
|
@ -25,6 +42,7 @@ export class Controller {
|
|||
my.settings = comp.settings ? comp.settings : pond.ComponentSettings.create();
|
||||
my.status = comp.status ? comp.status : pond.ComponentStatus.create();
|
||||
|
||||
|
||||
my.on = Boolean(my.status.lastMeasurement?.measurement?.booleanOutput?.value);
|
||||
|
||||
return my;
|
||||
|
|
|
|||
|
|
@ -158,6 +158,10 @@ export class GrainCable {
|
|||
return this.humidities.reduce((p: any, c: any) => p + c, 0) / this.humidities.length;
|
||||
}
|
||||
|
||||
public aveMoisture() {
|
||||
return this.grainMoistures.reduce((p: any, c: any) => p + c, 0) / this.grainMoistures.length;
|
||||
}
|
||||
|
||||
public maxTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS) {
|
||||
if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
|
||||
return this.farenheit(Math.max(...this.temperatures));
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export class Headspace {
|
|||
public status: pond.ComponentStatus = pond.ComponentStatus.create();
|
||||
public temperature: number = -127;
|
||||
public humidity: number = 200;
|
||||
public lastReading: string = "";
|
||||
|
||||
public static create(comp: Component): Headspace {
|
||||
let my = new Headspace();
|
||||
|
|
@ -18,6 +19,9 @@ export class Headspace {
|
|||
|
||||
if (comp.status.measurement.length > 0) {
|
||||
comp.status.measurement.forEach(um => {
|
||||
if (um.timestamps[0]){
|
||||
my.lastReading = um.timestamps[0]
|
||||
}
|
||||
if (um.values[0]) {
|
||||
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
|
||||
my.temperature = um.values[0].values[0];
|
||||
|
|
@ -39,6 +43,7 @@ export class Headspace {
|
|||
hum = humAndTemp["relativeHumidityTimes100"];
|
||||
}
|
||||
}
|
||||
my.lastReading = comp.status.lastMeasurement?.timestamp ?? ""
|
||||
my.temperature = or(temp, -1270) / 10;
|
||||
my.humidity = or(hum, 20000) / 100;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export class Plenum {
|
|||
public status: pond.ComponentStatus = pond.ComponentStatus.create();
|
||||
public temperature: number = -127;
|
||||
public humidity: number = 200;
|
||||
public lastReading: string = ""
|
||||
|
||||
public static create(comp: Component): Plenum {
|
||||
let my = new Plenum();
|
||||
|
|
@ -18,6 +19,9 @@ export class Plenum {
|
|||
|
||||
if (comp.status.measurement.length > 0) {
|
||||
comp.status.measurement.forEach(um => {
|
||||
if (um.timestamps[0]) {
|
||||
my.lastReading = um.timestamps[0];
|
||||
}
|
||||
if (um.values[0]) {
|
||||
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
|
||||
my.temperature = or(um.values[0].values[0], -127);
|
||||
|
|
@ -41,6 +45,7 @@ export class Plenum {
|
|||
}
|
||||
my.temperature = or(temp, -1270) / 10;
|
||||
my.humidity = or(hum, 20000) / 100;
|
||||
my.lastReading = comp.status.lastMeasurement?.timestamp ?? ""
|
||||
}
|
||||
|
||||
return my;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ export class Pressure {
|
|||
public settings: pond.ComponentSettings = pond.ComponentSettings.create();
|
||||
public status: pond.ComponentStatus = pond.ComponentStatus.create();
|
||||
public pascals: number = 0;
|
||||
public airflow: number | undefined
|
||||
public fanId: number = 0;
|
||||
public lastReading: string = ""
|
||||
|
||||
public static create(comp: Component): Pressure {
|
||||
let my = new Pressure();
|
||||
|
|
@ -19,19 +21,23 @@ export class Pressure {
|
|||
//getting the value from the unitmeasurements in status instead of the old style measurements in the status
|
||||
if (comp.status.measurement.length > 0) {
|
||||
comp.status.measurement.forEach(um => {
|
||||
if (um.timestamps[0]){
|
||||
my.lastReading = um.timestamps[0]
|
||||
}
|
||||
if (um.values[0] && um.values[0].values.length > 0) {
|
||||
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE) {
|
||||
my.pascals = um.values[0].values[0];
|
||||
}
|
||||
//TODO-CS: could expand this to have the fan cfm in the pressure model as well
|
||||
// if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_CFM){
|
||||
// my.fanCFM = um.values[0].values[0]
|
||||
// }
|
||||
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_CFM){
|
||||
my.airflow = um.values[0].values[0]
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//if no unit measurements in status use the old measurements in status
|
||||
let pre = comp.status?.lastMeasurement?.measurement?.pressure?.pascals;
|
||||
my.lastReading = comp.status.lastMeasurement?.timestamp ?? ""
|
||||
if (pre) {
|
||||
my.pascals = pre;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,11 +215,6 @@ export default function Bin(props: Props) {
|
|||
const [binPresets, setBinPresets] = useState<DevicePreset[]>([]);
|
||||
const [missedReadings, setMissedReadings] = useState(0);
|
||||
|
||||
//3d bin variables/toggle
|
||||
const [showGrain, setShowGrain] = useState(false)
|
||||
const [showHotspots, setShowHotspots] = useState(false)
|
||||
const [showHeatmap, setShowHeatmap] = useState(false)
|
||||
|
||||
const handleChange = (_event: React.ChangeEvent<{}>, newValue: number) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -48,6 +48,15 @@ function makePaletteColor(name: keyof typeof Colours) {
|
|||
|
||||
export const getTheme = (mode: 'light' | 'dark') =>
|
||||
createTheme({
|
||||
components: {
|
||||
MuiPaper: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundImage: 'none', // disables MUI's elevation overlay in dark mode
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...baseTheme,
|
||||
palette: {
|
||||
...baseTheme.palette,
|
||||
|
|
@ -57,8 +66,8 @@ export const getTheme = (mode: 'light' | 'dark') =>
|
|||
...(mode === 'dark'
|
||||
? {
|
||||
background: {
|
||||
default: '#121212',
|
||||
paper: '#1e1e1e',
|
||||
default: '#0A1118', // was #0F1923 — push it darker
|
||||
paper: '#141F2A', // was #1A2530 — slightly darker, less grey/blue
|
||||
},
|
||||
}
|
||||
: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue