updating the gate and terminal stuff for the device changes since we no longer have MiPCA device use chains for pressure and duct temp and are now only measuring the outlet and substituting the temp inlet with the ambient temp and the pressure inlet with 0

This commit is contained in:
csawatzky 2026-02-25 16:10:12 -06:00
parent 2413cc36d1
commit b5adba8f93
6 changed files with 164 additions and 65 deletions

View file

@ -1,4 +1,4 @@
import { Box, Card, CardHeader, Grid2, Typography } from "@mui/material" import { Card, CardHeader, Grid2, Typography } from "@mui/material"
import { blue, orange } from "@mui/material/colors" import { blue, orange } from "@mui/material/colors"
import SingleSetAreaChart, { SSAreaDataPoint } from "charts/SingleSetAreaChart" import SingleSetAreaChart, { SSAreaDataPoint } from "charts/SingleSetAreaChart"
import { useComponentAPI } from "hooks" import { useComponentAPI } from "hooks"
@ -38,11 +38,68 @@ export default function GateDeltaTempGraph(props: Props){
const warmingColour = orange["500"] const warmingColour = orange["500"]
const coolingColour = blue["500"] const coolingColour = blue["500"]
const getUnitByType = (unitMeasurements: UnitMeasurement[], type: quack.MeasurementType) => {
return unitMeasurements.find(u => u.type === type);
}
const buildDeltas = (ambientMeasurements: UnitMeasurement[], tempCompMeasurements: UnitMeasurement[], measurementType: quack.MeasurementType) => {
const ambient = getUnitByType(ambientMeasurements, measurementType);
const outlet = getUnitByType(tempCompMeasurements, measurementType);
if (!ambient || !outlet) return [];
let i = 0;
let j = 0;
let lastAmbient: number | null = null;
let lastOutlet: number | null = null;
const deltas: SSAreaDataPoint[] = [];
const ambientTimes = ambient.timestamps.map(t => moment(t).valueOf()).reverse();
const outletTimes = outlet.timestamps.map(t => moment(t).valueOf()).reverse();
const ambientValues = [...ambient.values].reverse();
const outletValues = [...outlet.values].reverse();
while (i < ambientTimes.length || j < outletTimes.length) {
const aTime = i < ambientTimes.length ? ambientTimes[i] : Infinity;
const bTime = j < outletTimes.length ? outletTimes[j] : Infinity;
if (aTime <= bTime) {
const valueArray = ambientValues[i++];
if (!valueArray.error && valueArray.values.length > 0) {
lastAmbient = valueArray.values[valueArray.values.length -1];
}
if (lastAmbient !== null && lastOutlet !== null) {
deltas.push({
timestamp: aTime,
value: Math.round((lastOutlet - lastAmbient)*100)/100
});
}
} else {
const valueArray = outletValues[j++];
if (!valueArray.error && valueArray.values.length > 0) {
lastOutlet = valueArray.values[valueArray.values.length -1];
}
if (lastAmbient !== null && lastOutlet !== null) {
deltas.push({
timestamp: bTime,
value: Math.round((lastOutlet - lastAmbient)*100)/100
});
}
}
}
return deltas
}
/** /**
* the use effect will use the temp component passed in get the measurements, then use the first node and the last node to determine the difference, * the use effect will use the temp component passed in get the measurements, then use the first node and the last node to determine the difference,
* if there is only one node, or only one node is reading due to errors, then it will use that as both the inlet and outlet so the delta will be 0, * if there is only one node, or only one node is reading due to errors, then it will use that as both the inlet and outlet so the delta will be 0,
* *
* //NOT IMPLEMENTED YET//
* if an ambient is passed in it will use the readings from the ambient as the inlet and the temp component as the outlet * if an ambient is passed in it will use the readings from the ambient as the inlet and the temp component as the outlet
*/ */
useEffect(()=>{ useEffect(()=>{
@ -52,7 +109,7 @@ export default function GateDeltaTempGraph(props: Props){
tempComponent.key(), tempComponent.key(),
start.toISOString(), start.toISOString(),
end.toISOString(), end.toISOString(),
500, 500,
0, 0,
"timestamp", "timestamp",
).then(resp => { ).then(resp => {
@ -60,7 +117,24 @@ export default function GateDeltaTempGraph(props: Props){
let tempCompMeasurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user)); let tempCompMeasurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user));
//if there is an ambient component, then treat the temp component like a single sensor and not a chain, and use the ambient measurements as the inlet //if there is an ambient component, then treat the temp component like a single sensor and not a chain, and use the ambient measurements as the inlet
if(ambient){ if(ambient){
//TODO: this is just an idea of how to handle a possible path for the overhaul of gates, if that is the route we take then this needs to be coded //need to then load the measurements for the ambient component
componentAPI.listUnitMeasurements(
deviceID,
ambient.key(),
start.toISOString(),
end.toISOString(),
500,
0,
"timestamp",
).then(resp => {
let ambientMeasurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user));
//now loop through each of them to 'combine' them into a single array
const deltas = buildDeltas(ambientMeasurements, tempCompMeasurements, quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE)
if(deltas.length > 0){
setData(deltas)
setRecent(deltas[deltas.length-1])
}
})
}else{//else no ambient is passed in treat the temp component like a chain }else{//else no ambient is passed in treat the temp component like a chain
let deltas: SSAreaDataPoint[] = [] let deltas: SSAreaDataPoint[] = []

View file

@ -23,6 +23,7 @@ import GateGraphs from "./GateGraphs";
//import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab"; //import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab";
import { getThemeType } from "theme"; import { getThemeType } from "theme";
import ButtonGroup from "common/ButtonGroup"; import ButtonGroup from "common/ButtonGroup";
import { cloneDeep } from "lodash";
interface Props { interface Props {
gate: Gate; gate: Gate;
@ -55,8 +56,11 @@ export default function GateDevice(props: Props) {
// const [pcaFanOn, setPCAFanOn] = useState(false); // const [pcaFanOn, setPCAFanOn] = useState(false);
const [detail, setDetail] = useState<"sensors" | "analytics">("analytics"); const [detail, setDetail] = useState<"sensors" | "analytics">("analytics");
const [lastAmbient, setLastAmbient] = useState(0); const [lastAmbient, setLastAmbient] = useState(0);
const [lastTemps, setLastTemps] = useState({ t1: 0, t2: 0 }); //const [lastTemps, setLastTemps] = useState({ t1: 0, t2: 0 });
const [lastPressures, setLastPressures] = useState({ p1: 0, p2: 0 }); const [ductTemp, setDuctTemp] = useState<number | undefined>()
//const [lastPressures, setLastPressures] = useState({ p1: 0, p2: 0 });
const [ductPressure, setDuctPressure] = useState<number | undefined>()
useEffect(() => { useEffect(() => {
//addresses of controller LEDs on a V1 device //addresses of controller LEDs on a V1 device
@ -142,24 +146,20 @@ export default function GateDevice(props: Props) {
useEffect(() => { useEffect(() => {
let tempComponent = componentOptions.get(tempKey); let tempComponent = componentOptions.get(tempKey);
let t1 = 0; if(tempComponent){
let t2 = 0;
if (tempComponent) {
tempComponent.status.measurement.forEach(um => { tempComponent.status.measurement.forEach(um => {
let measurement = UnitMeasurement.any(um, user); if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
if ( let clone = cloneDeep(um)
measurement.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE && let measurement = UnitMeasurement.any(clone, user)
measurement.values.length > 0 if(measurement.values.length > 0){
) { let readings = measurement.values[measurement.values.length -1]
let nodeVals = measurement.values[0].values; if(readings.values.length > 0){
if (nodeVals.length > 1) { setDuctTemp(readings.values[readings.values.length -1])
t1 = nodeVals[0]; //uses the first node }
t2 = nodeVals[1]; //uses second node node
} }
} }
}); })
} }
setLastTemps({ t1, t2 });
}, [componentOptions, tempKey, user]); }, [componentOptions, tempKey, user]);
useEffect(() => { useEffect(() => {
@ -180,24 +180,20 @@ export default function GateDevice(props: Props) {
useEffect(() => { useEffect(() => {
let pressureComponent = componentOptions.get(pressureKey); let pressureComponent = componentOptions.get(pressureKey);
let p1 = 0; if(pressureComponent){
let p2 = 0;
if (pressureComponent) {
pressureComponent.status.measurement.forEach(um => { pressureComponent.status.measurement.forEach(um => {
let measurement = UnitMeasurement.any(um, user); if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE){
if ( let clone = cloneDeep(um)
measurement.type === quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE && let measurement = UnitMeasurement.any(clone, user)
measurement.values.length > 0 if(measurement.values.length > 0){
) { let readings = measurement.values[measurement.values.length -1]
let nodeVals = measurement.values[0].values; if(readings.values.length > 0){
if (nodeVals.length > 1) { setDuctPressure(readings.values[readings.values.length -1])
p1 = nodeVals[0]; }
p2 = nodeVals[1];
} }
} }
}); })
} }
setLastPressures({ p1, p2 });
}, [componentOptions, pressureKey, user]); }, [componentOptions, pressureKey, user]);
const ambientSelector = () => { const ambientSelector = () => {
@ -340,8 +336,10 @@ export default function GateDevice(props: Props) {
: 0 : 0
} }
ambientTemp={lastAmbient} ambientTemp={lastAmbient}
innerTemps={lastTemps} //innerTemps={lastTemps}
innerPressures={lastPressures} //innerPressures={lastPressures}
ductTemp={ductTemp}
ductPressure={ductPressure}
ambientSelector={ambientSelector} ambientSelector={ambientSelector}
tempChainSelector={tempSelector} tempChainSelector={tempSelector}
pressureChainSelector={pressureSelector} pressureChainSelector={pressureSelector}
@ -374,9 +372,6 @@ export default function GateDevice(props: Props) {
gate={gate} gate={gate}
display={detail} display={detail}
compMap={componentOptions} compMap={componentOptions}
// setPCAState={(state: boolean) => {
// setPCAState(state);
// }}
ambient={ambientKey} ambient={ambientKey}
pressure={pressureKey} pressure={pressureKey}
tempChain={tempKey} tempChain={tempKey}

View file

@ -303,6 +303,7 @@ export default function GateGraphs(props: Props) {
*/ */
const sensorGraphs = () => { const sensorGraphs = () => {
const tempComp = compMap.get(tempChain) const tempComp = compMap.get(tempChain)
const ambientComp = compMap.get(ambient)
let graphs: JSX.Element[] = [] let graphs: JSX.Element[] = []
//pressure //pressure
@ -314,7 +315,7 @@ export default function GateGraphs(props: Props) {
if(tempComp){ if(tempComp){
graphs.push( graphs.push(
<GateDeltaTempGraph deviceID={device} start={startDate} end={endDate} tempComponent={tempComp}/> <GateDeltaTempGraph deviceID={device} start={startDate} end={endDate} tempComponent={tempComp} ambient={ambientComp}/>
) )
} }
//T/H chain //T/H chain
@ -323,7 +324,6 @@ export default function GateGraphs(props: Props) {
graphs.push(graphCard(tempComp, tempReadings)) graphs.push(graphCard(tempComp, tempReadings))
} }
//ambient //ambient
let ambientComp = compMap.get(ambient)
let ambientReadings = compMeasurements.get(ambient) let ambientReadings = compMeasurements.get(ambient)
if (ambientComp && ambientReadings){ if (ambientComp && ambientReadings){
graphs.push(graphCard(ambientComp, ambientReadings)) graphs.push(graphCard(ambientComp, ambientReadings))
@ -345,6 +345,7 @@ export default function GateGraphs(props: Props) {
const analyticGraphs = () => { const analyticGraphs = () => {
let tempComp = compMap.get(tempChain) let tempComp = compMap.get(tempChain)
let ambientComp = compMap.get(ambient)
return ( return (
<Box> <Box>
<GateFlowGraph <GateFlowGraph
@ -365,7 +366,7 @@ export default function GateGraphs(props: Props) {
/> />
{/* add a new chart here for the delta temp over time */} {/* add a new chart here for the delta temp over time */}
{tempComp && {tempComp &&
<GateDeltaTempGraph deviceID={device} start={startDate} end={endDate} tempComponent={tempComp}/> <GateDeltaTempGraph deviceID={device} start={startDate} end={endDate} tempComponent={tempComp} ambient={ambientComp}/>
} }
</Box> </Box>
); );

View file

@ -164,29 +164,49 @@ export default function GateList(props: Props) {
// } // }
const conditionDisplay = (gate: Gate) => { const conditionDisplay = (gate: Gate) => {
console.log(gate)
let display = "" let display = ""
let deltaTemp = 0
if(gate.status.pcaState === pond.PCAState.PCA_STATE_OFF){ if(gate.status.pcaState === pond.PCAState.PCA_STATE_OFF){
display = "Inactive" display = "Inactive"
} else if (gate.status.pcaState === pond.PCAState.PCA_STATE_UNKNOWN){ } else if (gate.status.pcaState === pond.PCAState.PCA_STATE_UNKNOWN){
display = "--" display = "--"
} else { //the pca is currently active calulate the delta temp (T2 - T1) provided there are two temps } else { //the pca is currently active calulate the delta temp (outlet - ambient)
//loop to find the temp readings let ambient = cloneDeep(gate.status.lastAmbientReading)
let clone = cloneDeep(gate.status.lastTempReading) let temp = cloneDeep(gate.status.lastTempReading)
clone.forEach(unitMeasurement => {
let um = UnitMeasurement.create(unitMeasurement, user) if(ambient.length > 0 && temp.length > 0){
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){ let ambientTemp: number | undefined
if(um.values.length > 0){ //as long as there is at least on thing in the measurements let outletTemp: number | undefined
let lastReading = um.values[um.values.length-1] //there should only be one measurement in here but just make sure to get the end of the array ambient.forEach(um => {
if(lastReading.values.length > 1){ //make sure there are at least two values in the array if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
console.log(lastReading.values) let clone = cloneDeep(um)
deltaTemp = lastReading.values[lastReading.values.length -1] - lastReading.values[0] //subtract the first value from the last value to get the delta let measurement = UnitMeasurement.any(clone, user)
if(measurement.values.length > 0){
let readings = measurement.values[measurement.values.length -1]
if(readings.values.length > 0){
ambientTemp = readings.values[readings.values.length -1]
}
} }
} }
})
temp.forEach(um => {
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
let clone = cloneDeep(um)
let measurement = UnitMeasurement.any(clone, user)
if(measurement.values.length > 0){
let readings = measurement.values[measurement.values.length -1]
if(readings.values.length > 0){
outletTemp = readings.values[readings.values.length -1]
}
}
}
})
if(ambientTemp && outletTemp){
let deltaTemp = outletTemp - ambientTemp
display = deltaTemp.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C")
}else{
display = "Sensor Missing"
} }
}) }
display = deltaTemp.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C")
} }
return ( return (
<Typography> <Typography>

View file

@ -43,8 +43,10 @@ const useStyles = makeStyles(() => ({
interface Props { interface Props {
finalTemp: number; finalTemp: number;
ambientTemp: number; ambientTemp: number;
innerTemps: { t1: number; t2: number }; //innerTemps: { t1: number; t2: number };
innerPressures: { p1: number; p2: number }; //innerPressures: { p1: number; p2: number };
ductTemp?: number;
ductPressure?: number;
ambientSelector: () => JSX.Element; ambientSelector: () => JSX.Element;
tempChainSelector: () => JSX.Element; tempChainSelector: () => JSX.Element;
pressureChainSelector: () => JSX.Element; pressureChainSelector: () => JSX.Element;
@ -57,8 +59,10 @@ export default function GateSVG(props: Props) {
const { const {
finalTemp, finalTemp,
ambientTemp, ambientTemp,
innerTemps, //innerTemps,
innerPressures, //innerPressures,
ductTemp,
ductPressure,
ambientSelector, ambientSelector,
tempChainSelector, tempChainSelector,
pressureChainSelector, pressureChainSelector,
@ -277,12 +281,15 @@ export default function GateSVG(props: Props) {
values.push( values.push(
<g key={"tempChain"} id={"tempChain"} data-name={"tempChain"}> <g key={"tempChain"} id={"tempChain"} data-name={"tempChain"}>
<text fontSize={7} className={classes.fontBase} x={30} y={149}> <text fontSize={7} className={classes.fontBase} x={30} y={149}>
T1: {innerTemps.t1} {/* T1: {innerTemps.t1}
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT {user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F" ? "°F"
: "°C"} : "°C"}
, T2: {innerTemps.t2} , T1: {innerTemps.t2}
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT {user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F"
: "°C"} */}
Temperature: {ductTemp ?? "N/A"} {user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F" ? "°F"
: "°C"} : "°C"}
</text> </text>
@ -292,12 +299,15 @@ export default function GateSVG(props: Props) {
values.push( values.push(
<g key={"pressureChain"} id={"pressureChain"} data-name={"pressureChain"}> <g key={"pressureChain"} id={"pressureChain"} data-name={"pressureChain"}>
<text fontSize={7} className={classes.fontBase} x={30} y={184}> <text fontSize={7} className={classes.fontBase} x={30} y={184}>
P1: {innerPressures.p1}{" "} {/* P1: {innerPressures.p1}{" "}
{user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER {user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER
? "iwg" ? "iwg"
: "kPa"} : "kPa"}
, P2: {innerPressures.p2}{" "} , P2: {innerPressures.p2}{" "}
{user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER {user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER
? "iwg"
: "kPa"} */}
Pressure: {ductPressure ?? "N/A"} {user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER
? "iwg" ? "iwg"
: "kPa"} : "kPa"}
</text> </text>

View file

@ -138,7 +138,6 @@ export default function GateProvider(props: PropsWithChildren<Props>) {
return new Promise<AxiosResponse<pond.ListGatesResponse>>((resolve, reject) => { return new Promise<AxiosResponse<pond.ListGatesResponse>>((resolve, reject) => {
get<pond.ListGatesResponse>(url).then(resp => { get<pond.ListGatesResponse>(url).then(resp => {
resp.data = pond.ListGatesResponse.fromObject(resp.data) resp.data = pond.ListGatesResponse.fromObject(resp.data)
console.log(resp)
return resolve(resp) return resolve(resp)
}).catch(err => { }).catch(err => {
return reject(err) return reject(err)