expanded component extensions with secondary components (mainly due to the scd30)

This commit is contained in:
csawatzky 2025-07-02 16:49:38 -06:00
parent 1f733d1d01
commit 98717b7c00
5 changed files with 131 additions and 75 deletions

2
package-lock.json generated
View file

@ -10889,7 +10889,7 @@
}, },
"node_modules/protobuf-ts": { "node_modules/protobuf-ts": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#872519b46c04050d5cdf1c0efee7236119a83ed2", "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#c03dc20d337c77fbe06dfe880cd35fff28c9c244",
"dependencies": { "dependencies": {
"protobufjs": "^6.8.8" "protobufjs": "^6.8.8"
} }

View file

@ -49,6 +49,7 @@ export default function DeviceScannedComponents(props: Props){
},[scannedComponents]) },[scannedComponents])
useEffect(() => { useEffect(() => {
console.log(components)
let steps: CompStep[] = [] let steps: CompStep[] = []
components.forEach(comp => { components.forEach(comp => {
steps.push({ steps.push({
@ -117,7 +118,7 @@ export default function DeviceScannedComponents(props: Props){
variant="contained" variant="contained"
color="primary" color="primary"
onClick={() => { onClick={() => {
close(); setOpenDialog(false)
}}> }}>
Cancel Cancel
</Button> </Button>
@ -164,20 +165,23 @@ export default function DeviceScannedComponents(props: Props){
); );
}; };
const componentSelection = (newComponent: Component, checked: boolean) => { const componentSelection = (newComponents: Component[], checked: boolean) => {
let cloneList = cloneDeep(components) let cloneList = cloneDeep(components)
if(checked){ if(checked){
//add the component to the list //add the component to the list
cloneList.push(newComponent) cloneList = cloneList.concat(newComponents)
}else{ }else{
//remove any components that match the component location: (type)-(addressType)-(address) newComponents.forEach(newComponent => {
components.forEach((comp, i) => { //remove any components that match the component location: (type)-(addressType)-(address)
//check the components location against the one in the list cloneList.forEach((comp, i) => {
if(comp.locationString() === newComponent.locationString()){ //check the components location against the one in the list
cloneList.splice(i, 1) if(comp.locationString() === newComponent.locationString()){
} cloneList.splice(i, 1)
}
})
}) })
} }
console.log(cloneList)
setComponents(cloneList) setComponents(cloneList)
} }

View file

@ -1,16 +1,16 @@
import { Box, Button, Checkbox, FormControlLabel, Grid2, MenuItem, Radio, RadioGroup, TextField, Tooltip, Typography } from "@mui/material"; import { Box, Button, Checkbox, FormControlLabel, Grid2, MenuItem, Radio, RadioGroup, TextField, Tooltip, Typography } from "@mui/material";
import { Component } from "models"; import { Component } from "models";
import { getAddressTypes, getFriendlyName, getSubtypes, Subtype } from "pbHelpers/ComponentType"; import { getAddressTypes, getFriendlyName, GetSecondaryComponents, getSubtypes, Subtype } from "pbHelpers/ComponentType";
import { ComponentAvailabilityMap, DevicePositions } from "pbHelpers/DeviceAvailability"; import { ComponentAvailabilityMap, DevicePositions } from "pbHelpers/DeviceAvailability";
import { GetProductAvailability } from "products/DeviceProduct"; import { GetProductAvailability } from "products/DeviceProduct";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack"; import { quack } from "protobuf-ts/quack";
import { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
interface Props { interface Props {
decimalAddress: number decimalAddress: number
deviceProduct: pond.DeviceProduct deviceProduct: pond.DeviceProduct
componentSelectionCallback: (component: Component, checked: boolean) => void componentSelectionCallback: (component: Component[], checked: boolean) => void
availablePositions: DevicePositions availablePositions: DevicePositions
sensorNum?: number sensorNum?: number
} }
@ -19,8 +19,9 @@ export default function ScannedI2C(props: Props){
const {decimalAddress, deviceProduct, componentSelectionCallback, availablePositions, sensorNum} = props const {decimalAddress, deviceProduct, componentSelectionCallback, availablePositions, sensorNum} = props
const [types, setTypes] = useState<quack.ComponentType[]>([]) const [types, setTypes] = useState<quack.ComponentType[]>([])
const [subtypeOptions, setSubtypeOptions] = useState<Array<Subtype>>([]) const [subtypeOptions, setSubtypeOptions] = useState<Array<Subtype>>([])
const [selectedType, setSelectedType] = useState<quack.ComponentType>(quack.ComponentType.COMPONENT_TYPE_INVALID) const [selectedPrimaryType, setSelectedPrimaryType] = useState<quack.ComponentType>(quack.ComponentType.COMPONENT_TYPE_INVALID)
const [selectedSubtype, setSelectedSubtype] = useState(0) const [selectedPrimarySubtype, setSelectedPrimarySubtype] = useState(0)
const [added,setAdded] = useState(false)
const [addressInUse, setAddressInUse] = useState(false) const [addressInUse, setAddressInUse] = useState(false)
useEffect(()=>{ useEffect(()=>{
@ -41,7 +42,7 @@ export default function ScannedI2C(props: Props){
} }
//this if can be taken out if we dont want to select the first component type by default //this if can be taken out if we dont want to select the first component type by default
if(types.length > 0) { if(types.length > 0) {
setSelectedType(types[0]) setSelectedPrimaryType(types[0])
buildSubtypeOptions(types[0]) buildSubtypeOptions(types[0])
checkAddressAvailable(types[0]) checkAddressAvailable(types[0])
} }
@ -60,7 +61,7 @@ export default function ScannedI2C(props: Props){
}) })
//set the selected on to be the first in the valid selection //set the selected on to be the first in the valid selection
if(validSub.length > 0){ if(validSub.length > 0){
setSelectedSubtype(validSub[0].key) setSelectedPrimarySubtype(validSub[0].key)
} }
setSubtypeOptions(validSub) setSubtypeOptions(validSub)
} }
@ -78,15 +79,25 @@ export default function ScannedI2C(props: Props){
const componentSelected = (event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => { const componentSelected = (event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => {
let toAdd: Component[] = []
setAdded(true)
//build a component with given information and defaults for the rest //build a component with given information and defaults for the rest
let component = Component.create() let primaryComponent = Component.create()
component.settings.type = selectedType primaryComponent.settings.type = selectedPrimaryType
component.settings.subtype = selectedSubtype primaryComponent.settings.subtype = selectedPrimarySubtype
component.settings.addressType = quack.AddressType.ADDRESS_TYPE_I2C primaryComponent.settings.addressType = quack.AddressType.ADDRESS_TYPE_I2C
component.settings.address = decimalAddress primaryComponent.settings.address = decimalAddress
component.settings.name = getFriendlyName(selectedType, selectedSubtype) primaryComponent.settings.name = getFriendlyName(selectedPrimaryType, selectedPrimarySubtype)
toAdd.push(primaryComponent)
GetSecondaryComponents(selectedPrimaryType, selectedPrimarySubtype).forEach(secComp => {
let component = Component.create()
component.settings = secComp
toAdd.push(component)
})
//return the component and the checked status to the parent //return the component and the checked status to the parent
componentSelectionCallback(component, checked) componentSelectionCallback(toAdd, checked)
} }
@ -94,58 +105,64 @@ export default function ScannedI2C(props: Props){
<Box sx={{marginY: 1}}> <Box sx={{marginY: 1}}>
{sensorNum && <Typography>Sensor {sensorNum}</Typography>} {sensorNum && <Typography>Sensor {sensorNum}</Typography>}
{types.length > 0 ? {types.length > 0 ?
<Grid2 container direction="row" alignItems="center" justifyContent="space-between"> <React.Fragment>
<Grid2 size={3}> <Grid2 container direction="row" alignItems="center" justifyContent="space-between">
<RadioGroup <Grid2 size={3}>
value={selectedType} <RadioGroup
onChange={(_, value) => { value={selectedPrimaryType}
let v = parseInt(value) onChange={(_, value) => {
if (!isNaN(v)){ let v = parseInt(value)
buildSubtypeOptions(v) if (!isNaN(v)){
checkAddressAvailable(v) buildSubtypeOptions(v)
} checkAddressAvailable(v)
}}> }
{types.map((type, i) => { }}>
return ( {types.map((type, i) => {
<FormControlLabel return (
key={i} <FormControlLabel
value={type} key={i}
control={<Radio />} value={type}
label={getFriendlyName(type)} control={<Radio />}
/> label={getFriendlyName(type)}
) />
})} )
</RadioGroup> })}
</Grid2> </RadioGroup>
<Grid2 size={8}> </Grid2>
{subtypeOptions.length > 0 && <Grid2 size={8}>
<TextField {subtypeOptions.length > 0 &&
value={selectedSubtype} <TextField
fullWidth value={selectedPrimarySubtype}
onChange={(event)=>{ fullWidth
let val = +event.target.value onChange={(event)=>{
setSelectedSubtype(val) let val = +event.target.value
}} setSelectedPrimarySubtype(val)
select> }}
{subtypeOptions.map(s => ( select>
<MenuItem value={s.key}>{s.friendlyName}</MenuItem> {subtypeOptions.map(s => (
))} <MenuItem value={s.key}>{s.friendlyName}</MenuItem>
</TextField> ))}
} </TextField>
</Grid2>
<Grid2 size={1}>
<Tooltip
title="Add to list to be added to the device"
children={
<Checkbox
onChange={componentSelected}
disabled={addressInUse}
/>
} }
/> </Grid2>
<Grid2 size={1}>
<Tooltip
title="Add to list to be added to the device"
children={
<Checkbox
value={added}
onChange={componentSelected}
//disabled={addressInUse}
/>
}
/>
</Grid2>
</Grid2> </Grid2>
</Grid2> {
}
</React.Fragment>
: :
<Typography>Sensor Not Supported By Product</Typography> <Typography>Sensor Not Supported By Product</Typography>
} }

View file

@ -164,6 +164,7 @@ export interface ComponentTypeExtension {
smoothingAverages?: number, smoothingAverages?: number,
filters?: GraphFilters filters?: GraphFilters
) => LineChartData; ) => LineChartData;
secondaryComponentSettings?: () => pond.ComponentSettings[]
} }
export interface Summary { export interface Summary {
@ -621,6 +622,11 @@ export function GetComponentIcon(
return describer.icon ? describer.icon(theme) : undefined; return describer.icon ? describer.icon(theme) : undefined;
} }
export function GetSecondaryComponents(type: quack.ComponentType, subtype?: number): pond.ComponentSettings[] {
let describer = extension(type, subtype)
return describer.secondaryComponentSettings ? describer.secondaryComponentSettings() : []
}
//TODO: deprecated function, new graphs are handled in measurementChart (using reCharts instead of victory) //TODO: deprecated function, new graphs are handled in measurementChart (using reCharts instead of victory)
// export function getGraphProps( // export function getGraphProps(
// componentType: quack.ComponentType, // componentType: quack.ComponentType,

View file

@ -25,7 +25,18 @@ export function CO2(subtype: number = 0): ComponentTypeExtension {
); );
return { return {
type: quack.ComponentType.COMPONENT_TYPE_CO2, type: quack.ComponentType.COMPONENT_TYPE_CO2,
subtypes: [], subtypes: [
{
key: quack.Co2Subtype.CO2_SUBTYPE_NONE,
value: "CO2_SUBTYPE_NONE",
friendlyName: "CO2 Only"
},
{
key: quack.Co2Subtype.CO2_SUBTYPE_SCD30,
value: "CO2_SUBTYPE_SCD30",
friendlyName: "SCD30 (CO2)"
},
],
friendlyName: "CO2", friendlyName: "CO2",
description: "Measures the parts per million of CO2", description: "Measures the parts per million of CO2",
isController: false, isController: false,
@ -71,6 +82,24 @@ export function CO2(subtype: number = 0): ComponentTypeExtension {
minMeasurementPeriodMs: 5000, minMeasurementPeriodMs: 5000,
icon: (theme?: "light" | "dark"): string | undefined => { icon: (theme?: "light" | "dark"): string | undefined => {
return theme === "light" ? Co2SensorDarkIcon : Co2SensorLightIcon; return theme === "light" ? Co2SensorDarkIcon : Co2SensorLightIcon;
},
secondaryComponentSettings: () => {
let secondaryComps: pond.ComponentSettings[] = []
switch (subtype){ //doesn't have a default because it would just do nothing
case quack.Co2Subtype.CO2_SUBTYPE_SCD30:
let tempHumComp: pond.ComponentSettings = pond.ComponentSettings.create()
//set the defaults for the component
tempHumComp.type = quack.ComponentType.COMPONENT_TYPE_DHT
tempHumComp.subtype = quack.DHTSubtype.DHT_SUBTYPE_SCD30
tempHumComp.addressType = quack.AddressType.ADDRESS_TYPE_I2C
tempHumComp.address = 0x40
tempHumComp.name = "SCD30 (Temp/Humidity)"
//add it to the array to be returned
secondaryComps.push(tempHumComp)
break
}
//switch statement here that uses the subtype to determine whaat the secondary component is
return secondaryComps
} }
}; };
} }