openhab-scripting 2.14.1 → 2.14.2

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openhab.rb +3 -0
  3. data/lib/openhab/core/dsl/actions.rb +1 -1
  4. data/lib/openhab/core/dsl/entities.rb +41 -4
  5. data/lib/openhab/core/dsl/gems.rb +1 -1
  6. data/lib/openhab/core/dsl/group.rb +3 -1
  7. data/lib/openhab/core/dsl/items/items.rb +3 -1
  8. data/lib/openhab/core/dsl/items/number_item.rb +151 -50
  9. data/lib/openhab/core/dsl/items/string_item.rb +21 -3
  10. data/lib/openhab/core/dsl/monkey_patch/items/metadata.rb +66 -42
  11. data/lib/openhab/core/dsl/monkey_patch/items/switch_item.rb +2 -1
  12. data/lib/openhab/core/dsl/monkey_patch/ruby/range.rb +2 -1
  13. data/lib/openhab/core/dsl/property.rb +15 -4
  14. data/lib/openhab/core/dsl/rule/automation_rule.rb +348 -0
  15. data/lib/openhab/core/dsl/rule/guard.rb +43 -6
  16. data/lib/openhab/core/dsl/rule/rule.rb +80 -367
  17. data/lib/openhab/core/dsl/rule/rule_config.rb +153 -0
  18. data/lib/openhab/core/dsl/rule/triggers/changed.rb +145 -0
  19. data/lib/openhab/core/dsl/rule/{channel.rb → triggers/channel.rb} +22 -8
  20. data/lib/openhab/core/dsl/rule/triggers/command.rb +106 -0
  21. data/lib/openhab/core/dsl/rule/{cron.rb → triggers/cron.rb} +36 -14
  22. data/lib/openhab/core/dsl/rule/triggers/trigger.rb +126 -0
  23. data/lib/openhab/core/dsl/rule/triggers/updated.rb +100 -0
  24. data/lib/openhab/core/dsl/time_of_day.rb +50 -24
  25. data/lib/openhab/core/dsl/timers.rb +2 -6
  26. data/lib/openhab/core/dsl/types/quantity.rb +106 -69
  27. data/lib/openhab/core/log.rb +3 -8
  28. data/lib/openhab/core/startup_delay.rb +1 -0
  29. data/lib/openhab/osgi.rb +7 -0
  30. data/lib/openhab/version.rb +1 -1
  31. metadata +10 -6
  32. data/lib/openhab/core/dsl/rule/item.rb +0 -203
  33. 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: 6851ec9dce8db775e48aaf7d08078281e2a2e9c1d6a2c2cd379de0d3fd2391f0
4
- data.tar.gz: 2f656d9cb621d3826c72c2aa588d1ba478108bc30cffd32bf8e0e5fae85dbe47
3
+ metadata.gz: cf896ff53247e4170a9d3c09ebd3b337114bcc8509963001f4ccea9a4c81d52f
4
+ data.tar.gz: 974c4e5175053de2818fa725de5376458f21a01bc45fb444ac4a6b82e84685bb
5
5
  SHA512:
6
- metadata.gz: 248767b8ec14f2f68412f152755beb872b15b6db26b363cc0837419e16f044b8c6b078843cd93c4efe8fe03358256df7cb8d390ebe43ddb6ad00b962ecb197ce
7
- data.tar.gz: 9bb8e09d8a34c96b220b16b209c92cf5fce0f1bd54f28086ed3f6e4ae83ab24b0288bb828d9b2111fec3fbef10025b70624b13005140690ce5a9b2556df1a132
6
+ metadata.gz: f3d966995bcf7d4d6ae5ff5a6567af8e47e57355d7ac438b46b236755205a6dbc955520f28734901f8e49b8d415b6a0497f82f1a89a698cc90d61363621d9396
7
+ data.tar.gz: 0a76fe6c623432d7203ddbac268690be10ff7c9bcdcd1a2b56ace1cda3ead34f036ee70cd7f66c695c284faba921f37b3e5bbf6a091b616b31a2ca94e1c4cb89
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
@@ -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,21 +49,14 @@ 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
- [other.to_d, @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
61
  logger.trace("#{self} cannot be coereced to #{other.class}")
66
62
  nil
@@ -72,22 +68,15 @@ 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
  #
77
74
  def <=>(other)
78
75
  logger.trace("Comparing #{self} to #{other}")
79
76
  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 <=> other.to_d
89
- when String
90
- @number_item.state <=> QuantityType.new(other) if dimension
77
+ when NumberItem then number_item_compare(other)
78
+ when Numeric then @number_item.state.to_big_decimal.to_d <=> other.to_d
79
+ when String then @number_item.state <=> QuantityType.new(other) if dimension
91
80
  end
92
81
  end
93
82
 
@@ -167,6 +156,7 @@ module OpenHAB
167
156
  # @return [Object] Value from delegated method in OpenHAB NumberItem
168
157
  #
169
158
  def method_missing(meth, *args, &block)
159
+ logger.trace("Method missing, performing dynamic lookup for: #{meth}")
170
160
  if @number_item.respond_to?(meth)
171
161
  @number_item.__send__(meth, *args, &block)
172
162
  elsif ::Kernel.method_defined?(meth) || ::Kernel.private_method_defined?(meth)
@@ -176,42 +166,153 @@ module OpenHAB
176
166
  end
177
167
  end
178
168
 
169
+ #
170
+ # Checks if this method responds to the missing method
171
+ #
172
+ # @param [String] method_name Name of the method to check
173
+ # @param [Boolean] _include_private boolean if private methods should be checked
174
+ #
175
+ # @return [Boolean] true if this object will respond to the supplied method, false otherwise
176
+ #
177
+ def respond_to_missing?(method_name, _include_private = false)
178
+ @number_item.respond_to?(method_name) ||
179
+ ::Kernel.method_defined?(method_name) ||
180
+ ::Kernel.private_method_defined?(method_name)
181
+ end
182
+
179
183
  %w[+ - * /].each do |operation|
180
184
  define_method(operation) do |other|
181
185
  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, other.to_d)
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)
186
+ left_operand, right_operand = operands_for_operation(other)
187
+ left_operand.public_send(operation, right_operand)
188
+ end
189
+ end
190
+
191
+ private
192
+
193
+ #
194
+ # Get the operands for any operation
195
+ #
196
+ # @param [Object] other object to convert to a compatible operand
197
+ #
198
+ # @return [Array[Object,Object]] of operands where the first value is the left operand
199
+ # and the second value is the right operand
200
+ #
201
+ def operands_for_operation(other)
202
+ case other
203
+ when NumberItem then number_item_operands(other)
204
+ when Numeric then [to_d, other.to_d]
205
+ when String then string_operands(other)
206
+ else
207
+ return other.coerce(to_d) if other.respond_to? :coerce
208
+
209
+ raise ArgumentError, "#{other.class} can't be coerced into a NumberItem"
210
+ end
211
+ end
212
+
213
+ #
214
+ # Get operands for an operation when the right operand is provided as a string
215
+ #
216
+ # @param [String] other right operand
217
+ #
218
+ # @return [Array[QuantityType,QuantiyType]] of operands where the first value is the left operand
219
+ # and the second value is the right operand
220
+ #
221
+ def string_operands(other)
222
+ return [to_qt, Quantity.new(other)] if dimension
223
+
224
+ raise ArgumentError, 'Strings are only valid operands if NumberItem is dimensions=ed.'
225
+ end
226
+
227
+ #
228
+ # Get operands for an operation when the right operand is provided is another number item
229
+ #
230
+ # @param [NumberItem] other right operand
231
+ #
232
+ # @return [Array<QuantityType,QuantityType>,Array<BigDecimal,BigDecimal>] of operands depending on
233
+ # if the left or right operand has a dimensions
234
+ #
235
+ def number_item_operands(other)
236
+ if dimension || other.dimension
237
+ dimensioned_operands(other)
238
+ else
239
+ logger.trace("Both objects lack dimension, self='#{self}' other='#{other}'")
240
+ # If nothing has a dimension, just use BigDecimals
241
+ [to_d, other.to_d]
242
+ end
243
+ end
244
+
245
+ #
246
+ # Get operands for an operation when the left or right operand has a dimension
247
+ #
248
+ # @param [NumberItem] other right operand
249
+ #
250
+ # @return [Array<QuantityType,QuantityType>] of operands
251
+ #
252
+ def dimensioned_operands(other)
253
+ logger.trace("Dimensions self='#{dimension}' other='#{other.dimension}'")
254
+ if dimension
255
+ if other.dimension
256
+ # If both numbers have dimensions, do the math on the quantity types.
257
+ [to_qt, other.to_qt]
208
258
  else
209
- raise TypeError, "#{other.class} can't be coerced into a NumberItem"
259
+ # If this number has dimension and the other does not,
260
+ # do math with this quantity type and the other as a big decimal
261
+ [to_qt, other]
210
262
  end
263
+ else
264
+ # If this number has no dimension and the other does, convert this into a dimensionless quantity
265
+ [to_qt, other]
266
+ end
267
+ end
268
+
269
+ #
270
+ # Compare two number items, taking into account any dimensions
271
+ #
272
+ # @param [NumberItem] other number item
273
+ #
274
+ # @return [-1,0,1] depending on if other object is less than, equal to or greater than self
275
+ #
276
+ def number_item_compare(other)
277
+ if other.dimension
278
+ logger.trace('Other is dimensioned, converting self and other to QuantityTypes to compare')
279
+ to_qt <=> other.to_qt
280
+ else
281
+ @number_item.state <=> other.state
211
282
  end
212
283
  end
284
+
285
+ #
286
+ # Coerce from a numberic object depnding on dimension and state
287
+ #
288
+ # @param [Numeric] other numeric object to convert
289
+ #
290
+ # @return [Array<QuantityType,QuantityType>,Array<BigDecimal,BigDecimal>,nil] depending on
291
+ # if this object has a dimension or state
292
+ #
293
+ def coerce_from_numeric(other)
294
+ if dimension
295
+ [Quantity.new(other), to_qt]
296
+ elsif @number_item.state?
297
+ [other.to_d, @number_item.state.to_big_decimal.to_d]
298
+ end
299
+ end
300
+
301
+ #
302
+ # Coerce when other is a quantity
303
+ #
304
+ # @param [QuantityType] other
305
+ #
306
+ # @return [Array<QuanityType,QuantityType] other and self as a quantity type
307
+ #
308
+ def coerce_from_quantity(other)
309
+ as_qt = to_qt
310
+ logger.trace("Converted #{self} to a Quantity #{as_qt}")
311
+ [other, as_qt]
312
+ end
213
313
  end
214
314
  end
215
315
  end
216
316
  end
217
317
  end
318
+ # rubocop: enable Metrics/ClassLength
@@ -15,6 +15,7 @@ module OpenHAB
15
15
  extend Forwardable
16
16
  include Comparable
17
17
 
18
+ # @return [Regex] Regular expression matching blank strings
18
19
  BLANK_RE = /\A[[:space:]]*\z/.freeze
19
20
  private_constant :BLANK_RE
20
21
 
@@ -33,7 +34,8 @@ module OpenHAB
33
34
  #
34
35
  # Convert the StringItem into a String
35
36
  #
36
- # @return [String] String representation of the StringItem or nil if underlying OpenHAB StringItem does not have a state
37
+ # @return [String] String representation of the StringItem or
38
+ # nil if underlying OpenHAB StringItem does not have a state
37
39
  #
38
40
  def to_str
39
41
  @string_item.state&.to_full_string&.to_s
@@ -64,7 +66,8 @@ module OpenHAB
64
66
  #
65
67
  # @param [Object] other object to compare to
66
68
  #
67
- # @return [Integer] -1,0,1 or nil depending on value supplied, nil comparison to supplied object is not possible.
69
+ # @return [Integer] -1,0,1 or nil depending on value supplied,
70
+ # nil comparison to supplied object is not possible.
68
71
  #
69
72
  def <=>(other)
70
73
  case other
@@ -87,7 +90,7 @@ module OpenHAB
87
90
  def method_missing(meth, *args, &block)
88
91
  if @string_item.respond_to?(meth)
89
92
  @string_item.__send__(meth, *args, &block)
90
- elsif @string_item.state? && @string_item.state.to_full_string.to_s.respond_to?(meth)
93
+ elsif @string_item.state&.to_full_string&.to_s.respond_to?(meth)
91
94
  @string_item.state.to_full_string.to_s.__send__(meth, *args, &block)
92
95
  elsif ::Kernel.method_defined?(meth) || ::Kernel.private_method_defined?(meth)
93
96
  ::Kernel.instance_method(meth).bind_call(self, *args, &block)
@@ -95,6 +98,21 @@ module OpenHAB
95
98
  super(meth, *args, &block)
96
99
  end
97
100
  end
101
+
102
+ #
103
+ # Checks if this method responds to the missing method
104
+ #
105
+ # @param [String] method_name Name of the method to check
106
+ # @param [Boolean] _include_private boolean if private methods should be checked
107
+ #
108
+ # @return [Boolean] true if this object will respond to the supplied method, false otherwise
109
+ #
110
+ def respond_to_missing?(method_name, _include_private = false)
111
+ @string_item.respond_to?(method_name) ||
112
+ @string_item.state&.to_full_string&.to_s.respond_to?(method_name) ||
113
+ ::Kernel.method_defined?(method_name) ||
114
+ ::Kernel.private_method_defined?(method_name)
115
+ end
98
116
  end
99
117
  end
100
118
  end