frontend/src/device/autoDetect/DeviceScannedComponents.tsx

320 lines
No EOL
11 KiB
TypeScript

import { Box, Button, Card, DialogActions, DialogContent, DialogTitle, FormControlLabel, Grid2, Radio, RadioGroup, Step, StepLabel, Stepper, Typography } from "@mui/material";
import { cloneDeep } from "lodash";
import { getSubtypes, Subtype } from "pbHelpers/ComponentType";
import { pond, quack } from "protobuf-ts/pond";
import React from "react";
import { useEffect, useState } from "react";
import ScannedI2C from "./ScannedI2C";
import ComponentForm from "component/ComponentForm";
import { Component, Device } from "models";
import { DeviceAvailabilityMap, FindAvailablePositions, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability";
import ResponsiveDialog from "common/ResponsiveDialog";
import { useComponentAPI, useDeviceAPI, useSnackbar } from "hooks";
import { ConfigurablePin } from "pbHelpers/AddressTypes";
import ScannedOneWirePort from "./OneWire/ScannedOneWirePort";
interface Props {
scannedComponents: pond.ComponentAddressMap
device: Device
availablePositions: DeviceAvailabilityMap
availableOffsets: OffsetAvailabilityMap
refreshCallback: () => void
}
interface CompStep {
label: string;
completed?: boolean;
}
let i2cBlacklistAddresses: number[] = [0x70]
export default function DeviceScannedComponents(props: Props){
const {scannedComponents, device, availablePositions, availableOffsets, refreshCallback} = props
const compAPI = useComponentAPI();
const deviceAPI = useDeviceAPI()
const [scannedI2C, setScannedI2C] = useState<pond.DetectI2C>() //the unmodified scan of addresses
const [scannedOneWire, setScannedOneWire] = useState<pond.DetectOneWire[]>([])
const [validI2CCompAddresses, setValidI2CComponentAddresses] = useState<quack.AddressData[]>([]) //the filtered array of address data
const [components, setComponents] = useState<Component[]>([])
const [steps, setSteps] = useState<CompStep[]>([]);
const { error, success } = useSnackbar();
const [currentStep, setCurrentStep] = useState(0)
const [settingsValid, setSettingsValid] = useState(false);
const [openDialog, setOpenDialog] = useState(false);
const [clearOpen, setClearOpen] = useState(false);
useEffect(()=>{
if (scannedComponents.i2c) setScannedI2C(scannedComponents.i2c)
if (scannedComponents.oneWire) setScannedOneWire(scannedComponents.oneWire)
//makes the array empty for testing
// if (scannedComponents.i2c) {
// let clone = cloneDeep(scannedComponents.i2c)
// if (clone.settings){
// clone.settings.foundAddresses = []
// }
// setScannedI2C(clone)
// }
},[scannedComponents])
useEffect(() => {
let steps: CompStep[] = []
components.forEach(comp => {
steps.push({
label: comp.name(),
completed: false
})
})
setSteps(steps)
},[components])
useEffect(()=>{
let valid: quack.AddressData[] = []
//filter the address data
let addr = scannedI2C?.settings?.foundAddresses
if(addr){
addr.forEach(addrData => {
if(!i2cBlacklistAddresses.includes(addrData.address)){
if(!(addrData.address === 0x71 && addrData.expansionLine === undefined)){
valid.push(addrData)
}
}
})
}
setValidI2CComponentAddresses(valid)
},[scannedI2C])
const stepper = () => {
return (
<Stepper nonLinear activeStep={currentStep} style={{ width: "100%" }}>
{steps.map(compStep => {
const labelProps: {
optional?: React.ReactNode;
} = {};
return (
<Step key={compStep.label} completed={compStep.completed}>
<StepLabel {...labelProps}>{compStep.label}</StepLabel>
</Step>
);
})}
</Stepper>
);
};
const addComponents = () => {
let c: pond.MultiComponentSettings = pond.MultiComponentSettings.create();
components.forEach(component => {
c.components.push(component.settings);
});
compAPI
.addMultiComponents(device.id(), c)
.then(resp => {
success("Components added to Device");
})
.catch(err => {
error("One or more component failed to add");
})
.finally(() => {
refreshCallback();
});
};
const addComponentDialog = () => {
return (
<ResponsiveDialog open={openDialog} onClose={()=>{setOpenDialog(false)}}>
<DialogTitle>Adding Components</DialogTitle>
<DialogContent>
{stepper()}
<ComponentForm
component={components[currentStep]}
canEdit
device={device}
componentChanged={(component, isValid) => {
components[currentStep].settings = component.settings;
setSettingsValid(isValid);
}}
/>
</DialogContent>
<DialogActions>
<Grid2 container direction="row" justifyContent="space-between">
<Grid2>
<Button
variant="contained"
color="primary"
onClick={() => {
setOpenDialog(false)
}}>
Cancel
</Button>
</Grid2>
<Grid2>
{currentStep !== 0 && (
<Button
sx={{marginX: 1}}
variant="contained"
color="primary"
onClick={() => {
setCurrentStep(currentStep - 1);
}}>
Back
</Button>
)}
{currentStep !== steps.length - 1 && (
<React.Fragment>
<Button
sx={{marginX: 1}}
variant="contained"
color="primary"
onClick={() => {
setCurrentStep(currentStep + 1);
}}>
Next
</Button>
</React.Fragment>
)}
<Button
sx={{marginX: 1}}
variant="contained"
color="primary"
onClick={() => {
setOpenDialog(false)
addComponents();
}}>
Confirm
</Button>
</Grid2>
</Grid2>
</DialogActions>
</ResponsiveDialog>
);
};
const componentSelection = (newComponents: Component[], checked: boolean) => {
let cloneList = cloneDeep(components)
if(checked){
//add the component to the list
cloneList = cloneList.concat(newComponents)
}else{
newComponents.forEach(newComponent => {
//remove any components that match the component location: (type)-(addressType)-(address)
cloneList.forEach((comp, i) => {
//check the components location against the one in the list
if(comp.locationString() === newComponent.locationString()){
cloneList.splice(i, 1)
}
})
})
}
console.log(cloneList)
setComponents(cloneList)
}
const removeScan = (key: string, type: pond.ObjectType) => {
deviceAPI.removeFoundComponents(device.settings.deviceId, key, type)
.then(resp => {
console.log("Cleared this scan")
}).catch(err => {
console.log("There was a problem clearing this scan")
})
}
const removeAllScans = () => {
deviceAPI.removeAllFoundComponents(device.settings.deviceId)
.then(resp => {
console.log("Cleared all scans")
refreshCallback()
}).catch(err => {
console.log("There was a problem clearing scans")
})
}
const clearWarning = () => {
return (
<ResponsiveDialog open={clearOpen} onClose={()=>{setClearOpen(false)}}>
<DialogTitle>Clear All Scans</DialogTitle>
<DialogContent>This action will clear all scans for this device.</DialogContent>
<DialogActions>
<Button variant="contained" onClick={()=>{setClearOpen(false)}}>Close</Button>
<Button variant="contained" color="primary" onClick={()=>{
removeAllScans()
setClearOpen(false)
}}>Continue</Button>
</DialogActions>
</ResponsiveDialog>
)
}
return (
<React.Fragment>
{clearWarning()}
<Card raised sx={{padding: 1}}>
<Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
<Typography sx={{fontWeight: 650, fontSize: 25}}>Sensor Scan</Typography>
<Button variant="contained" color="primary" onClick={()=>{setClearOpen(true)}}>Clear</Button>
</Box>
{scannedI2C !== undefined &&
<Box marginBottom={5}>
<Box justifyContent="space-between" display="flex">
<Typography sx={{fontWeight: 650}}>
I2C Sensors
</Typography>
<Button variant="contained" color="primary" onClick={()=>{removeScan(scannedI2C.key, pond.ObjectType.OBJECT_TYPE_DETECT_I2C)}}>Clear Scan</Button>
</Box>
{validI2CCompAddresses.length > 0 ? validI2CCompAddresses.map((addr, index) => {
return (
<ScannedI2C key={index} sensorNum={index+1} addressData={addr} deviceProduct={device.settings.product} componentSelectionCallback={componentSelection} availablePositions={availablePositions.get(quack.AddressType.ADDRESS_TYPE_I2C) ?? new Map<quack.ComponentType, number[]>()}/>
)
}) :
<Box sx={{
margin: 1,
display: "flex",
justifyContent: "center"
}}>
<Typography>
No Sensors Found
</Typography>
</Box>
}
</Box>
}
{scannedOneWire.length > 0 &&
<Box>
<Box justifyContent="space-between" display="flex">
<Typography sx={{fontWeight: 650}}>
Pin Port Sensors
</Typography>
</Box>
{scannedOneWire.map((scan,i) => {
let map = FindAvailablePositions([], device.settings.product).availability.get(quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY)
let portAddress = scan.settings?.oneWireData?.port
let portLabel = ""
map?.forEach(entry => {
let pin = entry as ConfigurablePin
if (pin.address && pin.address === portAddress) {
portLabel = pin.label
}
})
return(
<Box key={i} paddingX={2}>
<Box display="flex" justifyContent="space-between" alignItems={"center"}>
<Typography>Port: {portLabel}</Typography>
<Button variant="contained" color="primary" onClick={()=>{
removeScan(scan.key, pond.ObjectType.OBJECT_TYPE_DETECT_ONEWIRE)
}}>Clear Port Scan</Button>
</Box>
<ScannedOneWirePort scan={scan} componentSelectionCallback={componentSelection}/>
</Box>
)
})}
</Box>
}
<Box display="flex" flexDirection="row-reverse" marginTop={2}>
<Button disabled={components.length === 0} variant="contained" color="primary" onClick={()=>{setOpenDialog(true)}}>Add Components</Button>
</Box>
</Card>
{addComponentDialog()}
</React.Fragment>
)
}