Module: OpenHAB::DSL

Includes:
Core::Actions, Core::EntityLookup, Core::ScriptHandling, Rules::Terse
Included in:
Items::Builder, Rules::BuilderDSL
Defined in:
lib/openhab/dsl.rb,
lib/openhab/dsl/rules.rb,
lib/openhab/dsl/events.rb,
lib/openhab/dsl/version.rb,
lib/openhab/dsl/debouncer.rb,
lib/openhab/dsl/rules/guard.rb,
lib/openhab/dsl/rules/terse.rb,
lib/openhab/dsl/items/ensure.rb,
lib/openhab/dsl/thread_local.rb,
lib/openhab/dsl/items/builder.rb,
lib/openhab/dsl/rules/builder.rb,
lib/openhab/dsl/timer_manager.rb,
lib/openhab/dsl/rules/property.rb,
lib/openhab/dsl/rules/triggers.rb,
lib/openhab/dsl/things/builder.rb,
lib/openhab/dsl/sitemaps/builder.rb,
lib/openhab/dsl/events/watch_event.rb,
lib/openhab/dsl/items/timed_command.rb,
lib/openhab/dsl/rules/rule_triggers.rb,
lib/openhab/dsl/rules/name_inference.rb,
lib/openhab/dsl/rules/automation_rule.rb,
lib/openhab/dsl/rules/triggers/changed.rb,
lib/openhab/dsl/rules/triggers/channel.rb,
lib/openhab/dsl/rules/triggers/command.rb,
lib/openhab/dsl/rules/triggers/trigger.rb,
lib/openhab/dsl/rules/triggers/updated.rb,
lib/openhab/dsl/rules/triggers/cron/cron.rb,
lib/openhab/dsl/rules/triggers/conditions.rb,
lib/openhab/dsl/config_description/builder.rb,
lib/openhab/dsl/rules/triggers/cron/cron_handler.rb,
lib/openhab/dsl/rules/triggers/conditions/generic.rb,
lib/openhab/dsl/rules/triggers/conditions/duration.rb,
lib/openhab/dsl/rules/triggers/watch/watch_handler.rb

Overview

The main DSL available to rules.

Methods on this module are extended onto ‘main`, the top level `self` in any file. You can also access them as class methods on the module for use inside of other classes, or include the module.

Defined Under Namespace

Modules: ConfigDescription, Events, Items, Rules, Sitemaps, Things Classes: Debouncer, TimerManager

Constant Summary collapse

VERSION =

Version of openHAB helper libraries

Returns:

  • (String)
"5.17.0"

Rule Creation collapse

Rule Support collapse

Object Access collapse

Utilities collapse

Block Modifiers These methods allow certain operations to be grouped inside the given block to reduce repetitions collapse

Methods included from Rules::Terse

#changed, #channel, #channel_linked, #channel_unlinked, #cron, #every, #item_added, #item_removed, #item_updated, #on_start, #received_command, #thing_added, #thing_removed, #thing_updated, #updated

Methods included from Core::ScriptHandling

script_loaded, script_unloaded

Methods included from Core::Actions

notify

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missingObject (private)

Provide access to the script context / variables see OpenHAB::DSL::Rules::AutomationRule#execute!



1070
1071
1072
1073
1074
1075
1076
# File 'lib/openhab/dsl.rb', line 1070

ruby2_keywords def method_missing(method, *args)
  return super unless args.empty? && !block_given?
  return super unless (context = Thread.current[:openhab_context]) && context.key?(method)

  logger.trace("DSL#method_missing found context variable: '#{method}'")
  context[method]
end

Class Method Details

.after(duration, id: nil, reschedule: true) {|timer| ... } ⇒ Core::Timer

Create a timer and execute the supplied block after the specified duration

### Reentrant Timers

Timers with an id are reentrant by id. Reentrant means that when the same id is encountered, the timer is rescheduled rather than creating a second new timer. Note that the timer will execute the block provided in the latest call.

This removes the need for the usual boilerplate code to manually keep track of timer objects.

Timers with ‘id` can be managed with the built-in timers object.

When a timer is cancelled, it will be removed from the object.

Be sure that your ids are unique. For example, if you’re using items as your ids, you either need to be sure you don’t use the same item for multiple logical contexts, or you need to make your id more specific, by doing something like embedding the item in array with a symbol of the timer’s purpose, like ‘[:vacancy, item]`. But also note that assuming default settings, every Ruby file (for file-based rules) or UI rule gets its own instance of the timers object, so you don’t need to worry about collisions among different files.

Examples:

Create a simple timer

after 5.seconds do
  logger.info("Timer Fired")
end

Timers delegate methods to openHAB timer objects

after 1.second do |timer|
  logger.info("Timer is active? #{timer.active?}")
end

Timers can be rescheduled to run again, waiting the original duration

after 3.seconds do |timer|
  logger.info("Timer Fired")
  timer.reschedule
end

Timers can be rescheduled for different durations

after 3.seconds do |timer|
  logger.info("Timer Fired")
  timer.reschedule 5.seconds
end

Timers can be manipulated through the returned object

mytimer = after 1.minute do
  logger.info("It has been 1 minute")
end

mytimer.cancel

Reentrant timers will automatically reschedule if the same id is encountered again

rule "Turn off closet light after 10 minutes" do
  changed ClosetLights.members, to: ON
  triggered do |item|
    after 10.minutes, id: item do
      item.ensure.off
    end
  end
end

Timers with id can be managed through the built-in ‘timers` object

after 1.minute, id: :foo do
  logger.info("managed timer has fired")
end

timers.cancel(:foo)

if timers.include?(:foo)
  logger.info("The timer :foo is not active")
end

Only create a new timer if it isn’t already scheduled

after(1.minute, id: :foo, reschedule: false) do
  logger.info("Timer fired")
end

Reentrant timers will execute the block from the most recent call

# In the following example, if Item1 received a command, followed by Item2,
# the timer will execute the block referring to Item2.
rule "Execute The Most Recent Block" do
  received_command Item1, Item2
  run do |event|
    after(10.minutes, id: :common_timer) do
      logger.info "The latest command was received from #{event.item}"
    end
  end
end

Parameters:

  • duration (java.time.temporal.TemporalAmount, #to_zoned_date_time, Proc)

    Duration after which to execute the block

  • id (Object) (defaults to: nil)

    ID to associate with timer. The timer can be managed via timers.

  • reschedule (true, false) (defaults to: true)

    Reschedule the timer if it already exists.

Yields:

  • Block to execute when the timer is elapsed.

Yield Parameters:

Returns:

  • (Core::Timer)

    if ‘reschedule` is false, the existing timer. Otherwise the new timer.

Raises:

  • (ArgumentError)

See Also:



401
402
403
404
405
406
407
# File 'lib/openhab/dsl.rb', line 401

def after(duration, id: nil, reschedule: true, &block)
  raise ArgumentError, "Block is required" unless block

  # Carry rule name to timer
  thread_locals = ThreadLocal.persist
  timers.create(duration, id: id, reschedule: reschedule, thread_locals: thread_locals, block: block)
end

.between(range) ⇒ Range

Convert a string based range into a range of LocalTime, LocalDate, MonthDay, or ZonedDateTime depending on the format of the string.

Examples:

Range#cover?

logger.info("Within month-day range") if between('02-20'..'06-01').cover?(MonthDay.now)

Use in a Case

case MonthDay.now
when between('01-01'..'03-31')
  logger.info("First quarter")
when between('04-01'..'06-30')
 logger.info("Second quarter")
end

Create a time range

between('7am'..'12pm').cover?(LocalTime.now)

Returns:

  • (Range)

    converted range object

Raises:

  • (ArgumentError)

See Also:



431
432
433
434
435
436
437
# File 'lib/openhab/dsl.rb', line 431

def between(range)
  raise ArgumentError, "Supplied object must be a range" unless range.is_a?(Range)

  start = try_parse_time_like(range.begin)
  finish = try_parse_time_like(range.end)
  Range.new(start, finish, range.exclude_end?)
end

.config_description(uri = nil) { ... } ⇒ org.openhab.core.config.core.ConfigDescription

Create a ConfigDescription object.

Examples:

config_description = config_description do
  parameter :ungrouped_parameter, :decimal, label: "Ungrouped Parameter", min: 1, max: 5

  group "Config Group", label: "Grouped parameters", advanced: true do
    parameter :my_parameter, :string, label: "My Parameter", description: "My Parameter Description"
    parameter :other_parameter, :integer, label: "Other Parameter", description: "Other Parameter Description"
  end
end

Parameters:

  • uri (String, java.net.URI) (defaults to: nil)

    The URI for the ConfigDescription. When nil, a dummy URI is used which will be replaced by the profile with the correct URI for that profile.

Yields:

Returns:

  • (org.openhab.core.config.core.ConfigDescription)

    The created ConfigDescription object

Raises:

  • (ArgumentError)


197
198
199
200
201
# File 'lib/openhab/dsl.rb', line 197

def config_description(uri = nil, &block)
  raise ArgumentError, "Block is required" unless block

  ConfigDescription::Builder.new.build(uri, &block)
end

.debounce_for(debounce_time, id: nil, &block) ⇒ void

This method returns an undefined value.

Waits until calls to this method have stopped firing for a period of time before executing the block.

This method acts as a guard for the given block to ensure that it doesn’t get executed too frequently. The debounce_for method can be called as frequently as possible. The given block, however, will only be executed once the ‘debounce_time` has passed since the last call to debounce_for.

This method can be used from within a UI rule as well as from a file-based rule.

Examples:

Run a block of code only after an item has stopped changing

# This can be placed inside a UI rule with an Item Change trigger for
# a Door contact sensor.
# If the door state has stopped changing state for 10 minutes,
# execute the block.
debounce_for(10.minutes) do
  if DoorState.open?
    Voice.say("The door has been left open!")
  end
end

Parameters:

  • id (Object) (defaults to: nil)

    ID to associate with this call.

  • block (Block)

    The block to be debounced.

  • 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:



503
504
505
506
# File 'lib/openhab/dsl.rb', line 503

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

.ensure_states { ... } ⇒ Object

Global method that takes a block and for the duration of the block all commands sent will check if the item is in the command’s state before sending the command. This also applies to updates.

Examples:

Turn on several switches only if they’re not already on

ensure_states do
  Switch1.on
  Switch2.on
end
# VirtualSwitch is in state `ON`
ensure_states do
  VirtualSwitch << ON       # No command will be sent
  VirtualSwitch.update(ON)  # No update will be posted
  VirtualSwitch << OFF      # Off command will be sent
  VirtualSwitch.update(OFF) # No update will be posted
end
ensure_states do
  rule 'Items in an execution block will have ensure_states applied to them' do
    changed VirtualSwitch
    run do
      VirtualSwitch.on
      VirtualSwitch2.on
    end
  end
end
rule 'ensure_states must be in an execution block' do
  changed VirtualSwitch
  run do
     ensure_states do
        VirtualSwitch.on
        VirtualSwitch2.on
     end
  end
end

Yields:

Returns:

  • (Object)

    The result of the block.



691
692
693
694
695
696
# File 'lib/openhab/dsl.rb', line 691

def ensure_states
  old = ensure_states!
  yield
ensure
  ensure_states!(active: old)
end

.ensure_states!(active: true) ⇒ Boolean

Note:

This method is only intended for use at the top level of rule scripts. If it’s used within library methods, or hap-hazardly within rules, things can get very confusing because the prior state won’t be properly restored.

Permanently enable conditional execution of commands and updates for the current thread.

When conditional executions are enabled, commands and updates will only be sent if the item’s current state is not the same as the command or updated state. This eliminates the need to chain the command and update calls through ensure.

When conditional executions are enabled either by this method or within a block of ensure_states, commands and updates can still be forcefully executed using the corresponding bang methods, e.g. ‘Item1.on!`, `Item1.command!(50)`, or `Item1.update!(ON)`.

Examples:

Make ensure_states the default for the rest of the script

ensure_states!

# From now, all commands are "ensured", i.e. only sent when the current state is different
Item1.on
Item2.command(ON)

# While ensure_states! is active, we can still forcibly send a command
# regardless of the item's current state
Item2.on!

Parameters:

  • active (Boolean) (defaults to: true)

    Whether to enable or disable conditional executions.

Returns:

  • (Boolean)

    The previous ensure_states setting.

See Also:



640
641
642
643
644
# File 'lib/openhab/dsl.rb', line 640

def ensure_states!(active: true)
  old = Thread.current[:openhab_ensure_states]
  Thread.current[:openhab_ensure_states] = active
  old
end

.holiday_file(file) { ... } ⇒ Object .holiday_fileString?

Overloads:

  • .holiday_file(file) { ... } ⇒ Object

    Sets a thread local variable to use a specific holiday file for ephemeris calls inside the block.

    Examples:

    Set a specific holiday configuration file temporarily

    holiday_file("/home/cody/holidays.xml") do
      Time.now.next_holiday
    end

    Parameters:

    • file (String, nil)

      Path to a file defining holidays; ‘nil` to reset to default.

    Yields:

    • Block executed in context of the supplied holiday file

    Returns:

    • (Object)

      The return value from the block.

    See Also:

  • .holiday_fileString?

    Returns the current thread local value for the holiday file.

    Returns:

    • (String, nil)

      the current holiday file

See Also:



1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
# File 'lib/openhab/dsl.rb', line 1021

def holiday_file(*args)
  raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 0..1)" if args.length > 1

  old = Thread.current[:openhab_holiday_file]
  return old if args.empty?

  holiday_file!(args.first)
  yield
ensure
  holiday_file!(old)
end

.holiday_file!(file = nil) ⇒ Symbol?

Sets a thread local variable to set the default holiday file.

Examples:

holiday_file!("/home/cody/holidays.xml")
Time.now.next_holiday

Parameters:

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

    Path to a file defining holidays; ‘nil` to reset to default.

Returns:

  • (Symbol, nil)

    the new holiday file

See Also:



1046
1047
1048
# File 'lib/openhab/dsl.rb', line 1046

def holiday_file!(file = nil)
  Thread.current[:openhab_holiday_file] = file
end

.itemsCore::Items::Registry

Fetches all items from the item registry

The examples all assume the following items exist.

“‘xtend Dimmer DimmerTest “Test Dimmer” Switch SwitchTest “Test Switch” “`

Examples:

logger.info("Item Count: #{items.count}")  # Item Count: 2
logger.info("Items: #{items.map(&:label).sort.join(', ')}")  # Items: Test Dimmer, Test Switch'
logger.info("DimmerTest exists? #{items.key?('DimmerTest')}") # DimmerTest exists? true
logger.info("StringTest exists? #{items.key?('StringTest')}") # StringTest exists? false
rule 'Use dynamic item lookup to increase related dimmer brightness when switch is turned on' do
  changed SwitchTest, to: ON
  triggered { |item| items[item.name.gsub('Switch','Dimmer')].brighten(10) }
end
rule 'search for a suitable item' do
  on_load
  triggered do
    # Send ON to DimmerTest if it exists, otherwise send it to SwitchTest
    (items['DimmerTest'] || items['SwitchTest'])&.on
  end
end

Returns:



260
261
262
# File 'lib/openhab/dsl.rb', line 260

def items
  Core::Items::Registry.instance
end

.only_every(interval, id: nil, &block) ⇒ void

This method returns an undefined value.

Limit how often the given block executes to the specified interval.

only_every will execute the given block but prevents further executions until the given interval has passed. In contrast, throttle_for will not execute the block immediately, and will wait until the end of the interval.

Examples:

Prevent door bell from ringing repeatedly

# This can be called from a UI rule.
# For file based rule, use the `only_every` rule guard
only_every(30.seconds) { Audio.play_sound("doorbell.mp3") }

Parameters:

  • id (Object) (defaults to: nil)

    ID to associate with this call.

  • block (Block)

    The block to be throttled.

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

    The period during which subsequent triggers are ignored.

See Also:



566
567
568
569
# File 'lib/openhab/dsl.rb', line 566

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

.persistence(service) { ... } ⇒ Object

Sets a thread local variable to set the default persistence service for method calls inside the block

Examples:

persistence(:influxdb) do
  Item1.persist
  Item1.changed_since(1.hour)
  Item1.average_since(12.hours)
end

Parameters:

  • service (Object)

    Persistence service either as a String or a Symbol

Yields:

  • Block executed in context of the supplied persistence service

Returns:

  • (Object)

    The return value from the block.

See Also:



716
717
718
719
720
721
# File 'lib/openhab/dsl.rb', line 716

def persistence(service)
  old = persistence!(service)
  yield
ensure
  persistence!(old)
end

.persistence!(service = nil) ⇒ Object?

Note:

This method is only intended for use at the top level of rule scripts. If it’s used within library methods, or hap-hazardly within rules, things can get very confusing because the prior state won’t be properly restored.

Permanently sets the default persistence service for the current thread

Parameters:

  • service (Object) (defaults to: nil)

    Persistence service either as a String or a Symbol. When nil, use the system’s default persistence service.

Returns:

  • (Object, nil)

    The previous persistence service settings, or nil when using the system’s default.

See Also:



738
739
740
741
742
# File 'lib/openhab/dsl.rb', line 738

def persistence!(service = nil)
  old = Thread.current[:openhab_persistence_service]
  Thread.current[:openhab_persistence_service] = service
  old
end

.profile(id, label: nil, config_description: nil) {|event, command: nil, state: nil, time_series: nil, callback:, link:, item:, channel_uid:, configuration:, context:| ... } ⇒ void

This method returns an undefined value.

Defines a new profile that can be applied to item channel links.

To create a profile that can be used in the UI, provide a label and optionally a config_description, otherwise the profile will not be visible in the UI.

Examples:

Vetoing a command

profile(:veto_closing_shades) do |event, item:, command:|
  next false if command&.down?

  true
end

items.build do
  rollershutter_item "MyShade" do
    channel "thing:rollershutter", profile: "ruby:veto_closing_shades"
  end
end
# can also be referenced from an `.items` file:
# Rollershutter MyShade { channel="thing:rollershutter"[profile="ruby:veto_closing_shades"] }

Overriding units from a binding

profile(:set_uom) do |event, callback:, configuration:, state:, command:|
  unless configuration["unit"]
    logger.warn("Unit configuration not provided for set_uom profile")
     next true
  end

  case event
  when :state_from_handler
    next true unless state.is_a?(DecimalType) || state.is_a?(QuantityType) # what is it then?!

    state = state.to_d if state.is_a?(QuantityType) # ignore the units if QuantityType was given
    callback.send_update(state | configuration["unit"])
    false
  when :command_from_item
    # strip the unit from the command, as the binding likely can't handle it
    next true unless command.is_a?(QuantityType)

    callback.handle_command(DecimalType.new(command.to_d))
    false
  else
    true # pass other events through as normal
  end
end
# can also be referenced from an `.items` file:
# Number:Temperature MyTempWithNonUnitValueFromBinding "I prefer Celsius [%d °C]" { channel="something_that_returns_F"[profile="ruby:set_uom", unit="°F"] }

Create a profile that is usable in the UI

config_description = config_description do
  parameter :min, :decimal, label: "Minimum", description: "Minimum value"
  parameter :max, :decimal, label: "Maximum", description: "Maximum value"
end

profile(:range_filter, label: "Range Filter", config_description: config_description) do |event, state:, configuration:|
  return true unless event == :state_from_handler

  (configuration["min"]..configuration["max"]).cover?(state)
end

Parameters:

  • id (String, Symbol)

    The id for the profile.

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

    The label for the profile. When nil, the profile will not be visible in the UI.

  • config_description (org.openhab.core.config.core.ConfigDescription, nil) (defaults to: nil)

    The configuration description for the profile so that it can be configured in the UI.

Yields:

  • (event, command: nil, state: nil, time_series: nil, callback:, link:, item:, channel_uid:, configuration:, context:)

    All keyword params are optional. Any that aren’t defined won’t be passed.

Yield Parameters:

  • event (:command_from_item, :state_from_item, :command_from_handler, :state_from_handler, :time_series_from_handler)

    The event that needs to be processed.

  • command (Command, nil)

    The command being sent for ‘:command_from_item` and `:command_from_handler` events.

  • state (State, nil)

    The state being sent for ‘:state_from_item` and `:state_from_handler` events.

  • time_series (TimeSeries)

    The time series for ‘:time_series_from_handler` events. Only available since openHAB 4.1.

  • callback (Core::Things::ProfileCallback)

    The callback to be used to customize the action taken.

  • link (Core::Things::ItemChannelLink)

    The link between the item and the channel, including its configuration.

  • item (Item)

    The linked item.

  • channel_uid (Core::Things::ChannelUID)

    The linked channel.

  • configuration (Hash)

    The profile configuration.

  • context (org.openhab.core.thing.profiles.ProfileContext)

    The profile context.

Yield Returns:

  • (Boolean)

    Return true from the block in order to have default processing.

Raises:

  • (ArgumentError)

See Also:

  • orgorg.openhaborg.openhab.coreorg.openhab.core.thingorg.openhab.core.thing.profilesorg.openhab.core.thing.profiles.Profile
  • orgorg.openhaborg.openhab.coreorg.openhab.core.thingorg.openhab.core.thing.profilesorg.openhab.core.thing.profiles.StateProfile
  • orgorg.openhaborg.openhab.coreorg.openhab.core.thingorg.openhab.core.thing.profilesorg.openhab.core.thing.profiles.TimeSeriesProfile


167
168
169
170
171
172
173
174
175
# File 'lib/openhab/dsl.rb', line 167

def profile(id, label: nil, config_description: nil, &block)
  raise ArgumentError, "Block is required" unless block

  id = id.to_s

  ThreadLocal.thread_local(openhab_rule_type: "profile", openhab_rule_uid: id) do
    Core::ProfileFactory.instance.register(id, block, label: label, config_description: config_description)
  end
end

.provider(*providers, **providers_by_type) { ... } ⇒ Object

Sets the implicit provider(s) for operations inside the block.

Examples:

provider(metadata: :persistent) do
  Switch1.[:last_status_from_service] = status
end

provider!(metadata: { last_status_from_service: :persistent }, Switch2: :persistent)
Switch1.[:last_status_from_service] = status # this will persist in JSONDB
Switch1.[:homekit] = "Lightbulb" # this will be removed when the script is deleted
Switch2.[:homekit] = "Lightbulb" # this will persist in JSONDB

Parameters:

  • providers (Core::Provider, org.openhab.core.common.registry.ManagedProvider, :persistent, :transient, Proc)

    An explicit provider to use. If it’s a Core::Provider, the type will be inferred automatically. Otherwise it’s applied to all types.

  • providers_by_type (Hash)

    A list of providers by type. Type can be ‘:items`, `:metadata`, `:things`, `:links`, an Item applying the provider to all metadata on that item, or a String or Symbol applying the provider to all metadata of that namespace.

    The provider can be a Provider, ‘:persistent`, `:transient`, or a Proc returning one of those types. When the Proc is called for metadata elements, the Core::Items::Metadata::Hash will be passed as an argument. Therefore it’s recommended that you use a Proc, not a Lambda, for permissive argument matching.

Yields:

  • The block will be executed using the specified provider(s).

Returns:

  • (Object)

    the result of the block

Raises:

  • (ArgumentError)

See Also:



892
893
894
895
896
897
898
899
900
901
# File 'lib/openhab/dsl.rb', line 892

def provider(*providers, **providers_by_type)
  raise ArgumentError, "You must give a block to set the provider for the duration of" unless block_given?

  begin
    old_providers = provider!(*providers, **providers_by_type)
    yield
  ensure
    Thread.current[:openhab_providers] = old_providers
  end
end

.provider!(things: nil, items: nil, metadata: nil, links: nil, **metadata_items) ⇒ Hash

Note:

This method is only intended for use at the top level of rule scripts. If it’s used within library methods, or hap-hazardly within rules, things can get very confusing because the prior state won’t be properly restored.

Permanently set the implicit provider(s) for this thread.

provider! calls are cumulative - additional calls will not erase the effects of previous calls unless they are for the same provider type.

Parameters:

  • providers (Core::Provider, org.openhab.core.common.registry.ManagedProvider, :persistent, :transient, Proc)

    An explicit provider to use. If it’s a Core::Provider, the type will be inferred automatically. Otherwise it’s applied to all types.

  • providers_by_type (Hash)

    A list of providers by type. Type can be ‘:items`, `:metadata`, `:things`, `:links`, an Item applying the provider to all metadata on that item, or a String or Symbol applying the provider to all metadata of that namespace.

    The provider can be a Provider, ‘:persistent`, `:transient`, or a Proc returning one of those types. When the Proc is called for metadata elements, the Core::Items::Metadata::Hash will be passed as an argument. Therefore it’s recommended that you use a Proc, not a Lambda, for permissive argument matching.

Returns:

  • (Hash)

    the prior provider configuration.

See Also:



934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
# File 'lib/openhab/dsl.rb', line 934

def provider!(*providers, **providers_by_type)
  thread_providers = Thread.current[:openhab_providers] ||= {}
  old_providers = thread_providers.dup

  providers.each do |provider|
    case provider
    when Core::Provider
      thread_providers[provider.class.type] = provider
    when org.openhab.core.common.registry.ManagedProvider
      type = provider.type
      unless type
        raise ArgumentError, "#{provider.inspect} is for objects which are not supported by openhab-scripting"
      end

      thread_providers[type] = provider
    when Proc,
      :transient,
      :persistent
      Core::Provider::KNOWN_TYPES.each do |known_type|
        thread_providers[known_type] = provider
      end
    when Hash
      # non-symbols can't be used as kwargs, so Item keys show up as a separate hash here
      # just merge it in, and allow it to be handled below
      providers_by_type.merge!(provider)
    else
      raise ArgumentError, "#{provider.inspect} is not a valid provider"
    end
  end

  providers_by_type.each do |type, provider|
    case provider
    when Proc,
      org.openhab.core.common.registry.ManagedProvider,
      :transient,
      :persistent,
      nil
      nil
    else
      raise ArgumentError, "#{provider.inspect} is not a valid provider"
    end

    case type
    when :items, :metadata, :things, :links
      if provider.is_a?(org.openhab.core.common.registry.ManagedProvider) && provider.type != type
        raise ArgumentError, "#{provider.inspect} is not a provider for #{type}"
      end

      thread_providers[type] = provider
    when Symbol, String
      (thread_providers[:metadata_namespaces] ||= {})[type.to_s] = provider
    when Item
      (thread_providers[:metadata_items] ||= {})[type.name] = provider
    else
      raise ArgumentError, "#{type.inspect} is not provider type"
    end
  end

  old_providers
end

.rule(name = nil, **kwargs) {|rule| ... } ⇒ Core::Rules::Rule?

Create a new rule

The rule must have at least one trigger and one execution block. To create a “script” without any triggers, use script.

Examples:

require "openhab/dsl"

rule "name" do
  <one or more triggers>
  <one or more execution blocks>
  <zero or more guards>
end

Parameters:

  • name (String) (defaults to: nil)

    The rule name

Yields:

Yield Parameters:

  • rule (Rules::BuilderDSL)

    Optional parameter to access the rule configuration from within execution blocks and guards.

Returns:

See Also:



59
60
61
# File 'lib/openhab/dsl.rb', line 59

def rule(name = nil, **kwargs, &block)
  rules.build { rule(name, **kwargs, &block) }
end

.rulesCore::Rules::Registry

Fetches all rules from the rule registry.



223
224
225
# File 'lib/openhab/dsl.rb', line 223

def rules
  Core::Rules::Registry.instance
end

.scene(name = nil, description: nil, id: nil, tag: nil, tags: nil, **kwargs) { ... } ⇒ Core::Rules::Rule

Create a new scene

A scene is a rule with no triggers. It can be called by various other actions, such as the Run Rules action.

Parameters:

Yields:

  • Block executed when the script is executed.

Returns:



64
65
66
# File 'lib/openhab/dsl.rb', line 64

def scene(name = nil, description: nil, id: nil, tag: nil, tags: nil, **kwargs, &block)
  rules.build { scene(name, description: description, id: id, tag: tag, tags: tags, **kwargs, &block) }
end

.script(name = nil, description: nil, id: nil, tag: nil, tags: nil, **kwargs) { ... } ⇒ Core::Rules::Rule

Create a new script

A script is a rule with no triggers. It can be called by various other actions, such as the Run Rules action, or by calling Core::Rules::Rule#trigger.

Scripts can be executed with some additional context, similar to method parameters (see Core::Rules::Rule#trigger). The context can be accessed from within the script’s execution block as a “local” variable.

Examples:

A simple script

# return the script object into a variable
door_check = script "Check all doors", id: "door_check", tags: :security do
  open_doors = gDoors.members.select(&:open?).map(&:label).join(", ")
  notify("The following doors are open: #{open_doors}") unless open_doors.empty?
end

# run is an alias of trigger
door_check.run

A script with context

# This script expects to be called with `message` as context/parameter
DESTINATION_EMAIL = "[email protected]"
script "Send Notifications", id: "send_alert" do
  notify(message)
  things["mail:smtp:local"].send_mail(DESTINATION_EMAIL, "OpenHAB Alert", message)
end

rules.scripts["send_alert"].run(message: "The door is open!")

Parameters:

Yields:

  • Block executed when the script is executed.

Returns:

See Also:



69
70
71
# File 'lib/openhab/dsl.rb', line 69

def script(name = nil, description: nil, id: nil, tag: nil, tags: nil, **kwargs, &block)
  rules.build { script(name, description: description, id: id, tag: tag, tags: tags, **kwargs, &block) }
end

.shared_cacheCore::ValueCache

Note:

Only the sharedCache is exposed in Ruby. For a private cache, simply use an instance variable. See Instance Variables.

Note:

Because every script or UI rule gets its own JRuby engine instance, you cannot rely on being able to access Ruby objects between them. Only objects that implement a Java interface that’s part of Java or openHAB Core (such as Hash implements javajava.utiljava.util.Map, or other basic datatypes) can be reliably stored and accessed from the shared cache. Likewise, you can use the cache to access data from other scripting languages, but they’ll be all but useless in Ruby. It’s best to stick to simple data types. If you’re having troubles, serializing to_json before storing may help.

ValueCache is the interface used to access a shared cache available between scripts and/or rule executions.

While ValueCache looks somewhat like a Hash, it does not support iteration of the contained elements. So it’s limited to strictly storing, fetching, or removing known elements.

Shared caches are not persisted between openHAB restarts. And in fact, if all scripts are unloaded that reference a particular key, that key is removed.

Examples:

shared_cache.compute_if_absent(:execution_count) { 0 }
shared_cache[:execution_count] += 1

Returns:

  • (Core::ValueCache)

    the cache shared among all scripts and UI rules in all languages.

See Also:



214
215
216
# File 'lib/openhab/dsl.rb', line 214

def shared_cache
  $sharedCache
end

.sitemapsCore::Sitemaps::Provider



265
266
267
# File 'lib/openhab/dsl.rb', line 265

def sitemaps
  Core::Sitemaps::Provider.instance
end

.store_states(*items) ⇒ Core::Items::StateStorage

Store states of supplied items

Takes one or more items and returns a map ‘=> State` with the current state of each item. It is implemented by calling openHAB’s [events.storeStates()](www.openhab.org/docs/configuration/actions.html#event-bus-actions).

Examples:

states = store_states Item1, Item2
...
states.restore

With a block

store_states Item1, Item2 do
  ...
end # the states will be restored here

Parameters:

  • items (Item)

    Items to store states of.

Returns:



592
593
594
595
596
597
598
599
# File 'lib/openhab/dsl.rb', line 592

def store_states(*items)
  states = Core::Items::StateStorage.from_items(*items)
  if block_given?
    yield
    states.restore
  end
  states
end

.thingsCore::Things::Registry

Get all things known to openHAB

Examples:

things.each { |thing| logger.info("Thing: #{thing.uid}")}
logger.info("Thing: #{things['astro:sun:home'].uid}")
homie_things = things.select { |t| t.thing_type_uid == "mqtt:homie300" }
zwave_things = things.select { |t| t.binding_id == "zwave" }
homeseer_dimmers = zwave_things.select { |t| t.thing_type_uid.id == "homeseer_hswd200_00_000" }
things['zwave:device:512:node90'].uid.bridge_ids # => ["512"]
things['mqtt:topic:4'].uid.bridge_ids # => []

Returns:



283
284
285
# File 'lib/openhab/dsl.rb', line 283

def things
  Core::Things::Registry.instance
end

.throttle_for(duration, id: nil, &block) ⇒ void

This method returns an undefined value.

Rate-limits block executions by delaying calls and only executing the last call within the given duration.

When throttle_for is called, it will hold from executing the block and start a fixed timer for the given duration. Should more calls occur during this time, keep holding and once the wait time is over, execute the block.

throttle_for will execute the block after it had waited for the given duration, regardless of how frequently ‘throttle_for` was called. 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.

Parameters:

  • id (Object) (defaults to: nil)

    ID to associate with this call.

  • block (Block)

    The block to be throttled.

  • duration (Duration)

    The minimum amount of time to wait inbetween rule executions.

See Also:



536
537
538
# File 'lib/openhab/dsl.rb', line 536

def throttle_for(duration, id: nil, &block)
  debounce(for: duration, id: id, &block)
end

.timersTimerManager

Provides access to timers created by after

Returns:



291
292
293
# File 'lib/openhab/dsl.rb', line 291

def timers
  TimerManager.instance
end

.transform(type, function, value) ⇒ String

Applies a transformation of a given type with some function to a value.

Examples:

Run a transformation

transform(:map, "myfan.map", 0)

Parameters:

  • type (String, Symbol)

    The transformation type, e.g. REGEX or MAP

  • function (String, Symbol)

    The function to call. This value depends on the transformation type

  • value (String)

    The value to apply the transformation to

Returns:

  • (String)

    the transformed value, or the original value if an error occurred



541
542
543
# File 'lib/openhab/dsl.rb', line 541

def transform(type, function, value)
  Transformation.transform(type, function, value)
end

.unit(*units) { ... } ⇒ Object .unit(dimension) ⇒ javax.measure.Unit

Sets the implicit unit(s) for operations inside the block.

Overloads:

  • .unit(*units) { ... } ⇒ Object

    Sets the implicit unit(s) for this thread such that classes operating inside the block can perform automatic conversions to the supplied unit for QuantityType.

    To facilitate conversion of multiple dimensioned and dimensionless numbers the unit block may be used. The unit block attempts to do the _right thing_ based on the mix of dimensioned and dimensionless items within the block. Specifically all dimensionless items are converted to the supplied unit, except when they are used for multiplication or division.

    Examples:

    Arithmetic Operations Between QuantityType and Numeric

    # Number:Temperature NumberC = 23 °C
    # Number:Temperature NumberF = 70 °F
    # Number Dimensionless = 2
    unit('°F') { NumberC.state - NumberF.state < 4 }                                      # => true
    unit('°F') { NumberC.state - 24 | '°C' < 4 }                                          # => true
    unit('°F') { (24 | '°C') - NumberC.state < 4 }                                        # => true
    unit('°C') { NumberF.state - 20 < 2 }                                                 # => true
    unit('°C') { NumberF.state - Dimensionless.state }                                    # => 19.11 °C
    unit('°C') { NumberF.state - Dimensionless.state < 20 }                               # => true
    unit('°C') { Dimensionless.state + NumberC.state == 25 }                              # => true
    unit('°C') { 2 + NumberC.state == 25 }                                                # => true
    unit('°C') { Dimensionless.state * NumberC.state == 46 }                              # => true
    unit('°C') { 2 * NumberC.state == 46 }                                                # => true
    unit('°C') { ( (2 * (NumberF.state + NumberC.state) ) / Dimensionless.state ) < 45 }  # => true
    unit('°C') { [NumberC.state, NumberF.state, Dimensionless.state].min }                # => 2

    Commands and Updates inside a unit block

    unit('°F') { NumberC << 32 }; NumberC.state                                           # => 0 °C
    # Equivalent to
    NumberC << "32 °F"
    # or
    NumberC << 32 | "°F"

    Specifying Multiple Units

    unit("°C", "kW") do
      TemperatureItem.update("50 °F")
      TemperatureItem.state < 20          # => true. TemperatureItem.state < 20 °C
      PowerUsage.update("3000 W")
      PowerUsage.state < 10               # => true. PowerUsage.state < 10 kW
    end

    Parameters:

    • units (String, javax.measure.Unit)

      Unit or String representing unit

    Yields:

    • The block will be executed in the context of the specified unit(s).

    Returns:

    • (Object)

      the result of the block

  • .unit(dimension) ⇒ javax.measure.Unit

    Returns The current unit for the thread of the specified dimensions.

    Examples:

    unit(SIUnits::METRE.dimension) # => ImperialUnits::FOOT

    Parameters:

    • dimension (javax.measure.Dimension)

      The dimension to fetch the unit for.

    Returns:

    • (javax.measure.Unit)

      The current unit for the thread of the specified dimensions

Yields:

Raises:

  • (ArgumentError)


805
806
807
808
809
810
811
812
813
814
815
816
817
818
# File 'lib/openhab/dsl.rb', line 805

def unit(*units)
  if units.length == 1 && units.first.is_a?(javax.measure.Dimension)
    return Thread.current[:openhab_units]&.[](units.first)
  end

  raise ArgumentError, "You must give a block to set the unit for the duration of" unless block_given?

  begin
    old_units = unit!(*units)
    yield
  ensure
    Thread.current[:openhab_units] = old_units
  end
end

.unit!(*units) ⇒ Hash<javax.measure.Dimension=>javax.measure.Unit> .unit!Hash<javax.measure.Dimension=>javax.measure.Unit>

Note:

This method is only intended for use at the top level of rule scripts. If it’s used within library methods, or hap-hazardly within rules, things can get very confusing because the prior state won’t be properly restored.

Permanently sets the implicit unit(s) for this thread

unit! calls are cumulative - additional calls will not erase the effects of previous calls unless they are for the same dimension.

Overloads:

  • .unit!(*units) ⇒ Hash<javax.measure.Dimension=>javax.measure.Unit>

    Examples:

    Set several defaults at once

    unit!("°F", "ft", "lbs")
    (50 | "°F") == 50 # => true

    Calls are cumulative

    unit!("°F")
    unit!("ft")
    (50 | "°F") == 50 # => true
    (2 | "yd") == 6 # => true

    Subsequent calls override the same dimension from previous calls

    unit!("yd")
    unit!("ft")
    (2 | "yd") == 6 # => true

    Parameters:

    • units (String, javax.measure.Unit)

      Unit or String representing unit.

  • .unit!Hash<javax.measure.Dimension=>javax.measure.Unit>

    Clear all unit settings

    Examples:

    Clear all unit settings

    unit!("ft")
    unit!
    (2 | "yd") == 6 # => false

Returns:

  • (Hash<javax.measure.Dimension=>javax.measure.Unit>)

    the prior unit configuration



861
862
863
864
865
866
867
868
869
870
# File 'lib/openhab/dsl.rb', line 861

def unit!(*units)
  units = units.each_with_object({}) do |unit, r|
    unit = org.openhab.core.types.util.UnitUtils.parse_unit(unit) if unit.is_a?(String)
    r[unit.dimension] = unit
  end

  old_units = Thread.current[:openhab_units] || {}
  Thread.current[:openhab_units] = units.empty? ? {} : old_units.merge(units)
  old_units
end