Rule to check if period of no meter reading exist

I have a watermeter sending m3 readings to my OH1 system through MQTT - where the OH1 has an rrd4j persistence database set up to store at “everychange”. So far so good.

I have made small features like “waterusage last hour and since midnight” etc…with:
var LiterToday = Watermeter_Liter.deltaSince(now.toDateMidnight)
var Number LiterLastHour = Watermeter_Liter.deltaSince(now.minusHours(1))

But, my original intention was to have OH send me a message (i.e. Tweet) if it couldnt find a period of i.e. 3 hours without any waterusage - within a day. The theory is, that there must be at least one period in a day, where no usage exist - otherwise, I should check if a leaking pipe or something exist…

Has anyone made a rule for this and would like to share how it is done ?

I thought about running a rule every 30 min looking back 3 hours and if no Water has been used, then set a flag - and then i.e. once every day send me a Tweet if no flag was set in the last 24 hours.

But I am thinking, that this might be done somewhat more elegant somehow ?

To start off I think you can’t use rrd4j dartabases with a "every change"persistance logging since rrd4j needs a reading every minute. Maybe your actual usage will write a value every minute,
And secondly I don’t get how you detect a period with now water usage. Could you elaborate on that?

If you persist only on everyChange (as you indicate above) then you should be able to setup a cron rule that fires every 3 hours, and then simply check if the last value in the database was persisted more than 3 hours ago or not.

Apart from that, I agree with @opus that rrd4j is probably not your best bet for something like this. If you only care about this rule, then I would suggest that mapdb can do the trick since you only need one value to be persisted (the last change). If you want something more, e.g. charts on water usage, etc. then you should look at MySQL, DB4O or maybe InfluxDB.

IGNORE This Approach, see next posting.

Assuming that when there is no usage the water meter doesn’t send a message. There are lots of little edge cases here so this is a bit more complicated than one would expect.

#Items

We need a new Item that gets set to ON when there has been a period of three hours without a meter message

Switch ThreeHourGap "There has been a three hour gap in water usage today [%s]"

#Rules

import org.openhab.model.script.actions.*
import java.util.concurrent.locks.ReentrantLock

val waterMeterTimer = null
// use a lock to prevent a water meter reading that happens while while resetting everything
val ReentrantLock lock = new ReentrantLock 

// If there has been a three hour gap, set the ThreeHourGap switch to ON
rule "Water Meter Received Update"
when
    Item WaterMeter_Liter received update
then
    if(ThreeHourGap.state == OFF) { // We don't care if there has already been a three hour gap
        try {
            lock.lock
            if(waterMeterTimer == null || waterMeterTimer.hasTerminated){ // No Timer yet, first reading of the day
                waterMeterTimer = createTimer(now.plusHours(3), [|
                    // Its been three hours without a reading
                    ThreeHourGap.sendCommand(ON) 
                ])
            }
            else {
                // If there is a timer, reschedule it
                waterMeterTimer.reschedule(now.plusMinutes(30))
            }
        }
        catch(Throwable t){
            logError("waterMeter", "Error processing water meter update: " + t)
        }
        finally {
            lock.unlock
        }
    }
end

// Check for the three hour gap at midnight, reset the timer and ThreeHourGap Item (choose your own preferred time)
rule "Check for a three hour gap in water usage"
when
    Time cron "0 0 0 * * ? *"
then
    if(ThreeHourGap.state != ON) {
        // Alert, there hasn't been a three hour gap!
    }

    try {
        lock.lock
        if(waterMeterTimer != null && !waterMeterTimer.hasTerminated) waterMeterTimer.cancel
        waterMeterTimer = null
        ThreeHourGap.sendCommand(OFF)
    }
    catch(Throwable t){
        logError("waterMeter", "Error processing check for three hour gap: " + t)
    }
    finally {
        lock.unlock
    }
end

// At startup, check to see when the last message was set and create a Timer for the amount of time left
rule "Initialize the Timer"
when
    System started
then
    try {
        lock.lock
        val long timeLeft = (3 * 60 * 60 * 1000) - (now.millis - Watermeter_Liter.lastUpdate.time)
        if(timeLeft > 0) { // if timeLeft is negative ThreeHourGap has already been set to ON
            waterMeterTimer = createTimer(now.plusMillis(timeLeft), [|
                // Its been three hours without a reading
                ThreeHourGap.sendCommand(ON) 
            ])
        }
    }
    catch(Throwable t){
        logError("waterMeter", "Error during water meter startup: " + t)
    }
    finally {
        lock.unlock
    }
end

Theory of Operation:

Every time there is a reading from the meter set or reschedule a Timer for three hours into the future. The Timer will go off after three hours of no meter readings. Set a flag in the Timer so we know when the Timer has gone off.

Once a day, check the flag to see if there has been a three hour period of time without any messages from the meter. If there hasn’t (i.e. the flag is OFF) send an alert. Then reset everything (Timer and flag) for the next day.

There is a small but real possibility that a meter reading will come in while the once a day rule is processing and resetting everything so use a reentrant lock to prevent the rule from executing at the same time as the once a day reset processing.

At system startup, check for when the last message was received and create the Timer for the remaining time.

NOTES:

  • If your meter reader crashes and stops sending messages these rules will think everything is going great. It might be a good idea to replicate these rules to send an alert when there hasn’t been any readings for a day or longer.
  • I just typed in the above, there are likely errors.
  • ThreeHourGap needs to be persisted and restoreOnStartup

DOH! I just thought of a WAY simpler way. I’ll leave the previous posting for posterity.

rules "Water Meter Received Update"
when
    Item WaterMeter_Liter received update
then
    if(ThreeHourGap.state == OFF) {
        val long timeSinceLast = now.millis - Watermeter_Liter.lastUpdate.time
        if(timeSinceLast >= (3 * 60 * 60 * 1000)){
            ThreeHourGap.sendCommand(ON)
        }
    }
end

rule "Check for a three hour gap in water usage"
when
    Time cron "0 0 0 * * ? *"
then
    if(ThreeHourGap.state != ON) {
        // Alert, there hasn't been a three hour gap!
    }

    ThreeHourGap.sendCommand(OFF)
end

This approach does away with the need for the Timer and because of that all of the edge cases go away. Use this approach, not my previous one.

1 Like

I’ll try this one - and let you know if it works like designed !! Thanks !

Another approach, which I have just spent the last few weeks implementing in my system, is to use an external monitoring system for this type of check.

Now this is definitely not for everyone but I thought I would mention it here in case anyone else was interested.

I installed icinga2 after a recommendation and have been configuring it to not only monitor hosts/services on my LAN but also a load of openHAB related data. For example I can setup rules to ensure sensors are reporting values at least every n mins, monitor check battery levels, check temp values are between limits, check Z-Wave node statuses.

I have found this a much more robust way to perform all these types of system-checks and it has resulted in a load of these types of rules (e.g. above) from being removed from my openHAB rules and left to the dedicated monitoring system.

I would be happy to share some of what I have done for anyone interested.

1 Like

@ben_jones12 I am very interested in learning more about what you have done with Icinga2, especially when it comes to such things as monitoring the battery level of devices and the status of Z-Wave nodes. Whatever you feel like sharing is appreciated!

I like the idea of using something besides OH for doing monitoring. Eventually I was planning on using some sort of system based on SNMP (eg. Nagios) but maybe I’ll look into icinga2. OH is great at the automation but not so great at the monitoring sometimes.

I have done most of my openHAB integration with icinga2 by adding MQTT bindings to the sensor/battery/state items I want to monitor and then implementing rules and checks via mqttwarn.

E.g. in my mqttwarn.ini I have an icinga2 target configured;

[config:icinga2]
host      = 'https://icinga2-host'
port      = 5665
username  = 'icinga2-api-username'
password  = 'icinga2-api-password'
targets   = {
                      # host    service
    'check-service' : [ 'host', 'service', 'mqttwarn' ],
    }

Then I have a series of subscriptions (also in mqttwarn.ini);

# various MQTT services/clients
[/clients/+]
targets  = icinga2:check-service
format   = icinga2_lwt_publish()

# zwave node alive/dead
[/network/+/+/zwave]
targets  = icinga2:check-service
format   = icinga2_network_zwave()

# battery reports for various sensors
[/sensor/+/+/battery]
targets  = icinga2:check-service
format   = icinga2_sensor_battery()

Then I have a series of functions which are being referenced in the targets above, e.g. icinga2_sensor_temp();

def icinga2_lwt_publish(data, srv):
    # /clients/+
    # /homie/+/$online
    payload  = data['payload']
    parts    = data['topic'].split('/')
    host     = parts[2]

    if payload == '1' or payload == 'true':
        status = 0
        output = "OK: {0} is online".format(host)
    else:
        status = 2
        output = "CRITICAL: {0} is offline".format(host)

    icinga2_payload = {
        'exit_status'     : status,
        'plugin_output'   : output,
        'service'         : "{0}.home!mqtt-lwt".format(host),
        }

    return json.dumps(icinga2_payload)

def icinga2_network_zwave(data, srv):
    payload  = data['payload']

    if payload == 'alive':
        status = 0
        code = 'OK'
    elif payload == 'dead':
        status = 2
        code = 'CRITICAL'
    else:
        status = 3
        code = 'UNKNOWN'

    # /network/location/type/zwave
    parts    = data['topic'].split('/')
    location = parts[2]
    type     = parts[3]
    host     = "{0}-{1}".format(location, type)

    icinga2_payload = {
        'exit_status'     : status,
        'plugin_output'   : "{0}: zwave node is {1}".format(code, payload),
        'service'         : "{0}!zwave".format(host),
        }

    return json.dumps(icinga2_payload)

def icinga2_sensor_battery(data, srv):
    batt_warn = 20
    batt_crit = 10

    battery   = int(float(data['payload']))
    status    = 0
    code      = 'OK'

    if battery <= batt_warn:
        status = 1
        code   = 'WARNING'

    if battery <= batt_crit:
        status = 2
        code   = 'CRITICAL'

    # /sensor/location/type/battery
    parts    = data['topic'].split('/')
    location = parts[2]
    type     = parts[3]
    host     = "{0}-{1}".format(location, type)

    icinga2_payload = {
        'exit_status'     : status,
        'plugin_output'   : "BATT {0}: {1}%".format(code, battery),
        'service'         : "{0}!battery".format(host),
        'performance_data': [ "battery={0}%;{1};{2}".format(battery, batt_warn, batt_crit) ],
        }

    return json.dumps(icinga2_payload)

So once you establish the required payload for an icinga2 check it becomes pretty easy to add.

You then have to define the host/service/checkcommand in your icinga2 config and away you go. I won’t go into the icinga2 config as that is a whole new kettle of fish!

But the key bits from this are mqttwarn. Using this tool it is very easy to take data from MQTT topics and transform it into something useful for another service. Without having to touch any openHAB config. I already had all my sensor data being published to MQTT topics in the hope I would one day be able to use it - and along came icinga2!

The real beauty of icinga2 is the ability to configure how and where you are notified. For example by default a service won’t send a notification until it has failed 5 checks (checks are typically executed every minute but this is of course configurable).

I have my system setup to publish alerts to Pushover (again via mqttwarn) and also direct to a private Slack channel;

Here is a screenshot of my hallway pir sensor (a host) with 4 services being checked by icinga2;

Hey Ben,
this a great way to integrate icinga/nagios. Would you care to copy this over to the Tutorials & Examples category??

There is quite a lot of additional info required for a proper posting about this - icinga2 configuration being the main part. I will try and find some time to write it up in more detail but with a 6 month old baby I am struggling to find much spare time!

Totally agreed, I have a nagios installation myself. Setting up icinga doesn’t have to be part of your tutorial, just reference one of the many tutorial on that :wink: For now I guess it would be enough to just copy and paste your response up there to a new thread :wink:

Btw congrats :wink:

Did you manage to get some of you icinga2 config posted anywhere? Though I do wonder about just doing something else to see if a script checking my alarm integration is still pulling values.

@psyciknz I haven’t sorry - I have all my config in a private git repo with sensitive details/passwords etc. Happy to share snippets if you have specific questions tho.