Skip to content

HomeIO case study with Drools rules

András Jankó edited this page Dec 28, 2017 · 9 revisions

To be able to understand the automation rules based on HomeIO devices first we have to understand the functioning of HomeIO devices. In every section the first part is a presentation of the used devices.

Light system

HomeIO devices for light

Light switches in HomeIO

There are two types of devices to control a light in HomeIO: Push switch and Dimmer switch. Both of them are read only devices.

Push switch

Description: A simple push switch. It can be pressed and released. When released, it bounces back to its original state.

States: PRESSED, RELEASED.

Usage: If pressed, the corresponding light changes its state.

The type name of a Push switch is Light_Switch.

Dimmer switch

There are many types of dimmer switches, but in general, they are used to adjust the brightness of the lights. The concrete type of switch, used in HomeIO is a switch that can be pressed into two directions: Up and Down. Usually, if we short press it, it will act as a regular switch. If we long press it, it will change into dimming mode and increases or decreases the brightness of the light until the switch is held.

HomeIO’s Dimmer switches by default does not provide short press and long press functionality, they have to be handled with an outer logic based on how long the switch is pressed, and into witch direction.

A switch can have three states: pressed up, pressed down or released. The switch has two interaction form: Up and Down. If one of them is pressed, its state is PRESSED, and the other’s state is RELEASED. If none of them is pressed (the state is released), both states are RELEASED. Both of them cannot be pressed at once (physically in HomeIO).

The type name of a Dimmer is Light_Switch_Dimmer. The Up switch ends with Up and the Down switch ends with Down.

Naming example:

C_Light_Switch – Push Switch in room C. Room C has only one push switch.

A_Light_Switch_1 – First Push switch in room A. Room A has three Push switches.

A_Light_Switch_Dimmer_1_Up - First Dimmer in room A. Room A has three Dimmers. This is the Up interaction form of the dimmer. Note, this is a different switch from A_Light_Switch_1.

A_Light_Switch_Dimmer_3_Down – Third Dimmer in room A. This is the Down interaction form of the dimmer.

Lights in HomeIO

Every light in HomeIO has two representation forms: simple and dimmable. Unfortunately, their states are not synchronized. If the Dimmable light’s state is not ZERO, the light’s actual state is the state of the dimmable light, else it is state is the state of the simple light. To avoid this, when a light’s dimmer functionality is used, I always use the dimmable form to control it.

Simple light

States: ON, OFF.

Commands: ON, OFF.

The type name of a Simple light is Lights.

Dimmable light

States: Percent.

Commands: Percent, ON, OFF, ZERO, HUNDRED.

The type name of a Dimmable light is also Lights. To be able to distinguish it from the Simple light, it ends with Analog.

Naming example:

A_Lights – Simple form of light in room A. There is only one light in room A, so no additional ID needed.

A_Lights_Analog – Dimmable form of the above Simple light.

D_Lights_1 – Simple form of the first light in room D. There are two lights in room D

D_Lights_2_Analog – Dimmable form of the second light in room D.

Sensors

Sensors are read only, so they cannot receive commands.

Motion detector

States: MOTION, NOMOTION

Description: If the Motion detector detects us, its state is MOTION for 5 seconds. The Motion detector does not retrigger, so it will always switch to NOMOTION state after 5 seconds, it does not matter if we move again in this interval.

The type name of a Motion detector is Motion_Detector. There is at most one Motion detector per room.

Brightness sensor

Description: The Brightness sensor in HomeIO can detect light from lights and outdoor light sources such as sun.

States: BRIGHTNESS, DARKNESS.

The type name of a Brightness sensor is Brightness_Sensor. There is at most one Brightness sensor per room.

Light for Push switch rule

Based on the above HomeIO devices light switching rules can be created. For Push switches the lights have to change their states.

An example of a rule, switching an outside Simple light – O_Lights_Porch_1 – with the Push switch in room A, A_Light_Switch_3.

rule "A Outdoor lights 1"
    when
        $light: Item( name == "O_Lights_Porch_1", $state : state)

        ItemStateChangedEvent( name == "A_Light_Switch_3", newState == PRESSED )
    then
        if ($state == ON) {
            openhab.postCommand($light, OFF);
        } else {
            openhab.postCommand($light, ON);
        }
end

The name of the rule is A Outdoor lights 1. The activation of the rule is in the when section. The actions are in the then section.

The conditions to activate:

  • An item exists with the name O_Lights_Porch_1. The Item is saved into a variable $light. The light’s current state is saved into a variable $state.
  • The second condition, an ItemStateChangedEvent happened, the Item’s name is A_Lights_Switch_1 and its newState is PRESSED.

The actions:

  • Change the light’s state.
    • If the light’s current state is ON, we send an OFF command to it.
    • If the light’s current state is OFF, we send an ON command to it.

Light for motion rule

With the help of a Motion detector and Brightness sensor, we can compose rules to switch on the lights if the Motion detector detects motion, the Brightness sensor detects darkness and the Light is off. Because of the fact, that one Motion detector and one Brightness sensor can be at most one in a room in HomeIO and they occur together, so these rules suite to a template. This is a good example to show the Drools Rule Template functionality.

The rule template has 3 parameters: brightnessSensor, motionDetector and light. These parameters are in an excel table. Each row has 3 columns for the 3 parameters. Every row will be a new rule where the parameters are the row’s cell values.

The data from the table:

Motion detector Brightness sensor Light to switch
A_Motion_Detector A_Brightness_Sensor A_Lights_Analog
B_Motion_Detector B_Brightness_Sensor B_Lights_1_Analog
D_Motion_Detector D_Brightness_Sensor D_Lights_1_Analog
D_Motion_Detector D_Brightness_Sensor D_Lights_2_Analog
E_Motion_Detector E_Brightness_Sensor E_Lights_Analog
F_Motion_Detector F_Brightness_Sensor F_Lights_1_Analog
F_Motion_Detector F_Brightness_Sensor F_Lights_2_Analog
G_Motion_Detector G_Brightness_Sensor G_Lights_Analog

The first row is just a description. Drools will start processing the table from the second row which is specified in the configuration of the resource. Note, this is the full content of the table, so no additional data are needed.

The rule template’s relevant snippet.

rule "Lights for motion_@{row.rowNumber}"
    when
       $light : Item( name == "@{light}", state == ZERO )
       Item( name == "@{brightnesSensor}", state == DARKNESS )
		
       ItemStateChangedEvent( name == "@{motionDetector}", newState == MOTION)
    then
        openhab.postCommand($light, ON);
end

The @{variableName} part will be replaced with the corresponding data. The row.rowNumber is defined by the Drools, and it can be used to get the actual row number in the table. It is used to generate individual rule names. For the Light’s state, the ZERO is used instead of the OFF because the inside lights have dimmer functionality, so the Dimmable form of the light is used, which only has percent values.

When Drools generates rules from the template, this will one of them:

rule "Lights for motion_1"
    when
       $light : Item( name == "A_Lights_Analog", state == ZERO )
       Item( name == "A_Brightness_Sensor", state == DARKNESS )
		
       ItemStateChangedEvent( name == "A_Motion_Detector", newState == MOTION)
    then
        openhab.postCommand($light, ON);
end

Dimmer functionality rules

With the help of the Dimmable switch, we can switch a Dimmable light on or off with a Short press, or increase or decrease the light’s brightness with a Long press.

In HomeIO – as mentioned in the description of the Dimmer switch – the Short press and Long press functionality is not provided by the Dimmer switch, it has to be handled with outer logic. This enables us to customize, how long we have to hold the switch to change into dimming mode and the intensity of the dimming.

Short press

A press is a Short press if we release the switch within 1 second. As a result, when pressing a switch, we do not actually know, what will happen, only after we release it. If this press was an up press, the light is switched ON. If the press was a down press, the light is switched off.

This is an example rule in room E, where two Dimmer switches control one light.

rule "E Lights Switch On"
    when
        $light : Item( name == "E_Lights_Analog") 

        $switchPressed : ItemStateHistory( ( 
        name == "E_Light_Switch_Dimmer_1_Up" || 
        name == "E_Light_Switch_Dimmer_2_Up" ) && 
        newState == PRESSED )

        ItemStateChangedEvent ( 
        this after[0s,1s] $switchPressed && (
        name == "E_Light_Switch_Dimmer_1_Up" || 
        name == "E_Light_Switch_Dimmer_2_Up" ) && 
        newState == RELEASED )
    then
        openhab.postCommand($light, ON);
end

A technical reminder, the ItemStateChangedEvent is only available in the simulated now. For past events ItemStateHistory is used. So first, we find the event, when the switch was pressed and save it into a variable, $switchPressed. If within 1 second we release the switch, a Short press is happened.

Long press

A press is a Long press, if we hold the switch for more than 1 second. This starts changing the light’s brightness until we hold it. If the press is an up press, it increases the light’s brightness, if the percent value of the light is less than 100. If the press is a down press, it starts decreasing the light’s brightness, if the percent value of the light is more than 0.

The detection of the Long press is harder than the Short press because there we received and event (switch release), from which the short press could be unambiguously detected. As opposed to this, when the Long press starts, there is no such an event, the Drools could react to because Drools only supports event based reaction, not time based.

The action to the Long press is also not trivial. This is also because of the fact that Drools is not used for time based events. As a result, increasing or decreasing the light’s brightness with a fixed period is not supported.

To solve these problems, I have created a helper class for dimming the light, TimedDimmer. Its parameters are the light to dim, increase or decrease, dimming intensity, delay, period to repeat. It implements the ITimedCommand interface.

With the help of this, the command can be issued with Drools, and the actual execution of the command is happening elsewhere. The delayed functionality is used, to solve the Long press problem. When we press the switch, we send this command with a delay of 1 second, and if we release the switch within this interval, we retract this command before it is executed.

For this functionality, Drools decision table is used. Its purpose is similar to rule templates, but here is not just the data specified in the table. It has additional cells, for processing the data cell’s values. There is no template attached to it, so it contains all information for the rule creation.

The relevant parts of the table which handles the sending of a TimedDimmer command:

The red cells are the cells containing the rule logic, and the green cells are the data cells. The blue cells are just description. First row of the red cells specifies whether the column is evaluated as a condition or an action. If the first row was a condition in that column, the second row of the red cells specifies the fact or event, upon which we have a condition. The third row of the red cells is where we have the condition to the attribute of the above specified fact or event. If we want to use more attribute condition to one fact or event, we have to merge its cell with its neighboring column, like it is done with ItemStateChangedEvent. If the first row was an action, the second row is not needed, and the third row is the action to execute if all conditions match.

The rules are generated from the logic specified in the red cells, and the data specified in the green cells. Every green row will be one rule. If the condition’s third cell is simple attribute, the rule will check whether the attribute equals to the corresponding data cell, such as name and newState. If the data is a list, like in the second column, the processing of the list can be specified. We can set what kind of logical connection should be between the list elements. The forall(||) means that the list elements will be in logical or connection. The $ part is where the list element is placed in the generated rule. The name == "$" means that the event’s item name has to be one of the elements in the list. In the action column the data from the cell is the place where the $param is.

This part of the table composes rules which will retract the timed command for the corresponding item.

Roller shades

HomeIO devices for roller shades

Up/Down switch

Up/Down switch is the same device as the Dimmer switch, only the logic built upon it is different. It also has the two interaction forms: Up and Down.

The type name of an Up/Down switch is Up_Down_Switch. The Up switch ends with Up and the Down switch ends with Down.

Naming example

A_Up_Down_Switch_1_Up

D_Up_Down_Switch_Down

Roller shades motors

Roller shades have motors, to control them. A motor has two interaction forms: Up and Down. Both can be turned ON or OFF. If only one of them is turned ON, the roller shades goes into that direction. If both of them are ON, it goes down, however, this should be avoided.

Up motor Down motor Direction
OFF OFF None
OFF ON Down
ON OFF Up
ON ON Down

The type name of a Roller shades motor is Roller_Shades. The Up motor ends with Up and the Down motor ends with Down.

Naming example:

A_Roller_Shades_1_Up

D_Roller_Shades_Down

Roller shades openness

Description: Roller shades openness is a sensor indicating the openness of the corresponding roller shades. It is used to switch off the motors if fully open or fully lowered. It is a NumberItem.

State: Any percent value. The HUNDRED and ZERO static helpers can be used.

Type name: Roller_Shades_Openness.

Roller shades functionality rules

With the Up/Down switch, there are two possible ways to control the Roller shades. The first one works similarly to the Dimmer functionality. It goes up as long as we hold the switch in the Up direction. However, this has a disadvantage that we have to wait it out, until it goes up. The second one is when we press the switch into up direction, then the Roller shades goes up until it is pressed into the opposite direction or fully opens. This is the basic HomeIO functionality for this, so I have decided to implement this. The following logic is represented in a diagram below:

This logic could be separated into two rules for the two events: Pressed up and Pressed down.

The rule to handle the up switching:

rule "D roller shades up switch"
    when
        $upMotor : Item( name == "D_Roller_Shades_Up", state == OFF)
        $downMotor : Item( name == "D_Roller_Shades_Down", $stateDown : state) 
        $itemState : ItemStateChangedEvent( name == "D_Up_Down_Switch_Up", newState == OPEN )
    then
        if ( $stateDown == OFF ) {
        	openhab.postCommand($upMotor, ON);
        } else {
            openhab.postCommand($downMotor, OFF);
        }
end

The rule to switch off the motor when fully open:

rule "D roller shades up off"
	when
		$upMotor : Item( name == "D_Roller_Shades_Up", state == ON)
		ItemStateChangedEvent( name == "D_Roller_Shades_Openness", newState == HUNDRED )
	then
		openhab.postCommand($upMotor, OFF);
end

Based on the logic of the up switching, the down switching is created:

rule "D RollerShades down switch"
    when
        $upMotor : Item( name == "D_Roller_Shades_Up", $stateUp : state)
        $downMotor : Item( name == "D_Roller_Shades_Down", state == OFF) 
        $itemState : ItemStateChangedEvent( name == "D_Up_Down_Switch_Down", newState == OPEN )
    then
        if( $stateUp == OFF) {
            openhab.postCommand($downMotor, ON);
        } else {
            openhab.postCommand($upMotor, OFF);
        }
end

The rule to switch off the motor when fully lowered:

rule "D roller shades down off"
	when
		$downMotor : Item( name == "D_Roller_Shades_Down", state == ON) 
		ItemStateChangedEvent( name == "D_Roller_Shades_Openness", newState == ZERO)
	then
		openhab.postCommand($downMotor, OFF);
end

Alarm system

HomeIO alarm system’s functioning in wired mode. The alarm can be armed and disarmed with the Alarm keypad. If the alarm is armed, it will be only activated after a short time, to leave time for leaving the house. After that, if any movement or door opening is detected, the Sirens are switched on. I have also implemented these functionalities, plus I have expanded it with another. The idea behind it is to try to detect someone’s presence with different sensors. The motion detectors are usually easy to trick because they usually use infrared radiation to detect changes in the temperature. If we use a big object to shield us, it will not be able to detect us. Most types of door detectors can also be bypassed. They usually use magnetic field to detect if the door is closed. With a high enough magnetic field, the detector can be tricked. Based on this, I wanted to provide another possibility to detect an intruder’s presence. If someone breaks into our house, he usually does it at night, and he uses some kind of light source, which can be detected with the brightness sensors, and the alarm can be raised. As this might not be as powerful individually as the previous ones, combination of them can offer a greater and unexpected defense. In an alarm system, the more ways it has to detect an intruder, the better it is.

HomeIO devices for alarm

As mentioned in the precious section, the alarm uses Motion detectors, Brightness sensors, Door detectors, Alarm keypad, Sirens.

Door detector

Description: The Door detector is used to detect opened doors. The Door detector is a ContactItem.

States: DOOR_OPENED, DOOR_CLOSED.

Type name: Door_Detector.

Alarm keypad

Description: The Alarm keypad in HomeIO. The code is unchangeable, 1234. After entering the code, it can be armed with 1, and disarmed with 2. The Alarm keypad is a ContactItem.

States: ARMED and DISARMED.

Name: E_Alarm_Key_Pad_Armed.

Siren

Description: The Siren can be turned on or off. If it is on, red light and sound indicates it. The Siren is a SwitchItem.

States: ON, OFF.

Type name: Siren. There are two Sirens. An indoor in room E and an outdoor O.

Alarm armed rules

I have created a helper event AlarmArmed. Its purpose is to indicate wheter the alarm is armed. It can also be used for time comparison with other events. There is only one event present at a time (for easier time comparison).

If the alarm is armed, an AlarmArmed event is fired.

rule "Alarm armed"
    when
        not( AlarmArmed() )
        ItemStateChangedEvent( name == "E_Alarm_Key_Pad_Armed", newState == ARMED)
    then
        insert(new AlarmArmed());
end

If the alarm is disarmed, this event is retracted.

rule "Alarm disarmed"
    when
        $armed : AlarmArmed()
        
        ItemStateChangedEvent( name == "E_Alarm_Key_Pad_Armed", newState == DISARMED)
    then
        retract($armed);
end

Alarm raised rules

I have created a helper event AlarmRaised. Its purpose is to avoid code duplication. The individual intruder detector rules can just fire this event.

If an AlarmRaised event is detected, the Sirens switch on.

rule "Alarm raised"
    when
        AlarmRaised()
    then
        openhab.postCommand("E_Siren", ON);
        openhab.postCommand("O_Siren", ON);
end

When the alarm is raised, the sirens should go off, if the alarm is disarmed with the keypad.

rule "Alarm stop"
    when
        not( AlarmArmed() )
        $alarmRaised : AlarmRaised()
    then
        retract($alarmRaised);
        openhab.postCommand("E_Siren", OFF);
        openhab.postCommand("O_Siren", OFF);
end

Alarm for motion or opened door rule

This rule is responsible for raising the alarm if movement or door opening detected.

The conditions of this rule:

  • The alarm is armed.
  • Alarm is not raised yet.
  • 10 seconds elapsed since the alarm arming.
  • Motion or door opening detected.
rule "Alarm for motion or door open"
    when
        $armed : AlarmArmed()
        not ( AlarmRaised() )
        
        ItemStateChangedEvent( this after[10s] $armed && ( 
        ( name matches ".*Motion_Detector" && newState == MOTION ) || 
        ( name matches ".*Door_Detector.*" && newState == DOOR_OPENED ) ) )
    then
        insert(new AlarmRaised());
end

The AlarmArmed event is saved into an $armed variable. The ItemStateChangedEvent( this after[10s] $armed part guaranties that our rule will only activate if the events happened 10 seconds after the arming of the alarm. The matches keyword is used for regex expressions. With its help, we can check if the Motion_Detector or the Door_Detector substring is present in an item name, as opposed to listing all available Motion detector and Door detector names. The action of this rule (insert(new AlarmRaised())) is to create an AlarmRaised event.

Alarm for light rule

The introduced alarm for light with the help of the Brightness sensors. The alarm is raised when two sequential brightness is detected in a short time. The alarm only activates for the second brightness to avoid false detections. If a brightness is detected, other sensors also detects it. To filter this out, between the two brightness, some time has to elapse.

The conditions of this rule:

  • The alarm is armed.
  • Alarm is not raised yet.
  • 10 seconds elapsed since the alarm arming.
  • First brightness detected.
  • 5 seconds after, but within 1 minute of the first brightness, a second brightness is also detected.
rule "Alarm for light"
    when
        $armed : AlarmArmed()
        not ( AlarmRaised() )
        $firstBrigthness : ItemStateHistory(this after[10s] $armed, name matches ".*Brightness_Sensor", newState == BRIGHTNESS)
        
        $secondBrigthness : ItemStateChangedEvent(this after[5s, 1m] $firstBrigthness, name matches ".*Brightness_Sensor", newState == BRIGHTNESS)
    then
        insert(new AlarmRaised());
end