built out the functions to create the interactions

This commit is contained in:
csawatzky 2025-11-14 16:21:18 -06:00
parent 393a2f77dc
commit e70c971a88
4 changed files with 291 additions and 3 deletions

View file

@ -485,7 +485,6 @@ 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 => {

View file

@ -2194,6 +2194,7 @@ export default function BinVisualizer(props: Props) {
<ModeChangeDialog <ModeChangeDialog
binKey={bin.key()} binKey={bin.key()}
binMode={newBinMode} binMode={newBinMode}
grain={bin.settings.inventory?.grainType}
devices={devices} devices={devices}
open={openModeChange} open={openModeChange}
binCables={cables} binCables={cables}

View file

@ -15,6 +15,12 @@ import { useEffect, useState } from "react";
import ConditioningSelector from "./conditioningSelector"; import ConditioningSelector from "./conditioningSelector";
import CableTopNodeSummary from "bin/CableTopNodeSummary"; import CableTopNodeSummary from "bin/CableTopNodeSummary";
import { ComponentSet } from "./pickComponentSet"; import { ComponentSet } from "./pickComponentSet";
import { sameComponentID } from "pbHelpers/Component";
import moment from "moment";
import { lowerCase } from "lodash";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { getTemperatureUnit } from "utils";
import { GetGrainExtensionMap } from "grain";
interface Props { interface Props {
open: boolean open: boolean
@ -44,7 +50,8 @@ export default function ModeChangeDialog(props: Props){
binKey, binKey,
preferences, preferences,
binCables, binCables,
compDevMap compDevMap,
grain
} = props } = props
const [{as}] = useGlobalState() const [{as}] = useGlobalState()
const [componentSets, setComponentSets] = useState<ComponentSet[]>([]) const [componentSets, setComponentSets] = useState<ComponentSet[]>([])
@ -62,6 +69,9 @@ export default function ModeChangeDialog(props: Props){
const [ambients, setAmbients] = useState<Ambient[]>([]) const [ambients, setAmbients] = useState<Ambient[]>([])
const [heaters, setHeaters] = useState<Controller[]>([]) const [heaters, setHeaters] = useState<Controller[]>([])
const [fans, setFans] = useState<Controller[]>([]) const [fans, setFans] = useState<Controller[]>([])
const [selectedPreset, setSelectedPreset] = useState<DevicePreset | undefined>(undefined);
const grainExtensionMap = GetGrainExtensionMap();
//get the interactions and components for the device when it is selected from the dropdown //get the interactions and components for the device when it is selected from the dropdown
useEffect(()=>{ useEffect(()=>{
if (!binKey) return; if (!binKey) return;
@ -85,6 +95,7 @@ export default function ModeChangeDialog(props: Props){
newDComponents.push(c); newDComponents.push(c);
dComponents.set(deviceNumber.toString(), newDComponents); dComponents.set(deviceNumber.toString(), newDComponents);
}); });
console.log(dComponents)
setDeviceComponents(dComponents); setDeviceComponents(dComponents);
setExistingInteractions(interactionResp); setExistingInteractions(interactionResp);
}) })
@ -141,9 +152,266 @@ if (!selectedDevice) return;
setOptions(o); setOptions(o);
}, [devices, setOptions]); }, [devices, setOptions]);
const buildHeaterInteraction = (
sensor: Plenum | Ambient,
heater: Controller
): pond.InteractionSettings => {
let interaction = pond.InteractionSettings.create({
source: sensor.location(),
sink: heater.location(),
schedule: pond.InteractionSchedule.create({
timeOfDayStart: "00:00",
timeOfDayEnd: "24:00",
timezone: moment.tz.guess(),
weekdays: moment.weekdays().map(d => lowerCase(d))
}),
notifications: pond.InteractionNotifications.create({
reports: true
}),
result: pond.InteractionResult.create({
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE,
value: 1
})
});
let temp = 0;
let hum = 0;
//if a preset was selected use its temp/hum values
if (selectedPreset !== undefined) {
temp = selectedPreset.temp();
hum = selectedPreset.hum();
} else {
//otherwise use default values
switch(binMode){
case pond.BinMode.BIN_MODE_COOLDOWN:
temp = 10
hum = 45
break;
case pond.BinMode.BIN_MODE_DRYING:
if(grain){
//get the values using the grain type
let ext = grainExtensionMap.get(grain)
if (ext) {
temp = ext.setTempC
hum = ext.targetMC
}
}else{//otherwise use the default drying interaction
temp = 40
hum = 20
}
}
}
let conditions = [];
let humidityCondition = pond.InteractionCondition.create({
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
value: Math.round(
describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
sensor.settings.type,
sensor.settings.subtype
).toStored(hum)
)
});
conditions.push(humidityCondition);
//since the measurement describers function to stored does a conversion into celsius if the users pref is fahrenheit for temperature we need to convert the preset value into fahrenheit
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.round((temp * (9 / 5) + 32) * 100) / 100;
}
let tempVal = describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
sensor.settings.type,
sensor.settings.subtype
).toStored(temp);
let tempCondition = pond.InteractionCondition.create({
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
value: Math.round(tempVal) //and then round the converted value since interaction conditions wont take floats
});
conditions.push(tempCondition);
interaction.conditions = conditions;
return interaction;
};
const buildFanInteraction = (
sensor: Plenum | Ambient,
fan: Controller,
tempComparison: "greater" | "less",
humidityComparison: "greater" | "less"
) => {
let interaction = pond.InteractionSettings.create({
source: sensor.location(),
sink: fan.location(),
schedule: pond.InteractionSchedule.create({
timeOfDayStart: "00:00",
timeOfDayEnd: "24:00",
timezone: moment.tz.guess(),
weekdays: moment.weekdays().map(d => lowerCase(d))
}),
notifications: pond.InteractionNotifications.create({
reports: true
}),
result: pond.InteractionResult.create({
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE,
value: 1
})
});
let tempPreset = 0;
let humidityPreset = 0;
//if a custom preset was selected use it
if (selectedPreset !== undefined) {
tempPreset = selectedPreset.temp();
humidityPreset = selectedPreset.hum();
} else {
//otherwise use one of the defaults
switch (binMode) {
case pond.BinMode.BIN_MODE_COOLDOWN:
tempPreset = 15;
humidityPreset = 60;
break;
case pond.BinMode.BIN_MODE_DRYING:
if(grain){
//get the values using the grain type
let ext = grainExtensionMap.get(grain)
if (ext) {
tempPreset = ext.setTempC
humidityPreset = ext.targetMC
}
}else{//otherwise use the default drying interaction
tempPreset = 40
humidityPreset = 20
}
break;
case pond.BinMode.BIN_MODE_HYDRATING:
tempPreset = 25;
humidityPreset = 60;
break;
}
}
let conditions = [];
let fanConditionOne = pond.InteractionCondition.create({
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
comparison:
humidityComparison === "greater"
? quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN
: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
value: describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
sensor.settings.type,
sensor.settings.subtype
).toStored(humidityPreset)
});
conditions.push(fanConditionOne);
//since the measurement describers function toStored does a conversion into celsius for temperature if the users pref is fahrenheit we need to convert the preset value into fahrenheit
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
tempPreset = Math.round((tempPreset * (9 / 5) + 32) * 100) / 100;
}
let tempVal = describeMeasurement(
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
sensor.settings.type,
sensor.settings.subtype
).toStored(tempPreset);
let fanConditionTwo = pond.InteractionCondition.create({
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
comparison:
tempComparison === "greater"
? quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN
: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
value: Math.round(tempVal) //and then round the converted value since the interaction does not take decimals
});
conditions.push(fanConditionTwo);
interaction.conditions = conditions;
return interaction;
};
//function to loop through the interaction sets building the settings //function to loop through the interaction sets building the settings
const createInteractions = () => { const createInteractions = () => {
if(!selectedDevice) return //if there is no device that was selected, do nothing
let multiInteractionSettings: pond.MultiInteractionSettings = pond.MultiInteractionSettings.create()
let linkedComponents = deviceComponents.get(selectedDevice.id().toString());
//loop through the sets
componentSets.forEach(async (set) => {
//loop through the controllers
set.controllers.forEach(controller => {
//filter the conflicting interactions for that controller
let conflictingInteractions = existingInteractions.filter(i => {
let conflicting = false;
if (linkedComponents) {
linkedComponents.forEach(comp => {
if (sameComponentID(comp.location(), i.settings.sink)) {
conflicting = true;
}
});
}
return conflicting;
});
//create an array of promises to delete them
let deleteConflictingInteractions = conflictingInteractions.map(i => {
return interactionAPI.removeInteraction(selectedDevice.id(), i.key(), as);
});
//execute the promises
Promise.all([...deleteConflictingInteractions]).finally(() => {
//and one they are done make the new interaction to be added for the sensor and controller pair
switch(binMode){
case pond.BinMode.BIN_MODE_COOLDOWN:
// if the controller is a heater create heater interaction
if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER){
multiInteractionSettings.interactions.push(buildHeaterInteraction(set.sensor, controller))
}else if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN ||
controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){
// if the controller is a fan create a fan interaction
multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "less", "less"))
}
break;
case pond.BinMode.BIN_MODE_DRYING:
// if the controller is a heater create heater interaction
if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER){
multiInteractionSettings.interactions.push(buildHeaterInteraction(set.sensor, controller))
}else if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN ||
controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){// if the controller is a fan
//if it is a combination set using a heater as well just turn the fan on
if(set.combo){
controller.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_ON
}else{
//otherwise make a fan interaction
multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "greater", "less"))
}
}
break;
case pond.BinMode.BIN_MODE_HYDRATING:
//check to make sure the controller is a fan, you cant hydrate with a heater, if the controller is a heater then it will not add an interaction
if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN ||
controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){
//create fan interaction
multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "less", "greater"))
}
break;
}
console.log(multiInteractionSettings)
})
})
})
//add all of the interactions that were created (multiInteraction)
interactionAPI.addMultiInteractions(selectedDevice.id(), multiInteractionSettings).then(resp => {
//then once the interactions are added update the controllers to the proper states
}).catch(err => {
//there was a problem adding the interactions
})
} }
const deviceSelector = () => { const deviceSelector = () => {
@ -246,6 +514,7 @@ if (!selectedDevice) return;
updateSets={(sets) => { updateSets={(sets) => {
//update the component sets that will be used for the interactions //update the component sets that will be used for the interactions
console.log(sets) console.log(sets)
setComponentSets(sets)
}}/> }}/>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
@ -255,8 +524,8 @@ if (!selectedDevice) return;
Cancel Cancel
</Button> </Button>
<Button variant="contained" color="primary" onClick={()=>{ <Button variant="contained" color="primary" onClick={()=>{
//remove comflicting interactions
//add new interactions to the component sets //add new interactions to the component sets
createInteractions()
close(true) close(true)
}}> }}>
Submit Submit

View file

@ -4,11 +4,13 @@ import { cloneDeep } from "lodash";
import { Ambient } from "models/Ambient"; import { Ambient } from "models/Ambient";
import { Controller } from "models/Controller"; import { Controller } from "models/Controller";
import { Plenum } from "models/Plenum"; import { Plenum } from "models/Plenum";
import { quack } from "protobuf-ts/quack";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
export interface ComponentSet { export interface ComponentSet {
sensor: Plenum | Ambient, sensor: Plenum | Ambient,
controllers: Controller[] controllers: Controller[]
combo?: boolean //if the set contains both a heater and a fan
} }
interface Props { interface Props {
@ -34,6 +36,21 @@ export default function PickComponentSet(props: Props) {
return index return index
} }
const comboCheck = (set: ComponentSet) => {
let heaterFound = false
let fanFound = false
set.controllers.forEach(controller => {
if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER){
heaterFound = true
}
if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN ||
controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){
fanFound = true
}
})
return heaterFound && fanFound
}
return ( return (
<Box> <Box>
<FormControlLabel <FormControlLabel
@ -79,6 +96,7 @@ export default function PickComponentSet(props: Props) {
set.controllers.splice(index, 1) set.controllers.splice(index, 1)
} }
} }
set.combo = comboCheck(set)
setCurrentSet(set) setCurrentSet(set)
updateSet(sensor.key(), set) updateSet(sensor.key(), set)
}} }}
@ -107,6 +125,7 @@ export default function PickComponentSet(props: Props) {
set.controllers.splice(index, 1) set.controllers.splice(index, 1)
} }
} }
set.combo = comboCheck(set)
setCurrentSet(set) setCurrentSet(set)
updateSet(sensor.key(), set) updateSet(sensor.key(), set)
}} }}