OH3: BooleanExtensions.operator equals(boolean,boolean) on instance: null

  • openHABian 3.4.2 on rPi4 with 4GB

Much to my surprise, I am getting this error.

2024-06-02 12:09:16.870 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'sppro_modbus-6' failed: An error occurred during the script execution: Could not invoke method: org.eclipse.xtext.xbase.lib.BooleanExtensions.operator_equals(boolean,boolean) on instance: null in sppro_modbus

I have reduced the rule for troubleshooting to:

val LOG_PREFIX = "Modbus."
var g_b_grid_off_detected = false


rule "Modbus: Switch OFF inverter 3 if grid is gone"
    when
        Item spm_AC_Source_Status received update or
        Item spm_Battery_SoC changed
    then
        val RULE_ID = 06

        // Exit for all stati other than grid OFF = 0
        if (spm_AC_Source_Status.state != 0)
        {
            if (g_b_grid_off_detected == true)
            {
                g_b_grid_off_detected = false
                logInfo(LOG_PREFIX + RULE_ID + ".01",
                    "Grid connection is......: ON")
            }
            return
        }
end

I have no idea why this would create an error, as I am the same construct in other rules and it works there.

Now comes the kicker; if I take this rule out of one rule file (modbus), and put into another (test), it works.

If I comment the boolean operations/conditions, I have no error.

Any hints appreciated.

FYI, here the complete rule:

// switch OFF KACO #3
rule "Modbus: Switch OFF inverter 3 if grid is gone"
    when
        Item spm_AC_Source_Status received update or
        Item spm_Battery_SoC changed
    then
        val RULE_ID = 06

        // Exit for all stati other than grid OFF = 0
        if (spm_AC_Source_Status.state != 0)
        {

            if (g_b_grid_off_detected == true)
            {
                g_b_grid_off_detected = false

                logInfo(LOG_PREFIX + RULE_ID + ".01",
                    "Grid connection is......: ON")
            }

            return
        }

        if (g_b_grid_off_detected == false)
        {
            g_b_grid_off_detected = true

            logInfo(LOG_PREFIX + RULE_ID + ".02",
                "Grid connection is......: OFF")
        }

        // Exit if less than 1 W is generated by solar PV; basically no solar PV
        // generation.
        if ((grp_spm_AC_Coupled_Solar_Power.state as DecimalType).intValue < 1)
        {
            return
        }

        // Exit if the inverter is already OFF
        if (DIN_Rail_Relay_03_ON_OFF_Switch.state == OFF)
        {
            return
        }

        // grid went offline

        // Establish generation and load values
        val SOLAR_PV_TOTAL = (grp_spm_AC_Coupled_Solar_Power.state as DecimalType).intValue
        val AC_LOAD_POWER = (spm_AC_Load_Power.state as DecimalType).intValue

        // Calculate how much power the battery can absorb
        val BATTERY_CAPACITY = 20000
        var missing_battery_capacity = Math::abs((BATTERY_CAPACITY
            * (spm_Battery_SoC.state as DecimalType).intValue / 100)
            - BATTERY_CAPACITY)

        // Logic here: if total solar PV generation is greater than load, that
        // is battery charging and load, then switch OFF inverter #3.
        // Why? Because the outputs of inverter #1 and #2 are being limited by
        // the Selectronic Inverter / Charger while #3 is not, because it does
        // not have the limiting capability.

        logInfo(LOG_PREFIX + RULE_ID + ".03", "SOLAR_PV_TOTAL..........: {}", SOLAR_PV_TOTAL)
        logInfo(LOG_PREFIX + RULE_ID + ".04", "AC_LOAD_POWER...........: {}", AC_LOAD_POWER)
        logInfo(LOG_PREFIX + RULE_ID + ".05", "missing_battery_capacity: {}", missing_battery_capacity)

        // Charge power is constrained to 5kW; = 53 V * 100 A = 5300 W
        // However, for a safety margin reduced to 5000
        val MAXIMUM_CHARGE_POWER = 5000

        if (missing_battery_capacity > MAXIMUM_CHARGE_POWER)
        {
            missing_battery_capacity = MAXIMUM_CHARGE_POWER

            logInfo(LOG_PREFIX + RULE_ID + ".06",
                "missing_battery_capacity: {}",
                missing_battery_capacity)
        }

        if (SOLAR_PV_TOTAL > (AC_LOAD_POWER + missing_battery_capacity))
        {
            // Set visual alarm by switching LED light ON in red
            Shed_KDL_Colour.sendCommand("10,100,30")

            // Switch OFF inverter #3
            DIN_Rail_Relay_03_ON_OFF_Switch.sendCommand(OFF)

            // Send email
            val mailActions = getActions("mail", "mail:smtp:argylecourt")

            val mailSubject = "Alarm: Inverter #3 has been switched OFF!"

            mailActions.sendMail
            (
                Const_Email_Admin.state.toString(),
                mailSubject,
                mailSubject
            )

            logWarn(LOG_PREFIX + RULE_ID + ".06", mailSubject)
        }
end

Even though the error is complaining about null, what that error means is it cannot convert the types on both sites of the operator (== in this case) to compatible types to perform the operation.

As for why this is occurring itā€™s impossible to say given the information available. Youā€™d have to log out g_b_grid_off_detected Just before the error to see what it actually is.

Note, you donā€™t need to use == with booleans.

1 Like

As always, thank you for your handy input.

Minimising the rule to:

rule "Modbus: Switch OFF inverter 3 if grid is gone"
    when
        Item spm_AC_Source_Status received update or
        Item spm_Battery_SoC changed
    then
        val RULE_ID = 06

        logInfo(LOG_PREFIX + RULE_ID + ".0a",
            "g_b_grid_off_detected...: {}", g_b_grid_off_detected)

        // Exit for all stati other than grid OFF = 0
        if (spm_AC_Source_Status.state != 0)
        {

            if (g_b_grid_off_detected == true)
            {
                logInfo(LOG_PREFIX + RULE_ID + ".0b",
                    "g_b_grid_off_detected...: {}", g_b_grid_off_detected)
                g_b_grid_off_detected = false
                logInfo(LOG_PREFIX + RULE_ID + ".0c",
                    "g_b_grid_off_detected...: {}", g_b_grid_off_detected)

                logInfo(LOG_PREFIX + RULE_ID + ".01",
                    "Grid connection is......: ON")
            }

            return
        }

        logInfo(LOG_PREFIX + RULE_ID + ".0d",
            "g_b_grid_off_detected...: {}", g_b_grid_off_detected)

        if (g_b_grid_off_detected == false)
        {
            g_b_grid_off_detected = true
            logInfo(LOG_PREFIX + RULE_ID + ".0e",
                "g_b_grid_off_detected...: {}", g_b_grid_off_detected)

            logInfo(LOG_PREFIX + RULE_ID + ".02",
                "Grid connection is......: OFF")
        }

        logInfo(LOG_PREFIX + RULE_ID + ".0f",
            "g_b_grid_off_detected...: {}", g_b_grid_off_detected)
end

leads to

2024-06-03 06:54:43.565 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'sppro_modbus.rules'
2024-06-03 06:55:22.270 [INFO ] [g.openhab.core.model.script.null6.0a] - g_b_grid_off_detected...: null
2024-06-03 06:55:22.272 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'sppro_modbus-6' failed: An error occurred during the script execution: Could not invoke method: org.eclipse.xtext.xbase.lib.BooleanExtensions.operator_equals(boolean,boolean) on instance: null in sppro_modbus
2024-06-03 06:55:22.297 [INFO ] [g.openhab.core.model.script.null6.0a] - g_b_grid_off_detected...: null
2024-06-03 06:55:22.301 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'sppro_modbus-6' failed: An error occurred during the script execution: Could not invoke method: org.eclipse.xtext.xbase.lib.BooleanExtensions.operator_equals(boolean,boolean) on instance: null in sppro_modbus
2024-06-03 06:55:27.549 [INFO ] [penhab.core.model.script.Modbus.6.0a] - g_b_grid_off_detected...: false
2024-06-03 06:55:37.629 [INFO ] [penhab.core.model.script.Modbus.6.0a] - g_b_grid_off_detected...: false
2024-06-03 06:55:47.708 [INFO ] [penhab.core.model.script.Modbus.6.0a] - g_b_grid_off_detected...: false

The error seems to occur after the rule is loaded; usually 38 seconds after the rule file has been savedā€¦ assuming it hasnā€™t figured out then what g_b_grid_off_detected is. Five seconds later, and for every every other rule execution it ā€˜knowsā€™ what g_b_grid_off_detected is, and stops complaining.

Well, at least I know what is seemingly happening, and will ignore the error.

This sounds like a recommendation?! Meaning == works just as well?!
For the sake of my old brain, I rather stick with ==, which is the same for any other conditional test, rather than using an assignment character. :slight_smile:
[There is already the exception for null and ===ā€¦ or was it for NULL? Never mind :slight_smile: ]


Actually [edit2], I tested this, and a single = does not work.
The rule file has the global g_b_grid_off_detected set to false.
The first test in the rule should not execute, but does when = is used:

2024-06-03 08:31:52.005 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'test-1' failed: An error occurred during the script execution: Cannot assign a value in null context. in test
2024-06-03 08:31:53.194 [INFO ] [.openhab.core.model.script.test.6.01] - Grid connection is......: ON
2024-06-03 08:32:03.274 [INFO ] [.openhab.core.model.script.test.6.01] - Grid connection is......: ON
2024-06-03 08:32:13.354 [INFO ] [.openhab.core.model.script.test.6.01] - Grid connection is......: ON
2024-06-03 08:32:21.392 [INFO ] [.openhab.core.model.script.test.6.01] - Grid connection is......: ON
2024-06-03 08:32:23.433 [INFO ] [.openhab.core.model.script.test.6.01] - Grid connection is......: ON
2024-06-03 08:32:24.151 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'test.rules'
2024-06-03 08:36:35.387 [INFO ] [openhab.core.model.script.Shed.19.02] - KDL not occupied...
2024-06-03 08:44:41.884 [INFO ] [openhab.core.model.script.Shed.19.01] - KDL occupied...

What the log also shows, loading test.rules with the KACO rule did not trigger the error (but did so in first load). Something weird going on there!


[edit1]

Even more interesting; putting this rule at the beginning of the rule rule, leads to this:

2024-06-03 07:15:36.792 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'sppro_modbus.rules'
2024-06-03 07:15:50.044 [ERROR] [xt.xbase.jvmmodel.JvmModelAssociator] - Error calling inferrer
2024-06-03 07:15:50.431 [ERROR] [xt.xbase.jvmmodel.JvmModelAssociator] - Error calling inferrer
2024-06-03 07:15:50.908 [ERROR] [xt.xbase.jvmmodel.JvmModelAssociator] - Error calling inferrer
2024-06-03 07:16:18.635 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'sppro_modbus-1' failed: An error occurred during the script execution: Could not invoke method: org.eclipse.xtext.xbase.lib.BooleanExtensions.operator_equals(boolean,boolean) on instance: null in sppro_modbus

Adding `booleanā€™ before the variable name in the definition does not make a difference either.

var g_b_grid_off_detected = false
var boolean g_b_grid_off_detected = false

You could try to use the cache instead of a global variable. Thatā€™s considered best practice now anyway. It has the advantage that you can share the variable even between rules in different files or even different languages. And if you put timers into the cache, the timers get cancelled automatically on a rules reload instead of leaving the timers orphaned.

Get rid of the global variable and youā€™d use:

            if(sharedCache.get('g_b_grid_off_detected', [ | false ]) ) {
                sharedCache.put('g_b_grid_off_detected', false)

The lambda in the call to get() initializes that property in the cache to false if it doesnā€™t already exist.

Itā€™s redundant and in some edge cases it can lead to unexpected results.

Itā€™s for null only and itā€™s not an exception so much as requirement. === and == are different operators. They do different things and itā€™s just one of the many quirks of Rules DSL that you must use === with null in order to get the result you want and expect.

Now I think there is some confusion. There are three operations that use the = character.

Operator Name What it does
= Assignment Sets the value of the left operand (i.e. variable) to the value of the right operand.
== Equals Returns true if the contents of the left operand is calculated to be the same as the right operand.
=== Identity Returns true if the left operand and the right operand are the same thing; i.e. occupy the same space in memory.

You should never use = in a comparison.

And as for the difference between == and ===:

val one = "One"
val two = "One"
val three = one

one == two // true, the two variables have equivalent contents
one === two // false, variable one points to a different value in memory from variable two
one === three // true, variable one and variable three point to the same value in memory

What I said above is when you have a boolean variable, you donā€™t need the comparison. Itā€™s redundant.

if(g_b_grid_off_detected == true)

and

if(g_b_grid_off_detected)

are mostly the same operation, though using == true cuts of a number of options, particualrly with languages that treat other values as booleans like null being the same as false or 0 being the same thing as false.

if(g_b_grid_off_detected = true)

is invalid.

The name of the variable makes it clear that itā€™s a boolean and when itā€™s not often using is is used (e.g. is_offgrid).

1 Like

Hmmā€¦

Is this for rules DSL?

If not, where does it go?
Does it replace the ā€˜if()ā€™ for each conditional comparison?

Since a single equal sign is an assignment and not valid for a comparison, did you mean no = should be used; like:

if (g_b_grid_off_detected) {}
or
if (!g_b_grid_off_detected) {}

Well, in short: boolean without the == is working; as in not throwing the error I initially stated.

Also, I am using ā€˜myā€™ construct in these rules

grep -i "== false" /etc/openhab/rules/*.rules
/etc/openhab/rules/astro.rules:        //if (azimuth > 295 && azimuth < 330 && Weather_Cloudy.state == OFF && ShutterHouseLivingWestClosed == false) {
/etc/openhab/rules/astro.rules:        if (azimuth > 295 && azimuth < 330 && rs_KDL_West_Closed == false)
/etc/openhab/rules/irrigation1.rules:                if (g_b_emailed_max_pump_still_running == false)
/etc/openhab/rules/rf433rx.rules:        if ((g_b_is_email_lock_set == false) && (g_device_name != "not specified"))
/etc/openhab/rules/shed.rules:            if (g_b_preheater == false)
/etc/openhab/rules/tesla.rules:                if (blnOpenGateSignalSent == false)
/etc/openhab/rules/zeva.rules:                if (blnEmailLock_CellV == false)
/etc/openhab/rules/zeva.rules:                if (blnEmailLock_CellV == false)
/etc/openhab/rules/zeva.rules:        if (blnEmailLock_errMsg == false)

ā€¦ without ever seeing this error.


In any case: case closed :slight_smile:

Yes.

The sharedCache and privateCache were added in OH 3.4.

Yes, instead of using a global variable you get the value from the sharedCache. Everywhere you use g_b_grid_off_detected use sharedCache.get('g_b_grid_off_detected', [ | false ]) instead. Everywhere you have g_b_grid_off_detected = usesharedCache.put(ā€˜g_b_grid_off_detectedā€™, )` instead.

Correct.

Except for the error that prompted this post in the first place?

Thank youā€¦

Adding sharedCache.put('g_b_is_off_grid', 'false') at the beginning of my rule file, results in red twigglies in VScodeā€¦ with the attached error:

missing EOF at 'sharedCache'(org.eclipse.xtext.diagnostics.Diagnostic.Syntax)

I created a new empty rules file, same outcome.

Is there an import statement that needs to go in?
image
The doc says must be implicitly imported, but does not say how.

Out of interest, looking at the single quotes in the statement, does it only accept string values? I assume the cache function/object stores numbers w/o quotes too?!

You would never do that there. There is no reason to ever use the cache anywhere outside of a rule. And I donā€™t think that itā€™s available outside the rule anyway since itā€™s an implicit variable.

Thatā€™s why I said above ā€œget rid of the global variableā€ though later on where I " Everywhere you use g_b_grid_off_detected use ā€¦" I didnā€™t make it clear ā€œeverywhereā€ doesnā€™t include the global variable.

So to make it more clear:

  1. Remove the global variable. This line doesnā€™t get replaced with anythng.
  2. Everywhere else use sharedCache.get('name', [ | false]) and use sharedCache.put('name', true) to set.

It is in the docs for Rules DSL as being there as part of OH 3.4 so I assumed the cacheā€™s got back ported. Maybe not. But if the only error you are seeing is where you are trying to use the cache outside of a rule than remove that line and see if it works without that error.

The cache does need to be implicitly imported for the other rules languages. There really isnā€™t anything available to you to import it yourself in Rules DSL. Yet another area where Rules DSL has fallen behind all the other rules languages.

Itā€™s usually best to use a String or something like a String as the key (first argument) but the second argument can be anything: numbers, booleans, strings, timers, etc. Do not use Items as keys though.

Thank you for the detailed reply.

I did try this statement in a rule:

rule "init cache"
    when
        System started
    then
        sharedCache.put('g_b_is_off_grid', 'false')
end

ā€¦ which produced this error:

The method or field sharedCache is undefined(org.eclipse.xtext.diagnostics.Diagnostic.Linking)

In any case, letā€™s assume it does not work in OH3.4. I will give it another try, once I have migrated to OH4.

:slight_smile: Is this a hintā€¦

Yes, it appears that it wasnā€™t back ported to 3.4 after all. It is there in the other languages through.

More of a warning. I do not recommend doing new development in Rules DSL. Any advantages it once had in terms of ease of use have been far surpassed by Blockly which makes all of itā€™s disadvantages even more of a problem. You get all the disadvantages (flakey typing system, incomplete access to the openHAB API, lack of proper programming constructs like classes and functions) without the advantages of ease of use.

1 Like