openhab-scripting 2.14.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openhab.rb +3 -0
  3. data/lib/openhab/core/dsl.rb +4 -0
  4. data/lib/openhab/core/dsl/actions.rb +1 -1
  5. data/lib/openhab/core/dsl/entities.rb +41 -4
  6. data/lib/openhab/core/dsl/gems.rb +1 -1
  7. data/lib/openhab/core/dsl/group.rb +3 -1
  8. data/lib/openhab/core/dsl/items/items.rb +3 -1
  9. data/lib/openhab/core/dsl/items/number_item.rb +158 -52
  10. data/lib/openhab/core/dsl/items/string_item.rb +23 -3
  11. data/lib/openhab/core/dsl/monkey_patch/items/dimmer_item.rb +20 -5
  12. data/lib/openhab/core/dsl/monkey_patch/items/items.rb +2 -0
  13. data/lib/openhab/core/dsl/monkey_patch/items/metadata.rb +66 -42
  14. data/lib/openhab/core/dsl/monkey_patch/items/persistence.rb +72 -0
  15. data/lib/openhab/core/dsl/monkey_patch/items/switch_item.rb +2 -1
  16. data/lib/openhab/core/dsl/monkey_patch/ruby/range.rb +2 -1
  17. data/lib/openhab/core/dsl/monkey_patch/ruby/ruby.rb +2 -0
  18. data/lib/openhab/core/dsl/monkey_patch/ruby/string.rb +43 -0
  19. data/lib/openhab/core/dsl/monkey_patch/types/decimal_type.rb +41 -5
  20. data/lib/openhab/core/dsl/monkey_patch/types/quantity_type.rb +58 -0
  21. data/lib/openhab/core/dsl/monkey_patch/types/types.rb +1 -0
  22. data/lib/openhab/core/dsl/persistence.rb +27 -0
  23. data/lib/openhab/core/dsl/property.rb +15 -4
  24. data/lib/openhab/core/dsl/rule/automation_rule.rb +348 -0
  25. data/lib/openhab/core/dsl/rule/guard.rb +43 -6
  26. data/lib/openhab/core/dsl/rule/rule.rb +80 -367
  27. data/lib/openhab/core/dsl/rule/rule_config.rb +153 -0
  28. data/lib/openhab/core/dsl/rule/triggers/changed.rb +145 -0
  29. data/lib/openhab/core/dsl/rule/{channel.rb → triggers/channel.rb} +22 -8
  30. data/lib/openhab/core/dsl/rule/triggers/command.rb +106 -0
  31. data/lib/openhab/core/dsl/rule/{cron.rb → triggers/cron.rb} +36 -14
  32. data/lib/openhab/core/dsl/rule/triggers/trigger.rb +126 -0
  33. data/lib/openhab/core/dsl/rule/triggers/updated.rb +100 -0
  34. data/lib/openhab/core/dsl/time_of_day.rb +50 -24
  35. data/lib/openhab/core/dsl/timers.rb +2 -6
  36. data/lib/openhab/core/dsl/types/quantity.rb +106 -69
  37. data/lib/openhab/core/log.rb +3 -8
  38. data/lib/openhab/core/startup_delay.rb +1 -0
  39. data/lib/openhab/osgi.rb +7 -0
  40. data/lib/openhab/version.rb +1 -1
  41. metadata +14 -6
  42. data/lib/openhab/core/dsl/rule/item.rb +0 -203
  43. data/lib/openhab/core/dsl/rule/triggers.rb +0 -77
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 376e7ceca67ad5ca1dfedcacee9096e97819f3a382404ddde4f7beb579416e7d
4
- data.tar.gz: bdffa11dab63e1910d38a1232c38e2b9d64deaac1d8ea3610de464792d75173e
3
+ metadata.gz: 9b15bf27412cd33d856b31cc646198b435567f5a0a8f2c1cb65507236c585c65
4
+ data.tar.gz: 0fd1efb711bc13e85c7963789b6e4757ee58aac859adc010b356dba0351cc038
5
5
  SHA512:
6
- metadata.gz: 712aa971a831d8b700ecbe23b429a083518deb3bf7c2b68aa91cc0f3e5253ac9a32087025994705b94d268db58ef73e27767dcb3ce182e4477bfa115c6c476e0
7
- data.tar.gz: e450d32857df9dd5aea47cc5e0baa7043e06901282324d2dfd4623cb2eac03c8fb60335e3de2e680911f1d4ad24095c2533fd3a9d5c90501694f849019e2d225
6
+ metadata.gz: ab32c08239a0da7dce995fc4fd4813c5b9895b1a1f6923357f4279fa62d882af6cbff6ecd0c2399783ea6b63361c98fae0f6445e41f969308898e9a550f484ac
7
+ data.tar.gz: 957b29f719d2ff8d0bea8655b08883f9fd69a49487cb9968f7c552e5b0a75df1d557cd835584b9397ac4f59cffc9aa531be2635588b440b60122ae16735a742d
data/lib/openhab.rb CHANGED
@@ -36,4 +36,7 @@ module OpenHAB
36
36
  end
37
37
 
38
38
  # Extend caller with OpenHAB methods
39
+
40
+ # rubocop: disable Style/MixinUsage
39
41
  extend OpenHAB
42
+ # rubocop: enable Style/MixinUsage
@@ -19,6 +19,7 @@ require 'core/dsl/gems'
19
19
  require 'core/dsl/units'
20
20
  require 'core/dsl/types/quantity'
21
21
  require 'core/dsl/states'
22
+ require 'core/dsl/persistence'
22
23
 
23
24
  module OpenHAB
24
25
  #
@@ -30,6 +31,7 @@ module OpenHAB
30
31
  #
31
32
  module DSL
32
33
  # Extend the calling module/class with the DSL
34
+ # rubocop: disable Metrics/MethodLength
33
35
  def self.extended(base)
34
36
  base.send :include, OpenHAB::Core::DSL::Rule
35
37
  base.send :include, OpenHAB::Core::DSL::Items
@@ -40,8 +42,10 @@ module OpenHAB
40
42
  base.send :include, OpenHAB::Core::DSL::Timers
41
43
  base.send :include, OpenHAB::Core::DSL::States
42
44
  base.send :include, OpenHAB::Core::DSL::Tod
45
+ base.send :include, OpenHAB::Core::DSL::Persistence
43
46
  base.send :include, Things
44
47
  end
48
+ # rubocop: enable Metrics/MethodLength
45
49
  end
46
50
  end
47
51
  end
@@ -46,7 +46,7 @@ module OpenHAB
46
46
  # rubocop: disable Style/GlobalVars
47
47
  action_keys = $actions.action_keys
48
48
  # rubocop: enable Style/GlobalVars
49
- logger.trace( "Registered actions: '#{action_keys}' for thing '#{thing_uid}'")
49
+ logger.trace("Registered actions: '#{action_keys}' for thing '#{thing_uid}'")
50
50
  action_keys.map { |action_key| action_key.split('-', 2) }
51
51
  .select { |action_pair| action_pair.last == thing_uid }
52
52
  .map(&:first)
@@ -4,6 +4,7 @@ require 'pp'
4
4
  require 'java'
5
5
  require 'set'
6
6
  require 'core/dsl/group'
7
+ require 'core/log'
7
8
  require 'core/dsl/items/number_item'
8
9
  require 'core/dsl/items/string_item'
9
10
 
@@ -25,6 +26,7 @@ end
25
26
  # Manages access to OpenHAB entities
26
27
  #
27
28
  module EntityLookup
29
+ include Logging
28
30
  #
29
31
  # Decorate items with Ruby wrappers
30
32
  #
@@ -32,14 +34,13 @@ module EntityLookup
32
34
  #
33
35
  # @return [Array] Array of decorated items
34
36
  #
37
+ # rubocop: disable Metrics/MethodLength
38
+ # Disabled line length - case dispatch pattern
35
39
  def self.decorate_items(*items)
36
40
  items.flatten.map do |item|
37
41
  case item
38
42
  when GroupItem
39
- group = item
40
- item = OpenHAB::Core::DSL::Groups::Group.new(Set.new(EntityLookup.decorate_items(item.all_members.to_a)))
41
- item.group = group
42
- item
43
+ decorate_group(item)
43
44
  when Java::Org.openhab.core.library.items::NumberItem
44
45
  OpenHAB::Core::DSL::Items::NumberItem.new(item)
45
46
  when Java::Org.openhab.core.library.items::StringItem
@@ -49,6 +50,7 @@ module EntityLookup
49
50
  end
50
51
  end
51
52
  end
53
+ # rubocop: enable Metrics/MethodLength
52
54
 
53
55
  #
54
56
  # Loops up a Thing in the OpenHAB registry replacing '_' with ':'
@@ -58,6 +60,7 @@ module EntityLookup
58
60
  # @return [Thing] if found, nil otherwise
59
61
  #
60
62
  def self.lookup_thing(name)
63
+ logger.trace("Looking up thing(#{name})")
61
64
  # Convert from : syntax to underscore
62
65
  name = name.to_s if name.is_a? Symbol
63
66
 
@@ -78,6 +81,7 @@ module EntityLookup
78
81
  # @return [Item] OpenHAB item if registry contains a matching item, nil othewise
79
82
  #
80
83
  def self.lookup_item(name)
84
+ logger.trace("Looking up item(#{name})")
81
85
  name = name.to_s if name.is_a? Symbol
82
86
  # rubocop: disable Style/GlobalVars
83
87
  item = $ir.get(name)
@@ -98,6 +102,39 @@ module EntityLookup
98
102
  return if method.to_s == 'scriptLoaded'
99
103
  return if method.to_s == 'scriptUnloaded'
100
104
 
105
+ logger.trace("method missing, performing OpenHab Lookup for: #{method}")
101
106
  EntityLookup.lookup_item(method) || EntityLookup.lookup_thing(method) || super
102
107
  end
108
+
109
+ #
110
+ # Checks if this method responds to the missing method
111
+ #
112
+ # @param [String] method_name Name of the method to check
113
+ # @param [Boolean] _include_private boolean if private methods should be checked
114
+ #
115
+ # @return [Boolean] true if this object will respond to the supplied method, false otherwise
116
+ #
117
+ def respond_to_missing?(method_name, _include_private = false)
118
+ logger.trace("Checking if OpenHAB entites exist for #{method_name}")
119
+ method_name = method_name.to_s if method_name.is_a? Symbol
120
+
121
+ method_name == 'scriptLoaded' ||
122
+ method_name == 'scriptUnloaded' ||
123
+ EntityLookup.lookup_item(method_name) ||
124
+ EntityLookup.lookup_thing(method_name) ||
125
+ super
126
+ end
127
+
128
+ #
129
+ # Decorate a group from an item base
130
+ #
131
+ # @param [OpenHAB item] item item to convert to a group item
132
+ #
133
+ # @return [OpenHAB::Core::DSL::Groups::Group] Group created from supplied item
134
+ #
135
+ def self.decorate_group(item)
136
+ group = OpenHAB::Core::DSL::Groups::Group.new(Set.new(EntityLookup.decorate_items(item.all_members.to_a)))
137
+ group.group = item
138
+ group
139
+ end
103
140
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'core/log'
4
- require 'bundler/inline'
4
+ require 'bundler/inline'
@@ -28,7 +28,7 @@ module OpenHAB
28
28
  #
29
29
  def[](name)
30
30
  group = EntityLookup.lookup_item(name)
31
- (group.is_a? Group) ? group : nil
31
+ group.is_a?(Group) ? group : nil
32
32
  end
33
33
  end
34
34
 
@@ -38,7 +38,9 @@ module OpenHAB
38
38
  # @return [Set] of OpenHAB Groups
39
39
  #
40
40
  def groups
41
+ # rubocop: disable Style/GlobalVars
41
42
  Groups.new(EntityLookup.decorate_items($ir.items.select { |item| item.is_a? GroupItem }))
43
+ # rubocop: enable Style/GlobalVars
42
44
  end
43
45
 
44
46
  # Group class that provides access to OpenHAB group object and delegates other methods to
@@ -21,7 +21,7 @@ module OpenHAB
21
21
  # rubocop: disable Style/GlobalVars
22
22
  item = $ir.getItem(name)
23
23
  # rubocop: enable Style/GlobalVars
24
- (item.is_a? GroupItem) ? nil : item
24
+ item.is_a?(GroupItem) ? nil : item
25
25
  rescue Java::OrgOpenhabCoreItems::ItemNotFoundException
26
26
  nil
27
27
  end
@@ -30,7 +30,9 @@ module OpenHAB
30
30
  # @param name [String] Item name to check
31
31
  # @return [Boolean] true if the item exists, false otherwise
32
32
  def include?(name)
33
+ # rubocop: disable Style/GlobalVars
33
34
  !$ir.getItems(name).empty?
35
+ # rubocop: enable Style/GlobalVars
34
36
  end
35
37
  alias key? include?
36
38
  end
@@ -12,6 +12,9 @@ module OpenHAB
12
12
  #
13
13
  # Delegation to OpenHAB Number Item
14
14
  #
15
+ # rubocop: disable Metrics/ClassLength
16
+ # Disabled because this class has a single responsibility, there does not appear a logical
17
+ # way of breaking it up into multiple classes
15
18
  class NumberItem < Numeric
16
19
  extend Forwardable
17
20
 
@@ -46,23 +49,16 @@ module OpenHAB
46
49
  #
47
50
  # @param [Object] other object to coerce to a NumberItem if possible
48
51
  #
49
- # @return [Object] NumberItem, QuantityTypes, BigDecimal or nil depending on NumberItem configuration and/or supplied object
52
+ # @return [Object] NumberItem, QuantityTypes, BigDecimal or nil depending on NumberItem configuration
53
+ # and/or supplied object
50
54
  #
51
55
  def coerce(other)
52
56
  logger.trace("Coercing #{self} as a request from #{other.class}")
53
57
  case other
54
- when Quantity
55
- as_qt = to_qt
56
- logger.trace("Converted #{self} to a Quantity #{as_qt}")
57
- [other, as_qt]
58
- when Numeric
59
- if dimension
60
- [Quantity.new(other), to_qt]
61
- elsif @number_item.state?
62
- [BigDecimal(other), @number_item.state.to_big_decimal.to_d]
63
- end
58
+ when Quantity then coerce_from_quantity(other)
59
+ when Numeric then coerce_from_numeric(other)
64
60
  else
65
- logger.trace("#{self} cannot be coereced to #{other.class}")
61
+ logger.trace("#{self} cannot be coerced to #{other.class}")
66
62
  nil
67
63
  end
68
64
  end
@@ -72,24 +68,22 @@ module OpenHAB
72
68
  #
73
69
  # @param [Object] other object to compare to
74
70
  #
75
- # @return [Integer] -1,0,1 or nil depending on value supplied, nil comparison to supplied object is not possible.
71
+ # @return [Integer] -1,0,1 or nil depending on value supplied,
72
+ # nil comparison to supplied object is not possible.
76
73
  #
74
+ # rubocop: disable Metrics/AbcSize
77
75
  def <=>(other)
78
- logger.trace("Comparing #{self} to #{other}")
76
+ logger.trace("NumberItem #{self} <=> #{other} (#{other.class})")
79
77
  case other
80
- when NumberItem
81
- if other.dimension
82
- logger.trace('Other is dimensioned, converting self and other to QuantityTypes to compare')
83
- to_qt <=> other.to_qt
84
- else
85
- @number_item.state <=> other.state
86
- end
87
- when Numeric
88
- @number_item.state.to_big_decimal.to_d <=> BigDecimal(other)
89
- when String
90
- @number_item.state <=> QuantityType.new(other) if dimension
78
+ when NumberItem then number_item_compare(other)
79
+ when Numeric then @number_item.state.to_big_decimal.to_d <=> other.to_d
80
+ when String then @number_item.state <=> QuantityType.new(other) if dimension
81
+ else
82
+ other = other.state if other.respond_to? :state
83
+ @number_item.state <=> other
91
84
  end
92
85
  end
86
+ # rubocop: enable Metrics/AbcSize
93
87
 
94
88
  #
95
89
  # Convert NumberItem to a Quantity
@@ -167,6 +161,7 @@ module OpenHAB
167
161
  # @return [Object] Value from delegated method in OpenHAB NumberItem
168
162
  #
169
163
  def method_missing(meth, *args, &block)
164
+ logger.trace("Method missing, performing dynamic lookup for: #{meth}")
170
165
  if @number_item.respond_to?(meth)
171
166
  @number_item.__send__(meth, *args, &block)
172
167
  elsif ::Kernel.method_defined?(meth) || ::Kernel.private_method_defined?(meth)
@@ -176,42 +171,153 @@ module OpenHAB
176
171
  end
177
172
  end
178
173
 
174
+ #
175
+ # Checks if this method responds to the missing method
176
+ #
177
+ # @param [String] method_name Name of the method to check
178
+ # @param [Boolean] _include_private boolean if private methods should be checked
179
+ #
180
+ # @return [Boolean] true if this object will respond to the supplied method, false otherwise
181
+ #
182
+ def respond_to_missing?(method_name, _include_private = false)
183
+ @number_item.respond_to?(method_name) ||
184
+ ::Kernel.method_defined?(method_name) ||
185
+ ::Kernel.private_method_defined?(method_name)
186
+ end
187
+
179
188
  %w[+ - * /].each do |operation|
180
189
  define_method(operation) do |other|
181
190
  logger.trace("Execution math operation '#{operation}' on #{inspect} with #{other.inspect}")
182
- if other.is_a? NumberItem
183
- logger.trace('Math operations is between two NumberItems.')
184
- if dimension && other.dimension
185
- # If both numbers have dimensions, do the math on the quantity types.
186
- logger.trace("Both objects have dimensions self='#{dimension}' other='#{other.dimension}'")
187
- to_qt.public_send(operation, other.to_qt)
188
- elsif dimension && !other.dimension
189
- # If this number has dimension and the other does not, do math with this quantity type and the other as a big decimal
190
- logger.trace("Self has dimension self='#{dimension}' other lacks dimension='#{other}'")
191
- to_qt.public_send(operation, other)
192
- elsif other.dimension
193
- # If this number has no dimension and the other does, convert this into a dimensionless quantity
194
- logger.trace("Self has no dimension self='#{self}' other has dimension='#{other.dimension}'")
195
- to_qt.public_send(operation, other)
196
- else
197
- logger.trace("Both objects lack dimension, self='#{self}' other='#{other}'")
198
- # If nothing has a dimension, just use BigDecimals
199
- to_d.public_send(operation, other.to_d)
200
- end
201
- elsif other.is_a? Numeric
202
- to_d.public_send(operation, BigDecimal(other))
203
- elsif dimension && other.is_a?(String)
204
- to_qt.public_send(operation, Quantity.new(other))
205
- elsif other.respond_to? :coerce
206
- a, b = other.coerce(to_d)
207
- a.public_send(operation, b)
191
+ left_operand, right_operand = operands_for_operation(other)
192
+ left_operand.public_send(operation, right_operand)
193
+ end
194
+ end
195
+
196
+ private
197
+
198
+ #
199
+ # Get the operands for any operation
200
+ #
201
+ # @param [Object] other object to convert to a compatible operand
202
+ #
203
+ # @return [Array[Object,Object]] of operands where the first value is the left operand
204
+ # and the second value is the right operand
205
+ #
206
+ def operands_for_operation(other)
207
+ case other
208
+ when NumberItem then number_item_operands(other)
209
+ when Numeric then [to_d, other.to_d]
210
+ when String then string_operands(other)
211
+ else
212
+ return other.coerce(to_d) if other.respond_to? :coerce
213
+
214
+ raise ArgumentError, "#{other.class} can't be coerced into a NumberItem"
215
+ end
216
+ end
217
+
218
+ #
219
+ # Get operands for an operation when the right operand is provided as a string
220
+ #
221
+ # @param [String] other right operand
222
+ #
223
+ # @return [Array[QuantityType,QuantiyType]] of operands where the first value is the left operand
224
+ # and the second value is the right operand
225
+ #
226
+ def string_operands(other)
227
+ return [to_qt, Quantity.new(other)] if dimension
228
+
229
+ raise ArgumentError, 'Strings are only valid operands if NumberItem is dimensions=ed.'
230
+ end
231
+
232
+ #
233
+ # Get operands for an operation when the right operand is provided is another number item
234
+ #
235
+ # @param [NumberItem] other right operand
236
+ #
237
+ # @return [Array<QuantityType,QuantityType>,Array<BigDecimal,BigDecimal>] of operands depending on
238
+ # if the left or right operand has a dimensions
239
+ #
240
+ def number_item_operands(other)
241
+ if dimension || other.dimension
242
+ dimensioned_operands(other)
243
+ else
244
+ logger.trace("Both objects lack dimension, self='#{self}' other='#{other}'")
245
+ # If nothing has a dimension, just use BigDecimals
246
+ [to_d, other.to_d]
247
+ end
248
+ end
249
+
250
+ #
251
+ # Get operands for an operation when the left or right operand has a dimension
252
+ #
253
+ # @param [NumberItem] other right operand
254
+ #
255
+ # @return [Array<QuantityType,QuantityType>] of operands
256
+ #
257
+ def dimensioned_operands(other)
258
+ logger.trace("Dimensions self='#{dimension}' other='#{other.dimension}'")
259
+ if dimension
260
+ if other.dimension
261
+ # If both numbers have dimensions, do the math on the quantity types.
262
+ [to_qt, other.to_qt]
208
263
  else
209
- raise TypeError, "#{other.class} can't be coerced into a NumberItem"
264
+ # If this number has dimension and the other does not,
265
+ # do math with this quantity type and the other as a big decimal
266
+ [to_qt, other]
210
267
  end
268
+ else
269
+ # If this number has no dimension and the other does, convert this into a dimensionless quantity
270
+ [to_qt, other]
271
+ end
272
+ end
273
+
274
+ #
275
+ # Compare two number items, taking into account any dimensions
276
+ #
277
+ # @param [NumberItem] other number item
278
+ #
279
+ # @return [-1,0,1] depending on if other object is less than, equal to or greater than self
280
+ #
281
+ def number_item_compare(other)
282
+ if other.dimension
283
+ logger.trace('Other is dimensioned, converting self and other to QuantityTypes to compare')
284
+ to_qt <=> other.to_qt
285
+ else
286
+ @number_item.state <=> other.state
211
287
  end
212
288
  end
289
+
290
+ #
291
+ # Coerce from a numberic object depnding on dimension and state
292
+ #
293
+ # @param [Numeric] other numeric object to convert
294
+ #
295
+ # @return [Array<QuantityType,QuantityType>,Array<BigDecimal,BigDecimal>,nil] depending on
296
+ # if this object has a dimension or state
297
+ #
298
+ def coerce_from_numeric(other)
299
+ if dimension
300
+ [Quantity.new(other), to_qt]
301
+ elsif @number_item.state?
302
+ [other.to_d, @number_item.state.to_big_decimal.to_d]
303
+ end
304
+ end
305
+
306
+ #
307
+ # Coerce when other is a quantity
308
+ #
309
+ # @param [QuantityType] other
310
+ #
311
+ # @return [Array<QuanityType,QuantityType] other and self as a quantity type
312
+ #
313
+ def coerce_from_quantity(other)
314
+ as_qt = to_qt
315
+ logger.trace("Converted #{self} to a Quantity #{as_qt}")
316
+ [other, as_qt]
317
+ end
213
318
  end
214
319
  end
215
320
  end
216
321
  end
217
322
  end
323
+ # rubocop: enable Metrics/ClassLength