Item and State Type Comparisons
Comparisons of items implicitly compare the states.
# When Item1, Item2 and Item3 all have the same state:
# State comparison
Item1 == Item2 # => true - when they both have the same state
Item1 == items['Item2'] # => true - same as above
Item1 == ON # => true if Item1's state is ON
ON == Item1 # => true if Item1's state is ON
case Item1
when OFF then logger.info('Item1 is OFF')
when ON then logger.info('Item1 is ON')
end
NumberItem1 > 5 # Item can be compared against numbers
Outside_Temp > '24 °C' # with Units of Measurement
StringItem1 == 'single' # or string
DateItem1 < Time.now # or date/time
DateItem1 < '2022-01-01' # datetime against string
Item Comparisons
To compare item objects, use the #item
method on either item:
# When Item1, Item2 and Item3 all have the same state:
# Item comparison
Item1 == Item2.item # => false - different items
Item1 == items['Item2'].item # => false - different items
Item1 == items['Item1'].item # => true - same item
# Using eql?
Item1.eql?(Item2) # => false - different items
Item1.eql?(items['Item1']) # => true - same item
# Case statement. Given Item1, Item2 and Item3 are OFF
result = case Item1 # => 'off'
when OFF then 'off' # OFF === Item1 is true, first match
when Item2 then 'i2'
when Item1 then 'i1'
end
result = case Item1.item # => 'i1'
when OFF then 'off' # OFF === Item1.item => false
when Item2 then 'i2' # Item2 === Item1.item => false
when Item1 then 'i1' # Item1 === Item1.item => true
end
Items within Hashes, Arrays and Groups
Using #item
is needed for Array#include?
and Hash#value?
, but not necessary for hash keys or grep
:
# Given that:
# Item1, Item2 and Item2 contain the same state
# Item1 and Item2 belong to Group12 group
# Item3 doesn't belong to Group12 group
# Arrays and Group #include? will compare against the given item's state
[Item1, Item2].include?(Item3) # => true - array has item(s) whose state match Item3's state
[Item1, Item2].include?(Item3.state) # => true - same as above
# To check for the actual item, use .item
[Item1, Item2].include?(Item3.item) # => false - now we're checking for the item object
Group12.include?(Item3) # => true - Item3 is not in the group but its state matches
Group12.include?(Item3.item) # => false - now we're checking for the actual item
# GOTCHA: Hash keys and grep operate on the item itself, not its state
hash = { Item1 => 'value1', Item2 => 'value2' }
hash[Item3] # => nil - this will look up the item
hash[Item2] # => 'value2' - this will look up the item
hash[Item2.item] # => 'value2' - we can use an explicit .item
[Item1, Item2].grep(Item3) # => [] - grep also looks up the item, not the state
[Item1, Item2].grep(Item3.item) # => [] - we can use an explicit .item
[Item1, Item2].grep(Item3.state) # => [Item1, Item2] - same state as Item3's state
In summary: To avoid ambiguity, use the item’s
#item
method whenever the item’s object needs to be matched.
Type Comparisons
Some OpenHAB item types can accept different command types. For example, a Dimmer item can accept a command with an OnOffType
, IncreaseDecreaseType
or a PercentType
. However, ultimately an item only stores its state in its native type, e.g. a Dimmer item’s native type is PercentType.
Loose Type-Comparisons
Comparisons between two compatible types will return true when applicable, for example:
- 0 (
PercentType
) equalsOFF
and theoff?
predicate will return true - A positive
PercentType
equalsON
and theon?
predicate will return true
DimmerItem1.update(10)
sleep 1
DimmerItem1 == 10 # => true
DimmerItem1 == ON # => true
DimmerItem1.on? # => true
DimmerItem1.off? # => false
rule 'command' do
received_command DimmerItem1
run do |event|
if event.command.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
Bypassing Loose Type-Comparisons
If at any point you want to bypass loose type conversions, use eql?
. Just be aware that this also bypasses the implicit conversion of an Item to its state.
DimmerItem1.update(10)
sleep 1
logger.error DimmerItem1.eql?(10) # => false. It compares the _item_ object not its state
logger.error DimmerItem1.eql?(items['DimmerItem1']) # => true. It compares the _item_ object
logger.error DimmerItem1.state.eql?(ON) # => false
logger.error DimmerItem1.state.eql?(10) # => false
logger.error DimmerItem1.state.eql?(PercentType.new(10)) # => true
Strict Type-Comparisons
Sometimes it is critical to know the exact command being sent. For example, a rule may need to distinguish between ON
vs. a PercentType
command. In this instance, Ruby’s case equality operator ===
can be used. It will only evaluate to true if the two operands have the same type.
The strict type comparison applies to Ruby’s case
statement because it is implemented using the case equality operator ===
rule 'command' do
received_command DimmerItem1
run do |event|
case event.command
when ON then logger.info("DimmerItem1 received an ON command")
when OFF then logger.info("DimmerItem1 received an OFF command")
when 0 then logger.info("DimmerItem1 received 0 percent")
when 1..99 then logger.info("DimmerItem1 received between 1 and 99 percent")
when 100 then logger.info("DimmerItem1 received 100 percent")
when INCREASE then logger.info("Increase")
when DECREASE then logger.info("Decrease")
when REFRESH then logger.info("Refresh")
end
end
end
Regular expressions can still be used on a StringType command.
rule 'command' do
received_command StringItem1
run do |event|
case event.command
when /abc/ then logger.info('Command contains "abc"')
else logger.info('Received something else')
end
end
end
StringItem1 << '123 abc 456' # This will log 'Command contains "abc"'
Comparisons Against States
Because case
statements match against the underlying item or state’s type, beware of the following case. Note that we are checking the event’s state this time, not command.
rule 'dimmer' do
changed DimmerItem1
run do |event|
case event.state
when ON then logger.info("This will never match")
when OFF then logger.info("Neither will this")
else logger.info("This will always be the case")
end
end
end
The correct way to handle this would be to use the underlying type which is PercentType or Numeric:
rule 'dimmer' do
changed DimmerItem1
run do |event|
case event.state
when 0 then logger.info("The dimmer is off")
when 1..100 then logger.info("The dimmer is on")
else logger.info("The dimmer is either NULL or UNDEF")
end
end
end