Groovy Rule Loop

Hi,
im trying to create a rule that writes an attribute when other attribute is updated.

rules.add()
        .name("Conectado Parq1")
        .when(
            {facts ->
            facts.matchFirstAssetState(
                new AssetQuery()
                    .ids("4HUgnoIT7ziFk1Lb9T3TWN")
                    .attributeName("lectura")
            ).isPresent()
        })
        .then(
            {facts ->
            assets.dispatch(new AttributeEvent("4HUgnoIT7ziFk1Lb9T3TWN", "estado", "Conectado"))
        })
        

Im encountering a rule loop. Any ideas to solve it? Thanks

Hi,
I am going to do the same thing, maybe a little more complex. I set a TCP agent which now can receive HEX message. I want to resolve the HEX message. So I want to set a groovy rule to write an attribute when receive the HEX message. I am still trying to get the HEX message in the “then” block, any ideas?

Hi,
You can add facts.reset() after your code. It seems to solve the rule loop.
But it brings other problems.

Hi song,
thanks for the reply. It seems that facts.matchFirstAssetState().isPresent() is not reacting to a write on the attribute. I solve the loop using a temporary fact. If i found a solution i report it back here!

The problem has confused me these days. In the V2.0 openremote, I found the solution.
OpenRemote Rules examples · openremote/Documentation Wiki · GitHub

But in the V3.0 I really can not find the solution. I have see the Rules and the converter, but not worked. I even want to develop the feature if I still can not find the way to solve the problem that the tcp agent receive the hex message but I can not resolve it to specified attribute.
I also ask for help in this topic. If there will be any good news, I will let you know as soon as possible.
Message converter

Hope you can bring good news. Thanks in advance.

Unfortunately, rules from version V2 are not supported anymore. They used to be in earlier versions of V3 but not anymore.

Hi,michal
are there any solutions to resolve the hex message when it is received?:joy:

Hi,

facts.reset() shouldn’t be used to avoid rule loops.

Here’s a groovy rule template which contains lots of comments to try and explain how to use Groovy rules and how to avoid rule loops:

package demo.rules

import org.openremote.manager.rules.RulesBuilder
import org.openremote.model.notification.*
import org.openremote.model.rules.AssetState
import org.openremote.model.asset.Asset
import org.openremote.model.asset.impl.*
import org.openremote.model.query.*
import org.openremote.model.query.filter.*
import org.openremote.model.rules.Assets
import org.openremote.model.rules.Notifications
import org.openremote.model.rules.Users
import org.simplejavamail.email.Email

import java.util.logging.Logger
import java.util.stream.Collectors

Logger LOG = binding.LOG
RulesBuilder rules = binding.rules
Notifications notifications = binding.notifications
Users users = binding.users
Assets assets = binding.assets

/*
* A groovy rule is made up of a when closure (LHS) which must return a boolean indicating whether the then closure (RHS)
* should be executed. The rule engine will periodically evaluate the when closure and if it evaluates to true then the
* rule then closure will execute.
*
* NOTE: DO NOT MODIFY THE FACTS IN THE WHEN CLOSURE THIS SHOULD BE DONE IN THE THEN CLOSURE
*
* To avoid an infinite rule loop the when closure should not continually return true for subsequent executions
* so either the then closure should perform an action that prevents the when closure from matching on subsequent
* evaluations, or custom facts should be used, some ideas:
*
* - Change the value of an attribute being matched in the when closure (which will prevent it matching on subsequent evaluations)
* - Insert a custom fact on first match and test this fact in the when closure to determine when the rule should match again (for
*   example if a rule should match whenever the asset state changes the asset state timestamp can be used)
*/

rules.add()
        .name("Example rule")
        .when({
    facts ->

        // Find first matching asset state using an asset query

        facts.matchFirstAssetState(

                // Find asset state by asset type and attribute name
                new AssetQuery().types(ThingAsset).attributeNames("someAttribute")

                // Find asset state by asset ID and attribute name
                //new AssetQuery().ids("7CaBoyiDhtdf2kn1Xso1w5").attributeNames("someAttribute")

                // Find asset state by asset type, attribute name and value string predicate
                //new AssetQuery().types(ThingAsset).attributes(
                //        new AttributePredicate()
                //                .name("someAttribute")
                //                .value(new StringPredicate()
                //                            .value("someValue")
                //                            .match(AssetQuery.Match.EXACT)
                //                            .caseSensitive(true)))

                // Find asset state by asset type and location attribute predicate
                //new AssetQuery().types(ThingAsset).attributes(
                //        new AttributePredicate()
                //                .name(Asset.LOCATION)
                //                .value(new RadialGeofencePredicate()
                //                            .radius(100)
                //                            .lat(50.0)
                //                            .lng(0.0)))

        ).map { assetState ->

            // Use logging to help with debugging if needed" +
            //LOG.info("ATTRIBUTE FOUND")

            // Check if this rule really should fire this time
            Optional<Long> lastFireTimestamp = facts.getOptional("someAttribute")
            if (lastFireTimestamp.isPresent() && assetState.getTimestamp() <= lastFireTimestamp.get()) {
                return false
            }

            // OK to fire if we reach here

            // Compute and bind any facts required for the then closure
            facts.bind("assetState", assetState)
            true
        }.orElseGet {
            // Asset state didn't match so clear out any custom facts to allow the rule to fire next time the when closure matches
            facts.remove("someAttribute")
            false
        }

})
        .then({
    facts ->

        // Extract any binded facts
        AssetState assetState = facts.bound("assetState")

        // Insert the custom fact to prevent rule loop
        facts.put("someAttribute", assetState.getTimestamp())

        // Write to attributes
        def otherAttributeValue = null
        if (assetState.getValue().orElse{null} == "Value 1") {
            otherAttributeValue = "Converted Value 1"
        } else if (assetState.getValue().orElse{null} == "Value 2") {
            otherAttributeValue = "Converted Value 2"
        } else {
            otherAttributeValue = "Unknown"
        }
        assets.dispatch(assetState.id, "otherAttribute", otherAttributeValue)

        // Generate notifications (useful for rules that check if an attribute is out of range)
        //notifications.send(new Notification()
        //        .setName("Attribute alert")
        //        .setMessage(new EmailNotificationMessage()
        //                .setTo("no-reply@openremote.io")
        //                .setSubject("Attribute out of range: Attribute=${assetState.name} Asset ID=${assetState.id}")
        //                .setText("Some text body")
        //                .setHtml("<p>Or some HTML body</p>")
        //        )
        //)
})

It doesn’t cover everything you can do in Groovy rules as you have access to the full Groovy language you can do pretty much anything but it should help you with the basic concepts.

Soon I will push an updated manager docker image which will use this template so when you add a new Groovy rule you’ll see the above template.

Rich

1 Like

Hi Rich,
Thanks for the support! I modify the example and is working great.
Best Regards

Hi rich,
Thanks so much for your help! The problem is solved.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.