Rule Error "null"

Are theses in a lambda or in a rule proper?

in the rule proper.

this is one rule that will trigger it



import java.util.concurrent.locks.ReentrantLock

var ReentrantLock lock = new ReentrantLock()



// do actions when an area becomes occupied or vacant
// propagate state to parent or child areas 
 

rule "React on OccupancyState changed"
when
    Member of gOccupancyState changed    // your condition here
then

    try {
        lock.lock ()

        logInfo("Occupancy State Changed","Changed:  "+triggeringItem.name+"     State:    "+triggeringItem.state)

        // get the area that the triggering item belongs too
        val GroupItem area = gArea.members.findFirst [ a | a.name == triggeringItem.getGroupNames.findFirst [ i | a.name == i ] ]
        //logInfo("Occupancy State Changed","Area Name:  "+area)     

        // find the occupancy locking item
        val occupancylocking = area.members.findFirst [ i | (i.name.indexOf ("OL_") == 0 ) ]
        logInfo("Occupancy State Update","Occupancy Locking:  "+occupancylocking)

        if (occupancylocking != ON) {
            // Process any actions based on the tags for that area for change from OFF to ON ie vacant to occupited
            if (triggeringItem.state == ON) {
                // perform any actions
                triggeringItem.getTags.forEach [ i |
                    val np = i.toString.split (":")
                    if (np.get (0) == "OccupiedAction")
                        switch (np.get (1)) {
                            case "LightsOn": {
                                area.members.forEach [ i |
                                    i.getTags.forEach [ t | 
                                        if (t=="Lighting") {
                                            i.sendCommand (ON)
                                        }
                                    ]
                                ]
                            }

                        }
                ]
            }

            // OFF = vacant, do any vacant actions
            if (triggeringItem.state == OFF) {
                // Process any actions based on the tags for that area
                triggeringItem.getTags.forEach [ i |
                    val np = i.toString.split (":")
                    if (np.get (0) == "VacantAction")
                        switch (np.get (1)) {
                            case "LightsOff": {
                                area.members.forEach [ i |
                                    i.getTags.forEach [ t | 
                                        if (t=="Lighting") {
                                            i.sendCommand (OFF)
                                        }
                                    ]
                                ]
                            }

                            case "SceneOff": {
                                area.members.forEach [ i |
                                    i.getTags.forEach [ t | 
                                        if (t=="AreaAll") {
                                            i.sendCommand (OFF)
                                        }
                                    ]
                                ]
                            }
                            
                            case "ExhaustFansOff": {
                                area.members.forEach [ i |
                                    i.getTags.forEach [ t | 
                                        if (t=="Exhaust Fan") {
                                            i.sendCommand (OFF)
                                        }
                                    ]
                                ]
                            }

                            case "AVOff": {
                                area.members.forEach [ i |
                                    if (gAVPower.members.findFirst [ a | a.name == i.name] !== null)
                                        i.sendCommand (OFF)
                                ]
                            }

                        }
                ]
            }   
        }

        //update parent area group
        val GroupItem parentarea = gArea.members.findFirst [ a | a.name == area.getGroupNames.findFirst [ i | a.name == i ] ]
        //logInfo("Occupancy Status Changed","parent area  "+parentarea)     
        
        if (parentarea !== null) {
            val GenericItem poccupancystate = parentarea.members.findFirst [ i | (i.name.indexOf ("OS_") == 0 ) ]

            if (poccupancystate !== null) {
                // need to iterate all child areas and see if any are on
                var Boolean anyon = false 
                parentarea.members.forEach [ i | // iterate all members of the parent area
                    //logInfo("Occupancy Status Changed","parent area members  "+i)     
                    val childarea = gArea.members.findFirst [ a | a.name == i.name ] as GroupItem // find if the member is a member of gArea

                    if (childarea !== null) { // we have a child area
                        val GenericItem childoccupancystate = childarea.members.findFirst [ i | (i.name.indexOf ("OS_") == 0 ) ]
                        if (childoccupancystate !==null)
                            if (childoccupancystate.state == ON)
                                anyon = true
                    }
                ]

                if (anyon == true)
                    poccupancystate.postUpdate (ON)
                else
                    poccupancystate.postUpdate (OFF)
            }
        }     


        // if area is vacant, force child areas to vacant
        if (triggeringItem.state == OFF) {
            area.members.forEach [ i | // iterate all members of the area
                //logInfo("Occupancy Status Changed","parent area members  "+i)     
                val childarea = gArea.members.findFirst [ a | a.name == i.name ] as GroupItem // find if the member is a member of gArea

                if (childarea !== null) { // we have a child area
                    val GenericItem childoccupancystate = childarea.members.findFirst [ i | (i.name.indexOf ("OS_") == 0 ) ]
                    if (childoccupancystate !==null)
                        childoccupancystate.postUpdate (OFF)
                }
            ]
        }

        //gOccupancyState.members.forEach [ i | 
        //  logInfo("Occupancy States"," Name:  "+i.name+"     State:    "+i.state)
        //]
    }

    finally {
        lock.unlock ()
    }    
end  




1 Like

All I can guess is that gOccupancyState is received so many events so rapidly that the Rule is being triggered before the even the Rule itself is done being loaded.

If this rule truly is being triggered that rapidly and constantly I can almost guarantee that you will run out of Threads at some point and will see high latency in the rest of your Rules running or a complete halt of the rest of your Rules.

I would strongly recommend looking for an approach that doesn’t require the lock, moving the logic to JSR223 rules, or moving the logic completely outside of OH entirely.

It is likely all the events that happen at startup. I am trying the solution where rules are moved out of the rule folder during startup and then added back in 5 minutes later.

I understand that OH has these limitations - but the way it deals with these problems is not satisfactory. Error messages are cryptic, often don’t reference the offending file or the line number that caused the problem or no error at all, and sometimes stuff that doesn’t work.

Also was considering throttling these events using a hashmap and time and only. The logic doesn’t depend on every event coming in - so I could discard events that happen within up to a minute of each other.

Not sure how the datetime works to do that but looking.

Are you saying that you would store the messages in the hashmap using the message as the key and a DateTime as the value that should work.

Map<String, DateTime> msgTimes = newHashMap
...
msgTimes.put(msgString, now)
...
if(msgTimes.get(msgString).isBefore(now.minusMinutes(10)) // message was last sent more than 10 minutes ago

However, is you want to use the Map as your queue (i.e. use the DateTime as the key) this isn’t going to work so well because Maps are unordered.

Adding throttling per your code, works great. Thank you


import java.util.HashMap
import java.util.concurrent.locks.ReentrantLock

val HashMap<GroupItem, DateTime> timelastareaevent = newHashMap() // contains time of last event for event throttling, indexed by area
var ReentrantLock lock = new ReentrantLock()


// gOccupancyItem group contains any item that generates an event that might change the occupancy state 
// these items need to tagged appropriate to generate an occupancy change event
rule "React on OccupancyItem change/update"
when
    Member of gOccupancyItem changed
then
    if(previousState == NULL) return; // we don't want to process events from restoreOnStartup
  
    try {
        lock.lock ()

        logInfo("Occupancy Item Event","Changed:  "+triggeringItem.name+"     State:    "+triggeringItem.state)
        val area = gArea.members.findFirst [ a | a.name == triggeringItem.getGroupNames.findFirst [ i | a.name == i ] ] as GroupItem
        //logInfo("Occupancy Item Event","Area:  "+area) 

        val prior = timelastareaevent.get(area.name) ?: now.minusMinutes (2)

        if (prior.isBefore (now.minusMinutes (1))) {  // been over a 1 minute since this item caused an event
            //logInfo("Occupancy Item Event","Keep:  ") 
            timelastareaevent.put(area.name,now) // update time of this item event

            // find the occupancy state item
            val occupancystate = area.members.findFirst [ i | (i.name.indexOf ("OS_") == 0 ) ]
            //logInfo("Occupancy Item Event","Occupancy State:  "+occupancystate)

            // find the occupancy locking item
            val occupancylocking = area.members.findFirst [ i | (i.name.indexOf ("OL_") == 0 ) ]
            //logInfo("Occupancy Item Event","Occupancy Locking:  "+occupancylocking)

            // get the type of event from the triggering item
            val String oe = triggeringItem.getTags.findFirst [ i | i.indexOf ("OE") == 0 ]
            //logInfo("Occupancy Item Event"," Occupancy Event:  "+oe) 

            logInfo("Occupancy Item Event","Item:  "+triggeringItem.name+"     State:    "+triggeringItem.state+"    Occupancy Event:  "+oe) 
            // proccess occupancy events if state is not locked  
            if (occupancylocking != ON) {       

                switch (oe) {

                    case "OE:Light_Standard": {
                        // occupied event for light being turned on
                        if (triggeringItem.state == ON || triggeringItem.state > 0) {
                            //logInfo("Occupancy Item Event"," OE Light Standard ON") 
                            occupancystate.postUpdate (ON)
                        }
                    }
            
                    case "OE:Scene_Standard": {
                        // occupied event for scene being turned on
                        if (triggeringItem.state == ON || triggeringItem.state > 0) {
                            //logInfo("Occupancy Item Event"," OE Light Standard ON") 
                            occupancystate.postUpdate (ON)
                        }
                    }
            
                    case "OE:ExhaustFan_Standard": {
                        // occupied event for exhaust fan being turned on
                        if (triggeringItem.state == ON || triggeringItem.state > 0) {
                        // logInfo("Occupancy Item Event"," OE Exhaust Fan Standard ON") 
                            occupancystate.postUpdate (ON)
                        }
                    }
            
                    case "OE:AVPower": {
                        // occupied event for audio power on
                        if (triggeringItem.state == ON || triggeringItem.state > 0) {
                            //logInfo("Occupancy Item Event"," AV Power ON") 
                            occupancystate.postUpdate (ON)
                        }
                    }
            
                    case "OE:Motion_Standard": {
                        // occupied event for motion detected
                        if (triggeringItem.state == OPEN) {
                            //logInfo("Occupancy Item Event"," OE Motion Standard") 
                            occupancystate.postUpdate (ON)
                        }      
                    }

                    case "OE:PerimeterDoor": {
                        // occupied event for motion detected
                        if (triggeringItem.state == OPEN) {
                            //logInfo("Occupancy Item Event"," OE Motion Standard") 
                            occupancystate.postUpdate (ON)
                        }      
                    }
                }
            }
        }
        else
            logInfo("Occupancy Item Event","Skipping:  "+triggeringItem) 
    }

    finally {
        lock.unlock ()
    }        
end








1 Like

Some further testing with and without locks during OH startup demonstrates some really odd behavior 


Its seems that is possible for a Rule to be loaded and then triggered before the globals in the rule file have been instantiated.

Yes, it is a special case of the known bug. Not only does the Rule start running before other files have been loaded, they can trigger before the .rules file it is in is fully loaded. I’ve seen an error on a global variable at startup once. I’ve only seen it once though so it is likely a rare occurrence.