Lambda in rule fails with 'Error during the execution of rule '{RuleName}': null'

Hi,

i implemented the following function:

var org.eclipse.xtext.xbase.lib.Functions$Function3<RollershutterItem, ContactItem, Number, Boolean> tryCloseDoorRollos = [
    rollo, contact, maxNumberOfRetries |
        var numberOfTries = 1;
        logInfo(logName, "RO:" + rollo);    
        logInfo(logName, "CI:" + contact);
        while (numberOfTries <= maxNumberOfRetries)
        {
            if(contact.state == OPEN)
            {
                logInfo(logName, contact.name + " is still open, waiting 60s before retrying ... (Try No. " + numberOfTries + ")");
                numberOfTries++;
                Thread.sleep(60000);
            } 
            else
            {
                logInfo(logName, "Closing " + rollo);
                rollo.sendCommand(100);
                return true;
            }
        }
        logInfo(logName, contact.name + " was open after " + numberOfTries + " retries. Exiting.");
        return false;
]

rule "Door_Rollos_Night"
when
    // Channel 'astro:sun:local:nauticDusk#start' triggered START
    Time cron "30 6 19 * * ? *" 
then
    if(Temperature_Today_Min.state < 15 || Temperature_Tomorrow_Min.state < 15)
    {
        logInfo(logName, "Forecast Temperature < 15°C, going to ANTI-CHILL mode (100) (Doors)");
        tryCloseDoorRollos.apply(WZ_Terrassentuer_Rolladen_1_Level, WZTerrassentuer_1_State, 10);
    }
end

When I have the rule triggered I get the following error:

2018-04-29 19:06:30.213 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - **Error during the execution of rule 'Door_Rollos_Night': null**

Does anyone know what might be null? The passed items are not null, at least according to the logInfo output:

2018-04-29 18:36:00.292 [INFO ] [clipse.smarthome.model.script.Rollos] - WZ_Fenster_Rolladen_1_Level (Type=RollershutterItem, State=0, Label=Level, Category=Blinds, Groups=[Rolladen, Wohnzimmer])
2018-04-29 18:36:00.292 [INFO ] [clipse.smarthome.model.script.Rollos] - WZTerrassentuer_1_State (Type=ContactItem, State=OPEN, Label=State, Category=Contact, Groups=[Zugangssensoren, Wohnzimmer])

Regards Sebastian

Add logging to the lambda to figure out what line it is failing on. Then look at that line and the state of everything used on that line to figure out why it is causing an error.

As an aside, any rule that takes longer than half a second or so to exit can cause problems. There are only 5 execution threads available for rules to run. Long running rules increases the likelihood that you will run out and then no rules will run.

The role above can take up to 10 minutes to run. If that lambda gets called from other rules (if not why have the lambda in the first place?) Or you have other similarly long running rules you will experience problems.

You should be using Timers instead of long Thread::sleeps.

Guessing you do not see the first lambda log message with "RO: "?
You don’t seem to pass anything called logName into the lambda.

Thanks a lot for your inputs, not only the ones in the thread but the design pattern posts as well. They are very helpful and inspiring as well!

After a morning of technical-tourette (my kids learned a few new words I guess …) and ~ 5000 new error log lines I managed to create a working solution AND I learned a few things about xtend language specifics (YES, closures exists in other programing languages as well, but that’s a diffferent story … :roll_eyes:).
Also, I didn’t realize that lambdas don’t have access to global variables, so rossko57’s hint was right.

Anyways … this is the new version:

import java.util.Map

val Map<String, Timer> doorRolloTimers = newHashMap  
val org.eclipse.xtext.xbase.lib.Functions$Function6<RollershutterItem, ContactItem, Map<String, Timer>, Number, Number, Functions$Function6, Boolean> tryCloseDoorRollos = [
        rollo, contact, doorRolloTimers, numberOfTries, maxNumberOfRetries, tryCloseDoorRollos |
            var logName = "Rollos"
            if (contact.state == OPEN)
            {
                if(numberOfTries <= maxNumberOfRetries)
                {
                    logInfo(logName, contact.name + " is still open, waiting 60s before retrying ... (Try No. " + numberOfTries + ")");
                    doorRolloTimers.put(contact.name, createTimer(
                        now.plusMinutes(1), 
                        // 4 debug
                        // now.plusSeconds(5), 
                        [|
                            doorRolloTimers.put(contact.name, null);
                            tryCloseDoorRollos.apply(rollo, contact, doorRolloTimers, numberOfTries + 1, maxNumberOfRetries, tryCloseDoorRollos);    
                        ]));
                    return false;
                } 
                else
                {
                    logInfo(logName, contact.name + " was open after " + (numberOfTries - 1) + " retries. Exiting.");
                    return false;
                }
            }
            else
            {
                logInfo(logName, "Closing " + rollo.name);
                rollo.sendCommand(100);
                return true;
            }
]

rule "Door_Rollos_Night"
when
    Channel 'astro:sun:local:nauticDusk#start' triggered START
then
    if(Temperature_Today_Min.state < 15 || Temperature_Tomorrow_Min.state < 15)
    {
        logInfo(logName, "Forecast Temperature < 15°C, going to ANTI-CHILL mode (100) (Doors)");

        tryCloseDoorRollos.apply(WZ_Terrassentuer_Rolladen_1_Level, WZTerrassentuer_1_State, doorRolloTimers, 1, 10, tryCloseDoorRollos);        
        tryCloseDoorRollos.apply(WSTerrassentuerRolladen_1_Level, WSTerrassentuer_1_State, doorRolloTimers, 1, 10, tryCloseDoorRollos);
    }
end

Do you still see any potential problems with resource usage?

…again, Thanks for your help!

Regards Sebastian

2 questions I forgot about:

-There is no way to declare a void returning lambda, correct? I’d like to get rid of the boolean returns.
-Any chance to disable the warning regarding the recursive lambda parameter declaration (Function6 is a raw type …)? It’s not that important,. but it catches my attention every time I look in the rule or the logs…

Regards Sebastian

Do you still see any potential problems with resource usage?

I don’t see anything that raises a red flag. This isn’t how I would implement it but I see nothing wrong with it. While it is good to keep track of your Timers so you can reference them later, I don’t see where you ever reference the timers though so you could probably eliminate doorRolloTimers.

There is no way to declare a void returning lambda, correct? I’d like to get rid of the boolean returns.

Use Procedures$Procedure6 instead of Functions$Function6.

Any chance to disable the warning regarding the recursive lambda parameter declaration

Not easily. You end up with an infinite recursion to do it normally. You can try to figure out a way to split the lambda into two and have one call the other but in this case I don’t really think that will work either.

This is one of those that you have to decide ho much it bothers you because fixing it will require a different approach.

For example, if I applied:

the rule could become:

var startedRollosLoop = now
val logName = "Rollos"

rule "Soor_Rollos_Night"
when
    Item vTimeOfDay changed
then
    // determine if we even need to do anything at all
    if(vTimeOfDay.state != "EVENING") return;
    if(Temperature_Today_Min.state >= 15 && Temperature_Tomorrow_Min.state >=15) return;

    logInfo(logName, "Forecast Temperature < 15°C, going to ANTI-CHILL mode (100) (Doors)")

    startedRollosLoop = now

    // Kick off the checking loop for each Rollershutter
    gRollos.members.forEach[ rollo |
        val timer = gTimers.members.findFirst[ t | t.name == rollo.name+"_Timer" ]
        timer.sendCommand(OFF) // kick off the retry loop
    ]
end

rule "Rollo Timer Expired"
when
    // Replace triggers with Member of gRollos received command OFF when running on 2.3+
    Item WZ_Terrassentuer_Rolladen_1_Level_Timer received command OFF or
    Item WSTerrassentuerRolladen_1_Level_Timer received command OFF
then
    val contact = gContacts.members.findFirst[ c | c.name == triggeringItem.name.replace("Timer", "Contact") ]

    // If contact is CLOSED, log and close the rollershutter    
    if(contact.state == CLOSE){
        val rolloName = triggeringItem.name.replace("_Timer", "")
        logInfo(logName, "Closing " + rolloName)
        sendCommand(rolloName, 100)
        return;
    }

    // contact is OPEN, have we been trying for 10 minutes?
    if(now.isAfter(startedRollosLoop.plusMinutes(10))) {
        logInfo(logName, contact.name + " was open after 10 minutes. Exiting")
        return;
    }

    // contact is OPEN and it has been less than 10 minutes, log and check again by resetting the Timer
    logInfo(logName, contact.name + " is still open, waiting before retrying ...")
    triggeringItem.sendCommand(ON)
end

Everything I did should be explained by the DP links above. If not let me know. Note that I just typed these in. There is likely to be a typo or two.

Theory of Operation:

When ever the time of day this rule runs.

We check to see if it is EVENING, if not we exit with nothing to do.

Then we check to see if today’s min and tomorrows min is greater equal to 15 and if so exit with nothing to do.

If we made it this far, we need to start the checking loop. Grab a timestamp for now and sendCommand(OFF) to the Expire Binding Timer Item associated with each rolloershutter Item. The Expire binding is set to expire="1m,command=OFF".

The OFF command immediately triggers the “Rollo Timer Expired” rule. This Rule implements the bulk of your old lambda.

Check to see if the contact associated with the Timer Item that triggered the Rule is CLOSED. If so log, close the rollershutter, and exit.

If the contact is OPEN and we have been trying for more than 10 minutes then log and exit.

If we made it this far, we need to try again in a minute, so start the Expire based Timer by sending command ON.