Search code examples
filteringdroolssensors

Drools - How to apply advanced filtering using a window?


Good day everyone,

Currently I'm working with a simple temperature sensor that basically updates 4 times a second on the current temperature, and filter using the following rule in Drools:

rule "temperature detected"
  when
    $acc: Number ( doubleValue > 22.0 ) from accumulate(
                $sensorMessage: SensorMessage($topic: topic, $timeStamp: timestamp, $data: data) over window:time ( 10s ) from entry-point "temperature_sensor",
                average ( $sensorMessage.getDouble("temperature", -100) )
            )
  then
    logger.info("Received temperature data > 22.0 -> " + $acc);
  end

This, however, logs the console after EVERY sensor update as long as the accumulated temperature average is larger than 22, over a window of 10 seconds.

This of course, is far from ideal.


Would it be possible to, after a sensor update has been received, continue listening UNTIL no more updates are received for, say, 3 seconds. Then log the starting time of when a sensor update first is detected, and the ending time. And only log these two timestamps if at least 10 updates have been received altogether, and some criterium is met.

Example scenarios (T being some target temperature):

  • If a motion sensor sends 20 updates within 2 seconds, the time of the first update and time of the last update is logged.
  • If a motion sensor sends 6 updates in 1 second, then another 6 updates after 5 seconds, nothing happens, as we expect more motion sensor updates to properly classify it as motion.
  • If a temperature sensor sends 10 updates within 1 second, and the average of all 10 pings is <= T, nothing happens, however, if it does exceed T, we log a single temperature "alert".

Solution

  • This is what I came-up with after few hours. Some requirements are not clear for me, like what is 'the last update is logged', each one is 'the last'. Following may still require a work to do, but you may get general idea: we have single object in the session we update and monitor.

    import java.lang.Double;
    import java.util.ArrayList;
    
    declare SensorMessage
        @role(event)
        @timestamp(timestamp)
        @expires(10s)
    end 
    
    rule "temperature monitoring"
    when
        $meta: SensorMetadata()
        $acc: Double () from accumulate(
            $message: SensorMessage() over window:time(1s),
            average ($message.getTemperature())
        )
    then
        $meta.setTemperature($acc);
        update($meta);
    end
    
    rule "temperature activated"
    when
        $meta: SensorMetadata(highTemperature == false, temperature >= $meta.targetTemperature)
        $lastMessage: SensorMessage($lastTimestamp: timestamp)
        not (SensorMessage(timestamp > $lastMessage.timestamp))
    then
        $meta.setLastActivated($lastMessage.getTimestamp());
        $meta.setHighTemperature(true);
        update($meta);
        System.out.printf("temperature activated %.2f%n", $meta.getTemperature());
    end
    
    rule "temperature deactivated"
    when
        $meta: SensorMetadata(highTemperature == true, temperature < $meta.targetTemperature)
        $lastMessage: SensorMessage($lastTimestamp: timestamp)
        not (SensorMessage(timestamp > $lastMessage.timestamp))
    then
        $meta.setHighTemperature(false);
        update($meta);
        System.out.printf("temperature deactivated %.2f%n", $meta.getTemperature());
    end
    
    rule "throttle state activated"
    when
        $meta: SensorMetadata(throttleState == false)
        $lastMessage: SensorMessage($lastTimestamp: timestamp)
        not (SensorMessage(timestamp > $lastMessage.timestamp))
        $messages: ArrayList(size > 20) from collect(
            $message: SensorMessage() over window:time(1s)
        )
    then
        $meta.setLastThrottled($lastMessage.getTimestamp());
        $meta.setThrottleState(true);
        update($meta);
        System.out.printf("throttle state activated %d%n", $messages.size());
    end
    
    rule "throttle state deactivated"
    when
        $meta: SensorMetadata(throttleState == true)
        $lastMessage: SensorMessage($lastTimestamp: timestamp)
        not (SensorMessage(timestamp > $lastMessage.timestamp))
        $messages: ArrayList(size <= 20) from collect(
            $message: SensorMessage() over window:time(1s)
        )
    then
        $meta.setThrottleState(false);
        update($meta);
        System.out.printf("throttle state deactivated %d%n", $messages.size());
    end
    

    the test

    @DroolsSession(resources = "file:src/main/resources/draft/rule.drl", showStateTransitionPopup = true, ignoreRules = "* monitoring")
    public class PlaygroundTest extends DroolsAssert {
    
        @RegisterExtension
        public DroolsAssert droolsAssert = this;
    
        @Test
        @TestRules(ignore = "throttle *", expectedCount = {
                "2", "temperature activated",
                "2", "temperature deactivated" })
        public void testTemperatureActivationDeactivation() throws IOException {
            insert(new SensorMetadata(22));
    
            Date date = new Date(0);
            for (int i = 0; i < 100; i++) {
                double temperature = i == 80 ? 100 : i == 81 ? -100 : i < 50 ? i : 1 / i;
                insertAndFire(new SensorMessage(date, temperature));
                date = addMilliseconds(date, 1);
                advanceTime(MILLISECONDS, 1);
            }
    
            advanceTime(10, SECONDS);
            assertFactsCount(1);
        }
    
        @Test
        @TestRules(ignore = "temperature *", expectedCount = {
                "2", "throttle state activated",
                "2", "throttle state deactivated" })
        public void testThrottleMode() throws IOException {
            insert(new SensorMetadata(22));
    
            Date date = new Date(0);
            for (int i = 0; i < 100; i++) {
                insertAndFire(new SensorMessage(date, 22));
                int advanceTime = i * 3;
                date = addMilliseconds(date, advanceTime);
                advanceTime(MILLISECONDS, advanceTime);
            }
    
            advanceTime(10, SECONDS);
            assertFactsCount(1);
        }
    }
    

    and models

    public class SensorMessage {
        private Date timestamp;
        private double temperature;
    
        public SensorMessage(Date timestamp, double temperature) {
            this.timestamp = timestamp;
            this.temperature = temperature;
        }
        // getters
    }
    
    public class SensorMetadata {
        private volatile Date lastActivated;
        private volatile Date lastThrottled;
        private double temperature;
        private boolean highTemperature;
        private boolean throttleState;
        private double targetTemperature;
    
        public SensorMetadata(double targetTemperature) {
            this.targetTemperature = targetTemperature;
        }
        // getters and setters
    }