Merge branch 'i2c_detect' into staging_environment

This commit is contained in:
csawatzky 2025-07-02 16:51:10 -06:00
commit 5bebde4bfb
5 changed files with 129 additions and 75 deletions

2
package-lock.json generated
View file

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

View file

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

View file

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

View file

@ -164,6 +164,7 @@ export interface ComponentTypeExtension {
smoothingAverages?: number,
filters?: GraphFilters
) => LineChartData;
secondaryComponentSettings?: () => pond.ComponentSettings[]
}
export interface Summary {
@ -621,6 +622,11 @@ export function GetComponentIcon(
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)
// export function getGraphProps(
// componentType: quack.ComponentType,

View file

@ -25,7 +25,18 @@ export function CO2(subtype: number = 0): ComponentTypeExtension {
);
return {
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",
description: "Measures the parts per million of CO2",
isController: false,
@ -71,6 +82,24 @@ export function CO2(subtype: number = 0): ComponentTypeExtension {
minMeasurementPeriodMs: 5000,
icon: (theme?: "light" | "dark"): string | undefined => {
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
}
};
}