diff --git a/src/gate/GateDeviceInteraction.tsx b/src/gate/GateDeviceInteraction.tsx index ca05a58..c8e97ef 100644 --- a/src/gate/GateDeviceInteraction.tsx +++ b/src/gate/GateDeviceInteraction.tsx @@ -1,4 +1,5 @@ -import { Button, DialogActions, DialogContent, DialogTitle, Typography } from "@mui/material"; +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"; @@ -6,6 +7,7 @@ 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; @@ -37,6 +39,7 @@ const densityMap = new Map([ export default function GateDeviceInteraction(props: Props) { const { open, close, gate, compDevice, densityTemp } = props; const [{ user, as }] = useGlobalState(); + const [device, setDevice] = useState() const [lowDelta, setLowDelta] = useState(0); const [highDelta, setHighDelta] = useState(0); const [greenComponent, setGreenComponent] = useState(); @@ -44,6 +47,10 @@ export default function GateDeviceInteraction(props: Props) { const [pressureComponent, setPressureComponent] = useState(); 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(); useEffect(() => { @@ -98,6 +105,10 @@ export default function GateDeviceInteraction(props: Props) { } } }); + + if(compDevice.device){ + setDevice(Device.create(compDevice.device)) + } }, [gate, densityTemp, user, compDevice]); // useEffect(() => { @@ -115,26 +126,54 @@ export default function GateDeviceInteraction(props: Props) { // }); // }; + 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 - //TOGGLE green ON when pressure within range of upper and lower if ( greenComponent !== undefined && redComponent !== undefined && pressureComponent !== undefined ) { - let greenToggle: pond.InteractionSettings = pond.InteractionSettings.create({ - sink: quack.ComponentID.create({ - type: greenComponent.type(), - address: greenComponent.settings.address, - addressType: greenComponent.settings.addressType - }), - source: quack.ComponentID.create({ - type: pressureComponent.type(), - address: pressureComponent.settings.address, - addressType: pressureComponent.settings.addressType - }), + + 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 pressureSource = quack.ComponentID.create({ + type: pressureComponent.type(), + address: pressureComponent.settings.address, + addressType: pressureComponent.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_GREATER_THAN, @@ -150,61 +189,146 @@ export default function GateDeviceInteraction(props: Props) { nodeOne: 2, nodeTwo: 1, subtype: 18, - schedule: pond.InteractionSchedule.create({ - timezone: moment.tz.guess(), - weekdays: ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"], - timeOfDayStart: "00:00", - timeOfDayEnd: "24:00" - }), + schedule: lightSchedule, result: pond.InteractionResult.create({ type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE, value: 1 }) - }); + })) - //TOGGLE red OFF when pressure within range of upper and lower - let redToggle: pond.InteractionSettings = pond.InteractionSettings.create({ - sink: quack.ComponentID.create({ - type: redComponent.type(), - address: redComponent.settings.address, - addressType: redComponent.settings.addressType - }), - source: quack.ComponentID.create({ - type: pressureComponent.type(), - address: pressureComponent.settings.address, - addressType: pressureComponent.settings.addressType - }), - conditions: [ - pond.InteractionCondition.create({ - comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN, - value: -highDelta, - measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE - }), - pond.InteractionCondition.create({ - comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN, - value: -lowDelta, - measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE + //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_LESS_THAN, + value: -highDelta, + measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE + }) + ], + nodeOne: 2, + nodeTwo: 1, + subtype: 18, + schedule: lightSchedule, + result: pond.InteractionResult.create({ + type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_SET, + value: 1 }) - ], - nodeOne: 2, - nodeTwo: 1, - subtype: 18, - schedule: pond.InteractionSchedule.create({ - timezone: moment.tz.guess(), - weekdays: ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"], - timeOfDayStart: "00:00", - timeOfDayEnd: "24:00" - }), - result: pond.InteractionResult.create({ - type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE, - value: 0 - }) - }); + })) + //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_GREATER_THAN, + value: -highDelta, + measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE + }), + pond.InteractionCondition.create({ + comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN, + value: -lowDelta, + measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE + }) + ], + nodeOne: 2, + nodeTwo: 1, + subtype: 18, + 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_GREATER_THAN, + value: -lowDelta, + measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE + }), + pond.InteractionCondition.create({ + comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN, + value: -thresholdPascals, + measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE + }), + ], + nodeOne: 2, + nodeTwo: 1, + subtype: 18, + 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 + }), + ], + subtype: 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_GREATER_THAN, + value: -highDelta, + measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE + }), + pond.InteractionCondition.create({ + comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN, + value: -lowDelta, + measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE + }) + ], + nodeOne: 2, + nodeTwo: 1, + subtype: 18, + 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: [greenToggle, redToggle] + interactions: lightInteractions }); interactionsAPI .addMultiInteractions(deviceID, multi, as) @@ -230,11 +354,51 @@ export default function GateDeviceInteraction(props: Props) { }}> Set Interaction For Light Toggle - Your Delta Pressures, in pascals, will be: + {/* Your Delta Pressures, in pascals, will be: low = {lowDelta} high = {highDelta} {greenComponent ? "" : "Green LED Component not found"} - {redComponent ? "" : "Red LED Component not found"} + {redComponent ? "" : "Red LED Component not found"} */} + + This will set interactions on the pressure chain to toggle the green light on and the red light off + when the difference between the pressures falls within this range and vice versa: + +
+ Upper Limit: {getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? (highDelta/249.089).toFixed(2) + " iwg" : highDelta/1000 + " kPa"} + Lower Limit: {getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? (lowDelta/249.089).toFixed(2) + " iwg" : lowDelta/1000 + " kPa"} +
+ + 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 + { + setUseRedOffCondition(checked) + }} + /> + } + label={Use Off Condition} + /> + + {getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? "iwg" : "kPa"} + + ) + }} + onChange={e => { + setRedThreshold(e.target.value) + }} + /> +
diff --git a/src/gate/GateFlowGraph.tsx b/src/gate/GateFlowGraph.tsx index 6835b63..e6cdaf4 100644 --- a/src/gate/GateFlowGraph.tsx +++ b/src/gate/GateFlowGraph.tsx @@ -70,13 +70,19 @@ export default function GateFlowGraph(props: Props) { const classes = useStyles(); const [flowEvents, setFlowEvents] = useState([]); const [eventsLoading, setEventsLoading] = useState(false); - //these two constants could be entered by the user at time of retrieval + //these two constants could be entered by the user at time of retrieval or set in the gates settings const eventThreshold = 5; //the threshold that if crossed from one measurement to the next during a flow event will end the current flow event and start a new one - const idleFlow = 3.5; //the mass air flow that must be crossed in order to start a flow event, anything below this will not start an event but must go below this to end an event - + //const idleFlow = 3.5; //the mass air flow that must be crossed in order to start a flow event, anything below this will not start an event but must go below this to end an event + const [idleFlow, setIdleFlow] = useState(3.5) + useEffect(() => { if (loadingChartData) return; if (ambient && pressureComponent) { + let gateIdle = idleFlow + if(gate.settings.idleFlow){ + gateIdle = gate.settings.idleFlow + } + let recent: SSAreaDataPoint | undefined; setLoadingChartData(true); gateAPI @@ -110,11 +116,11 @@ export default function GateFlowGraph(props: Props) { /** determine runtime */ // set the start time if the values is greater than the idleFlow and start is not already set - if (val.airFlow >= idleFlow && !start) { + if (val.airFlow >= gateIdle && !start) { start = time; } // set the stop time when off or at the last measurements and the start time is set - if ((val.airFlow < idleFlow || i === resp.data.values.length - idleFlow) && start) { + if ((val.airFlow < gateIdle || i === resp.data.values.length - gateIdle) && start) { stop = time; } // if both start and stop are set calculate add the timeframe to the total runtime diff --git a/src/gate/GateList.tsx b/src/gate/GateList.tsx index bdf58c2..3e3b330 100644 --- a/src/gate/GateList.tsx +++ b/src/gate/GateList.tsx @@ -11,7 +11,7 @@ import ResponsiveTable, { Column } from "common/ResponsiveTable"; import AddGateFab from "./AddGateFab"; import GateSettings from "./GateSettings"; import { useGateAPI, useGlobalState } from "providers"; -import { CheckCircleOutline, DoNotDisturb, ErrorOutline, RemoveCircleOutline, Settings } from "@mui/icons-material"; +import { CheckCircleOutline, DoNotDisturb, ErrorOutline, HelpOutlineOutlined, Settings } from "@mui/icons-material"; import { cloneDeep } from "lodash"; import moment from "moment"; import { pond } from "protobuf-ts/pond"; @@ -94,13 +94,13 @@ export default function GateList(props: Props) { const displayPCAStatus = (state: pond.PCAState) => { switch(state){ case pond.PCAState.PCA_STATE_IN_BOUNDS: - return + return case pond.PCAState.PCA_STATE_OUT_BOUNDS: return case pond.PCAState.PCA_STATE_OFF: - return - default: return + default: + return } } diff --git a/src/gate/GateSettings.tsx b/src/gate/GateSettings.tsx index b4a1d92..c2ae4fc 100644 --- a/src/gate/GateSettings.tsx +++ b/src/gate/GateSettings.tsx @@ -76,6 +76,7 @@ export default function GateSettings(props: Props) { //user set identifier to be shown on the marker on the map const [terminalIdentifier, setTerminalIdentifier] = useState(""); const [gateIdentifier, setGateIdentifier] = useState(""); + const [idleLimit, setIdleLimit] = useState(0); const [hourlyPCA, setHourlyPCA] = useState(0); const [hourlyAPU, setHourlyAPU] = useState(0); @@ -140,6 +141,7 @@ export default function GateSettings(props: Props) { settings.thermalResistance = ductProps.thermalResistance; settings.lowerFlow = lowerFlowBound; settings.upperFlow = upperFlowBound; + settings.idleFlow = idleLimit; settings.ductName = ductName; settings.pcaType = pcaUnit; settings.letterIdentifier = terminalIdentifier; @@ -167,6 +169,7 @@ export default function GateSettings(props: Props) { thermalResistance: ductProps.thermalResistance, lowerFlow: lowerFlowBound, upperFlow: upperFlowBound, + idleFlow: idleLimit, ductName: ductName, pcaType: pcaUnit, letterIdentifier: terminalIdentifier, @@ -283,16 +286,18 @@ export default function GateSettings(props: Props) { return ( setGateName(e.target.value)} /> - + setLowerFlowBound(+e.target.value)} /> setUpperFlowBound(+e.target.value)} /> setIdleLimit(+e.target.value)} + /> + setPcaUnit(e.target.value)} /> setHourlyPCA(+e.target.value)} />