Improving multiple, nested if-then-else statements

Hi,

I recently wrote, as a non-proffessional programmer, a function to manage the charging of my solar battery based on rules and time periodes defined in a Google Sheet (for simple, external, manual control).

The code works as intended, but I feel that this needs improvement for better readability and I hope for some ideas on how to improve it.

I do know about the "switch" statement, but I do not think it would make it better, given the nested layers.

Also I am aware, that I still use the "var" declaration, which is not ideal. It's replacement by "let" is work in progress.

I included the full code below but the basic scheme is this:

if (condition A) {

    if (condition AA) {

        if (condition AAA) {

            if (condition AAAA) { do AAAA }

        } else { 
            if (condition AAAB) { do AAAB            }
        }       

        if (condition AAB) { 
            if (condition AABA) { do AABA            }

        } else { 
            if (condition AABB)  { do AABB            }        
        }

    } else { 
        if (condition AB) {
            if (condition ABA){ do ABA }

        } else {
            if (condition ABB) { do ABB}

        } 
    }
} 

Full code including all comments:

//This routine checks every "Threshold" minutes
    //if Google Sheet Status = "NotStop", do nothing
    //if we are in off peak periode and if the State of Charge (SoC) is above "MinSoc"
    //if below MinSoc during off peak periode, discharge is stopped
    //if below CutOffSoC, the battery needs to stop discharging for battery protection
    //if a car is charging during LowCost period, which battery stops to save energy for later in the morning when tariff is more expensive
    //if the battery needs to be replenished from the grid (typically during winter)
    //if on high PV forecast the battery shall only start charging around noon (prevent grid overloading)


const CutOffSoC = 6; //SoC in % when battery is considered empty (inverter has turned of battery) 
const MinBatAvgPower = 1000; //Minimal power in Watt required to stop the battery charging on high PV forcast
const MinSoC = parseInt(global.get("GoogleInstructions")[1][1]); //to get MinSoc from Google Sheet
const Threshold = 5; //return only every 5min
const RemoteControl = global.get("GoogleInstructions")[0][0]; //to get operating instructions from Google Sheet
const ChargePower = parseInt(global.get("GoogleInstructions")[0][1]); //to get charging power from Google Sheet;
const OffPeakStart = new Date(global.get("GoogleInstructions")[2][1]); //ex. 21:00
const OffPeakEnd = new Date(global.get("GoogleInstructions")[3][1]); // this should be in line with the end time of EV charging to prevent, that battery will be depleted through EV charging
const LowCostStart = new Date(global.get("GoogleInstructions")[4][1]); // Start of periode when actual tariff is lower than normal off peak tariff
const LowCostEnd = new Date(global.get("GoogleInstructions")[5][1]); // End of periode when actual tariff is lower than normal off peak tariff
const ReplenishStartTime = new Date(global.get("GoogleInstructions")[6][1]); //ex. 03:00 because of low usage of grid
const ReplenishEndTime = new Date(global.get("GoogleInstructions")[7][1]); //ex. 05:00 because grid load starts here
const BatteryStopCharging = new Date(global.get("GoogleInstructions")[8][1]); //on high PV forecast stops battery charging until noon
const BatteryStartCharging = new Date(global.get("GoogleInstructions")[9][1]);//on high PV forecast starts battery charging at around noon
const AvgBatPower = flow.get("BatPowerAvg")[1]; //The average bat power over the last x measurements
const EVChargingState = new Object(global.get("EVCharging")); //current charging power of all charging points in an array
const TotalChargingPower = Object.values(EVChargingState).reduce((a, b) => a + b, 0); //Sum of charging power of all charging points


var ActionArray = [];
var SoC = parseFloat(msg.payload["SOC_real"]);
var ActualCapacity = parseInt(msg.payload["total_capacity"])/1000/3600 * parseFloat(msg.payload["state_of_health"])/100 * SoC/100; //computes the actual capacity based on max capacity, SoH and SoC in kWh
var BatPower = parseFloat(msg.payload["stat_batt_power"]);
var CurrentTime = new Date();
var TimerSec = parseInt(new Date().getSeconds());
msg.payload = "";



//Execute only at full minute and only if in Google Sheet OpMode is not in "NotStop" or "Laden" state
if ((6 > TimerSec || TimerSec > 58) && RemoteControl != "NotStop") {


    //Execute only during offpeak hours to not interfear with PV battery charging
    if (CurrentTime >= OffPeakStart && CurrentTime < OffPeakEnd) {


        //if during replenish hours, SoC is below minimal value and battery is not already set to charge
        //this basically should not happen during all the situations below, but the battery might already be on low SoC
        //when any of the below phases start
        if (SoC < MinSoC && ReplenishStartTime <= CurrentTime && CurrentTime < ReplenishEndTime) {
            if (RemoteControl != "Laden") { //write action only if status is not already set to "Laden"
                flow.set("BatCapacityBeforeCharging", ActualCapacity); //store current battery capacity to compute charged energy later on
                msg.payload = "Laden"; //Charge battery
            }

        } else { //if we are out of replenish hours or SoC is higher than MinSoC
            if (RemoteControl == "Laden") { //only update status, if battery has been set to charge before
                //we disable the battery here as it will be enabled when peak hours start by "Check Soc" function
                var ChargedFromGrid = (ActualCapacity - parseInt(flow.get("BatCapacityBeforeCharging"))); //compute energy charged from grid in kWh
                global.set("BatChargedFromGrid", ChargedFromGrid); 
                msg.payload = "KeineRegelung" ;
            }
        }       

        //if EV's are charging during low cost hours and SoC is below MinSoC, turn off battery to save energy for 
        //later in the morning when tariffs raise again
        if ((CurrentTime > LowCostStart && CurrentTime < LowCostEnd) && TotalChargingPower > 0 && SoC < MinSoC) { 
            if (BatPower < 0) { //prevent update when battery is already disabled (0 Amp) or 
                                //charging from grid (positive Amp value)

                msg.payload = "KeineRegelung";

            }

        } else { //if we are out of low cost period or SoC is higher than CutOffSoC or no car is charging

            //only update status, if battery has been set to idle before and SoC is below CutOff SoC (lowest possible SoC)
            if (SoC > CutOffSoC && RemoteControl != "Laden" && RemoteControl != "Entladen" && RemoteControl != "Automatik") { 

                msg.payload = "Automatik";

            }        
        }


    } else { 

        //if we are in a high PV forcast period and battery level is above minimum, we want to delay battery charging until around noon
        if (SoC > MinSoC && CurrentTime > BatteryStopCharging && CurrentTime < BatteryStartCharging && AvgBatPower > MinBatAvgPower) {
            if (RemoteControl != "KeineRegelung"){ //prevent constant writing of Mode, cannot be included in
                                                   //in the if statement above (toggling)

                msg.payload = "KeineRegelung";
                
            }

        } else {

            //if we are out of offpeak period, battery can be charged and provide no other manual command in Google Sheet(RemoteControl). 
            //This is a copy from above, just to make sure that the battery operation has been released when leaving offpeak period
            if (SoC > CutOffSoC && RemoteControl != "Laden" && RemoteControl != "Entladen" && RemoteControl != "Automatik") { //only update status, if battery has been set to idle before

                msg.payload = "Automatik";

            }

        } 
    }
} 

if (msg.payload != ""){
    return msg;
}

Many thanks for your improvement suggestions.

Best regards

Heinz

How about this with a bit less nesting...

if (!conditionA) return;

if (conditionAA) {
    if (conditionAAA) {
        if (conditionAAAA) {
            doAAAA();
            return;
        }
    } else if (conditionAAAB) {
        doAAAB();
    }

    if (conditionAAB) {
        if (conditionAABA) {
            doAABA();
        }
    } else if (conditionAABB) {
        doAABB();
    }

} else if (conditionAB) {
    if (conditionABA) {
        doABA();
    }
} else if (conditionABB) {
    doABB();
}

To make it more manageable, you could do...

if (!conditionA) return;

const actions = [
    { when: () => conditionAA && conditionAAA && conditionAAAA, do: doAAAA },
    { when: () => conditionAA && !conditionAAA && conditionAAAB, do: doAAAB },
    { when: () => conditionAA && conditionAAB && conditionAABA, do: doAABA },
    { when: () => conditionAA && !conditionAAB && conditionAABB, do: doAABB },
    { when: () => !conditionAA && conditionAB && conditionABA, do: doABA },
    { when: () => !conditionAA && !conditionAB && conditionABB, do: doABB }
];

for (const action of actions) {
    if (action.when()) {
        action.do();
        break;
    }
}

function doAAAA () {

}


function doAAAB () {

}

// etc


here is a more realworld example of the above pattern

const personDetected = msg.payload // or wherever this comes from
const isDarkOutside = global.get('isDarkOutside') // or wherever this comes from
const isLightOutside = global.get('isLightOutside') // or wherever this comes from
const isLightOn = global.get('isLightOn') // or wherever this comes from

const actions = [
    {
        when: () => personDetected && isDarkOutside && !isLightOn,
        do: turnOn
    },
    {
        when: () => !personDetected && isLightOn && isDarkOutside,
        do: turnOff
    },
    {
        when: () => !personDetected && isLightOn && isLightOutside,
        do: turnOff
    },
    {
        when: () => personDetected && isLightOutside && isLightOn,
        do: turnOff
    }
];

for (const action of actions) {
    if (action.when()) {
        action.do();
        break;
    }
}


function turnOn() {
   msg.payload = true
   node.send(msg)
}

function turnOff() {
   msg.payload = false
   node.send(msg)
}

4 Likes

And this is what your code looks like in that format (100% untested)

It is certainly more manageable (just add an action for new conditions)

// Constants and variables
const CutOffSoC = 6;
const MinBatAvgPower = 1000;
const MinSoC = parseInt(global.get("GoogleInstructions")[1][1]);
const Threshold = 5;
const RemoteControl = global.get("GoogleInstructions")[0][0];
const ChargePower = parseInt(global.get("GoogleInstructions")[0][1]);
const OffPeakStart = new Date(global.get("GoogleInstructions")[2][1]);
const OffPeakEnd = new Date(global.get("GoogleInstructions")[3][1]);
const LowCostStart = new Date(global.get("GoogleInstructions")[4][1]);
const LowCostEnd = new Date(global.get("GoogleInstructions")[5][1]);
const ReplenishStartTime = new Date(global.get("GoogleInstructions")[6][1]);
const ReplenishEndTime = new Date(global.get("GoogleInstructions")[7][1]);
const BatteryStopCharging = new Date(global.get("GoogleInstructions")[8][1]);
const BatteryStartCharging = new Date(global.get("GoogleInstructions")[9][1]);
const AvgBatPower = flow.get("BatPowerAvg")[1];
const EVChargingState = new Object(global.get("EVCharging"));
const TotalChargingPower = Object.values(EVChargingState).reduce((a, b) => a + b, 0);

let ActionArray = [];
let SoC = parseFloat(msg.payload["SOC_real"]);
let ActualCapacity = parseInt(msg.payload["total_capacity"]) / 1000 / 3600 * parseFloat(msg.payload["state_of_health"]) / 100 * SoC / 100;
let BatPower = parseFloat(msg.payload["stat_batt_power"]);
let CurrentTime = new Date();
let TimerSec = parseInt(new Date().getSeconds());
msg.payload = "";

const isFullMinute = (TimerSec <= 6 || TimerSec >= 59);
const isOffPeak = (CurrentTime >= OffPeakStart && CurrentTime < OffPeakEnd);
const isLowCost = (CurrentTime > LowCostStart && CurrentTime < LowCostEnd);
const isReplenishWindow = (ReplenishStartTime <= CurrentTime && CurrentTime < ReplenishEndTime);
const isPVHoldWindow = (CurrentTime > BatteryStopCharging && CurrentTime < BatteryStartCharging);

if (isFullMinute && RemoteControl != "NotStop") {
  return null // no action
}

const actions = [
    {
        when: () => SoC < MinSoC && isReplenishWindow && RemoteControl != "Laden",
        do: () => {
            flow.set("BatCapacityBeforeCharging", ActualCapacity);
            msg.payload = "Laden";
        }
    },
    {
        when: () => !(SoC < MinSoC && isReplenishWindow) && RemoteControl == "Laden",
        do: () => {
            let ChargedFromGrid = (ActualCapacity - parseInt(flow.get("BatCapacityBeforeCharging")));
            global.set("BatChargedFromGrid", ChargedFromGrid);
            msg.payload = "KeineRegelung";
        }
    },
    {
        when: () => isLowCost && TotalChargingPower > 0 && SoC < MinSoC && BatPower < 0,
        do: () => {
            msg.payload = "KeineRegelung";
        }
    },
    {
        when: () => SoC > CutOffSoC && !["Laden", "Entladen", "Automatik"].includes(RemoteControl),
        do: () => {
            msg.payload = "Automatik";
        }
    },
    {
        when: () => !isOffPeak && SoC > MinSoC && isPVHoldWindow && AvgBatPower > MinBatAvgPower && RemoteControl != "KeineRegelung",
        do: () => {
            msg.payload = "KeineRegelung";
        }
    },
    {
        when: () => !isOffPeak && SoC > CutOffSoC && !["Laden", "Entladen", "Automatik"].includes(RemoteControl),
        do: () => {
            msg.payload = "Automatik";
        }
    }
];

for (const action of actions) {
    if (action.when()) {
        action.do();
        break;
    }
}

if (msg.payload !== "") {
    return msg;
}
3 Likes

Many thanks Dave,

Unfortunately the nesting is necessary to maintain the correct functionality.

Heinz

Many thanks Steve.

Wow! You did all the work for me!

I am absolutely not used to this kind of notation, but it looks for sure better and simpler! I guess I need to dive into it a bit to learn it.

Few questions:

  • So, as I understand it so far, you basically pre-define a few const with all the relevant conditions (true/false) and then use the for the actions, which themselves are defined in an array of conditions/actions - correct?

  • How does this part work (never seen):
    when: () => (condition),
    do: () => { do something; }

    Especially the "() =>" part, I do not understand.

Many thanks

Heinz

Yep! On the money!

() => are Arrow Functions - they are an easier way of writing function () { /* code */ }.

Here is the same in regular function form

const actionsV2 = [
    {
        when: function () {
            return SoC < MinSoC && isReplenishWindow && RemoteControl != "Laden"
        },
        do: function () {
            flow.set("BatCapacityBeforeCharging", ActualCapacity);
            msg.payload = "Laden";
        }
    },
    ...

chrome_KomvDjTV0Q

Ok, understood!

You got me into a whole new experience with this! :slight_smile:

I will try to adapt my code in a test node first, as this one is one of the more important of my control system.

You probably saw, that I also had "else" situations. How would I implement those?

I may have additional questions while implementing and hope I can bother you again.

Many thanks and best regards

Heinz

They are (should be) catered for in the logic when functions because when a logic test is true, the for loop breaks out and the code moves to the last part where it sends the msg.

Imagine the 3rd action.when() resolves to true - thats the same as:

if (logic 1) {
  // nope
} else if (logic 2) {
  // nope
} else if (logic 3) {
  // yey - lets do this action
} else if (logic 4) {
  // nope
} ... etc ...

I think I might be right in saying that this kind of thing is really a finite state machine?

If so, you could probably do it all without a function node using an FSM node.

Oooohhh - Finite State Machine (FSM) - the love of my life.
I think you might be right - the code lends itself to being coded as an FSM (either using pure JavaScript or the Node-RED FSM node).

1 Like

Many thanks Gentlemen, for the additional FSM idea. Now I am in the dilemma of having a choice... :slight_smile:

I have no clue on FSM and was looking a bit into this description.

Looking into how to define such FSM, it appears to me, that this is very similar to what Steve proposed above with the array of actions - isn't it?

Comparing this FSM definition:

{
  "state": {
    "status": "IDLE"
  },
  "transitions": {
    "IDLE": {
      "run": "RUNNING"
    },
    "RUNNING": {
      "stop": "IDLE",
      "set": "RUNNING"
    }
  }
}

With Steve's array of actions:

const actions = [
    {
        when: () => SoC < MinSoC && isReplenishWindow && RemoteControl != "Laden",
        do: () => {
            flow.set("BatCapacityBeforeCharging", ActualCapacity);
            msg.payload = "Laden";
        }
    },
    {
        when: () => !(SoC < MinSoC && isReplenishWindow) && RemoteControl == "Laden",
        do: () => {
            let ChargedFromGrid = (ActualCapacity - parseInt(flow.get("BatCapacityBeforeCharging")));
            global.set("BatChargedFromGrid", ChargedFromGrid);
            msg.payload = "KeineRegelung";
        }
    },
    {
        when: () => isLowCost && TotalChargingPower > 0 && SoC < MinSoC && BatPower < 0,
        do: () => {
            msg.payload = "KeineRegelung";
        }
    },
    {
        when: () => SoC > CutOffSoC && !["Laden", "Entladen", "Automatik"].includes(RemoteControl),
        do: () => {
            msg.payload = "Automatik";
        }
    },
    {
        when: () => !isOffPeak && SoC > MinSoC && isPVHoldWindow && AvgBatPower > MinBatAvgPower && RemoteControl != "KeineRegelung",
        do: () => {
            msg.payload = "KeineRegelung";
        }
    },
    {
        when: () => !isOffPeak && SoC > CutOffSoC && !["Laden", "Entladen", "Automatik"].includes(RemoteControl),
        do: () => {
            msg.payload = "Automatik";
        }
    }
];

I am willing to look into both. But Steve's proposal seems to be better readable. Am I wrong?

Best regards

Heinz

A great learning experience for us @Steve-Mcl.
Thanks for the comprehensive examples.

...time for me to re-write some of my code....

1 Like

:grinning_face: ....and I thought, I am the only one! :sweat_smile:

Happy weekend to you all.

We aim to please! :grin:

I recon it took me about 5 years to make sense of it! :scream:

Yes, Steve's approach is a manual approach to FSM. And actually probably a good example to use in explaining what FSM is.

Gentlemen and @Steve-Mcl ,

During last weekend I studied Steve's proposal and got to understand it fully. What a genious way of writing all those different states!

Especially this

!["Laden", "Entladen", "Automatik"].includes(RemoteControl)

I have never seen before! Very cool.

Also it would never have occured to me to define logical expressions as a constant:

const isOffPeak = (CurrentTime >= OffPeakStart && CurrentTime < OffPeakEnd);

This is VERY helpful.

All in all, I am currently running Steve's code in parallel with my code and comparing the results on my live node-red implementation. You're code, with one single exeption (one action must only be executed during offpeak window but was during entire day), is doing the same as mine! Wow! You are really a master in your job! :slight_smile:

I again added all my comments and did 1 or 2 minor tweaks. If interested please find the current code below.

Again many thanks for your superb job Steve!

Heinz

//This routine checks every "Threshold" minutes
    //if Google Sheet Status = "NotStop", do nothing
    //if we are in off peak window and if the State of Charge (SoC) is above "MinSoc"
    //if below MinSoc during off peak window, discharge is stopped
    //if below CutOffSoC, the battery needs to stop discharging for battery protection
    //if a car is charging during LowCost window, which battery stops to save energy for later in the morning when tariff is more expensive
    //if the battery needs to be replenished from the grid (typically during winter)
    //if on high PV forecast the battery shall only start charging around noon (prevent grid overloading)
  //a graphical plan of the different windows can be found attached.



//define basic constants
const CutOffSoC = 6; //SoC in % when battery is considered empty (inverter has turned of battery) 
const MinBatAvgPower = 1000; //Minimal power in Watt required to stop the battery charging on high PV forcast
const MinSoC = parseInt(global.get("GoogleInstructions")[1][1]); //to get MinSoc from Google Sheet
const Threshold = 5; //return only every 5min
const RemoteControl = global.get("GoogleInstructions")[0][0]; //to get operating instructions from Google Sheet
const ChargePower = parseInt(global.get("GoogleInstructions")[0][1]); //to get charging power from Google Sheet;
const OffPeakStart = new Date(global.get("GoogleInstructions")[2][1]); //ex. 21:00
const OffPeakEnd = new Date(global.get("GoogleInstructions")[3][1]); // this should be in line with the end time of EV charging to prevent, that battery will be depleted through EV charging
const LowCostStart = new Date(global.get("GoogleInstructions")[4][1]); // Start of periode when actual tariff is lower than normal off peak tariff
const LowCostEnd = new Date(global.get("GoogleInstructions")[5][1]); // End of periode when actual tariff is lower than normal off peak tariff
const ReplenishStartTime = new Date(global.get("GoogleInstructions")[6][1]); //ex. 03:00 because of low usage of grid
const ReplenishEndTime = new Date(global.get("GoogleInstructions")[7][1]); //ex. 05:00 because grid load starts here
const BatteryStopCharging = new Date(global.get("GoogleInstructions")[8][1]); //on high PV forecast stops battery charging until noon
const BatteryStartCharging = new Date(global.get("GoogleInstructions")[9][1]);//on high PV forecast starts battery charging at around noon
const AvgBatPower = flow.get("BatPowerAvg")[1]; //The average bat power over the last x measurements
const EVChargingState = new Object(global.get("EVCharging")); //current charging power of all charging points in an array
const TotalEVChargingPower = Object.values(EVChargingState).reduce((a, b) => a + b, 0); //Sum of charging power of all charging points
const SoC = parseFloat(msg.payload["SOC_real"]);
const ActualCapacity = parseInt(msg.payload["total_capacity"])/1000/3600 * parseFloat(msg.payload["state_of_health"])/100 * SoC/100; //computes the actual capacity based on max capacity, SoH and SoC in kWh
const BatPower = parseFloat(msg.payload["stat_batt_power"]);
const CurrentTime = new Date();
const TimerSec = parseInt(new Date().getSeconds());

//define conditions
const isFullMinute = (TimerSec <= 6 || TimerSec >= 59);
const isNoEmergency = (RemoteControl != "NotStop");
const isOffPeak = (CurrentTime >= OffPeakStart && CurrentTime < OffPeakEnd);
const isLowCost = (CurrentTime > LowCostStart && CurrentTime < LowCostEnd);
const isReplenishWindow = (ReplenishStartTime <= CurrentTime && CurrentTime < ReplenishEndTime);
const isPVHoldWindow = (CurrentTime > BatteryStopCharging && CurrentTime < BatteryStartCharging);

//define actions
const actions = [
    //--------------the following actions shall only be executed during offpeak window-------
    {
        //if during replenish hours, SoC is below minimal value and battery is not already set to charge
        when: () => SoC < MinSoC && isReplenishWindow && RemoteControl != "Laden",
        do: () => {
            flow.set("BatCapacityBeforeCharging", ActualCapacity);
            msg.payload = ["Laden", 6000]; //Charge battery at 6kW;
        }
    },


    {
        //if we are in offpeak window but out of replenish window and SoC is higher than MinSoC
        //(we are out of charging condition above)
        //Remark: OffPeak is required otherwise manual charging command during the day will
        //        be constantly overwritten
        when: () => isOffPeak && !(SoC < MinSoC && isReplenishWindow) && RemoteControl == "Laden",
        do: () => {
            let ChargedFromGrid = (ActualCapacity - parseInt(flow.get("BatCapacityBeforeCharging")));
            global.set("BatChargedFromGrid", ChargedFromGrid);
            msg.payload = "KeineRegelung";
        }
    },


    {
        //if EV's are charging during low cost hours and SoC is below MinSoC, turn off battery to save energy for 
        //later in the morning when tariffs raise again
        when: () => isOffPeak && isLowCost && TotalEVChargingPower > 0 && SoC < MinSoC && BatPower < 0,
        do: () => {
            msg.payload = "KeineRegelung";
        }
    },


    {
        //if we are in offpeak window, but out of low cost cost window, SoC is higher than CutOffSoC
        //only update status, if battery has been set to idle before
        when: () => isOffPeak && !isLowCost && SoC > CutOffSoC && !["Laden", "Entladen", "Automatik"].includes(RemoteControl),
        do: () => {
            msg.payload = "Automatik";
        }
    },


    //--------------the following actions will be executed out of offpeak window only-------
    {
        //if we are in a high PV forcast window, battery level is above minimum and there is already enough PV power (AvgBatPower), 
        //we want to delay battery charging until around noon
        when: () => !isOffPeak && SoC > MinSoC && isPVHoldWindow && AvgBatPower > MinBatAvgPower && RemoteControl != "KeineRegelung",
        do: () => {
            msg.payload = "KeineRegelung";
        }
    },


    {
        //if we are out of offpeak period, battery can be charged and provide no other manual command in Google Sheet(RemoteControl). 
        //This is a copy from above, just to make sure that the battery operation has been released when leaving offpeak period
        when: () => !isOffPeak && SoC > CutOffSoC && !["Laden", "Entladen", "Automatik"].includes(RemoteControl),
        do: () => {
            msg.payload = "Automatik";
        }
    }
];

msg.payload = "";



//Execute only at full minute and only if in Google Sheet OpMode is not in "NotStop" or "Laden" state
if (isFullMinute && isNoEmergency) {

    for (const action of actions) {
        if (action.when()) {
            action.do();
            break;
        }
}

   
} 

if (msg.payload != ""){
    return msg;
}

2 Likes

Dammit! I failed you. :wink:

1 Like