diff --git a/src/bin/BinActions.tsx b/src/bin/BinActions.tsx index d50fedb..6331524 100644 --- a/src/bin/BinActions.tsx +++ b/src/bin/BinActions.tsx @@ -28,7 +28,7 @@ import ObjectTeams from "teams/ObjectTeams"; // import BinDuplication from "./BinDuplication"; import BinsIcon from "products/Bindapt/BinsIcon"; import HelpIcon from "@mui/icons-material/Help"; -// import BinSensors from "./BinSensors"; +import BinSensors from "./BinSensors"; import { useGlobalState, useSnackbar, useUserAPI } from "providers"; import { useMobile } from "hooks"; import { makeStyles } from "@mui/styles"; @@ -239,7 +239,7 @@ export default function BinActions(props: Props) { setOpenState({ ...openState, settings: false }); }} /> - {/* */} + /> { + return ({ + bgItem: { + background: darken(theme.palette.background.paper, getThemeType() === "light" ? 0.05 : 0.25), + position: "relative", + width: "auto", + borderRadius: theme.spacing(0.5), + display: "flex", + flexDirection: "column", + padding: theme.spacing(0.5) + }, + avatar: { + width: theme.spacing(3), + height: theme.spacing(3), + marginTop: "auto", + marginBottom: "auto" + }, + avatarIcon: { + background: "transparent", + width: theme.spacing(3), + height: theme.spacing(3), + margin: "auto", + marginBottom: "auto" + }, + spacingItem: { + paddingLeft: theme.spacing(0.75), + paddingRight: theme.spacing(1), + display: "flex", + flexDirection: "row", + justifyContent: "center" + }, + pointerLeft: { + borderBottom: "1px solid", + borderLeft: "1px solid", + borderColor: theme.palette.text.primary, + width: theme.spacing(5.5), + height: theme.spacing(0.7) + }, + pointerRight: { + borderBottom: "1px solid white", + borderLeft: "1px solid white", + borderRight: "1px solid white", + borderColor: theme.palette.text.primary, + width: theme.spacing(5.5), + height: theme.spacing(0.7) + }, + spacer: { + paddingLeft: theme.spacing(0.25) + }, + smallFont: { + fontSize: "10px" + }, + bigSpacer: { + paddingLeft: theme.spacing(3.2) + } + }); +}); + +interface Props { + bin: string; + components: Map; + preferences: Map; + setPreferences: React.Dispatch< + React.SetStateAction | undefined> + >; + updateBinStatus: (componentKeys: string[], removed?: boolean) => void; + setComponents: React.Dispatch>>; +} + +export default function BinComponentTypes(props: Props) { + const { bin, components, preferences, setPreferences, updateBinStatus } = props; + const classes = useStyles(); + const theme = useTheme(); + const binAPI = useBinAPI(); + const snackbar = useSnackbar(); + const [anchorEl, setAnchorEl] = React.useState(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 [unassigned, setUnassigned] = useState([]); + const [plenums, setPlenums] = useState([]); + const [ambients, setAmbients] = useState([]); + const [pressures, setPressures] = useState([]); + const [grainCables, setGrainCables] = useState([]); + const [headspace, setHeadspace] = useState([]); + const [heaters, setHeaters] = useState([]); + const [fans, setFans] = useState([]); + const [lidars, setLidars] = useState([]); + const [headspaceCo2s, setHeadspaceCo2s] = useState([]); + const [tempUnit, setTempUnit] = useState(); + const [pressureUnit, setPressureUnit] = useState(); + const [selectedComponentKey, setSelectedComponentKey] = useState(""); + const [selectedComponentSubtype, setSelectedComponentSubtype] = useState(0); + const [selectedComponentTopNode, setSelectedComponentTopNode] = useState(0); + const [{ user }] = useGlobalState(); + + useEffect(() => { + if (user.settings.temperatureUnit) { + setTempUnit(user.settings.temperatureUnit); + } else { + setTempUnit(pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS); + } + if (user.settings.pressureUnit) { + setPressureUnit(user.settings.pressureUnit); + } else { + setPressureUnit(pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER); + } + }, [user]); + + const selectType = useCallback( + (component: string, type: pond.BinComponent, topNode?: number) => { + setAnchorEl(null); + let p = preferences.get(component); + if (p) { + p.type = type; + p.node = topNode ?? 0; + binAPI + .updateComponentPreferences(bin, component, p) + .then(() => { + snackbar.success("Component preferences updated"); + preferences.set(component, p!); + setPreferences(new Map(preferences)); + }) + .catch(() => { + snackbar.error("Component preference update failed"); + }); + } + }, + [bin, binAPI, preferences, setPreferences, snackbar] + ); + + useEffect(() => { + var plenums: Plenum[] = []; + var grainCables: GrainCable[] = []; + var heaters: Controller[] = []; + var fans: Controller[] = []; + var pressures: Pressure[] = []; + var headspace: Headspace[] = []; + var lidar: Component[] = []; + var unassigned: Component[] = []; + var ambients: Ambient[] = []; + var headspaceCo2s: Component[] = []; + components.forEach(comp => { + let pref = preferences.get(comp.key()); + if (pref) { + if (pref.type) { + if (pref.type === pond.BinComponent.BIN_COMPONENT_PLENUM) + plenums.push(Plenum.create(comp)); + if (pref.type === pond.BinComponent.BIN_COMPONENT_AMBIENT) + ambients.push(Ambient.create(comp)); + if (pref.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE) + grainCables.push(GrainCable.create(comp)); + if (pref.type === pond.BinComponent.BIN_COMPONENT_HEATER) + heaters.push(Controller.create(comp)); + if (pref.type === pond.BinComponent.BIN_COMPONENT_FAN) fans.push(Controller.create(comp)); + if (pref.type === pond.BinComponent.BIN_COMPONENT_PRESSURE) + pressures.push(Pressure.create(comp)); + if (pref.type === pond.BinComponent.BIN_COMPONENT_HEADSPACE) { + if (comp.type() === quack.ComponentType.COMPONENT_TYPE_DHT) { + headspace.push(Headspace.create(comp)); + } else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR) { + lidar.push(comp); + } else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_CO2) { + headspaceCo2s.push(comp); + } + } + } else { + unassigned.push(comp); + } + } else { + unassigned.push(comp); + } + }); + setPlenums(plenums); + setAmbients(ambients); + setGrainCables(grainCables); + setHeaters(heaters); + setFans(fans); + setPressures(pressures); + setHeadspace(headspace); + setLidars(lidar); + setHeadspaceCo2s(headspaceCo2s); + setUnassigned(unassigned); + }, [components, preferences, setPlenums, setGrainCables, setHeaters, selectType, setPressures]); + + const getOptions = () => { + let options: JSX.Element[] = []; + + if (!componentUnassigned) { + options.push( + { + selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_UNKNOWN); + updateBinStatus([selectedComponentKey], true); + }}> + Remove + + ); + } + if ( + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_DHT || + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE + ) { + options.push( + { + selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_AMBIENT); + }}> + Ambient + + ); + options.push( + { + selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_PLENUM); + }}> + Plenum + + ); + } + if ( + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_DHT || + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_LIDAR || + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_CO2 + ) { + options.push( + { + selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_HEADSPACE); + updateBinStatus([selectedComponentKey]); + }}> + Headspace + + ); + } + if ( + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE || + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE + ) { + options.push( + { + selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_PRESSURE); + }}> + Plenum Pressure + + ); + } + if (selectedComponentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) { + options.push( + { + selectType( + selectedComponentKey, + pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE, + selectedComponentTopNode + ); + }}> + Grain Cable + + ); + } + if (selectedComponentType === quack.ComponentType.COMPONENT_TYPE_BOOLEAN_OUTPUT) { + options.push( + { + 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 + + ); + } + + if (options.length === 0) { + return No Available Options; + } else { + return options; + } + }; + + const assignmentMenu = () => { + return ( + setAnchorEl(null)} + keepMounted + disableAutoFocusItem> + {getOptions()} + + ); + }; + + const grainCableList = () => { + if (grainCables.length < 1) return null; + return ( + + + Grain Cables + + + {grainCables.map((cable, index) => { + let cIcon = GetComponentIcon( + cable.settings.type, + cable.settings.subtype, + theme.palette.mode + ); + return ( + + + + + {cable.name()} + + + + + + + + {tempUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT + ? "°F" + : "°C"} + + + + + {isFinite(cable.minTemp(tempUnit)) && !isNaN(cable.minTemp(tempUnit)) + ? cable.minTemp(tempUnit).toFixed(1) + : "--"} + + + {isFinite(cable.aveTemp(tempUnit)) && !isNaN(cable.aveTemp(tempUnit)) + ? cable.aveTemp(tempUnit).toFixed(1) + : "--"} + + + {isFinite(cable.maxTemp(tempUnit)) && !isNaN(cable.maxTemp(tempUnit)) + ? cable.maxTemp(tempUnit).toFixed(1) + : "--"} + + + + + + + + + + % + + + + + {isFinite(cable.minHumidity()) && !isNaN(cable.minHumidity()) + ? cable.minHumidity().toFixed(1) + : "--"} + + + {isFinite(cable.aveHumidity()) && !isNaN(cable.aveHumidity()) + ? cable.aveHumidity().toFixed(1) + : "--"} + + + {isFinite(cable.maxHumidity()) && !isNaN(cable.maxHumidity()) + ? cable.maxHumidity().toFixed(1) + : "--"} + + + + + + + ) => { + setComponentUnnassigned(false); + setSelectedComponentType(cable.type()); + setSelectedComponentKey(cable.key()); + setSelectedComponentTopNode(cable.settings.grainFilledTo); + setAnchorEl(event.currentTarget); + }}> + + + + + + + ); + })} + + ); + }; + + const controllerList = (heaters: Controller[], fans: Controller[]) => { + if (heaters.length < 1 && fans.length < 1) return null; + let controllers = heaters.concat(fans); + return ( + + + Heaters & Fans + + + {controllers.map((controller, index) => { + let cIcon = GetComponentIcon( + controller.settings.type, + controller.settings.subtype, + theme.palette.mode + ); + return ( + + + + + {controller.name()} + + + + + + + {controller.on ? "On" : "Off"} + + + + + + ) => { + setComponentUnnassigned(false); + setSelectedComponentType(controller.type()); + setSelectedComponentSubtype(controller.subType()); + setSelectedComponentKey(controller.key()); + setAnchorEl(event.currentTarget); + }}> + + + + + + + ); + })} + + ); + }; + + const pressureList = () => { + if (pressures.length < 1) return null; + return ( + + + Plenum Pressure + + + {pressures.map((component, index) => { + let cIcon = GetComponentIcon( + component.settings.type, + component.settings.subtype, + theme.palette.mode + ); + return ( + + + + + {component.name()} + + + + + + + {component.getPressureString(pressureUnit)} + + + + + + ) => { + setComponentUnnassigned(false); + setSelectedComponentType(component.type()); + setSelectedComponentKey(component.key()); + setAnchorEl(event.currentTarget); + }}> + + + + + + + ); + })} + + ); + }; + + const plenumList = (plenums: Plenum[]) => { + if (plenums.length < 1) return null; + return ( + + + Plenums + + + {plenums.map((component, index) => { + let cIcon = GetComponentIcon( + component.settings.type, + component.settings.subtype, + theme.palette.mode + ); + return ( + + + + + {component.name()} + + + + + + + {component.getTempString(user.settings.temperatureUnit) + ","} + + + {component.getHumidityString()} + + + + + + ) => { + setComponentUnnassigned(false); + setSelectedComponentType(component.type()); + setSelectedComponentKey(component.key()); + setAnchorEl(event.currentTarget); + }}> + + + + + + + ); + })} + + ); + }; + + const ambientList = (ambients: Ambient[]) => { + if (ambients.length < 1) return null; + return ( + + + Ambient + + + {ambients.map((component, index) => { + let cIcon = GetComponentIcon( + component.settings.type, + component.settings.subtype, + theme.palette.mode + ); + return ( + + + + + {component.name()} + + + + + + + {component.getTempString(user.settings.temperatureUnit) + ","} + + + {component.getHumidityString()} + + + + + + ) => { + setComponentUnnassigned(false); + setSelectedComponentType(component.type()); + setSelectedComponentKey(component.key()); + setAnchorEl(event.currentTarget); + }}> + + + + + + + ); + })} + + ); + }; + + const formatDistance = (distanceCM: number) => { + if (user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS) { + return distanceCM / 100 + "m"; + } else if (user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET) { + return (distanceCM / 30.48).toFixed(2) + "ft"; + } + return distanceCM + "cm"; + }; + + const headspaceList = (sensors: Headspace[], lidars: Component[], headspaceCo2s: Component[]) => { + if (sensors.length < 1 && lidars.length < 1 && headspaceCo2s.length < 1) return null; + let headspaceComponents = lidars.concat(headspaceCo2s); + return ( + + + Headspace + + + {sensors.map((component, index) => { + let cIcon = GetComponentIcon( + component.settings.type, + component.settings.subtype, + theme.palette.mode + ); + return ( + + + + + {component.name()} + + + + + + + + {component.getTempString(user.settings.temperatureUnit) + ","} + + + {component.getHumidityString()} + + + + + + ) => { + setComponentUnnassigned(false); + setSelectedComponentType(component.type()); + setSelectedComponentKey(component.key()); + setAnchorEl(event.currentTarget); + }}> + + + + + + + ); + })} + {headspaceComponents.map((component, index) => { + let cIcon = GetComponentIcon( + component.settings.type, + component.settings.subtype, + theme.palette.mode + ); + + let colour = "white"; //default colour if there are no measurements to get the type from + + let dString = "--"; + if ( + component.status.measurement[0] && + component.status.measurement[0].values[0] && + component.status.measurement[0].values[0].values[0] + ) { + colour = describeMeasurement( + component.status.measurement[0].type, + component.type(), + component.subType() + ).colour(); + dString = formatDistance(component.status.measurement[0].values[0].values[0]); + } + + return ( + + + + + {component.name()} + + + + + + + + {dString} + + + + + + ) => { + setComponentUnnassigned(false); + setSelectedComponentType(component.type()); + setSelectedComponentKey(component.key()); + setAnchorEl(event.currentTarget); + }}> + + + + + + + ); + })} + + ); + }; + + const unassignedList = (sensors: Component[]) => { + if (sensors.length < 1) return null; + return ( + + + Unassigned Components + + + {sensors.map((component, index) => { + let cIcon = GetComponentIcon( + component.settings.type, + component.settings.subtype, + theme.palette.mode + ); + return ( + + + + + {component.name()} + + ) => { + setComponentUnnassigned(true); + setSelectedComponentType(component.type()); + setSelectedComponentSubtype(component.subType()); + setSelectedComponentKey(component.key()); + setAnchorEl(event.currentTarget); + }}> + + + + + ); + })} + + ); + }; + + const noComponents = () => { + return ( + + + No attached components. + + + Go to settings to assign components from your devices. + + + ); + }; + + if (components.size < 1) return noComponents(); + + return ( + + Bin Components} + style={{ padding: theme.spacing(1), width: "100%" }}> + {plenumList(plenums)} + {ambientList(ambients)} + {headspaceList(headspace, lidars, headspaceCo2s)} + {grainCableList()} + {controllerList(heaters, fans)} + {pressureList()} + {unassignedList(unassigned)} + {assignmentMenu()} + + + ); +} diff --git a/src/bin/BinComponents.tsx b/src/bin/BinComponents.tsx new file mode 100644 index 0000000..0bc9e63 --- /dev/null +++ b/src/bin/BinComponents.tsx @@ -0,0 +1,605 @@ +import { + Autocomplete, + Avatar, + Box, + Button, + CircularProgress, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Divider, + Grid2 as Grid, + IconButton, + List, + ListItem, + ListItemAvatar, + ListItemButton, + ListItemSecondaryAction, + ListItemText, + ListSubheader, + Tab, + Tabs, + TextField, + Theme, + Tooltip, + Typography, + useTheme +} from "@mui/material"; +import { Component, Device } from "models"; +import { pond } from "protobuf-ts/pond"; +import React, { useCallback, useEffect, useState } from "react"; +import { useComponentAPI, useDeviceAPI, useSnackbar } from "hooks"; +// import { Autocomplete } from "@material-ui/lab"; +import { useBinAPI } from "providers"; +import { GetComponentIcon } from "pbHelpers/ComponentType"; +import { CheckBox as CheckBoxIcon, CheckBoxOutlineBlank, Remove } from "@mui/icons-material"; +import ResponsiveDialog from "common/ResponsiveDialog"; +import { quack } from "protobuf-ts/quack"; +import { GetProductDefaults } from "products/DeviceProduct"; +import DeviceWizard from "device/DeviceWizard"; +import { makeStyles } from "@mui/styles"; + +const useStyles = makeStyles((theme: Theme) => { + return ({ + stepper: { + padding: theme.spacing(0.5) + }, + secondaryColor: { + color: theme.palette.secondary.main + }, + textSecondaryColor: { + color: theme.palette.text.secondary + }, + bottomSpacing: { + marginBottom: theme.spacing(1) + }, + tabs: { + width: "100%", + boxSizing: "border-box", + flexShrink: 1 + }, + tabSmall: { + width: "100%", + boxSizing: "border-box", + flexShrink: 1, + minWidth: "48px", + marginTop: 0, + paddingTop: 0 + } + }) +}); + +interface Props { + components?: Map; + setComponents?: React.Dispatch>>; + bin: string; + binGrain: pond.Grain; + updateBinStatus?: (componentKeys: string[], removed?: boolean) => void; +} + +interface Option { + label: string; + id: number; + icon?: string; +} + +export default function BinComponents(props: Props) { + const { components, bin, setComponents, updateBinStatus, binGrain } = props; + const classes = useStyles(); + const binAPI = useBinAPI(); + const theme = useTheme(); + const deviceAPI = useDeviceAPI(); + const componentAPI = useComponentAPI(); + const snackbar = useSnackbar(); + const [devicesLoading, setDevicesLoading] = useState(false); + const [componentsLoading, setComponentsLoading] = useState(false); + const [deviceOptions, setDeviceOptions] = useState([]); + const [devMap, setDevMap] = useState>(new Map()); + const [selectedDevice, setSelectedDevice] = useState(-1); + const [deviceComponents] = useState>(new Map()); + const [activeStep, setActiveStep] = useState(0); + const [componentToRemove, setComponentToRemove] = useState(undefined); + const [removeAllDialog, setRemoveAllDialog] = useState(false); + const [wizardOpen, setWizardOpen] = useState(false); + const [setupAllowed, setSetupAllowed] = useState(false); + + const loadDevices = useCallback(() => { + let mounted = true; + setDevicesLoading(true); + deviceAPI + .list(100000, 0, "asc", "name") + .then(response => { + if (mounted) { + let data = response.data.devices ? response.data.devices : []; + let newDevMap: Map = new Map(); + let devices: Option[] = data.map(d => { + let device = Device.create(pond.Device.fromObject(d)); + newDevMap.set(device.id(), device); + return { id: device.id(), label: device.name() } as Option; + }); + setDevMap(newDevMap); + setDeviceOptions(devices); + } + }) + .catch(() => { + if (mounted) { + setDeviceOptions([]); + } + }) + .finally(() => { + if (mounted) { + setDevicesLoading(false); + } + }); + return () => { + mounted = false; + }; + }, [deviceAPI]); + + useEffect(() => { + loadDevices(); + }, [loadDevices]); + + const loadComponents = useCallback( + (forceLoad?: boolean) => { + if (selectedDevice < 1) return; + let comps = deviceComponents.get(selectedDevice); + if (comps === undefined || forceLoad) { + comps = []; + setComponentsLoading(true); + componentAPI + .list(selectedDevice, false, [selectedDevice.toString()], ["device"], true) + .then(resp => { + let d = pond.ListComponentsResponse.fromObject(resp.data); + d.components.forEach(comp => { + // Don't show modems + if (comp.settings?.type === quack.ComponentType.COMPONENT_TYPE_MODEM) return; + let c = Component.create(comp); + if (c.permissions.includes(pond.Permission.PERMISSION_WRITE)) { + setSetupAllowed(true); + } + comps!.push(c); + }); + if (comps) deviceComponents.set(selectedDevice, comps); + }) + .catch(err => { + snackbar.error(err); + }) + .finally(() => { + setComponentsLoading(false); + }); + } + }, + [selectedDevice, componentAPI, deviceComponents, snackbar] + ); + + useEffect(() => { + loadComponents(); + }, [loadComponents]); + + // useEffect(() => { + // if (selectedDevice < 1) return; + // let comps = deviceComponents.get(selectedDevice); + // if (comps === undefined) { + // comps = []; + // setComponentsLoading(true); + // componentAPI + // .list(selectedDevice, false, [selectedDevice.toString()], ["device"], true) + // .then(resp => { + // let d = pond.ListComponentsResponse.fromObject(resp.data); + // d.components.forEach(comp => { + // // Don't show modems + // if (comp.settings?.type === quack.ComponentType.COMPONENT_TYPE_MODEM) return; + // let c = Component.create(comp) + // if(c.permissions.includes(pond.Permission.PERMISSION_WRITE)){ + // setSetupAllowed(true) + // } + // comps!.push(c); + // }); + // if (comps) deviceComponents.set(selectedDevice, comps); + // }) + // .catch(err => { + // snackbar.error(err); + // }) + // .finally(() => { + // setComponentsLoading(false); + // }); + // } + // }, [selectedDevice, componentAPI, deviceComponents, snackbar]); + + const removeComponent = (component: string) => { + binAPI.removeComponent(bin, component).then(() => { + if (components && setComponents) { + if (components.delete(component)) { + let newComponents = new Map(components); + setComponents(newComponents); + } + } + snackbar.info("Component removed from bin"); + }); + setComponentToRemove(undefined); + }; + + const removeComponentConfirmation = () => { + return ( + setComponentToRemove(undefined)}> + Remove {componentToRemove?.name()}? + + + This will remove {componentToRemove?.name()} from this bin. If you don't have direct + access to this component or the device it is attached to, you will not be able to add it + back. + + + + setComponentToRemove(undefined)} color="primary"> + Cancel + + removeComponent(componentToRemove!.key())} color="primary"> + Remove + + + + ); + }; + + const removeAllConfirmation = () => { + return ( + setRemoveAllDialog(false)}> + Remove All Components? + + + This will remove All attached components from this bin. If you don't have direct access + to these components or the devices they are attached to, you will not be able to add + them back. + + + + setRemoveAllDialog(false)} color="primary"> + Cancel + + removeAll()} color="primary"> + Remove + + + + ); + }; + + const determinePreset = (comp: Component, devProduct?: pond.DeviceProduct) => { + let preset = pond.BinComponent.BIN_COMPONENT_UNKNOWN; + if (devProduct) { + let defaults = GetProductDefaults(devProduct); + let plenumAddresses: string[] | undefined = defaults?.get("plenum"); + let headspaceAddresses: string[] | undefined = defaults?.get("headspace"); + let pressureAddresses: string[] | undefined = defaults?.get("pressure"); + let heatersFansAddresses: string[] | undefined = defaults?.get("heatersFans"); + if (plenumAddresses && plenumAddresses.includes(comp.locationString())) { + preset = pond.BinComponent.BIN_COMPONENT_PLENUM; + } else if (pressureAddresses && pressureAddresses.includes(comp.locationString())) { + preset = pond.BinComponent.BIN_COMPONENT_PRESSURE; + } else if (headspaceAddresses && headspaceAddresses.includes(comp.locationString())) { + preset = pond.BinComponent.BIN_COMPONENT_HEADSPACE; + } else if (heatersFansAddresses && heatersFansAddresses.includes(comp.locationString())) { + switch (comp.subType()) { + case quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER: + preset = pond.BinComponent.BIN_COMPONENT_HEATER; + break; + case quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN: + case quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN: + preset = pond.BinComponent.BIN_COMPONENT_FAN; + break; + } + } else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) { + //grain cables should be treated as cables by default no matter what the address is + preset = pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE; + } // else if ("is a pressure cable"){} + } + return preset; + }; + + const toggleComponent = (device: number, component: Component, checked: boolean) => { + if (checked) { + let devObj = devMap.get(device); + let preset = determinePreset(component, devObj?.settings.product); + let preferences = pond.BinComponentPreferences.create({ + type: preset, + node: component.settings.grainFilledTo ?? 0 + }); + binAPI.addComponent(bin, device, component.key(), preferences).then(resp => { + if (components && setComponents) { + if (components.set(component.key(), component)) { + let newComponents = new Map(components); + setComponents(newComponents); + } + } + //if a grain cable was added to the bin update the components grain type to match the bin + if (preferences.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE) { + let settings = component.settings; + settings.grainType = binGrain; + settings.defaultMutations = [pond.Mutator.MUTATOR_EMC]; + componentAPI + .update(device, settings) + .then(resp => { + snackbar.info("Component added to bin, and updated cables grain type"); + }) + .catch(err => { + snackbar.info("Component added to bin, but failed to update grain type on cable"); + }); + } else { + snackbar.info("Component added to bin"); + } + }); + } else { + binAPI.removeComponent(bin, component.key()).then(resp => { + if (components && setComponents) { + if (components.delete(component.key())) { + let newComponents = new Map(components); + setComponents(newComponents); + } + } + snackbar.info("Component removed from bin"); + }); + } + }; + + const removeAll = () => { + binAPI.removeAllComponents(bin).then(resp => { + if (setComponents) { + setComponents(new Map()); + } + if (components && updateBinStatus) { + updateBinStatus(Array.from(components.keys()), true); + } + snackbar.info("All components removed from bin"); + setRemoveAllDialog(false); + }); + }; + + const deviceComponentsDisplay = (device: number) => { + if (deviceComponents.get(device) && deviceComponents.get(device)!.length < 1) { + return ( + + No Sensors + + ); + } + return ( + + {deviceComponents.get(device)?.map((comp, index) => { + let cIcon = GetComponentIcon( + comp.settings.type, + comp.settings.subtype, + theme.palette.mode + ); + let share = comp.permissions.includes(pond.Permission.PERMISSION_SHARE); + + return ( + + + + + toggleComponent(device, comp, components?.get(comp.key()) === undefined) + }> + {cIcon && ( + + + + )} + + + {comp.name()} + + {components?.get(comp.key()) !== undefined ? ( + + ) : ( + + )} + + + + + + + + + ); + })} + + ); + }; + + const wizardDialog = () => { + let device = devMap.get(selectedDevice); + let components = deviceComponents.get(selectedDevice); + if (device && components) { + return ( + { + setWizardOpen(false); + }}> + { + loadComponents(true); + }} + /> + + ); + } + return; + }; + + const componentsByDevice = () => { + return ( + + + Bin Sensors + + + option.label || ""} + onChange={(_, newValue) => { + setSelectedDevice(newValue ? newValue.id : -1); + }} + renderInput={params => } + loading={devicesLoading} + /> + {selectedDevice < 0 ? ( + + Select a device + + ) : componentsLoading ? ( + + ) : ( + + {setupAllowed && ( + { + setWizardOpen(true); + }} + style={{ marginTop: 15 }}> + Device Setup + + )} + + + Choose Sensors + + + + ) + }> + {deviceComponentsDisplay(selectedDevice)} + + + )} + + + ); + }; + + const attachedComponents = () => { + if (components === undefined) return; + let comps = [...components.values()]; + return ( + + { + setRemoveAllDialog(true); + }}> + Remove All Components + + + + This Bin's Sensors + + + {comps.length < 1 && ( + + No Sensors + + )} + {comps.map((component, index) => { + let cIcon = GetComponentIcon( + component.settings.type, + component.settings.subtype, + theme.palette.mode + ); + return ( + + + + + + {component.name()} + + setComponentToRemove(component)}> + + + + + + + ); + })} + + + ); + }; + + return ( + + setActiveStep(value)} + indicatorColor="primary" + textColor="primary" + variant="fullWidth" + aria-label="bin tabs" + classes={{ root: classes.tabSmall }}> + + + + + {activeStep === 0 ? componentsByDevice() : attachedComponents()} + + {removeComponentConfirmation()} + {removeAllConfirmation()} + {wizardDialog()} + + ); +} diff --git a/src/bin/BinSensors.tsx b/src/bin/BinSensors.tsx new file mode 100644 index 0000000..f1c81ac --- /dev/null +++ b/src/bin/BinSensors.tsx @@ -0,0 +1,145 @@ +import { + AppBar, + Box, + Button, + DialogActions, + DialogTitle, + Grid2 as Grid, + IconButton, + Toolbar, + Typography +} from "@mui/material"; +import { Close } from "@mui/icons-material"; +import ResponsiveDialog from "common/ResponsiveDialog"; +import { useMobile } from "hooks"; +import { Bin, Component } from "models"; +import { pond } from "protobuf-ts/pond"; +import React, { useEffect, useState } from "react"; +import BinComponents from "./BinComponents"; + +type Mode = "add" | "update" | "remove"; + +interface Props { + open: boolean; + onClose: (refresh?: boolean) => void; + mode?: Mode; + bin?: Bin; + canEdit: boolean; + openedBinYard?: string; + userID: string; + coords?: { longitude: number; latitude: number }; + binYards?: pond.BinYardSettings[]; + components?: Map; + setComponents?: React.Dispatch>>; + updateBinStatus?: (componentKeys: string[], removed?: boolean) => void; +} + +export default function BinSensors(props: Props) { + const isMobile = useMobile(); + const { + open, + bin, + onClose, + mode, + openedBinYard, + components, + setComponents, + updateBinStatus + } = props; + const [initialized, setInitialized] = useState(false); + + useEffect(() => { + if (open) { + setInitialized(true); + } else { + setInitialized(false); + } + }, [bin, open, mode, openedBinYard]); + + const close = (refresh?: boolean) => { + onClose(refresh); + }; + + const titleText = () => { + return "Add Sensors to " + (bin ? bin.name() : "Bin"); + }; + + const title = () => { + if (isMobile) { + return ( + + + close()} aria-label="close"> + + + + {titleText()} + + + + ); + } + + return ( + + {titleText()} + + ); + }; + + const componentsForm = () => { + if (bin) { + return ( + + + + ); + } + + return null; + }; + + const actions = () => { + return ( + + + + {!isMobile && ( + + close()} aria-label="Cancel"> + Done + + + )} + + + + ); + }; + + if (!open || !initialized) { + return null; + } + + return ( + { + onClose(); + }}> + {title()} + {componentsForm()} + {actions()} + + ); +} diff --git a/src/bin/BinTour.tsx b/src/bin/BinTour.tsx index aa356c9..a685ada 100644 --- a/src/bin/BinTour.tsx +++ b/src/bin/BinTour.tsx @@ -156,8 +156,6 @@ export default function BinTour() { return steps; }; - console.log(user.status.finishedBinIntro) - // if this intro has been done, return null if (user.status.finishedBinIntro.length > 1) return null; diff --git a/src/device/DeviceWizard.tsx b/src/device/DeviceWizard.tsx new file mode 100644 index 0000000..33da9bf --- /dev/null +++ b/src/device/DeviceWizard.tsx @@ -0,0 +1,267 @@ +import { + Avatar, + Box, + Card, + CardContent, + CardHeader, + List, + ListItem, + ListItemIcon, + ListItemText, + ListSubheader, + Menu, + MenuItem, + Theme, + Tooltip, + Typography +} from "@mui/material"; +import ComponentSettings from "component/ComponentSettings"; +import { Component, Device } from "models"; +import { GetComponentIcon } from "pbHelpers/ComponentType"; +import { + DeviceAvailabilityMap, + FindAvailablePositions, + OffsetAvailabilityMap +} from "pbHelpers/DeviceAvailability"; +import { GetDeviceProductIcon, GetDeviceProductLabel } from "products/DeviceProduct"; +import { pond } from "protobuf-ts/pond"; +import { quack } from "protobuf-ts/quack"; +import { useComponentAPI, useSnackbar } from "providers"; +import React, { useState } from "react"; +import DeviceSVG, { PortInformation } from "./deviceSVG"; +import { makeStyles } from "@mui/styles"; + +interface Props { + device: Device; + //permissions: pond.Permission[]; + components: Component[]; + refreshCallback: () => void; +} + +const useStyles = makeStyles((theme: Theme) => { + return ({ + card: { + // position: "relative", + // display: "flex", + // height: "100%", + // flexDirection: "column", + // overflow: "visible" + minHeight: "200px" + }, + cardHeader: { + padding: theme.spacing(1), + paddingLeft: theme.spacing(2) + }, + cardContent: { + display: "flex", + flexDirection: "column", + justifyContent: "space-between", + alignItems: "center", + padding: theme.spacing(1), + paddingTop: 0 + }, + avatarIcon: { + width: theme.spacing(3), + height: theme.spacing(3) + } + }) +}); + +export default function DeviceWizard(props: Props) { + const { device, components, refreshCallback } = props; + const [componentDialogOpen, setComponentDialogOpen] = useState(false); + const [availableOffsets, setAvailableOffsets] = useState(new Map()); + const [availablePositions, setAvailablePositions] = useState(new Map()); + const compAPI = useComponentAPI(); + const [menuAnchor, setMenuAnchor] = useState(null); + const [portComponents, setPortComponents] = useState([]); + const [componentToUpdate, setComponentToUpdate] = useState(); + const { error, success } = useSnackbar(); + const [ComponentSettingsMode, setComponentSettingsMode] = useState<"add" | "remove" | "update">( + "add" + ); + const classes = useStyles(); + const [selectedPort, setSelectedPort] = useState(); + //const [addressType, setAddressType] = useState(quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY); + + const wizardMenu = () => { + return ( + setMenuAnchor(null)} + keepMounted + disableAutoFocusItem> + { + setComponentSettingsMode("add"); + setComponentToUpdate(undefined); + setComponentDialogOpen(true); + }}> + Add Component + + {/* { + }}> + List Errors + */} + {selectedPort && selectedPort.autoDetectable && ( + + { + setAutoDetect(); + }}> + Auto Detect + + + )} + {portComponents.length > 0 && ( + + Current Components + {portComponents.map(comp => ( + { + setComponentToUpdate(comp); + setComponentSettingsMode("update"); + setComponentDialogOpen(true); + }}> + + + + {comp.name()} + + ))} + + )} + + ); + }; + + /** + * sets an autodetect component on the port that was selected + * TODO: when the auto detect gets expanded out of being a grain cable component this will have to be re-factored to use the new component type since it is hard coded + */ + const setAutoDetect = () => { + if (selectedPort) { + let componentSettings = pond.ComponentSettings.create(); + componentSettings.type = quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE; // use the grain cable component + componentSettings.subtype = quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_DIAG; //use the port diagnostic subtype + componentSettings.addressType = quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY; + componentSettings.address = selectedPort.address; // the address of the selected port + componentSettings.measurementPeriodMs = 600000; //set the measurement period to 10 mins + componentSettings.reportPeriodMs = 600000; + componentSettings.name = "Port " + selectedPort.label + " Auto Detect"; + compAPI + .add(device.id(), componentSettings) + .then(resp => { + success("Added Auto Detect Component to Port"); + }) + .catch(err => { + error("Failed to Add Auto Detect Component to Port"); + }) + .finally(() => { + refreshCallback(); + }); + } + }; + + /** + * function that restricts the pin availabilty in the availability map to the pin of the port that was selected + * does nothing for I2C ports as the restrictions for that are handled by the component type + * @param port port that was selected + * @param availability availability map of the device + * @returns modified availability map + */ + const adjustAvailablePositions = (port: PortInformation, availability: DeviceAvailabilityMap) => { + let currentAvailability = availability; + switch (port.addressType) { + case quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY: + currentAvailability.set(port.addressType, [{ address: port.address, label: port.label }]); + } + return currentAvailability; + }; + + const findPortComponents = (components: Component[], port: PortInformation) => { + let pc: Component[] = []; + switch (port.addressType) { + case quack.AddressType.ADDRESS_TYPE_I2C: + components.forEach(comp => { + if (comp.settings.addressType === quack.AddressType.ADDRESS_TYPE_I2C) { + pc.push(comp); + } + }); + break; + case quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY: + components.forEach(comp => { + if (comp.settings.address === port.address) { + pc.push(comp); + } + }); + } + return pc; + }; + + const setupCard = () => { + return ( + + + } + title={GetDeviceProductLabel(device.settings.product) + " - ID:" + device.id()} + className={classes.cardHeader} + /> + + + Select a port on the device below to modify components + + + { + let available = FindAvailablePositions(components, device.settings.product); + setAvailableOffsets(available.offsetAvailability); + setAvailablePositions(adjustAvailablePositions(port, available.availability)); + //setAddressType(port.addressType); + setSelectedPort(port); + setPortComponents(findPortComponents(components, port)); + setMenuAnchor(e.currentTarget); + }} + /> + + + + ); + }; + + return ( + + {setupCard()} + setComponentDialogOpen(false)} + availablePositions={availablePositions} + availableOffsets={availableOffsets} + refreshCallback={refreshCallback} + canEdit + addressTypeRestriction={selectedPort && selectedPort.addressType} + /> + {wizardMenu()} + + ); +} diff --git a/src/device/deviceSVG.tsx b/src/device/deviceSVG.tsx new file mode 100644 index 0000000..d6399f7 --- /dev/null +++ b/src/device/deviceSVG.tsx @@ -0,0 +1,5246 @@ +import { + Box, + Button, + DialogActions, + DialogContent, + DialogTitle, + Theme +} from "@mui/material"; +import { makeStyles } from "@mui/styles"; +import ResponsiveDialog from "common/ResponsiveDialog"; +import { useMobile } from "hooks"; +import { Device } from "models"; +import { pond } from "protobuf-ts/pond"; +import { quack } from "protobuf-ts/quack"; +import React, { useState } from "react"; + +const useStyles = makeStyles((theme: Theme) => { + return ({ + testBox: { + fill: "#ff0000", + strokeWidth: 0.05 + }, + testButton: { + fill: "#ffffff", + fillOpacity: 0.25, + cursor: "pointer" + }, + label: { + fontSize: "2.8px", + fontWeight: 650, + fill: "white" + }, + whiteLine: { + fill: "#FFFFFF" + }, + haloBorder: { + //fill: none, + fillOpacity: 0, + stroke: "#FFFFFF", + strokeWidth: 1.2402, + strokeMiterlimit: 10 + }, + boxBorder: { + fillOpacity: 0, + stroke: "#FFFFFF", + strokeWidth: 1.2402, + strokeMiterlimit: 10 + }, + st2: { + opacity: 0.5 + }, + grayLine: { + opacity: 0.5, + fill: "gray" + }, + miVent0: { + strokeWidth: "1.5px", + fill: "none", + stroke: "#fff", + strokeMiterlimit: 10 + }, + miVent1: { + strokeWidth: "1.2px", + fill: "none", + stroke: "#fff", + strokeMiterlimit: 10 + }, + miVent2: { + fill: "#fff" + }, + miVentRectButton: { + fill: "#fff", + cursor: "pointer" + } + }); +}); + +export interface PortInformation { + address: number; + addressType: quack.AddressType; + label: string; + autoDetectable: boolean; +} + +interface Props { + device: Device; + openMenu: (event: any, port: PortInformation) => void; +} + +export default function DeviceSVG(props: Props) { + const { device, openMenu } = props; + const isMobile = useMobile(); + const classes = useStyles(); + const [lockOpen, setLockOpen] = useState(false); + const buttonSize = { + rxLarge: 12, + ryLarge: 12, + rxSmall: 8, + rySmall: 8 + }; + + const lockedPortDialog = () => { + return ( + { + setLockOpen(false); + }}> + Port Locked + + Contact Adaptive Agriculture at 1-306-850-1259 to upgrade your box and unlock remaining + ports + + + { + setLockOpen(false); + }} + color="primary"> + Close + + + + ); + }; + + const bindaptPlusSVG = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* buttons for port selection */} + {/* I2C PORTS */} + {/* TEMP/HUMIDITY */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="47" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PRESSURE */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="90" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + + {/* CONFIGURABLE PIN PORTS */} + {/* PORT 1 */} + { + openMenu(e, { + address: 4, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "1", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="131" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 2 */} + { + openMenu(e, { + address: 8, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "2", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="173" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT C1 */} + { + openMenu(e, { + address: 512, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "C1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="157" + cy="205" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + + ); + }; + + const bindaptPlusModSVG = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* buttons for port selection */} + {/* I2C PORT */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="22" + cy="51" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* CONFIGURABLE PIN PORTS */} + {/* PORT 1 */} + { + openMenu(e, { + address: 4, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "1", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="22" + cy="85" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 2 */} + { + openMenu(e, { + address: 8, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "2", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="22" + cy="116" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 3 */} + { + openMenu(e, { + address: 16, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "3", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="22" + cy="146" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT C1 */} + { + openMenu(e, { + address: 512, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "C1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="160" + cy="205" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + + ); + }; + + const bindaptPlusProModSVG = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C1 + + + + + C2 + + + + + + + + + + + + + + + + + + {/* buttons for port selection */} + {/* I2C PORT */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="21" + cy="51" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* CONFIGURABLE PIN PORTS */} + {/* PORT 1 */} + { + openMenu(e, { + address: 4, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "1", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="21" + cy="82" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 2 */} + { + openMenu(e, { + address: 8, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "2", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="21" + cy="113" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 3 */} + { + openMenu(e, { + address: 16, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "3", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="21" + cy="143" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 4 */} + { + openMenu(e, { + address: 32, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "4", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="21" + cy="173" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT C1 */} + { + openMenu(e, { + address: 512, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "C1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="117" + cy="204" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT C2 */} + { + openMenu(e, { + address: 1024, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "C2", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="169" + cy="204" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + + ); + }; + + const bindaptPlusProSVG = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* buttons for port selection */} + {/* I2C PORTS */} + {/* TEMP/HUMIDITY */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="41" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PRESSURE */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="76" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + + {/* CONFIGURABLE PIN PORTS */} + {/* PORT 1 */} + { + openMenu(e, { + address: 4, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "1", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="114" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 2 */} + { + openMenu(e, { + address: 8, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "2", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="148" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 3 */} + { + openMenu(e, { + address: 16, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "3", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="180" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT C1 */} + { + openMenu(e, { + address: 512, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "C1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="116" + cy="205" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT C2 */} + { + openMenu(e, { + address: 1024, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "C2", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="168" + cy="205" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + + ); + }; + + const binHaloSVG = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* lidar icon */} + + + + + + + + + + {/* buttons for port selection */} + {/* I2C PORTS */} + {/* HALOHEAD */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="50" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + + {/* CONFIGURABLE PIN PORTS */} + {/* PORT 1 */} + { + openMenu(e, { + address: 4, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "1", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="108" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 2 */} + { + openMenu(e, { + address: 8, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "2", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="170" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT C1 */} + { + openMenu(e, { + address: 512, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "C1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="149" + cy="205" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + + ); + }; + + const binMonitorSVG = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* buttons for port selection */} + {/* I2C PORTS */} + {/* HALOHEAD */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="50" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + + {/* CONFIGURABLE PIN PORTS */} + {/* PORT 1 */} + { + openMenu(e, { + address: 4, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "1", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="108" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 2 */} + { + openMenu(e, { + address: 8, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "2", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="172" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + + ); + }; + + const miPCASVG = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* buttons for port selection */} + {/* I2C PORTS */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="43" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* CONFIGURABLE PIN PORTS */} + {/* PORT 1 */} + { + openMenu(e, { + address: 4, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "1", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="85" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 2 */} + { + openMenu(e, { + address: 8, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "2", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="130" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 3 */} + { + openMenu(e, { + address: 16, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "3", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="175" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* + Ports are tecnically non configurable, they only work with the lights + PORT L1 + { + openMenu(e, { + address: 512, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "L1" + }); + }} + className={classes.testButton} + id="button" + cx="121" + cy="204" + rx={buttonSize.rx} + ry={buttonSize.ry} + /> + PORT L2 + { + openMenu(e, { + address: 1024, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "L2" + }); + }} + className={classes.testButton} + id="button" + cx="170" + cy="204" + rx={buttonSize.rx} + ry={buttonSize.ry} + /> */} + + ); + }; + + const miPCAV2SVG = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* buttons for port selection */} + {/* I2C PORTS */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="43" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* CONFIGURABLE PIN PORTS */} + {/* PORT 1 */} + { + openMenu(e, { + address: 1, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "1", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="85" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 2 */} + { + openMenu(e, { + address: 2, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "2", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="130" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 3 */} + { + openMenu(e, { + address: 4, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "3", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="24" + cy="175" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* + Ports are tecnically non configurable, they only work with the lights + PORT L1 + { + openMenu(e, { + address: 512, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "L1" + }); + }} + className={classes.testButton} + id="button" + cx="121" + cy="204" + rx={buttonSize.rx} + ry={buttonSize.ry} + /> + PORT L2 + { + openMenu(e, { + address: 1024, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "L2" + }); + }} + className={classes.testButton} + id="button" + cx="170" + cy="204" + rx={buttonSize.rx} + ry={buttonSize.ry} + /> */} + + ); + }; + + const nexusSTSVG = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* CONFIGURABLE PIN PORTS */} + {/* PORT 1 */} + { + openMenu(e, { + address: 4, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "1", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="16" + cy="56" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 2 */} + { + openMenu(e, { + address: 8, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "2", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="16" + cy="110" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 3 */} + { + openMenu(e, { + address: 16, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "3", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="16" + cy="165" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT C1 */} + { + openMenu(e, { + address: 512, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "C1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="147" + cy="206" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + + ); + }; + + const bindaptV2Monitor = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* buttons for port selection */} + {/* I2C PORTS */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="11.5" + cy="30" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* CONFIGURABLE PIN PORTS - Note input Ports 3 and 4 and all control ports are locked for the monitor */} + {/* PORT 1 */} + { + openMenu(e, { + address: 1, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "1", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="11.5" + cy="55" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* PORT 2 */} + { + openMenu(e, { + address: 2, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "2", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="11.5" + cy="79" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* PORT 3 */} + { + setLockOpen(true); + // openMenu(e, { + // address: 4, + // addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + // label: "3", + // autoDetectable: true + // }); + }} + className={classes.testButton} + id="button" + cx="11.5" + cy="103" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* Port 4 */} + { + setLockOpen(true); + // openMenu(e, { + // address: 8, + // addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + // label: "3", + // autoDetectable: true + // }); + }} + className={classes.testButton} + id="button" + cx="11.5" + cy="127" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* Controller ports - note these are all locked for monitor devices */} + {/* PORT C1 */} + { + setLockOpen(true); + // openMenu(e, { + // address: 2, + // addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + // label: "2", + // autoDetectable: true + // }); + }} + className={classes.testButton} + id="button" + cx="64" + cy="141" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* PORT C2 */} + { + setLockOpen(true); + // openMenu(e, { + // address: 4, + // addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + // label: "3", + // autoDetectable: true + // }); + }} + className={classes.testButton} + id="button" + cx="93" + cy="141" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* Port C3 */} + { + setLockOpen(true); + // openMenu(e, { + // address: 8, + // addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + // label: "3", + // autoDetectable: true + // }); + }} + className={classes.testButton} + id="button" + cx="122" + cy="141" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + + ); + }; + + const bindaptV2Automate = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* buttons for port selection */} + {/* I2C PORTS */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="11.5" + cy="30" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* CONFIGURABLE PIN PORTS - Note input Ports 3 and 4 and all control ports are locked for the monitor */} + {/* PORT 1 */} + { + openMenu(e, { + address: 1, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "1", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="11.5" + cy="55" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* PORT 2 */} + { + openMenu(e, { + address: 2, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "2", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="11.5" + cy="79" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* PORT 3 */} + { + openMenu(e, { + address: 4, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "3", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="11.5" + cy="103" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* Port 4 */} + { + openMenu(e, { + address: 8, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "4", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="11.5" + cy="127" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* Controller ports - note these are all locked for monitor devices */} + {/* PORT C1 */} + { + openMenu(e, { + address: 256, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "C1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="64" + cy="141" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* PORT C2 */} + { + openMenu(e, { + address: 512, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "C2", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="93" + cy="141" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + {/* Port C3 */} + { + openMenu(e, { + address: 1024, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "C3", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="122" + cy="141" + rx={buttonSize.rxSmall} + ry={buttonSize.rySmall} + /> + + ); + }; + + const miVentSVG = (V2?: boolean) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* I2C PORTS */} + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + id="button" + cx="22.7" + cy="60" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + { + openMenu(e, { + address: 0, //address here is not important for I2C, the restrictions are per component not per port + addressType: quack.AddressType.ADDRESS_TYPE_I2C, + label: "1", + autoDetectable: false + }); + }} + className={classes.testButton} + d="M108,187h86c 3.5,0, 6.3,2.8, 6.3,6.3 v30h-86v-30c 0-3.5,2.8-6.3, 6.3-6.3Z" + /> + {/* PORT 1 */} + { + openMenu(e, { + address: V2 ? 1 : 4, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "1", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="22.7" + cy="93" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 2 */} + { + openMenu(e, { + address: V2 ? 2 : 8, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "2", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="22.7" + cy="128" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* PORT 3 */} + { + openMenu(e, { + address: V2 ? 4 : 16, + addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, + label: "3", + autoDetectable: true + }); + }} + className={classes.testButton} + id="button" + cx="22.7" + cy="163" + rx={buttonSize.rxLarge} + ry={buttonSize.ryLarge} + /> + {/* + */} + + ); + }; + + const getProductSVG = () => { + switch (device.settings.product) { + case pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_PRO: + return bindaptPlusProSVG(); + case pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_PRO_MOD: + return bindaptPlusProModSVG(); + case pond.DeviceProduct.DEVICE_PRODUCT_BIN_HALO: + return binHaloSVG(); + case pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS: + return bindaptPlusSVG(); + case pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_MOD: + return bindaptPlusModSVG(); + case pond.DeviceProduct.DEVICE_PRODUCT_BIN_MONITOR: + return binMonitorSVG(); + case pond.DeviceProduct.DEVICE_PRODUCT_OMNIAIR: + return miPCASVG(); + case pond.DeviceProduct.DEVICE_PRODUCT_MIPCA_V2: + return miPCAV2SVG(); + case pond.DeviceProduct.DEVICE_PRODUCT_NEXUS_ST: + return nexusSTSVG(); + case pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_V2_MONITOR: + return bindaptV2Monitor(); + case pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_V2_AUTOMATE: + return bindaptV2Automate(); + case pond.DeviceProduct.DEVICE_PRODUCT_MIVENT: + return miVentSVG(); + case pond.DeviceProduct.DEVICE_PRODUCT_MIVENT_V2: + return miVentSVG(true); + } + }; + + return ( + + {lockedPortDialog()} + + {getProductSVG()} + + + ); +} diff --git a/src/models/Headspace.ts b/src/models/Headspace.ts new file mode 100644 index 0000000..bb4342b --- /dev/null +++ b/src/models/Headspace.ts @@ -0,0 +1,139 @@ +import { pond } from "protobuf-ts/pond"; +import { quack } from "protobuf-ts/pond"; +import { or } from "utils/types"; +import { cloneDeep } from "lodash"; +import { Component } from "models"; +import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; + +export class Headspace { + public settings: pond.ComponentSettings = pond.ComponentSettings.create(); + public status: pond.ComponentStatus = pond.ComponentStatus.create(); + public temperature: number = -127; + public humidity: number = 200; + + public static create(comp: Component): Headspace { + let my = new Headspace(); + my.settings = comp.settings; + my.status = comp.status; + + if (comp.status.measurement.length > 0) { + comp.status.measurement.forEach(um => { + if (um.values[0]) { + if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) { + my.temperature = um.values[0].values[0]; + } + if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) { + my.humidity = um.values[0].values[0]; + } + } + }); + } else { + let temp = comp.status?.lastMeasurement?.measurement?.humidityAndTemperature?.celciusTimes_10; + let hum = + comp.status?.lastMeasurement?.measurement?.humidityAndTemperature + ?.relativeHumidityTimes_100; + if (temp === undefined) { + if (comp.status?.lastMeasurement?.measurement?.humidityAndTemperature) { + let humAndTemp = comp.status?.lastMeasurement?.measurement?.humidityAndTemperature as any; + temp = humAndTemp["celciusTimes10"]; + hum = humAndTemp["relativeHumidityTimes100"]; + } + } + my.temperature = or(temp, -1270) / 10; + my.humidity = or(hum, 20000) / 100; + } + + return my; + } + + public static createPond(comp: pond.Component): Headspace { + let my = new Headspace(); + my.settings = comp.settings ? comp.settings : pond.ComponentSettings.create(); + my.status = comp.status ? comp.status : pond.ComponentStatus.create(); + let temp = comp.status?.lastMeasurement?.measurement?.humidityAndTemperature?.celciusTimes_10; + let hum = + comp.status?.lastMeasurement?.measurement?.humidityAndTemperature?.relativeHumidityTimes_100; + if (temp === undefined) { + if (comp.status?.lastMeasurement?.measurement?.humidityAndTemperature) { + let humAndTemp = comp.status?.lastMeasurement?.measurement?.humidityAndTemperature as any; + temp = humAndTemp["celciusTimes10"]; + hum = humAndTemp["relativeHumidityTimes100"]; + } + } + my.temperature = or(temp, -1270) / 10; + my.humidity = or(hum, 20000) / 100; + + return my; + } + + public static any(data: any): Headspace { + let comp = pond.Component.fromObject(cloneDeep(data)); + let my = Headspace.createPond(comp); + if (data && data.status && data.status.lastMeasurement) { + my.status.lastMeasurement = data.status.lastMeasurement; + } + return my; + } + + public update(other: Headspace) { + this.settings = other.settings; + this.status = other.status; + } + + public static humidColour() { + return describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, + quack.ComponentType.COMPONENT_TYPE_DHT + ).colour(); + } + + public static tempColour() { + return describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE).colour(); + } + + public name(): string { + return this.settings.name !== "" ? this.settings.name : "Component " + this.key(); + } + + public key(): string { + return this.settings.key; + } + + public location(): quack.ComponentID { + return quack.ComponentID.fromObject({ + type: this.settings.type, + addressType: this.settings.addressType, + address: this.settings.address + }); + } + + public locationString(): string { + return ( + or(this.settings.type, 0).toString() + + "-" + + or(this.settings.addressType, 0).toString() + + "-" + + or(this.settings.address, 0).toString() + ); + } + + public type(): quack.ComponentType { + return this.settings.type; + } + + public subType(): number { + return this.settings.subtype; + } + + public getTempString(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS): string { + if (this.temperature < -126) return "NaN"; + if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) + return (this.temperature * (9 / 5) + 32).toFixed(2) + "°F"; + return this.temperature.toFixed(2) + "°C"; + } + + public getHumidityString(): string { + if (this.humidity > 199) return "NaN"; + return this.humidity.toFixed(2) + "%"; + } +} diff --git a/src/pages/Bin.tsx b/src/pages/Bin.tsx index 86b3157..1cda075 100644 --- a/src/pages/Bin.tsx +++ b/src/pages/Bin.tsx @@ -49,7 +49,7 @@ import Chat from "chat/Chat"; import TasksIcon from "products/Construction/TasksIcon"; // import TaskViewer from "tasks/TaskViewer"; import FieldsIcon from "products/AgIcons/FieldsIcon"; -// import BinComponentTypes from "bin/BinComponentTypes"; +import BinComponentTypes from "bin/BinComponentTypes"; import { Plenum } from "models/Plenum"; import { GrainCable } from "models/GrainCable"; // import { Controller } from "models/Controller"; @@ -218,7 +218,7 @@ export default function Bin(props: Props) { setValue(newValue); }; - const StyledToggle = styled(ToggleButton)(({ theme }: { theme: Theme }) => ({ + const StyledToggle = styled(ToggleButton)(() => ({ root: { backgroundColor: "transparent", overflow: "visible", @@ -297,11 +297,11 @@ export default function Bin(props: Props) { let attachedDevIds: number[] = []; if (resp.data.componentDevices) Object.keys(resp.data.componentDevices).forEach(k => { - // newComponentDevices.set(k, parseInt(resp.data.componentDevices[k])); - // //make a list of all of the ids for devices that components are currently attached - // if (!attachedDevIds.includes(parseInt(resp.data.componentDevices[k]))) { - // attachedDevIds.push(parseInt(resp.data.componentDevices[k])); - // } + newComponentDevices.set(k, resp.data.componentDevices[k]); + //make a list of all of the ids for devices that components are currently attached + if (!attachedDevIds.includes(resp.data.componentDevices[k])) { + attachedDevIds.push(resp.data.componentDevices[k]); + } }); setComponentDevices(newComponentDevices); @@ -329,12 +329,12 @@ export default function Bin(props: Props) { } if (resp.data.components) { let compMap: Map = new Map(); - // resp.data.components.forEach((comp: Component, index: number, arr: Component[]) => { - // if (comp && comp.settings) { - // let c = Component.any(comp); - // compMap.set(comp.settings.key, c); - // } - // }); + resp.data.components.forEach((comp: pond.Component) => { + if (comp && comp.settings) { + let c = Component.any(comp); + compMap.set(comp.settings.key, c); + } + }); setComponents(compMap); } if (resp.data.presets) { @@ -803,14 +803,14 @@ export default function Bin(props: Props) { Bin Components - {/* */} + />