[Solved] Question about rules based on values that are not 'events'

Hi,
Newb here, I’ve had some successes with OpenHAB, but I’m still learning how it all works.

I am having trouble understanding exactly how the rule system is executed.

The rule execution seems to be closer to the event driven programming I’ve done in .NET, as opposed to something more procedural. Is that a valid assessment? I can’t really find a good explanation of how rules are executed, so short of the examples I’m having trouble getting moving. I’m fine with the syntax, just not mechanisms that drive it.

Here is a real world solution that I’ve “Solved” but I’m trying to improve on by better understanding how the rules are triggered:

My Raspberry Pi is set up to serve it’s GPIO up to an MQTT server on my main OpenHAB box. rPi only sends data when a door is opened or closed in the house (I’ve wired GPIO to the reed switches on doors and windows that were pre-installed in the house).

Until OpenHAB gets an update published to the MQTT topic, each Item holds the state: “Uninitialized”. I can post to a ‘refresh’ topic, and the rPi’s python code will update each MQTT topic… so I can easily force a refresh, but I need to know when to force it.

So, I needed a rule that ‘self heals’ the states of the inputs if any of them ever goes Uninitialized, like they tend to do on bootup. Since some windows here don’t open for years, I’ll never have a state, and that is not cool.

Here is the rule I came up with:

/* Refresh of security system MQTT on Remote rPi */

rule "Refresh MQTT"
when
        System started or
        Time cron "0 0/5 * 1/1 * ? *"  // Every 5 minutes
then

        logInfo("Security rPi MQTT","Checking MQTT Status...")

        gSecurity_Sensors?.members.forEach(Contact|

                if (Contact.state == Uninitialized) {
                        logInfo("Security rPi MQTT","Sending MQTT Refresh Request Due to Uninitialized MQTT State")
                        sendCommand(RefreshSecurity, ON)
                }
                else {
                        // logInfo("Security rPi MQTT","No MQTT Refresh Needed")
                }
        )

end

The question is, how can I do this in an event driven way that does not require me to hit the rule with Cron every 5 minutes?
Essentially I want to know if a value is Uninitialized, and fire refresh until it is not (with a delay in there to not spam it). I don’t think changes need made in the rPi, because it is not the one losing the state.
Is there a way to achieve this with a rule?

Bonus question: How do I break the For Each loop after the first ‘Uninitialized’ contact? If they are all uninitialized it will send 8 or so refreshes, which is technically not a problem, but annoys me that I don’t know how to fix it. I could use a flag but that seems ugly.

Thanks.

If I understand your scenario, you could combine a STARTUP rule (for forcing a refresh on startup and a second rule of the general form

rule “foobar”
when
Item x changed to Uninitialized
then
//do your refresh logic
end

But maybe I don’t understand your scenario

Thanks for the response. I guess I figured that if the system booted with Items having the ‘Uninitialized’ state, I would never see an event of them changing to uninitialized, it would be just in a state that means “I’ve never seen this do anything”. In other words, I feel like ‘Uninitialized’ is a lack of state, aka “Undefined”, and therefore would have never fired an event about it since it was instantiated without a value.

This logic may be based on a flawed understanding of how the rule system works.

Would I need to put an “or” in there for every Item I want to monitor? I tried checking the state of the group, but even with all the members of the group having an initialized state, the group still had an “Uninitialized” state. Is there more info on group states somewhere and how they are aggregated (if at all)?

I also have had some trouble with the startup event not firing every time, or possibly firing before the MQTT client was connected in the binding. I have restarted the service, and the rule does not seem to reliably run. Just now I restarted it and the rule above, which posts a debug message every time it runs, never ran.

Edit: My rules file was messed up somehow from another rule (gotta figure that out next). I’m pressing forward with your solution, I’ll see how it goes.

On my phone so pardon the typos and terseness.

You are correct, rules are basically event driven, though possibly not as strictly as in .Net.

I think if you poll for the update at system started, maybe with a short timer to give the mqtt binding time to load and the items initialize coupled with the changes to uninitialized to cover any cases where the state drops to uninitialized (something I’ve never seen without a reboot). You shouldn’t need the cron in this case.

Also, you may need to update your configuration polling times at the top of openHAB.cfg so that the rules get loaded last. I’ve had issues where a system started rule fired before the items were loaded. This is aggravated if you have more than one items it rules files.

I’ve also never had a system started rule not run so this is odd behavior and probably needs to be pursued further. Does this happen often?

To only fire the poll request once do a filter instead of a foreach.

‘if(gSecurity,_sensors.members.filter(s| s.state == uninitialized).size > 0) // poll for update’

You can trigger on the group, probably best to use received update, though in my experience that will cause the rule to trigger repeatedly per event, once per item in the group. I’m not sure why. I don’t think a group will take the uninitialized state unless all the items in the group are uninitialized.

Thank you for your suggestions.

@bob_dickenson: Your “foobar” rule did not fire on startup, but the system startup option did, so I’ll add the “changed to uninitialized” as an “or” in my rule, which should catch any state loss if it were ever to happen.

@rlkoshak: Thank you for clarifying on my other questions. My rules do load last. My problem was simply some bad syntax in my rule. That is why it looked like it was not firing startup events and part of what had me confused. I bit the bullet and set up openHAB Designer again and the checker found a few problems in my rules file.

I agree on ditching the cron part of it. I may add in a heartbeat to the python code on the rPi side just so I can watch it and make sure it is alive. I’m new to this “send once” behavior of MQTT and openHAB, so if the rPi crashes on me it will not be known to OpenHAB unless I check it with a cyclic heartbeat.

Lastly, the ‘filter’ is what I was looking for. I’ll give a go.

Have a good one! Marking this solved.

I have an almost identical setup with a raspi reporting state changes over mqtt. Instead of a steady heart beat I set a timer in a rule that keeps getting reset for fifteen minutes in the future every time I get an update. If the timer goes off I request an update (just as you do), sleep for a sec, and if I still don’t get a response then I generate an alert.

On the topic of knowing when the client disconnects/crashes…have you looked at the LWT option in MQTT?

There is already a keep alive communication between client and broker and if you use the LWT option, when the broker notices the client has disconnected a message can be posted to a designated topic.

E.g.

When the client connects to the broker it sets up a LWT to send “0” to dev1/status. Once the client has connected it sends the message “1” to dev1/status to show that it is online. If the broker notices the device has gone offline (lack of keep alive) it will send a “0” to dev1/status to show the device is now offline.

Thanks guys, both are very good options.

I am aware of LWT, but I was not aware the broker would post a message on comm loss for it though. I never really understood the point before realizing that, since a client rarely knows it is disconnecting before it happens, so when would it post its LWT? It makes sense though if the broker watches that connection.

@danielwalters86 You learn something new every day. Thanks! LWT will be much cleaner I think.