- [Early Exit From a Rule](#early-exit-from-a-rule)
- [Dynamic Generation of Rules](#dynamic-generation-of-rules)
- [Hooks](#hooks)
- [Calling Java From JRuby](#calling-java-from-jruby)
Additional [example rules are available](https://openhab.github.io/openhab-jruby/5.0/file.examples.html), as well as examples of [conversions from DSL and Python rules](https://openhab.github.io/openhab-jruby/5.0/file.conversions.html).
## Why Ruby?
- Ruby is designed for programmers' productivity with the idea that programming should be fun for programmers.
- Ruby emphasizes the necessity for software to be understood by humans first and computers second.
- Ruby makes writing automation enjoyable without having to fight with compilers and interpreters.
- Rich ecosystem of tools, including things like Rubocop to help developers write clean code and RSpec to test the libraries.
- Ruby is really good at letting one express intent and create a DSL to make that expression easier.
After installing this add-on, you will find configuration options in the openHAB portal under _Settings -> Other Services -> JRuby Scripting_.
Alternatively, JRuby configuration parameters may be set by creating a `jruby.cfg` file in `conf/services/`.
By default this add-on includes the [openhab-scripting](https://github.com/openhab/openhab-jruby) Ruby gem and automatically `require`s it.
This allows the use of [items](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL.html#items-class_method), [rules](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL.html#rules-class_method), [shared_cache](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL.html#shared_cache-class_method) and other objects in your scripts.
This functionality can be disabled for users who prefer to manage their own gems and `require`s via the add-on configuration options.
Simply change the `gems` and `require` configuration settings.
| `openhab-scripting` | install the latest version of `openhab-scripting` gem |
| `openhab-scripting=~>5.0.0` | install the latest version 5.0.x but not 5.1.x |
| `openhab-scripting=~>5.0` | install the latest version 5.x but not 6.x |
| `openhab-scripting=~>5.0, faraday=~>2.7;>=2.7.4` | install `openhab-scripting` gem version 5.x and `faraday` gem version 2.7.4 or higher, but less than 3.0 |
| `gem1= >= 2.2.1; <= 2.2.5` | install `gem1` gem version 2.2.1 or above, but less than or equal to version 2.2.5 |
Theoretically you could even use a system start trigger with a UI rule, and then use the [syntax](#file-based-rules) mostly developed for file based rules to create multiple rules.
### File Based Scripts
The JRuby Scripting addon will load scripts from `automation/ruby` in the user configuration directory.
The system will automatically reload scripts when changes are detected to files.
Local variable state is not persisted among reloads, see using the [cache](#cache) for a convenient way to persist objects.
See [File Based Rules](#file-based-rules) for examples of creating rules within your scripts.
### Event Object
When you use "Item event" as trigger (i.e. "[item] received a command", "[item] was updated", "[item] changed"), there is additional context available for the action in a variable called `event`.
This tables gives an overview of the `event` object for most common trigger types. For full details, explore [OpenHAB::Core::Events](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Events.html).
| `state` | [State](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/State.html) or `nil` | `[item] changed`, `[item] was updated` | State that triggered event | `triggeringItem.state` |
| `was` | [State](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/State.html) or `nil` | `[item] changed` | Previous state of Item or Group that triggered event | `previousState` |
| `command` | [Command](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/Command.html) | `[item] received a command` | Command that triggered event | `receivedCommand` |
| `item` | [Item](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Items/Item.html) | all | Item that triggered event | `triggeringItem` |
```ruby
logger.info(event.state == ON)
```
```ruby
event.item
```
Get the Triggering Item's Name:
```ruby
event.item.name
```
Get the Triggering Item's Label:
```ruby
event.item.label
```
Get the Triggering Item's State:
```ruby
event.state # this version retrieves the item's state when the event was generated
```
or
```ruby
event.item.state # this version will re-query the item for its state
```
```ruby
if event.item.state == ON
# do something
end
# or (preferable)
if event.item.on?
# do something
end
```
Get the Triggering Item's Previous State:
```ruby
event.was
```
```ruby
if event.was.on?
# do something
end
```
Compare Triggering Item's State Against Previous State:
```ruby
event.state > event.was
```
Get the Received Command:
```ruby
event.command
```
```ruby
if event.command.on?
# do something
end
```
## Library Details
The openHAB JRuby Scripting runtime attempts to provide a familiar environment to Ruby developers.
### Items
The [items](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL.html#items-class_method) object allows interactions with openHAB items.
However, most items can be referred to directly by name:
```ruby
My_Item
gWindowBlinds
```
Items can be retrieved dynamically:
```ruby
the_item = items['My_Item'] # This returns an Item object, not just its state
# For all intents and purposes, the_item variable is the same as My_Item in the previous example
A [Timed Command](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL/Items/TimedCommand.html) is similar to the openHAB Item's [expire parameter](https://www.openhab.org/docs/configuration/items.html#parameter-expire) but it offers more flexibility.
It removes the need to manually create a timer.
The command is sent to the item, then after the duration has elapsed, reverted.
It also handles automatically canceling the timer if the item changes states before it reverts.
```ruby
My_Switch.on for: 5.minutes
```
#### Updates
Post an update to an item:
```ruby
My_Switch.update ON
```
#### State
The Item's state is accessible through [Item#state](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Items/GenericItem.html#state-instance_method):
```ruby
if My_Item.state == ON
# do something
end
# This syntax is equivalent and preferred:
if My_Item.on?
# do something
end
if Indoor_Temperature.state > 20 | '°C' || Indoor_Temperature.state > Outdoor_Temperature.state
# do something
end
```
Note: Boolean helper methods are available depending on the item / state type.
For example `up?`, `down?`, `closed?`, `open?`, etc.
Check if an Item's state is [NULL](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/UnDefType.html#NULL-constant) of [UNDEF](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/UnDefType.html#UNDEF-constant):
Types that are comparable, such as [StringType](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/StringType.html), [DateTimeType](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/DateTimeType.html), [DecimalType](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/DecimalType.html), [PercentType](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/PercentType.html),
include Ruby's [Comparable](https://docs.ruby-lang.org/en/master/Comparable.html) module which provides
the handy [between?](https://docs.ruby-lang.org/en/master/Comparable.html#method-i-between-3F) method.
Some openHAB item types can accept different command types.
For example, a [DimmerItem](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Items/DimmerItem.html) can accept a command with an [OnOffType](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/OnOffType.html), [IncreaseDecreaseType](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/IncreaseDecreaseType.html) or a [PercentType](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/PercentType.html).
However, ultimately an item only stores its state in its native type, e.g. a [DimmerItems](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Items/DimmerItem.html)'s native type is [PercentType](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/PercentType.html).
In some contexts, you don't care about the precise value of a particular state, and just want to know if it fits the general definition of [ON](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/OnOffType.html#ON-constant), etc.
You can either explicitly convert to the general type, _or_ all of the state predicate methods available on [Item](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Items/Item.html), [ItemStateEvent](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Events/ItemStateEvent.html), [ItemStateChangedEvent](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Events/ItemStateChangedEvent.html), [ItemCommandEvent](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Events/ItemCommandEvent.html), as well as specific types such as [PercentType](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/PercentType.html) and [HSBType](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Types/HSBType.html), will do the conversion internally.
```ruby
DimmerItem1.update(10)
sleep 1
DimmerItem1.state == 10 # => true
DimmerItem1.state == ON # => false
DimmerItem1.state.as(OnOffType) == ON # => true
DimmerItem1.state.on? # => true
DimmerItem1.on? # => true
DimmerItem1.off? # => false
rule 'command' do
received_command DimmerItem1
run do |event|
if event.on?
# This will be executed even when the command is a positive PercentType
# instead of an actual ON command
logger.info("DimmerItem1 is being turned on")
end
end
end
DimmerItem1 <<100#=> This will trigger the logger.info above
```
#### Metadata
Metadata is accessed through [Item#metadata](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Items/Item.html#metadata-instance_method).
```ruby
metadata = My_Item.metadata['namespace'].value
```
#### Persistence
[Persistence](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Items/Persistence.html) methods are available directly on [Items](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Items/Item.html).
Many [helper methods](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Items/Semantics.html) are available to make it easy to navigate the semantic model to get related items.
```ruby
LivingRoom_Motion.location # Location of the motion sensor
.equipments(Semantics::Lightbulb) # Get all Lightbulb Equipments in the location
.members # Get all the member items of the equipments
.points(Semantics::Switch) # Select only items that are Switch Points
.on # Send an ON command to the items
```
#### Linked Things
If an [Item](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Items/Item.html) is linked to a [Thing](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Things/Thing.html), you can easily retrieve it.
```ruby
linked_thing = My_Item.thing
thing_uid = My_Item.thing.uid
```
An item can be linked to multiple things:
```ruby
My_Item.things.each do |thing|
logger.info("Thing: #{thing.uid}")
end
```
#### Item Builder
New items can be created via [items.build](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Items/Registry.html#build-instance_method).
Note that by default items are not persisted to storage, and will be removed when the script unloads.
[openHAB built-in actions](https://www.openhab.org/docs/configuration/actions.html) are available as children of the [Actions](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Actions.html) module.
Action classes are also imported into the top-level namespace.
Thing actions can be called directly on the [Thing](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Things/Thing.html).
The [logger](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Logger.html) is similar to a standard [Ruby Logger](https://docs.ruby-lang.org/en/master/Logger.html).
Supported logging functions include:
-`logger.log(severity, obj)`
-`logger.info(obj)`
-`logger.warn(obj)`
-`logger.error(obj)`
-`logger.debug(obj)`
-`logger.trace(obj)`
`obj` is any Ruby (or Java) object.
`#to_s` (or `toString()` if it's a Java object) is called on `obj`, and the result is output to the openHAB log.
Additionally, all of these methods can take a [Ruby block](https://ruby-doc.com/docs/ProgrammingRuby/html/tut_containers.html#S2) instead, which will only be called if logging is enabled at the given level, and the result of the block will be treated as the log message.
### Timers
```ruby
sleep 1.5 # sleep for 1.5 seconds
```
See Ruby docs on [sleep](https://docs.ruby-lang.org/en/master/Kernel.html#method-i-sleep).
`sleep` should be avoided if possible. A [delay](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL/Rules/BuilderDSL.html#delay-instance_method) can be inserted in between two execution blocks to achieve the same result. This delay is implemented with a timer.
This is available only on file-based rules.
```ruby
rule "delay something" do
on_load
run { logger.info "This will run immediately" }
delay 10.seconds
run { logger.info "This will run 10 seconds after" }
end
```
Alternatively a timer can be used in either a file-based rule or in a UI based rule using [after](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL.html#after-class_method).
After takes a [Duration](#durations), e.g. `10.minutes` instead of using [ZonedDateTime](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/ZonedDateTime.html).
```ruby
rule "simple timer" do
changed Watering_System, to: ON
run do
after(5.minutes) { Watering_System.off }
end
end
```
When a script is unloaded, all created timers are automatically cancelled.
You can access all variables of the current context in the created timers.
Note: Variables can be mutated (changed) after the timer has been created.
Be aware that this can lead to unattended side effects, e.g. when you change the variable after timer creation, which can make debugging quite difficult!
```ruby
my_var = "Hello world!";
# Schedule a timer that expires in ten seconds
after(10.seconds) do
logger.info("Timer expired with my_var = '#{my_var}'")
end
my_var = "Hello mutation!" # When the timer runs, it will log "Hello mutation!" instead of "Hello world!"
@timers[event.item] = after 3.minutes do # Use the triggering item as the timer ID
event.item.off
@timers.delete(event.item)
end
end
```
However, a built in mechanism is available to help manage multiple timers, and is done in a thread-safe manner.
This is done using timer IDs.
The following rule automatically finds and reschedules the timer matching the same ID, which corresponds to each group member.
```ruby
after 3.minutes, id: event.item do # Use the triggering item as the timer ID
event.item.off
end
```
Furthermore, you can manipulate the managed timers using the built-in [timers](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL/TimerManager.html) object.
```ruby
# timers is a special object to access the timers created with an id
rule "cancel all timers" do
received_command Cancel_All_Timers, to: ON # Send a command to this item to cancel all timers
run do
gOutdoorLights.members.each do |item_as_timer_id|
timers.cancel(item_as_timer_id)
end
end
end
rule "reschedule all timers" do
received_command Reschedule_All_Timers, to: ON # Send a command to this item to restart all timers
run do
gOutdoorLights.members.each do |item_as_timer_id|
timers.reschedule(item_as_timer_id)
end
end
end
```
### Cache
The [shared_cache](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL.html#shared_cache-class_method) object provides a cache that can be used to set and retrieve objects that will be persisted between reloads of scripts, and available between different rules.
It acts similarly to a regular Ruby Hash.
Just be wary of Ruby-only data types (such as Symbols) that won't be accessible between different scripts.
Get a previously set object with a default value, without assigning it (this version has an even longer amount of time between fetchig the value and assigning it):
```ruby
count = shared_count.fetch(:counter) { 0 }
shared_count[:counter] = count + 1
```
### Time
Several options are available for time related code, including but not limited to:
- Java [LocalDate](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/LocalDate.html) - represents a date with no time
- Java [LocalTime](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/LocalTime.html) - represents a time with no date
- Java [MonthDay](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/MonthDay.html) - represents a date with no time or year
- Java [ZonedDateTime](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/ZonedDateTime.html) - represents a specific instance with a date and time
Ruby [integers](https://docs.ruby-lang.org/en/master/Integer.html) and [floats](https://docs.ruby-lang.org/en/master/Float.html) are extended with several methods to support durations.
These methods create a new [Duration](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/Duration.html) or [Period](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/Period.html) object that is used by the [every](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL/Rules/BuilderDSL.html#every-instance_method) trigger, [delay](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL/Rules/BuilderDSL.html#delay-instance_method) block, the for option of [changed](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL/Rules/BuilderDSL.html#changed-instance_method) triggers, and [timers](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Timer.html).
```ruby
rule "run every 30 seconds" do
every 30.seconds
run { logger.info("Hello") }
end
```
```ruby
rule "Warn about open door" do
changed FrontDoor, to: OPEN, for: 10.minutes
run { |event| logger.info("#{event.item.name} has been open for 10 minutes") }
end
```
```ruby
rule "Timer example" do
on_load
run do
after(3.hours) { logger.info("3 hours have passed") }
Comparisons, conversions and arithmetic are automatic between Java and Ruby types.
Note that anytime you do a comparison between a type with more specific data, and a type missing specific data, the comparison is done as if the more specific data is at the beginning of its period.
I.e. comparing a time to a month, the month will be treated as 00:00:00 on the first day of the month.
When comparing with a type that's missing more generic data, it will be filled in from the other object.
I.e. comparing a time to a month, the month will be assumed to be in the same year as the time.
Make sure to use `#cover?` instead of `#include?` to do a simple comparison, instead of generating an array and searching it linearly.
Ranges of non-absolute, "circular" types ([LocalTime](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/LocalTime.html), [Month](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/Month.html), [MonthDay](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/MonthDay.html)) are smart enough to automatically handle boundary issues.
Coarse types (like [LocalDate](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/LocalDate.html), [Month](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/Month.html), [MonthDay](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/MonthDay.html)) will also work correctly when checking against a more specific type.
To easily parse strings into date-time ranges, use the [OpenHAB::DSL.between](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL.html#between-class_method) helper.
[Duration](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/Duration.html), [ZonedDateTime](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/ZonedDateTime.html), [LocalTime](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/LocalTime.html), [LocalDate](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/LocalDate.html), [MonthDay](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/MonthDay.html), [Month](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Java/Month.html), [Time](https://openhab.github.io/openhab-jruby/5.0/Time.html), [Date](https://openhab.github.io/openhab-jruby/5.0/Date.html), and [DateTime](https://openhab.github.io/openhab-jruby/5.0/DateTime.html) classes include [between?](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Between.html#between%3F-instance_method) method that accepts a range of string or any of the date/time objects.
[Helper methods](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/CoreExt/Ephemeris.html) to easily access [openHAB's Ephemeris action](https://www.openhab.org/docs/configuration/actions.html#ephemeris) are provided on all date-like objects:
[Rules](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Rules/Rule.html), Scenes and Scripts can be accessed using the
[rules](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Rules/Registry.html) object. For example, to execute/trigger a rule:
```ruby
rules[rule_uid].trigger
```
Scenes are rules with a `Scene` tag, and Scripts are rules with a `Script` tag. They can be found
using their uid just like normal rules, i.e. `rules[uid]`. For convenience, a list of all Scenes are
available through the enumerable [rules.scenes](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Rules/Registry.html#scenes-instance_method),
and a list of all Scripts through [rules.scripts](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/Rules/Registry.html#scripts-instance_method).
Example: All scenes tagged `sunrise` will be triggered at sunrise, and all scenes tagged
`sunset` will be triggered at sunset. Note: these use the [Terse Rule](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL/Rules/Terse.html) syntax.
Or it can be written as one rule with the help of [trigger attachments](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/DSL/Rules/BuilderDSL.html#Triggers-group).
[Bundler](https://bundler.io/) is integrated, enabling any [Ruby gem](https://rubygems.org/) compatible with JRuby to be used within rules. This permits easy access to the vast ecosystem of libraries within the Ruby community.
Gems are available using the [inline bundler syntax](https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html).
The require statement can be omitted.
```ruby
gemfile do
source 'https://rubygems.org'
gem 'json', require: false
gem 'nap', '1.1.0', require: 'rest'
end
logger.info("The nap gem is at version #{REST::VERSION}")
```
### Shared Code
If you would like to easily share code among multiple scripts, you can place it in `<OPENHAB_CONF>/automation/ruby/lib`.
You can then simply `require` the file from your rules files.
Files located in `$RUBYLIB` won't be automatically loaded individually by openHAB, only when you `require` them.
`automation/ruby/myrule.rb` OR a UI Rule's script:
This add-on also provides the necessary infrastructure to use Ruby for writing [transformations](https://www.openhab.org/docs/configuration/transformations.html).
The main value to be transformed is given to the script in a variable called `input`.
Note that the values are passed to the transformation as Strings even for numeric items and data types.
**Note**: In openHAB 3.4, due to an [issue](https://github.com/jruby/jruby/issues/5876) in the current version of JRuby,
you will need to begin your script with `input ||= nil` (and `a ||= nil` etc. for additional query variables) so that
JRuby will recognize the variables as variables--rather than method calls--when it's parsing the script.
Otherwise you will get errors like `(NameError) undefined local variable or method 'input' for main:Object`.
This is not necessary in openHAB 4.0+.
#### File Based Transformations
Once the addon is installed, you can create a Ruby file in the `$OPENHAB_CONF/transform` directory, with the extension `.script`.
It's important that the extension is `.script` so that the core `SCRIPT` transform service will recognize it.
When referencing the file, you need to specify the `SCRIPT` transform, with `rb` as the script type: `SCRIPT(rb:mytransform.script):%s`.
You can also specify additional variables to be set in the script using a URI-like query syntax: `SCRIPT(rb:mytransform.script?a=1&b=c):%s`
in order to share a single script with slightly different parameters for different items.
##### Example: Display the wind direction in degrees and cardinal direction
`weather.items`
```Xtend
Number:Angle Exterior_WindDirection "Wind Direction [SCRIPT(rb:compass.script):%s]" <wind>
```
`compass.script`
```ruby
DIRECTIONS = %w[N NE E SE S SW W NW N].freeze
if input.nil? || input == "NULL" || input == "UNDEF"
"-"
else
cardinal = DIRECTIONS[(input.to_f / 45).round]
"#{cardinal} (#{input.to_f.round}°)"
end
```
Given a state of `82 °`, this will produce a formatted state of `E (82°)`.
##### Example: Display the number of lights that are on/off within a group
```Xtend
Group gIndoorLights "Indoor Lights [SCRIPT(rb:group_count.script?group=gIndoorLights):%s]"
Group gOutdoorLights "Outdoor Lights [SCRIPT(rb:group_count.script?group=gOutdoorLights):%s]"
The rule definition itself is just Ruby code, which means you can use code to generate your rules.
Take care when doing this as the the items/groups are processed when the rules file is processed, meaning that new items/groups will not automatically generate new rules.
File based scripts can also register [hooks](https://openhab.github.io/openhab-jruby/5.0/OpenHAB/Core/ScriptHandling.html) that will be called when the script has completed loading (`script_loaded`) and when it gets unloaded (`script_unloaded`).