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.12.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!



1066
1067
1068
1069
1070
1071
1072
# File 'lib/openhab/dsl.rb', line 1066

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:



397
398
399
400
401
402
403
# File 'lib/openhab/dsl.rb', line 397

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:



427
428
429
430
431
432
433
# File 'lib/openhab/dsl.rb', line 427

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)


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

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:



499
500
501
502
# File 'lib/openhab/dsl.rb', line 499

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.



687
688
689
690
691
692
# File 'lib/openhab/dsl.rb', line 687

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:



636
637
638
639
640
# File 'lib/openhab/dsl.rb', line 636

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:



1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
# File 'lib/openhab/dsl.rb', line 1017

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:



1042
1043
1044
# File 'lib/openhab/dsl.rb', line 1042

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:



256
257
258
# File 'lib/openhab/dsl.rb', line 256

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:



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

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:



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

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:



734
735
736
737
738
# File 'lib/openhab/dsl.rb', line 734

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, 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, 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)

    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.

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



163
164
165
166
167
168
169
170
171
# File 'lib/openhab/dsl.rb', line 163

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:



888
889
890
891
892
893
894
895
896
897
# File 'lib/openhab/dsl.rb', line 888

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:



930
931
932
933
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
# File 'lib/openhab/dsl.rb', line 930

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.



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

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

.scene(name = nil, id: 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:

  • name (String) (defaults to: nil)

    A descriptive name

  • id (String) (defaults to: nil)

    The script's ID

Yields:

  • [] Block executed when the script is executed.

Returns:



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

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

.script(name = nil, id: 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.

Parameters:

  • name (String) (defaults to: nil)

    A descriptive name

  • id (String) (defaults to: nil)

    The script's ID

Yields:

  • [] Block executed when the script is executed.

Returns:



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

def script(name = nil, id: nil, **kwargs, &block)
  rules.build { script(name, id: id, **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 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:



210
211
212
# File 'lib/openhab/dsl.rb', line 210

def shared_cache
  $sharedCache
end

.sitemapsCore::Sitemaps::Provider



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

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:



588
589
590
591
592
593
594
595
# File 'lib/openhab/dsl.rb', line 588

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:



279
280
281
# File 'lib/openhab/dsl.rb', line 279

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:



532
533
534
# File 'lib/openhab/dsl.rb', line 532

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

.timersTimerManager

Provides access to timers created by after

Returns:



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

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



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

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)


801
802
803
804
805
806
807
808
809
810
811
812
813
814
# File 'lib/openhab/dsl.rb', line 801

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:



857
858
859
860
861
862
863
864
865
866
# File 'lib/openhab/dsl.rb', line 857

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