Class: OpenHAB::DSL::Rules::BuilderDSL

Inherits:
Object
  • Object
show all
Includes:
Core::EntityLookup, OpenHAB::DSL
Defined in:
lib/openhab/dsl/rules/builder.rb

Overview

Rule configuration for openHAB Rules engine

Constant Summary

Constants included from OpenHAB::DSL

VERSION

Execution Blocks collapse

Configuration collapse

Guards collapse

Guards exist to only permit rules to run if certain conditions are satisfied. Think of these as declarative if statements that keep the run block free of conditional logic, although you can of course still use conditional logic in run blocks if you prefer.

#Guard Combination

Multiple guards can be used on the same rule. All must be satisfied for a rule to execute.

Examples:

rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is OFF and Door is CLOSED" do
  changed LightSwitch, to: ON
  run { OutsideDimmer << 50 }
  only_if { Door.closed? }
  not_if { OtherSwitch.on? }
end

Triggers collapse

Note:

The trigger attachment feature is not available for UI rules.

Triggers specify what will cause the execution blocks to run. Multiple triggers can be defined within the same rule.

#Trigger Attachments

All triggers support event attachments that enable the association of an object to a trigger. This enables one to use the same rule and take different actions if the trigger is different. The attached object is passed to the execution block through the OpenHAB::Core::Events::AbstractEvent#attachment accessor.

Examples:

rule 'Set Dark switch at sunrise and sunset' do
  channel 'astro:sun:home:rise#event', attach: OFF
  channel 'astro:sun:home:set#event', attach: ON
  run { |event| Dark << event.attachment }
end

Instance Method Summary collapse

Methods included from OpenHAB::DSL

after, between, config_description, debounce_for, ensure_states, ensure_states!, holiday_file, holiday_file!, items, only_every, persistence, persistence!, profile, provider, provider!, rule, rule!, rules, scene, scene!, script, script!, shared_cache, sitemaps, store_states, things, throttle_for, timers, transform, unit, unit!

Methods included from Core::ScriptHandling

script_loaded, script_unloaded

Methods included from Core::Actions

notify

Methods included from Core::EntityLookup

#items, #method_missing, #things

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class OpenHAB::DSL

Instance Method Details

#at(item, offset: nil) ⇒ void

This method returns an undefined value.

Creates a trigger based on the date and time stored in a DateTimeItem

The trigger will dynamically update whenever the state of the item changes. If the item is NULL or UNDEF, the trigger will not run.

To trigger just on the time portion of the item, use #every instead, e.g. every :day, at: MyDateTimeItem.

The event passed to run blocks will be a Core::Events::TimerEvent.

Examples:

rule "say hello when the kids get home from school" do
  at HomeFromSchool_Time
  run do
    KitchenEcho_TTS << "hi kids! how was school?"
  end
end

rule "set home from school time" do
  on_load
  every :day, at: "5:00am" do
  run do
    HomeFromSchool_Time.ensure.update(school_day? ? LocalTime.parse("3:30pm") : NULL)
  end
end

Using an offset

rule "Turn on lights 15 minutes before sunset" do
  at Sunset_Time, offset: -15.minutes
  run { Lights.on }
end

Parameters:

  • item (Item, String, Symbol)

    The item (or its name)

  • offset (Duration, Integer, nil) (defaults to: nil)

    Offset the execution time from the time specified in the item. This can be a negative value to execute the rule before the time specified in the item. A Duration will be converted to its total length in seconds, with any excess precision information dropped. @since openHAB 4.3

See Also:



1844
1845
1846
1847
1848
1849
1850
# File 'lib/openhab/dsl/rules/builder.rb', line 1844

def at(item, offset: nil)
  item = item.name if item.is_a?(Item)
  offset ||= 0
  offset = offset.to_i if offset.is_a?(Duration)
  @ruby_triggers << [:at, item, { offset: offset }]
  trigger("timer.DateTimeTrigger", itemName: item.to_s, offset: offset)
end

#between(range) ⇒ void

This method returns an undefined value.

Only execute rule if the current time is between the supplied time ranges.

If the range is of strings, it will be parsed to an appropriate time class.

Examples:

rule "Between guard" do
  changed MotionSensor, to: OPEN
  between "6:05".."14:05:05" # Include end
  run { Light.on }
end
rule "Between guard" do
  changed MotionSensor, to: OPEN
  between "6:05".."14:05:05" # Excludes end second
  run { Light.on }
end
rule "Between guard" do
  changed MotionSensor, to: OPEN
  between LocalTime.of(6, 5)..LocalTime.of(14, 15, 5)
  run { Light.on }
end

String of LocalTime

rule 'Log an entry if started between 3:30:04 and midnight using strings' do
  on_load
  run { logger.info ("Started at #{LocalTime.now}")}
  between '3:30:04'..LocalTime::MIDNIGHT
end

rule 'Log an entry if started between 3:30:04 and midnight using LocalTime objects' do
  on_load
  run { logger.info ("Started at #{LocalTime.now}")}
  between LocalTime.of(3, 30, 4)..LocalTime::MIDNIGHT
end

String of MonthDay

rule 'Log an entry if started between March 9th and April 10ths' do
  on_load
  run { logger.info ("Started at #{Time.now}")}
  between '03-09'..'04-10'
end

rule 'Log an entry if started between March 9th and April 10ths' do
  on_load
  run { logger.info ("Started at #{Time.now}")}
  between MonthDay.of(03,09)..'04-06'
end

Parameters:



609
# File 'lib/openhab/dsl/rules/builder.rb', line 609

prop :between

#changed(*items, to: nil, from: nil, for: nil, attach: nil) ⇒ void

This method returns an undefined value.

Creates a trigger when an item, member of a group, or a thing changed states.

When the changed element is a Thing, the from and to values will accept symbols and strings, where the symbol' matches the supported status.

The event passed to run blocks will be an Core::Events::ItemStateChangedEvent or a Core::Events::ThingStatusInfoChangedEvent depending on if the triggering element was an item or a thing.

Examples:

Single item trigger

rule "Execute rule when a sensor changed" do
  changed FrontMotion_Sensor
  run { |event| logger.info("Motion detected by #{event.item.name}") }
end

Multiple items can be separated with a comma:

rule "Execute rule when either sensor changed" do
  changed FrontMotion_Sensor, RearMotion_Sensor
  run { |event| logger.info("Motion detected by #{event.item.name}") }
end

Group member trigger

rule "Execute rule when member changed" do
  changed Sensors.members
  run { |event| logger.info("Motion detected by #{event.item.name}") }
end

You can optionally provide from and/or to states to restrict the cases in which the rule executes:

rule "Execute rule when item is changed to specific number, from specific number" do
  changed Alarm_Mode, from: 8, to: [14,12]
  run { logger.info("Alarm Mode Updated") }
end

Works with ranges:

rule "Execute when item changed to a range of numbers, from a range of numbers" do
  changed Alarm_Mode, from: 8..10, to: 12..14
  run { logger.info("Alarm Mode Updated") }
end

Works with endless ranges:

rule "Execute rule when item is changed to any number greater than 12"
  changed Alarm_Mode, to: (12..)   # Parenthesis required for endless ranges
  run { logger.info("Alarm Mode Updated") }
end

Works with procs:

rule "Execute when item state is changed from an odd number, to an even number" do
  changed Alarm_Mode, from: proc { |from| from.odd? }, to: proc {|to| to.even? }
  run { logger.info("Alarm Mode Updated") }
end

Works with lambdas:

rule "Execute when item state is changed from an odd number, to an even number" do
  changed Alarm_Mode, from: -> from { from.odd? }, to: -> to { to.even? }
  run { logger.info("Alarm Mode Updated") }
end

Works with regexes:

rule "Execute when item state is changed to something matching a regex" do
  changed Alarm_Mode, to: /armed/
  run { logger.info("Alarm armed") }
end

Delay the trigger until the item has been in the same state for 10 seconds

rule "Execute rule when item is changed for specified duration" do
  changed Closet_Door, to: CLOSED, for: 10.seconds
  run do
    Closet_Light.off
  end
end

for parameter can be a proc that returns a duration:

Alarm_Delay << 20

rule "Execute rule when item is changed for specified duration" do
  changed Alarm_Mode, for: -> { Alarm_Delay.state.to_i.seconds }
  run { logger.info("Alarm Mode Updated") }
end

Works with Things:

rule "Execute rule when thing is changed" do
  changed things["astro:sun:home"], :from => :online, :to => :uninitialized
  run { |event| logger.info("Thing #{event.uid} status <trigger> to #{event.status}") }
end

Trigger when any Thing changes status

rule "Thing status monitoring" do
  changed things, to: :offline
  run do |event|
    Notification.send("Thing #{event.thing.uid} is offline")
  end
end

Real World Example

rule "Log (or notify) when an exterior door is left open for more than 5 minutes" do
  changed ExteriorDoors.members, to: OPEN, for: 5.minutes
  triggered {|door| logger.info("#{door.name} has been left open!") }
end

Parameters:

  • items (Item, GroupItem::Members, Thing, ThingUID, Things::Registry)

    Objects to create trigger for.

  • from (State, Array<State>, #===, nil) (defaults to: nil)

    Only execute rule if previous state matches from state(s).

  • to (State, Array<State>, #===, nil) (defaults to: nil)

    Only execute rule if new state matches to state(s).

  • for (java.time.temporal.TemporalAmount, Proc, nil) (defaults to: nil)

    Duration for which the item/thing must remain in the same state before executing the execution blocks. When a proc is provided, it will be called when the rule is triggered to get the duration.

  • attach (Object, nil) (defaults to: nil)

    object to be attached to the trigger



1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
# File 'lib/openhab/dsl/rules/builder.rb', line 1164

def changed(*items, to: nil, from: nil, for: nil, attach: nil)
  changed = Changed.new(rule_triggers: @rule_triggers)
  # for is a reserved word in ruby, so use local_variable_get :for
  duration = binding.local_variable_get(:for)

  @ruby_triggers << [:changed, items, { to: to, from: from, duration: duration }]

  from = [nil] if from.nil?
  to = [nil] if to.nil?
  items.each do |item|
    case item
    when Core::Things::Thing,
         Core::Things::ThingUID,
         Core::Things::Registry,
         Core::Items::Item,
         Core::Items::GroupItem::Members
      nil
    else
      raise ArgumentError,
            "items must be an Item, GroupItem::Members, Thing, ThingUID, or Things::Registry"
    end

    logger.trace { "Creating changed trigger for entity(#{item}), to(#{to.inspect}), from(#{from.inspect})" }

    Array.wrap(from).each do |from_state|
      Array.wrap(to).each do |to_state|
        changed.trigger(item: item, from: from_state, to: to_state, duration: duration, attach: attach)
      end
    end
  end
end

#channel(*channels, thing: nil, triggered: nil, attach: nil) ⇒ void

This method returns an undefined value.

Creates a channel trigger

The channel trigger executes rule when a specific channel is triggered. The syntax supports one or more channels with one or more triggers. thing is an optional parameter that makes it easier to set triggers on multiple channels on the same thing.

The event passed to run blocks will be a Core::Events::ChannelTriggeredEvent.

Examples:

rule "Execute rule when channel is triggered" do
  channel "astro:sun:home:rise#event"
  run { logger.info("Channel triggered") }
end
# The above is the same as each of the below

rule "Execute rule when channel is triggered" do
  channel "rise#event", thing: "astro:sun:home"
  run { logger.info("Channel triggered") }
end

rule "Execute rule when channel is triggered" do
  channel "rise#event", thing: things["astro:sun:home"]
  run { logger.info("Channel triggered") }
end

rule "Execute rule when channel is triggered" do
  channel "rise#event", thing: things["astro:sun:home"].uid
  run { logger.info("Channel triggered") }
end

rule "Execute rule when channel is triggered" do
  channel "rise#event", thing: ["astro:sun:home"]
  run { logger.info("Channel triggered") }
end

rule "Execute rule when channel is triggered" do
  channel things["astro:sun:home"].channels["rise#event"]
  run { logger.info("Channel triggered") }
end

rule "Execute rule when channel is triggered" do
  channel things["astro:sun:home"].channels["rise#event"].uid
  run { logger.info("Channel triggered") }
end
rule "Rule provides access to channel trigger events in run block" do
  channel "astro:sun:home:rise#event", triggered: 'START'
  run { |trigger| logger.info("Channel(#{trigger.channel}) triggered event: #{trigger.event}") }
end
rule "Keypad Code Received test" do
  channel "mqtt:homie300:mosquitto:backgate:keypad#code"
  run do |event|
    logger.info("Received keycode from #{event.channel.thing.uid.id}")
  end
end
rule "Rules support multiple channels" do
  channel "rise#event", "set#event", thing: "astro:sun:home"
  run { logger.info("Channel triggered") }
end
rule "Rules support multiple channels and triggers" do
  channel "rise#event", "set#event", thing: "astro:sun:home", triggered: ["START", "STOP"]
  run { logger.info("Channel triggered") }
end
rule "Rules support multiple things" do
  channel "keypad#code", thing: ["mqtt:homie300:keypad1", "mqtt:homie300:keypad2"]
  run { logger.info("Channel triggered") }
end

Parameters:

  • channels (String, Core::Things::Channel, Core::Things::ChannelUID)

    channels to create triggers for in form of 'binding_id:type_id:thing_id#channel_id' or 'channel_id' if thing is provided.

  • thing (String, Core::Things::Thing, Core::Things::ThingUID) (defaults to: nil)

    Thing(s) to create trigger for if not specified with the channel.

  • triggered (String, Array<String>) (defaults to: nil)

    Only execute rule if the event on the channel matches this/these event/events.

  • attach (Object) (defaults to: nil)

    object to be attached to the trigger



982
983
984
985
986
987
988
989
990
991
992
# File 'lib/openhab/dsl/rules/builder.rb', line 982

def channel(*channels, thing: nil, triggered: nil, attach: nil)
  channel_trigger = Channel.new(rule_triggers: @rule_triggers)
  flattened_channels = Channel.channels(channels: channels, thing: thing)
  triggers = [triggered].flatten
  @ruby_triggers << [:channel, flattened_channels, { triggers: triggers }]
  flattened_channels.each do |channel|
    triggers.each do |trigger|
      channel_trigger.trigger(channel: channel, trigger: trigger, attach: attach)
    end
  end
end

#channel_linked(item: nil, channel: nil, attach: nil) ⇒ void

This method returns an undefined value.

Creates a channel linked trigger

The event passed to run blocks will be an Core::Events::ItemChannelLinkAddedEvent.

Examples:

rule "channel linked" do
  channel_linked
  run do |event|
    logger.info("#{event.link.item.name} linked to #{event.link.channel_uid}.")
  end
end

Parameters:

  • item (Item, String, nil) (defaults to: nil)

    The item to create a trigger for. If nil, all items are matched.

  • channel (Core::Things::Channel, Core::Things::ChannelUID, String, nil) (defaults to: nil)

    The channel to create a trigger for. If nil, all channels are matched.

  • attach (Object) (defaults to: nil)

    object to be attached to the trigger

Since:

  • openHAB 4.0 Support for filtering with item and channel was added



1014
1015
1016
1017
1018
# File 'lib/openhab/dsl/rules/builder.rb', line 1014

def channel_linked(item: nil, channel: nil, attach: nil)
  pattern = (item.nil? && channel.nil?) ? "*" : "#{item || "*"}-#{channel || "*"}"
  @ruby_triggers << [:channel_linked, pattern]
  event("openhab/links/#{pattern}/added", types: "ItemChannelLinkAddedEvent", attach: attach)
end

#channel_unlinked(item: nil, channel: nil, attach: nil) ⇒ void

This method returns an undefined value.

Creates a channel unlinked trigger

The event passed to run blocks will be an Core::Events::ItemChannelLinkRemovedEvent.

Note that the item or the thing it's linked to may no longer exist, so if you try to access those objects they'll be nil.

Examples:

rule "channel unlinked" do
  channel_unlinked
  run do |event|
    logger.info("#{event.link.item_name} unlinked from #{event.link.channel_uid}.")
  end
end

Parameters:

  • item (Item, String, nil) (defaults to: nil)

    The item to create a trigger for. If nil, all items are matched.

  • channel (Core::Things::Channel, Core::Things::ChannelUID, String, nil) (defaults to: nil)

    The channel to create a trigger for. If nil, all channels are matched.

  • attach (Object) (defaults to: nil)

    object to be attached to the trigger

Since:

  • openHAB 4.0 Support for filtering with item and channel was added



1043
1044
1045
1046
1047
# File 'lib/openhab/dsl/rules/builder.rb', line 1043

def channel_unlinked(item: nil, channel: nil, attach: nil)
  pattern = (item.nil? && channel.nil?) ? "*" : "#{item || "*"}-#{channel || "*"}"
  @ruby_triggers << [:channel_unlinked, pattern]
  event("openhab/links/#{pattern}/removed", types: "ItemChannelLinkRemovedEvent", attach: attach)
end

#cron(expression, attach: nil) ⇒ void #cron(second: nil, minute: nil, hour: nil, dom: nil, month: nil, dow: nil, year: nil, attach: nil) ⇒ void

This method returns an undefined value.

Create a cron trigger

The event passed to run blocks will be a Core::Events::TimerEvent.

Overloads:

  • #cron(expression, attach: nil) ⇒ void

    Examples:

    Using a cron expression

    rule "cron expression" do
      cron "43 46 13 ? * ?"
      run { Light.on }
    end

    Parameters:

  • #cron(second: nil, minute: nil, hour: nil, dom: nil, month: nil, dow: nil, year: nil, attach: nil) ⇒ void

    The trigger can be created by specifying each field as keyword arguments.

    When certain fields were omitted:

    • The more specific fields will default to 0 for hour, minute, and second, to MON for dow, and to 1 for dom and month.
    • The less specific fields will default to * or ? as appropriate.

    Each field is optional, but at least one must be specified.

    The same rules for the standard cron expression apply for each field. For example, multiple values can be separated with a comma within a string, and ranges can be specified with a dash or with a Ruby Range.

    Examples:

    Using String values

    # Run every 3 minutes on Monday to Friday
    # equivalent to the cron expression "0 */3 * ? * MON-FRI *"
    rule "Using cron fields" do
      cron minute: "*/3", dow: "MON-FRI"
      run { logger.info "Cron rule executed" }
    end

    Defaults for unspecified fields

    # Run at midnight on the first day of January, February, and March
    # equivalent to the cron expression "0 0 0 1 JAN-MAR ? *"
    rule "Using cron fields" do
      cron month: "JAN-MAR"
      run { logger.info "Cron rule executed" }
    end

    Using Ruby Range values

    # Run on the hour, every hour between 1pm and 5pm
    # equivalent to the cron expression "0 0 13-17 ? * ? *"
    rule "Using cron fields" do
      cron hour: 13..17
      run { logger.info "Cron rule executed" }
    end

    Parameters:

Raises:

  • (ArgumentError)


1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
# File 'lib/openhab/dsl/rules/builder.rb', line 1262

def cron(expression = nil, attach: nil, **fields)
  if fields.any?
    raise ArgumentError, "Cron elements cannot be used with a cron expression" if expression

    cron_expression = Cron.from_fields(fields)
    return cron(cron_expression, attach: attach)
  end

  raise ArgumentError, "Missing cron expression or elements" unless expression

  cron = Cron.new(rule_triggers: @rule_triggers)
  cron.trigger(config: { "cronExpression" => expression }, attach: attach)
end

#debounce_for(debounce_time) ⇒ void

This method returns an undefined value.

Waits until triggers have stopped firing for a period of time before executing the rule.

It ignores triggers that are "bouncing around" (rapidly firing) by ignoring them until they have quiesced (stopped triggering for a while).

#Comparison Table

Guard Triggers Immediately Description
#debounce_for No Waits until there is a minimum interval between triggers.
#throttle_for No Rate-limits the executions to a minimum interval, regardless of the interval between triggers. Waits until the end of the period before executing, ignores any leading triggers.
#only_every Yes Rate-limits the executions to a minimum interval. Immediately executes the first trigger, then ignores subsequent triggers for the period.

#Timing Diagram

The following timing diagram illustrates the difference between #debounce_for, #throttle_for, and #only_every guards:

TIME INDEX ===>                1    1    2    2    3    3    4    4
                     0    5    0    5    0    5    0    5    0    5
Triggers          : "X.X...X...X..XX.X.X....X.XXXXXXXXXXX....X....."
debounce_for 5    : "|......................X.|..............X....."
debounce_for 5..5 : "|....X|....X.|....X....|....X|....X|....X....X"
debounce_for 5..6 : "|.....X...|.....X.|....X.|.....X|.....X.|....X"
debounce_for 5..7 : "|......X..|......X|....X.|......X|......X....."
debounce_for 5..8 : "|.......X.|.......X....|.......X|.......X....."
debounce_for 5..20: "|...................X..|................X....."
# throttle_for will fire every 5 intervals after the "first" trigger
throttle_for 5    : "|....X|....X.|....X....|....X|....X|....X....."
only_every 5      : "X.....X......X....X....X....X....X......X....."

Triggers          : "X.X...X...X..XX.X.X..X...XXXXXXXXXXX.X..X.X..."
debounce_for 5..44: "|...........................................X."

# Notice above, triggers keep firing with intervals less than 5, so
# debouncer keeps waiting, but puts a stop at 44 (the end of range).

Examples:

Wait until item stopped changing for at least 1 minute before running the rule

rule do
  changed Item1
  debounce_for 1.minute
  run { ... }
end

Alert when door is open for a while

# Note: When combined with a state check (only_if), this becomes functionally
# equivalent to the changed duration feature.
rule "Door alert" do
  changed Door_State
  debounce_for 10.minutes
  only_if { Door_State.open? }
  run { Notification.send("The Door has been open for 10 minutes!") }
end

Parameters:

  • debounce_time (Duration, Range)

    The minimum interval between two consecutive triggers before the rules are allowed to run.

    When specified just as a Duration or an endless range, it sets the minimum interval between two consecutive triggers before rules are executed. It will wait endlessly unless this condition is met or an end of range was specified.

    When the end of the range is specified, it sets the maximum amount of time to wait from the first trigger before the rule will execute, even when triggers continue to occur more frequently than the minimum interval.

    When an equal beginning and ending values are given, it will behave just like #throttle_for.

See Also:



750
751
752
753
# File 'lib/openhab/dsl/rules/builder.rb', line 750

def debounce_for(debounce_time)
  idle_time = debounce_time.is_a?(Range) ? debounce_time.begin : debounce_time
  debounce(for: debounce_time, idle_time: idle_time)
end

#delay(duration) ⇒ void

This method returns an undefined value.

Add a wait between or after run blocks.

The delay property is a non thread-blocking element that is executed after, before, or between run blocks.

Examples:

rule "delay execution" do
  changed MotionSensor, to: CLOSED
  delay 5.seconds
  run { Light.off }
end
rule 'Delay sleeps between execution elements' do
  on_load
  run { logger.info("Sleeping") }
  delay 5.seconds
  run { logger.info("Awake") }
end

Like other execution blocks, multiple can exist in a single rule.

rule 'Multiple delays can exist in a rule' do
  on_load
  run { logger.info("Sleeping") }
  delay 5.seconds
  run { logger.info("Sleeping Again") }
  delay 5.seconds
  run { logger.info("Awake") }
end

You can use Ruby code in your rule across multiple execution blocks like a run and a delay.

rule 'Dim a switch on system startup over 100 seconds' do
  on_load
  100.times do
    run { DimmerSwitch.dim }
    delay 1.second
  end
end

Parameters:



418
# File 'lib/openhab/dsl/rules/builder.rb', line 418

prop_array :delay, array_name: :run_queue, wrapper: Delay

#dependencies(trigger_types = %i[changed updated]) ⇒ Array<Item, GroupItem::Members>

Returns all Items (or GroupItem::Members) referenced by the specified trigger types in this rule.

Examples:

Ensure all dependencies have a state when executing a rule

rule do |rule|
  changed Item1, Item2, Item3
  only_if { rule.dependencies.all?(&:state?) }
  run { FormulaItem.update(Item3.state - (Item1.state + Item2.state)) }
end

Parameters:

  • trigger_types (Symbol, Array<Symbol>) (defaults to: %i[changed updated])

    Trigger types to search for dependencies

Returns:



520
521
522
523
524
525
526
527
528
# File 'lib/openhab/dsl/rules/builder.rb', line 520

def dependencies(trigger_types = %i[changed updated])
  trigger_types = Array.wrap(trigger_types)

  ruby_triggers.flat_map do |t|
    next [] unless trigger_types.include?(t.first)

    t[1].select { |i| i.is_a?(Item) || i.is_a?(GroupItem::Members) }
  end.uniq
end

#description(value) ⇒ void

This method returns an undefined value.

Set the rule's description.

Parameters:

  • value (String)


474
# File 'lib/openhab/dsl/rules/builder.rb', line 474

prop :description

#enabled(value) ⇒ void

This method returns an undefined value.

Enable or disable the rule from executing

Examples:

rule "disabled rule" do
  enabled(false)
end

Parameters:

  • value (true, false)


504
# File 'lib/openhab/dsl/rules/builder.rb', line 504

prop :enabled

#event(topic, source: nil, types: nil, attach: nil) ⇒ void

This method returns an undefined value.

Creates a trigger on events coming through the event bus

Examples:

rule "thing updated" do
  event("openhab/things/*/updated", types: "ThingUpdatedEvent")
  run do |event|
    logger.info("#{event.thing.uid} updated")
  end
end

Parameters:

  • topic (String)

    The topic to trigger on; can contain the wildcard *.

  • source (String, nil) (defaults to: nil)

    The sender of the event to trigger on. Default does not filter on source.

  • types (String, Array<String>, nil) (defaults to: nil)

    Only subscribe to certain event types. Default does not filter on event types.



1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
# File 'lib/openhab/dsl/rules/builder.rb', line 1788

def event(topic, source: nil, types: nil, attach: nil)
  types = types.join(",") if types.is_a?(Enumerable)
  # @deprecated OH3.4 - OH3 config uses eventXXX vs OH4 uses `topic`, `source`, and `types`
  # See https://github.com/openhab/openhab-core/pull/3299
  trigger("core.GenericEventTrigger",
          eventTopic: topic,
          eventSource: source,
          eventTypes: types, # @deprecated OH3.4
          topic: topic,
          source: source,
          types: types,
          attach: attach)
end

#every(*values, at: nil, offset: nil, attach: nil) ⇒ void

This method returns an undefined value.

Create a rule that executes at the specified interval.

The event passed to run blocks will be a Core::Events::TimerEvent.

For a more complex schedule, use #cron.

Examples:

rule "Daily" do
  every :day, at: '5:15'
  run do
    Light.on
  end
end

The above rule could also be expressed using LocalTime class as below

rule "Daily" do
  every :day, at: LocalTime.of(5, 15)
  run { Light.on }
end

Specific day of the week

rule "Weekly" do
  every :monday, at: '5:15'
  run do
    Light.on
  end
end

Symbolic interval

rule "Often" do
  every :minute
  run do
    Light.on
  end
end
rule "Hourly" do
  every :hour
  run do
    Light.on
  end
end

Duration interval

rule "Often" do
  every 5.minutes
  run do
    Light.on
  end
end

MonthDay

rule 'Every 14th of Feb at 2pm' do
  every '02-14', at: '2pm'
  run { logger.info "Happy Valentine's Day!" }
end

Multiple day-of-week

rule "Weekend" do
  every :saturday, :sunday, at: "10:00"
  run { logger.info "It's the weekend!" }
end

Trigger at the time portion of a DateTime Item

rule "Every day at sunset" do
  every :day, at: Sunset_Time
  run { logger.info "It's sunset!" }
end

Using DateTime trigger with offset

rule "Every day before sunset" do
  every :day, at: Sunset_Time, offset: -20.minutes
  run { logger.info "It's almost sunset!" }
end

Parameters:

  • values (String, Duration, java.time.MonthDay, :second, :minute, :hour, :day, :week, :month, :year, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday)

    When to execute rule. Multiple day-of-week can be specified. Otherwise, only one value is allowed.

  • at (LocalTime, String, Core::Items::DateTimeItem, nil) (defaults to: nil)

    What time of day to execute rule If value is :day, at can be a DateTimeItem, and the trigger will run every day at the (time only portion of) current state of the item. If the item is NULL or UNDEF, the trigger will not run.

  • offset (Duration, Integer, nil) (defaults to: nil)

    Offset the execution time from the time specified in the item. This can be a negative value to execute the rule before the time specified in the item. A Duration will be converted to its total length in seconds, with any excess precision information dropped. @since openHAB 4.3

  • attach (Object) (defaults to: nil)

    Object to be attached to the trigger

Raises:

  • (ArgumentError)

See Also:



1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
# File 'lib/openhab/dsl/rules/builder.rb', line 1385

def every(*values, at: nil, offset: nil, attach: nil)
  raise ArgumentError, "Missing values" if values.empty?
  raise ArgumentError, "Offset can only be used when 'at' is given a DateTimeItem" if offset && !at.is_a?(Item)

  if Cron.all_dow_symbols?(values)
    @ruby_triggers << [:every, values.join(", "), { at: at }]
    return cron(Cron.from_dow_symbols(values, at), attach: attach)
  end

  if values.size != 1
    raise ArgumentError,
          "Multiple values are only allowed for day-of-week. " \
          "Otherwise only one value is allowed, given: #{values.size}"
  end

  value = values.first
  value = java.time.MonthDay.parse(value.to_str) if value.respond_to?(:to_str)

  @ruby_triggers << [:every, value, { at: at }]

  if value == :day && at.is_a?(Item)
    # @deprecated OH 3.4 - attachments are supported in OH 4.0+
    if Core.version <= Core::V4_0 && !attach.nil?
      raise ArgumentError, "Attachments are not supported with dynamic datetime triggers in openHAB 3.x"
    end

    offset ||= 0
    offset = offset.to_i # Duration#to_i converts it to seconds, but we also want to convert float/string to int
    @ruby_triggers.last[2][:offset] = offset
    return trigger("timer.DateTimeTrigger", itemName: at.name, timeOnly: true, offset: offset, attach: attach)
  end

  cron_expression = case value
                    when Symbol then Cron.from_symbol(value, at)
                    when Duration then Cron.from_duration(value, at)
                    when java.time.MonthDay then Cron.from_monthday(value, at)
                    else raise ArgumentError, "Unknown interval"
                    end
  cron(cron_expression, attach: attach)
end

#inspectString

Returns:

  • (String)


2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
# File 'lib/openhab/dsl/rules/builder.rb', line 2136

def inspect
  <<~TEXT.tr("\n", " ")
    #<OpenHAB::DSL::Rules::Builder: #{uid}
    triggers=#{triggers.inspect},
    run blocks=#{run.inspect},
    on_load=#{!@on_load.nil?},
    Trigger Conditions=#{trigger_conditions.inspect},
    Trigger UIDs=#{triggers.map(&:id).inspect},
    Attachments=#{attachments.inspect}
    >
  TEXT
end

#item_added(pattern = "*", attach: nil) ⇒ void

This method returns an undefined value.

Creates an item added trigger

The event passed to run blocks will be an Core::Events::ItemAddedEvent.

Examples:

rule "item added" do
  item_added
  run do |event|
    logger.info("#{event.item.name} added.")
  end
end

Parameters:

  • pattern (String, nil) (defaults to: "*")

    The pattern to match items against

  • attach (Object) (defaults to: nil)

    object to be attached to the trigger

Since:

  • openHAB 4.0 Support for pattern filter was added



1650
1651
1652
1653
# File 'lib/openhab/dsl/rules/builder.rb', line 1650

def item_added(pattern = "*", attach: nil)
  @ruby_triggers << [:item_added, pattern]
  event("openhab/items/#{pattern}/added", types: "ItemAddedEvent", attach: attach)
end

#item_removed(pattern = "*", attach: nil) ⇒ void

This method returns an undefined value.

Creates an item removed trigger

The event passed to run blocks will be an Core::Events::ItemRemovedEvent.

Examples:

rule "item removed" do
  item_removed
  run do |event|
    logger.info("#{event.item.name} removed.")
  end
end

Parameters:

  • pattern (String, nil) (defaults to: "*")

    The pattern to match items against

  • attach (Object) (defaults to: nil)

    object to be attached to the trigger

Since:

  • openHAB 4.0 Support for pattern filter was added



1673
1674
1675
1676
# File 'lib/openhab/dsl/rules/builder.rb', line 1673

def item_removed(pattern = "*", attach: nil)
  @ruby_triggers << [:item_removed, pattern]
  event("openhab/items/#{pattern}/removed", types: "ItemRemovedEvent", attach: attach)
end

#item_updated(pattern = "*", attach: nil) ⇒ void

This method returns an undefined value.

Creates an item updated trigger

The event passed to run blocks will be an Core::Events::ItemUpdatedEvent.

Examples:

rule "item updated" do
  item_updated
  run do |event|
    logger.info("#{event.item.name} updated.")
  end
end

Parameters:

  • pattern (String, nil) (defaults to: "*")

    The pattern to match items against

  • attach (Object) (defaults to: nil)

    object to be attached to the trigger



1695
1696
1697
1698
# File 'lib/openhab/dsl/rules/builder.rb', line 1695

def item_updated(pattern = "*", attach: nil)
  @ruby_triggers << [:item_updated, pattern]
  event("openhab/items/#{pattern}/updated", types: "ItemUpdatedEvent", attach: attach)
end

#name(value) ⇒ void

This method returns an undefined value.

Set the rule's name.

Parameters:

  • value (String)


464
# File 'lib/openhab/dsl/rules/builder.rb', line 464

prop :name

#not_if {|event| ... } ⇒ void

This method returns an undefined value.

Prevents execution of rules when the block's result is true and allows it when it's true.

Examples:

rule "Set OutsideDimmer to 50% if LightSwtich turned on and OtherSwitch is OFF" do
  changed LightSwitch, to: ON
  run { OutsideDimmer << 50 }
  not_if { OtherSwitch.on? }
end

Multiple #not_if statements can be used and if any of them are not satisfied the rule will not run.

rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is OFF and Door is not CLOSED" do
  changed LightSwitch, to: ON
  run { OutsideDimmer << 50 }
  not_if { OtherSwitch.on? }
  not_if { Door.closed? }
end

Yield Parameters:

Yield Returns:

  • (Boolean)

    A value indicating if the rule should not run.



670
671
672
# File 'lib/openhab/dsl/rules/builder.rb', line 670

prop_array(:not_if) do |item|
  raise ArgumentError, "Object passed to not_if must be a proc" unless item.is_a?(Proc)
end

#on_load(delay: nil, attach: nil) ⇒ void

This method returns an undefined value.

Creates a trigger that executes when the script is loaded

Execute the rule whenever the script is first loaded, including on openHAB start up, and on subsequent reloads on file modifications. This is useful to perform initialization routines, especially when combined with other triggers.

Examples:

rule "script startup rule" do
  on_load
  run do
    <calculate some item state>
  end
end
rule "Ensure all security lights are on" do
  on_load
  run { Security_Lights.on }
end

Parameters:

  • delay (Duration) (defaults to: nil)

    The amount of time to wait before executing the rule. When nil, execute immediately.

  • attach (Object) (defaults to: nil)

    Object to be attached to the trigger

Raises:

  • (ArgumentError)


1452
1453
1454
1455
1456
1457
1458
# File 'lib/openhab/dsl/rules/builder.rb', line 1452

def on_load(delay: nil, attach: nil)
  # prevent overwriting @on_load
  raise ArgumentError, "on_load can only be used once within a rule" if @on_load

  @on_load = { module: SecureRandom.uuid, delay: delay }
  attachments[@on_load[:module]] = attach
end

#on_start(at_level: nil, at_levels: nil, attach: nil) ⇒ void

This method returns an undefined value.

Creates a trigger that executes when openHAB reaches a certain start level

The event passed to run blocks will be a Core::Events::StartlevelEvent.

This will only trigger once during openHAB start up. It won't trigger on script reloads.

Examples:

rule "Trigger at openHAB system start" do
  on_start # trigger at the default startlevel 100
  run { logger.info "openHAB start up complete." }
end

Trigger at a specific start level

rule "Trigger after things are loaded" do
  on_start at_level: :things
  run { logger.info "Things are ready!" }
end

Trigger at multiple levels

rule "Multiple start up triggers" do
  on_start at_levels: %i[ui things complete]
  run do |event|
    logger.info "openHAB startlevel has reached level #{event.startlevel}"
  end
end

Parameters:

  • at_level (Integer, :rules, :ruleengine, :ui, :things, :complete) (defaults to: nil)

    Zero or more start levels. Note that Startlevels less than 40 are not available as triggers because the rule engine needs to start up first before it can execute any rules

    Symbol Start Level
    :osgi 10
    :model 20
    :state 30
    :rules 40
    :ruleengine 50
    :ui 70
    :things 80
    :complete 100
  • at_levels (Array<Integer,:rules,:ruleengine,:ui,:things,:complete>) (defaults to: nil)

    Fluent alias for at_level

  • attach (Object) (defaults to: nil)

    Object to be attached to the trigger

See Also:



1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
# File 'lib/openhab/dsl/rules/builder.rb', line 1507

def on_start(at_level: nil, at_levels: nil, attach: nil)
  levels = Array.wrap(at_level) | Array.wrap(at_levels)
  levels = [100] if levels.empty?

  levels.map! do |level|
    next level unless level.is_a?(Symbol)

    begin
      klass = org.openhab.core.service.StartLevelService.java_class
      klass.declared_field("STARTLEVEL_#{level.upcase}").get_int(klass)
    rescue java.lang.NoSuchFieldException
      raise ArgumentError, "Invalid symbol for at_level: :#{level}"
    end
  end

  @ruby_triggers << [:on_start, levels]
  levels.each do |level|
    logger.warn "Rule engine doesn't start until start level 40" if level < 40
    config = { startlevel: level }
    logger.trace { "Creating a SystemStartlevelTrigger with startlevel=#{level}" }
    Triggers::Trigger.new(rule_triggers: @rule_triggers)
                     .append_trigger(type: "core.SystemStartlevelTrigger", config: config, attach: attach)
  end
end

#only_every(interval) ⇒ void

This method returns an undefined value.

Executes the rule then ignores subsequent triggers for a given duration.

Additional triggers that occur within the given duration after the rule execution will be ignored. This results in executions happening only at the specified interval or more.

Unlike #throttle_for, this guard will execute the rule as soon as a new trigger occurs instead of waiting for the specified duration. This is ideal for triggers such as a door bell where the rule should run as soon as a new trigger is detected but ignore subsequent triggers if they occur too soon after.

Examples:

Only allow executions every 10 minutes or more

rule "Aircon Vent Control" do
  changed BedRoom_Temperature
  only_every 10.minutes
  run do
    # Adjust BedRoom_Aircon_Vent
  end
end

Run only on the first update and ignore subsequent triggers for the next minute

# They can keep pressing the door bell as often as they like,
# but the bell will only ring at most once every minute
rule do
  updated DoorBell_Button, to: "single"
  only_every 1.minute
  run { Audio.play_stream "doorbell.mp3" }
end

Using symbolic duration

rule "Display update" do
  updated Power_Usage
  only_every :minute
  run { Power_Usage_Display.update "Current power usage: #{Power_Usage.average_since(1.minute.ago)}" }
end

Parameters:

  • interval (Duration, :second, :minute, :hour, :day)

    The period during which subsequent triggers are ignored.

See Also:



849
850
851
852
# File 'lib/openhab/dsl/rules/builder.rb', line 849

def only_every(interval)
  interval = 1.send(interval) if %i[second minute hour day].include?(interval)
  debounce(for: interval, leading: true)
end

#only_if {|event| ... } ⇒ void

This method returns an undefined value.

Allows rule execution when the block's result is true and prevents it when it's false.

Examples:

rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is also ON" do
  changed LightSwitch, to: ON
  run { OutsideDimmer << 50 }
  only_if { OtherSwitch.on? }
end

Multiple #only_if statements can be used and all must be true for the rule to run.

rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is also ON and Door is closed" do
  changed LightSwitch, to: ON
  run { OutsideDimmer << 50 }
  only_if { OtherSwitch.on? }
  only_if { Door.closed? }
end

Guards have access to event information.

rule "Set OutsideDimmer to 50% if any switch in group Switches starting with Outside is switched On" do
  changed Switches.items, to: ON
  run { OutsideDimmer << 50 }
  only_if { |event| event.item.name.start_with?("Outside") }
end

Yield Parameters:

Yield Returns:

  • (Boolean)

    A value indicating if the rule should run.



642
643
644
# File 'lib/openhab/dsl/rules/builder.rb', line 642

prop_array(:only_if) do |item|
  raise ArgumentError, "Object passed to only_if must be a proc" unless item.is_a?(Proc)
end

#otherwise {|event| ... } ⇒ Object

Add a block that will be passed event data, to be run if guards are not satisfied.

The #otherwise property is the automation code that is executed when a rule is triggered and guards are not satisfied. This property accepts a block of code and executes it. The block is automatically passed an event object which can be used to access multiple properties about the triggering event.

Examples:

rule 'Turn switch ON or OFF based on value of another switch' do
  on_load
  run { TestSwitch << ON }
  otherwise { TestSwitch << OFF }
  only_if { OtherSwitch.on? }
end

Yield Parameters:



442
# File 'lib/openhab/dsl/rules/builder.rb', line 442

prop_array :otherwise, array_name: :run_queue, wrapper: Otherwise

#received_command(*items, command: nil, commands: nil, attach: nil) ⇒ void

This method returns an undefined value.

Creates a trigger for when an item or group receives a command.

The command/commands parameters are replicated for DSL fluency.

The event passed to run blocks will be an Core::Events::ItemCommandEvent.

Examples:

rule 'Execute rule when item received command' do
  received_command Alarm_Mode
  run { |event| logger.info("Item received command: #{event.command}" ) }
end
rule 'Execute rule when item receives specific command' do
  received_command Alarm_Mode, command: 7
  run { |event| logger.info("Item received command: #{event.command}" ) }
end
rule 'Execute rule when item receives one of many specific commands' do
  received_command Alarm_Mode, commands: [7,14]
  run { |event| logger.info("Item received command: #{event.command}" ) }
end
rule 'Execute rule when group receives a specific command' do
  received_command AlarmModes
  triggered { |item| logger.info("Group #{item.name} received command")}
end
rule 'Execute rule when member of group receives any command' do
  received_command AlarmModes.members
  triggered { |item| logger.info("Group item #{item.name} received command")}
end
rule 'Execute rule when member of group is changed to one of many states' do
  received_command AlarmModes.members, commands: [7, 14]
  triggered { |item| logger.info("Group item #{item.name} received command")}
end
rule 'Execute rule when item receives a range of commands' do
  received_command Alarm_Mode, commands: 7..14
  run { |event| logger.info("Item received command: #{event.command}" ) }
end

Works with procs

rule 'Execute rule when Alarm Mode command is odd' do
  received_command Alarm_Mode, command: proc { |c| c.odd? }
  run { |event| logger.info("Item received command: #{event.command}" ) }
end

Works with lambdas

rule 'Execute rule when Alarm Mode command is odd' do
  received_command Alarm_Mode, command: -> c { c.odd? }
  run { |event| logger.info("Item received command: #{event.command}" ) }
end

Works with regexes

rule 'Execute rule when Alarm Mode command matches a substring' do
  received_command Alarm_Mode, command: /arm/
  run { |event| logger.info("Item received command: #{event.command}" ) }
end

Parameters:



1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
# File 'lib/openhab/dsl/rules/builder.rb', line 1606

def received_command(*items, command: nil, commands: nil, attach: nil)
  command_trigger = Command.new(rule_triggers: @rule_triggers)

  # if neither command nor commands is specified, ensure that we create
  # a trigger that isn't looking for a specific command.
  commands = [nil] if command.nil? && commands.nil?
  commands = Array.wrap(command) | Array.wrap(commands)

  @ruby_triggers << [:received_command, items, { command: commands }]

  items.each do |item|
    case item
    when Core::Items::Item,
         Core::Items::GroupItem::Members
      nil
    else
      raise ArgumentError, "items must be an Item or GroupItem::Members"
    end
    commands.each do |cmd|
      logger.trace { "Creating received command trigger for items #{item.inspect} and commands #{cmd.inspect}" }

      command_trigger.trigger(item: item, command: cmd, attach: attach)
    end
  end
end

#run {|event| ... } ⇒ void

This method returns an undefined value.

Add a block that will be passed event data.

The run property is the automation code that is executed when a rule is triggered. This property accepts a block of code and executes it. The block is automatically passed an event object which can be used to access multiple properties about the triggering event. The code for the automation can be entirely within the run block and can call methods defined in the Ruby script.

Examples:

{} style used for single line blocks.

rule 'Access Event Properties' do
  changed TestSwitch
  run { |event| logger.info("#{event.item.name} triggered from #{event.was} to #{event.state}") }
end

do/end style used for multi-line blocks.

rule 'Multi Line Run Block' do
  changed TestSwitch
  run do |event|
    logger.info("#{event.item.name} triggered")
    logger.info("from #{event.was}") if event.was?
    logger.info("to #{event.state}") if event.state?
   end
end

Rules can have multiple run blocks and they are executed in order. Useful when used in combination with #delay.

rule 'Multiple Run Blocks' do
  changed TestSwitch
  run { |event| logger.info("#{event.item.name} triggered") }
  run { |event| logger.info("from #{event.was}") if event.was? }
  run { |event| logger.info("to #{event.state}") if event.state?  }
end

Yield Parameters:



321
# File 'lib/openhab/dsl/rules/builder.rb', line 321

prop_array :run, array_name: :run_queue, wrapper: Run

#tags(*tags) ⇒ void

This method returns an undefined value.

Set the rule's tags.

Examples:

rule "tagged rule" do
  tags "lighting", "security"
end

Parameters:



489
# File 'lib/openhab/dsl/rules/builder.rb', line 489

prop :tags

#thing_added(pattern = "*", attach: nil) ⇒ void

This method returns an undefined value.

Creates a thing added trigger

The event passed to run blocks will be a Core::Events::ThingAddedEvent.

Examples:

rule "thing added" do
  thing_added
  run do |event|
    logger.info("#{event.thing.uid} added.")
  end
end

Parameters:

  • pattern (String, nil) (defaults to: "*")

    The pattern to match things against

  • attach (Object) (defaults to: nil)

    object to be attached to the trigger

Since:

  • openHAB 4.0 Support for pattern filter was added



1718
1719
1720
1721
# File 'lib/openhab/dsl/rules/builder.rb', line 1718

def thing_added(pattern = "*", attach: nil)
  @ruby_triggers << [:thing_added, pattern]
  event("openhab/things/#{pattern}/added", types: "ThingAddedEvent", attach: attach)
end

#thing_removed(pattern = "*", attach: nil) ⇒ void

This method returns an undefined value.

Creates a thing removed trigger

The event passed to run blocks will be a Core::Events::ThingRemovedEvent.

Examples:

rule "thing removed" do
  thing_removed
  run do |event|
    logger.info("#{event.thing.uid} removed.")
  end
end

Parameters:

  • pattern (String, nil) (defaults to: "*")

    The pattern to match things against

  • attach (Object) (defaults to: nil)

    object to be attached to the trigger

Since:

  • openHAB 4.0 Support for pattern filter was added



1741
1742
1743
1744
# File 'lib/openhab/dsl/rules/builder.rb', line 1741

def thing_removed(pattern = "*", attach: nil)
  @ruby_triggers << [:thing_removed, pattern]
  event("openhab/things/#{pattern}/removed", types: "ThingRemovedEvent", attach: attach)
end

#thing_updated(pattern = "*", attach: nil) ⇒ void

This method returns an undefined value.

Creates a thing updated trigger

The event passed to run blocks will be a Core::Events::ThingUpdatedEvent.

Examples:

rule "thing updated" do
  thing_updated
  run do |event|
    logger.info("#{event.thing.uid} updated.")
  end
end

Parameters:

  • pattern (String, nil) (defaults to: "*")

    The pattern to match things against

  • attach (Object) (defaults to: nil)

    object to be attached to the trigger

Since:

  • openHAB 4.0 Support for pattern filter was added



1765
1766
1767
1768
# File 'lib/openhab/dsl/rules/builder.rb', line 1765

def thing_updated(pattern = "*", attach: nil)
  @ruby_triggers << [:thing_updated, pattern]
  event("openhab/things/#{pattern}/updated", types: "ThingUpdatedEvent", attach: attach)
end

#throttle_for(duration) ⇒ void

This method returns an undefined value.

Rate-limits rule executions by delaying triggers and executing the last trigger within the given duration.

When a new trigger occurs, it will hold the execution and start a fixed timer for the given duration. Should more triggers occur during this time, keep holding and once the wait time is over, execute the latest trigger.

#throttle_for will execute rules after it had waited for the given duration, regardless of how frequently the triggers were occuring. In contrast, #debounce_for will wait until there is a minimum interval between two triggers.

#throttle_for is ideal in situations where regular status updates need to be made for frequently changing values. It is also useful when a rule responds to triggers from multiple related items that are updated at around the same time. Instead of executing the rule multiple times, #throttle_for will wait for a pre-set amount of time since the first group of triggers occurred before executing the rule.

Examples:

Perform calculations from multiple items

rule "Update Power Summary " do |rule|
  changed Power_From_Solar, Power_Load, Power_From_Grid
  throttle_for 1.second
  only_if { rule.dependencies.all?(&:state?) } # make sure all items have a state
  run do
    msg = []
    msg << Power_Load.state.negate.to_unit("kW").format("Load: %.2f %unit%")
    msg << Power_From_Solar.state.to_unit("kW").format("PV: %.2f %unit%")
    if Power_From_Grid.positive?
      msg << Power_From_Grid.state.to_unit("kW").format("From Grid: %.1f %unit%")
    else
      msg << Power_From_Grid.state.negate.to_unit("kW").format("To Grid: %.1f %unit%")
    end
    Power_Summary.update(msg.join(", "))
  end
end

Parameters:

  • duration (Duration)

    The minimum amount of time to wait inbetween rule executions.

See Also:



801
802
803
# File 'lib/openhab/dsl/rules/builder.rb', line 801

def throttle_for(duration)
  debounce(for: duration)
end

#time_series_updated(*items, attach: nil) ⇒ void

This method returns an undefined value.

Creates a time series updated trigger

The event passed to run blocks will be a Core::Events::ItemTimeSeriesUpdatedEvent

Examples:

rule 'Execute rule when item time series is updated' do
  time_series_updated MyItem
  run do |event|
    logger.info("Item time series updated: #{event.item.name}.")
    logger.info("  TimeSeries size: #{event.time_series.size}, policy: #{event.time_series.policy}")
    event.time_series.each do |entry|
      timestamp = entry.timestamp.to_time.strftime("%Y-%m-%d %H:%M:%S")
      logger.info("    Entry: #{timestamp}: State: #{entry.state}")
    end
  end
end

Parameters:

  • items (Item)

    Items to create trigger for.

  • attach (Object) (defaults to: nil)

    Object to be attached to the trigger.

See Also:

Since:

  • openHAB 4.1



2025
2026
2027
2028
2029
2030
2031
2032
# File 'lib/openhab/dsl/rules/builder.rb', line 2025

def time_series_updated(*items, attach: nil)
  @ruby_triggers << [:time_series_updated, items]
  items.map do |item|
    raise ArgumentError, "items must be an Item or GroupItem::Members" unless item.is_a?(Core::Items::Item)

    event("openhab/items/#{item.name}/timeseriesupdated", types: "ItemTimeSeriesUpdatedEvent", attach: attach)
  end
end

#trigger(type, attach: nil, **configuration) ⇒ void

This method returns an undefined value.

Create a generic trigger given the trigger type uid and a configuration hash

This provides the ability to create a trigger type not already covered by the other methods.

Examples:

Create a trigger for the PID Controller Automation add-on.

rule 'PID Control' do
  trigger 'pidcontroller.trigger',
    input: InputItem.name,
    setpoint: SetPointItem.name,
    kp: 10,
    ki: 10,
    kd: 10,
    kdTimeConstant: 1,
    loopTime: 1000

  run do |event|
    logger.info("PID controller command: #{event.command}")
    ControlItem << event.command
  end
end

DateTime Trigger

rule 'DateTime Trigger' do
  description 'Triggers at a time specified in MyDateTimeItem'
  trigger 'timer.DateTimeTrigger', itemName: MyDateTimeItem.name
  run do
    logger.info("DateTimeTrigger has been triggered")
  end
end

Parameters:

  • type (String)

    Trigger type UID

  • attach (Object) (defaults to: nil)

    object to be attached to the trigger

  • configuration (Hash)

    A hash containing the trigger configuration entries



1888
1889
1890
1891
1892
# File 'lib/openhab/dsl/rules/builder.rb', line 1888

def trigger(type, attach: nil, **configuration)
  logger.trace { "Creating trigger (#{type}) with configuration(#{configuration})" }
  Triggers::Trigger.new(rule_triggers: @rule_triggers)
                   .append_trigger(type: type, config: configuration, attach: attach)
end

#triggered {|item| ... } ⇒ void

This method returns an undefined value.

Add a block that will be passed the triggering item.

This property is the same as the #run property except rather than passing an event object to the automation block the triggered item is passed. This enables optimizations for simple cases and supports Ruby's pretzel colon &: operator..

Examples:

rule "motion sensor triggered" do
  changed MotionSensor.members, to: :OPEN
  triggered do |item|
    logger.info("#{item.name} detected motion")
  end
end
rule 'Triggered has access directly to item triggered' do
  changed TestSwitch
  triggered { |item| logger.info("#{item.name} triggered") }
end

Triggered items are highly useful when working with groups

# Switches is a group of Switch items
rule 'Triggered item is item changed when a group item is changed.' do
  changed Switches.members
  triggered { |item| logger.info("Switch #{item.name} changed to #{item.state}")}
end

rule 'Turn off any switch that changes' do
  changed Switches.members
  triggered(&:off)
end

Like other execution blocks, multiple triggered blocks are supported in a single rule

rule 'Turn a switch off and log it, 5 seconds after turning it on' do
  changed Switches.members, to: ON
  delay 5.seconds
  triggered(&:off)
  triggered {|item| logger.info("#{item.label} turned off") }
end

Yield Parameters:



371
# File 'lib/openhab/dsl/rules/builder.rb', line 371

prop_array :triggered, array_name: :run_queue, wrapper: Trigger

#uid(id) ⇒ void

This method returns an undefined value.

Set the rule's UID.

Parameters:

  • id (String)


454
# File 'lib/openhab/dsl/rules/builder.rb', line 454

prop(:uid) { |id| Thread.current[:openhab_rule_uid] = id }

#updated(*items, to: nil, attach: nil) ⇒ void

This method returns an undefined value.

Create a trigger when item, group or thing is updated

The event passed to run blocks will be an Core::Events::ItemStateEvent or a Core::Events::ThingStatusInfoEvent depending on if the triggering element was an item or a thing.

Examples:

rule 'Execute rule when item is updated to any value' do
  updated Alarm_Mode
  run { logger.info("Alarm Mode Updated") }
end
rule 'Execute rule when item is updated to specific number' do
  updated Alarm_Mode, to: 7
  run { logger.info("Alarm Mode Updated") }
end
rule 'Execute rule when item is updated to one of many specific states' do
  updated Alarm_Mode, to: [7, 14]
  run { logger.info("Alarm Mode Updated")}
end
rule 'Execute rule when item is within a range' do
  updated Alarm_Mode, to: 7..14
  run { logger.info("Alarm Mode Updated to a value between 7 and 14")}
end
rule 'Execute rule when group is updated to any state' do
  updated AlarmModes
  triggered { |item| logger.info("Group #{item.name} updated")}
end
rule 'Execute rule when member of group is changed to any state' do
  updated AlarmModes.members
  triggered { |item| logger.info("Group item #{item.name} updated")}
end
rule 'Execute rule when member of group is changed to one of many states' do
  updated AlarmModes.members, to: [7, 14]
  triggered { |item| logger.info("Group item #{item.name} updated")}
end

Works with procs

rule 'Execute rule when member of group is changed to an odd state' do
  updated AlarmModes.members, to: proc { |t| t.odd? }
  triggered { |item| logger.info("Group item #{item.name} updated")}
end

Works with lambdas:

rule 'Execute rule when member of group is changed to an odd state' do
  updated AlarmModes.members, to: -> t { t.odd? }
  triggered { |item| logger.info("Group item #{item.name} updated")}
end

Works with regexes:

rule 'Execute rule when member of group is changed to a substring' do
  updated AlarmModes.members, to: /armed/
  triggered { |item| logger.info("Group item #{item.name} updated")}
end

Works with things as well

rule 'Execute rule when thing is updated' do
   updated things['astro:sun:home'], :to => :uninitialized
   run { |event| logger.info("Thing #{event.uid} status <trigger> to #{event.status}") }
end

Parameters:



1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
# File 'lib/openhab/dsl/rules/builder.rb', line 1978

def updated(*items, to: nil, attach: nil)
  updated = Updated.new(rule_triggers: @rule_triggers)
  @ruby_triggers << [:updated, items, { to: to }]
  items.map do |item|
    case item
    when Core::Things::Thing,
         Core::Things::ThingUID,
         Core::Items::Item,
         Core::Items::GroupItem::Members
      nil
    else
      raise ArgumentError, "items must be an Item, GroupItem::Members, Thing, or ThingUID"
    end

    logger.trace { "Creating updated trigger for item(#{item}) to(#{to})" }
    [to].flatten.map do |to_state|
      updated.trigger(item: item, to: to_state, attach: attach)
    end
  end.flatten
end

#watch(path, glob: "*", for: %i[created deleted modified], attach: nil) ⇒ void

This method returns an undefined value.

Create a trigger to watch a path

It provides the ability to create a trigger on file and directory changes.

If a file or a path that does not exist is supplied as the argument to watch, the parent directory will be watched and the file or non-existent part of the supplied path will become the glob. For example, if the path given is /tmp/foo/bar and /tmp/foo exists but bar does not exist inside of of /tmp/foo then the directory /tmp/foo will be watched for any files that match */bar.

If the last part of the path contains any glob characters e.g. /tmp/foo/*bar, the parent directory will be watched and the last part of the path will be treated as if it was passed as the glob argument. In other words, watch '/tmp/foo/*bar' is equivalent to watch '/tmp/foo', glob: '*bar'

#Watching inside subdirectories

Subdirectories aren't watched unless:

  • One of the glob patterns include the recursive match pattern **, or
  • The glob patterns include subdirectories, see examples below.

The event passed to run blocks will be a Events::WatchEvent.

Examples:

Watch items directory inside of the openHAB configuration path and log any changes.

rule 'watch directory' do
  watch OpenHAB::Core.config_folder / 'items'
  run { |event| logger.info("#{event.path.basename} - #{event.type}") }
end

Watch items directory for files that end in *.erb and log any changes

rule 'watch directory' do
  watch OpenHAB::Core.config_folder / 'items', glob: '*.erb'
  run { |event| logger.info("#{event.path.basename} - #{event.type}") }
end

Watch items/foo.items log any changes

rule 'watch directory' do
  watch OpenHAB::Core.config_folder / 'items/foo.items'
  run { |event| logger.info("#{event.path.basename} - #{event.type}") }
end

Watch items/*.items log any changes

rule 'watch directory' do
  watch OpenHAB::Core.config_folder / 'items/*.items'
  run { |event| logger.info("#{event.path.basename} - #{event.type}") }
end

Watch items/*.items for when items files are deleted or created (ignore changes)

rule 'watch directory' do
  watch OpenHAB::Core.config_folder / 'items/*.items', for: [:deleted, :created]
  run { |event| logger.info("#{event.path.basename} - #{event.type}") }
end

Watch for changes inside all subdirs of config_folder/automation/ruby/lib

rule "Watch recursively" do
  watch OpenHAB::Core.config_folder / "automation/ruby/lib/**"
  run { |event| logger.info("#{event.path} - #{event.type}") }
end

Recursively watch using subdirectory in glob

rule "Monitor changes in the list of installed gems" do
  watch ENV['GEM_HOME'], glob: "gems/*"
  run { |event| logger.info("#{event.path} - #{event.type}") }
end

Recursively watch using glob option

rule "Watch recursively" do
  watch OpenHAB::Core.config_folder / "automation/ruby/lib", glob: "**"
  run { |event| logger.info("#{event.path} - #{event.type}") }
end

Parameters:

  • path (String)

    Path to watch. Can be a directory or a file.

  • glob (String) (defaults to: "*")

    Limit events to paths matching this glob. Globs are matched using glob PathMatcher rules.

  • for (Array<:created, :deleted, :modified>, :created, :deleted, :modified) (defaults to: %i[created deleted modified])

    Types of changes to watch for.

  • attach (Object) (defaults to: nil)

    object to be attached to the trigger



2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
# File 'lib/openhab/dsl/rules/builder.rb', line 2120

def watch(path, glob: "*", for: %i[created deleted modified], attach: nil)
  types = [binding.local_variable_get(:for)].flatten

  WatchHandler::WatchTriggerHandlerFactory.instance # ensure it's registered
  trigger(WatchHandler::WATCH_TRIGGER_MODULE_ID,
          path: path.to_s,
          types: types.map(&:to_s),
          glob: glob.to_s,
          attach: attach)
end