diff --git a/package-lock.json b/package-lock.json index abf6c85..0e25c26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -10953,7 +10953,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#c08965a296f2cd0799472fd8b163186cf6885d4c", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#3f647071f211c4552c2acc23a8ecabb6904a4156", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index ee0e9b7..fc7a1ac 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", diff --git a/src/device/DeviceWizard.tsx b/src/device/DeviceWizard.tsx index 32387eb..cf0a061 100644 --- a/src/device/DeviceWizard.tsx +++ b/src/device/DeviceWizard.tsx @@ -141,6 +141,28 @@ export default function DeviceWizard(props: Props) { )} + {selectedPort && + selectedPort.addressType === quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY && + device.featureSupported("detectOneWire") && + selectedPort.autoDetectable && ( + + { + setButtonClicked(true) + console.log(selectedPort) + deviceAPI.detectOneWire(device.id(), selectedPort.address) + .then(resp => { + openSnack("Device will scan port on next check-in") + }).catch(err => { + openSnack("Command failed to send to device") + }) + }}> + Scan Port + + + )} {portComponents.length > 0 && ( Current Components diff --git a/src/device/autoDetect/DeviceScannedComponents.tsx b/src/device/autoDetect/DeviceScannedComponents.tsx index 0381281..2be89a9 100644 --- a/src/device/autoDetect/DeviceScannedComponents.tsx +++ b/src/device/autoDetect/DeviceScannedComponents.tsx @@ -7,9 +7,11 @@ import { useEffect, useState } from "react"; import ScannedI2C from "./ScannedI2C"; import ComponentForm from "component/ComponentForm"; import { Component, Device } from "models"; -import { DeviceAvailabilityMap, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability"; +import { DeviceAvailabilityMap, FindAvailablePositions, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability"; import ResponsiveDialog from "common/ResponsiveDialog"; import { useComponentAPI, useDeviceAPI, useSnackbar } from "hooks"; +import { ConfigurablePin } from "pbHelpers/AddressTypes"; +import ScannedOneWirePort from "./OneWire/ScannedOneWirePort"; interface Props { scannedComponents: pond.ComponentAddressMap @@ -31,17 +33,19 @@ export default function DeviceScannedComponents(props: Props){ const compAPI = useComponentAPI(); const deviceAPI = useDeviceAPI() const [scannedI2C, setScannedI2C] = useState() //the unmodified scan of addresses - const [validCompAddresses, setValidComponentAddresses] = useState([]) //the filtered array of address data + const [scannedOneWire, setScannedOneWire] = useState([]) + const [validI2CCompAddresses, setValidI2CComponentAddresses] = useState([]) //the filtered array of address data const [components, setComponents] = useState([]) const [steps, setSteps] = useState([]); const { error, success } = useSnackbar(); const [currentStep, setCurrentStep] = useState(0) const [settingsValid, setSettingsValid] = useState(false); const [openDialog, setOpenDialog] = useState(false); + const [clearOpen, setClearOpen] = useState(false); useEffect(()=>{ - //console.log(scannedComponents) if (scannedComponents.i2c) setScannedI2C(scannedComponents.i2c) + if (scannedComponents.oneWire) setScannedOneWire(scannedComponents.oneWire) //makes the array empty for testing // if (scannedComponents.i2c) { // let clone = cloneDeep(scannedComponents.i2c) @@ -76,7 +80,7 @@ export default function DeviceScannedComponents(props: Props){ } }) } - setValidComponentAddresses(valid) + setValidI2CComponentAddresses(valid) },[scannedI2C]) const stepper = () => { @@ -200,11 +204,12 @@ export default function DeviceScannedComponents(props: Props){ }) }) } + console.log(cloneList) setComponents(cloneList) } - const removeScan = (key: string) => { - deviceAPI.removeFoundComponents(device.settings.deviceId, key) + const removeScan = (key: string, type: pond.ObjectType) => { + deviceAPI.removeFoundComponents(device.settings.deviceId, key, type) .then(resp => { console.log("Cleared this scan") }).catch(err => { @@ -212,20 +217,51 @@ export default function DeviceScannedComponents(props: Props){ }) } + const removeAllScans = () => { + deviceAPI.removeAllFoundComponents(device.settings.deviceId) + .then(resp => { + console.log("Cleared all scans") + refreshCallback() + }).catch(err => { + console.log("There was a problem clearing scans") + }) + } + + const clearWarning = () => { + return ( + {setClearOpen(false)}}> + Clear All Scans + This action will clear all scans for this device. + + + + + + ) + } + return ( + {clearWarning()} + + Sensor Scan + + {scannedI2C !== undefined && - + I2C Sensors - + - {validCompAddresses.length > 0 ? validCompAddresses.map((addr, index) => { + {validI2CCompAddresses.length > 0 ? validI2CCompAddresses.map((addr, index) => { return ( ()}/> ) @@ -240,7 +276,39 @@ export default function DeviceScannedComponents(props: Props){ } - + + } + {scannedOneWire.length > 0 && + + + + Pin Port Sensors + + + {scannedOneWire.map((scan,i) => { + let map = FindAvailablePositions([], device.settings.product).availability.get(quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY) + let portAddress = scan.settings?.oneWireData?.port + let portLabel = "" + + map?.forEach(entry => { + let pin = entry as ConfigurablePin + if (pin.address && pin.address === portAddress) { + portLabel = pin.label + } + }) + return( + + + Port: {portLabel} + + + + + ) + })} + } diff --git a/src/device/autoDetect/OneWire/PortComponent.tsx b/src/device/autoDetect/OneWire/PortComponent.tsx new file mode 100644 index 0000000..b1e26e1 --- /dev/null +++ b/src/device/autoDetect/OneWire/PortComponent.tsx @@ -0,0 +1,150 @@ +import { Box, Checkbox, Grid2, MenuItem, TextField, Tooltip, Typography } from "@mui/material" +import { useMobile } from "hooks" +import { getFriendlyName, getSubtypes, Subtype } from "pbHelpers/ComponentType" +import { quack } from "protobuf-ts/quack" +import React, { useEffect, useState } from "react" + +interface Props { + compData: quack.OneWireComponentData + componentSelect: (selected: boolean, componentData: quack.OneWireComponentData) => void +} + +export default function PortComponent(props: Props){ + const {compData, componentSelect} = props + // const [selectedSubtype, setSelectedSubtype] = useState(0) + const [subtypeVal, setSubtypeVal] = useState("no subtype")//note that this is the friendly name and not the key or the value because it needs to be different from the other alias options + const [subtypeMap, setSubtypeMap] = useState>(new Map()) + const [subtypeOptions, setSubtypeOptions] = useState([]) + const [added, setAdded] = useState(false) + const isMobile = useMobile() + + useEffect(()=>{ + // setSelectedSubtype(compData.subtype) + let validSubtypes: Subtype[] = [] + let subMap: Map = new Map() + let subVal: string = "no subtype" + getSubtypes(compData.type).forEach(option => { + if(compData.subtype === option.key || compData.subtype === 0){ + validSubtypes.push(option) + if(subVal === "no subtype"){ + subVal = option.friendlyName + } + } + subMap.set(option.friendlyName, option.key) + }) + setSubtypeOptions(validSubtypes) + setSubtypeMap(subMap) + setSubtypeVal(subVal) + },[compData]) + + const subtypeSelector = () => { + return ( + { + setSubtypeVal(event.target.value) + // setSelectedSubtype(subtypeMap.get(event.target.value) ?? 0) + compData.subtype = subtypeMap.get(event.target.value) ?? 0 + }} + select> + Select the Component Subtype + {subtypeOptions.map((s, i) => ( + {s.friendlyName} + ))} + + ) + } + + const selectCompCheckbox = () => { + return ( + { + setAdded(checked) + componentSelect(checked, compData) + }} + /> + } + /> + ) + } + + const desktopDisplay = () => { + return ( + + + + + {getFriendlyName(compData.type)} + + + + {subtypeOptions.length < 2 + ? + + {subtypeVal} + + : + subtypeSelector()} + + + + Cable ID: {compData.cableId} + + + + + Nodes: {compData.nodeCount} + + + + {selectCompCheckbox()} + + + + ) + } + + const mobileDisplay = () => { + return( + + + + + {getFriendlyName(compData.type)} + + + Cable ID: {compData.cableId} + + + Nodes: {compData.nodeCount} + + + + {subtypeOptions.length < 2 + ? + + {subtypeVal} + + : + subtypeSelector()} + + + {selectCompCheckbox()} + + + + ) + } + + return ( + + {isMobile ? mobileDisplay() : desktopDisplay()} + + ) +} \ No newline at end of file diff --git a/src/device/autoDetect/OneWire/ScannedOneWirePort.tsx b/src/device/autoDetect/OneWire/ScannedOneWirePort.tsx new file mode 100644 index 0000000..e035660 --- /dev/null +++ b/src/device/autoDetect/OneWire/ScannedOneWirePort.tsx @@ -0,0 +1,57 @@ +import { Box, MenuItem, TextField, Typography } from "@mui/material" +import { extension, getFriendlyName, getSubtypes, Subtype } from "pbHelpers/ComponentType" +import { pond } from "protobuf-ts/pond" +import { quack } from "protobuf-ts/quack" +import React, { useEffect, useState } from "react" +import PortComponent from "./PortComponent" +import { Component } from "models" + +interface Props { + scan: pond.DetectOneWire + componentSelectionCallback: (newComponents: Component[], checked: boolean) => void +} + +export default function ScannedOneWirePort(props: Props){ + const {scan, componentSelectionCallback} = props + const [portComponents, setPortComponents] = useState([]) + + useEffect(()=>{ + setPortComponents(scan.settings?.oneWireData?.components ?? []) + },[scan]) + + const componentSelected = (checked: boolean, componentData: quack.OneWireComponentData) => { + let newComponents: Component[] = [] + //use the component data to create a new Component + //build a component with given information and defaults for the rest + let primaryComponent = Component.create() + primaryComponent.settings.type = componentData.type + primaryComponent.settings.subtype = componentData.subtype + primaryComponent.settings.address = scan.settings?.oneWireData?.port ?? 0 + primaryComponent.settings.addressType = quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY + primaryComponent.settings.name = getFriendlyName(componentData.type, componentData.subtype) + + //need to check if the component is chainable, meaning the address type will reflect the cable id + let ext = extension(componentData.type, componentData.subtype) + if(ext.isChainable){ + primaryComponent.settings.addressType = componentData.cableId + 8 + } + newComponents.push(primaryComponent) + + componentSelectionCallback(newComponents, checked) + } + + return ( + + {portComponents.length > 0 ? portComponents.map((compData, i) => { + return ( + { + componentSelected(checked, component) + }}/> + ) + }): + + No Components Found + } + + ) +} \ No newline at end of file diff --git a/src/models/Device.ts b/src/models/Device.ts index 2577618..6ee074d 100644 --- a/src/models/Device.ts +++ b/src/models/Device.ts @@ -64,6 +64,21 @@ const featureVersions: Map = new Map([ v2CellBlue: "2.1.7", v2EthBlue: "2.1.7" } + ],[ + "detectOneWire", + { + photon: "N/A", + electron: "N/A", + v2Wifi: "N/A", + v2Cell: "N/A", + v2WifiS3: "N/A", + v2CellS3: "N/A", + v2CellBlack: "2.1.10", + v2CellGreen: "N/A", + v2WifiBlue: "2.1.10", + v2CellBlue: "2.1.10", + v2EthBlue: "2.1.10" + } ] ]); export class Device { diff --git a/src/pages/Device.tsx b/src/pages/Device.tsx index 5acc7aa..7344659 100644 --- a/src/pages/Device.tsx +++ b/src/pages/Device.tsx @@ -1,4 +1,4 @@ -import { Button, Card, Divider, List, ListItem, Typography } from "@mui/material"; +import { Box, Button, Card, Divider, List, ListItem, Typography } from "@mui/material"; import Grid from '@mui/material/Grid2'; import { Component, Device, Interaction, User } from "models"; import { getContextKeys, getContextTypes } from "pbHelpers/Context"; @@ -169,8 +169,9 @@ export default function DevicePage() { const loadPortScan = () => { deviceAPI.listFoundComponents(parseInt(deviceID)) .then(resp => { - if (resp.data.foundComponents?.i2c){ - setScannedAddresses(resp.data.foundComponents ?? undefined) + console.log(resp.data) + if (resp.data.foundComponents?.i2c || resp.data.foundComponents?.oneWire){ + setScannedAddresses(pond.ListFoundComponentsResponse.fromObject(resp.data).foundComponents ?? undefined) } }) .catch(err => { @@ -411,7 +412,16 @@ export default function DevicePage() { /> {/* add grid card here for I2C detected components */} - + {(scannedAddresses?.i2c || scannedAddresses?.oneWire) && + + + + } {diagnosticComponents.map(comp => ( - {scannedAddresses && - + {(scannedAddresses?.i2c || scannedAddresses?.oneWire) && + + isChainable?: boolean } export interface Summary { diff --git a/src/pbHelpers/ComponentTypes/CapacitorCable.ts b/src/pbHelpers/ComponentTypes/CapacitorCable.ts index cc56e27..8843952 100644 --- a/src/pbHelpers/ComponentTypes/CapacitorCable.ts +++ b/src/pbHelpers/ComponentTypes/CapacitorCable.ts @@ -68,6 +68,7 @@ export function CapacitorCable(subtype: number = 0): ComponentTypeExtension { isSource: true, isArray: true, isCalibratable: false, + isChainable: true, addressTypes: addressTypes, interactionResultTypes: [], states: [], diff --git a/src/pbHelpers/ComponentTypes/GrainCable.ts b/src/pbHelpers/ComponentTypes/GrainCable.ts index c715611..bacc514 100644 --- a/src/pbHelpers/ComponentTypes/GrainCable.ts +++ b/src/pbHelpers/ComponentTypes/GrainCable.ts @@ -176,6 +176,7 @@ export function GrainCable(subtype: number = 0): ComponentTypeExtension { isSource: true, isArray: true, isCalibratable: false, + isChainable: true, addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, quack.AddressType.ADDRESS_TYPE_I2C], interactionResultTypes: [], states: [], diff --git a/src/pbHelpers/ComponentTypes/PressureCable.ts b/src/pbHelpers/ComponentTypes/PressureCable.ts index f03cc9e..3a22362 100644 --- a/src/pbHelpers/ComponentTypes/PressureCable.ts +++ b/src/pbHelpers/ComponentTypes/PressureCable.ts @@ -64,6 +64,7 @@ export function PressureCable(subtype: number = 0): ComponentTypeExtension { isArray: true, hasFan: true, isCalibratable: false, + isChainable: true, addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY], interactionResultTypes: [], states: [], diff --git a/src/providers/pond/deviceAPI.tsx b/src/providers/pond/deviceAPI.tsx index fbaefc0..241781b 100644 --- a/src/providers/pond/deviceAPI.tsx +++ b/src/providers/pond/deviceAPI.tsx @@ -23,6 +23,7 @@ export interface IDeviceAPIContext { ) => Promise>; getMulti: (ids: number[] | string[], otherTeam?: string) => Promise>; detectI2C: (id: number, otherTeam?: string) => Promise>; + detectOneWire: (id: number, port: number, otherTeam?: string) => Promise> listFoundComponents: (id: number, otherTeam?: string) => Promise> getGeoJson: (id: number | string, demo?: boolean, otherTeam?: string) => Promise; getMultiGeoJson: (ids: number[] | string[], otherTeam?: string) => Promise; @@ -148,7 +149,8 @@ export interface IDeviceAPIContext { keys?: string[], types?: string[] ) => Promise; - removeFoundComponents: (id: number, key: string, otherTeam?: string) => Promise>; + removeAllFoundComponents: (id: number,otherTeam?: string) => Promise>; + removeFoundComponents: (id: number, key: string, type: pond.ObjectType, otherTeam?: string) => Promise>; } export const DeviceAPIContext = createContext({} as IDeviceAPIContext); @@ -962,6 +964,19 @@ export default function DeviceProvider(props: PropsWithChildren) { }) } + const detectOneWire = (id: number, port: number, otherTeam?: string) => { + let url = "/devices/" + id + "/detectOneWire?port=" + port; + const view = otherTeam ? otherTeam : as + if(view) url = url + "?as=" + view + return new Promise>((resolve, reject) => { + put(pondURL(url)).then(resp => { + return resolve(resp) + }).catch(err => { + return reject(err) + }) + }) + } + const listFoundComponents = (id: number, otherTeam?: string) => { let url = "/devices/" + id + "/listScannedComponents"; const view = otherTeam ? otherTeam : as @@ -975,10 +990,10 @@ export default function DeviceProvider(props: PropsWithChildren) { }) } - const removeFoundComponents = (id: number, key: string, otherTeam?: string) => { - let url = "/devices/" + id + "/removeFoundComponents/" + key; + const removeFoundComponents = (id: number, key: string, type: pond.ObjectType, otherTeam?: string) => { + let url = "/devices/" + id + "/removeFoundComponents/" + key + "?scanType=" + type; const view = otherTeam ? otherTeam : as - if(view) url = url + "?as=" + view + if(view) url = url + "&as=" + view return new Promise>((resolve, reject) => { del(pondURL(url)).then(resp => { return resolve(resp) @@ -988,6 +1003,19 @@ export default function DeviceProvider(props: PropsWithChildren) { }) } + const removeAllFoundComponents = (id: number, otherTeam?: string) => { + let url = "/devices/" + id + "/removeAllFoundComponents"; + const view = otherTeam ? otherTeam : as + if(view) url = url + "?as=" + view + return new Promise>((resolve, reject) => { + del(pondURL(url)).then(resp => { + return resolve(resp) + }).catch(err => { + return reject(err) + }) + }) + } + return ( ) { updateComponentPreferences, listDeviceComponentPreferences, detectI2C, + detectOneWire, listFoundComponents, - removeFoundComponents + removeFoundComponents, + removeAllFoundComponents }}> {children}