import { Avatar, Box, Card, CardContent, CardHeader, Grid2 as Grid, Theme, Typography, useTheme } from "@mui/material"; import { useDeviceAPI, useGlobalState, useMutationAPI } from "providers"; import { Component, Device } from "models"; import moment from "moment"; import { pond } from "protobuf-ts/pond"; import { quack } from "protobuf-ts/quack"; import { GetDefaultDateRange } from "common/time/DateRange"; import React, { useEffect, useState } from "react"; import MultiLineGraph, { LineData } from "charts/measurementCharts/MultiLineGraph"; import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; import DateSelect from "common/time/DateSelect"; import { Area, AreaChart, ReferenceArea, ResponsiveContainer, Tooltip, TooltipProps, XAxis, YAxis } from "recharts"; import FuelDark from "assets/components/fuelDark.png"; import FuelLight from "assets/components/fuelLight.png"; import { useThemeType } from "hooks"; import { GetComponentIcon } from "pbHelpers/ComponentType"; import UnitMeasurementSummary from "component/UnitMeasurementSummary"; import { UnitMeasurement } from "models/UnitMeasurement"; import MaterialChartTooltip from "charts/MaterialChartTooltip"; import { roundTo } from "utils"; import { makeStyles } from "@mui/styles"; //import TimeBar from "common/time/TimeBar"; interface Props { device: pond.ComprehensiveDevice; linearMutations: pond.LinearMutation[]; xDomain: string[] | number[]; changeXDomain: (domain: string[] | number[]) => void; getFuelLevel?: (level: string) => void; } interface GraphData { time: number; value: number; } interface MutationData { mutation: pond.LinearMutation; data: pond.ValueAt[]; } const useStyles = makeStyles((theme: Theme) => ({ avatarIcon: { width: theme.spacing(3), height: theme.spacing(3) }, card: { height: 550 }, cardHeader: { height: "20%" }, cardContent: { height: "80%" } }) ); export default function ObjectHeaterCharts(props: Props) { const { device, linearMutations, getFuelLevel, xDomain, changeXDomain } = props; const deviceAPI = useDeviceAPI(); const mutationAPI = useMutationAPI(); const [tempHum, setTempHum] = useState(); const [ambientTemps, setAmbientTemps] = useState([]); const [heaterTemps, setHeaterTemps] = useState([]); const [heaterState, setHeaterState] = useState([]); const [tempOnly, setTempOnly] = useState(); const [heater, setHeater] = useState(); const [start, setStart] = useState(GetDefaultDateRange().start); const [end, setEnd] = useState(GetDefaultDateRange().end); const now = moment(); const theme = useTheme(); const [mutationData, setMutationData] = useState([]); const [{ user, as }] = useGlobalState(); const themeType = useThemeType(); const classes = useStyles(); const [mutationsLoading, setMutationsLoading] = useState(false); const [refLeft, setRefLeft] = useState(); const [refRight, setRefRight] = useState(); useEffect(() => { if (!mutationsLoading) { setMutationsLoading(true); //make call to the backend to get measurements for the mutation let md: MutationData[] = []; linearMutations.forEach(mut => { mutationAPI.linearMutation(mut, start.toISOString(), end.toISOString()).then(resp => { let d: pond.ValueAt[] = resp.data.values; if (mut.mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) { d = []; resp.data.values.forEach(valAt => { d.push( pond.ValueAt.create({ time: valAt.time, value: valAt.value / 10 }) ); }); } md.push({ mutation: mut, data: d }); if (md.length >= linearMutations.length) { setMutationsLoading(false); } setMutationData([...md]); if (getFuelLevel && mut.mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) { if (resp.data.values.length > 0) { getFuelLevel( (resp.data.values[resp.data.values.length - 1].value / 10).toFixed(2) + "%" ); } } }); }); } }, [linearMutations, start, end, getFuelLevel, mutationAPI]); //eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { let tempHum: Component | undefined = undefined; let tempOnly: Component | undefined = undefined; let heater: Component | undefined = undefined; let dev = Device.any(device.device); let components = device.components; let measurementsFor: Component[] = []; components.forEach(comp => { let c = Component.any(comp); if (c.settings.type === quack.ComponentType.COMPONENT_TYPE_DHT) { tempHum = c; measurementsFor.push(c); } if (c.settings.type === quack.ComponentType.COMPONENT_TYPE_TEMPERATURE) { tempOnly = c; measurementsFor.push(c); } if (c.settings.type === quack.ComponentType.COMPONENT_TYPE_BOOLEAN_OUTPUT) { if (c.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER) { heater = c; measurementsFor.push(c); } } }); setTempHum(tempHum); setTempOnly(tempOnly); setHeater(heater); deviceAPI .listJSONMeasurements(dev.id(), measurementsFor, start, end, 0, 0, "asc", "timestamp", as) .then(resp => { resp.data.components.forEach(comp => { if (comp.componentId === tempHum?.key()) { if (comp.measurementType === "Temperature") { let lineData: LineData[] = []; comp.exportedMeasurements.forEach(m => { lineData.push({ timestamp: moment(m.timestamp).valueOf(), points: m.measurementValue.map((t, i) => ({ value: convertTemp(t), node: i })) }); }); setAmbientTemps(lineData); } } if (comp.componentId === tempOnly?.key()) { if (comp.measurementType === "Temperature") { setHeaterTemps(comp.exportedMeasurements); } } if (comp.componentId === heater?.key()) { if (comp.measurementType === "State") { setHeaterState(comp.exportedMeasurements); } } }); }); }, [device, deviceAPI, start, end, as]); //eslint-disable-line react-hooks/exhaustive-deps const updateDateRange = (newStartDate: any, newEndDate: any, live: boolean) => { setStart(newStartDate); setEnd(newEndDate); // setLive(live); // if (props.updateDate) { // props.updateDate(newStartDate, newEndDate, live); // } }; const convertTemp = (temp: number) => { let t = temp; if (user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { t = temp * 1.8 + 32; } return t; }; //had to make custom chart for this in order to show the heater state and the temp of the ds18 const heaterStateWithTemps = (tempComponent: Component, heaterComponent: Component) => { let tempDescriber = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, tempComponent.type(), tempComponent.subType(), undefined, user ); let heaterDescriber = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN, heaterComponent.type(), heaterComponent.subType(), undefined, user ); let tempData: LineData[] = []; heaterTemps.forEach(ht => { tempData.push({ timestamp: moment(ht.timestamp).valueOf(), points: ht.measurementValue.map((t, i) => ({ value: convertTemp(t), node: i })) }); }); let stateData: LineData[] = []; heaterState.forEach(hs => { stateData.push({ timestamp: moment(hs.timestamp).valueOf(), points: hs.measurementValue.map((s, i) => ({ value: s, node: i })) }); }); return ( { changeXDomain(domain); }} multiGraphZoomOut /> { changeXDomain(domain); }} multiGraphZoomOut /> ); }; //// TODO-CS: Make a mutator describer to put the get functions into //// const getMutationIcon = (mutation: pond.Mutator, themeType: string) => { if (mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) { return themeType === "light" ? FuelDark : FuelLight; } return ""; }; const getMutationLabel = (mutation: pond.Mutator) => { if (mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) { return "Fuel Level"; } return ""; }; const getMutationColour = (mutation: pond.Mutator) => { if (mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) { return "lightBlue"; } return ""; }; const getMutationUnit = (mutation: pond.Mutator) => { if (mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) { return "%"; } return ""; }; //// /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ //// const zoom = () => { let newDomain: number[] | string[] = ["dataMin", "dataMax"]; if (refLeft && refRight && refLeft !== refRight) { refLeft < refRight ? (newDomain = [refLeft, refRight]) : (newDomain = [refRight, refLeft]); setRefLeft(undefined); setRefRight(undefined); changeXDomain(newDomain); } }; const mutationGraphCard = (mutation: pond.LinearMutation, data: pond.ValueAt[], key: string) => { let graphData: GraphData[] = []; let lastReading: pond.ValueAt | undefined; data.forEach(d => { graphData.push({ time: moment(d.time).valueOf(), value: d.value }); lastReading = d; }); return ( {graphData.length > 0 ? ( } title={mutation.mutationName} titleTypographyProps={{ variant: "subtitle1" }} subheader={ {getMutationLabel(mutation.mutation) + ": "}{" "} {lastReading?.value.toFixed(2) + getMutationUnit(mutation.mutation)} {" "}
{moment(lastReading?.time).fromNow()}
} /> { if (e) { setRefLeft(e.activeLabel); } }} onMouseMove={(e: any) => { if (e) { setRefRight(e.activeLabel); } }} onMouseUp={() => { setRefLeft(undefined); setRefRight(undefined); zoom(); }}> moment(timestamp).format("lll")} content={(props: TooltipProps) => { return ( roundTo(parseFloat(String(value)), 2).toString() } /> ); }} /> { let t = moment(timestamp); return now.isSame(t, "day") ? t.format("LT") : t.format("MMM DD"); }} scale="time" type="number" tick={{ fill: theme.palette.text.primary }} stroke={theme.palette.divider} interval="preserveStartEnd" /> {refLeft && refRight ? ( ) : null}
) : ( No measurements for selected timeframe )}
); }; const mutationGraphs = () => { let graphCards: JSX.Element[] = []; if (mutationData.length === 0) { return ( No Calculated Measurements Set ); } mutationData.forEach((mutation, i) => { graphCards.push(mutationGraphCard(mutation.mutation, mutation.data, "mutation" + i)); }); return graphCards; }; return ( {/* */} {tempOnly && heater && ( {(heaterTemps.length > 0 || heaterState.length > 0) ? ( {tempOnly && ( } title={tempOnly.name()} subheader={ UnitMeasurement.any(um, user)) )} /> } titleTypographyProps={{ variant: "subtitle1" }} /> )} {heater && ( } title={heater.name()} subheader={ UnitMeasurement.create(um, user)) )} /> } titleTypographyProps={{ variant: "subtitle1" }} /> )} {heaterStateWithTemps(tempOnly, heater)} ) : ( No measurements for selected timeframe )} )} {tempHum && ( {ambientTemps.length > 0 ? ( } title={tempHum.name()} subheader={ um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE ) .map(um => UnitMeasurement.any(um, user)) )} /> } titleTypographyProps={{ variant: "subtitle1" }} /> { changeXDomain(domain); }} multiGraphZoomOut /> ) : ( No measurements for selected timeframe )} )} {mutationGraphs()} ); }