Merge branch 'airflow' into node_exclusion_update

This commit is contained in:
csawatzky 2025-07-28 15:30:14 -06:00
commit 25fc5021fe
13 changed files with 107 additions and 31 deletions

2
package-lock.json generated
View file

@ -10911,7 +10911,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#83d9f47f0512161504cc82b52d850105807695b0", "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dc29e71ec9598556c1b7cdff4dc02f74ecbac20c",
"dependencies": { "dependencies": {
"protobufjs": "^6.8.8" "protobufjs": "^6.8.8"
} }

View file

@ -49,7 +49,7 @@ export default function BinsList(props: Props) {
//const scrollRef = useRef<any>(null); //const scrollRef = useRef<any>(null);
const goToBin = (i: number) => { const goToBin = (i: number) => {
let path = "/bins/" + bins[i].key(); let path = bins[i].key();
navigate(path, { state: {bin: bins[i]} }); navigate(path, { state: {bin: bins[i]} });
}; };

View file

@ -482,7 +482,7 @@ export default function ComponentForm(props: Props) {
const updateCalibrationTypes = (event: any, index: number) => { const updateCalibrationTypes = (event: any, index: number) => {
let f = cloneDeep(form); let f = cloneDeep(form);
f.component.settings.calibrations[index].type = event.target.value as quack.MeasurementType; f.component.settings.calibrations[index].measurementType = event.target.value as quack.MeasurementType;
setForm(f); setForm(f);
}; };
@ -508,7 +508,7 @@ export default function ComponentForm(props: Props) {
if (dimensions !== undefined && dimensions !== null) { if (dimensions !== undefined && dimensions !== null) {
dimensions.lengthCm = value; dimensions.lengthCm = value;
} else { } else {
dimensions = pond.Dimensions.create({ dimensions = quack.Dimensions.create({
heightCm: 0, heightCm: 0,
lengthCm: value, lengthCm: value,
widthCm: 0 widthCm: 0
@ -537,7 +537,7 @@ export default function ComponentForm(props: Props) {
if (dimensions !== undefined && dimensions !== null) { if (dimensions !== undefined && dimensions !== null) {
dimensions.heightCm = value; dimensions.heightCm = value;
} else { } else {
dimensions = pond.Dimensions.create({ dimensions = quack.Dimensions.create({
heightCm: value, heightCm: value,
lengthCm: 0, lengthCm: 0,
widthCm: 0 widthCm: 0
@ -566,7 +566,7 @@ export default function ComponentForm(props: Props) {
if (dimensions !== undefined && dimensions !== null) { if (dimensions !== undefined && dimensions !== null) {
dimensions.widthCm = value; dimensions.widthCm = value;
} else { } else {
dimensions = pond.Dimensions.create({ dimensions = quack.Dimensions.create({
heightCm: 0, heightCm: 0,
lengthCm: 0, lengthCm: 0,
widthCm: value widthCm: value
@ -788,7 +788,7 @@ export default function ComponentForm(props: Props) {
// !isMeasurementTypeValid(interaction.settings.conditions[index].measurementType) // !isMeasurementTypeValid(interaction.settings.conditions[index].measurementType)
// } // }
disabled={!component.settings.calibrate} disabled={!component.settings.calibrate}
value={calibration.type ?? quack.MeasurementType.MEASUREMENT_TYPE_INVALID} value={calibration.measurementType ?? quack.MeasurementType.MEASUREMENT_TYPE_INVALID}
onChange={event => updateCalibrationTypes(event, i)} onChange={event => updateCalibrationTypes(event, i)}
autoFocus={false} autoFocus={false}
margin="normal" margin="normal"
@ -814,8 +814,8 @@ export default function ComponentForm(props: Props) {
f.coefficients.push("0"); f.coefficients.push("0");
f.offsets.push("0"); f.offsets.push("0");
f.component.settings.calibrations.push( f.component.settings.calibrations.push(
pond.Calibration.create({ quack.Calibration.create({
type: quack.MeasurementType.MEASUREMENT_TYPE_INVALID, measurementType: quack.MeasurementType.MEASUREMENT_TYPE_INVALID,
calibrationOffset: 0, calibrationOffset: 0,
calibrationCoefficient: 0 calibrationCoefficient: 0
}) })

View file

@ -256,5 +256,15 @@ function unitConversion(
}); });
} }
} }
if (type === quack.MeasurementType.MEASUREMENT_TYPE_SPEED) {
if (user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
newVals.forEach((val, i) => {
val.values.forEach((v, j) => {
//convert m/s to ft/m by multiplying by 196.9
newVals[i].values[j] = Math.round(v * 196.9)
});
});
}
}
return newVals; return newVals;
} }

View file

@ -81,6 +81,7 @@ import GrainBagList from "grainBag/grainBagList";
import BinYards from "bin/BinYards"; import BinYards from "bin/BinYards";
import ButtonGroup from "common/ButtonGroup"; import ButtonGroup from "common/ButtonGroup";
import TeamDialog from "teams/TeamDialog"; import TeamDialog from "teams/TeamDialog";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
// import { useHistory } from "react-router"; // import { useHistory } from "react-router";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
@ -311,6 +312,16 @@ export default function Bins(props: Props) {
} }
}, [props.yardFilter]); }, [props.yardFilter]);
const getKeys = () => {
if (as) return undefined
return getContextKeys()
}
const getTypes = () => {
if (as) return undefined
return getContextTypes()
}
const loadBins = useCallback(() => { const loadBins = useCallback(() => {
//let filter = grainFilter; //let filter = grainFilter;
let filter = yardFilter; let filter = yardFilter;
@ -325,7 +336,7 @@ export default function Bins(props: Props) {
setDisplayFert(false); setDisplayFert(false);
binAPI binAPI
.listBinsAndData(binsLimit, 0, order, orderBy, filter, as, false) .listBinsAndData(binsLimit, 0, order, orderBy, filter, as, false, getKeys(), getTypes())
.then(resp => { .then(resp => {
let grain: Bin[] = []; let grain: Bin[] = [];
let empty: Bin[] = []; let empty: Bin[] = [];
@ -623,8 +634,9 @@ export default function Bins(props: Props) {
preLoadedData={paginatedBins.bins} preLoadedData={paginatedBins.bins}
rowClickFunction={data => { rowClickFunction={data => {
let bin = data as Bin; let bin = data as Bin;
let path = "/bins/" + bin.key(); // let path = "bins/" + bin.key();
navigate(path, { state: { bin: bin }}); // navigate(path, { state: { bin: bin }});
navigate(bin.key(), { state: { bin: bin }, relative: "path"});
}} }}
customButtons={[ customButtons={[
{ {

View file

@ -12,10 +12,10 @@ import { appendToUrl } from "navigation/Router";
import PageContainer from "pages/PageContainer"; import PageContainer from "pages/PageContainer";
import { getContextKeys, getContextTypes } from "pbHelpers/Context"; import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useSnackbar, useTeamAPI, useUserAPI } from "providers"; import { useGlobalState, useSnackbar, useTeamAPI, useUserAPI } from "providers";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { getSignatureAccentColour } from "services/whiteLabel"; import { getSignatureAccentColour, IsAdaptiveAgriculture, IsStreamline } from "services/whiteLabel";
import TeamActions from "teams/TeamActions"; import TeamActions from "teams/TeamActions";
import TeamKeyManager from "teams/TeamKeyManager"; import TeamKeyManager from "teams/TeamKeyManager";
import ObjectUsers from "user/ObjectUsers"; import ObjectUsers from "user/ObjectUsers";
@ -88,6 +88,7 @@ export default function TeamPage() {
const [loadingUsers, setLoadingUsers] = useState(false) const [loadingUsers, setLoadingUsers] = useState(false)
const [userDialog, setUserDialog] = useState(false) const [userDialog, setUserDialog] = useState(false)
const teamKey = teamID ? teamID : ""; const teamKey = teamID ? teamID : "";
const [{ user }] = useGlobalState()
const getTeam = () => { const getTeam = () => {
@ -212,6 +213,14 @@ export default function TeamPage() {
const toGroups = () => { const toGroups = () => {
navigate(appendToUrl("groups")) navigate(appendToUrl("groups"))
} }
const toBins = () => {
// navigate(appendToUrl("bins"))
navigate("bins")
}
const isAg = IsAdaptiveAgriculture()
const isStreamline = IsStreamline()
return ( return (
<PageContainer spacing={isMobile ? 0 : 2}> <PageContainer spacing={isMobile ? 0 : 2}>
@ -237,6 +246,7 @@ export default function TeamPage() {
isDialogOpen={userDialog} isDialogOpen={userDialog}
closeDialogCallback={() => setUserDialog(false)} closeDialogCallback={() => setUserDialog(false)}
refreshCallback={() => {}} refreshCallback={() => {}}
useImitation={false}
/> />
<Grid2 container spacing={1}> <Grid2 container spacing={1}>
<Grid2 size={{ xs: 12, sm: 4 }}> <Grid2 size={{ xs: 12, sm: 4 }}>
@ -255,6 +265,14 @@ export default function TeamPage() {
<ListItemButton onClick={toGroups}> <ListItemButton onClick={toGroups}>
Groups Groups
</ListItemButton> </ListItemButton>
{((isAg || isStreamline) || user.hasFeature("admin")) &&
<>
<Divider />
<ListItemButton onClick={toBins}>
Bins
</ListItemButton>
</>
}
<Divider /> <Divider />
</List> </List>
</Card> </Card>

View file

@ -16,7 +16,7 @@ export const I2C: AddressTypeExtension = {
[quack.ComponentType.COMPONENT_TYPE_CAPACITANCE, 0x4f], [quack.ComponentType.COMPONENT_TYPE_CAPACITANCE, 0x4f],
[quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE, 0x4f], [quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE, 0x4f],
[quack.ComponentType.COMPONENT_TYPE_SEN5X, 0x68], [quack.ComponentType.COMPONENT_TYPE_SEN5X, 0x68],
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, 0x29] [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, 0x6b]
]); ]);
const offset = offsets.get(componentType); const offset = offsets.get(componentType);

View file

@ -146,7 +146,7 @@ export interface ComponentTypeExtension {
interactionResultTypes: Array<quack.InteractionResultType>; interactionResultTypes: Array<quack.InteractionResultType>;
states: Array<string>; states: Array<string>;
measurements: Array<ComponentMeasurement>; measurements: Array<ComponentMeasurement>;
measurementSummary: Function; //Deprecated: this summary used the old measurement structure measurementSummary?: Function; //Deprecated: this summary used the old measurement structure
unitMeasurementSummary: ( unitMeasurementSummary: (
measurements: convertedUnitMeasurement measurements: convertedUnitMeasurement
) => Summary[]; ) => Summary[];
@ -365,7 +365,8 @@ export async function getMeasurementSummary(
measurement: quack.IMeasurement, measurement: quack.IMeasurement,
filters: GraphFilters filters: GraphFilters
): Promise<Array<Summary>> { ): Promise<Array<Summary>> {
return extension(type, subtype).measurementSummary(measurement, filters); let sumFunc = extension(type, subtype).measurementSummary
return sumFunc ? sumFunc(measurement, filters) : [];
} }
const validNodes = (nodeVals: number[], filters?: GraphFilters) => { const validNodes = (nodeVals: number[], filters?: GraphFilters) => {

View file

@ -1,14 +1,15 @@
import { import {
ComponentTypeExtension, ComponentTypeExtension,
Summary, Summary,
simpleMeasurements, // simpleMeasurements,
simpleSummaries, // simpleSummaries,
unitMeasurementSummaries, unitMeasurementSummaries,
AreaChartData, AreaChartData,
GraphFilters, GraphFilters,
simpleAreaChartData, simpleAreaChartData,
LineChartData, LineChartData,
simpleLineChartData simpleLineChartData,
ComponentMeasurement
} from "pbHelpers/ComponentType"; } from "pbHelpers/ComponentType";
import PressureDarkIcon from "assets/components/pressureDark.png"; import PressureDarkIcon from "assets/components/pressureDark.png";
import PressureLightIcon from "assets/components/pressureLight.png"; import PressureLightIcon from "assets/components/pressureLight.png";
@ -18,14 +19,46 @@ import { convertedUnitMeasurement } from "models/UnitMeasurement";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
export function Airflow(subtype: number = 0): ComponentTypeExtension { export function Airflow(subtype: number = 0): ComponentTypeExtension {
let airflow = describeMeasurement( let cfm = describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_CFM, quack.MeasurementType.MEASUREMENT_TYPE_CFM,
quack.ComponentType.COMPONENT_TYPE_AIRFLOW, quack.ComponentType.COMPONENT_TYPE_AIRFLOW,
subtype subtype
); );
let velocity = describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_CFM,
quack.ComponentType.COMPONENT_TYPE_AIRFLOW,
subtype
);
let measurementTypes = [
{
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PPM,
label: cfm.label(),
colour: cfm.colour(),
graphType: cfm.graph()
} as ComponentMeasurement,
{
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_VOLTAGE,
label: velocity.label(),
colour: velocity.colour(),
graphType: velocity.graph()
} as ComponentMeasurement
];
return { return {
type: quack.ComponentType.COMPONENT_TYPE_AIRFLOW, type: quack.ComponentType.COMPONENT_TYPE_AIRFLOW,
subtypes: [], subtypes: [
{
key: quack.AirFlowSubtype.AIR_FLOW_SUBTYPE_PROSENSE,
value: "AIR_FLOW_SUBTYPE_PROSENSE",
friendlyName: "Prosense"
},
{
key: quack.AirFlowSubtype.AIR_FLOW_SUBTYPE_ULTRASONIC,
value: "AIR_FLOW_SUBTYPE_ULTRASONIC",
friendlyName: "Ultrasonic"
},
],
friendlyName: "Airflow", friendlyName: "Airflow",
description: "Measure the flow of air though a specified area", description: "Measure the flow of air though a specified area",
isController: false, isController: false,
@ -36,10 +69,10 @@ export function Airflow(subtype: number = 0): ComponentTypeExtension {
addressTypes: [quack.AddressType.ADDRESS_TYPE_I2C], addressTypes: [quack.AddressType.ADDRESS_TYPE_I2C],
interactionResultTypes: [], interactionResultTypes: [],
states: [], states: [],
measurements: simpleMeasurements(airflow), measurements: measurementTypes,
measurementSummary: async function(measurement: quack.Measurement): Promise<Array<Summary>> { // measurementSummary: async function(measurement: quack.Measurement): Promise<Array<Summary>> {
return simpleSummaries(measurement, airflow); // return simpleSummaries(measurement, airflow);
}, // },
unitMeasurementSummary: ( unitMeasurementSummary: (
measurements: convertedUnitMeasurement, measurements: convertedUnitMeasurement,
): Summary[] => { ): Summary[] => {

View file

@ -60,7 +60,7 @@ const DefaultAvailability: DeviceAvailabilityMap = new Map<quack.AddressType, De
[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]],
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x2a]] [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]]
]) ])
], ],
[quack.AddressType.ADDRESS_TYPE_DAC, [0, 1]], [quack.AddressType.ADDRESS_TYPE_DAC, [0, 1]],

View file

@ -450,8 +450,9 @@ export class MeasurementDescriber {
this.details.max = 100000; this.details.max = 100000;
break; break;
case quack.MeasurementType.MEASUREMENT_TYPE_SPEED: case quack.MeasurementType.MEASUREMENT_TYPE_SPEED:
let speedUnit = getDistanceUnit()
this.details.label = "Speed"; this.details.label = "Speed";
this.details.unit = "km/h"; this.details.unit = speedUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m/s" : "ft/m";
this.details.colour = green["500"]; this.details.colour = green["500"];
this.details.path = "edgeTriggered.rises"; this.details.path = "edgeTriggered.rises";
this.details.max = 500; this.details.max = 500;

View file

@ -22,7 +22,7 @@ export const MiVentV1Availability: 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_SEN5X, [0x69]], [quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x2a]] [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]]
]) ])
], ],
[quack.AddressType.ADDRESS_TYPE_POWER, [0]], [quack.AddressType.ADDRESS_TYPE_POWER, [0]],
@ -52,7 +52,7 @@ export const MiVentV2Availability: 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_SEN5X, [0x69]], [quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x2a]] [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]]
]) ])
], ],
[quack.AddressType.ADDRESS_TYPE_POWER, [0]], [quack.AddressType.ADDRESS_TYPE_POWER, [0]],

View file

@ -38,7 +38,8 @@ export default function AuthWrapper(props: Props) {
options.authorizationParams.login_hint = parsed.email.toString(); // prefill email options.authorizationParams.login_hint = parsed.email.toString(); // prefill email
} }
options.authorizationParams.redirect_uri = "https://" + url.hostname + "/signupCallback" console.log(url)
options.authorizationParams.redirect_uri = url.origin + "/signupCallback"
console.log(options.authorizationParams.redirect_uri) console.log(options.authorizationParams.redirect_uri)
loginWithRedirect(options) loginWithRedirect(options)