openhab-scripting 5.7.0 → 5.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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