404 lines
16 KiB
TypeScript
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>
|
|
);
|
|
}
|