Use Array-Items and one Rule

Hello,
i want to reduce the code of same kind of items and use one rule for that.
actually i using a powerstrip with 6 ports with one rule for a port
its possible to use one rule with variables for all ?

Here an example with 2 Ports:

rule "Steckdose1"
    when
        Item gPowerStrip1Port1 received command
    then
        switch(receivedCommand) {
		
            	case ON :{
			var poweron = URLEncoder::encode('{"port":1,"state":1}','UTF-8');		
			sendHttpGetRequest("http://192.168.22.12/?cmd=200&json=" + poweron);
			logInfo("Steckdose1", "eingeschaltet");
			}
		        	
		case OFF:
			{
			var poweroff = URLEncoder::encode('{"port":1,"state":0}','UTF-8');
			sendHttpGetRequest("http://192.168.22.12/?cmd=200&json=" + poweroff);
			logInfo("Steckdose1", "ausgeschaltet");
			
			}
		}
end
rule "Steckdose2"
    when
        Item gPowerStrip1Port2 received command
    then
        switch(receivedCommand) {
		
            	case ON :{
			var poweron = URLEncoder::encode('{"port":2,"state":1}','UTF-8');		
			sendHttpGetRequest("http://192.168.22.12/?cmd=200&json=" + poweron);
			logInfo("Steckdose2", "eingeschaltet");
			}
		        	
		case OFF:
			{
			var poweroff = URLEncoder::encode('{"port":2,"state":0}','UTF-8');
			sendHttpGetRequest("http://192.168.22.12/?cmd=200&json=" + poweroff);
			logInfo("Steckdose2", "ausgeschaltet");
			
			}
		}
end

the same for cron job:

rule "Request data from Powerstrip"
	when
		Time cron "0/10 * * * * ?" 
	then

		var String json = sendHttpGetRequest("http://192.168.22.12/?cmd=511")

		val String  PowerStrip1Port1Status  = transform("JSONPATH", "$.data.switch[0]", json)
		val String  PowerStrip1Port2Status  = transform("JSONPATH", "$.data.switch[1]", json)
		
		val DecimalType PowerStrip1Port1Watt  = new DecimalType(transform("JSONPATH", "$.data.watt[0]", json))
		val DecimalType PowerStrip1Port2Watt  = new DecimalType(transform("JSONPATH", "$.data.watt[1]", json))


		//logInfo("Powerstatus", PowerStrip1Port1Status );
		switch(PowerStrip1Port1Status) {
			case "1" : postUpdate(gPowerStrip1Port1, ON)
			case "0" : postUpdate(gPowerStrip1Port1, OFF)
		}
		switch(PowerStrip1Port2Status) {
			case "1" : postUpdate(gPowerStrip1Port2, ON)
			case "0" : postUpdate(gPowerStrip1Port2, OFF)
		}

		postUpdate(gPowerStrip1Port1Watt,(PowerStrip1Port1Watt/1000))
		postUpdate(gPowerStrip1Port2Watt,(PowerStrip1Port2Watt/1000))


end

What you could do as a first step is to implement your case blocks to a reusable lambda.
Searching the forum for lambdas, you will find many examples on how to do this.

Here is a good tutorial on lambdas: Reusable Functions: A simple lambda example with copious notes.

But I don’t think you need one in this case. To make things just a little easier, change your naming conventions to use some underscores (e.g. gPowerString_1_Port_1).

Next, add each Item as a trigger to a single rule. New in OH 2.2. is a variable called triggeringItem which will contain the Item that triggered the Rule.

Then we can use some string manipulation on the name of the Item that triggered the rule to get the port and state for your URL.

I’ll use a MAP to provide mapping between the strip number and its IP address (not shown, see https://docs.openhab.org/addons/transformations/map/readme.html)

With a few other minor changes your rules could collapse to:

rule "Steckdose"
when
    Item gPowerStrip_1_Port_1 received command or
    Item gPowerStrip_1_Port_2 received command or
    ...
    Item gPowerStrip_2_Port_1 received command or
    Item gPowerStrip_2_Port_2 received command or
    ...
then
    val strip = triggeringItem.name.split("_").get(1) 
    val ip = transform("MAP", "steckdose.map", strip)

    val port = triggeringItem.name.split("_").get(3)
    val cmd = if(receivedCommand == ON) "1" else "0"
    val encoded = URLEncoder::encode('{"port":'+port+', "state":'+cmd+'}', 'UTF-8')

    sendHttpGetRequest("http://"+ip+"/?cmd=200&json="+encoded)
    logInfo("Steckdose", "eingeschalet")
end

For the second rule I would use the HTTP binding with the caching config. Then create Items for the status and watt values. And then you just need a couple of simple rules.

rule "Status"
when
    Item PowerStrip_1_Port_1_Status changes or
    Item PowerStrip_1_Port_2_Status changes or
    ...
    Item PowerStrip_2_Port_1_Status changes or
    Item PowerStrip_2_Port_2_Status changes or
    ...
then
    val gItemName = triggeringItem.name.replace("_Status","")
    val status = if(triggeringItem.state.toString == "1") "ON" else "OFF"
    postUpdate(gItemName, status)
end

rule "Watt"
when
    Item PowerStrip_1_Port_1_Watt changes or
    Item PowerStrip_1_Port_2_Watt changes or
    ...
    Item PowerStrip_2_Port_1_Watt changes or
    Item PowerStrip_2_Port_2_Watt changes or
    ...
then
    val watt = (triggeringItem.state as Number)/1000
    postUpdate("g"+triggeringItem.name, watt)
end

When you move to 2.3, you will be able to get rid of all of those triggers on the rules as well. Features for Rules that work with Groups There is a new Member of trigger for Rules so if you put all your Items into a Group you can trigger the rule on that Group and triggeringItem will be the member of the Group that triggered the Rule.

NOTE: the above examples are the only times when it is appropriate to use the postUpdate actions. You should use the method (e.g. gPowerString1Port1.postUpdate(ON)) in all other cases. See https://docs.openhab.org/configuration/rules-dsl.html#sendcommand-method-vs-action.

Finally, it would be possible to remove the last watts rule above and use the JS transform to extract the value and divide by 1000 in the transform. Or you could keep the value large and use the JS transform to divide by 1000 in the label.

You also might be able to do away with the status change rule as well by adding the HTTP binding config to the gPowerString Item itself. The HTTP binding will update the state of the Item and you can use the rule above to command it.

It also occurs to me that you may be able to do away with the first rule as well using the outgoing config.

powerStrip1.url=http://192.168.22.12/?cmd=511
powerStrip1.updateInterval=10000 # every ten seconds

powerStrip2.url=http://192.168.22.13/?cmd=511
powerStrip2.updateInterval=10000
Switch gPowerStrip1Port1 { http="<[powerStrip1:10000:JSONPATH($.data.switch[0])] 
    >[ON:GET:http://192.168.22.12/?cmd=200&json=%7B%22port%22%3A1%2C%22state%22%3A1%7D] 
    >[OFF:GET:http://192.168/22/12?cmd=200&json=%7B%22port%22%3A1%2C%22state%22%3A0%7D]" }
Number gPowerStrip1Port1Watt "Watts [JS(watts.js): %d]" { http="<[powerStrip1:1000:JSONPATH($.data.watt[0])]" }

watt.js

(function(1) {
    if(isNaN(i) return -1
    return i / 1000
})(input)

In the above the HTTP caching config pulls the data down every ten seconds. The gPowerStrip1Port1 has three HTTP clauses. The first polls the cached data pulled by the binding every ten seconds, extracts the status and updates gPowerStrip1Port1 with the status. NOTE: this might not work if Switch can’t handle converting a “1” to ON and “0” to OFF in which case we would probably need a JS transform or the Rule above. The other two clauses causes the binding to issue a GET command to the supplied URL when it receives the ON or OFF command. Notice there are two clauses, one for ON and one for OFF.

For the Watts Item I use the cached data and I only divide the value by 1000 in the label. So the Item keeps the full value but shows it divided by 1000 on the UI.

3 Likes

Wow… thanks for this detailed help.
I already searched for the caching function and didnt found the right solution. so i will try it with your details.
The powerstrip is very dumb and allows only fixed commands. so thats why i need transforms.

Your solution about mapping sounds great. i will check it out.

Regards, Web

https://docs.openhab.org/addons/bindings/http1/readme.html#example-of-how-to-configure-an-http-cache-item

this is was i found too. but not a solution for json.

Actually http refresh service do not working and displays follow error:

2018-03-13 19:20:18.538 [ERROR] [b.core.service.AbstractActiveService] - Error while executing background thread HTTP Refresh Service
java.lang.NullPointerException: null
        at java.util.regex.Matcher.getTextLength(Matcher.java:1283) [?:?]
        at java.util.regex.Matcher.reset(Matcher.java:309) [?:?]
        at java.util.regex.Matcher.<init>(Matcher.java:229) [?:?]
        at java.util.regex.Pattern.matcher(Pattern.java:1093) [?:?]
        at org.openhab.io.net.http.HttpUtil.extractCredentials(HttpUtil.java:278) [209:org.openhab.core.compat1x:2.2.0]
        at org.openhab.io.net.http.HttpUtil.executeUrl(HttpUtil.java:181) [209:org.openhab.core.compat1x:2.2.0]
        at org.openhab.io.net.http.HttpUtil.executeUrl(HttpUtil.java:130) [209:org.openhab.core.compat1x:2.2.0]
        at org.openhab.binding.http.internal.HttpBinding.getCacheData(HttpBinding.java:423) [223:org.openhab.binding.http:1.11.0]
        at org.openhab.binding.http.internal.HttpBinding.execute(HttpBinding.java:169) [223:org.openhab.binding.http:1.11.0]
        at org.openhab.core.binding.AbstractActiveBinding$BindingActiveService.execute(AbstractActiveBinding.java:144) [209:org.openhab.core.compat1x:2.2.0]
        at org.openhab.core.service.AbstractActiveService$RefreshThread.run(AbstractActiveService.java:166) [209:org.openhab.core.compat1x:2.2.0]

2018-03-13 19:20:28.690 [INFO ] [b.core.service.AbstractActiveService] - HTTP Refresh Service has been shut down

this is the used switchitem:

Switch gPowerStrip1Port1 { http="<[powerStrip1:10000:JSONPATH($.data.switch[0])] >[ON:GET:http://192.168.22.12/?cmd=200&json=%7B%22port%22%3A1%2C%22state%22%3A1%7D] >[OFF:GET:http://192.168.22.12?cmd=200&json=%7B%22port%22%3A1%2C%22state%22%3A0%7D]" }

The Switch Changes with Mapping table works fine. only the trigger with http service make some problems.

I think the missing transform from 1 to on is the reason or?

I’m actively using the HTTP cache config with JSON so I know it works.

The problem could be OH can’t handle the 1/0 for updates to a Switch Item. You will probably need the rule for this.

Do you can help me to write this rule?

Its possible to map the switch to the rule or do i need the cron job?

I basically already wrote it for you above. I wrote both the Rules and the HTTP config.

To deal with the problem on the Switch, just use the Steckdose rule above.

the steckdose rule works only if i toggle the switch manually
but to verify the current state, i need a cronjob who get the informations from powerstrip.

so i’m not sure if this rule trigger is correct for reading informations from powerstrip

I copied the name of the wrong rule. You need the Status rule. The outgoing HTTP configs (i.e. the ones that start with >) replace the Steckdose rule.

You don’t need a cron rule because the powerStrip1 config on the HTTP binding already queries the URL every ten seconds. You just need a rule that triggers when the status changes.

powerStrip1.url=http://192.168.22.12/?cmd=511
powerStrip1.updateInterval=10000 # every ten seconds

powerStrip2.url=http://192.168.22.13/?cmd=511
powerStrip2.updateInterval=10000
Switch gPowerStrip_1_Port_1 { http=">[ON:GET:http://192.168.22.12/?cmd=200&json=%7B%22port%22%3A1%2C%22state%22%3A1%7D] 
    >[OFF:GET:http://192.168/22/12?cmd=200&json=%7B%22port%22%3A1%2C%22state%22%3A0%7D]" }

String PowerStrip_1_Port_1_Status {http="<[powerStrip1:10000:JSONPATH($.data.switch[0])]" }

Number gPowerStrip1Port1Watt "Watts [JS(watts.js): %d]" { http="<[powerStrip1:1000:JSONPATH($.data.watt[0])]" }
rule "Status"
when
    Item PowerStrip_1_Port_1_Status changes or
    Item PowerStrip_1_Port_2_Status changes or
    ...
    Item PowerStrip_2_Port_1_Status changes or
    Item PowerStrip_2_Port_2_Status changes or
    ...
then
    val gItemName = triggeringItem.name.replace("_Status","")
    val status = if(triggeringItem.state.toString == "1") "ON" else "OFF"
    postUpdate(gItemName, status)
end

There are only three changes in the copy and paste from my posting from above.

  1. I changed the name of the Switch Item to add the “_”
  2. I added the PowerStrip_1_Port_1_Status Item
  3. Moved the http incoming config from gPowerStrip1Port1 to the new Item

Hm i tried it with Status Item too but actually i got the same issue with http refresh service:

2018-03-13 23:40:36.537 [ERROR] [b.core.service.AbstractActiveService] - Error while executing background thread HTTP Refresh Service
java.lang.NullPointerException: null
    at java.util.regex.Matcher.getTextLength(Matcher.java:1283) [?:?]
    at java.util.regex.Matcher.reset(Matcher.java:309) [?:?]
    at java.util.regex.Matcher.<init>(Matcher.java:229) [?:?]
    at java.util.regex.Pattern.matcher(Pattern.java:1093) [?:?]
    at org.openhab.io.net.http.HttpUtil.extractCredentials(HttpUtil.java:278) [209:org.openhab.core.compat1x:2.2.0]
    at org.openhab.io.net.http.HttpUtil.executeUrl(HttpUtil.java:181) [209:org.openhab.core.compat1x:2.2.0]
    at org.openhab.io.net.http.HttpUtil.executeUrl(HttpUtil.java:130) [209:org.openhab.core.compat1x:2.2.0]
    at org.openhab.binding.http.internal.HttpBinding.getCacheData(HttpBinding.java:423) [223:org.openhab.binding.http:1.11.0]
    at org.openhab.binding.http.internal.HttpBinding.execute(HttpBinding.java:169) [223:org.openhab.binding.http:1.11.0]
    at org.openhab.core.binding.AbstractActiveBinding$BindingActiveService.execute(AbstractActiveBinding.java:144) [209:org.openhab.core.compat1x:2.2.0]
    at org.openhab.core.service.AbstractActiveService$RefreshThread.run(AbstractActiveService.java:166) [209:org.openhab.core.compat1x:2.2.0]
2018-03-13 23:40:41.558 [ERROR] [b.core.service.AbstractActiveService] - Error while executing background thread HTTP Refresh Service

if i use only String PowerStrip_1_Port_1_Status, i got the same error.

Now i found the reason. but don’t know why.

after every change in http.cfg i got follow error:

==> /var/log/openhab2/openhab.log <==
2018-03-14 12:45:01.736 [ERROR] [org.apache.felix.configadmin ] - [org.osgi.service.cm.ManagedService, org.osgi.service.event.EventHandler, id=311, bundle=230/mvn:org.openhab.binding/org.openhab.binding.http/1.11.0]: Unexpected problem updating configuration org.openhab.http
java.lang.NumberFormatException: For input string: “10000 # every ten seconds”
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[?:?]
at java.lang.Integer.parseInt(Integer.java:580) [?:?]
at java.lang.Integer.valueOf(Integer.java:766) [?:?]
at org.openhab.binding.http.internal.HttpBinding.updated(HttpBinding.java:507) [230:org.openhab.binding.http:1.11.0]
at org.apache.felix.cm.impl.helper.ManagedServiceTracker.updated(ManagedServiceTracker.java:189) [7:org.apache.felix.configadmin:1.8.16]
at org.apache.felix.cm.impl.helper.ManagedServiceTracker.updateService(ManagedServiceTracker.java:152) [7:org.apache.felix.configadmin:1.8.16]
at org.apache.felix.cm.impl.helper.ManagedServiceTracker.provideConfiguration(ManagedServiceTracker.java:85) [7:org.apache.felix.configadmin:1.8.16]
at org.apache.felix.cm.impl.ConfigurationManager$UpdateConfiguration.run(ConfigurationManager.java:1792) [7:org.apache.felix.configadmin:1.8.16]
at org.apache.felix.cm.impl.UpdateThread.run0(UpdateThread.java:141) [7:org.apache.felix.configadmin:1.8.16]
at org.apache.felix.cm.impl.UpdateThread.run(UpdateThread.java:109) [7:org.apache.felix.configadmin:1.8.16]
at java.lang.Thread.run(Thread.java:748) [?:?]

Thats the content of this file (i change the cache name for testing):

# timeout in milliseconds for the http requests (optional, defaults to 5000)
#timeout=

# the interval in milliseconds when to find new refresh candidates
# (optional, defaults to 1000)
#granularity=

# whether to substitute the current time or state value into the URL
# (optional, defaults to true)
#format=

# configuration of the first cache item
#<id1>.url=
#<id1>.updateInterval=

# configuration of the second cache item  
#<id2>.url=
#<id2>.updateInterval=

PS1.url=http://192.168.22.12/?cmd=511
PS1.updateInterval=6000

now i don’t know why the error message contains: java.lang.NumberFormatException: For input string: “10000 # every ten seconds”

this was an content of the old file and i remove the comment… i’m not sure where OH get this content.
I already reinstalled http.binding and restarted the server. but no luck.

I’m guessing the config file cannot handle that comment. Remove the “# every ten seconds” from http.cfg.

Please use code fences for log entries as well as code: How to use code fences.

There is a bug in OH where when you remove fields from .cfg files they don’t actually go away permanently. See MQTT config under OH2 only delete http.conf instead of mqtt.conf.

However, since you are using PS1 for your cache configuration name you must use PS1 in your Items as well.

Hi Rich,

Can transformations be nested? I which case he could MAP transform the JSONPATH transform.

Sadly no. Were I king for a day and/or had the time to code on OH, I would strip out the transform service so it is not dependent on the bindings (i.e. we can use a transform on any binding) and be able to chain them like you suggest.

As a work around though, if you have to do something complex like that, you should consider the JS transform which lets you write a JavaScript function to transform the data. That can usually get you past most of these sorts of snags.

Thanks Rich,
I think I am not wrong in saying that the community would gladly crown you king the day.
I was aware of the JS options but that would have been neat.
I hope you get the time to get to code this one day.

Thanks

Actual State:
after i deleted the http.config in/var/lib/openhab2/config/org/openhab cache modul now working.
But now i have an issue with reading Number items and postupdate not working.

cache modul detect if i toggle the switch at power strip directly:

item:
Number PowerStrip_1_Port_1_Status {http="<[PowerStrip1:2000:JSONPATH($.data.switch[0])]" }

Log:
2018-03-14 16:10:19.113 [vent.ItemStateChangedEvent] - PowerStrip_1_Port_1_Status changed from 0 to 1

but i can’t read the Item to transform from 0:1 to on:off
It issnt possible to using the State attribute. but if i get all informations about this item, i get follow output:
PowerStrip_1_Port_1_Status (Type=NumberItem, State=1, Label=null, Category=null)

But it issnt possible to use this rule code:

val status = if(PowerStrip_1_Port_1_Status == "1") "ON" else "OFF"

every time i get “off” . i tried it already without " "

Any ideas whats wrong?

Regards, Web

Edit:

i enabled follow rule with this result:

rule "Status"
	when
  		 Item PowerStrip_1_Port_1_Status changed or
  		 Item PowerStrip_1_Port_2_Status changed or
  		 Item PowerStrip_1_Port_3_Status changed or
  		 Item PowerStrip_1_Port_4_Status changed or
  		 Item PowerStrip_1_Port_5_Status changed or
  		 Item PowerStrip_1_Port_6_Status changed
	then
  		val gItemName = triggeringItem.name.replace("_Status","")
   		val status = if(triggeringItem.state == 1) "ON" else "OFF"
		   logWarn("StartPostUpdate",gItemName)
   		postUpdate(gItemName, ON)
end

2018-03-14 16:15:24.583 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Status': An error occurred during the script execution: Could not invoke method: org.eclipse.smarthome.model.script.actions.BusEvent.postUpdate(java.lang.String,java.lang.String) on instance: null

That’s a typo. Two actually.

val status = if(PowerStrip_1_Port_1_Status.state == 1) "ON" else "OFF"

Since the Item is a Number you need to remove the quotes around 1 (as you already found). And to get the state of an Item you must call .state.

What is gItemName? Does it exactly match the Item as you defined it in your .items file, including case?

Lets explain it with this code:

rule "Status"
	when
  		 Item PowerStrip_1_Port_1_Status changed or
  		 Item PowerStrip_1_Port_2_Status changed or
  		 Item PowerStrip_1_Port_3_Status changed or
  		 Item PowerStrip_1_Port_4_Status changed or
  		 Item PowerStrip_1_Port_5_Status changed or
  		 Item PowerStrip_1_Port_6_Status changed
	then
		//val status = if(PowerStrip_1_Port_1_Status.state == 1) "ON" else "OFF"
		
		val  gItemName = triggeringItem.name.replace("_Status","")
		val status = if(triggeringItem.state == 1) "ON" else "OFF"
		logWarn("StartPostUpdate",status)
   		//postUpdate(PowerStrip_1_Port_1,status)
end

Result:

==> /var/log/openhab2/events.log <==

[vent.ItemStateChangedEvent] - PowerStrip_1_Port_1_Status changed from 1 to 0

==> /var/log/openhab2/openhab.log <==

[WARN ] [arthome.model.script.StartPostUpdate] - OFF

==> /var/log/openhab2/events.log <==

[vent.ItemStateChangedEvent] - PowerStrip_1_Port_1_Status changed from 0 to 1

==> /var/log/openhab2/openhab.log <==

[WARN ] [arthome.model.script.StartPostUpdate] - ON

Script changes may work. but i dont see any postUpdate on BasicUI.

i see gItemName is still empty and triggeringItem.name.replace("_Status","") remove the Text _Status of itself.
maybe it issn’t possible to use variables as object / items.