#Examples

The following examples are for file-based rules but most of them are applicable to UI rules as well.

#Trigger when an item changed state

rule 'Turn on light when sensor changed to open' do
  changed Door_Sensor, to: OPEN
  run { Cupboard_Light.on }
end

Use multiple triggers

rule 'Control light based on multiple doors' do
  changed Door_Sensor1, to: OPEN
  changed Door_Sensor2, to: OPEN
  run { Cupboard_Light.on }
end

# Which is the same as:
rule 'Control light based on multiple doors' do
  changed Door_Sensor1, Door_Sensor2, to: OPEN
  run { Cupboard_Light.on }
end

Check against multiple states

rule 'Control light based on door state' do
  changed Door_Sensor, to: [OPEN, CLOSED]
  run { Cupboard_Light << Door_Sensor.open? } # Send a boolean command to a Switch Item
end

#Trigger when a group member changed state

# Assumption: Motion sensor items are named using the pattern RoomName_Motion
# and Light switch items are named with the pattern RoomName_Light
rule 'Generic motion rule' do
  changed Motion_Sensors.members, to: ON
  run do |event|
    light = items[event.item.name.sub('_Motion', '_Light')] # Lookup item name from a string
    light&.on
  end
end

See also: Triggers

#Various ways of sending a command to an item

# Using the shovel operator
Light1 << ON
DimmerItem1 << 100
Set_Temperature << '24 °C'

# Using command predicates
Light1.on
Rollershutter1.up
Player1.play

# Using .command
ColorItem1.command '#ffff00'
ColorItem1.command {r: 255, g: 0xFF, b: 0}

# Send a command to all the array members
# Note The << operator doesn't send a command here because it's for appending to the array
[SwitchItem1, SwitchItem2, SwitchItem3].on
[RollerItem1, RollerItem2, RollerItem3].down
[NumberItem1, NumberItem2, NumberItem3].command 100

Each item type supports command helpers relevant to the type. For example, a SwitchItem supports on and off. See specific item types under OpenHAB::Core::Items

#Dealing with Item States

# Items:
# Number:Temperature Outside_Temperature e.g. 28 °C
# Number:Temperature Inside_Temperature e.g. 22 °C
temperature_difference = Outside_Temperature.state - Inside_Temperature.state
logger.info("Temperature difference: #{temperature_difference}") # "Temperature difference: 6 °C"

Items have predicates to query its state.

Switch1.on?    # => true if Switch1.state == ON
Shutter1.up?   # => true if Shutter1.state == UP

#Detect change duration without creating an explicit timer

rule 'Warn when garage door is open a long time' do
  changed Garage_Door, to: OPEN, for: 15.minutes
  run { Voice.say "Warning, the garage door is open" } # call TTS to the default audio sink
end

#Automatic activation of exhaust fan based on humidity sensor

This uses the evolution_rate persistence feature, coupled with an easy way to specify duration. It is accessed simply through ItemName.persistence_function.

# Note: don't activate the exhaust fan if the bathroom light is off at night
# Sun_Elevation is an Astro item. Its state is positive during daylight
rule "Humidity: Control ExhaustFan" do
  updated BathRoom_Humidity
  triggered do |humidity|
    evo_rate = humidity.evolution_rate(4.minutes.ago, :influxdb)
    logger.info("#{humidity.name} #{humidity.state} evolution_rate: #{evo_rate}")

    if (humidity.state > 70 && evo_rate > 15) || humidity.state > 85
      BathRoom_ExhaustFan.ensure.on if Sun_Elevation.state.positive? || BathRoom_Light.state.nil? || BathRoom_Light.on?
    elsif humidity.state < 70 || evo_rate < -5
      BathRoom_ExhaustFan.ensure.off
    end
  end
end

#Executing an External Command

OpenHAB offers execute_command_line to execute an external command. However, in Ruby it is also possible to use, amongst others:

  • system: waits for the execution to finish, then returns true (zero exit status), false (non-zero exit status), or nil if the execution fails.
  • backtick operator: waits for the execution to finish, then returns the stdout output of the command as a String.
  • spawn: executes the command and immediately returns control to the caller, leaving the command running in the background. This method returns the pid of the spawned process which must be waited out or detached to avoid creating zombie processes.
  • IO#popen: a more advanced method of executing an external process.

Unlike execute_command_line which expects each command argument to be split up, Ruby's execution methods can accept a single string containing the full command which also allows IO redirections / pipe because it spawns a subshell.

system("ls -l #{OpenHAB::Core.config_folder} > #{OpenHAB::Core.config_folder / "misc" / "directory_listing.txt"}")

# Get the remote Raspberry Pi's core temperature
core_temperature = `ssh pi@192.168.1.20 vcgencmd measure_temp`
logger.info "Raspberry Pi's core #{core_temperature}"

# Reboot a remote Raspberry Pi
pid = spawn("ssh pi@192.168.1.20 sudo reboot")
Process.detach(pid)

#Gem Cleanup

The openHAB JRuby add-on will automatically download and install the latest version of the library according to the settings in jruby.cfg. Over time, the older versions of the library will accumulate in the gem_home directory. The following code saved as gem_cleanup.rb or another name of your choice can be placed in the automation/ruby directory to perform uninstallation of the older gem versions every time openHAB starts up.

require "rubygems/commands/uninstall_command"
require "pathname"

after(3.minutes) do
  cmd = Gem::Commands::UninstallCommand.new

  # uninstall all the older versions of the openhab-scripting gems
  Gem::Specification.find_all
                    .select { |gem| gem.name == "openhab-scripting" }
                    .sort_by(&:version)
                    .tap(&:pop) # don't include the latest version
                    .each do |gem|
    cmd.handle_options ["-x", "-I", gem.name, "--version", gem.version.to_s]
    cmd.execute
  end

  # Delete gems in other ruby versions
  next unless (gem_home = ENV.fetch("GEM_HOME", nil))

  gem_home = Pathname.new(gem_home)
  next unless gem_home.parent.basename.to_s == ".gem"

  gem_home.parent.children.reject { |p| p == gem_home }.each(&:rmtree)
end

#UI rules

#Reset the switch that triggered the rule after 5 seconds

Trigger defined as:

  • When: a member of an item group receives a command
  • Group: Reset_5Seconds
  • Command: ON
logger.info("#{event.item.name} Triggered the rule")
after 5.seconds do
  event.item.off
end

#Update a DateTime Item with the current time when a motion sensor is triggered

Given the following group and items:

Group MotionSensors
Switch Sensor1 (MotionSensors)
Switch Sensor2 (MotionSensors)

DateTime Sensor1_LastMotion
DateTime Sensor2_LastMotion

Trigger defined as:

  • When: the state of a member of an item group is updated
  • Group: MotionSensors
  • State: ON
logger.info("#{event.item.name} Triggered")
items["#{event.item_name}_LastMotion"].update Time.now

#Trigger a Scene with an ON OFF Switch

Use Scenes in combination with Semantic Model

Trigger defined as:

  • When: the state of a member of an item group is updated
  • Group: LightSwitches
# Find scenes for the specific room by matching the scene's tags against the
# Location item name of the room, e.g. "BedRoom1", "Downstairs_BedRoom", etc.
# This is when you have multiple locations/rooms with the same semantic location e.g. (Bedroom)
scenes = rules.scenes.tagged(event.item.location.name)

# If none were defined, try finding scenes assigned to the same semantic location as the motion sensor
# You can have scenes for LivingRoom, LaundryRoom, Garage, etc. provided that they are unique
scenes = rules.scenes.tagged(event.item.location.location_type) if scenes.empty?

# the Active_Scene item can be set to the Scene tag that we wish to activate when motion is detected
# e.g. ACTIVE/RELAXING/MOVIE/READING
# To do nothing when motion was detected, just set the Active_Scene to blank so no scenes would match
active_scene = event.state.on ? Active_Scene.state.to_s : "OFF" # Get the active scene name
scenes.tagged(active_scene).each(&:trigger) # Execute the scenes