openhab-scripting 5.7.0 → 5.8.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: 86b3b636fb3b4c119d2ed6a5d6b81bedd731456c750d28f4e16f602d75778e7c
4
- data.tar.gz: 673ed9dc1dfba2e38e1a57b01468c3a6d74a3a3fd0141c5bd9f51bc25d84e634
3
+ metadata.gz: 00dfdfae614d0fc89c09ddfe86b16c4d0b41e94bc1c2e73889eb359ff09ada3c
4
+ data.tar.gz: ecafc5d2f33cf0b0015ac9e8c59449a57488e4f18316bd584e8dfe028d5ec4c7
5
5
  SHA512:
6
- metadata.gz: 5974cf573c9fe03b1cd8df81cd312a6be70e3175c1a157d19db5dee17db2ecf8afae6619b5a3f81f91a2fce1834c7c24f7fbc7a6bf3166e60106050892d4f9ca
7
- data.tar.gz: 9cced687ace50ae3bcab50ee9ad421e9bd34cabf5338b7d927c4415f540148bf2f8033f8e72b43814a0b8a94ad399b3119a3b3ea953a1215fbc8be3e915a77f5
6
+ metadata.gz: eff4e5471afb074c92cd7d3fc01d9070a80db680ed79c4814dc8b88e14eb67c8f9215a77f80c6b1a9a5eb6f4e220b6ce5e38ba01f0287779821db918f81c0fef
7
+ data.tar.gz: f187e831d86e87fe4a2e91b311f8bdd3c4e046e9107054ff74c91cdf7644d4fa303d86d8abf3c5f80e8e8cb1762f5c436b89e3e841a86ef05af351c6f0d6abbb
@@ -3,6 +3,17 @@
3
3
  module OpenHAB
4
4
  module Core
5
5
  module Actions
6
+ #
7
+ # The HTTP actions allow you to send HTTP requests and receive the response.
8
+ #
9
+ # @example
10
+ # # Send a GET request
11
+ # headers = {
12
+ # "User-Agent": "JRuby/1.2.3", # enclose in quotes if the key contains dashes
13
+ # Accept: "application/json",
14
+ # }
15
+ # response = HTTP.send_http_get_request("http://example.com/list", headers: headers)
16
+ #
6
17
  # @see https://www.openhab.org/docs/configuration/actions.html#http-actions HTTP Actions
7
18
  class HTTP
8
19
  class << self
@@ -10,7 +21,8 @@ module OpenHAB
10
21
  # Sends an HTTP GET request and returns the result as a String.
11
22
  #
12
23
  # @param [String] url
13
- # @param [Hash<String, String>] headers
24
+ # @param [Hash<String, String>, Hash<Symbol, String>] headers
25
+ # A hash of headers to send with the request. Symbolic keys will be converted to strings.
14
26
  # @param [Duration, int, nil] timeout Timeout (in milliseconds, if given as an Integer)
15
27
  # @return [String] the response body
16
28
  # @return [nil] if an error occurred
@@ -19,7 +31,7 @@ module OpenHAB
19
31
  timeout ||= 5_000
20
32
  timeout = timeout.to_millis if timeout.is_a?(Duration)
21
33
 
22
- sendHttpGetRequest(url, headers, timeout)
34
+ sendHttpGetRequest(url, headers.transform_keys(&:to_s), timeout)
23
35
  end
24
36
 
25
37
  #
@@ -28,7 +40,8 @@ module OpenHAB
28
40
  # @param [String] url
29
41
  # @param [String] content_type
30
42
  # @param [String] content
31
- # @param [Hash<String, String>] headers
43
+ # @param [Hash<String, String>, Hash<Symbol, String>] headers
44
+ # A hash of headers to send with the request. Symbolic keys will be converted to strings.
32
45
  # @param [Duration, int, nil] timeout Timeout (in milliseconds, if given as an Integer)
33
46
  # @return [String] the response body
34
47
  # @return [nil] if an error occurred
@@ -37,7 +50,7 @@ module OpenHAB
37
50
  timeout ||= 1_000
38
51
  timeout = timeout.to_millis if timeout.is_a?(Duration)
39
52
 
40
- sendHttpPutRequest(url, content_type, content, headers, timeout)
53
+ sendHttpPutRequest(url, content_type, content, headers.transform_keys(&:to_s), timeout)
41
54
  end
42
55
 
43
56
  #
@@ -46,7 +59,8 @@ module OpenHAB
46
59
  # @param [String] url
47
60
  # @param [String] content_type
48
61
  # @param [String] content
49
- # @param [Hash<String, String>] headers
62
+ # @param [Hash<String, String>, Hash<Symbol, String>] headers
63
+ # A hash of headers to send with the request. Symbolic keys will be converted to strings.
50
64
  # @param [Duration, int, nil] timeout Timeout (in milliseconds, if given as an Integer)
51
65
  # @return [String] the response body
52
66
  # @return [nil] if an error occurred
@@ -55,14 +69,16 @@ module OpenHAB
55
69
  timeout ||= 1_000
56
70
  timeout = timeout.to_millis if timeout.is_a?(Duration)
57
71
 
58
- sendHttpPostRequest(url, content_type, content, headers, timeout)
72
+ sendHttpPostRequest(url, content_type, content, headers.transform_keys(&:to_s), timeout)
59
73
  end
60
74
 
61
75
  #
62
76
  # Sends an HTTP DELETE request and returns the result as a String.
63
77
  #
64
78
  # @param [String] url
65
- # @param [Hash<String, String>] headers
79
+ # @param [Hash<String, String>, Hash<Symbol, String>] headers
80
+ # A hash of headers to send with the request. Keys are strings or symbols, values are strings.
81
+ # Underscores in symbolic keys are replaced with dashes.
66
82
  # @param [Duration, int, nil] timeout Timeout (in milliseconds, if given as an Integer)
67
83
  # @return [String] the response body
68
84
  # @return [nil] if an error occurred
@@ -71,7 +87,7 @@ module OpenHAB
71
87
  timeout ||= 1_000
72
88
  timeout = timeout.to_millis if timeout.is_a?(Duration)
73
89
 
74
- sendHttpDeleteRequest(url, headers, timeout)
90
+ sendHttpDeleteRequest(url, headers.transform_keys(&:to_s), timeout)
75
91
  end
76
92
  end
77
93
  end
@@ -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
  #
@@ -70,20 +71,20 @@ module OpenHAB
70
71
  # frame label: "Main Floor" do
71
72
  # text item: MainFloor_AmbTemp
72
73
  # switch item: MainFloorThermostat_TargetMode, label: "Mode", mappings: %w[off auto cool heat]
73
- # setpoint item: MainFloorThermostate_SetPoint, label: "Set Point", visibility: "MainFloorThermostat_TargetMode!=off"
74
+ # setpoint item: MainFloorThermostat_SetPoint, label: "Set Point", visibility: "MainFloorThermostat_TargetMode!=off"
74
75
  # end
75
76
  # frame label: "Basement" do
76
77
  # text item: Basement_AmbTemp
77
78
  # switch item: BasementThermostat_TargetMode, label: "Mode", mappings: { OFF: "off", COOL: "cool", HEAT: "heat" }
78
- # setpoint item: BasementThermostate_SetPoint, label: "Set Point", visibility: "BasementThermostat_TargetMode!=off"
79
+ # setpoint item: BasementThermostat_SetPoint, label: "Set Point", visibility: "BasementThermostat_TargetMode!=off"
79
80
  # end
80
81
  # end
81
82
  # end
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
@@ -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
 
@@ -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.
@@ -174,6 +174,11 @@ module OpenHAB
174
174
  uid.to_s
175
175
  end
176
176
 
177
+ # @return [org.openhab.core.common.registry.Provider, nil]
178
+ def provider
179
+ Provider.registry.provider_for(uid)
180
+ end
181
+
177
182
  #
178
183
  # Fetches the actions available for this thing.
179
184
  #
@@ -192,6 +197,16 @@ module OpenHAB
192
197
  $actions.get(scope || uid.binding_id, uid.to_s)
193
198
  end
194
199
 
200
+ #
201
+ # Compares all attributes of the thing with another thing.
202
+ #
203
+ # @param other [Thing] The thing to compare with
204
+ # @return [true,false] true if all attributes are equal, false otherwise
205
+ #
206
+ def config_eql?(other)
207
+ %i[uid label channels bridge_uid location configuration].all? { |method| send(method) == other.send(method) }
208
+ end
209
+
195
210
  #
196
211
  # Delegate missing methods to the thing's default actions scope.
197
212
  #
@@ -49,7 +49,8 @@ module OpenHAB
49
49
  @id = id
50
50
  @thread_locals = thread_locals
51
51
  @block = block
52
- @timer = ScriptExecution.create_timer(1.minute.from_now) { execute }
52
+ timer_identifier = block.source_location.join(":")
53
+ @timer = ScriptExecution.create_timer(timer_identifier, 1.minute.from_now) { execute }
53
54
  reschedule!(@time)
54
55
  end
55
56
 
@@ -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
@@ -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.0"
7
+ VERSION = "5.8.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.0
4
+ version: 5.8.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-07 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler