frontend/src/gate/GateDeviceInteraction.tsx

404 lines
16 KiB
TypeScript

import { CheckBox } from "@mui/icons-material";
import { Button, Checkbox, DialogActions, DialogContent, DialogTitle, FormControl, FormControlLabel, FormLabel, InputAdornment, TextField, Typography } from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog";
import { Component, Device } from "models";
import { Gate } from "models/Gate";
import moment from "moment";
import { pond, quack } from "protobuf-ts/pond";
import { useGlobalState, useInteractionsAPI, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
import { getPressureUnit } from "utils";
interface Props {
open: boolean;
close: (canceled: boolean) => void;
gate: Gate;
compDevice: pond.ComprehensiveDevice;
densityTemp: number;
}
//map that the temp is the key and the air density is the value
const densityMap = new Map<number, number>([
[35, 1.15],
[30, 1.16],
[25, 1.18],
[20, 1.2],
[15, 1.23],
[10, 1.25],
[5, 1.27],
[0, 1.29],
[-5, 1.32],
[-10, 1.34],
[-15, 1.37],
[-20, 1.39],
[-25, 1.42]
]);
//this entire component will only be used for manual setting of each device on the gate
//once we have numbers for presets it may never be used again
export default function GateDeviceInteraction(props: Props) {
const { open, close, gate, compDevice, densityTemp } = props;
const [{ user, as }] = useGlobalState();
const [device, setDevice] = useState<Device>(Device.create())
const [lowDelta, setLowDelta] = useState(0);
const [highDelta, setHighDelta] = useState(0);
const [greenComponent, setGreenComponent] = useState<Component>();
const [redComponent, setRedComponent] = useState<Component>();
const [pressureComponent, setPressureComponent] = useState<Component>();
const interactionsAPI = useInteractionsAPI();
const [adding, setAdding] = useState(false);
//boolean to determine if a third condition should be put on the interaction for the red light, requires a V2 device running at least fromware version 2.1.9
const [useRedOffCondition, setUseRedOffCondition] = useState(false)
//if the pressure is under this value then the red light should be off
const [redThreshold, setRedThreshold] = useState("0")
const { openSnack } = useSnackbar();
const [pressureSource, setPressureSource] = useState<quack.ComponentID>(quack.ComponentID.create())
useEffect(() => {
//math to determine what the delta pressures to set will be
let celciusTemp = densityTemp;
if (user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
celciusTemp = densityTemp * 1.8 + 32;
}
let roundedTemp = Math.round(celciusTemp / 5) * 5;
let airDensity = densityMap.get(roundedTemp);
//have default values if the temp is outside the map
if (roundedTemp > 35 && airDensity === undefined) airDensity = 1.1;
if (roundedTemp < -25 && airDensity === undefined) airDensity = 1.5;
//calculate the c value for the equation
if (airDensity !== undefined) {
let diameterM = gate.ductDiameter() / 1000;
let pieRadSquare = 3.14 * Math.pow(diameterM / 2, 2);
let c = 0.98 * pieRadSquare * Math.sqrt(2 * airDensity);
let qmHigh = gate.upperFlow();
let qmLow = gate.lowerFlow();
setLowDelta(Math.round(Math.pow(qmLow / c, 2)));
setHighDelta(Math.round(Math.pow(qmHigh / c, 2)));
}
//addresses of controller LEDs on a V1 device
let redAddr = "3-1-512";
let greenAddr = "3-1-1024";
if (compDevice.device) {
let dev = Device.create(compDevice.device);
if (dev.settings.product === pond.DeviceProduct.DEVICE_PRODUCT_MIPCA_V2) {
redAddr = "3-1-256";
greenAddr = "3-1-512";
}
}
//need to find if they have LED's in both of the controller positions
compDevice.components.forEach(comp => {
let component = Component.any(comp);
//checks the address for the LED components to get the red and green LED's
if (component.locationString() === redAddr && component.subType() === 1) {
setRedComponent(component);
} else if (component.locationString() === greenAddr && component.subType() === 1) {
setGreenComponent(component);
} else if (
gate.preferences[component.key()] === pond.GateComponentType.GATE_COMPONENT_TYPE_PRESSURE
) {
setPressureComponent(component);
setPressureSource(
quack.ComponentID.create({
type: component.type(),
address: component.settings.address,
addressType: component.settings.addressType
})
)
}
});
if(compDevice.device){
setDevice(Device.create(compDevice.device))
}
}, [gate, densityTemp, user, compDevice]);
const buttonDisabled = () => {
if (greenComponent === undefined) return true
if (redComponent === undefined) return true
if (adding) return true
if (useRedOffCondition && isNaN(parseFloat(redThreshold))) return true
return false
}
const createInteractions = async () => {
//the interactions to be made
if (
greenComponent !== undefined &&
redComponent !== undefined &&
pressureComponent !== undefined
) {
//get the number of nodes in the pressure components
let nodes = pressureComponent.numNodes()
let lightInteractions: pond.InteractionSettings[] = []
//making variables for the parameters of the interactions
const redSink = quack.ComponentID.create({
type: redComponent.type(),
address: redComponent.settings.address,
addressType: redComponent.settings.addressType
})
const greenSink = quack.ComponentID.create({
type: greenComponent.type(),
address: greenComponent.settings.address,
addressType: greenComponent.settings.addressType
})
const lightSchedule = pond.InteractionSchedule.create({
timezone: moment.tz.guess(),
weekdays: ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"],
timeOfDayStart: "00:00",
timeOfDayEnd: "24:00"
})
//TOGGLE green ON when pressure within range of upper and lower, this will always be the same regardless
lightInteractions.push(pond.InteractionSettings.create({
sink: greenSink,
source: pressureSource,
conditions: [
pond.InteractionCondition.create({
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
value: highDelta,
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
}),
pond.InteractionCondition.create({
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
value: lowDelta,
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
})
],
nodeOne: nodes,
subtype: nodes+1,
schedule: lightSchedule,
result: pond.InteractionResult.create({
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE,
value: 1
})
}))
//if they want the red light off when below a certain pressure, it will need 4 seperate SET interactions
if(useRedOffCondition){
//need to make sure the redthreshold is in pascals
let thresholdPascals = 0
if(!isNaN(parseFloat(redThreshold))){
if(getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_KILOPASCALS){
thresholdPascals = parseFloat(redThreshold)*1000
}else if (getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER){
thresholdPascals = parseFloat(redThreshold)*249
}
}
//SET red on when above high
lightInteractions.push(pond.InteractionSettings.create({
sink: redSink,
source: pressureSource,
conditions: [
pond.InteractionCondition.create({
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
value: highDelta,
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
})
],
nodeOne: nodes,
subtype: nodes+1,
schedule: lightSchedule,
result: pond.InteractionResult.create({
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_SET,
value: 1
})
}))
//SET red off when below high and above low
lightInteractions.push(pond.InteractionSettings.create({
sink: redSink,
source: pressureSource,
conditions: [
pond.InteractionCondition.create({
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
value: highDelta,
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
}),
pond.InteractionCondition.create({
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
value: lowDelta,
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
})
],
nodeOne: nodes,
subtype: nodes+1,
schedule: lightSchedule,
result: pond.InteractionResult.create({
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_SET,
value: 0
})
}))
//SET red on when below low and above threshold
lightInteractions.push(pond.InteractionSettings.create({
sink: redSink,
source: pressureSource,
conditions: [
pond.InteractionCondition.create({
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
value: lowDelta,
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
}),
pond.InteractionCondition.create({
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
value: thresholdPascals,
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
}),
],
nodeOne: nodes,
subtype: nodes+1,
schedule: lightSchedule,
result: pond.InteractionResult.create({
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_SET,
value: 1
})
}))
//SET red off when below threshold
lightInteractions.push(pond.InteractionSettings.create({
sink: redSink,
source: pressureSource,
conditions: [
pond.InteractionCondition.create({
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
value: thresholdPascals,
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
}),
],
nodeOne: nodes,
subtype: nodes+1,
schedule: lightSchedule,
result: pond.InteractionResult.create({
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_SET,
value: 0
})
}))
}else{
//otherwise use the regular single TOGGLE interaction
//TOGGLE red OFF when pressure within range of upper and lower
lightInteractions.push(pond.InteractionSettings.create({
sink: redSink,
source: pressureSource,
conditions: [
pond.InteractionCondition.create({
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
value: highDelta,
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
}),
pond.InteractionCondition.create({
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
value: lowDelta,
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
})
],
nodeOne: nodes,
subtype: nodes+1,
schedule: lightSchedule,
result: pond.InteractionResult.create({
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE,
value: 0
})
}))
}
let deviceID = Device.any(compDevice.device).id();
if (deviceID !== undefined) {
let multi = pond.MultiInteractionSettings.create({
interactions: lightInteractions
});
interactionsAPI
.addMultiInteractions(deviceID, multi, as)
.then(resp => {
openSnack("Interactions added");
})
.catch(err => {
openSnack("There was a problem adding interactions to the device");
})
.finally(() => {
setAdding(false);
});
}
}
close(false);
};
return (
<ResponsiveDialog
open={open}
onClose={() => {
close(true);
}}>
<DialogTitle>Set Interaction For Light Toggle</DialogTitle>
<DialogContent>
{/* Your Delta Pressures, in pascals, will be:
<Typography>low = {lowDelta}</Typography>
<Typography>high = {highDelta}</Typography>
<Typography>{greenComponent ? "" : "Green LED Component not found"}</Typography>
<Typography>{redComponent ? "" : "Red LED Component not found"}</Typography> */}
<Typography>
This will clear existing interactions on the pressure chain and set new ones to toggle the green light on and the red light off
when the difference between the pressures falls within this range and vice versa:
</Typography>
<br />
<Typography>Upper Limit: {getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? (highDelta/249.089).toFixed(2) + " iwg" : highDelta/1000 + " kPa"}</Typography>
<Typography>Lower Limit: {getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? (lowDelta/249.089).toFixed(2) + " iwg" : lowDelta/1000 + " kPa"}</Typography>
<br />
<Typography>If you would like to have the interactions set in such a way that if the average pressure falls below a given value set the use off condition and set the value to use</Typography>
<FormControlLabel
control={
<Checkbox
checked={useRedOffCondition}
onChange={(e, checked) => {
setUseRedOffCondition(checked)
}}
/>
}
label={<Typography>Use Off Condition</Typography>}
/>
<TextField
type="number"
fullWidth
disabled={!useRedOffCondition}
value={redThreshold}
error={isNaN(parseFloat(redThreshold))}
helperText={isNaN(parseFloat(redThreshold)) ? "Must be a valid number" : ""}
InputProps={{
endAdornment: (
<InputAdornment position="end">
{getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? "iwg" : "kPa"}
</InputAdornment>
)
}}
onChange={e => {
setRedThreshold(e.target.value)
}}
/>
</DialogContent>
<DialogActions>
<Button
variant="contained"
color="primary"
onClick={() => {
//TODO: Once we figure out how to fix the backend to handle rapid addition/removal of interactions
setAdding(true);
interactionsAPI.clearInteractions(device.id(), [pressureSource]).then(resp => {
createInteractions();
}).catch(err => {
console.error(err)
})
}}
disabled={buttonDisabled()}
>
{adding ? "Adding Interaction" : "Create Interaction"}
</Button>
</DialogActions>
</ResponsiveDialog>
);
}