openhab-scripting 5.7.1 → 5.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 951ad54507dbf4b297d59534c124c21bbf6f6b7a02e4c3fa967a5290aac9968e
4
- data.tar.gz: d4b33c4b8cdf237c6bdc1c2b99b87459f913f2d787423f261677d469d9629df3
3
+ metadata.gz: 6f531c0d4e77bbee531e574b759f56e458d4eacde89520b8cf9f810dd46e61d9
4
+ data.tar.gz: 7724b5709c56fb80f095c14930fd3077a2019111731865f1956f4b080a8039ab
5
5
  SHA512:
6
- metadata.gz: f26471513aca95dcb0266ffca7176ee57980cbea404c8290c095642c4851954e4d870232507cbea34c6b6c4a940da84d6681944ee039199c3fcc766bdd64a598
7
- data.tar.gz: 0c6e9e9cf72ee4a1935c399c235256144b0c6fbbb7bc0eb3f07a81405f3e52cc0193a1856ec910212f963c37208cb29191bbe90d143bf822bfc2c987828de47d
6
+ metadata.gz: 11237012154342229a7b1a4b24a2e5e8f81833d758e81614b067c4a1187a598e5fbc45cfd300312ef0f039130a039e6df7c75f59e08e458d0bdf7761e2a85f8d
7
+ data.tar.gz: 3bd394f5d950d2ab5a0016a4219d7d1ac0cb9f4fbdca541d4f41c9e59406c01a1d0df242334cc95a4cf1668f546e1f0fc7aa03cd835f71384e4a3de4cace8304
@@ -103,7 +103,12 @@ module OpenHAB
103
103
  def merge!(*others, &block)
104
104
  return self if others.empty?
105
105
 
106
- replace(merge(*others, &block))
106
+ # don't call replace here so we don't touch other keys
107
+ others.shift.merge(*others, &block).each do |key, value|
108
+ value = yield key, self[key], value if key?(key) && block
109
+ store(key, value)
110
+ end
111
+ self
107
112
  end
108
113
  alias_method :update, :merge!
109
114
 
@@ -106,6 +106,9 @@ module OpenHAB
106
106
  alias_method :to_s, :inspect
107
107
  end
108
108
 
109
+ # @!attribute [r] function
110
+ # @return [GroupFunction] Returns the function of this GroupItem
111
+
109
112
  # Override because we want to send them to the base item if possible
110
113
  %i[command update].each do |method|
111
114
  define_method(method) do |command|
@@ -152,6 +155,19 @@ module OpenHAB
152
155
  RUBY
153
156
  end
154
157
 
158
+ #
159
+ # Compares all attributes of the item with another item.
160
+ #
161
+ # @param other [Item] The item to compare with
162
+ # @return [true,false] true if all attributes are equal, false otherwise
163
+ #
164
+ # @!visibility private
165
+ def config_eql?(other)
166
+ return false unless super
167
+
168
+ base_item&.type == other.base_item&.type && function&.inspect == other.function&.inspect
169
+ end
170
+
155
171
  private
156
172
 
157
173
  # Add base type and function details
@@ -264,12 +264,17 @@ module OpenHAB
264
264
  #
265
265
  # @return [Array<Thing>] An array of things or an empty array
266
266
  def things
267
- registry = Things::Links::Provider.registry
268
- channels = registry.get_bound_channels(name).to_a
269
- channels.map(&:thing_uid).uniq.map { |tuid| EntityLookup.lookup_thing(tuid) }.compact
267
+ Things::Links::Provider.registry.get_bound_things(name).map { |thing| Things::Proxy.new(thing) }
270
268
  end
271
269
  alias_method :all_linked_things, :things
272
270
 
271
+ # Returns all of the item's links (channels and link configurations).
272
+ #
273
+ # @return [Array<ItemChannelLink>] An array of ItemChannelLink or an empty array
274
+ def links
275
+ Things::Links::Provider.registry.get_links(name)
276
+ end
277
+
273
278
  # @return [String]
274
279
  def inspect
275
280
  s = "#<OpenHAB::Core::Items::#{type}Item#{type_details} #{name} #{label.inspect} state=#{raw_state.inspect}"
@@ -281,11 +286,28 @@ module OpenHAB
281
286
  "#{s}>"
282
287
  end
283
288
 
284
- # @return [org.openhab.core.common.registry.Provider]
289
+ # @return [org.openhab.core.common.registry.Provider, nil]
285
290
  def provider
286
291
  Provider.registry.provider_for(self)
287
292
  end
288
293
 
294
+ #
295
+ # Compares all attributes except metadata and channels/links of the item with another item.
296
+ #
297
+ # @param other [Item] The item to compare with
298
+ # @return [true,false] true if all attributes are equal, false otherwise
299
+ #
300
+ # @!visibility private
301
+ def config_eql?(other)
302
+ # GenericItem#equals checks whether other has the same name and class
303
+ return false unless equals(other)
304
+
305
+ %i[label category tags group_names].all? do |method|
306
+ # Don't use #send here. It is defined in GenericItem for sending commands
307
+ public_send(method) == other.public_send(method)
308
+ end
309
+ end
310
+
289
311
  def_type_predicate(:color)
290
312
  def_type_predicate(:contact)
291
313
  def_type_predicate(:date_time)
@@ -13,6 +13,11 @@ module OpenHAB
13
13
  #
14
14
  # All keys are converted to strings.
15
15
  #
16
+ # As a special case, a #== comparison can be done against a [value, config] array.
17
+ # @example
18
+ # MyItem.metadata[:namespace] = "value", { key: "value" }
19
+ # MyItem.metadata[:namespace] == ["value", { "key" => "value" }] #=> true
20
+ #
16
21
  # @!attribute [rw] value
17
22
  # @return [String] The main value for the metadata namespace.
18
23
  # @!attribute [r] namespace
@@ -88,7 +93,7 @@ module OpenHAB
88
93
  return unless attached?
89
94
 
90
95
  javaify
91
- provider.update(@metadata)
96
+ provider!.update(@metadata)
92
97
  end
93
98
 
94
99
  # @!visibility private
@@ -96,12 +101,12 @@ module OpenHAB
96
101
  return unless attached?
97
102
 
98
103
  javaify
99
- (p = provider).get(uid) ? p.update(@metadata) : p.add(@metadata)
104
+ (p = provider!).get(uid) ? p.update(@metadata) : p.add(@metadata)
100
105
  end
101
106
 
102
107
  # @!visibility private
103
108
  def remove
104
- provider.remove(uid)
109
+ provider!.remove(uid)
105
110
  end
106
111
 
107
112
  # @!visibility private
@@ -149,6 +154,8 @@ module OpenHAB
149
154
  return configuration == other.configuration
150
155
  elsif value.empty? && other.respond_to?(:to_hash)
151
156
  return configuration == other.to_hash
157
+ elsif other.is_a?(Array)
158
+ return other == [value, configuration]
152
159
  end
153
160
  false
154
161
  end
@@ -213,12 +220,17 @@ module OpenHAB
213
220
  [value, to_h].inspect
214
221
  end
215
222
 
223
+ # @return [org.openhab.core.common.registry.Provider, nil]
224
+ def provider
225
+ Provider.registry.provider_for(uid)
226
+ end
227
+
216
228
  #
217
229
  # @raise [FrozenError] if the provider is not a
218
230
  # {org.openhab.core.common.registry.ManagedProvider ManagedProvider} that can be updated.
219
231
  # @return [org.openhab.core.common.registry.ManagedProvider]
220
232
  #
221
- def provider
233
+ def provider!
222
234
  preferred_provider = Provider.current(
223
235
  Thread.current[:openhab_providers]&.dig(:metadata_items, uid.item_name) ||
224
236
  Thread.current[:openhab_providers]&.dig(:metadata_namespaces, uid.namespace),
@@ -226,10 +238,10 @@ module OpenHAB
226
238
  )
227
239
 
228
240
  if attached?
229
- provider = Provider.registry.provider_for(uid)
241
+ provider = self.provider
230
242
  return preferred_provider unless provider
231
243
 
232
- unless provider.is_a?(org.openhab.core.common.registry.ManagedProvider)
244
+ unless provider.is_a?(ManagedProvider)
233
245
  raise FrozenError, "Cannot modify metadata from provider #{provider.inspect} for #{uid}."
234
246
  end
235
247
 
@@ -48,6 +48,11 @@ module OpenHAB
48
48
  super
49
49
  end
50
50
 
51
+ # @!visibility private
52
+ def config_eql?(other)
53
+ super && dimension == other.dimension
54
+ end
55
+
51
56
  protected
52
57
 
53
58
  # Adds the unit dimension
@@ -45,13 +45,19 @@ module OpenHAB
45
45
  # Enter the Item Builder DSL.
46
46
  #
47
47
  # @param (see Core::Provider.current)
48
+ # @param update [true, false] Update existing items with the same name.
49
+ # When false, an error will be raised if an item with the same name already exists.
48
50
  # @yield Block executed in the context of a {DSL::Items::Builder}
49
51
  # @return [Object] The return value of the block.
52
+ # @raise [ArgumentError] if an item with the same name already exists and `update` is false.
53
+ # @raise [FrozenError] if `update` is true but the existing item with the same name
54
+ # wasn't created by the current provider.
50
55
  #
51
56
  # @see DSL::Items::Builder
52
57
  #
53
- def build(preferred_provider = nil, &block)
54
- DSL::Items::BaseBuilderDSL.new(preferred_provider).instance_eval_with_dummy_items(&block)
58
+ def build(preferred_provider = nil, update: true, &block)
59
+ DSL::Items::BaseBuilderDSL.new(preferred_provider, update: update)
60
+ .instance_eval_with_dummy_items(&block)
55
61
  end
56
62
 
57
63
  #
@@ -311,9 +311,14 @@ module OpenHAB
311
311
  synonyms = Array.wrap(synonyms).map { |s| s.to_s.strip }
312
312
 
313
313
  tags.map do |name, parent|
314
- parent = lookup(parent) unless parent.is_a?(SemanticTag)
314
+ unless parent.is_a?(SemanticTag)
315
+ parent_tag = lookup(parent)
316
+ raise ArgumentError, "Unknown parent: #{parent}" unless parent_tag
317
+
318
+ parent = parent_tag
319
+ end
320
+
315
321
  next if lookup(name)
316
- next unless parent
317
322
 
318
323
  new_tag = org.openhab.core.semantics.SemanticTagImpl.new("#{parent.uid}_#{name}",
319
324
  label,
@@ -323,6 +328,44 @@ module OpenHAB
323
328
  lookup(name)
324
329
  end.compact
325
330
  end
331
+
332
+ #
333
+ # Removes custom semantic tags.
334
+ #
335
+ # @param [SemanticTag, String, Symbol] tags Custom Semantic Tags to remove.
336
+ # The built in Semantic Tags cannot be removed.
337
+ # @param [true, false] recursive Remove all children of the given tags.
338
+ #
339
+ # @return [Array<SemanticTag>] An array of tags successfully removed.
340
+ # @raise [ArgumentError] if any of the tags have children
341
+ # @raise [FrozenError] if any of the tags are not custom tags
342
+ #
343
+ # @since openHAB 4.0
344
+ #
345
+ def remove(*tags, recursive: false)
346
+ tags.flat_map do |tag|
347
+ tag = lookup(tag) unless tag.is_a?(SemanticTag)
348
+ next unless tag
349
+
350
+ provider = Provider.registry.provider_for(tag)
351
+ unless provider.is_a?(ManagedProvider)
352
+ raise FrozenError, "Cannot remove item #{tag} from non-managed provider #{provider.inspect}"
353
+ end
354
+
355
+ children = []
356
+ Provider.registry.providers.grep(ManagedProvider).each do |managed_provider|
357
+ managed_provider.all.each do |existing_tag|
358
+ next unless existing_tag.parent_uid == tag.uid
359
+ raise ArgumentError, "Cannot remove #{tag} because it has children" unless recursive
360
+
361
+ children += remove(existing_tag, recursive: recursive)
362
+ end
363
+ end
364
+
365
+ remove_const(tag.name) if provider.remove(tag.uid) && const_defined?(tag.name)
366
+ [tag] + children
367
+ end.compact
368
+ end
326
369
  end
327
370
  end
328
371
 
@@ -20,12 +20,20 @@ module OpenHAB
20
20
  def provider_for(key)
21
21
  elementReadLock.lock
22
22
  if key.is_a?(org.openhab.core.common.registry.Identifiable)
23
- element = key
24
- else
25
- return nil unless (element = identifierToElement[key])
26
- end
23
+ return unless (provider = elementToProvider[key])
24
+
25
+ # The HashMap lookup above uses java's hashCode() which has been overridden
26
+ # by GenericItem and ThingImpl to return object's uid, e.g. item's name, thing uid
27
+ # so it will return a provider even for an unmanaged object having the same uid
28
+ # as an existing managed object.
27
29
 
28
- elementToProvider[element]
30
+ # So take an extra step to verify that the provider really holds the given instance.
31
+ # by using equal? to compare the object's identity.
32
+ # Only ManagedProviders have a #get method to look up the object by uid.
33
+ provider if !provider.is_a?(ManagedProvider) || provider.get(key.uid).equal?(key)
34
+ elsif (element = identifierToElement[key])
35
+ elementToProvider[element]
36
+ end
29
37
  ensure
30
38
  elementReadLock.unlock
31
39
  end
@@ -57,6 +57,7 @@ module OpenHAB
57
57
  #
58
58
  # Enter the Sitemap Builder DSL.
59
59
  #
60
+ # @param update [true, false] When true, existing sitemaps with the same name will be updated.
60
61
  # @yield Block executed in the context of a {DSL::Sitemaps::Builder}
61
62
  # @return [void]
62
63
  #
@@ -82,8 +83,8 @@ module OpenHAB
82
83
  # end
83
84
  # end
84
85
  #
85
- def build(&block)
86
- DSL::Sitemaps::Builder.new(self).instance_eval(&block)
86
+ def build(update: true, &block)
87
+ DSL::Sitemaps::Builder.new(self, update: update).instance_eval(&block)
87
88
  end
88
89
  # rubocop:enable Layout/LineLength
89
90
 
@@ -123,7 +124,7 @@ module OpenHAB
123
124
  @listeners.each { |l| l.model_changed(element.name, org.openhab.core.model.core.EventType::REMOVED) }
124
125
  end
125
126
 
126
- def notify_listeners_about_updated_element(element)
127
+ def notify_listeners_about_updated_element(_old_element, element)
127
128
  @listeners.each { |l| l.model_changed(element.name, org.openhab.core.model.core.EventType::MODIFIED) }
128
129
  end
129
130
  end
@@ -32,9 +32,14 @@ module OpenHAB
32
32
  def inspect
33
33
  r = "#<OpenHAB::Core::Things::Channel #{uid}"
34
34
  r += " #{label.inspect}" if label
35
- r += " auto_update_policy=#{auto_update_policy}" if auto_update_policy
35
+ r += " description=#{description.inspect}" if description
36
+ r += " kind=#{kind.inspect}"
37
+ r += " channel_type_uid=#{channel_type_uid.inspect}" if channel_type_uid
36
38
  r += " configuration=#{configuration.properties.to_h}" unless configuration.properties.empty?
37
39
  r += " properties=#{properties.to_h}" unless properties.empty?
40
+ r += " default_tags=#{default_tags.to_a}" unless default_tags.empty?
41
+ r += " auto_update_policy=#{auto_update_policy}" if auto_update_policy
42
+ r += " accepted_item_type=#{accepted_item_type}" if accepted_item_type
38
43
  "#{r}>"
39
44
  end
40
45
 
@@ -42,6 +47,34 @@ module OpenHAB
42
47
  def to_s
43
48
  uid.to_s
44
49
  end
50
+
51
+ # @deprecated OH3.4 this whole section is not needed in OH4+. Also see Thing#config_eql?
52
+ if Gem::Version.new(Core::VERSION) < Gem::Version.new("4.0.0")
53
+ # @!visibility private
54
+ module ChannelComparable
55
+ # @!visibility private
56
+ # This is only needed in OH3 because it is implemented in OH4 core
57
+ def ==(other)
58
+ return true if equal?(other)
59
+ return false unless other.is_a?(Channel)
60
+
61
+ %i[class
62
+ uid
63
+ label
64
+ description
65
+ kind
66
+ channel_type_uid
67
+ configuration
68
+ properties
69
+ default_tags
70
+ auto_update_policy
71
+ accepted_item_type].all? do |attr|
72
+ send(attr) == other.send(attr)
73
+ end
74
+ end
75
+ end
76
+ org.openhab.core.thing.binding.builder.ChannelBuilder.const_get(:ChannelImpl).prepend(ChannelComparable)
77
+ end
45
78
  end
46
79
  end
47
80
  end
@@ -24,13 +24,11 @@ module OpenHAB
24
24
  end
25
25
 
26
26
  # @!visibility private
27
- def link(item, channel, config = {})
27
+ def create_link(item, channel, config = {})
28
28
  config = Configuration.new(config.transform_keys(&:to_s))
29
29
  channel = ChannelUID.new(channel) if channel.is_a?(String)
30
30
  channel = channel.uid if channel.is_a?(Channel)
31
- link = org.openhab.core.thing.link.ItemChannelLink.new(item.name, channel, config)
32
-
33
- current.add(link)
31
+ org.openhab.core.thing.link.ItemChannelLink.new(item.name, channel, config)
34
32
  end
35
33
  end
36
34
 
@@ -29,7 +29,7 @@ module OpenHAB
29
29
  #
30
30
  # @return [Array] channels
31
31
  def channels
32
- Thing::ChannelsArray.new(super.to_a)
32
+ Thing::ChannelsArray.new(self, super.to_a)
33
33
  end
34
34
 
35
35
  #
@@ -39,11 +39,17 @@ module OpenHAB
39
39
  #
40
40
  # Enter the Thing Builder DSL.
41
41
  # @param (see Core::Provider.current)
42
+ # @param update [true, false]
43
+ # When true, existing things with the same name will be redefined if they're different.
44
+ # When false, an error will be raised if a thing with the same uid already exists.
42
45
  # @yield Block executed in the context of a {DSL::Things::Builder}.
43
46
  # @return [Object] The result of the block.
47
+ # @raise [ArgumentError] if a thing with the same uid already exists and `update` is false.
48
+ # @raise [FrozenError] if `update` is true but the existing thing with the same uid
49
+ # wasn't created by the current provider.
44
50
  #
45
- def build(preferred_provider = nil, &block)
46
- DSL::Things::Builder.new(preferred_provider).instance_eval(&block)
51
+ def build(preferred_provider = nil, update: true, &block)
52
+ DSL::Things::Builder.new(preferred_provider, update: update).instance_eval(&block)
47
53
  end
48
54
 
49
55
  #
@@ -35,7 +35,7 @@ module OpenHAB
35
35
  # @return [org.openhab.core.thing.ThingStatus]
36
36
  #
37
37
  # @!attribute [r] channels
38
- # @return [ChannelArray]
38
+ # @return [ChannelsArray]
39
39
  #
40
40
  # @!attribute [r] uid
41
41
  # Return the UID.
@@ -64,13 +64,17 @@ module OpenHAB
64
64
  # Array wrapper class to allow searching a list of channels
65
65
  # by channel id
66
66
  class ChannelsArray < Array
67
+ def initialize(thing, array)
68
+ super(array)
69
+ @thing = thing
70
+ end
71
+
67
72
  # Allows indexing by both integer as an array or channel id acting like a hash.
68
- # @param [Integer, String] index Numeric index or string channel id to search for.
73
+ # @param [Integer, String, ChannelUID] index
74
+ # Numeric index, string channel id, or a {ChannelUID} to search for.
69
75
  def [](index)
70
- if index.respond_to?(:to_str)
71
- key = index.to_str
72
- return find { |channel| channel.uid.id == key }
73
- end
76
+ return @thing.get_channel(index) if index.is_a?(ChannelUID)
77
+ return @thing.get_channel(index.to_str) if index.respond_to?(:to_str)
74
78
 
75
79
  super
76
80
  end
@@ -174,6 +178,11 @@ module OpenHAB
174
178
  uid.to_s
175
179
  end
176
180
 
181
+ # @return [org.openhab.core.common.registry.Provider, nil]
182
+ def provider
183
+ Provider.registry.provider_for(uid)
184
+ end
185
+
177
186
  #
178
187
  # Fetches the actions available for this thing.
179
188
  #
@@ -192,6 +201,18 @@ module OpenHAB
192
201
  $actions.get(scope || uid.binding_id, uid.to_s)
193
202
  end
194
203
 
204
+ #
205
+ # Compares all attributes of the thing with another thing.
206
+ #
207
+ # @param other [Thing] The thing to compare with
208
+ # @return [true,false] true if all attributes are equal, false otherwise
209
+ #
210
+ def config_eql?(other)
211
+ # @deprecated OH3.4 - in OH4, channels can be included in the array and do not need to be compared separately
212
+ channels.to_a == other.channels.to_a &&
213
+ %i[uid label bridge_uid location configuration].all? { |method| send(method) == other.send(method) }
214
+ end
215
+
195
216
  #
196
217
  # Delegate missing methods to the thing's default actions scope.
197
218
  #
@@ -80,19 +80,19 @@ module OpenHAB
80
80
  # @!attribute [r] latitude
81
81
  # @return [QuantityType]
82
82
  def latitude
83
- QuantityType.new(raw_latitude.to_big_decimal, SIUnits::DEGREE_ANGLE)
83
+ QuantityType.new(raw_latitude.to_big_decimal, Units::DEGREE_ANGLE)
84
84
  end
85
85
 
86
86
  # @!attribute [r] longitude
87
87
  # @return [QuantityType]
88
88
  def longitude
89
- QuantityType.new(raw_longitude.to_big_decimal, SIUnits::DEGREE_ANGLE)
89
+ QuantityType.new(raw_longitude.to_big_decimal, Units::DEGREE_ANGLE)
90
90
  end
91
91
 
92
92
  # @!attribute [r] altitude
93
93
  # @return [QuantityType]
94
94
  def altitude
95
- QuantityType.new(raw_altitude.to_big_decimal, Units::METRE)
95
+ QuantityType.new(raw_altitude.to_big_decimal, SIUnits::METRE)
96
96
  end
97
97
 
98
98
  #
@@ -118,23 +118,68 @@ module OpenHAB
118
118
  class ProviderWrapper
119
119
  attr_reader :provider
120
120
 
121
- def initialize(provider)
121
+ def initialize(provider, update:)
122
122
  @provider = provider
123
+ @update = update
123
124
  end
124
125
 
125
126
  # @!visibility private
126
127
  def add(builder)
127
- item = builder.build
128
- provider.add(item)
128
+ if DSL.items.key?(builder.name)
129
+ raise ArgumentError, "Item #{builder.name} already exists" unless @update
130
+
131
+ # Use provider.get because openHAB's ManagedProvider does not support the #[] method.
132
+ unless (old_item = provider.get(builder.name))
133
+ raise FrozenError, "Item #{builder.name} is managed by #{DSL.items[builder.name].provider}"
134
+ end
135
+
136
+ item = builder.build
137
+ if item.config_eql?(old_item)
138
+ logger.debug { "Not replacing existing item #{item.uid} because it is identical" }
139
+ item = old_item
140
+ else
141
+ logger.debug { "Replacing existing item #{item.uid} because it is not identical" }
142
+ provider.update(item)
143
+ end
144
+ item.metadata.merge!(builder.metadata)
145
+ item.metadata
146
+ .reject { |namespace, _| builder.metadata.key?(namespace) }
147
+ .each do |namespace, metadata|
148
+ item.metadata.delete(namespace) if metadata.provider == Core::Items::Metadata::Provider.current
149
+ end
150
+ else
151
+ item = builder.build
152
+ item.metadata.merge!(builder.metadata)
153
+ provider.add(item)
154
+ end
155
+
156
+ item.update(builder.state) unless builder.state.nil?
157
+
129
158
  # make sure to add the item to the registry before linking it
130
- builder.channels.each do |(channel, config)|
159
+ provider = Core::Things::Links::Provider.current
160
+ channel_uids = builder.channels.to_set do |(channel, config)|
161
+ # fill in partial channel names from group's thing id
131
162
  if !channel.include?(":") &&
132
163
  (group = builder.groups.find { |g| g.is_a?(GroupItemBuilder) && g.thing })
133
164
  thing = group.thing
134
165
  channel = "#{thing}:#{channel}"
135
166
  end
136
- Core::Things::Links::Provider.link(item, channel, config)
167
+
168
+ new_link = Core::Things::Links::Provider.create_link(item, channel, config)
169
+ if !(current_link = provider.get(new_link.uid))
170
+ provider.add(new_link)
171
+ elsif current_link.configuration != config
172
+ provider.update(new_link)
173
+ end
174
+
175
+ new_link.linked_uid
176
+ end
177
+
178
+ # remove links not in the new item
179
+ provider.all.each do |link|
180
+ provider.remove(link.uid) if link.item_name == item.name && !channel_uids.include?(link.linked_uid)
137
181
  end
182
+
138
183
  item
139
184
  end
140
185
  end
@@ -143,13 +188,15 @@ module OpenHAB
143
188
  # @return [org.openhab.core.items.ItemProvider]
144
189
  attr_reader :provider
145
190
 
146
- def initialize(provider)
147
- @provider = ProviderWrapper.new(Core::Items::Provider.current(provider))
191
+ def initialize(provider, update:)
192
+ @provider = ProviderWrapper.new(Core::Items::Provider.current(provider), update: update)
148
193
  end
149
194
  end
150
195
 
151
196
  # The ItemBuilder DSL allows you to customize an Item
152
197
  class ItemBuilder
198
+ include Core::EntityLookup
199
+
153
200
  # The type of this item
154
201
  # @example
155
202
  # type #=> :switch
@@ -181,7 +228,7 @@ module OpenHAB
181
228
  # @return [String, nil]
182
229
  attr_accessor :format
183
230
  # The icon to be associated with the item
184
- # @return [Symbol, nil]
231
+ # @return [Symbol, String, nil]
185
232
  attr_accessor :icon
186
233
  # Groups to which this item should be added
187
234
  # @return [Array<String, GroupItem>]
@@ -237,10 +284,11 @@ module OpenHAB
237
284
  end
238
285
  end
239
286
 
240
- # @param dimension [Symbol, nil] The unit dimension for a {NumberItem} (see {ItemBuilder#dimension})
287
+ # @param dimension [Symbol, String, nil] The unit dimension for a {NumberItem} (see {ItemBuilder#dimension})
288
+ # Note the dimension must be properly capitalized.
241
289
  # @param unit [Symbol, String, nil] The unit for a {NumberItem} (see {ItemBuilder#unit})
242
290
  # @param format [String, nil] The formatting pattern for the item's state (see {ItemBuilder#format})
243
- # @param icon [Symbol, nil] The icon to be associated with the item (see {ItemBuilder#icon})
291
+ # @param icon [Symbol, String, nil] The icon to be associated with the item (see {ItemBuilder#icon})
244
292
  # @param group [String,
245
293
  # GroupItem,
246
294
  # GroupItemBuilder,
@@ -259,9 +307,10 @@ module OpenHAB
259
307
  # Fluent alias for `tag`.
260
308
  # @param autoupdate [true, false, nil] Autoupdate setting (see {ItemBuilder#autoupdate})
261
309
  # @param thing [String, Core::Things::Thing, Core::Things::ThingUID, nil]
262
- # A Thing to be used as the base for the channel
263
- # @param channel [String, Core::Things::ChannelUID, nil] Channel to link the item to
264
- # @param expire [String] An expiration specification.
310
+ # A Thing to be used as the base for the channel.
311
+ # @param channel [String, Symbol, Core::Things::ChannelUID, Core::Things::Channel, nil]
312
+ # Channel to link the item to (see {ItemBuilder#channel}).
313
+ # @param expire [String] An expiration specification (see {ItemBuilder#expire}).
265
314
  # @param alexa [String, Symbol, Array<(String, Hash<String, Object>)>, nil]
266
315
  # Alexa metadata (see {ItemBuilder#alexa})
267
316
  # @param ga [String, Symbol, Array<(String, Hash<String, Object>)>, nil]
@@ -292,7 +341,16 @@ module OpenHAB
292
341
  metadata: nil,
293
342
  state: nil)
294
343
  raise ArgumentError, "`name` cannot be nil" if name.nil?
295
- raise ArgumentError, "`dimension` can only be specified with NumberItem" if dimension && type != :number
344
+
345
+ if dimension
346
+ raise ArgumentError, "`dimension` can only be specified with NumberItem" unless type == :number
347
+
348
+ begin
349
+ org.openhab.core.types.util.UnitUtils.parse_dimension(dimension.to_s)
350
+ rescue java.lang.IllegalArgumentException
351
+ raise ArgumentError, "Invalid dimension '#{dimension}'"
352
+ end
353
+ end
296
354
 
297
355
  name = name.name if name.respond_to?(:name)
298
356
  if provider.is_a?(GroupItemBuilder)
@@ -413,6 +471,8 @@ module OpenHAB
413
471
  #
414
472
  # Add a channel link to this item.
415
473
  #
474
+ # @param channel [String, Symbol, Core::Things::ChannelUID, Core::Things::Channel]
475
+ # Channel to link the item to. When thing is set, this can be a relative channel name.
416
476
  # @param config [Hash] Additional configuration, such as profile
417
477
  # @return [void]
418
478
  #
@@ -423,16 +483,27 @@ module OpenHAB
423
483
  # end
424
484
  # end
425
485
  #
486
+ # @example Relative channel name
487
+ # items.build do
488
+ # switch_item Bedroom_Light, thing: "mqtt:topic:bedroom-light", channel: :power
489
+ # end
490
+ #
426
491
  def channel(channel, config = {})
492
+ channel = channel.to_s
427
493
  channel = "#{@thing}:#{channel}" if @thing && !channel.include?(":")
428
494
  @channels << [channel, config]
429
495
  end
430
496
 
431
497
  #
432
- # @!method expire(command: nil, state: nil)
498
+ # @!method expire(duration, command: nil, state: nil, ignore_state_updates: nil, ignore_commands: nil)
433
499
  #
434
500
  # Configure item expiration
435
501
  #
502
+ # @param duration [String, Duration, nil] Duration after which the command or state should be applied
503
+ # @param command [String, nil] Command to send on expiration
504
+ # @param state [String, nil] State to set on expiration
505
+ # @param ignore_state_updates [true, false] When true, state updates will not reset the expire timer
506
+ # @param ignore_commands [true, false] When true, commands will not reset the expire timer
436
507
  # @return [void]
437
508
  #
438
509
  # @example Get the current expire setting
@@ -448,7 +519,12 @@ module OpenHAB
448
519
  # expire 5.minutes, state: NULL
449
520
  # @example Send a command on expiration
450
521
  # expire 5.minutes, command: OFF
451
- def expire(*args, command: nil, state: nil)
522
+ # @example Specify the duration and command in the same string
523
+ # expire "5h,command=OFF"
524
+ # @example Set the expire configuration
525
+ # expire 5.minutes, ignore_state_updates: true
526
+ #
527
+ def expire(*args, command: nil, state: nil, ignore_state_updates: nil, ignore_commands: nil)
452
528
  unless (0..2).cover?(args.length)
453
529
  raise ArgumentError,
454
530
  "wrong number of arguments (given #{args.length}, expected 0..2)"
@@ -463,9 +539,14 @@ module OpenHAB
463
539
 
464
540
  duration = duration.to_s[2..].downcase if duration.is_a?(Duration)
465
541
  state = "'#{state}'" if state.respond_to?(:to_str) && type == :string
466
- @expire = duration
467
- @expire += ",state=#{state}" if state
468
- @expire += ",command=#{command}" if command
542
+
543
+ value = duration
544
+ value += ",state=#{state}" if state
545
+ value += ",command=#{command}" if command
546
+
547
+ config = { ignoreStateUpdates: ignore_state_updates, ignoreCommands: ignore_commands }
548
+ config.compact!
549
+ @expire = [value, config]
469
550
  end
470
551
 
471
552
  # @!attribute [w] unit
@@ -513,12 +594,10 @@ module OpenHAB
513
594
  tags.each do |tag|
514
595
  item.add_tag(tag)
515
596
  end
516
- item.metadata.merge!(metadata)
517
- item.metadata["autoupdate"] = autoupdate.to_s unless autoupdate.nil?
518
- item.metadata["expire"] = expire if expire
519
- item.metadata["stateDescription"] = { "pattern" => format } if format
520
- item.metadata["unit"] = unit if unit
521
- item.state = item.format_update(state) unless state.nil?
597
+ metadata["autoupdate"] = autoupdate.to_s unless autoupdate.nil?
598
+ metadata["expire"] = expire if expire
599
+ metadata["stateDescription"] = { "pattern" => format } if format
600
+ metadata["unit"] = unit if unit
522
601
  item
523
602
  end
524
603
 
@@ -567,7 +646,7 @@ module OpenHAB
567
646
 
568
647
  class_eval <<~RUBY, __FILE__, __LINE__ + 1
569
648
  def #{m}(*args, groups: nil, **kwargs) # def dimmer_item(*args, groups: nil, **kwargs)
570
- groups ||= [] # groups ||= []
649
+ groups = Array.wrap(groups) # groups = Array.wrap(groups)
571
650
  groups << self # groups << self
572
651
  super # super
573
652
  end # end
@@ -12,8 +12,9 @@ module OpenHAB
12
12
  # Base sitemap builder DSL
13
13
  class Builder
14
14
  # @!visibility private
15
- def initialize(provider)
15
+ def initialize(provider, update:)
16
16
  @provider = provider
17
+ @update = update
17
18
  end
18
19
 
19
20
  # (see SitemapBuilder#initialize)
@@ -23,8 +24,12 @@ module OpenHAB
23
24
  def sitemap(name, label = nil, icon: nil, &block)
24
25
  sitemap = SitemapBuilder.new(name, label, icon: icon)
25
26
  sitemap.instance_eval_with_dummy_items(&block) if block
26
- @provider.add(sitemap.build)
27
- sitemap
27
+ sitemap = sitemap.build
28
+ if @update && @provider.get(sitemap.uid)
29
+ @provider.update(sitemap)
30
+ else
31
+ @provider.add(sitemap)
32
+ end
28
33
  end
29
34
  end
30
35
 
@@ -22,7 +22,7 @@ module OpenHAB
22
22
  # payloadNotAvailable: "offline"
23
23
  # }
24
24
  # things.build do
25
- # thing("mqtt:topic:my-switch", "My Switch", bridge: "mqtt:bridge:mosquitto", config: thing_config) do
25
+ # thing("mqtt:topic:my-switch", "My Switch", bridge: "mqtt:broker:mosquitto", config: thing_config) do
26
26
  # channel("switch1", "switch", config: {
27
27
  # stateTopic: "stat/my-switch/switch1/state", commandTopic: "cmnd/my-switch/switch1/command"
28
28
  # })
@@ -40,8 +40,9 @@ module OpenHAB
40
40
  # @return [org.openhab.core.thing.ManagedThingProvider]
41
41
  attr_reader :provider
42
42
 
43
- def initialize(provider)
43
+ def initialize(provider, update: false)
44
44
  @provider = Core::Things::Provider.current(provider)
45
+ @update = update
45
46
  end
46
47
 
47
48
  # Create a new Bridge
@@ -61,10 +62,27 @@ module OpenHAB
61
62
  def build(klass, *args, **kwargs, &block)
62
63
  builder = klass.new(*args, **kwargs)
63
64
  builder.instance_eval(&block) if block
64
- thing = provider.add(builder.build)
65
- thing = Core::Things::Proxy.new(thing)
65
+ thing = builder.build
66
+
67
+ if DSL.things.key?(thing.uid)
68
+ raise ArgumentError, "Thing #{thing.uid} already exists" unless @update
69
+
70
+ unless (old_thing = provider.get(thing.uid))
71
+ raise FrozenError, "Thing #{thing.uid} is managed by #{thing.provider}"
72
+ end
73
+
74
+ if thing.config_eql?(old_thing)
75
+ logger.debug { "Not replacing existing thing #{thing.uid}" }
76
+ thing = old_thing
77
+ else
78
+ provider.update(thing)
79
+ end
80
+ else
81
+ provider.add(thing)
82
+ end
66
83
  thing.enable(enabled: builder.enabled) unless builder.enabled.nil?
67
- thing
84
+
85
+ Core::Things::Proxy.new(thing)
68
86
  end
69
87
  end
70
88
 
@@ -176,10 +194,11 @@ module OpenHAB
176
194
 
177
195
  # Add an explicitly configured channel to this item
178
196
  # @see ChannelBuilder#initialize
197
+ # @return [Core::Things::Channel]
179
198
  def channel(*args, **kwargs, &block)
180
199
  channel = ChannelBuilder.new(*args, thing: self, **kwargs)
181
200
  channel.instance_eval(&block) if block
182
- @channels << channel.build
201
+ channel.build.tap { |c| @channels << c }
183
202
  end
184
203
 
185
204
  # @!visibility private
@@ -195,6 +214,7 @@ module OpenHAB
195
214
  builder = org.openhab.core.thing.binding.builder.ThingBuilder
196
215
  .create(thing_type_uid, uid)
197
216
  .with_label(label)
217
+ .with_location(location)
198
218
  .with_configuration(configuration)
199
219
  .with_bridge(bridge_uid)
200
220
  .with_channels(channels)
@@ -209,9 +229,7 @@ module OpenHAB
209
229
  builder.with_properties(thing_type.properties)
210
230
  end
211
231
 
212
- thing = builder.build
213
- Core::Things.manager.set_enabled(uid, enabled) unless enabled.nil?
214
- thing
232
+ builder.build
215
233
  end
216
234
 
217
235
  private
@@ -239,7 +257,14 @@ module OpenHAB
239
257
  # The ChannelBuilder DSL allows you to customize a channel
240
258
  class ChannelBuilder
241
259
  attr_accessor :label
242
- attr_reader :uid, :config, :type
260
+ attr_reader :uid,
261
+ :config,
262
+ :type,
263
+ :default_tags,
264
+ :properties,
265
+ :description,
266
+ :auto_update_policy,
267
+ :accepted_item_type
243
268
 
244
269
  #
245
270
  # Constructor for ChannelBuilder
@@ -251,10 +276,27 @@ module OpenHAB
251
276
  # @param [String] label The channel label.
252
277
  # @param [thing] thing The thing associated with this channel.
253
278
  # This parameter is not needed for the {ThingBuilder#channel} method.
279
+ # @param [String] description The channel description.
254
280
  # @param [String] group The group name.
255
281
  # @param [Hash] config Channel configuration. The keys can be strings or symbols.
282
+ # @param [Hash] properties The channel properties.
283
+ # @param [String,Symbol,Semantics::Tag,Array<String,Symbol,Semantics::Tag>] default_tags
284
+ # The default tags for this channel.
285
+ # @param [:default, :recommend, :veto, org.openhab.core.thing.type.AutoUpdatePolicy] auto_update_policy
286
+ # The channel's auto update policy.
287
+ # @param [String] accepted_item_type The accepted item type.
256
288
  #
257
- def initialize(uid, type, label = nil, thing:, group: nil, config: {})
289
+ def initialize(uid,
290
+ type,
291
+ label = nil,
292
+ thing:,
293
+ description: nil,
294
+ group: nil,
295
+ config: nil,
296
+ properties: nil,
297
+ default_tags: nil,
298
+ auto_update_policy: nil,
299
+ accepted_item_type: nil)
258
300
  @thing = thing
259
301
 
260
302
  uid = uid.to_s
@@ -274,7 +316,14 @@ module OpenHAB
274
316
  end
275
317
  @type = type
276
318
  @label = label
277
- @config = config.transform_keys(&:to_s)
319
+ @config = config&.transform_keys(&:to_s)
320
+ @default_tags = Items::ItemBuilder.normalize_tags(*Array.wrap(default_tags))
321
+ @properties = properties&.transform_keys(&:to_s)
322
+ @description = description
323
+ @accepted_item_type = accepted_item_type
324
+ return unless auto_update_policy
325
+
326
+ @auto_update_policy = org.openhab.core.thing.type.AutoUpdatePolicy.value_of(auto_update_policy.to_s.upcase)
278
327
  end
279
328
 
280
329
  # @!visibility private
@@ -282,7 +331,15 @@ module OpenHAB
282
331
  org.openhab.core.thing.binding.builder.ChannelBuilder.create(uid)
283
332
  .with_kind(kind)
284
333
  .with_type(type)
285
- .with_configuration(Core::Configuration.new(config))
334
+ .tap do |builder|
335
+ builder.with_label(label) if label
336
+ builder.with_configuration(Core::Configuration.new(config)) if config && !config.empty?
337
+ builder.with_default_tags(Set.new(default_tags).to_java) unless default_tags.empty?
338
+ builder.with_properties(properties) if properties
339
+ builder.with_description(description) if description
340
+ builder.with_auto_update_policy(auto_update_policy) if auto_update_policy
341
+ builder.with_accepted_item_type(accepted_item_type) if accepted_item_type
342
+ end
286
343
  .build
287
344
  end
288
345
 
@@ -4,6 +4,6 @@ module OpenHAB
4
4
  module DSL
5
5
  # Version of openHAB helper libraries
6
6
  # @return [String]
7
- VERSION = "5.7.1"
7
+ VERSION = "5.9.0"
8
8
  end
9
9
  end
data/lib/openhab/log.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "forwardable"
4
4
 
5
+ $ctx ||= nil
6
+
5
7
  module OpenHAB
6
8
  # rubocop:disable Layout/LineLength
7
9
 
@@ -83,8 +85,8 @@ module OpenHAB
83
85
  when String
84
86
  name = object
85
87
  when :main
86
- name = "#{Logger::PREFIX}.#{rules_file.tr_s(":", "_")
87
- .gsub(/[^A-Za-z0-9_.-]/, "")}"
88
+ name = "#{Logger::PREFIX}.#{rules_file.tr_s(":", "_").gsub(/[^A-Za-z0-9_.-]/, "")}"
89
+ name = "#{name}.#{$ctx["ruleUID"]}" if $ctx&.key?("ruleUID")
88
90
  return @loggers[name] ||= BiLogger.new(Logger.new(name))
89
91
  end
90
92
 
@@ -231,14 +231,6 @@ module OpenHAB
231
231
 
232
232
  rs = OSGi.service("org.openhab.core.service.ReadyService")
233
233
 
234
- # Add a fake automation:scriptEngineFactories to satisfy startlevel 30
235
- begin
236
- sef_marker = org.openhab.core.automation.module.script.internal.ScriptEngineFactoryBundleTracker::READY_MARKER
237
- rs.mark_ready(sef_marker)
238
- rescue NameError
239
- # @deprecated OH3.4 NOOP - the ScriptEngineFactoryBundleTracker doesn't exist in OH3
240
- end
241
-
242
234
  # wait for the rule engine
243
235
  filter = org.openhab.core.service.ReadyMarkerFilter.new
244
236
  .with_type(org.openhab.core.service.StartLevelService::STARTLEVEL_MARKER_TYPE)
@@ -327,6 +327,7 @@ module OpenHAB
327
327
  org.openhab.core.io.monitor
328
328
  org.openhab.core.io.rest
329
329
  org.openhab.core.io.rest.sse
330
+ org.eclipse.jetty.http2.hpack
330
331
  ].freeze
331
332
  private_constant :ALLOWED_BUNDLES
332
333
 
@@ -742,6 +743,8 @@ module OpenHAB
742
743
 
743
744
  startlevels = File.read(config_file)
744
745
  startlevels.sub!(",rules:refresh,rules:dslprovider", "")
746
+ startlevels.sub!(",automation:scriptEngineFactories", "") # since OH4
747
+ startlevels.sub!("dsl:rules,", "") # since OH41-snapshot
745
748
 
746
749
  target_file = "#{oh_userdata}/services.cfg"
747
750
  target_file_contents = File.read(target_file) if File.exist?(target_file)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openhab-scripting
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.7.1
4
+ version: 5.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian O'Connell
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-09-18 00:00:00.000000000 Z
13
+ date: 2023-10-09 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler