Lucid script step by step ... (Jython scripting tutorial using lucid,jsr223 Jython helper library)

No. The event object has no itemState property. It has a state property though.

I will soon add some documentation for the event object.

1 Like

Ah… I’m still using the item_triggered decorator which uses ItemEventTrigger, where the event object does contain itemState. I assumed that this was always the case. That seems like it could get messy if you’re not using the decorators and have a rule with multiple triggers… you’d need to determine which trigger fired in order to know what the event object has in it. Hmmm.

That would be very helpful. Specifically, what the event object contains for each of the trigger classes.

In reading through the reeadme, under Event-based triggers, you have…

ItemStateChangeTrigger('<item>', ['<state>']), # Item changed

Previous state is also supported (item changed from X to Y). The order of the arguments should probably be rearranged to have triggerName last, because without using keyword arguments, it would be…

ItemStateChangeTrigger('<item>', ['<state>'], None, ['<previousState>']), # Item changed

BTW, how is the triggerName used? When I put in a value I don’t see a difference. It looks like it is just for logging though, and I’m doing my logging a little different.

Thanks for your reply. I’d be very happy to discuss all this in detail. Quite interesting. Can we do it in a new thread because it would be a bit off-topic in this tutorial step by step thread. :wink:

Update your items file and your sitemap file so that they match this:

stepbystep.items

Switch My_TestSwitch_1 "My Test Switch 1"
Switch My_TestSwitch_2 "My Test Switch 2"
String Test_String_1 "Test String 1: [%s]"
String Test_String_2 "Test String 2: [%s]"

stepbystep.sitemap

sitemap stepbystep label="Lucid Step By Step" {
	Frame label="Test Items" {
		Default item=My_TestSwitch_1
		Default item=My_TestSwitch_2
		Default item=Test_String_1
		Default item=Test_String_2
	}
}

Now what we will do today is to modify the script a bit so that when the My_TestSwitch_1 is switched on, the string Test_String_1 will be set to “CAT” and when you should make the cron event set the string to “DOG”.

To set a variable (posting updates and sending commands) you use the events object which is part of the default scope, meaning that you don’t have to import it.

Just use it like this:

events.postUpdate("Test_String_1", "some data")

It will be your “home work” to change the script to do the above.

If you followed my instructions, your script should now similar to this:

from lucid.rules import rule, addRule # Needed for defining and adding a rule
from lucid.triggers import ItemStateChangeTrigger, CronTrigger # Import the different type of triggers that you will be using

@rule
class StepByStep(object): # Giving the class a unique name
    def getEventTriggers(self):
        return [
            ItemStateChangeTrigger('My_TestSwitch_1'), # Triggering when the switch changes its state.
            ItemStateChangeTrigger('My_TestSwitch_2', ON), # Only trigger when switch turns on
            CronTrigger('0 0/2 * 1/1 * ? *'), # Runs every other minute
        ]

    def execute(self, modules, inputs):
        self.log.setLevel(DEBUG)

        if self.event.isItem:
            self.log.debug('One of the test switches has changed its state')
            self.log.debug('Triggering item name: \'' + unicode(self.event.itemName) + '\', state: ' + str(self.event.state))

            if self.event.itemName == 'My_TestSwitch_1':
                events.postUpdate("Test_String_1", "CAT")

        elif self.event.isCron:
            self.log.debug('A cron event occurred')
            events.postUpdate("Test_String_1", "DOG")

addRule(StepByStep()) # Needed to add the rule, use the same name as defined for the class

In the next step, we want to change the logic a bit.

Now, we only want to set Test_String_1 to "CAT" when both switches are ON but only when an item trigger has occurred. (Not at cron events)

We now have the need to check items for their current values. How do we do that?

You can get an items state by using the itemRegistry object like this:

itemName = "My_TestSwitch_1"
item = itemRegistry.getItem(itemName)

That will make the variable named item store a reference to the My_TestSwitch_1 item object so that you can access it’s methods and attributes. You can then get the items state simply by it’s state attribute: item.state. To compare whatever is inside item.state with a text string you can conveniently convert it to a string object by item.state.toString().

To check if the switch is on, we’d need to do something like:

itemName = "My_TestSwitch_1"
item = itemRegistry.getItem(itemName)
if item.state == ON:
    # Do something

Now, in case an item isn’t initialized (never used) we should have to handle such cases too and now I think it’s already getting tedious. Lot’s of work for making a small task.

We are more lazy than that. Therefore we take advantage of another nifty function from lucid.utils named getItemValue.

getItemValue(itemName, defVal) #  Returns the items value if the item is initialized otherwise return the default value.

Simply add getItemValue to the import statement above and separate it with a comma sign.

Add an import statement in the code:
from lucid.utils import getItemValue

You can now get an items current value as simple as this example:

getItemValue("My_TestSwitch_1", OFF)

It can be stored in a variable:
someVar = getItemValue("My_TestSwitch_1", OFF)

or it can be used in an if statement directly just like:
if getItemValue("My_TestSwitch_1", OFF) == ON

You may have noticed that we used OFF as the second argument for getItemValue. OFF is here simply the value that we want to get back if the item state we request doesn’t exist (uninitialized).

Now, go ahead and modify the script to achieve what we want using the lucid getItemValue function.

Thanks again for the nice tutorial!

With the script above I get the following error when it is executed:

2018-07-16 10:00:00.987 [ERROR] [.automation.core.internal.RuleEngine] - Failed to execute rule 'ShowSomeEventInfo-ec62e7cf-415e-4cd1-89bd-97c6145c7fdd': Failed to execute action '1': null

I have no idea why…

@juelicher

So, the script from the initial post works but the latest version fails when cron triggers it. Is that right?

Are you using the exact script above or have you made any modifications to it? I see that your script seems to be named differently: “ShowSomeEventInfo-ec62e7cf-415e-4cd1-89bd-97c6145c7fdd”.

Can you try the exact code from post #13 ?

Sorry, I copied the wrong line from the log, that was one from your example scripts, that shows the same error. But it ist he same for the step by step script:

2018-07-16 10:20:01.249 [ERROR] [.automation.core.internal.RuleEngine] - Failed to execute rule 'StepByStep-4ba4cb78-d698-4ca8-854a-1cc4bc9fd4bc': Failed to execute action '1': null

I did a copy&paste for the script and the items, so that should be identical.

That’s a bit strange because the script from post #13 works here.

Please double check that you have copied all text from the script.
Please also check again that the script from post 1 is still working.

I’ll be busy now for some hours but I will return later.

The script from the first post works as expected:

2018-07-16 11:19:32.395 [INFO ] [lucid.StepByStep                    ] - One of the test switches has changed its state

The script from post #7 and #13 shows the error, whether triggered by item change or cron:

018-07-16 11:21:55.500 [INFO ] [lucid.utils                         ] - WAITING 1.2100315961 sec !!!
2018-07-16 11:21:55.501 [ERROR] [.automation.core.internal.RuleEngine] - Failed to execute rule 'StepByStep-dac8a7cc-5976-41fb-b872-b0d1804c8da8': Failed to execute action '1': null
2018-07-16 11:21:57.038 [INFO ] [lucid.utils                         ] - WAITING 0.64235910478 sec !!!
2018-07-16 11:21:57.039 [ERROR] [.automation.core.internal.RuleEngine] - Failed to execute rule 'StepByStep-dac8a7cc-5976-41fb-b872-b0d1804c8da8': Failed to execute action '1': null
2018-07-16 11:22:00.870 [INFO ] [lucid.utils                         ] - WAITING 0.857558331162 sec !!!
2018-07-16 11:22:00.871 [ERROR] [.automation.core.internal.RuleEngine] - Failed to execute rule 'StepByStep-dac8a7cc-5976-41fb-b872-b0d1804c8da8': Failed to execute action '1': null
2018-07-16 11:22:14.832 [INFO ] [lucid.utils                         ] - WAITING 1.3118525047 sec !!!
2018-07-16 11:22:14.832 [ERROR] [.automation.core.internal.RuleEngine] - Failed to execute rule 'StepByStep-dac8a7cc-5976-41fb-b872-b0d1804c8da8': Failed to execute action '1': null
2018-07-16 11:24:01.285 [ERROR] [.automation.core.internal.RuleEngine] - Failed to execute rule 'StepByStep-dac8a7cc-5976-41fb-b872-b0d1804c8da8': Failed to execute action '1': null

@juelicher

I might have an idea what’s going on. Have a look at the last step in installing lucid. (Create an openHAB item named ZZZ_Test_Reload_Finished) I just added it. Does that help?

Thanks a lot, that did the trick, after adding the item it works as expected.

Sorry, somehow I must have overlocked that part of the installation instructions!

No you didn’t. I added it afterwards to the installation instructions. I’m happy that it works now! :rofl:

Hello! Did you make it?

Your script may look similar to this now:

from lucid.rules import rule, addRule # Needed for defining and adding a rule
from lucid.triggers import ItemStateChangeTrigger, CronTrigger # Import the different type of triggers that you will be using
from lucid.utils import getItemValue

@rule
class StepByStep(object): # Giving the class a unique name
    def getEventTriggers(self):
        return [
            ItemStateChangeTrigger('My_TestSwitch_1'), # Triggering when the switch changes its state.
            ItemStateChangeTrigger('My_TestSwitch_2', ON), # Only trigger when switch turns on
            CronTrigger('0 0/2 * 1/1 * ? *'), # Runs every other minute
        ]

    def execute(self, modules, inputs):
        self.log.setLevel(DEBUG)

        if self.event.isItem:
            self.log.debug('One of the test switches has changed its state')
            self.log.debug('Triggering item name: \'' + unicode(self.event.itemName) + '\', state: ' + str(self.event.state))

            # Only set Test_String_1 to "CAT" when both switches are ON and only when an item trigger has occurred. (Not at cron events)
            bothSwitchesAreOn = ((getItemValue("My_TestSwitch_1", OFF) == ON) and (getItemValue("My_TestSwitch_2", OFF) == ON))
            if bothSwitchesAreOn:
                events.postUpdate("Test_String_1", "CAT")
                self.log.debug('CAT!!!')

        elif event.isCron:
            self.log.debug('A cron event occurred')
            events.postUpdate("Test_String_1", "DOG")

addRule(StepByStep()) # Needed to add the rule, use the same name as defined for the class

We add a second function to our script that triggers upon the update and give it a unique rule name. We use a predefined “constant” for the cron expression and we import yet another trigger type from lucid.triggers named ItemStateUpdateTrigger. As you know, an item update is not the same as an item change. In case you need a recap.

Update your scripts to this:

from lucid.rules import rule, addRule # Needed for defining and adding a rule
from lucid.triggers import ItemStateChangeTrigger, ItemStateUpdateTrigger, CronTrigger # Import the different type of triggers that you will be using
from lucid.utils import getItemValue

@rule
class StepByStep(object): # Giving the class a unique name
    def getEventTriggers(self):
        return [
            ItemStateChangeTrigger('My_TestSwitch_1'), # Triggering when the switch changes its state.
            ItemStateChangeTrigger('My_TestSwitch_2', ON), # Only trigger when switch turns on
            CronTrigger(EVERY_30_SECONDS), # Using a cron expression constant
        ]

    def execute(self, modules, inputs):
        self.log.setLevel(DEBUG)

        if self.event.isItem:
            self.log.debug('One of the test switches has changed its state')
            self.log.debug('Triggering item name: \'' + unicode(self.event.itemName) + '\', state: ' + str(self.event.state))

            # Only set Test_String_1 to "CAT" when both switches are ON and only when an item trigger has occurred. (Not at cron events)
            bothSwitchesAreOn = ((getItemValue("My_TestSwitch_1", OFF) == ON) and (getItemValue("My_TestSwitch_2", OFF) == ON))
            if bothSwitchesAreOn:
                events.postUpdate("Test_String_1", "CAT")
                self.log.debug('CAT!!!')

        elif event.isCron:
            self.log.debug('A cron event occurred')
            events.postUpdate("Test_String_1", "DOG")

addRule(StepByStep()) # Needed to add the rule, use the same name as defined for the class

@rule
class WatchMyString(object): # Giving the class a unique name
    def getEventTriggers(self):
        return [ItemStateUpdateTrigger('Test_String_1')] # Triggering when the Test_String_1 items updates
    def execute(self, modules, inputs):
        self.log.setLevel(DEBUG)
        self.log.debug('Item: \'' + unicode(self.event.itemName) + '\', was updated')

addRule(WatchMyString()) # Needed to add the rule, use the same name as defined for the class

After you’ve saved the new script, you’ll notice in the log that Test_String_1 gets an update every 30 seconds!

We don’t want that to happen.

When we send an update to the Test_String_1 in the first rule, we’d like to check first if the new value differs from the value stored so that the second rule only triggers whenever there is an actual change.

It can be done by inspecting the old value together with an if statement. However we will use another handy function in lucid.utils named postUpdateCheckFirst.

# Checks if the current state of the item is different than the desired new state.
# If the target state is the same, no update is posted.

Use it like this: postUpdateCheckFirst('Item', 'New Value')

Your job is to modify the script to do that.

Is there the equivalent for sendCommand? I’ve often wished the Rules DSL had this.

Sure there is.

sendCommandCheckFirst(itemName, newValue):

:grin:

Hello! How is your lucid jython scripting going?

By now, you’ve added postUpdateCheckFirst to the lucid.utils import line.
Then you replaced the line events.postUpdate("Test_String_1", "CAT") with postUpdateCheckFirst('Test_String_1', 'CAT')

Your script should look like this now:

from lucid.rules import rule, addRule # Needed for defining and adding a rule
from lucid.triggers import ItemStateChangeTrigger, ItemStateUpdateTrigger, CronTrigger # Import the different type of triggers that you will be using
from lucid.utils import getEvent, getItemValue, postUpdateCheckFirst

@rule
class StepByStep(object): # Giving the class a unique name
    def getEventTriggers(self):
        return [
            ItemStateChangeTrigger('My_TestSwitch_1'), # Triggering when the switch changes its state.
            ItemStateChangeTrigger('My_TestSwitch_2', ON), # Only trigger when switch turns ON
            CronTrigger(EVERY_30_SECONDS), # Using a cron expression constant
        ]

    def execute(self, modules, inputs):
        self.log.setLevel(DEBUG)
        event = self.event # Now you can use event instead of self.event below

        if event.isItem:
            self.log.debug('One of the test switches has changed its state')
            self.log.debug('Triggering item name: \'' + unicode(event.itemName) + '\', state: ' + str(event.state))

            # Only set Test_String_1 to "CAT" when both switches are "ON" and only when an item trigger has occurred. (Not at cron events)
            bothSwitchesAreOn = ((getItemValue("My_TestSwitch_1", OFF) == ON) and (getItemValue("My_TestSwitch_2", OFF) == ON))
            if bothSwitchesAreOn:
                postUpdateCheckFirst('Test_String_1', 'CAT')
                self.log.debug('CAT!!!')

        elif event.isCron:
            self.log.debug('A cron event occurred')
            postUpdateCheckFirst('Test_String_1', 'DOG')

addRule(StepByStep()) # Needed to add the rule, use the same name as defined for the class

@rule
class WatchMyString(object): # Giving the class a unique name
    def getEventTriggers(self):
        return [ItemStateUpdateTrigger('Test_String_1')] # Triggering when the Test_String_1 items updates
    def execute(self, modules, inputs):
        self.log.setLevel(DEBUG)
        self.log.debug('Item: \'' + unicode(self.event.itemName) + '\', was updated')

addRule(WatchMyString()) # Needed to add the rule, use the same name as defined for the class

By watching the log output, you’ll find that the Test_String_1 doesn’t get “touched” by an update unless the new value is different from the old one.

Unless you have any questions, I think I’ll end here for this time.
I will continue with adding some more examples for lucid. Studying them and maybe modify them is aslo a great way to learn

Thanks for joining this tutorial.

Cheers!

EDIT: Scripts in the tutorial is modified to work with lucid v1.0.0