Merge branch 'i2c_detect' into dev_environment

This commit is contained in:
csawatzky 2025-10-07 12:44:07 -06:00
commit 37b1a1fade
30 changed files with 332 additions and 141 deletions

View file

@ -103,8 +103,10 @@ export default function BinCard(props: Props) {
let filteredNodes = c.filteredNodes(true) let filteredNodes = c.filteredNodes(true)
allTemps.push(...filteredNodes.temps); allTemps.push(...filteredNodes.temps);
allHums.push(...filteredNodes.humids); if(avg(c.humidities) !== 0){ //if the average of the humidities is 0 that means that all the readings are 0 and it is a temp only cable
allMoistures.push(...filteredNodes.moistures); allHums.push(...filteredNodes.humids);
allMoistures.push(...filteredNodes.moistures);
}
//set the hottest and coldest nodes //set the hottest and coldest nodes
if (coldestNode === undefined || Math.min(...filteredNodes.temps) < coldestNode.temp) { if (coldestNode === undefined || Math.min(...filteredNodes.temps) < coldestNode.temp) {
@ -490,7 +492,7 @@ export default function BinCard(props: Props) {
return ( return (
<Card style={{ cursor: "pointer" }}> <Card style={{ cursor: "pointer" }}>
{user.hasFeature("admin") && ( {user.hasFeature("installer") && (
<IconButton <IconButton
style={{ style={{
height: 35, height: 35,

View file

@ -33,7 +33,7 @@ const useStyles = makeStyles((theme: Theme) => {
interface Props { interface Props {
interactions: Interaction[]; interactions: Interaction[];
interactionDevices: Map<string, number>; interactionDevices: Map<string, number>;
mode: pond.BinMode.BIN_MODE_DRYING | pond.BinMode.BIN_MODE_HYDRATING; mode: pond.BinMode;
plenums?: Plenum[]; plenums?: Plenum[];
ambients?: Ambient[]; ambients?: Ambient[];
heaters?: Controller[]; heaters?: Controller[];
@ -94,10 +94,21 @@ export default function BinConditioningCard(props: Props) {
return conditioningInteractions; return conditioningInteractions;
}; };
const modeDisplay = () => {
switch(mode){
case pond.BinMode.BIN_MODE_DRYING:
return "Drying"
case pond.BinMode.BIN_MODE_HYDRATING:
return "Hydrating"
case pond.BinMode.BIN_MODE_COOLDOWN:
return "Cooldown"
}
}
return ( return (
<Card raised className={classes.card}> <Card raised className={classes.card}>
<Typography style={{ fontWeight: 650 }}> <Typography style={{ fontWeight: 650 }}>
{mode === pond.BinMode.BIN_MODE_DRYING ? "Drying" : "Hydrating"} Conditions {modeDisplay()} Conditions
</Typography> </Typography>
{conditionsDisplay()} {conditionsDisplay()}
</Card> </Card>

View file

@ -47,8 +47,6 @@ const useStyles = makeStyles((theme: Theme) => {
sliderThumb: { sliderThumb: {
height: 15, height: 15,
width: 15, width: 15,
marginTop: -6,
marginLeft: -7.5,
backgroundColor: "yellow" backgroundColor: "yellow"
}, },
sliderTrack: { sliderTrack: {
@ -60,10 +58,10 @@ const useStyles = makeStyles((theme: Theme) => {
backgroundColor: "white" backgroundColor: "white"
}, },
sliderValLabel: { sliderValLabel: {
left: "calc(-50%)", left: -20,
top: 22, top: 40,
background: "transparent",
"& *": { "& *": {
background: "transparent",
color: "#fff" color: "#fff"
} }
}, },

View file

@ -34,7 +34,7 @@ export default function BinDuplication(props: Props) {
binAPI binAPI
.addBin(settings) .addBin(settings)
.then(resp => { .then(resp => {
openSnack("Successfully duplicated bin"); openSnack("Successfully duplicated bin");
}) })
.catch(err => { .catch(err => {
openSnack("Failed to duplicate bin"); openSnack("Failed to duplicate bin");

View file

@ -476,11 +476,11 @@ export default function BinSVGV2(props: Props) {
return cableGrainPath; return cableGrainPath;
}; };
const nodeClass = (nodeTemp: number) => { const nodeClass = (nodeTemp: number, underTop: boolean) => {
if (hottestNodeTemp && hottestNodeTemp.toFixed(2) === nodeTemp.toFixed(2)) { if (hottestNodeTemp && hottestNodeTemp.toFixed(2) === nodeTemp.toFixed(2) && underTop) {
return classes.hotNode; return classes.hotNode;
} }
if (coldestNodeTemp && coldestNodeTemp.toFixed(2) === nodeTemp.toFixed(2)) { if (coldestNodeTemp && coldestNodeTemp.toFixed(2) === nodeTemp.toFixed(2) && underTop) {
return classes.coldNode; return classes.coldNode;
} }
return classes.cableNode; return classes.cableNode;
@ -507,12 +507,12 @@ export default function BinSVGV2(props: Props) {
//if the cables have the same number of nodes //if the cables have the same number of nodes
if (b.temperatures.length === a.temperatures.length) { if (b.temperatures.length === a.temperatures.length) {
//sort by temp only last and any type that has humidity first //sort by temp only last and any type that has humidity first
if ( if ((avg(a.humidities) === 0) && (avg(b.humidities) !== 0)) {
a.settings.subtype === quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP &&
b.settings.subtype !== quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP
) {
return 1; return 1;
} else { } else if ((avg(b.humidities) === 0) && (avg(a.humidities) !== 0)) {
return -1;
}
else {
return a.key() > b.key() ? 1 : -1; return a.key() > b.key() ? 1 : -1;
} }
} }
@ -759,7 +759,7 @@ export default function BinSVGV2(props: Props) {
return ( return (
<g key={e.key}> <g key={e.key}>
{e.nodeNumber === topNode && !e.excluded && topNodeHat(cablePos, e.y)} {e.nodeNumber === topNode && !e.excluded && topNodeHat(cablePos, e.y)}
<circle cx={cablePos} cy={e.y} className={e.excluded ? classes.excludedNode : nodeClass(e.nodeTemp)} r="16"></circle> <circle cx={cablePos} cy={e.y} className={e.excluded ? classes.excludedNode : nodeClass(e.nodeTemp, e.nodeNumber <= topNode)} r="16"></circle>
</g> </g>
) )
} }

View file

@ -485,6 +485,7 @@ export default function BinSettings(props: Props) {
form.inventory.inventoryControl = inventoryControl form.inventory.inventoryControl = inventoryControl
form.inventory.autoThreshold = autoFillThreshold form.inventory.autoThreshold = autoFillThreshold
} }
console.log(form)
binAPI binAPI
.updateBin(bin.key(), form, as) .updateBin(bin.key(), form, as)
.then(response => { .then(response => {
@ -741,7 +742,7 @@ export default function BinSettings(props: Props) {
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR: case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR:
return "Hybrid (lidar)" return "Hybrid (lidar)"
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART: case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART:
return "Auto (LibraCart)" return "Auto (Libra Cart)"
default: default:
return "Manual" return "Manual"
} }
@ -905,7 +906,7 @@ export default function BinSettings(props: Props) {
<FormControlLabel <FormControlLabel
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART} value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART}
control={<Radio />} control={<Radio />}
label={"Auto (LibraCart)"} label={"Auto (Libra Cart)"}
/> />
} }
</RadioGroup> </RadioGroup>
@ -951,7 +952,7 @@ export default function BinSettings(props: Props) {
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART && {inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART &&
<Box width="100%" padding={2}> <Box width="100%" padding={2}>
<SearchSelect <SearchSelect
label="LibraCart Destination" label="Libra Cart Destination"
selected={lcDestination} selected={lcDestination}
changeSelection={option => { changeSelection={option => {
let newForm = form; let newForm = form;
@ -963,7 +964,7 @@ export default function BinSettings(props: Props) {
options={lcDestinationOptions} options={lcDestinationOptions}
/> />
<Typography variant="caption"> <Typography variant="caption">
The linked bins inventory will be adjusted When the LibraCart Destination data is synced every 6 hours The linked bins inventory will be adjusted When the Libra Cart Destination data is synced every 6 hours
</Typography> </Typography>
</Box> </Box>
} }

View file

@ -21,7 +21,7 @@ import { Bin, Component } from "models";
import { GrainCable } from "models/GrainCable"; import { GrainCable } from "models/GrainCable";
import { UnitMeasurement } from "models/UnitMeasurement"; import { UnitMeasurement } from "models/UnitMeasurement";
import Co2Icon from "products/CommonIcons/co2Icon"; import Co2Icon from "products/CommonIcons/co2Icon";
import { pond } from "protobuf-ts/pond"; import { pond, quack } from "protobuf-ts/pond";
import { useBinAPI, useGlobalState, useSnackbar } from "providers"; import { useBinAPI, useGlobalState, useSnackbar } from "providers";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { avg, getTemperatureUnit } from "utils"; import { avg, getTemperatureUnit } from "utils";
@ -200,33 +200,32 @@ export default function BinStorageConditions(props: Props) {
// } // }
}); });
let nodeEMCs: number[] = []; if(cable.subType() !== quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP){
let nodeEMCs: number[] = [];
filteredNodes.moistures.forEach((emc, i) => { filteredNodes.moistures.forEach((emc, i) => {
if (bin.settings.inventory?.targetMoisture) { if (bin.settings.inventory?.targetMoisture) {
// if (i < cable.topNode) { nodeEMCs.push(emc);
nodeEMCs.push(emc);
emcCounts.total++; emcCounts.total++;
if ( if (
emc > emc >
bin.settings.inventory.targetMoisture + bin.settings.inventory.targetMoisture +
bin.settings.inventory.moistureTargetDeviation bin.settings.inventory.moistureTargetDeviation
) { ) {
emcCounts.above++; emcCounts.above++;
} else if ( } else if (
emc < emc <
bin.settings.inventory.targetMoisture - bin.settings.inventory.targetMoisture -
bin.settings.inventory.moistureTargetDeviation bin.settings.inventory.moistureTargetDeviation
) { ) {
emcCounts.below++; emcCounts.below++;
} else { } else {
emcCounts.onTarget++; emcCounts.onTarget++;
} }
// } }
} });
}); cableEMCAvgs.push(avg(nodeEMCs));
}
cableTempAvgs.push(avg(nodeTemps)); cableTempAvgs.push(avg(nodeTemps));
cableEMCAvgs.push(avg(nodeEMCs));
} }
}); });
if (cableTempAvgs.length > 0) { if (cableTempAvgs.length > 0) {

View file

@ -250,6 +250,7 @@ export default function BinVisualizer(props: Props) {
const [cfmHighOpen, setCFMHighOpen] = useState(false); const [cfmHighOpen, setCFMHighOpen] = useState(false);
const [{ user }] = useGlobalState(); const [{ user }] = useGlobalState();
const [newPreset, setNewPreset] = useState<pond.BinMode>(pond.BinMode.BIN_MODE_NONE); const [newPreset, setNewPreset] = useState<pond.BinMode>(pond.BinMode.BIN_MODE_NONE);
const [newBinMode, setNewBinMode] = useState<pond.BinMode>(pond.BinMode.BIN_MODE_NONE);
const [selectedCable, setSelectedCable] = useState<GrainCable>(); const [selectedCable, setSelectedCable] = useState<GrainCable>();
const [openNodeDialog, setOpenNodeDialog] = useState(false); const [openNodeDialog, setOpenNodeDialog] = useState(false);
const [cableDevice, setCableDevice] = useState<Device | undefined>(); const [cableDevice, setCableDevice] = useState<Device | undefined>();
@ -345,6 +346,7 @@ export default function BinVisualizer(props: Props) {
setGrainOption(ToGrainOption(bin.settings.inventory.grainType)); setGrainOption(ToGrainOption(bin.settings.inventory.grainType));
setBushPerTonne(bin.settings.inventory.bushelsPerTonne.toFixed(2)); setBushPerTonne(bin.settings.inventory.bushelsPerTonne.toFixed(2));
setGrainSubtype(bin.subtype()); setGrainSubtype(bin.subtype());
setNewBinMode(bin.settings.mode);
} }
if (bin.settings) { if (bin.settings) {
let t = bin.settings.outdoorTemp; let t = bin.settings.outdoorTemp;
@ -384,8 +386,11 @@ export default function BinVisualizer(props: Props) {
//also reverse it so that the node 1 (end of the cable) is in the first position //also reverse it so that the node 1 (end of the cable) is in the first position
let filteredNodes = cable.filteredNodes(true) let filteredNodes = cable.filteredNodes(true)
temps.push(...filteredNodes.temps) temps.push(...filteredNodes.temps)
humids.push(...filteredNodes.humids) //only push to the humidity values if the cable reads humidities
emcs.push(...filteredNodes.moistures) if(cable.subType() !== quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP){
humids.push(...filteredNodes.humids)
emcs.push(...filteredNodes.moistures)
}
// let tempClone = cloneDeep(cable.temperatures).reverse(); // let tempClone = cloneDeep(cable.temperatures).reverse();
// let humClone = cloneDeep(cable.humidities).reverse(); // let humClone = cloneDeep(cable.humidities).reverse();
// let emcClone = cloneDeep(cable.grainMoistures).reverse(); // let emcClone = cloneDeep(cable.grainMoistures).reverse();
@ -409,14 +414,15 @@ export default function BinVisualizer(props: Props) {
//if the lowest temp for this cable is less than the temp in the low node conditions or the low node conditions are not set //if the lowest temp for this cable is less than the temp in the low node conditions or the low node conditions are not set
//use this cables lowest node and trend data in the condition //use this cables lowest node and trend data in the condition
if (!lowNodeConditions || Math.min(...temps) < lowNodeConditions.tempC) { if (!lowNodeConditions || Math.min(...filteredNodes.temps) < lowNodeConditions.tempC) {
//determine which node is the coldest so that the data displayed is for the same node //determine which node is the coldest so that the data displayed is for the same node
let lowTempIndex = let lowTempIndex =
temps.indexOf(Math.min(...temps)) === -1 ? 0 : temps.indexOf(Math.min(...temps)); filteredNodes.temps.indexOf(Math.min(...filteredNodes.temps)) === -1 ? 0 : filteredNodes.temps.indexOf(Math.min(...filteredNodes.temps));
console.log(lowTempIndex)
lowNodeConditions = { lowNodeConditions = {
tempC: temps[lowTempIndex], tempC: filteredNodes.temps[lowTempIndex],
humidity: humids[lowTempIndex], humidity: filteredNodes.humids[lowTempIndex],
emc: emcs[lowTempIndex], emc: filteredNodes.moistures[lowTempIndex],
tempCTrend: cableTrendData.coldNodeTrend?.tempTrend ?? 0, tempCTrend: cableTrendData.coldNodeTrend?.tempTrend ?? 0,
humidityTrend: cableTrendData.coldNodeTrend?.humidityTrend ?? 0, humidityTrend: cableTrendData.coldNodeTrend?.humidityTrend ?? 0,
emcTrend: cableTrendData.coldNodeTrend?.emcTrend ?? 0 emcTrend: cableTrendData.coldNodeTrend?.emcTrend ?? 0
@ -424,13 +430,13 @@ export default function BinVisualizer(props: Props) {
} }
//do the same for the high node //do the same for the high node
if (!highNodeConditions || cable.maxTemp() > highNodeConditions.tempC) { if (!highNodeConditions || Math.max(...filteredNodes.temps) > highNodeConditions.tempC) {
let highTempIndex = let highTempIndex =
temps.indexOf(Math.max(...temps)) === -1 ? 0 : temps.indexOf(Math.max(...temps)); filteredNodes.temps.indexOf(Math.max(...filteredNodes.temps)) === -1 ? 0 : filteredNodes.temps.indexOf(Math.max(...filteredNodes.temps));
highNodeConditions = { highNodeConditions = {
tempC: temps[highTempIndex], tempC: filteredNodes.temps[highTempIndex],
humidity: humids[highTempIndex], humidity: filteredNodes.humids[highTempIndex],
emc: emcs[highTempIndex], emc: filteredNodes.moistures[highTempIndex],
tempCTrend: cableTrendData.hotNodeTrend?.tempTrend ?? 0, tempCTrend: cableTrendData.hotNodeTrend?.tempTrend ?? 0,
humidityTrend: cableTrendData.hotNodeTrend?.humidityTrend ?? 0, humidityTrend: cableTrendData.hotNodeTrend?.humidityTrend ?? 0,
emcTrend: cableTrendData.hotNodeTrend?.emcTrend ?? 0 emcTrend: cableTrendData.hotNodeTrend?.emcTrend ?? 0
@ -1967,6 +1973,7 @@ export default function BinVisualizer(props: Props) {
const setModeStorage = () => { const setModeStorage = () => {
setNewPreset(pond.BinMode.BIN_MODE_STORAGE); setNewPreset(pond.BinMode.BIN_MODE_STORAGE);
setNewBinMode(pond.BinMode.BIN_MODE_STORAGE);
}; };
const setModeCooldown = () => { const setModeCooldown = () => {
@ -1974,6 +1981,7 @@ export default function BinVisualizer(props: Props) {
return; return;
} }
setNewPreset(pond.BinMode.BIN_MODE_COOLDOWN); setNewPreset(pond.BinMode.BIN_MODE_COOLDOWN);
setNewBinMode(pond.BinMode.BIN_MODE_COOLDOWN);
}; };
const setModeDrying = () => { const setModeDrying = () => {
@ -1982,13 +1990,16 @@ export default function BinVisualizer(props: Props) {
bin.settings.inventory?.initialMoisture < bin.settings.inventory?.targetMoisture bin.settings.inventory?.initialMoisture < bin.settings.inventory?.targetMoisture
) { ) {
setNewPreset(pond.BinMode.BIN_MODE_HYDRATING); setNewPreset(pond.BinMode.BIN_MODE_HYDRATING);
setNewBinMode(pond.BinMode.BIN_MODE_HYDRATING);
} else { } else {
setNewPreset(pond.BinMode.BIN_MODE_DRYING); setNewPreset(pond.BinMode.BIN_MODE_DRYING);
setNewBinMode(pond.BinMode.BIN_MODE_DRYING);
} }
}; };
const closeMoistureDialog = () => { const closeMoistureDialog = () => {
setNewPreset(0); setNewPreset(0);
setNewBinMode(bin.settings.mode);
setShowInputMoisture(false); setShowInputMoisture(false);
}; };
@ -2049,6 +2060,7 @@ export default function BinVisualizer(props: Props) {
<Button <Button
onClick={() => { onClick={() => {
setNewPreset(0); setNewPreset(0);
setNewBinMode(bin.settings.mode);
setShowInputMoisture(false); setShowInputMoisture(false);
}} }}
color="primary"> color="primary">
@ -2090,7 +2102,13 @@ export default function BinVisualizer(props: Props) {
} }
if (b.settings.outdoorHumidity !== undefined) if (b.settings.outdoorHumidity !== undefined)
b.settings.outdoorHumidity = Number(outdoorHumidityInput); b.settings.outdoorHumidity = Number(outdoorHumidityInput);
b.settings.mode = newPreset;
if (b.settings.mode != newBinMode){
if(b.settings.inventory){
b.settings.inventory.initialTimestamp = moment().format();
}
b.settings.mode = newBinMode;
}
binAPI.updateBin(bin.key(), b.settings, as).then(resp => { binAPI.updateBin(bin.key(), b.settings, as).then(resp => {
refresh(); refresh();

File diff suppressed because one or more lines are too long

View file

@ -36,6 +36,7 @@ import {
} from "pbHelpers/AddressType"; } from "pbHelpers/AddressType";
import { import {
extension, extension,
FilterSubtypeI2C,
getAddressTypes, getAddressTypes,
GetComponentTypeOptions, GetComponentTypeOptions,
getFriendlyName, getFriendlyName,
@ -240,7 +241,8 @@ export default function ComponentSettings(props: Props) {
const getAvailablePositions = ( const getAvailablePositions = (
addressType: quack.AddressType, addressType: quack.AddressType,
componentType: quack.ComponentType componentType: quack.ComponentType,
subtype: number
): number[] => { ): number[] => {
let positions = availablePositions.get(addressType); let positions = availablePositions.get(addressType);
if (!positions) return []; if (!positions) return [];
@ -249,7 +251,8 @@ export default function ComponentSettings(props: Props) {
case quack.AddressType.ADDRESS_TYPE_SPI: case quack.AddressType.ADDRESS_TYPE_SPI:
let componentPositions; let componentPositions;
if (positions) { if (positions) {
componentPositions = (positions as ComponentAvailabilityMap).get(componentType); componentPositions = (positions as ComponentAvailabilityMap).get(componentType); //gets the addresses that are not claimed yet
componentPositions = FilterSubtypeI2C(componentType, subtype, componentPositions as number[]) //filters the I2C addresses for valid ones for the subtype
} }
return componentPositions ? (componentPositions as number[]) : []; return componentPositions ? (componentPositions as number[]) : [];
case quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY: case quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY:
@ -264,7 +267,8 @@ export default function ComponentSettings(props: Props) {
if (mode === "add") { if (mode === "add") {
let positions = getAvailablePositions( let positions = getAvailablePositions(
component.settings.addressType, component.settings.addressType,
component.settings.type component.settings.type,
component.settings.subtype
); );
availablePosition = positions ? positions.length > 0 : false; availablePosition = positions ? positions.length > 0 : false;
} }
@ -285,7 +289,7 @@ export default function ComponentSettings(props: Props) {
component.settings.subtype component.settings.subtype
); );
for (let i = 0; i < addressTypes.length; i++) { for (let i = 0; i < addressTypes.length; i++) {
if (getAvailablePositions(addressTypes[i], component.settings.type).length > 0) { if (getAvailablePositions(addressTypes[i], component.settings.type, component.settings.subtype).length > 0) {
return true; return true;
} }
} }
@ -383,7 +387,7 @@ export default function ComponentSettings(props: Props) {
formComponent.settings.subtype formComponent.settings.subtype
)[0]); )[0]);
formComponent.settings.address = or( formComponent.settings.address = or(
getAvailablePositions(formComponent.settings.addressType, formComponent.settings.type)[0], getAvailablePositions(formComponent.settings.addressType, formComponent.settings.type, formComponent.settings.subtype)[0],
Component.create().settings.address Component.create().settings.address
); );
formComponent.settings.name = option.label; formComponent.settings.name = option.label;
@ -426,7 +430,7 @@ export default function ComponentSettings(props: Props) {
let availableMenuItemPositions = []; let availableMenuItemPositions = [];
for (let i = 0; i < addressTypes.length; i++) { for (let i = 0; i < addressTypes.length; i++) {
let addressType = addressTypes[i]; let addressType = addressTypes[i];
let availablePositions = getAvailablePositions(addressType, type); let availablePositions = getAvailablePositions(addressType, type, subtype);
availableMenuItemPositions.push( availableMenuItemPositions.push(
<MenuItem key={addressType} divider disabled> <MenuItem key={addressType} divider disabled>

View file

@ -40,7 +40,7 @@ export default function DeviceScannedComponents(props: Props){
const [openDialog, setOpenDialog] = useState(false); const [openDialog, setOpenDialog] = useState(false);
useEffect(()=>{ useEffect(()=>{
console.log(scannedComponents) //console.log(scannedComponents)
if (scannedComponents.i2c) setScannedI2C(scannedComponents.i2c) if (scannedComponents.i2c) setScannedI2C(scannedComponents.i2c)
//makes the array empty for testing //makes the array empty for testing
// if (scannedComponents.i2c) { // if (scannedComponents.i2c) {
@ -226,7 +226,9 @@ export default function DeviceScannedComponents(props: Props){
<Button variant="contained" color="primary" onClick={()=>{removeScan(scannedI2C.key)}}>Clear Scan</Button> <Button variant="contained" color="primary" onClick={()=>{removeScan(scannedI2C.key)}}>Clear Scan</Button>
</Box> </Box>
{validCompAddresses.length > 0 ? validCompAddresses.map((addr, index) => { {validCompAddresses.length > 0 ? validCompAddresses.map((addr, index) => {
return <ScannedI2C key={index} sensorNum={index+1} addressData={addr} deviceProduct={device.settings.product} componentSelectionCallback={componentSelection} availablePositions={availablePositions.get(quack.AddressType.ADDRESS_TYPE_I2C) ?? new Map<quack.ComponentType, number[]>()}/> return (
<ScannedI2C key={index} sensorNum={index+1} addressData={addr} deviceProduct={device.settings.product} componentSelectionCallback={componentSelection} availablePositions={availablePositions.get(quack.AddressType.ADDRESS_TYPE_I2C) ?? new Map<quack.ComponentType, number[]>()}/>
)
}) : }) :
<Box sx={{ <Box sx={{
margin: 1, margin: 1,

View file

@ -15,7 +15,7 @@ interface Props {
sensorNum?: number sensorNum?: number
} }
export default function ScannedI2C(props: Props){ export default function ScannedI2C(props: Props){
const {addressData, deviceProduct, componentSelectionCallback, availablePositions, sensorNum} = props const {addressData, 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>>([])
@ -107,6 +107,7 @@ export default function ScannedI2C(props: Props){
return ( return (
<Box sx={{marginY: 1}}> <Box sx={{marginY: 1}}>
{sensorNum && <Typography>Sensor {sensorNum}</Typography>} {sensorNum && <Typography>Sensor {sensorNum}</Typography>}
<Typography variant="caption">{addressData.expansionLine !== 0 && "Expansion Line: " + addressData.expansionLine}</Typography>
{types.length > 0 ? {types.length > 0 ?
<React.Fragment> <React.Fragment>
<Grid2 container direction="row" alignItems="center" justifyContent="space-between"> <Grid2 container direction="row" alignItems="center" justifyContent="space-between">
@ -145,7 +146,7 @@ export default function ScannedI2C(props: Props){
}} }}
select> select>
{subtypeOptions.map(s => ( {subtypeOptions.map(s => (
<MenuItem value={s.key}>{s.friendlyName}</MenuItem> <MenuItem key={s.key} value={s.key}>{s.friendlyName}</MenuItem>
))} ))}
</TextField> </TextField>
} }

View file

@ -338,14 +338,12 @@ export default function GateDevice(props: Props) {
return ( return (
<Grid <Grid
container container
direction="row" direction={(isMobile) ? "column" : "row"}
alignContent="center"
width="100%" width="100%"
//alignItems="center" alignItems="center"
> >
<Grid size={{ sm: 12, lg: isMobile || drawerView ? 12 : 4}} style={{ padding: 10 }}> <Grid size={{ sm: 12, lg: isMobile || drawerView ? 12 : 4}} style={{ padding: 10 }}>
<Box <Box
paddingLeft={1}
display="flex" display="flex"
justifyContent="space-between" justifyContent="space-between"
alignItems="center" alignItems="center"
@ -364,20 +362,6 @@ export default function GateDevice(props: Props) {
} }
]} ]}
/> />
{/* <ToggleButtonGroup value={detail} exclusive size="small" aria-label="detail">
<ToggleButton
onClick={() => setDetail("analytics")}
value={"analytics"}
aria-label="Analysis Graphs">
Analysis
</ToggleButton>
<ToggleButton
onClick={() => setDetail("sensors")}
value={"sensors"}
aria-label="Sensor Graphs">
Sensors
</ToggleButton>
</ToggleButtonGroup> */}
</Box> </Box>
<Card raised> <Card raised>
<GateSVG <GateSVG

View file

@ -258,9 +258,9 @@ export default function GateFlowGraph(props: Props) {
<React.Fragment> <React.Fragment>
{flowEvents.length > 0 ? ( {flowEvents.length > 0 ? (
<Grid container direction="row" spacing={2} className={classes.eventGrid}> <Grid container direction="row" spacing={2} className={classes.eventGrid}>
<Grid size={3}> <Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.eventCard}> <Card raised className={classes.eventCard}>
<Grid container alignItems="center"> <Grid container width="100%" direction="column" alignItems="center">
<Grid size={9}> <Grid size={9}>
<Box display="flex" justifyContent="space-between"> <Box display="flex" justifyContent="space-between">
<Typography>Total Events:</Typography> <Typography>Total Events:</Typography>
@ -271,7 +271,7 @@ export default function GateFlowGraph(props: Props) {
</Grid> </Grid>
<Grid size={9}> <Grid size={9}>
<Box display="flex" justifyContent="space-between"> <Box display="flex" justifyContent="space-between">
<Typography>Total Time:</Typography> <Typography>Time:</Typography>
<Typography style={{ fontWeight: 650, fontSize: 15 }}> <Typography style={{ fontWeight: 650, fontSize: 15 }}>
{moment.duration(totalTimeS, "s").humanize()} {moment.duration(totalTimeS, "s").humanize()}
</Typography> </Typography>
@ -280,9 +280,9 @@ export default function GateFlowGraph(props: Props) {
</Grid> </Grid>
</Card> </Card>
</Grid> </Grid>
<Grid size={3}> <Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.eventCard}> <Card raised className={classes.eventCard}>
<Grid container alignItems="center"> <Grid container width="100%" direction="column" alignItems="center">
<Grid size={9}> <Grid size={9}>
<Box display="flex" justifyContent="space-between"> <Box display="flex" justifyContent="space-between">
<Typography>Events Inside:</Typography> <Typography>Events Inside:</Typography>
@ -293,7 +293,7 @@ export default function GateFlowGraph(props: Props) {
</Grid> </Grid>
<Grid size={9}> <Grid size={9}>
<Box display="flex" justifyContent="space-between"> <Box display="flex" justifyContent="space-between">
<Typography>Event Time:</Typography> <Typography>Time:</Typography>
<Typography style={{ fontWeight: 650, fontSize: 15 }}> <Typography style={{ fontWeight: 650, fontSize: 15 }}>
{moment.duration(timeSInside, "s").humanize()} {moment.duration(timeSInside, "s").humanize()}
</Typography> </Typography>
@ -302,9 +302,9 @@ export default function GateFlowGraph(props: Props) {
</Grid> </Grid>
</Card> </Card>
</Grid> </Grid>
<Grid size={3}> <Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.eventCard}> <Card raised className={classes.eventCard}>
<Grid container alignItems="center"> <Grid container width="100%" direction="column" alignItems="center">
<Grid size={9}> <Grid size={9}>
<Box display="flex" justifyContent="space-between"> <Box display="flex" justifyContent="space-between">
<Typography>Events Outside:</Typography> <Typography>Events Outside:</Typography>
@ -315,7 +315,7 @@ export default function GateFlowGraph(props: Props) {
</Grid> </Grid>
<Grid size={9}> <Grid size={9}>
<Box display="flex" justifyContent="space-between"> <Box display="flex" justifyContent="space-between">
<Typography>Event Time:</Typography> <Typography>Time:</Typography>
<Typography style={{ fontWeight: 650, fontSize: 15 }}> <Typography style={{ fontWeight: 650, fontSize: 15 }}>
{moment.duration(timeSOutside, "s").humanize()} {moment.duration(timeSOutside, "s").humanize()}
</Typography> </Typography>
@ -324,15 +324,15 @@ export default function GateFlowGraph(props: Props) {
</Grid> </Grid>
</Card> </Card>
</Grid> </Grid>
<Grid size={3}> <Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.eventCard}> <Card raised className={classes.eventCard}>
<Grid container direction="column" alignItems="center"> <Grid container width="100%" direction="column" alignItems="center">
<Grid> <Grid>
<Typography>Performance:</Typography> <Typography>Performance:</Typography>
</Grid> </Grid>
<Grid> <Grid>
<Typography style={{ fontWeight: 650, fontSize: 15 }}> <Typography style={{ fontWeight: 650, fontSize: 15 }}>
{(eventsInside / totalEvents) * 100}% {((eventsInside / totalEvents) * 100).toFixed(2)}%
</Typography> </Typography>
</Grid> </Grid>
</Grid> </Grid>
@ -367,7 +367,7 @@ export default function GateFlowGraph(props: Props) {
<React.Fragment> <React.Fragment>
{runtime && ( {runtime && (
<Grid container direction="row" spacing={2} className={classes.runtimeGrid}> <Grid container direction="row" spacing={2} className={classes.runtimeGrid}>
<Grid size={3}> <Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.calcCard}> <Card raised className={classes.calcCard}>
<Grid container width="100%" direction="column" alignItems="center"> <Grid container width="100%" direction="column" alignItems="center">
<Grid>Approximate Runtime:</Grid> <Grid>Approximate Runtime:</Grid>
@ -379,7 +379,7 @@ export default function GateFlowGraph(props: Props) {
</Grid> </Grid>
</Card> </Card>
</Grid> </Grid>
<Grid size={3}> <Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.calcCard}> <Card raised className={classes.calcCard}>
<Grid container width="100%" direction="column" alignItems="center"> <Grid container width="100%" direction="column" alignItems="center">
<Grid>Cost to run PCA:</Grid> <Grid>Cost to run PCA:</Grid>
@ -394,7 +394,7 @@ export default function GateFlowGraph(props: Props) {
</Grid> </Grid>
</Card> </Card>
</Grid> </Grid>
<Grid size={3}> <Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.calcCard}> <Card raised className={classes.calcCard}>
<Grid container width="100%" direction="column" alignItems="center"> <Grid container width="100%" direction="column" alignItems="center">
<Grid>Cost to run APU:</Grid> <Grid>Cost to run APU:</Grid>
@ -409,7 +409,7 @@ export default function GateFlowGraph(props: Props) {
</Grid> </Grid>
</Card> </Card>
</Grid> </Grid>
<Grid size={3}> <Grid size={{xs: 6, md: 3}}>
<Card raised className={classes.calcCard}> <Card raised className={classes.calcCard}>
<Grid container width="100%" direction="column" alignItems="center"> <Grid container width="100%" direction="column" alignItems="center">
<Grid>Total Cost:</Grid> <Grid>Total Cost:</Grid>

View file

@ -190,7 +190,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
[ [
pond.Grain.GRAIN_OATS, pond.Grain.GRAIN_OATS,
{ {
name: "Oats", name: "Oats a",
group: "Oats", group: "Oats",
equation: Equation.henderson, equation: Equation.henderson,
a: 0.000085511, a: 0.000085511,
@ -203,6 +203,40 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
weightConversionKg: 15.4222988297197, weightConversionKg: 15.4222988297197,
bushelsPerTonne: 64.842 bushelsPerTonne: 64.842
} }
],
[
pond.Grain.GRAIN_OATS_B,
{
name: "Oats b",
group: "Oats",
equation: Equation.chungPfost,
a: 442.85,
b: 0.21228,
c: 35.803,
setTempC: 32.0,
targetMC: 13.6,
img: OatImg,
colour: "#79955a",
weightConversionKg: 15.4222988297197,
bushelsPerTonne: 64.842
}
],
[
pond.Grain.GRAIN_OATS_C,
{
name: "Oats c",
group: "Oats",
equation: Equation.oswin,
a: 12.412,
b: -0.060707,
c: 2.9397,
setTempC: 32.0,
targetMC: 13.6,
img: OatImg,
colour: "#79955a",
weightConversionKg: 15.4222988297197,
bushelsPerTonne: 64.842
}
], ],
[ [
pond.Grain.GRAIN_PEANUTS, pond.Grain.GRAIN_PEANUTS,
@ -506,6 +540,66 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
weightConversionKg: 27.2155, weightConversionKg: 27.2155,
bushelsPerTonne: 36.744 bushelsPerTonne: 36.744
} }
],[
pond.Grain.GRAIN_RYE_A,
{
name: "Rye a",
group: "Rye",
equation: Equation.henderson,
a: 0.00006343,
b: 2.2060,
c: 13.1810,
setTempC: defaultSetTemp,
targetMC: 15.0,
colour: "grey",
weightConversionKg: 25.4,
bushelsPerTonne: 39.368
}
],[
pond.Grain.GRAIN_RYE_B,
{
name: "Rye b",
group: "Rye",
equation: Equation.chungPfost,
a: 461.0230,
b: 0.1840,
c: 36.7410,
setTempC: defaultSetTemp,
targetMC: 15.0,
colour: "grey",
weightConversionKg: 25.4,
bushelsPerTonne: 39.368
},
],[
pond.Grain.GRAIN_RYE_C,
{
name: "Rye c",
group: "Rye",
equation: Equation.halsey,
a: 4.2970,
b: 0.380,
c: 2.2710,
setTempC: defaultSetTemp,
targetMC: 15.0,
colour: "grey",
weightConversionKg: 25.4,
bushelsPerTonne: 39.368
},
],[
pond.Grain.GRAIN_RYE_D,
{
name: "Rye d",
group: "Rye",
equation: Equation.oswin,
a: 11.8870,
b: 0.0210,
c: 3.2620,
setTempC: defaultSetTemp,
targetMC: 15.0,
colour: "grey",
weightConversionKg: 25.4,
bushelsPerTonne: 39.368
}
] ]
]); ]);

View file

@ -182,7 +182,7 @@ export default function LibraCartAccess() {
color="primary" color="primary"
onClick={e => { onClick={e => {
e.preventDefault(); e.preventDefault();
window.open(`https://staging.cloud.agrimatics.com/grain/integrations`, "_blank"); window.open(`https://cloud.agrimatics.com/grain/integrations`, "_blank");
}}> }}>
Link Libra Cart Account Link Libra Cart Account
</Button> </Button>
@ -196,7 +196,7 @@ export default function LibraCartAccess() {
</Grid> </Grid>
</Grid> </Grid>
<Grid> <Grid>
<Tooltip title={"This will Sync the data for all LibraCart accounts linked to " + (as ? "this team" : "your account")}> <Tooltip title={"This will Sync the data for all Libra Cart accounts linked to " + (as ? "this team" : "your account")}>
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
@ -210,7 +210,7 @@ export default function LibraCartAccess() {
</Grid> </Grid>
<Box paddingTop={2}> <Box paddingTop={2}>
<Typography> <Typography>
LibraCart data will sync every 6 hours, if the data is needed immediately you can select the Sync button. It may take up to 15 minutes for the data to appear in our platform. Libra Cart data will sync every 6 hours, if the data is needed immediately you can select the Sync button. It may take up to 15 minutes for the data to appear in our platform.
</Typography> </Typography>
</Box> </Box>

View file

@ -96,7 +96,7 @@ export class Bin {
} }
public empty(): boolean { public empty(): boolean {
return this.settings.inventory?.empty || (this.settings.inventory !== undefined && this.settings.inventory !== null && this.settings.inventory.grainBushels < 5); return this.settings.inventory?.empty || (this.settings.inventory !== undefined && this.settings.inventory !== null && this.bushels() < 5);
} }
public objectType(): pond.ObjectType { public objectType(): pond.ObjectType {

View file

@ -273,7 +273,7 @@ export class GrainCable {
let filtered: number[] = [] let filtered: number[] = []
values.forEach((val, index) => { values.forEach((val, index) => {
//if the node is not excluded AND is either under the top node OR top node is not set //if the node is not excluded AND is either under the top node OR top node is not set
if(!this.excludedNodes.includes(index) && (index <= this.topNode || this.topNode === 0)){ if(!this.excludedNodes.includes(index) && (index < this.topNode || this.topNode === 0)){
filtered.push(val) filtered.push(val)
} }
}) })

View file

@ -70,19 +70,26 @@ const useStyles = makeStyles((theme: Theme) => ({
}) })
}, },
sideMenuOnClosed: { sideMenuOnClosed: {
transition: theme.transitions.create(["width", "z-index", "opacity"], { transition: theme.transitions.create(["width"], {
easing: theme.transitions.easing.sharp, easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen duration: theme.transitions.duration.leavingScreen
}), }),
overflowX: "hidden", // overflowX: "hidden",
width: theme.spacing(7), width: theme.spacing(0),
zIndex: theme.zIndex.drawer, // zIndex: theme.zIndex.drawer,
opacity: 0, // opacity: 0,
[theme.breakpoints.up("md")]: { [theme.breakpoints.up("md")]: {
width: theme.spacing(9), width: theme.spacing(9.25),
opacity: 1 opacity: 1
} }
}, },
sideMenuOnClosedMobile: {
transition: theme.transitions.create(["width"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
width: theme.spacing(0),
},
list: { list: {
paddingTop: 0 paddingTop: 0
}, },
@ -480,7 +487,7 @@ export default function SideNavigator(props: Props) {
</Tooltip> </Tooltip>
} }
{user.hasFeature("libra-cart") && {user.hasFeature("libra-cart") &&
<Tooltip title="LibraCart" placement="right"> <Tooltip title="Libra Cart" placement="right">
<ListItemButton <ListItemButton
id="tour-libraCart" id="tour-libraCart"
onClick={() => goTo("/libracart")} onClick={() => goTo("/libracart")}
@ -489,7 +496,7 @@ export default function SideNavigator(props: Props) {
<ListItemIcon> <ListItemIcon>
<LibraCartIcon /> <LibraCartIcon />
</ListItemIcon> </ListItemIcon>
{open && <ListItemText primary="LibraCart" />} {open && <ListItemText primary="Libra Cart" />}
</ListItemButton> </ListItemButton>
</Tooltip> </Tooltip>
} }
@ -504,7 +511,7 @@ export default function SideNavigator(props: Props) {
classes={{ classes={{
paper: classNames( paper: classNames(
classes.sideMenu, classes.sideMenu,
open ? classes.sideMenuOpened : classes.sideMenuOnClosed open ? classes.sideMenuOpened : (isMobile ? classes.sideMenuOnClosedMobile : classes.sideMenuOnClosed)
) )
}} }}
anchor="left" anchor="left"

View file

@ -810,7 +810,8 @@ export default function Bin(props: Props) {
</Grid> </Grid>
<Grid size={{ xs: 12, sm: 12, md: 12, lg: 2.6 }}> <Grid size={{ xs: 12, sm: 12, md: 12, lg: 2.6 }}>
{(bin.settings.mode === pond.BinMode.BIN_MODE_DRYING || {(bin.settings.mode === pond.BinMode.BIN_MODE_DRYING ||
bin.settings.mode === pond.BinMode.BIN_MODE_HYDRATING) && ( bin.settings.mode === pond.BinMode.BIN_MODE_HYDRATING ||
bin.settings.mode === pond.BinMode.BIN_MODE_COOLDOWN) && (
<BinConditioningCard <BinConditioningCard
mode={bin.settings.mode} mode={bin.settings.mode}
interactions={interactions} interactions={interactions}

View file

@ -472,7 +472,7 @@ export default function Devices() {
] ]
if (hasPlenums) { if (hasPlenums) {
columns.push({ columns.push({
title: "Plenum", title: "Temp/Humidity",
// sortKey: "hi", // sortKey: "hi",
// disableSort: true, // disableSort: true,
render: (device: Device) => { render: (device: Device) => {

View file

@ -103,7 +103,7 @@ export default function LibraCart() {
<Grid> <Grid>
<Select <Select
//style={{ maxWidth: 110 }} //style={{ maxWidth: 110 }}
title="LibraCart Account" title="Libra Cart Account"
displayEmpty displayEmpty
disableUnderline={true} disableUnderline={true}
value={currentOrg} value={currentOrg}

View file

@ -129,7 +129,7 @@ export default function Users() {
"marketplace", "marketplace",
"installer", "installer",
"cnhi", "cnhi",
"libracart" "libra-cart"
].sort(); ].sort();
const [rows, setRows] = useState<User[]>([]) const [rows, setRows] = useState<User[]>([])

View file

@ -164,6 +164,10 @@ export interface ComponentTypeExtension {
filters?: GraphFilters filters?: GraphFilters
) => LineChartData; ) => LineChartData;
secondaryComponentSettings?: () => pond.ComponentSettings[] secondaryComponentSettings?: () => pond.ComponentSettings[]
//(only for I2C) possibly add an optional map here that has a number (subtype) as the key and the addresses available for that subtype
//if the map does not exist then just use the array from the device availability,
//if the map does exist filter the availability after claims to find matching address between them and use that
subtypeI2CMap?: Map<number, number[]>
} }
export interface Summary { export interface Summary {
@ -1090,3 +1094,31 @@ export const GetNumNodesFromUnitMeasurement = (unitMeasurement: UnitMeasurement)
} }
return nodes; return nodes;
}; };
export const FilterSubtypeI2C = (
componentType: quack.ComponentType,
subtype: number,
positions: number[]
) => {
console.log("Filter I2C")
console.log(positions)
let filtered: number[] = []
//create an extension using the type and subtype
let ext = extension(componentType, subtype)
//if the extension has a subtype map
if (ext.subtypeI2CMap !== undefined){
//check if any of the position passed in match to the map values according to the subtype
let subtypePositions = ext.subtypeI2CMap.get(subtype)
if(subtypePositions !== undefined){
positions.forEach(position => {
if(subtypePositions.includes(position)){
filtered.push(position)
}
})
}
}else{
filtered = positions
}
//return any matches between the arrays
return filtered
}

View file

@ -95,6 +95,11 @@ export function Lidar(subtype: number = 0): ComponentTypeExtension {
minMeasurementPeriodMs: 1000, minMeasurementPeriodMs: 1000,
icon: (theme?: "light" | "dark"): string | undefined => { icon: (theme?: "light" | "dark"): string | undefined => {
return theme === "light" ? LidarSensorDarkIcon : LidarSensorLightIcon; return theme === "light" ? LidarSensorDarkIcon : LidarSensorLightIcon;
} },
subtypeI2CMap: new Map<number, number[]>([
[quack.LidarSubtype.LIDAR_SUBTYPE_NONE, [0x62]],
[quack.LidarSubtype.LIDAR_SUBTYPE_LONG_DISTANCE, [0x66]],
[quack.LidarSubtype.LIDAR_SUBTYPE_BENEWAKE_TF02W_MID_RANGE, [0x10]],
])
}; };
} }

View file

@ -5,6 +5,7 @@ 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 { or } from "utils/types"; import { or } from "utils/types";
import { FilterSubtypeI2C } from "./ComponentType";
export type ComponentAvailabilityMap = Map<quack.ComponentType, number[]>; export type ComponentAvailabilityMap = Map<quack.ComponentType, number[]>;
export type DevicePositions = number[] | ComponentAvailabilityMap | ConfigurablePin[]; export type DevicePositions = number[] | ComponentAvailabilityMap | ConfigurablePin[];
@ -47,16 +48,17 @@ const DefaultAvailability: DeviceAvailabilityMap = new Map<quack.AddressType, De
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, DefaultPinArray], [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, DefaultPinArray],
[ [
quack.AddressType.ADDRESS_TYPE_I2C, quack.AddressType.ADDRESS_TYPE_I2C,
//this will contain all the possible addresses for a component type, if subtypes use specific addresses add a filter to the type describer, see lidar.ts for an example
new Map<quack.ComponentType, number[]>([ new Map<quack.ComponentType, number[]>([
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18, 0x19, 0x1a]], [quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18, 0x19, 0x1a]],
[quack.ComponentType.COMPONENT_TYPE_LIGHT, [0x29]], [quack.ComponentType.COMPONENT_TYPE_LIGHT, [0x29]],
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]], [quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
[quack.ComponentType.COMPONENT_TYPE_STEPPER_MOTOR, [0x14, 0x15, 0x16, 0x17]], [quack.ComponentType.COMPONENT_TYPE_STEPPER_MOTOR, [0x14, 0x15, 0x16, 0x17]],
[quack.ComponentType.COMPONENT_TYPE_PH, [0x40]], [quack.ComponentType.COMPONENT_TYPE_PH, [0x40]],
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40, 0x61]], //TODO temp fix, needs to be fixed to use channels similar to PH [quack.ComponentType.COMPONENT_TYPE_DHT, [0x40, 0x61]],
// [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated? // [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated?
[quack.ComponentType.COMPONENT_TYPE_AIR_QUALITY, [0x58]], [quack.ComponentType.COMPONENT_TYPE_AIR_QUALITY, [0x58]],
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]], [quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62, 0x66, 0x10]],
[quack.ComponentType.COMPONENT_TYPE_CAPACITANCE, [0x50]], [quack.ComponentType.COMPONENT_TYPE_CAPACITANCE, [0x50]],
[quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE, [0x50]], [quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE, [0x50]],
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]], [quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],

View file

@ -71,16 +71,19 @@ export function describePower(power?: pond.DevicePower | null): PowerDescriber {
} }
} else { } else {
let thresholds = notChargingThresholds; let thresholds = notChargingThresholds;
let suffix = "not charging"; // we would not actually know if it is charging here since it is just looking at the voltage now and not comparing to previous voltage
if (power.inputVoltage >= 4) { // let suffix = "not charging";
thresholds = chargingThresholds; // if (power.inputVoltage >= 4) {
if (power.chargePercent === 100) { // thresholds = chargingThresholds;
suffix = "charged"; // if (power.chargePercent === 100) {
} else { // suffix = "charged";
suffix = "charging"; // } else {
} // suffix = "charging";
} // }
result.description = power.chargePercent.toString() + "%, " + suffix; // }
// result.description = power.chargePercent.toString() + "%, " + suffix;
// TODO: there is a plan in place to add a boolean value to power in the backend to have it check and set the boolean, we can then use that to know if it is charging or not
result.description = power.chargePercent.toString() + "%"
for (let i = 0; i < thresholds.length; i++) { for (let i = 0; i < thresholds.length; i++) {
if (power.chargePercent >= thresholds[i].threshold) { if (power.chargePercent >= thresholds[i].threshold) {
result.icon = thresholds[i].icon; result.icon = thresholds[i].icon;

View file

@ -249,7 +249,7 @@ export const BindaptV2MonitorAvailability: DeviceAvailabilityMap = new Map<
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]], [quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]], [quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
// [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated? // [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated?
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]], [quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62, 0x66, 0x10]],
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]], [quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]], [quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]] [quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]]
@ -282,7 +282,7 @@ export const BindaptV2AutomateAvailability: DeviceAvailabilityMap = new Map<
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]], [quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]], [quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
// [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated? // [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated?
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]], [quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62, 0x66, 0x10]],
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]], [quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]], [quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]] [quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]]

View file

@ -10,7 +10,7 @@ import {
Textsms as TextIcon, Textsms as TextIcon,
Contrast, Contrast,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { AppBar, Box, Button, Checkbox, CircularProgress, Collapse, Divider, FormControl, FormControlLabel, FormGroup, FormHelperText, FormLabel, Grid2, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText, MenuItem, Radio, RadioGroup, TextField, Theme, ToggleButton, ToggleButtonGroup, Toolbar, Tooltip, Typography } from "@mui/material"; import { AppBar, Box, Button, Checkbox, CircularProgress, Collapse, Divider, FormControl, FormControlLabel, FormGroup, FormHelperText, FormLabel, Grid2, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText, MenuItem, Radio, RadioGroup, Slider, TextField, Theme, ToggleButton, ToggleButtonGroup, Toolbar, Tooltip, Typography } from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog"; import ResponsiveDialog from "common/ResponsiveDialog";
import UserAvatar from "./UserAvatar"; import UserAvatar from "./UserAvatar";
import { useGlobalState, useSnackbar, useUserAPI } from "providers"; import { useGlobalState, useSnackbar, useUserAPI } from "providers";
@ -31,6 +31,7 @@ import UnitsIcon from "@mui/icons-material/Category";
import { setThemeType } from "theme"; import { setThemeType } from "theme";
import { IsAdaptiveAgriculture } from "services/whiteLabel"; import { IsAdaptiveAgriculture } from "services/whiteLabel";
import { setDistanceUnit, setGrainUnit, setPressureUnit, setTemperatureUnit } from "utils"; import { setDistanceUnit, setGrainUnit, setPressureUnit, setTemperatureUnit } from "utils";
import FieldsIcon from "products/AgIcons/FieldsIcon";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
return ({ return ({
@ -82,7 +83,9 @@ export default function UserSettings(props: Props) {
const [avatarExpanded, setAvatarExpanded] = useState<boolean>(false); const [avatarExpanded, setAvatarExpanded] = useState<boolean>(false);
const [notificationsExpanded, setNotificationsExpanded] = useState<boolean>(false); const [notificationsExpanded, setNotificationsExpanded] = useState<boolean>(false);
const [unitsExpanded, setUnitsExpanded] = useState<boolean>(false); const [unitsExpanded, setUnitsExpanded] = useState<boolean>(false);
const [mapsExpanded, setMapsExpanded] = useState<boolean>(false);
const [avatarUrl, setAvatarUrl] = useState<string>(""); const [avatarUrl, setAvatarUrl] = useState<string>("");
const [sliderVal, setSliderVal] = useState(globalState.user.settings.mapZoom);
// const [themeValue, setThemeValue] = useState<"light" | "dark" | "system">(themeMode.mode) // const [themeValue, setThemeValue] = useState<"light" | "dark" | "system">(themeMode.mode)
useEffect(() => { useEffect(() => {
@ -151,6 +154,30 @@ export default function UserSettings(props: Props) {
setUser(updatedUser); setUser(updatedUser);
}; };
const changeDefaultZoom = (value: number) => {
setSliderVal(value);
let updatedUser = User.clone(user);
updatedUser.settings.mapZoom = value;
setUser(updatedUser)
};
const mapSettings = () => {
return (
<Box display="flex">
<Typography>Map Zoom Level</Typography>
<Slider
valueLabelDisplay="auto"
value={sliderVal}
min={1}
max={16}
onChange={(_, val) => {
changeDefaultZoom(val as number);
}}
/>
</Box>
);
};
const generalSettings = () => { const generalSettings = () => {
const { name, email, phoneNumber, timezone } = user.settings; const { name, email, phoneNumber, timezone } = user.settings;
@ -631,15 +658,15 @@ export default function UserSettings(props: Props) {
</List> </List>
</Collapse> </Collapse>
{/* <Divider /> <Divider />
<ListItem button onClick={() => setMapsExpanded(!mapsExpanded)}> <ListItemButton onClick={() => setMapsExpanded(!mapsExpanded)}>
<ListItemIcon> <ListItemIcon>
<FieldsIcon /> <FieldsIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={"Map Settings"} /> <ListItemText primary={"Map Settings"} />
{mapsExpanded ? <ExpandLess /> : <ExpandMore />} {mapsExpanded ? <ExpandLess /> : <ExpandMore />}
</ListItem> </ListItemButton>
<Collapse in={mapsExpanded} timeout="auto" unmountOnExit> <Collapse in={mapsExpanded} timeout="auto" unmountOnExit>
<List component="div"> <List component="div">
<ListItem> <ListItem>
@ -648,7 +675,7 @@ export default function UserSettings(props: Props) {
</ListItemText> </ListItemText>
</ListItem> </ListItem>
</List> </List>
</Collapse> */} </Collapse>
</List> </List>
); );
}; };

View file

@ -62,14 +62,14 @@ const agFeatureList: ProductDetails[] = [
// bulletPoints: ["bullet one", "bullet two"], // bulletPoints: ["bullet one", "bullet two"],
// questions: [], // questions: [],
}, },
{ // {
featureName: "libra-cart", // featureName: "libra-cart",
featureLogo: <LibraCartIcon />, // featureLogo: <LibraCartIcon />,
featureCost: 0, // featureCost: 0,
cardImage: FeatureImageTest, // cardImage: FeatureImageTest,
featureTitle: "Libra Cart", // featureTitle: "Libra Cart",
longDescription: "Integrate with Libra Cart to bring your data into our platform" // longDescription: "Integrate with Libra Cart to bring your data into our platform"
} // }
]; ];
// const constructionFeatureList: ProductDetails[] = [] // const constructionFeatureList: ProductDetails[] = []