[SOLVED] Error building string from group item members

Aargh :confounded:. Not the best way to start first post but two weeks trying to learn rule syntax has been an ‘exciting’ journey :grinning:. This forum has been a great help and I learned a lot, however, now I’m stuck.

I try to send a mail message building a string based on group member information. I have a system comprising three units (member of gUnitStatus). Each unit can has only three states: “OK”, “WARNING” or “FAILED”. System_Status is derived from ‘worst’ unit state.

I like to send an email when the system state changes to a non-OK state. The message should only contain the unit name(s) and the status when not in an OK state. My rule looks as follows:

rule "Send Email for System Status"
when
	Item System_Status changed
then
	var String message
	var String unitStatus = ""
	if (System_Status != "OK"){
 		gUnitStatus.members.forEach(x |
			if (x.state != "OK"){
				unitStatus = unitStatus.concat(x.name + " - " + x.state + "\n")
			}
		)
		message = ("Hello,\n\nThe following units have raised an alarm:\n" + unitStatus + "Regards, \nPeter")
		sendMail("xxxxxxxxxxxx", "Home Automation System Message", message)
	}
end

Unfortunately this gives the following error message: “Cannot refer to a non-final variable unitStatus from within a closure”. I have seen examples where it seems to work for others so I do not have a clue why it does not work for me. I have three questions:

  1. How do I need to change the syntax to make it work?
  2. Is there an easier way to achieve this? I played with filter() but forEach() seems more appropriate.
  3. How do I map the item name to something more understandable, e.g. where does a MAP statement fit?

So much to learn! I am glad it is a hobby.
Peter

  1. Make UnitStatus a StringBuilder and define it as final using “val” instead of var.

  2. ForEach is the right approach for this.

  3. You will have to build your own Map<String, String> using the item named as the key and your human readable name as the value. Search the examples for createHashMap fit examples of how to create and use a Map object.

Rich, thanks for the pointers; best way to learn! I have updated the code using the StringBuilder method. This has created another issue that I have spend an hour on trying to find the solution, but with no luck.

I get an error message that ‘x.name’ and ‘x.state’ are ‘String’ type and that an ‘AbstractStringBuilder type’ is expected. That’s weird as the .append method is overloaded and should accept a String. I have tried to use ‘toString’ to force it to String
 but the same message. No error for appending String items " - " and “\n”.

Peter

Updated rule:

rule "Send Email for System Status"
when
	Item System_Status changed
then
	var String message
	val StringBuilder unitStatus = new StringBuilder(64)
	if (System_Status != "OK"){
		gUnitStatus.members.forEach(x |
			if (x.state != "OK"){
				unitStatus.append(x.name).append(" - ").append(x.state).append("\n")
			}
		)
		message = ("Hello,\n\nThe following units have raised an alarm:\n" + unitStatus.toString() + "Regards, \nPeter")
		sendMail("xxxxxxx", "Home Automation System Message", message)
	}
end

New day, fresh brain.
Combining info from other forum messages I found a surprisingly simple solution for building the String! I also implemented a HashMap to make the Item Name more readable.

I tried with newHashMap() and then using put() statements to fill the map, but I had a problem that the created map Object (yy) was not recognised by the yy.put() or yy.get() statements. No idea why, but newLinkedHashMap() did work in the end so that’s what it will be.

Below the code for what I set out to do. Hopefully it helps others the way I was helped by this forum.

/* Create HashMap for sensor units */
var HashMap<String, String> unitName = newLinkedHashMap(
	Sensor_Living_1.name -> "Ceiling Living Room", 
	Sensor_Roaming_1.name -> "Mobile Sensor", 
	Sensor_Weather_1.name -> "Weather Information"
)

rule "Send Email for System Status"
when
	Item System_Status changed
then
	if (System_Status.state != "OK"){
		var unitStatus = gUnitStatus.members.filter(x| x.state != "OK").map[unitName.get(name) + ": " + state].join("\n")
		var message = ("Hello,\n\nThe following unit(s) have raised an alarm:\n" + unitStatus + "\n\nRegards, \nPeter")
		sendMail(mailTo, "Home Automation System Message", message)
	}
end
2 Likes