You either have to deal with the fact that the Rule gets triggered 9+ times for every update to a member (e.g. make it so what the rule does can be done multiple times in a row and get the same result, add locks to avoid multiple instances of the rule from running that the same time, etc) or you have to list all 10+ Items as rule triggers individually.
Or you can consider the JSR223 Rules which might have more feature to let you handle that a little more reasonably.
Hmmmm. This may be a side effect of the changes made to the way a Group’s state is calculated. Post your Group definition so I know exactly what we are dealing with.
rule "IFTTT lowercase string"
when
Item gLC received update
then
val changedswitch = gLC.members.sortBy[lastUpdate].last
val switchname = changedswitch.name.substring(0, changedswitch.name.lastIndexOf('_'))
if (changedswitch.previousState !== null){
sendCommand(switchname, changedswitch.state.toString.toUpperCase)
logInfo("IFTTT", switchname + " " + changedswitch.state.toString.toUpperCase )
}
end
Yes, the problem is your Group doesn’t have a Type. There was a change early on in the 2.2 snapshot I think, maybe earlier. All Groups must have a Type to receive an update.
Hi Guys,
i want to use this for my online/offline detection of some network components in my house and adjusted the items, groups and rules a bit. But i get this error and have not found anything similar so far:
2018-01-11 12:33:26.278 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘A Door Sensor Changed’: ‘name’ is not a member of ‘java.util.ArrayList’; line 30, column 16, length 13
Any idea? Do i need to load any library in the rule?
Hi Rich,
i simply copied over your code and modified it, but i guess one of the " " may have been a problem.
I wrote some lines, also line 30, complete fresh, so replaced the copy and now it seems to work. At least i get no error message anymore.
BTW:
In your code you have this line:
val door = gDoorSensors.members.filter[s|s.lastUpdate(“mapdb”) != null].sortBy[lastUpdate(“mapdb”)].last as ContactItem
The code for this DP was written long before that warning started being reported in the logs and I haven’t come back to all the DPs yet to update them for the latest 2.2 release. This is one of the errors you will find throughout the DPs. The other big one is the deprecation warnings when trying to use calendar.
Thanks for pointing it out though. It makes my work easier.
If you can tell me what line is line 30 in your .rules file I can fix the quotes problem as well. It’s really hard to find lines just by line number in a forum posting and I don’t know if you have extra lines or white space in your file which would push the count off.
Hi Rich,
glad i could help a little bit. Your tutorial helps me a lot more than i could give back
Ähm, line 30… i modified the code a lot but i tried to re-think and i am very sure it was this line:
val lastUpdate = gDoorsLast.members.filter[dt | dt.name == door.name + “_LastUpdate”].head as DateTimeItem
I modified your code to report me a few things. I did a version for
whenever a device comes online or goes offline, like my NAS, TV, Homematic CCU etc.
monitoring all my battery driven homematic devices
My next project would be a window open timer check. Means when a window got open run for 10 min, check again if still open and notify. If the Window got closed during the 10 Min do nothing. I hope i can get this to work, maybe i will post here a question for you if i do not get this working on my own
At the risk of veering off topic, here is my working code that does the window open alerts (doors in my case).
Group:Contact:OR(OPEN,CLOSED) gDoorSensors "The doors are [MAP(en.map):%s]"
<door>
Group:Number:SUM gDoorCounts "Open Doors [%d]" // this Item is not needed, if one uses %d on the lable for gDoorSenors it will print the count
<door>
Group:DateTime:MAX gDoorsLast "The last door event was [%1$tm/%1$td %1$tH:%1$tM]"
<time>
Group:Switch:OR(ON, OFF) gDoorsTimers
Contact vGarageOpener1 "Garage Door Opener 1 is [MAP(en.map):%s]"
<garagedoor> (gDoorSensors,gDoorCounts)
{ mqtt="<[mosquitto:entry_sensors/main/garage/door1:state:default]" }
Switch vGarageOpener1_Timer
(gDoorsTimers, gResetExpire)
{ expire="1h,command=OFF" }
DateTime vGarageOpener1_LastUpdate "Garage Door Opener 1 [%1$tm/%1$td %1$tH:%1$tM]"
<time> (gDoorsLast)
String GarageOpener1_Cmd
{ mqtt=">[mosquitto:entry_sensors/main/garage/door1/cmd:state:*:default]" }
Contact vGarageOpener2 "Garage Door Opener 2 is [MAP(en.map):%s]"
<garagedoor> (gDoorSensors, gDoorCounts)
{ mqtt="<[mosquitto:entry_sensors/main/garage/door2:state:default]" }
Switch vGarageOpener2_Timer
(gDoorsTimers, gResetExpire)
{ expire="1h,command=OFF" }
DateTime vGarageOpener2_LastUpdate "Garage Door Opener 2 [%1$tm/%1$td %1$tH:%1$tM]"
<time> (gDoorsLast)
String GarageOpener2_Cmd
{ mqtt=">[mosquitto:entry_sensors/main/garage/door2/cmd:state:*:default]" }
Contact vFrontDoor "Front Door is [MAP(en.map):%s]"
<door> (gDoorSensors,gDoorCounts)
{ mqtt="<[mosquitto:entry_sensors/main/front_door:state:default]" }
Switch vFrontDoor_Timer
(gDoorsTimers, gResetExpire)
{ expire="1h,command=OFF" }
DateTime vFrontDoor_LastUpdate "Front Door [%1$tm/%1$td %1$tH:%1$tM]"
<time> (gDoorsLast)
Contact vBackDoor "Back Door is [MAP(en.map):%s]"
<door> (gDoorSensors, gDoorCounts)
{ mqtt="<[mosquitto:entry_sensors/main/back_door:state:default]" }
Switch vBackDoor_Timer
(gDoorsTimers, gResetExpire)
{ expire="1h,command=OFF" }
DateTime vBackDoor_LastUpdate "Back Door [%1$tm/%1$td %1$tH:%1$tM]"
<time> (gDoorsLast)
Contact vGarageDoor "Garage Door is [MAP(en.map):%s]"
<door> (gDoorSensors, gDoorCounts)
{ mqtt="<[mosquitto:entry_sensors/main/garage_door:state:default]" }
Switch vGarageDoor_Timer
(gDoorsTimers, gResetExpire)
{ expire="1h,command=OFF" }
DateTime vGarageDoor_LastUpdate "Garage Door [%1$tm/%1$td %1$tH:%1$tM]"
<time> (gDoorsLast)
val logName = "entry"
rule "Keep track of the last time a door was opened or closed"
when
Item vGarageOpener1 changed from CLOSED to OPEN or
Item vGarageOpener1 changed from OPEN to CLOSED or
Item vGarageOpener2 changed from CLOSED to OPEN or
Item vGarageOpener2 changed from OPEN to CLOSED or
Item vFrontDoor changed from CLOSED to OPEN or
Item vFrontDoor changed from OPEN to CLOSED or
Item vBackDoor changed from CLOSED to OPEN or
Item vBackDoor changed from OPEN to CLOSED or
Item vGarageDoor changed from CLOSED to OPEN or
Item vGarageDoor changed from OPEN to CLOSED
then
val door = triggeringItem as ContactItem
// Update LastUpdate
val last = gDoorsLast.members.filter[dt | dt.name == door.name+"_LastUpdate"].head as DateTimeItem
last.postUpdate(new DateTimeType)
// Set/cancel the Timer
if(door.state == OPEN) sendCommand(door.name+"_Timer", "ON")
else postUpdate(door.name+"_Timer", "OFF")
// Log and alert
val StringBuilder msg = new StringBuilder
val doorName = transform("MAP", "en.map", door.name)
msg.append(doorName)
msg.append(" was")
msg.append(if(door.state == OPEN) " opened" else " closed")
var alert = false
if(vTimeOfDay.state.toString == "NIGHT" || vTimeOfDay.state.toString == "BED"){
alert = true
msg.append(" and it is night")
}
if(vPresent.state == OFF) {
alert = true
msg.append(" and no one is home")
}
if(alert){
msg.append("!")
aAlert.sendCommand(msg.toString)
}
else {
logInfo(logName, msg.toString)
}
end
rule "Timer expired for a door"
when
Item vGarageOpener1_Timer received command OFF or
Item vGarageOpener2_Timer received command OFF or
Item vFrontDoor_Timer received command OFF or
Item vBackDoor_Timer received command OFF or
Item vGarageDoor_Timer received command OFF
then
val doorName = transform("MAP", "en.map", triggeringItem.name)
// send the alert
aAlert.sendCommand(doorName + " has been open for over an hour")
// repeate the alerts if it is night time or after bed time
if(vTimeOfDay.state.toString == "NIGHT" || vTimeOfDay.state.toString == "BED") {
triggeringItem.sendCommand(ON) // reschedule the timer
}
end
not sure were to put this and whether this may be an already known bug (haven’t found anything about this), but it seems to me that Group changes in Rules do not get triggered if the Group name conatins an underscore… It took me nearly one day to find out why my rule is not triggered so I thought this might be worth sharing.
rule "gTimer01OnDays_RCV_UPD"
when
Item gTimer01OnDays received update
then
logWarn( "(timer)", "(timer) TestGroup received update!! " + getStrOnDay.apply( gTimer01OnDays ) )
end
rule "gTimer_01_On_Days_RCV_UPD"
when
Item gTimer_01_On_Days received update
then
logWarn( "(timer)", "(timer) TestGroup received update!! " + getStrOnDay.apply( gTimer_01_On_Days ) )
end
Wow! Thank you! Not only does it not trigger rules, but it doesn’t report a group state (null), it doesn’t persist the group. I think this is not true for all group types though. Group:Numbers works, but Switch and Contact type do not.
Thanks for confirming! I thought I might be doing something wrong
btw: I tried to submit an issue on github, but after registration I haven’t received any confirmation email for my account. It’s also not in any spam-folder, so I fear somebody else needs to raise it until I get this sorted out.
Triggers should work if the group is defined to a type, e.g. Group:Number gMyNumber or Group:Switch gMySwitch should trigger updates, changes and even commands - but be aware that changes on groups will be dependent on all group members and in which way the group state is calculated (OR, AND, SUM, AVG, MIN, MAX)
Groups should be persisted, if the group itself is added to the *.persist file, but with one restriction: the persistence service has to provide a method to persist the data - e.g. switch, contact and string can’t be persisted by rrd4j.
Hey @rlkoshak I have a question about the usage of sortBy.
I have this code snippet in my rule.
val StringBuilder body = new StringBuilder("Battery health report:\n\n")
gBattery.members.sortBy[state].forEach [ NumberItem myItem | {
body.append(String::format("%-30s%8.4s\n", myItem.name, myItem.state.toString))
}]
gBattery is a group of Number items, each of which whose state represents the battery level of my battery-powered zwave devices. I use this in a rule that sends me a daily email of the items’ battery level sorted lowest to highest. This rule works, but vscode complains about it.
Bounds mismatch: The type arguments <Item, State> are not a valid substitute for the bounded type parameters <T, C extends Comparable<? super C>> of the method sortBy(Iterable, Function1<? super T, C>)
While not a very helpful error message, I’ve determined that if I change the statement to this, vscode is happy.
gBattery.members.sortBy[(state as Number).intValue].forEach [ NumberItem myItem | {
Is this because state doesn’t extend Comparable? And, when I change the statement to use a primitive int value, vscode stops complaining? If so, then why does it work when I just use sortBy[state]?