openhab-jrubyscripting 5.0.0.rc4 → 5.0.0.rc6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openhab/core/actions.rb +21 -9
  3. data/lib/openhab/core/dependency_tracking.rb +34 -0
  4. data/lib/openhab/core/entity_lookup.rb +132 -78
  5. data/lib/openhab/core/events/item_channel_link.rb +2 -2
  6. data/lib/openhab/core/events/item_command_event.rb +1 -1
  7. data/lib/openhab/core/events/item_event.rb +2 -2
  8. data/lib/openhab/core/events/item_state_changed_event.rb +1 -1
  9. data/lib/openhab/core/events/thing.rb +1 -1
  10. data/lib/openhab/core/items/accepted_data_types.rb +2 -2
  11. data/lib/openhab/core/items/contact_item.rb +1 -1
  12. data/lib/openhab/core/items/dimmer_item.rb +2 -2
  13. data/lib/openhab/core/items/generic_item.rb +45 -224
  14. data/lib/openhab/core/items/group_item.rb +5 -3
  15. data/lib/openhab/core/items/image_item.rb +2 -2
  16. data/lib/openhab/core/items/item.rb +219 -0
  17. data/lib/openhab/core/items/metadata/hash.rb +1 -1
  18. data/lib/openhab/core/items/metadata/namespace_hash.rb +1 -1
  19. data/lib/openhab/core/items/persistence.rb +19 -10
  20. data/lib/openhab/core/items/provider.rb +2 -2
  21. data/lib/openhab/core/items/proxy.rb +68 -7
  22. data/lib/openhab/core/items/registry.rb +6 -6
  23. data/lib/openhab/core/items/semantics/enumerable.rb +6 -6
  24. data/lib/openhab/core/items/semantics.rb +8 -7
  25. data/lib/openhab/core/items.rb +3 -2
  26. data/lib/openhab/core/provider.rb +14 -7
  27. data/lib/openhab/core/rules/registry.rb +2 -2
  28. data/lib/openhab/core/rules.rb +1 -1
  29. data/lib/openhab/core/script_handling.rb +6 -6
  30. data/lib/openhab/core/things/channel.rb +1 -1
  31. data/lib/openhab/core/things/channel_uid.rb +2 -2
  32. data/lib/openhab/core/things/item_channel_link.rb +2 -2
  33. data/lib/openhab/core/things/links/provider.rb +2 -2
  34. data/lib/openhab/core/things/profile_callback.rb +1 -1
  35. data/lib/openhab/core/things/registry.rb +1 -1
  36. data/lib/openhab/core/things/thing.rb +1 -1
  37. data/lib/openhab/core/timer.rb +21 -10
  38. data/lib/openhab/core/types/date_time_type.rb +4 -4
  39. data/lib/openhab/core/types/hsb_type.rb +2 -2
  40. data/lib/openhab/core/types/point_type.rb +1 -1
  41. data/lib/openhab/core/types/quantity_type.rb +1 -1
  42. data/lib/openhab/core/types.rb +1 -1
  43. data/lib/openhab/core/uid.rb +1 -1
  44. data/lib/openhab/core/value_cache.rb +188 -0
  45. data/lib/openhab/core.rb +57 -15
  46. data/lib/openhab/core_ext/between.rb +32 -0
  47. data/lib/openhab/core_ext/java/duration.rb +1 -0
  48. data/lib/openhab/core_ext/java/local_date.rb +1 -0
  49. data/lib/openhab/core_ext/java/local_time.rb +1 -0
  50. data/lib/openhab/core_ext/java/month.rb +12 -1
  51. data/lib/openhab/core_ext/java/month_day.rb +2 -0
  52. data/lib/openhab/core_ext/java/zoned_date_time.rb +4 -4
  53. data/lib/openhab/core_ext/ruby/date.rb +3 -1
  54. data/lib/openhab/core_ext/ruby/date_time.rb +1 -0
  55. data/lib/openhab/core_ext/ruby/symbol.rb +7 -0
  56. data/lib/openhab/core_ext/ruby/time.rb +1 -0
  57. data/lib/openhab/dsl/items/builder.rb +17 -10
  58. data/lib/openhab/dsl/items/ensure.rb +5 -5
  59. data/lib/openhab/dsl/items/timed_command.rb +5 -5
  60. data/lib/openhab/dsl/rules/automation_rule.rb +54 -40
  61. data/lib/openhab/dsl/rules/builder.rb +128 -79
  62. data/lib/openhab/dsl/rules/guard.rb +5 -5
  63. data/lib/openhab/dsl/rules/name_inference.rb +21 -2
  64. data/lib/openhab/dsl/rules/rule_triggers.rb +3 -3
  65. data/lib/openhab/dsl/rules/terse.rb +1 -0
  66. data/lib/openhab/dsl/rules/triggers/changed.rb +27 -24
  67. data/lib/openhab/dsl/rules/triggers/command.rb +6 -5
  68. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +3 -3
  69. data/lib/openhab/dsl/rules/triggers/cron/cron.rb +2 -2
  70. data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +6 -6
  71. data/lib/openhab/dsl/rules/triggers/updated.rb +5 -5
  72. data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +11 -12
  73. data/lib/openhab/dsl/things/builder.rb +73 -14
  74. data/lib/openhab/dsl/version.rb +2 -2
  75. data/lib/openhab/dsl.rb +45 -17
  76. data/lib/openhab/log.rb +5 -5
  77. data/lib/openhab/rspec/configuration.rb +5 -5
  78. data/lib/openhab/rspec/example_group.rb +1 -1
  79. data/lib/openhab/rspec/helpers.rb +5 -5
  80. data/lib/openhab/rspec/hooks.rb +19 -1
  81. data/lib/openhab/rspec/karaf.rb +13 -21
  82. data/lib/openhab/rspec/mocks/persistence_service.rb +15 -0
  83. data/lib/openhab/rspec/mocks/thing_handler.rb +2 -2
  84. data/lib/openhab/rspec/suspend_rules.rb +2 -1
  85. data/lib/openhab/yard/base_helper.rb +46 -0
  86. data/lib/openhab/yard/html_helper.rb +3 -3
  87. data/lib/openhab/yard/markdown_directive.rb +125 -0
  88. data/lib/openhab/yard/markdown_helper.rb +99 -0
  89. metadata +15 -7
@@ -10,15 +10,9 @@ module OpenHAB
10
10
  #
11
11
  # @see https://www.openhab.org/javadoc/latest/org/openhab/core/items/genericitem
12
12
  #
13
- # @!attribute [r] name
14
- # The item's name.
15
- # @return [String]
16
- #
17
- # @!attribute [r] label
18
- # The item's descriptive label.
19
- # @return [String, nil]
20
- #
21
13
  class GenericItem
14
+ # @!parse include Item
15
+
22
16
  # rubocop:disable Naming/MethodName these mimic Java fields, which are
23
17
  # actually methods
24
18
  class << self
@@ -36,7 +30,10 @@ module OpenHAB
36
30
 
37
31
  # @!visibility private
38
32
  #
39
- # Override to support Proxy
33
+ # Override to support {Proxy}
34
+ #
35
+ # Item.=== isn't actually included (on the Ruby side) into
36
+ # {GenericItem}
40
37
  #
41
38
  def ===(other)
42
39
  other.is_a?(self)
@@ -44,11 +41,13 @@ module OpenHAB
44
41
  end
45
42
  # rubocop:enable Naming/MethodName
46
43
 
47
- # @!attribute [r] accepted_command_types
48
- # @return [Array<Class>] An array of {Command}s that can be sent as commands to this item
44
+ # @!attribute [r] name
45
+ # The item's name.
46
+ # @return [String]
49
47
 
50
- # @!attribute [r] accepted_data_types
51
- # @return [Array<Class>] An array of {State}s that can be sent as commands to this item
48
+ # @!attribute [r] label
49
+ # The item's descriptive label.
50
+ # @return [String, nil]
52
51
 
53
52
  alias_method :hash, :hash_code
54
53
 
@@ -62,6 +61,35 @@ module OpenHAB
62
61
  #
63
62
  alias_method :raw_state, :state
64
63
 
64
+ #
65
+ # Check if the item has a state (not {UNDEF} or {NULL})
66
+ #
67
+ # @return [true, false]
68
+ #
69
+ def state?
70
+ !raw_state.is_a?(Types::UnDefType)
71
+ end
72
+
73
+ #
74
+ # @!attribute [r] state
75
+ # @return [State, nil]
76
+ # openHAB item state if state is not {UNDEF} or {NULL}, nil otherwise.
77
+ # This makes it easy to use with the
78
+ # [Ruby safe navigation operator `&.`](https://ruby-doc.org/core-2.6/doc/syntax/calling_methods_rdoc.html)
79
+ # Use {#undef?} or {#null?} to check for those states.
80
+ #
81
+ def state
82
+ raw_state if state?
83
+ end
84
+
85
+ # @!method null?
86
+ # Check if the item state == {NULL}
87
+ # @return [true,false]
88
+
89
+ # @!method undef?
90
+ # Check if the item state == {UNDEF}
91
+ # @return [true,false]
92
+
65
93
  #
66
94
  # Send a command to this item
67
95
  #
@@ -91,6 +119,10 @@ module OpenHAB
91
119
 
92
120
  # @!parse alias_method :<<, :command
93
121
 
122
+ # @!method refresh
123
+ # Send the {REFRESH} command to the item
124
+ # @return [Item] `self`
125
+
94
126
  #
95
127
  # Send an update to this item
96
128
  #
@@ -105,200 +137,6 @@ module OpenHAB
105
137
  Proxy.new(self)
106
138
  end
107
139
 
108
- #
109
- # Check if the item has a state (not {UNDEF} or {NULL})
110
- #
111
- # @return [true, false]
112
- #
113
- def state?
114
- !raw_state.is_a?(Types::UnDefType)
115
- end
116
-
117
- #
118
- # @!attribute [r] state
119
- # @return [State, nil]
120
- # OpenHAB item state if state is not {UNDEF} or {NULL}, nil otherwise.
121
- # This makes it easy to use with the
122
- # [Ruby safe navigation operator `&.`](https://ruby-doc.org/core-2.6/doc/syntax/calling_methods_rdoc.html)
123
- # Use {#undef?} or {#null?} to check for those states.
124
- #
125
- def state
126
- raw_state if state?
127
- end
128
-
129
- #
130
- # The item's {#label} if one is defined, otherwise it's {#name}.
131
- #
132
- # @return [String]
133
- #
134
- def to_s
135
- label || name
136
- end
137
-
138
- #
139
- # @!attribute [r] groups
140
- #
141
- # Return all groups that this item is part of
142
- #
143
- # @return [Array<Group>] All groups that this item is part of
144
- #
145
- def groups
146
- group_names.map { |name| EntityLookup.lookup_item(name) }.compact
147
- end
148
-
149
- # rubocop:disable Layout/LineLength
150
-
151
- # @!attribute [r] metadata
152
- # @return [Metadata::NamespaceHash]
153
- #
154
- # Access to the item's metadata.
155
- #
156
- # Both the return value of this method as well as the individual
157
- # namespaces can be treated as Hashes.
158
- #
159
- # Examples assume the following items:
160
- #
161
- # ```xtend
162
- # Switch Item1 { namespace1="value" [ config1="foo", config2="bar" ] }
163
- # String StringItem1
164
- # ```
165
- #
166
- # @example Check namespace's existence
167
- # Item1.metadata["namespace"].nil?
168
- # Item1.metadata.key?("namespace")
169
- #
170
- # @example Access item's metadata value
171
- # Item1.metadata["namespace1"].value
172
- #
173
- # @example Access namespace1's configuration
174
- # Item1.metadata["namespace1"]["config1"]
175
- #
176
- # @example Safely search for the specified value - no errors are raised, only nil returned if a key in the chain doesn"t exist
177
- # Item1.metadata.dig("namespace1", "config1") # => "foo"
178
- # Item1.metadata.dig("namespace2", "config1") # => nil
179
- #
180
- # @example Set item's metadata value, preserving its config
181
- # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
182
- # Item1.metadata["namespace1"].value = "new value"
183
- # # Item1's metadata after: {"namespace1"=>["new value", {"config1"=>"foo", "config2"=>"bar"]}}
184
- #
185
- # @example Set item's metadata config, preserving its value
186
- # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
187
- # Item1.metadata["namespace1"].replace({ "scooby"=>"doo" })
188
- # # Item1's metadata after: {"namespace1"=>["value", {scooby="doo"}]}
189
- #
190
- # @example Set a namespace to a new value and config in one line
191
- # # Item1's metadata before: {"namespace1"=>"value", {"config1"=>"foo", "config2"=>"bar"}}
192
- # Item1.metadata["namespace1"] = "new value", { "scooby"=>"doo" }
193
- # # Item1's metadata after: {"namespace1"=>["new value", {scooby="doo"}]}
194
- #
195
- # @example Set item's metadata value and clear its previous config
196
- # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
197
- # Item1.metadata["namespace1"] = "new value"
198
- # # Item1's metadata after: {"namespace1"=>"value" }
199
- #
200
- # @example Set item's metadata config, set its value to nil, and wiping out previous config
201
- # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
202
- # Item1.metadata["namespace1"] = { "newconfig"=>"value" }
203
- # # Item1's metadata after: {"namespace1"=>{"config1"=>"foo", "config2"=>"bar"}}
204
- #
205
- # @example Update namespace1's specific configuration, preserving its value and other config
206
- # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
207
- # Item1.metadata["namespace1"]["config1"] = "doo"
208
- # # Item1's metadata will be: {"namespace1"=>["value", {"config1"=>"doo", "config2"=>"bar"}]}
209
- #
210
- # @example Add a new configuration to namespace1
211
- # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
212
- # Item1.metadata["namespace1"]["config3"] = "boo"
213
- # # Item1's metadata after: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar", config3="boo"}]}
214
- #
215
- # @example Delete a config
216
- # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
217
- # Item1.metadata["namespace1"].delete("config2")
218
- # # Item1's metadata after: {"namespace1"=>["value", {"config1"=>"foo"}]}
219
- #
220
- # @example Add a namespace and set it to a value
221
- # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
222
- # Item1.metadata["namespace2"] = "qx"
223
- # # Item1's metadata after: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}], "namespace2"=>"qx"}
224
- #
225
- # @example Add a namespace and set it to a value and config
226
- # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
227
- # Item1.metadata["namespace2"] = "qx", { "config1"=>"doo" }
228
- # # Item1's metadata after: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}], "namespace2"=>["qx", {"config1"=>"doo"}]}
229
- #
230
- # @example Enumerate Item1's namespaces
231
- # Item1.metadata.each { |namespace, metadata| logger.info("Item1's namespace: #{namespace}=#{metadata}") }
232
- #
233
- # @example Add metadata from a hash
234
- # Item1.metadata.merge!({"namespace1"=>{"foo", {"config1"=>"baz"} ], "namespace2"=>{"qux", {"config"=>"quu"} ]})
235
- #
236
- # @example Merge Item2's metadata into Item1's metadata
237
- # Item1.metadata.merge!(Item2.metadata)
238
- #
239
- # @example Delete a namespace
240
- # Item1.metadata.delete("namespace1")
241
- #
242
- # @example Delete all metadata of the item
243
- # Item1.metadata.clear
244
- #
245
- # @example Does this item have any metadata?
246
- # Item1.metadata.any?
247
- #
248
- # @example Store another item's state
249
- # StringItem1.update "TEST"
250
- # Item1.metadata["other_state"] = StringItem1.state
251
- #
252
- # @example Store event's state
253
- # rule "save event state" do
254
- # changed StringItem1
255
- # run { |event| Item1.metadata["last_event"] = event.was }
256
- # end
257
- #
258
- # @example If the namespace already exists: Update the value of a namespace but preserve its config; otherwise create a new namespace with the given value and nil config.
259
- # Item1.metadata["namespace"] = "value", Item1.metadata["namespace"]
260
- #
261
- # @example Copy another namespace
262
- # # Item1's metadata before: {"namespace2"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
263
- # Item1.metadata["namespace"] = Item1.metadata["namespace2"]
264
- # # Item1's metadata after: {"namespace2"=>["value", {"config1"=>"foo", "config2"=>"bar"}], "namespace"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
265
- #
266
- def metadata
267
- @metadata ||= Metadata::NamespaceHash.new(name)
268
- end
269
- # rubocop:enable Layout/LineLength
270
-
271
- # Return the item's thing if this item is linked with a thing. If an item is linked to more than one thing,
272
- # this method only returns the first thing.
273
- #
274
- # @return [Thing] The thing associated with this item or nil
275
- def thing
276
- all_linked_things.first
277
- end
278
- alias_method :linked_thing, :thing
279
-
280
- # Returns all of the item's linked things.
281
- #
282
- # @return [Array<Thing>] An array of things or an empty array
283
- def things
284
- registry = OSGi.service("org.openhab.core.thing.link.ItemChannelLinkRegistry")
285
- channels = registry.get_bound_channels(name).to_a
286
- channels.map(&:thing_uid).uniq.map { |tuid| EntityLookup.lookup_thing(tuid) }.compact
287
- end
288
- alias_method :all_linked_things, :things
289
-
290
- # @!method null?
291
- # Check if the item state == {NULL}
292
- # @return [true,false]
293
-
294
- # @!method undef?
295
- # Check if the item state == {UNDEF}
296
- # @return [true,false]
297
-
298
- # @!method refresh
299
- # Send the {REFRESH} command to the item
300
- # @return [GenericItem] `self`
301
-
302
140
  # @!visibility private
303
141
  def format_command(command)
304
142
  command = format_type(command)
@@ -327,23 +165,6 @@ module OpenHAB
327
165
 
328
166
  type.to_s
329
167
  end
330
-
331
- # @return [String]
332
- def inspect
333
- s = "#<OpenHAB::Core::Items::#{type}Item#{type_details} #{name} #{label.inspect} state=#{raw_state.inspect}"
334
- s += " category=#{category.inspect}" if category
335
- s += " tags=#{tags.to_a.inspect}" unless tags.empty?
336
- s += " groups=#{group_names}" unless group_names.empty?
337
- meta = metadata.to_h
338
- s += " metadata=#{meta.inspect}" unless meta.empty?
339
- "#{s}>"
340
- end
341
-
342
- private
343
-
344
- # Allows sub-classes to append additional details to the type in an inspect string
345
- # @return [String]
346
- def type_details; end
347
168
  end
348
169
  end
349
170
  end
@@ -33,7 +33,7 @@ module OpenHAB
33
33
  # ```
34
34
  #
35
35
  # @!attribute [r] base_item
36
- # @return [GenericItem, nil] A typed item if the group has a particular type.
36
+ # @return [Item, nil] A typed item if the group has a particular type.
37
37
  #
38
38
  # @example Operate on items in a group using enumerable methods
39
39
  # logger.info("Total Temperatures: #{Temperatures.members.count}")
@@ -99,7 +99,9 @@ module OpenHAB
99
99
 
100
100
  # @return [String]
101
101
  def inspect
102
- "#<OpenHAB::Core::Items::GroupItems::Members #{name} #{map(&:name).inspect}>"
102
+ r = "#<OpenHAB::Core::Items::GroupItems::Members #{name}"
103
+ r += " #{map(&:name).inspect}>" unless @group.__getobj__.nil?
104
+ "#{r}>"
103
105
  end
104
106
  alias_method :to_s, :inspect
105
107
  end
@@ -120,7 +122,7 @@ module OpenHAB
120
122
  # @see Enumerable
121
123
  #
122
124
  def members
123
- Members.new(self)
125
+ Members.new(Proxy.new(self))
124
126
  end
125
127
 
126
128
  #
@@ -79,12 +79,12 @@ module OpenHAB
79
79
  private
80
80
 
81
81
  #
82
- # Encode image information in the format required by OpenHAB
82
+ # Encode image information in the format required by openHAB
83
83
  #
84
84
  # @param [String] mime_type for image
85
85
  # @param [Object] bytes image data
86
86
  #
87
- # @return [String] OpenHAB image format with image data Base64 encoded
87
+ # @return [String] openHAB image format with image data Base64 encoded
88
88
  #
89
89
  def encode_image(mime_type:, bytes:)
90
90
  "data:#{mime_type};base64,#{Base64.strict_encode64(bytes)}"
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module Core
5
+ module Items
6
+ # @interface
7
+ java_import org.openhab.core.items.Item
8
+
9
+ #
10
+ # The core features of an openHAB item.
11
+ #
12
+ module Item
13
+ class << self
14
+ # @!visibility private
15
+ #
16
+ # Override to support {Proxy}
17
+ #
18
+ def ===(other)
19
+ other.is_a?(self)
20
+ end
21
+ end
22
+
23
+ # @!attribute [r] name
24
+ # The item's name.
25
+ # @return [String]
26
+
27
+ # @!attribute [r] label
28
+ # The item's descriptive label.
29
+ # @return [String, nil]
30
+
31
+ # @!attribute [r] accepted_command_types
32
+ # @return [Array<Class>] An array of {Command}s that can be sent as commands to this item
33
+
34
+ # @!attribute [r] accepted_data_types
35
+ # @return [Array<Class>] An array of {State}s that can be sent as commands to this item
36
+
37
+ #
38
+ # The item's {#label} if one is defined, otherwise it's {#name}.
39
+ #
40
+ # @return [String]
41
+ #
42
+ def to_s
43
+ label || name
44
+ end
45
+
46
+ #
47
+ # @!attribute [r] groups
48
+ #
49
+ # Return all groups that this item is part of
50
+ #
51
+ # @return [Array<Group>] All groups that this item is part of
52
+ #
53
+ def groups
54
+ group_names.map { |name| EntityLookup.lookup_item(name) }.compact
55
+ end
56
+
57
+ # rubocop:disable Layout/LineLength
58
+
59
+ # @!attribute [r] metadata
60
+ # @return [Metadata::NamespaceHash]
61
+ #
62
+ # Access to the item's metadata.
63
+ #
64
+ # Both the return value of this method as well as the individual
65
+ # namespaces can be treated as Hashes.
66
+ #
67
+ # Examples assume the following items:
68
+ #
69
+ # ```xtend
70
+ # Switch Item1 { namespace1="value" [ config1="foo", config2="bar" ] }
71
+ # String StringItem1
72
+ # ```
73
+ #
74
+ # @example Check namespace's existence
75
+ # Item1.metadata["namespace"].nil?
76
+ # Item1.metadata.key?("namespace")
77
+ #
78
+ # @example Access item's metadata value
79
+ # Item1.metadata["namespace1"].value
80
+ #
81
+ # @example Access namespace1's configuration
82
+ # Item1.metadata["namespace1"]["config1"]
83
+ #
84
+ # @example Safely search for the specified value - no errors are raised, only nil returned if a key in the chain doesn"t exist
85
+ # Item1.metadata.dig("namespace1", "config1") # => "foo"
86
+ # Item1.metadata.dig("namespace2", "config1") # => nil
87
+ #
88
+ # @example Set item's metadata value, preserving its config
89
+ # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
90
+ # Item1.metadata["namespace1"].value = "new value"
91
+ # # Item1's metadata after: {"namespace1"=>["new value", {"config1"=>"foo", "config2"=>"bar"]}}
92
+ #
93
+ # @example Set item's metadata config, preserving its value
94
+ # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
95
+ # Item1.metadata["namespace1"].replace({ "scooby"=>"doo" })
96
+ # # Item1's metadata after: {"namespace1"=>["value", {scooby="doo"}]}
97
+ #
98
+ # @example Set a namespace to a new value and config in one line
99
+ # # Item1's metadata before: {"namespace1"=>"value", {"config1"=>"foo", "config2"=>"bar"}}
100
+ # Item1.metadata["namespace1"] = "new value", { "scooby"=>"doo" }
101
+ # # Item1's metadata after: {"namespace1"=>["new value", {scooby="doo"}]}
102
+ #
103
+ # @example Set item's metadata value and clear its previous config
104
+ # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
105
+ # Item1.metadata["namespace1"] = "new value"
106
+ # # Item1's metadata after: {"namespace1"=>"value" }
107
+ #
108
+ # @example Set item's metadata config, set its value to nil, and wiping out previous config
109
+ # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
110
+ # Item1.metadata["namespace1"] = { "newconfig"=>"value" }
111
+ # # Item1's metadata after: {"namespace1"=>{"config1"=>"foo", "config2"=>"bar"}}
112
+ #
113
+ # @example Update namespace1's specific configuration, preserving its value and other config
114
+ # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
115
+ # Item1.metadata["namespace1"]["config1"] = "doo"
116
+ # # Item1's metadata will be: {"namespace1"=>["value", {"config1"=>"doo", "config2"=>"bar"}]}
117
+ #
118
+ # @example Add a new configuration to namespace1
119
+ # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
120
+ # Item1.metadata["namespace1"]["config3"] = "boo"
121
+ # # Item1's metadata after: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar", config3="boo"}]}
122
+ #
123
+ # @example Delete a config
124
+ # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
125
+ # Item1.metadata["namespace1"].delete("config2")
126
+ # # Item1's metadata after: {"namespace1"=>["value", {"config1"=>"foo"}]}
127
+ #
128
+ # @example Add a namespace and set it to a value
129
+ # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
130
+ # Item1.metadata["namespace2"] = "qx"
131
+ # # Item1's metadata after: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}], "namespace2"=>"qx"}
132
+ #
133
+ # @example Add a namespace and set it to a value and config
134
+ # # Item1's metadata before: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
135
+ # Item1.metadata["namespace2"] = "qx", { "config1"=>"doo" }
136
+ # # Item1's metadata after: {"namespace1"=>["value", {"config1"=>"foo", "config2"=>"bar"}], "namespace2"=>["qx", {"config1"=>"doo"}]}
137
+ #
138
+ # @example Enumerate Item1's namespaces
139
+ # Item1.metadata.each { |namespace, metadata| logger.info("Item1's namespace: #{namespace}=#{metadata}") }
140
+ #
141
+ # @example Add metadata from a hash
142
+ # Item1.metadata.merge!({"namespace1"=>{"foo", {"config1"=>"baz"} ], "namespace2"=>{"qux", {"config"=>"quu"} ]})
143
+ #
144
+ # @example Merge Item2's metadata into Item1's metadata
145
+ # Item1.metadata.merge!(Item2.metadata)
146
+ #
147
+ # @example Delete a namespace
148
+ # Item1.metadata.delete("namespace1")
149
+ #
150
+ # @example Delete all metadata of the item
151
+ # Item1.metadata.clear
152
+ #
153
+ # @example Does this item have any metadata?
154
+ # Item1.metadata.any?
155
+ #
156
+ # @example Store another item's state
157
+ # StringItem1.update "TEST"
158
+ # Item1.metadata["other_state"] = StringItem1.state
159
+ #
160
+ # @example Store event's state
161
+ # rule "save event state" do
162
+ # changed StringItem1
163
+ # run { |event| Item1.metadata["last_event"] = event.was }
164
+ # end
165
+ #
166
+ # @example If the namespace already exists: Update the value of a namespace but preserve its config; otherwise create a new namespace with the given value and nil config.
167
+ # Item1.metadata["namespace"] = "value", Item1.metadata["namespace"]
168
+ #
169
+ # @example Copy another namespace
170
+ # # Item1's metadata before: {"namespace2"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
171
+ # Item1.metadata["namespace"] = Item1.metadata["namespace2"]
172
+ # # Item1's metadata after: {"namespace2"=>["value", {"config1"=>"foo", "config2"=>"bar"}], "namespace"=>["value", {"config1"=>"foo", "config2"=>"bar"}]}
173
+ #
174
+ def metadata
175
+ @metadata ||= Metadata::NamespaceHash.new(name)
176
+ end
177
+ # rubocop:enable Layout/LineLength
178
+
179
+ # Return the item's thing if this item is linked with a thing. If an item is linked to more than one thing,
180
+ # this method only returns the first thing.
181
+ #
182
+ # @return [Thing] The thing associated with this item or nil
183
+ def thing
184
+ all_linked_things.first
185
+ end
186
+ alias_method :linked_thing, :thing
187
+
188
+ # Returns all of the item's linked things.
189
+ #
190
+ # @return [Array<Thing>] An array of things or an empty array
191
+ def things
192
+ registry = OSGi.service("org.openhab.core.thing.link.ItemChannelLinkRegistry")
193
+ channels = registry.get_bound_channels(name).to_a
194
+ channels.map(&:thing_uid).uniq.map { |tuid| EntityLookup.lookup_thing(tuid) }.compact
195
+ end
196
+ alias_method :all_linked_things, :things
197
+
198
+ # @return [String]
199
+ def inspect
200
+ s = "#<OpenHAB::Core::Items::#{type}Item#{type_details} #{name} #{label.inspect} state=#{raw_state.inspect}"
201
+ s += " category=#{category.inspect}" if category
202
+ s += " tags=#{tags.to_a.inspect}" unless tags.empty?
203
+ s += " groups=#{group_names}" unless group_names.empty?
204
+ meta = metadata.to_h
205
+ s += " metadata=#{meta.inspect}" unless meta.empty?
206
+ "#{s}>"
207
+ end
208
+
209
+ private
210
+
211
+ # Allows sub-classes to append additional details to the type in an inspect string
212
+ # @return [String]
213
+ def type_details; end
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ # @!parse Item = OpenHAB::Core::Items::Item
@@ -111,7 +111,7 @@ module OpenHAB
111
111
  end
112
112
 
113
113
  # @!attribute [r] item
114
- # @return [GenericItem, nil] The item this namespace is attached to.
114
+ # @return [Item, nil] The item this namespace is attached to.
115
115
  def item
116
116
  return nil unless attached?
117
117
 
@@ -288,7 +288,7 @@ module OpenHAB
288
288
 
289
289
  # @!visibility private
290
290
  def hash
291
- "metadata_namespace_hash".hash + @item_name.hash
291
+ ["metadata_namespace_hash", @item_name.hash]
292
292
  end
293
293
 
294
294
  # @!visibility private
@@ -9,7 +9,7 @@ module OpenHAB
9
9
  module Items
10
10
  #
11
11
  # Items extensions to support
12
- # {https://www.openhab.org/docs/configuration/persistence.html OpenHAB's Persistence} feature.
12
+ # {https://www.openhab.org/docs/configuration/persistence.html openHAB's Persistence} feature.
13
13
  #
14
14
  # @see OpenHAB::DSL.persistence Persistence Block
15
15
  #
@@ -40,16 +40,26 @@ module OpenHAB
40
40
  module Persistence
41
41
  GenericItem.prepend(self)
42
42
 
43
- # A state class with an added timestamp attribute. This is used to hold OpenHAB's HistoricItem.
43
+ #
44
+ # A state class with an added timestamp attribute.
45
+ #
46
+ # This wraps {org.openhab.core.persistence.HistoricItem HistoricItem}
47
+ # to allow implicitly treating the object as its state, and wrapping of
48
+ # that state into a {QuantityType} as necessary.
49
+ #
44
50
  class HistoricState < SimpleDelegator
45
- attr_reader :timestamp
46
-
47
51
  alias_method :state, :__getobj__
48
52
 
49
- def initialize(state, timestamp)
50
- @timestamp = timestamp
53
+ def initialize(state, historic_item)
54
+ @historic_item = historic_item
51
55
  super(state)
52
56
  end
57
+
58
+ # @!attribute [r] timestamp
59
+ # @return [ZonedDateTime]
60
+ def timestamp
61
+ @historic_item.timestamp
62
+ end
53
63
  end
54
64
 
55
65
  # All persistence methods that could return a QuantityType
@@ -77,7 +87,7 @@ module OpenHAB
77
87
  # @!method last_update(service = nil)
78
88
  # Return the time the item was last updated.
79
89
  # @param [Symbol, String] service An optional persistence id instead of the default persistence service.
80
- # @return [ZonedDateTime] The timestamp of the last update
90
+ # @return [ZonedDateTime, nil] The timestamp of the last update
81
91
 
82
92
  # @!method average_since(timestamp, service = nil)
83
93
  # Return the average value of the item's state since the given time
@@ -245,7 +255,7 @@ module OpenHAB
245
255
  def previous_state(service = nil, skip_equal: false)
246
256
  service ||= persistence_service
247
257
  result = Actions::PersistenceExtensions.previous_state(self, skip_equal, service&.to_s)
248
- HistoricState.new(quantify(result.state), result.timestamp)
258
+ HistoricState.new(quantify(result.state), result) if result
249
259
  end
250
260
 
251
261
  PERSISTENCE_METHODS.each do |method|
@@ -307,8 +317,7 @@ module OpenHAB
307
317
  #
308
318
  def wrap_result(result, method)
309
319
  if result.is_a?(org.openhab.core.persistence.HistoricItem)
310
- return HistoricState.new(quantify(result.state),
311
- result.timestamp)
320
+ return HistoricState.new(quantify(result.state), result)
312
321
  end
313
322
  return quantify(result) if QUANTITY_METHODS.include?(method)
314
323