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.18.0"

Rule Creation collapse

Rule Support collapse

Object Access collapse

Utilities collapse

Block Modifiers collapse

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

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!



1092
1093
1094
1095
1096
1097
1098
# File 'lib/openhab/dsl.rb', line 1092

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:



423
424
425
426
427
428
429
# File 'lib/openhab/dsl.rb', line 423

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:



453
454
455
456
457
458
459
# File 'lib/openhab/dsl.rb', line 453

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:

Raises:

  • (ArgumentError)


219
220
221
222
223
# File 'lib/openhab/dsl.rb', line 219

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:



525
526
527
528
# File 'lib/openhab/dsl.rb', line 525

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.



713
714
715
716
717
718
# File 'lib/openhab/dsl.rb', line 713

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:



662
663
664
665
666
# File 'lib/openhab/dsl.rb', line 662

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:



1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
# File 'lib/openhab/dsl.rb', line 1043

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:



1068
1069
1070
# File 'lib/openhab/dsl.rb', line 1068

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.

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:



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

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:



588
589
590
591
# File 'lib/openhab/dsl.rb', line 588

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:



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

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:



760
761
762
763
764
# File 'lib/openhab/dsl.rb', line 760

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:



189
190
191
192
193
194
195
196
197
# File 'lib/openhab/dsl.rb', line 189

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.metadata[:last_status_from_service] = status
end

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



914
915
916
917
918
919
920
921
922
923
# File 'lib/openhab/dsl.rb', line 914

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:



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
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
# File 'lib/openhab/dsl.rb', line 956

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, id: 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.

When explicit id is not provided, the rule's ID will be inferred from the block's source location, and a suffix will be added to avoid clashing against existing rules.

When an explicit id is provided and an existing rule with the same id already exists, the rule will not be created, and the method will return nil.

To ensure that a rule is created even when the same id already exists, use rule! or call rules.remove to remove any existing rule prior to creating the new rule.

Examples:

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

Create a rule with an explicit id, deleting any existing rule with the same id

rule! "name", id: "my_happy_day_reminder" do
  every :day
  run { logger.info "Happy new day!" }
end

Parameters:

  • name (String) (defaults to: nil)

    The rule name

  • id (String) (defaults to: nil)

    The rule's ID. This can also be defined in the block using uid.

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, id: nil, **kwargs, &block)
  rules.build { rule(name, id: id, **kwargs, &block) }
end

.rule!(name = nil, id: nil, **kwargs, &block) ⇒ Object

Creates a rule that will remove existing rules with the same id, even when the id has been inferred.

See Also:



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

def rule!(name = nil, id: nil, **kwargs, &block)
  rules.build { rule(name, id: id, replace: true, **kwargs, &block) }
end

.rulesCore::Rules::Registry

Fetches all rules from the rule registry.



245
246
247
# File 'lib/openhab/dsl.rb', line 245

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:

See Also:



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

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

.scene!(name = nil, description: nil, id: nil, tag: nil, tags: nil, **kwargs, &block) ⇒ Object

Creates a scene that will remove existing rules/scenes with the same id, even when the id has been inferred.

See Also:



76
77
78
79
80
# File 'lib/openhab/dsl.rb', line 76

def scene!(name = nil, description: nil, id: nil, tag: nil, tags: nil, **kwargs, &block)
  rules.build do
    scene(name, description: description, id: id, tag: tag, tags: tags, replace: true, **kwargs, &block)
  end
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 = "myemail@example.com"
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:



83
84
85
# File 'lib/openhab/dsl.rb', line 83

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

.script!(name = nil, description: nil, id: nil, tag: nil, tags: nil, **kwargs, &block) ⇒ Object

Creates a script that will remove existing rules/scripts with the same id, even when the id has been inferred.

See Also:



89
90
91
92
93
# File 'lib/openhab/dsl.rb', line 89

def script!(name = nil, description: nil, id: nil, tag: nil, tags: nil, **kwargs, &block)
  rules.build do
    script(name, description: description, id: id, tag: tag, tags: tags, replace: true, **kwargs, &block)
  end
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 java.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:



236
237
238
# File 'lib/openhab/dsl.rb', line 236

def shared_cache
  $sharedCache
end

.sitemapsCore::Sitemaps::Provider



287
288
289
# File 'lib/openhab/dsl.rb', line 287

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 {Item => State} with the current state of each item. It is implemented by calling openHAB's events.storeStates().

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:



614
615
616
617
618
619
620
621
# File 'lib/openhab/dsl.rb', line 614

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:



305
306
307
# File 'lib/openhab/dsl.rb', line 305

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:



558
559
560
# File 'lib/openhab/dsl.rb', line 558

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

.timersTimerManager

Provides access to timers created by after

Returns:



313
314
315
# File 'lib/openhab/dsl.rb', line 313

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



563
564
565
# File 'lib/openhab/dsl.rb', line 563

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:

    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:

    Returns:

Yields:

Raises:

  • (ArgumentError)


827
828
829
830
831
832
833
834
835
836
837
838
839
840
# File 'lib/openhab/dsl.rb', line 827

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:

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



883
884
885
886
887
888
889
890
891
892
# File 'lib/openhab/dsl.rb', line 883

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