diff --git a/src/bin/binModes/BinModeController.tsx b/src/bin/binModes/BinModeController.tsx index f4f825e..32dd6af 100644 --- a/src/bin/binModes/BinModeController.tsx +++ b/src/bin/binModes/BinModeController.tsx @@ -11,12 +11,25 @@ import { PromiseProgress, Stage, Step } from "common/PromiseProgress"; import { useBinAPI, useComponentAPI, useInteractionsAPI } from "providers"; import { ComponentSet } from "bin/conditioning/pickComponentSet"; import { cloneDeep } from "lodash"; +import { Controller } from "models/Controller"; export interface DeviceChangeData { + /** + * the id of the device changes are being made to + */ deviceId: number + /** + * the array of interactions to remove from the device + */ toRemove: Interaction[] + /** + * the new interactions to add to the device + */ toAdd?: pond.MultiInteractionSettings - componentSets: ComponentSet[] + /** + * the controllers that need to have there state updated + */ + controllers: Controller[] } interface Props { @@ -99,16 +112,16 @@ export default function BinModeController(props: Props) { promise: () => interactionAPI.addMultiInteractions(data.deviceId, newInteractions) }) } - data.componentSets.forEach(set => { - set.controllers.forEach(controller => { - let newStep: Step = { - title: "Updating " + controller.name(), - promise: () => componentAPI.update(data.deviceId, controller.settings) - } - stage3.steps.push(newStep) - }) + + data.controllers.forEach(controller => { + let newStep: Step = { + title: "Updating " + controller.name(), + promise: () => componentAPI.update(data.deviceId, controller.settings) + } + stage3.steps.push(newStep) }) - }) + }) + if(stage1.steps.length > 0){ stages.push(stage1) } @@ -195,7 +208,15 @@ export default function BinModeController(props: Props) { case pond.BinMode.BIN_MODE_COOLDOWN: return default: - return {}}/> + return { + buildStages(deviceData) + setShowProgress(true) + }}/> } } diff --git a/src/bin/binModes/DryingMode.tsx b/src/bin/binModes/DryingMode.tsx index 40d071a..d0f7bb0 100644 --- a/src/bin/binModes/DryingMode.tsx +++ b/src/bin/binModes/DryingMode.tsx @@ -19,6 +19,7 @@ import BinConditioningInteraction from "bin/BinConditioningInteraction"; import ConditionDisplay from "./conditionDisplay"; import React from "react"; import { DeviceChangeData } from "./BinModeController"; +import { GrainCable } from "models/GrainCable"; const useStyles = makeStyles((theme: Theme) => { @@ -66,6 +67,7 @@ export default function DryingMode(props: Props){ const [ambients, setAmbients] = useState([]) const [heaters, setHeaters] = useState([]) const [fans, setFans] = useState([]) + const [cables, setCables] = useState([]) const [currentStep, setCurrentStep] = useState(0) const [interactionLoading,setInteractionsLoading] = useState(false) @@ -87,7 +89,7 @@ export default function DryingMode(props: Props){ if (!deviceComponents.get(selectedDevice.id())) return; var plenums: Plenum[] = []; var ambients: Ambient[] = []; - //var grainCables: GrainCable[] = []; + var grainCables: GrainCable[] = []; var heaters: Controller[] = []; var fans: Controller[] = []; var sinkMap: Map = new Map() @@ -105,8 +107,10 @@ export default function DryingMode(props: Props){ ambients.push(Ambient.create(comp)); sourceMap.set(comp.locationString(), comp) } - // if (pref.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE) - // grainCables.push(GrainCable.create(comp)); + if (pref.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE){ + grainCables.push(GrainCable.create(comp)); + sourceMap.set(comp.locationString(), comp) + } if (pref.type === pond.BinComponent.BIN_COMPONENT_HEATER) { let heater = Controller.create(comp); heater.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_OFF; @@ -128,7 +132,7 @@ export default function DryingMode(props: Props){ setFans(fans); setSourceMap(sourceMap) setSinkMap(sinkMap) - + setCables(grainCables) //also load the interactions for the selected device so that we can find any conflicting ones setInteractionsLoading(true) interactionAPI.listInteractionsByDevice(selectedDevice.id()).then(resp => { @@ -315,11 +319,12 @@ export default function DryingMode(props: Props){ //this function just takes in the sets as they change and creates the list of conflicting interactions and the settings to create the new ones and returns them const createInteractions = (sets: ComponentSet[]) => { + console.log("create interactions") if(!selectedDevice) return undefined //if there is no device that was selected, do nothing let multiInteractionSettings: pond.MultiInteractionSettings = pond.MultiInteractionSettings.create() let conflictingInteractions: Interaction[] = [] let linkedComponents = deviceComponents.get(selectedDevice.id()); - //loop through the sets + //loop through the sets to find interactions to remove as well as set the new ones sets.forEach((set) => { //loop through the controllers set.controllers.forEach(controller => { @@ -335,6 +340,7 @@ export default function DryingMode(props: Props){ } return conflicting; }); + conflictingInteractions = conflictingInteractions.concat(c) // if the controller is a heater create heater interaction if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER){ @@ -351,6 +357,17 @@ export default function DryingMode(props: Props){ } }) }) + console.log(cables) + cables.forEach(cable => { + console.log(cable.name()) + let c = existingInteractions.filter(i => { + if (sameComponentID(cable.location(), i.settings.source) && !i.settings.sink && i.settings.notifications?.notify) { + return true + } + }); + conflictingInteractions = conflictingInteractions.concat(c) + }) + setComponentSets([...sets]) return { conflicting: conflictingInteractions, @@ -497,7 +514,7 @@ export default function DryingMode(props: Props){ {/* next - goes to the next step, hidden on the last step */} {currentStep !== steps.length - 1 && } {/* confirm - tells the parent to build the stages using the data, only visible on the last step */} - {currentStep === steps.length - 1 && interactionsToAdd && selectedDevice && } + {currentStep === steps.length - 1 && interactionsToAdd && selectedDevice && } ) } diff --git a/src/bin/binModes/StorageMode.tsx b/src/bin/binModes/StorageMode.tsx index 70c4f72..e9a0926 100644 --- a/src/bin/binModes/StorageMode.tsx +++ b/src/bin/binModes/StorageMode.tsx @@ -1,5 +1,5 @@ -import { DialogContent, DialogTitle, DialogActions } from "@mui/material"; -import { Component, Device, Interaction } from "models"; +import { DialogContent, DialogTitle, DialogActions, Button } from "@mui/material"; +import { Bin, Component, Device, Interaction } from "models"; import { Ambient } from "models/Ambient"; import { Controller } from "models/Controller"; import { GrainCable } from "models/GrainCable"; @@ -10,15 +10,16 @@ import React, { useEffect, useState } from "react"; import { DeviceChangeData } from "./BinModeController"; import { useInteractionsAPI } from "providers"; import { AxiosResponse } from "axios"; - -interface InteractionToRemove { - deviceID: number, -} +import { componentIDToString } from "pbHelpers/Component"; +import { ComponentSet } from "bin/conditioning/pickComponentSet"; +import moment from "moment"; +import { lowerCase } from "lodash"; interface Props { // the devices connected to the bin devices: Device[] deviceComponents: Map + bin: Bin binPrefs?: Map confirm: (deviceData: DeviceChangeData[]) => void } @@ -29,13 +30,17 @@ interface Props { * @returns */ export default function StorageMode(props: Props){ - const {devices, deviceComponents, binPrefs} = props + const {devices, deviceComponents, bin, binPrefs, confirm} = props const interactionsAPI = useInteractionsAPI() + const [data, setData] = useState([]) + const [loading, setLoading] = useState(false) + const [loadErr, setLoadErr] = useState(false) useEffect(()=>{ let interactionPromises: Promise[] = [] let deviceContext: {device: Device, plenums: Plenum[], ambients: Ambient[], cables: GrainCable[], controllers: Controller[]}[] = [] devices.forEach(device => { + if (!deviceComponents.get(device.id())) return; let plenums: Plenum[] = [] let ambients: Ambient[] = [] let cables: GrainCable[] = [] //since this mode is going to add notification interactions to cables they should be filtered as well @@ -70,29 +75,106 @@ export default function StorageMode(props: Props){ deviceContext.push({device, plenums, ambients, cables, controllers}) interactionPromises.push(interactionsAPI.listInteractionsByDevice(device.id())) }) - + setLoading(true) + let deviceChangeData: DeviceChangeData[] = [] Promise.all(interactionPromises).then(resp => { //the index of this should match the device index in the devices array resp.forEach((interactions,i) => { + let toRemove: Interaction[] = [] + let ctx = deviceContext[i] + let sensorsAddr = ctx.plenums.concat(ctx.ambients).map(sensor => sensor.locationString()) + let controllerAddr = ctx.controllers.map(cont => cont.locationString()) + let cableAddr = ctx.cables.map(cab => cab.locationString()) + let cablesWithInteraction = new Set() + interactions.forEach(interaction => { - + //first check if the interaction has a sink in the devices fans or heaters and is being controlled by a plenum or ambient + //this should make it so that it only removes conditioning interactions and not all interactions on the device + let source = interaction.settings.source + let sink = interaction.settings.sink + //this checks if the interaction is a 'conditioning' interaction that needs to be removed + if(source && sink){ + if(controllerAddr.includes(componentIDToString(sink)) && sensorsAddr.includes(componentIDToString(source))){ + toRemove.push(interaction) + } + } + //need to make sure that it is a notification only interaction and not a controlling one + //it is possible to control things with cables and we want to leave those alone and not consider them notification interactions + //the reason we check that there is no sink is because even control interactions can still send notifications + //we want interactions that effectively ONLY send notifications + if (source && !sink && cableAddr.includes(componentIDToString(source))) { + //check if notifications is true + if(interaction.settings.notifications?.notify){ + cablesWithInteraction.add(componentIDToString(source)) + } + } }) + let cablesNeedingNotification = ctx.cables.filter( + cable => !cablesWithInteraction.has(cable.locationString()) + ) + + let dd: DeviceChangeData = { + deviceId: ctx.device.id(), + toRemove: toRemove, + controllers: ctx.controllers, + + } + + if(cablesNeedingNotification.length > 0){ + let toAdd: pond.MultiInteractionSettings = pond.MultiInteractionSettings.create() + cablesNeedingNotification.forEach(cable => { + //create the new interaction here + let notification: pond.InteractionSettings = pond.InteractionSettings.create({ + source: cable.location(), + schedule: pond.InteractionSchedule.create({ + timeOfDayStart: "00:00", + timeOfDayEnd: "24:00", + timezone: moment.tz.guess(), + weekdays: moment.weekdays().map(d => lowerCase(d)) + }), + conditions: [pond.InteractionCondition.create({ + measurementType: quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, + comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN, + value: bin.settings.highTemp + })], + result: pond.InteractionResult.create({ + type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_REPORT + }), + notifications: pond.InteractionNotifications.create({ + notify: true + }), + }) + + //push it to toAdd.interactions + toAdd.interactions.push(notification) + + }) + //add toAdd to dd + dd.toAdd = toAdd + } + deviceChangeData.push(dd) }) + setData(deviceChangeData) + }).catch(err => { + //TODO: display a snackBar message saying there was an issue loading the data for one or more devices + setData([]) + setLoadErr(true) + }).finally(() => { + setLoading(false) }) - },[devices, binPrefs, deviceComponents]) - //load the interactions for all of the bins connected devices - - // - return ( Storage Mode {/* could have the explanation of what changing to storage mode does here */} - + + + ) } \ No newline at end of file diff --git a/src/models/Ambient.ts b/src/models/Ambient.ts index d2c7577..4fde1ea 100644 --- a/src/models/Ambient.ts +++ b/src/models/Ambient.ts @@ -108,18 +108,25 @@ export class Ambient { return quack.ComponentID.fromObject({ type: this.settings.type, addressType: this.settings.addressType, - address: this.settings.address + address: this.settings.address, + expansionLine: this.settings.expansionLine, + muxLine: this.settings.muxLine }); } public locationString(): string { - return ( - or(this.settings.type, 0).toString() + + let compositeLocation = or(this.settings.type, 0).toString() + "-" + or(this.settings.addressType, 0).toString() + "-" + or(this.settings.address, 0).toString() - ); + if(this.settings.expansionLine){ + compositeLocation = compositeLocation + "-" + this.settings.expansionLine + } + if(this.settings.muxLine){ + compositeLocation = compositeLocation + ":" + this.settings.muxLine + } + return compositeLocation; } public type(): quack.ComponentType { diff --git a/src/models/Controller.ts b/src/models/Controller.ts index 4e7170c..c29ba52 100644 --- a/src/models/Controller.ts +++ b/src/models/Controller.ts @@ -82,18 +82,25 @@ export class Controller { return quack.ComponentID.fromObject({ type: this.settings.type, addressType: this.settings.addressType, - address: this.settings.address + address: this.settings.address, + expansionLine: this.settings.expansionLine, + muxLine: this.settings.muxLine }); } public locationString(): string { - return ( - or(this.settings.type, 0).toString() + + let compositeLocation = or(this.settings.type, 0).toString() + "-" + or(this.settings.addressType, 0).toString() + "-" + or(this.settings.address, 0).toString() - ); + if(this.settings.expansionLine){ + compositeLocation = compositeLocation + "-" + this.settings.expansionLine + } + if(this.settings.muxLine){ + compositeLocation = compositeLocation + ":" + this.settings.muxLine + } + return compositeLocation; } public type(): quack.ComponentType { diff --git a/src/models/GrainCable.ts b/src/models/GrainCable.ts index 4de1ae4..e3e988b 100644 --- a/src/models/GrainCable.ts +++ b/src/models/GrainCable.ts @@ -230,18 +230,25 @@ export class GrainCable { return quack.ComponentID.fromObject({ type: this.settings.type, addressType: this.settings.addressType, - address: this.settings.address + address: this.settings.address, + expansionLine: this.settings.expansionLine, + muxLine: this.settings.muxLine }); } public locationString(): string { - return ( - or(this.settings.type, 0).toString() + + let compositeLocation = or(this.settings.type, 0).toString() + "-" + or(this.settings.addressType, 0).toString() + "-" + or(this.settings.address, 0).toString() - ); + if(this.settings.expansionLine){ + compositeLocation = compositeLocation + "-" + this.settings.expansionLine + } + if(this.settings.muxLine){ + compositeLocation = compositeLocation + ":" + this.settings.muxLine + } + return compositeLocation; } public type(): quack.ComponentType { diff --git a/src/models/Plenum.ts b/src/models/Plenum.ts index 5ec161f..15f99bc 100644 --- a/src/models/Plenum.ts +++ b/src/models/Plenum.ts @@ -115,13 +115,18 @@ export class Plenum { } public locationString(): string { - return ( - or(this.settings.type, 0).toString() + + let compositeLocation = or(this.settings.type, 0).toString() + "-" + or(this.settings.addressType, 0).toString() + "-" + or(this.settings.address, 0).toString() - ); + if(this.settings.expansionLine){ + compositeLocation = compositeLocation + "-" + this.settings.expansionLine + } + if(this.settings.muxLine){ + compositeLocation = compositeLocation + ":" + this.settings.muxLine + } + return compositeLocation; } public type(): quack.ComponentType {