Rule for batch processing of input/outputs

Hi everyone.

Couldn’t find something similar in sample rules WIKI.

I have bunch of inputs(wall buttons) and corresponding outputs(Lights) - approx 30 items.

Switch MCP23017_LED0 "MCP_LED_A0" (FF_Corridor, Lights) { mcp23017="{ address:20, pin:'A0', mode:'DIGITAL_OUTPUT', defaultState:'HIGH'}" }
...
// A1
// A2
....
// An

Contact MCP23017Contact_B0 "MCP23017 Contact B0" (FF_Corridor, Lights) { mcp23017="{ address:20, pin:'B0', mode:'DIGITAL_INPUT'}" }

// B1
// B2
// ....
// Bn

I need to write some single generic rule,

without duplicating single rule 30 times,

like:
for each pair of (A[i],B[i]) where i = [0…n], n =30

rule "MCP23017 B[i] Changed"
when
	Item MCP23017Contact_B[i] changed
then
	if (MCP23017Contact_B[i].state == OPEN) {
		sendCommand(MCP23017_LED[i], ON)
	} 
	else {
		sendCommand(MCP23017_LED[i], OFF)
	}
	logInfo("MCP23017Contact_B[i]", "MCP23017Contact_B[i] State " + MCP23017Contact_B[i].state)
end

How to implement this?

First of all, your rule as written is not possible. There are no arrays of Items you can reference like that and you certainly cannot trigger rules like that.

The simplest approach:

Here you will still have your 30 rules but the “logic” for each will be in a single and separately callable lambda.

The less simple approach which “might” work depending on timing is to use Groups:

Pay particular attention to triggering the rule with a Group and the sorting by lastUpdate to get access to the Item that triggered the rule.

If you choose to trigger the rule by the Group, be aware that your Group must have a type and function or else the Group will not receive updates. For example:

Group:Switch:OR(ON,OFF) gMCP23017

will work and

Group gMCP23017

will not.

Thanks for reply, Great articles I need time to digest this before proceed further.

rlkoshak, Looks like this part works well, but I have issue with persistence,
I did all steps as you described in article.
Created mapdb.persist.

Strategies {
        default = everyUpdate
}

Items {
        // persist all items on every change and restore them from the db at startup
        * : strategy = everyChange, restoreOnStartup
}

Rule looks like:

rule "Wall Button State Changed"
    when
        Item MCP23017Contact_B0 changed or
        Item MCP23017Contact_B1 changed or
        Item MCP23017Contact_B2 changed or
        Item MCP23017Contact_B3 changed or
        Item MCP23017Contact_B4 changed or
        Item MCP23017Contact_B5 changed or
        Item MCP23017Contact_B6 changed or
        Item MCP23017Contact_B7 changed
then
    Thread::sleep(50) // give persistence time to catch up

    val wall_switch = gWallButtons.members.filter[s|s.lastUpdate("mapdb") != null].sortBy[lastUpdate("mapdb")].last as ContactItem


    val lamp = gLights.members.filter[t | t.name == wall_switch.name+"_LED"].head as SwitchItem
    if (wall_switch.state == OPEN) sendCommand(lamp, ON) else sendCommand(lamp, OFF)
end

I see that Paper UI state restores well!

But actual physical signal on my pin isn’t restored.

Item file:

Group:Contact:OR(OPEN, CLOSED)  gWallButtons     "Turn-on Light [(%d)]"              <contact>

I tried to do something like, but this doesn’t work:

rule "Initialize contact states"
    when
        System started
    then

        gWallButtons?.members.forEach(member|
             postUpdate(member, member.historicState(now.minusSeconds(30)).state)
        )
        
end
  • is not a wildcard in that usage. It means persist “all members of this group”.
    Try
    gWallButtons* : strategy = everyChange, restoreOnStartup

As I understood

* : strategy

capture all items of all groups, so my script correct as well.

Isn’t it?

That is what the docs say yes, looks like I am wrong there.

Looking again at your complaint -

You want the hardware outputs to be restored? You need to send commands, not updates.

Looks like that.
How exactly perfrorm restore?

That is BasicUI, not PaperUI.

That isn’t how persistence works. Persistence only updates the state of the Items within openHAB. It does not go out through the bindings to the devices.

You have to write a System started rule to sendCommand the restored state to the Items for the command to go out to the GPIO pins.

You need to use sendCommand. postUpdate only updates the state of the Item in openHAB. You use sendCommand to trigger the binding to send the command out to the device.

Assuming restoreOnStartup is working fast enough, you can use:

gWallButtons.members.forEach[member |
    member.sendCommand(member.state)
]

What to do if not?

2017-07-14 18:43:38.219 [ERROR] [ntime.internal.engine.RuleEngineImpl] - 
Error during the execution of startup rule 'Initialize contact states': Could not invoke method: 
org.eclipse.smarthome.model.script.actions.BusEvent.sendCommand(org.eclipse.smarthome.core.items.Item,java.lang.String) on instance: null
rule "Initialize contact states"
    when
        System started
    then
    gWallButtons.members.forEach(member |
        member.sendCommand(member.state)
    )
        
end

Continue to do it as you are using historicState.

Do you mean

gWallButtons.members.forEach(member |
        member.sendCommand(member.historicState(now.minusSeconds(30)).state)
    )

Looks like member in lamba gets null

I had a similar issue (Could not invoke method […] sendCommand […] on instance: null) and @rlkoshak helped me to fix it with .state.toString:

gWallButtons.members.forEach(member |
        member.sendCommand(member.historicState(now.minusSeconds(30)).state.toString)
    )

rlkoshak,

I found that approach Persist Contacts which you described, isn’t perfect for me.
I need rather persist actual state of Switch, because I can change Switch state from two different places
from PaperUI of from hardware buttons.

If I persist hardware buttons, i.e. Contacts ONLY
OH doesn’t restore light state correctly, it doesn’t take into account clicks which were made via PaperGUI.

I.e. after OH started if I control Switch ONLY from GUI, it won’t be restored! As soon as I store to mapdb only hardware inputs rather that actual state of Switch.

What can you suggest ?

Read a different group then, change the “Initialize contact states” rule into a “Initialize switch states” by changing the group that it reads. You may need to make a gtoup of your switches, you may need to make sure switches are persisted and restored.

I’m afraid I really do not understand your question.

From a persistence perspective, there is no difference between a Contact and a Switch. If you have both Contacts and Switches then put the Contacts into another Group and loop through that Group in your System started rule just like you are for the Switches.

Assuming you really do mean PaperUI and not BasicUI (you mistakenly called BasicUI PaperUI above in your screenshot) then depending on the Binding, sending command straight to the Things through PaperUI may not be reflected in the Item states. Thus no updates get saved to the database so there is nothing to restore.

If you are wanting a situation where OH goes down and then you change the state of a light through some other means then when OH comes back up it doesn’t match, I can’t help with that. restoreOnStartup will only be able to handle restoring from historic data.

No I don’t need this.

What I need is persist most recent change of light state, whether it was through BASIC UI or through hardware Contact.

keep fighting with this:
I can’t figure out how to do null pointer check properly inside forEach lambda loop?

[INFO ] [.eclipse.smarthome.model.script.LAMP] - MCP23017Contact_B0_LED OFF
[INFO ] [.eclipse.smarthome.model.script.LAMP] - MCP23017Contact_B1_LED NULL
[INFO ] [.eclipse.smarthome.model.script.LAMP] - MCP23017Contact_B2_LED NULL
[INFO ] [.eclipse.smarthome.model.script.LAMP] - MCP23017Contact_B3_LED NULL
[INFO ] [.eclipse.smarthome.model.script.LAMP] - MCP23017Contact_B4_LED OFF
[INFO ] [.eclipse.smarthome.model.script.LAMP] - MCP23017Contact_B5_LED NULL
[INFO ] [.eclipse.smarthome.model.script.LAMP] - MCP23017Contact_B6_LED NULL
[INFO ] [.eclipse.smarthome.model.script.LAMP] - MCP23017Contact_B7_LED NULL
[ERROR] [ntime.internal.engine.RuleEngineImpl] - 
Error during the execution of startup rule 'Initialize switch states': The argument 'command' must not be null or empty.
[INFO ] [se.smarthome.model.script.SwitchLED0] - OFF

It successfully update my first LED but then stucked on second NULL pointer and forEach loop breaks immediately without updating LED4.
How to fix this?

Here is my rule:


rule "Initialize switch states"
    when
        System started
    then
        gLights?.members.forEach(lamp | 
            logInfo("LAMP", lamp.name + " " + lamp.state.toString)
        )
        gLights?.members.filter[lamp| lamp != null && lamp.state != null].forEach(lamp | 
            lamp.sendCommand(lamp.state.toString)
        )
end

Maybe not the best syntax, but try:

rule "Initialize switch states"
when
	System started
then
	gLights?.members.forEach[lamp | 
		logInfo("LAMP", lamp.name + " " + lamp.state.toString)
	]
	gLights?.members.forEach[lamp |
		if (lamp.state != NULL) {
			lamp.sendCommand(lamp.state.toString)
		}
	]
end

or if (lamp.state.toString != NULL) :slight_smile:
note: I haven’t tested this… I wrote it fast using some other examples that I have found…
If it works… you can combine the first forEach with the second into one

Great! Thanks, this works.

my bet is that I tried this as well!

By the way, this language is case senstive or not??

!= null is the same as != NULL ???