import { blue, brown, deepOrange, deepPurple, green, grey, lightGreen, orange, purple, red, teal, yellow } from "@mui/material/colors"; import { GraphType } from "common/Graph"; import { IsCardController } from "products/DeviceProduct"; import { pond } from "protobuf-ts/pond"; import { quack } from "protobuf-ts/quack"; import { getTextSecondary } from "theme/text"; // import { getTextSecondary } from "theme"; import { getDistanceUnit, getPressureUnit, getTemperatureUnit, roundTo } from "utils"; import { extract } from "utils/types"; interface Enumeration { label: string; colour: any; } interface NodeDetails { labels: string[]; colours: string[]; } interface MeasurementDetails { label: string; unit: string; factor: number; complexConversion: ((value: number, mode: "toStored" | "toDisplay") => number) | null; multiply: boolean; spacer: boolean; enumerations: Enumeration[]; colour: any; decimals: number; path: string; graph: GraphType; min: number; max: number; step: number; errorValue?: number; nodeDetails?: NodeDetails; } function enumeration(label: string, colour: any) { return { label, colour }; } /** * This is used to determine the measurements that will be shown on the y axis of the graph as well as the measurements table */ export class MeasurementDescriber { private measurementType: quack.MeasurementType; private details: MeasurementDetails; constructor( measurementType: quack.MeasurementType, componentType: quack.ComponentType, componentSubtype: number, product?: pond.DeviceProduct ) { this.measurementType = measurementType; this.details = { label: "Unknown", unit: "", factor: 1.0, complexConversion: null, multiply: false, spacer: false, enumerations: [], colour: "", decimals: 0, path: "", graph: GraphType.LINEAR, min: 0, max: 100, step: 1 }; switch (measurementType) { case quack.MeasurementType.MEASUREMENT_TYPE_INVALID: break; case quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE: let temperatureUnit = getTemperatureUnit(); let isFahrenheit = temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT; this.details.label = "Temperature"; this.details.unit = isFahrenheit ? "°F" : "°C"; this.details.factor = 10.0; this.details.complexConversion = isFahrenheit ? (value: number, mode: "toStored" | "toDisplay"): number => { if (mode === "toStored") { return ((value - 32.0) / (9.0 / 5.0)) * this.details.factor; } return (value / this.details.factor) * (9.0 / 5.0) + 32.0; } : null; this.details.colour = orange["500"]; this.details.decimals = 2; this.details.path = "temperature.celciusTimes10"; this.details.errorValue = -127; if (componentType === quack.ComponentType.COMPONENT_TYPE_DHT) { this.details.path = "humidityAndTemperature.celciusTimes10"; } if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) { this.details.graph = GraphType.AREA; if(componentSubtype === quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_DIAG){ this.details.label = "Cable ID" this.details.unit = "" } } if (componentType === quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT) { this.details.path = "vpd.celciusTimes10"; if (componentSubtype === quack.DHTSubtype.DHT_SUBTYPE_22) { this.details.decimals = 1; } } if (IsCardController(product)) { this.details.min = isFahrenheit ? 32 : 0; this.details.max = isFahrenheit ? 113 : 45; } else { this.details.min = isFahrenheit ? -40 : -40; this.details.max = isFahrenheit ? 212 : 100; } break; case quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN: this.details.label = "State"; this.details.enumerations = [enumeration("Off", "#f9a825"), enumeration("On", "#f9a825")]; this.details.path = "booleanOutput.value"; this.details.colour = "#f9a825"; this.details.min = 0; this.details.max = 1; if (componentType === quack.ComponentType.COMPONENT_TYPE_BOOLEAN_INPUT) { this.details.path = "booleanInput.value"; } if (componentType === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) { this.details.path = "edgeTriggered.state"; this.details.graph = GraphType.SCATTER; if (componentSubtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_DOOR) { this.details.enumerations = [ enumeration("Closed", "var(--status-ok)"), enumeration("Open", "var(--status-alert)") ]; } else if (componentSubtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_WATER) { this.details.colour = "#2196f3"; this.details.enumerations = [ enumeration("Stopped", getTextSecondary()), enumeration("Flowing", "#2196f3") ]; } else { this.details.enumerations = [ enumeration("OK", "var(--status-ok)"), enumeration("Triggered", "var(--status-alert)") ]; } } break; case quack.MeasurementType.MEASUREMENT_TYPE_VOLTAGE: this.details.label = "Voltage"; this.details.unit = "V"; this.details.factor = 10.0; this.details.colour = deepOrange["700"]; this.details.path = "voltage.microvolts"; this.details.decimals = 3; if (componentType === quack.ComponentType.COMPONENT_TYPE_POWER) { this.details.path = "power.inputVoltageTimes10"; this.details.decimals = 1; } if (componentType === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE) { this.details.unit = "mV"; this.details.graph = GraphType.MULTILINE; this.details.unit = "mV"; // this.details.nodeDetails = { // colours: ["white", "black", "red", "blue"], // labels: ["millivolt1", "millivolt2", "millivolt3", "millivolt4"] // }; } this.details.max = 30; break; case quack.MeasurementType.MEASUREMENT_TYPE_PERCENT: this.details.label = "Percent"; this.details.unit = "%"; this.details.errorValue = 200; this.details.factor = 100.0; this.details.colour = teal["500"]; this.details.decimals = 2; if (componentType === quack.ComponentType.COMPONENT_TYPE_DHT) { this.details.label = "Humidity"; this.details.path = "humidityAndTemperature.relativeHumidityTimes100"; this.details.decimals = 1; } if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) { this.details.label = "Humidity"; this.details.graph = GraphType.AREA; if(componentSubtype === quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_DIAG){ this.details.label = "Cable Type" this.details.unit = "" } // can be used for testing node splitting on a cable if you dont have a component that uses it // this.details.nodeDetails = { // colours: ["white", "black"], // labels: ["hum1", "hum2"] // }; } if (componentType === quack.ComponentType.COMPONENT_TYPE_MODEM) { this.details.label = "Quality"; this.details.factor = 2.0; this.details.path = "modemSignal.quality"; this.details.colour = orange["500"]; this.details.multiply = true; } if (componentType === quack.ComponentType.COMPONENT_TYPE_POWER) { this.details.label = "Charged"; this.details.path = "power.percentChargedTimes100"; this.details.colour = lightGreen["600"]; } if (componentType === quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT) { this.details.label = "Humidity"; this.details.path = "vpd.relativeHumidityTimes100"; if (componentSubtype === quack.DHTSubtype.DHT_SUBTYPE_22) { this.details.decimals = 1; } } if (componentType === quack.ComponentType.COMPONENT_TYPE_SEN5X) { this.details.label = "Humidity"; } break; case quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE: let pressureUnit = getPressureUnit(); let isIWG = pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER; this.details.label = "Pressure"; this.details.unit = isIWG ? "iwg" : "kPa"; this.details.factor = isIWG ? 248.84 : 1000.0; this.details.spacer = true; this.details.colour = deepPurple["300"]; this.details.path = "pressure.pascals"; this.details.decimals = 2; if (componentType === quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT) { this.details.path = "vpd.pascals"; if (componentSubtype === quack.DHTSubtype.DHT_SUBTYPE_22) { this.details.decimals = 1; } } else if (componentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE) { this.details.path = "pressureCable.pascals"; this.details.graph = GraphType.AREA; } this.details.max = isIWG ? 20 : 5; this.details.step = 0.1; break; case quack.MeasurementType.MEASUREMENT_TYPE_RSSI: this.details.label = "Strength"; this.details.unit = "dBm"; this.details.spacer = true; this.details.colour = yellow["500"]; this.details.path = "modemSignal.rssi"; this.details.min = -150; this.details.max = 0; break; case quack.MeasurementType.MEASUREMENT_TYPE_LIGHT: this.details.label = "Illuminance"; this.details.unit = "lux"; this.details.factor = 100.0; this.details.spacer = true; this.details.colour = orange["500"]; this.details.path = "light.luxTimes100"; this.details.decimals = 2; this.details.max = 20000; break; case quack.MeasurementType.MEASUREMENT_TYPE_PPB: this.details.label = "Concentration"; this.details.spacer = true; this.details.unit = "ppb"; this.details.colour = green["500"]; this.details.decimals = 3; this.details.max = 300; this.details.path = "concentration.ppb"; switch (componentType) { case quack.ComponentType.COMPONENT_TYPE_CALCIUM: this.details.path = "concentration.ppb"; this.details.colour = teal["500"]; break; case quack.ComponentType.COMPONENT_TYPE_NITRATE: this.details.path = "concentration.ppb"; this.details.colour = green["500"]; break; case quack.ComponentType.COMPONENT_TYPE_POTASSIUM: this.details.path = "concentration.ppb"; this.details.colour = yellow["500"]; break; case quack.ComponentType.COMPONENT_TYPE_ETHYLENE: this.details.colour = red["300"]; this.details.max = 500000; this.details.path = "gas.ppb"; break; case quack.ComponentType.COMPONENT_TYPE_AIR_QUALITY: this.details.path = "gas.ppb"; this.details.label = "TVOC"; this.details.max = 60000; this.details.decimals = 1; this.details.colour = green["300"]; break; default: break; } break; case quack.MeasurementType.MEASUREMENT_TYPE_PPM: this.details.label = "Gas"; this.details.spacer = true; this.details.unit = "ppm"; this.details.path = "gas.ppm"; this.details.colour = brown["300"]; switch (componentType) { case quack.ComponentType.COMPONENT_TYPE_CO2: this.details.max = 5000; break; case quack.ComponentType.COMPONENT_TYPE_O2: this.details.max = 500000; break; case quack.ComponentType.COMPONENT_TYPE_AIR_QUALITY: this.details.label = "eCO2"; this.details.max = 60000; break; case quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE: this.details.graph = GraphType.MULTILINE; switch (componentSubtype) { case quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_DEBUG_VOLTAGE: this.details.label = "Voltage"; this.details.unit = "mV"; break; case quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_O2: case quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_LEL: this.details.colour = blue["400"]; this.details.decimals = 1; this.details.unit = "%"; this.details.complexConversion = ( value: number, mode: "toStored" | "toDisplay" ): number => { if (mode === "toStored") { //for O2/LEL dragers the display is in percentage so convert the percent to ppm return value * 10000; } return value / 10000; }; break; case quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_NO2: this.details.colour = teal["500"]; break; case quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_CO: this.details.colour = grey["500"]; } break; default: break; } break; case quack.MeasurementType.MEASUREMENT_TYPE_ANALOG: this.details.label = "Value"; this.details.colour = orange["500"]; this.details.path = "analogInput.value"; this.details.max = 50; switch (componentType) { case quack.ComponentType.COMPONENT_TYPE_ANALOG_INPUT: if (componentSubtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL) { this.details.factor = 10.0; this.details.max = 100; this.details.min = 0; this.details.decimals = 1; this.details.label = "Fuel"; this.details.unit = "%"; this.details.colour = "#CC9933"; } else if ( componentSubtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_WIND_DIRECTION ) { this.details.label = "Wind Direction"; this.details.colour = green["500"]; this.details.graph = GraphType.RADAR; } } break; case quack.MeasurementType.MEASUREMENT_TYPE_ORP: this.details.label = "ORP"; this.details.unit = "mV"; this.details.factor = 1000.0; this.details.spacer = true; this.details.colour = blue["200"]; this.details.path = "orp.microvolts"; this.details.decimals = 3; this.details.max = 1000; break; case quack.MeasurementType.MEASUREMENT_TYPE_PH: this.details.label = "pH"; this.details.unit = "pH"; this.details.factor = 100000.0; this.details.spacer = true; this.details.colour = purple["500"]; this.details.path = "ph.phTimes100000"; this.details.decimals = 3; this.details.min = 0; this.details.max = 14; this.details.step = 0.1; break; case quack.MeasurementType.MEASUREMENT_TYPE_CONDUCTIVITY: this.details.label = "Conductivity"; this.details.unit = "μS/cm"; this.details.spacer = true; this.details.colour = orange["500"]; this.details.path = "conductivity.microsiemensPerCm"; this.details.decimals = 3; this.details.max = 5000; break; case quack.MeasurementType.MEASUREMENT_TYPE_CAPACITANCE: this.details.label = "Capacitance"; this.details.unit = "nF"; this.details.path = "capacitance.picofarads"; if (componentType === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE) { this.details.unit = "fF"; } this.details.factor = 1000.0; this.details.spacer = true; this.details.colour = orange["500"]; this.details.decimals = 3; this.details.max = 100000; break; case quack.MeasurementType.MEASUREMENT_TYPE_WEIGHT: this.details.label = "Weight"; this.details.unit = "g"; this.details.colour = green["500"]; this.details.path = "weight.grams"; this.details.max = 100000; break; case quack.MeasurementType.MEASUREMENT_TYPE_DISTANCE_CM: let distanceUnit = getDistanceUnit(); this.details.label = "Distance"; this.details.unit = "cm"; if (distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET) { this.details.unit = "ft"; } if (distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS) { this.details.unit = "m"; } this.details.colour = blue["400"]; this.details.path = "distance.cm"; this.details.max = 100000; break; case quack.MeasurementType.MEASUREMENT_TYPE_SPEED: this.details.label = "Speed"; this.details.unit = "km/h"; this.details.colour = green["500"]; this.details.path = "edgeTriggered.rises"; this.details.max = 500; this.details.min = 0; break; case quack.MeasurementType.MEASUREMENT_TYPE_RAIN: this.details.label = "Rain"; this.details.unit = "mm"; this.details.colour = blue["500"]; this.details.path = "edgeTriggered.rises"; break; case quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC: this.details.label = "Grain Moisture"; this.details.unit = "% WB"; this.details.decimals = 2; this.details.colour = yellow["500"]; this.details.graph = GraphType.AREA; break; case quack.MeasurementType.MEASUREMENT_TYPE_CFM: this.details.label = "Airflow"; this.details.unit = "CFM"; this.details.colour = blue["300"]; break; case quack.MeasurementType.MEASUREMENT_TYPE_CAPACITANCE_GND_FF: this.details.label = "Capacitance(Ground Ref)"; this.details.unit = "fF"; this.details.colour = orange["500"]; this.details.graph = GraphType.AREA; break; case quack.MeasurementType.MEASUREMENT_TYPE_CAPACITANCE_SELF_FF: this.details.label = "Capacitance(Self Ref)"; this.details.unit = "fF"; this.details.colour = orange["500"]; this.details.graph = GraphType.AREA; break; case quack.MeasurementType.MEASUREMENT_TYPE_DUST_1UG: this.details.label = "Dust <1um"; this.details.unit = "ug/m3"; this.details.factor = 10; this.details.decimals = 2; this.details.multiply = false; this.details.colour = lightGreen["500"]; break; case quack.MeasurementType.MEASUREMENT_TYPE_DUST_2UG: this.details.label = "Dust <2.5um"; this.details.unit = "ug/m3"; this.details.factor = 10; this.details.decimals = 2; this.details.multiply = false; this.details.colour = lightGreen["400"]; break; case quack.MeasurementType.MEASUREMENT_TYPE_DUST_4UG: this.details.label = "Dust <4um"; this.details.unit = "ug/m3"; this.details.factor = 10; this.details.decimals = 2; this.details.multiply = false; this.details.colour = lightGreen["300"]; break; case quack.MeasurementType.MEASUREMENT_TYPE_DUST_10UG: this.details.label = "Dust <10um"; this.details.unit = "ug/m3"; this.details.factor = 10; this.details.decimals = 2; this.details.multiply = false; this.details.colour = lightGreen["200"]; break; case quack.MeasurementType.MEASUREMENT_TYPE_VOC_INDEX: this.details.label = "VOC percent"; this.details.unit = "%"; this.details.factor = 10; this.details.multiply = false; this.details.colour = teal["400"]; break; case quack.MeasurementType.MEASUREMENT_TYPE_NOX_INDEX: this.details.label = "NOX percent"; this.details.unit = "%"; this.details.factor = 10; this.details.multiply = false; this.details.colour = teal["300"]; break; case quack.MeasurementType.MEASUREMENT_TYPE_FREQUENCY: this.details.label = "Frequency"; this.details.unit = "Hz"; this.details.colour = green["300"]; break; case quack.MeasurementType.MEASUREMENT_TYPE_SINGLE_ACCELERATION_RMS: case quack.MeasurementType.MEASUREMENT_TYPE_TOTAL_ACCELERATION_RMS: this.details.label = "Acceleration"; this.details.unit = "m/s²"; this.details.colour = yellow["300"]; break; } } isErrorMeasurement(measurement?: quack.Measurement | null): boolean { if (this.details.errorValue === undefined || !measurement || this.details.path === "") return false; let value = extract(measurement, this.details.path, 0); return this.details.errorValue === value; } GetErrorValue() { return this.details.errorValue; } GetUnit() { return this.details.unit; } toDisplay(value: number): number { let converted; if (this.details.complexConversion !== null) { converted = this.details.complexConversion(value, "toDisplay"); } else { converted = this.details.multiply ? value * this.details.factor : value / this.details.factor; } return roundTo(converted, this.details.decimals); } toStored(value: number): number { let converted; if (this.details.complexConversion !== null) { converted = this.details.complexConversion(value, "toStored"); } else { converted = this.details.multiply ? value / this.details.factor : value * this.details.factor; } return converted; } forDisplay(measurement?: quack.Measurement | null): number { if (!measurement) return 0; if (this.details.path === "") return 0; let storedValue = extract(measurement, this.details.path, 0); return this.toDisplay(storedValue); } convertWithUnits(value: number, unitMeasurement?: boolean): string { let converted = unitMeasurement ? value : this.toDisplay(value); if ( this.details.enumerations.length > 0 && converted >= 0 && this.details.enumerations.length > converted ) { return this.details.enumerations[converted].label; } return ( converted.toFixed(this.details.decimals) + (this.details.spacer ? " " : "") + this.details.unit ); } convertWithoutUnits(value: number, unitMeasurement?: boolean): string { let converted = unitMeasurement ? value : this.toDisplay(value); if ( this.details.enumerations.length > 0 && converted >= 0 && this.details.enumerations.length > converted ) { return this.details.enumerations[converted].label; } return converted.toFixed(this.details.decimals); } withUnits(measurement: quack.Measurement): string { return this.convertWithUnits(extract(measurement, this.details.path, 0)); } withoutUnits(measurement: quack.Measurement): string { return this.convertWithoutUnits(extract(measurement, this.details.path, 0)); } type(): quack.MeasurementType { return this.measurementType; } label(): string { return this.details.label; } unit(): string { return this.details.unit; } colour(measurement?: quack.Measurement): any { if (measurement) { let value = this.forDisplay(measurement); if ( this.details.enumerations.length > 0 && value >= 0 && this.details.enumerations.length > value ) { return this.details.enumerations[value].colour; } } return this.details.colour; } nodeDetails(): NodeDetails | undefined { return this.details.nodeDetails; } graph(): GraphType { return this.details.graph; } enumerations(): string[] { return this.details.enumerations.map(e => e.label); } min(): number { return this.details.min; } max(): number { return this.details.max; } step(): number { return this.details.step; } decimals(): number { return this.details.decimals; } } export function describeMeasurement( measurementType: quack.MeasurementType | null | undefined, componentType: quack.ComponentType | null | undefined = quack.ComponentType .COMPONENT_TYPE_INVALID, componentSubtype: number = 0, product?: pond.DeviceProduct ): MeasurementDescriber { measurementType = measurementType ? measurementType : quack.MeasurementType.MEASUREMENT_TYPE_INVALID; componentType = componentType ? componentType : quack.ComponentType.COMPONENT_TYPE_INVALID; return new MeasurementDescriber(measurementType, componentType, componentSubtype, product); }