Examples needed

Hello guys!

I’m looking for some examples in these situations

1- A rule should be activated iff a particular sequence of events has occurred. I mean, if the events (state changes) occurred in a chronological way.
For example, "Notify me if MotionSensorA detected movement and then the DoorContactB was opened and ...."

2- A rule should be activated iff a number X of events has occurred.
For example, "Notify me if the boiler's temperature was 3 times over 60 C in the last hour"

3- A rule should be activated iff a threshold X if achieved.
For example, "Notify me if my electricity consumption is more than X kw in 3 weeks" Consider that you get your daily consumption of electricity every day at 00:00, so you need to keep historical data

If you can share any automation that uses a similar kind of logic will be appreciated

thanks in advance!
Humberto

Which language?

hi @5iver

Rule DSL, Jython, Groovy or JS

I don’t have any preference :slight_smile:

If this is for just two devices, then I’d use a rule that triggered on change of the second device and use persistence to check that the first device had been made active within a certain time interval. I have examples for this. However, it sounds like you want to use a sequence of more than two devices to predict that an area becomes triggered. I don’t have an example, but suggest you look into enabling/disabling rules based on a sequence of events. For example…

  1. rule1 triggers on change of Item1. The rule action enables/disables rule2.
  2. If enabled by Item1, rule2 triggers on change of Item2. The rule action enables/disables rule3.
  3. If enabled by Item2, rule3 triggers in change of Item3. The rule action is whatever you wanted to occur at the end of the chain.

If you are using a device or Item that is not self-expiring, then you would need to change the rule trigger and add a timer. A concrete use case would help to define your specific requirements.

One way to do it is to use a rule that triggers on change of the boiler temp. The rule action would not do anything if the temp was <= 60 °C. If the temp was greater, then add the current time to a global list and trim the list to 2 elements, then iterate the list to see if the times were all within the hour. I don’t have a specific example, but this may work (untested).

from core.rules import rule
from core.triggers import when

from org.joda.time import DateTime
times = []

@rule("Temperature: Boiler")
@when("Item BoilerTemp changed")
def boilerTemperature(event):
    if event.itemState > QuantityType("60 °C"):
        if len(times) > 2:
            if all(map(lambda time: time.isAfter(DateTime.now().minusHours(1)), times)):
                # send alert
        times.insert(0, DateTime.now())
        times = times[:2]

In my experience, energy reports can sometimes randomly come in with invalid values, so I have a rule to filtered out those bad values. I also store deltas, which allows the meter to be reset back to 0. I do not (yet) use UoM for my energy meter Items. It sounds like you only get a daily energy report, but I think this will still work for you. I have my meters set to report energy hourly.

from core.rules import rule
from core.triggers import when
from core.actions import PersistenceExtensions

from org.joda.time import DateTime

@rule("Power: Mains energy monitor and cleanup")
@when("Item HEM1_Total_Energy changed")
def mainsEneryMonitorAndCleanup(event):
    #mainsEneryMonitorAndCleanup.log.debug("Energy (mains): {}: start".format(event.itemState))
    if event.itemState >= DecimalType(0) and (event.itemState >= event.oldItemState or event.itemState < DecimalType(60)) and event.itemState <= DecimalType(event.oldItemState.intValue() + 60):
        events.sendCommand("HEM1_Total_Energy_Cleaned", str(event.itemState))
        delta = float(str(event.itemState)) - float(str(event.oldItemState))
        if delta >= 0:
            events.sendCommand("HEM1_Total_Energy_Delta", str(delta))
        else:
            events.sendCommand("HEM1_Total_Energy_Delta", str(event.itemState))

@rule("Power: Mains energy monitor monthly")
@when("Item HEM1_Total_Energy_Cleaned changed")
def mainsEnergyMonitor(event):
    lastThreeWeeksUsage = float(str(PersistenceExtensions.sumSince(ir.getItem("HEM1_Total_Energy_Delta"), DateTime.now().minusDays(21))))
    if lastThreeWeeksUsage > 555:# pick your own value
        # send alert

1 Is pretty rough. I’ll probably turn this into a DP at some point. There are a lot of unstated requirements here. For example, what sort of timing do the events need to occur in? Within 1 second of each other? 5 seconds? Days? Any amount of time?

I’ll assume no time limit. Let’s name the Event Items so they have their sequence order in the name

var Number lastEvent = null
val MAX_EVENT = 3

rule "Sequence Rule"
when
    Item Event_1 changed or
    Item Event_2 changed or
    Item Event_3 changed or
    // and so on
then
    // Get the event sequence number of the event
    val currEventNum = new BigDecimal(triggeringItem.name.split("_").get(1))

    // This is the first event in the sequence
    if(currEventNum == 1) lastEvent = currEventNum

    // This is not the first event in the sequence and the first event hasn't happened yet
    else if(lastEvent == null) return;

    // Check to see if this event immediately follows the last one
    else {
        val lastEventNum = Integer::parse(lastEvent.name.split("_").get(1))

        // This event immediately follows the last one
        if(lastEventNum == currEventNum + 1) lastEventNum = currEventNum

        // This event doesn't immediately follow the last one, reset the sequence
        else lastEvent = null
    }

    // We reached the end of the sequence
    if(lastEvent == MAX_EVENT) {
        // do stuff
    }
end

Adding a time period to the above is going to make things pretty complicated.

2 Is pretty easy. There are lots of ways to do it. Here is one I came up recently for another thread.

var overTemp = 0

rule "Temp over 60 in last hour"
when
    Item BoilerTemp changed
then
    if(BoilerTemp.state <= 60 | °C) return; // assuming a Number:Temperature type

    overTemp = overTemp + 1
    createTimer(now.plusMinutes(60),  [ | overTemp = overTemp - 1 ]

    if(overTemp >= 3) {
        // send alert
    }
end

Theory of operation: When the tempo changes to a value over 60 °C increment a count. Then set a Timer to decrement the counter in an hour. Finally, check to see if the count is >= 3 meaning in the past hour it has been over 60 °C at least three times.

3 This is also easy. Store the values using Persistence and it’s a simple call.

    if(ElectricityConsumption.sumSince(now.minusDays(21)) > XXX) {
        // send alert
    }

How you trigger the Rule depends on when you want the alert, how you are calculating your consumption, and other details you have not provided.

That’s a good use of Rule enable/disable. I like it. This needs to be a DP when we start working the docs for NGRE/JSR223.

Don’t forget to reenable the first Rule.

This approach might not handle this completely though. Consider the following sequence of events:

1,2,2,1,1,2,3

Intuitively I would expect to need to start the sequence over upon that second occurrence of the second event. But if the Rules are disabled those events would be ignored and the sequence would be detected.

@rhumbertgz, as you can see, this may seem like a simple ask but it’s actually a pretty hard problem.

I like it too, but haven’t found a need for it yet in my own automations! If @rhumbertgz provides some more requirements and this fits, I may write it up and test just to see it in action.

Sphinx has opened a lot of doors for the helper libraries. I’m not sure I can go back to just markdown! I’d love to see examples for the forum being documented straight to the docs and then people providing links to them in their posts. Can you imagine the amount of content there’d be for people to look through and learn from, rather than the information getting buried in the forum? I feel guilty after posting my examples, because I didn’t get it into the documentation :blush:! Another topic… another day…

This sounds a lot like the wasp in box example I posted awhile back here

based on this rule example comparing two times

1 Like

Honestly, my goal is that instead of sharing code many/most of examples like these would be offered as libraries/Rule templates that can be installed and configured for use using one of the UIs.

There was a discussion years ago about making the DPs part of the official docs. I can’t remember the reasoning but we decided that wasn’t a good idea at the time.

Too much content isn’t always a good thing either. But I definitely would like to see more examples in the docs.

@5iver, @rlkoshak, @Andrew_Rowe many thanks for your replies!

@5iver I had a busy afternoon-night, but I will try to elaborate more complex automations tomorrow

@rlkoshak

Do you know where can I find info over the new Exp Rule Engine (implementation details)?

There are some significant limitations right now that make it not suitable for general use, at least from the UI perspective. You can use JSR223 Rules, like Scott’s examples above. The best information is the “helper libraries” link Scott already provided.

There really isn’t any good documentation for the PaperUI Rules. Things are changing too fast in the support and helper libraries and the UI is kind of broken and not going to be fixed (a new UI will be created in OH 3). So I recommend using JSR223 rules or Rules DSL.

thanks @rlkoshak

Hi @5iver

Here is my solution based on your proposal. I did some small changes to only send the notification after 3 values > 60

@rule("Boiler temperature")
@when("Item Boiler_Temperature changed")
def boiler_temperature(event):
    global times
    if event.itemState.intValue() > 60:
        if len(times) == 2:
            if all(map(lambda t: t.isAfter(DateTime.now().minusHours(1)), times)): 
               boiler_temperature.log.info("Send notification") 
            
            times.insert(0, DateTime.now()) 
            times = times[:2]
        else:
            times.insert(0, DateTime.now())

Thanks :+1:

1 Like

I see the bug and updated my example!

1 Like

Hi @Andrew_Rowe,

Based on your post I created these simple rules to detect when I arrive or leave home. More complex rules can be created, if more than a person lives in the house and if you consider other situations.

Basically, I have three sensors:
Motion Sensors

  • Entrance Hall
  • Front door (outside)

Contact Sensor

  • Front door

The pattern to detect when I arrive home is represented by the following sequence of events:
(1) Front_Door_Motion → (2) Front_Door_Contact → (3) Entrance_Hall_Motion

And the pattern to detect that I’m leaving home is:
(1) Entrance_Hall_Motion → (2) Front_Door_Contact → (3) Front_Door_Motion

Note: I’m only considering that all three events occur within a time window of 60 seconds.

Items

Contact Front_Door_Contact     ...
Switch  Entrance_Hall_Motion   ...
Switch  Front_Door_Motion       ...

Rules

// Initialize variables to avoid null checks
var lastDoorOpen = now().minusHours(24)
var lastEHallMotion = now().minusHours(24)
var lastFDoorMotion = now().minusHours(24)

rule "(DSL) Front Door Opened"
when
	Item Front_Door_Contact changed to OPEN
then
	lastDoorOpen = now()
end

rule "(DSL) Motion Detected - Entrance Hall"
when
	Item Entrance_Hall_Motion changed to ON
then
    lastEHallMotion = now()

    // discard events older than 60 secs
    if( lastFDoorMotion.isBefore(lastEHallMotion.minusSeconds(60)))   return;

    if(lastEHallMotion.isAfter(lastDoorOpen) && lastDoorOpen.isAfter(lastFDoorMotion)) {
         // code logic for arriving home
    }
end

rule "(DSL) Motion Detected - Front Door "
when
	Item Front_Door_Motion changed to ON
then
    lastFDoorMotion = now()

    // discard events older than 60 secs
    if( lastEHallMotion.isBefore(lastFDoorMotion.minusSeconds(60)))  return;

    if(lastFDoorMotion.isAfter(lastDoorOpen) && lastDoorOpen.isAfter(lastEHallMotion)) {
       // code logic for leaving home
    }
end

Once again, many thanks @Andrew_Rowe, @rlkoshak, @5iver :+1:

1 Like

Here is the translation of the previous code (DSL Rules) to Jython

from core.rules import rule
from core.triggers import when
from org.joda.time import DateTime

lastDoorOpen = DateTime.now().minusHours(24)
lastEHallMotion = DateTime.now().minusHours(24)
lastFDoorMotion = DateTime.now().minusHours(24)

@rule("(Py) Front Door Opened")
@when("Item Front_Door_Contact changed to OPEN")
def front_door_opened(event):
    global lastDoorOpen
    lastDoorOpen = DateTime.now()

@rule("(Py) Motion Detected - Entrance Hall")
@when("Item Entrance_Hall_Motion changed to ON")
def entrance_hall_motion(event):
    global lastEHallMotion, lastFDoorMotion
    lastEHallMotion = DateTime.now()

    if lastFDoorMotion.isBefore(lastEHallMotion.minusSeconds(60)):
        return
    
    if lastEHallMotion.isAfter(lastDoorOpen) and lastDoorOpen.isAfter(lastFDoorMotion):
        # code logic for arriving home


@rule("(Py) Motion Detected - Front Door")
@when("Item Front_Door_Motion changed to ON")
def front_door_motion(event):
    global lastEHallMotion, lastFDoorMotion
    lastFDoorMotion = DateTime.now()

    if lastEHallMotion.isBefore(lastFDoorMotion.minusSeconds(60)):
        return
    
    if lastFDoorMotion.isAfter(lastDoorOpen) and lastDoorOpen.isAfter(lastEHallMotion):
        # code logic for leaving Home