openhab-jrubyscripting 5.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/lib/openhab/core/actions.rb +163 -0
  3. data/lib/openhab/core/entity_lookup.rb +144 -0
  4. data/lib/openhab/core/events/abstract_event.rb +17 -0
  5. data/lib/openhab/core/events/item_channel_link.rb +36 -0
  6. data/lib/openhab/core/events/item_command_event.rb +78 -0
  7. data/lib/openhab/core/events/item_event.rb +22 -0
  8. data/lib/openhab/core/events/item_state_changed_event.rb +52 -0
  9. data/lib/openhab/core/events/item_state_event.rb +51 -0
  10. data/lib/openhab/core/events/thing.rb +29 -0
  11. data/lib/openhab/core/events/thing_status_info_event.rb +53 -0
  12. data/lib/openhab/core/events.rb +10 -0
  13. data/lib/openhab/core/items/accepted_data_types.rb +29 -0
  14. data/lib/openhab/core/items/color_item.rb +52 -0
  15. data/lib/openhab/core/items/contact_item.rb +52 -0
  16. data/lib/openhab/core/items/date_time_item.rb +58 -0
  17. data/lib/openhab/core/items/dimmer_item.rb +148 -0
  18. data/lib/openhab/core/items/generic_item.rb +344 -0
  19. data/lib/openhab/core/items/group_item.rb +174 -0
  20. data/lib/openhab/core/items/image_item.rb +109 -0
  21. data/lib/openhab/core/items/location_item.rb +34 -0
  22. data/lib/openhab/core/items/metadata/hash.rb +390 -0
  23. data/lib/openhab/core/items/metadata/namespace_hash.rb +469 -0
  24. data/lib/openhab/core/items/metadata.rb +11 -0
  25. data/lib/openhab/core/items/number_item.rb +62 -0
  26. data/lib/openhab/core/items/numeric_item.rb +22 -0
  27. data/lib/openhab/core/items/persistence.rb +327 -0
  28. data/lib/openhab/core/items/player_item.rb +66 -0
  29. data/lib/openhab/core/items/proxy.rb +59 -0
  30. data/lib/openhab/core/items/registry.rb +66 -0
  31. data/lib/openhab/core/items/rollershutter_item.rb +68 -0
  32. data/lib/openhab/core/items/semantics/enumerable.rb +152 -0
  33. data/lib/openhab/core/items/semantics.rb +476 -0
  34. data/lib/openhab/core/items/state_storage.rb +53 -0
  35. data/lib/openhab/core/items/string_item.rb +28 -0
  36. data/lib/openhab/core/items/switch_item.rb +78 -0
  37. data/lib/openhab/core/items.rb +114 -0
  38. data/lib/openhab/core/lazy_array.rb +52 -0
  39. data/lib/openhab/core/profile_factory.rb +118 -0
  40. data/lib/openhab/core/script_handling.rb +55 -0
  41. data/lib/openhab/core/things/channel.rb +48 -0
  42. data/lib/openhab/core/things/channel_uid.rb +51 -0
  43. data/lib/openhab/core/things/item_channel_link.rb +33 -0
  44. data/lib/openhab/core/things/profile_callback.rb +52 -0
  45. data/lib/openhab/core/things/proxy.rb +69 -0
  46. data/lib/openhab/core/things/registry.rb +46 -0
  47. data/lib/openhab/core/things/thing.rb +194 -0
  48. data/lib/openhab/core/things.rb +22 -0
  49. data/lib/openhab/core/timer.rb +128 -0
  50. data/lib/openhab/core/types/comparable_type.rb +23 -0
  51. data/lib/openhab/core/types/date_time_type.rb +259 -0
  52. data/lib/openhab/core/types/decimal_type.rb +192 -0
  53. data/lib/openhab/core/types/hsb_type.rb +183 -0
  54. data/lib/openhab/core/types/increase_decrease_type.rb +34 -0
  55. data/lib/openhab/core/types/next_previous_type.rb +34 -0
  56. data/lib/openhab/core/types/numeric_type.rb +52 -0
  57. data/lib/openhab/core/types/on_off_type.rb +46 -0
  58. data/lib/openhab/core/types/open_closed_type.rb +41 -0
  59. data/lib/openhab/core/types/percent_type.rb +95 -0
  60. data/lib/openhab/core/types/play_pause_type.rb +38 -0
  61. data/lib/openhab/core/types/point_type.rb +117 -0
  62. data/lib/openhab/core/types/quantity_type.rb +327 -0
  63. data/lib/openhab/core/types/raw_type.rb +26 -0
  64. data/lib/openhab/core/types/refresh_type.rb +27 -0
  65. data/lib/openhab/core/types/rewind_fastforward_type.rb +38 -0
  66. data/lib/openhab/core/types/stop_move_type.rb +34 -0
  67. data/lib/openhab/core/types/string_type.rb +76 -0
  68. data/lib/openhab/core/types/type.rb +117 -0
  69. data/lib/openhab/core/types/un_def_type.rb +38 -0
  70. data/lib/openhab/core/types/up_down_type.rb +50 -0
  71. data/lib/openhab/core/types.rb +69 -0
  72. data/lib/openhab/core/uid.rb +36 -0
  73. data/lib/openhab/core.rb +85 -0
  74. data/lib/openhab/core_ext/java/duration.rb +115 -0
  75. data/lib/openhab/core_ext/java/local_date.rb +93 -0
  76. data/lib/openhab/core_ext/java/local_time.rb +106 -0
  77. data/lib/openhab/core_ext/java/month.rb +59 -0
  78. data/lib/openhab/core_ext/java/month_day.rb +105 -0
  79. data/lib/openhab/core_ext/java/period.rb +103 -0
  80. data/lib/openhab/core_ext/java/temporal_amount.rb +34 -0
  81. data/lib/openhab/core_ext/java/time.rb +58 -0
  82. data/lib/openhab/core_ext/java/unit.rb +15 -0
  83. data/lib/openhab/core_ext/java/zoned_date_time.rb +116 -0
  84. data/lib/openhab/core_ext/ruby/array.rb +21 -0
  85. data/lib/openhab/core_ext/ruby/class.rb +15 -0
  86. data/lib/openhab/core_ext/ruby/date.rb +89 -0
  87. data/lib/openhab/core_ext/ruby/numeric.rb +190 -0
  88. data/lib/openhab/core_ext/ruby/range.rb +70 -0
  89. data/lib/openhab/core_ext/ruby/time.rb +104 -0
  90. data/lib/openhab/core_ext.rb +18 -0
  91. data/lib/openhab/dsl/events/watch_event.rb +18 -0
  92. data/lib/openhab/dsl/events.rb +9 -0
  93. data/lib/openhab/dsl/gems.rb +3 -0
  94. data/lib/openhab/dsl/items/builder.rb +618 -0
  95. data/lib/openhab/dsl/items/ensure.rb +93 -0
  96. data/lib/openhab/dsl/items/timed_command.rb +236 -0
  97. data/lib/openhab/dsl/rules/automation_rule.rb +308 -0
  98. data/lib/openhab/dsl/rules/builder.rb +1373 -0
  99. data/lib/openhab/dsl/rules/guard.rb +115 -0
  100. data/lib/openhab/dsl/rules/name_inference.rb +160 -0
  101. data/lib/openhab/dsl/rules/property.rb +76 -0
  102. data/lib/openhab/dsl/rules/rule_triggers.rb +96 -0
  103. data/lib/openhab/dsl/rules/terse.rb +63 -0
  104. data/lib/openhab/dsl/rules/triggers/changed.rb +169 -0
  105. data/lib/openhab/dsl/rules/triggers/channel.rb +57 -0
  106. data/lib/openhab/dsl/rules/triggers/command.rb +107 -0
  107. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +161 -0
  108. data/lib/openhab/dsl/rules/triggers/conditions/proc.rb +164 -0
  109. data/lib/openhab/dsl/rules/triggers/cron/cron.rb +195 -0
  110. data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +127 -0
  111. data/lib/openhab/dsl/rules/triggers/trigger.rb +56 -0
  112. data/lib/openhab/dsl/rules/triggers/updated.rb +130 -0
  113. data/lib/openhab/dsl/rules/triggers/watch/watch.rb +55 -0
  114. data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +155 -0
  115. data/lib/openhab/dsl/rules/triggers.rb +12 -0
  116. data/lib/openhab/dsl/rules.rb +29 -0
  117. data/lib/openhab/dsl/script_handling.rb +55 -0
  118. data/lib/openhab/dsl/things/builder.rb +263 -0
  119. data/lib/openhab/dsl/thread_local.rb +48 -0
  120. data/lib/openhab/dsl/timer_manager.rb +191 -0
  121. data/lib/openhab/dsl/version.rb +9 -0
  122. data/lib/openhab/dsl.rb +686 -0
  123. data/lib/openhab/log.rb +348 -0
  124. data/lib/openhab/osgi.rb +70 -0
  125. data/lib/openhab/rspec/configuration.rb +56 -0
  126. data/lib/openhab/rspec/example_group.rb +90 -0
  127. data/lib/openhab/rspec/helpers.rb +439 -0
  128. data/lib/openhab/rspec/hooks.rb +93 -0
  129. data/lib/openhab/rspec/jruby.rb +46 -0
  130. data/lib/openhab/rspec/karaf.rb +811 -0
  131. data/lib/openhab/rspec/mocks/bundle_install_support.rb +25 -0
  132. data/lib/openhab/rspec/mocks/bundle_resolver.rb +30 -0
  133. data/lib/openhab/rspec/mocks/event_admin.rb +146 -0
  134. data/lib/openhab/rspec/mocks/metadata_provider.rb +75 -0
  135. data/lib/openhab/rspec/mocks/persistence_service.rb +140 -0
  136. data/lib/openhab/rspec/mocks/safe_caller.rb +40 -0
  137. data/lib/openhab/rspec/mocks/synchronous_executor.rb +56 -0
  138. data/lib/openhab/rspec/mocks/thing_handler.rb +76 -0
  139. data/lib/openhab/rspec/mocks/timer.rb +95 -0
  140. data/lib/openhab/rspec/openhab/core/actions.rb +26 -0
  141. data/lib/openhab/rspec/openhab/core/items/proxy.rb +27 -0
  142. data/lib/openhab/rspec/openhab/core/things/proxy.rb +27 -0
  143. data/lib/openhab/rspec/shell.rb +31 -0
  144. data/lib/openhab/rspec/suspend_rules.rb +60 -0
  145. data/lib/openhab/rspec.rb +17 -0
  146. data/lib/openhab/yard/cli/stats.rb +23 -0
  147. data/lib/openhab/yard/code_objects/group_object.rb +17 -0
  148. data/lib/openhab/yard/code_objects/java/base.rb +31 -0
  149. data/lib/openhab/yard/code_objects/java/class_object.rb +11 -0
  150. data/lib/openhab/yard/code_objects/java/field_object.rb +15 -0
  151. data/lib/openhab/yard/code_objects/java/interface_object.rb +15 -0
  152. data/lib/openhab/yard/code_objects/java/package_object.rb +11 -0
  153. data/lib/openhab/yard/code_objects/java/proxy.rb +23 -0
  154. data/lib/openhab/yard/handlers/jruby/base.rb +49 -0
  155. data/lib/openhab/yard/handlers/jruby/class_handler.rb +18 -0
  156. data/lib/openhab/yard/handlers/jruby/constant_handler.rb +18 -0
  157. data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +27 -0
  158. data/lib/openhab/yard/handlers/jruby/mixin_handler.rb +23 -0
  159. data/lib/openhab/yard/html_helper.rb +44 -0
  160. data/lib/openhab/yard/tags/constant_directive.rb +20 -0
  161. data/lib/openhab/yard/tags/group_directive.rb +24 -0
  162. data/lib/openhab/yard/tags/library.rb +3 -0
  163. data/lib/openhab/yard.rb +32 -0
  164. metadata +504 -0
@@ -0,0 +1,618 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module DSL
5
+ #
6
+ # Contains extensions to simplify working with [GenericItem]s.
7
+ #
8
+ module Items
9
+ # Stores all items created in scripts, and notifies the ItemRegistry
10
+ # of their existence
11
+ # @!visibility private
12
+ class ItemProvider < org.openhab.core.common.registry.AbstractProvider
13
+ include org.openhab.core.items.ItemProvider
14
+ include Singleton
15
+
16
+ def initialize
17
+ super
18
+
19
+ @items = {}
20
+
21
+ $ir.add_provider(self)
22
+ ScriptHandling.script_unloaded { $ir.remove_provider(self) }
23
+ end
24
+
25
+ # Add an item to this provider
26
+ def add(builder)
27
+ item = builder.build
28
+ raise "Item #{item.name} already exists" if @items.key?(item.name)
29
+
30
+ @items[item.name] = item
31
+ notify_listeners_about_added_element(item)
32
+
33
+ # make sure to add the item to the registry before linking it
34
+ builder.channels.each do |(channel, config)|
35
+ if !channel.include?(":") &&
36
+ (group = builder.groups.find { |g| g.is_a?(GroupItemBuilder) && g.thing })
37
+ thing = group.thing
38
+ thing = thing.uid if thing.is_a?(Core::Things::Thing)
39
+ channel = "#{thing}:#{channel}"
40
+ end
41
+ ItemChannelLinkProvider.instance.link(item, channel, config)
42
+ end
43
+
44
+ item
45
+ end
46
+
47
+ # Remove an item from this provider
48
+ #
49
+ # @return [GenericItem, nil] The removed item, if found.
50
+ def remove(item_name, recursive: false)
51
+ return nil unless @items.key?(item_name)
52
+
53
+ item = @items.delete(item_name)
54
+ if recursive && item.is_a?(Core::Items::GroupItem)
55
+ item.members.each { |member| remove(member.__getobj__, recursive: true) }
56
+ end
57
+
58
+ notify_listeners_about_removed_element(item)
59
+ item
60
+ end
61
+
62
+ # Get all items in this provider
63
+ def getAll # rubocop:disable Naming/MethodName required by java interface
64
+ @items.values
65
+ end
66
+ end
67
+
68
+ # @!visibility private
69
+ class ItemChannelLinkProvider < org.openhab.core.common.registry.AbstractProvider
70
+ include org.openhab.core.thing.link.ItemChannelLinkProvider
71
+ include Singleton
72
+
73
+ def initialize
74
+ super
75
+
76
+ @links = Hash.new { |h, k| h[k] = Set.new }
77
+ registry = OSGi.service("org.openhab.core.thing.link.ItemChannelLinkRegistry")
78
+ registry.add_provider(self)
79
+ ScriptHandling.script_unloaded { registry.remove_provider(self) }
80
+ end
81
+
82
+ # @!visibility private
83
+ def link(item, channel, config = {})
84
+ config = org.openhab.core.config.core.Configuration.new(config.transform_keys(&:to_s))
85
+ channel = org.openhab.core.thing.ChannelUID.new(channel) if channel.is_a?(String)
86
+ channel = channel.uid if channel.is_a?(org.openhab.core.thing.Channel)
87
+ link = org.openhab.core.thing.link.ItemChannelLink.new(item.name, channel, config)
88
+
89
+ item_links = @links[item.name]
90
+ if item_links.include?(link)
91
+ notify_listeners_about_updated_element(link, link)
92
+ else
93
+ item_links << link
94
+ notify_listeners_about_added_element(link)
95
+ end
96
+ end
97
+
98
+ def getAll # rubocop:disable Naming/MethodName required by java interface
99
+ @links.values.flatten
100
+ end
101
+ end
102
+
103
+ # An item builder allows you to dynamically create OpenHAB items at runtime.
104
+ # This can be useful either to create items as soon as the script loads,
105
+ # or even later based on a rule executing.
106
+ #
107
+ # @example
108
+ # items.build do
109
+ # switch_item "MySwitch", "My Switch"
110
+ # switch_item "NotAutoupdating", autoupdate: false, channel: "mqtt:topic:1#light"
111
+ # group_item "MyGroup" do
112
+ # contact_item "ItemInGroup", channel: "binding:thing#channel"
113
+ # end
114
+ # # passing `thing` to a group item will automatically use it as the base
115
+ # # for item channels
116
+ # group_item "Equipment", tags: Semantics::HVAC, thing: "binding:thing"
117
+ # string_item "Mode", tags: Semantics::Control, channel: "mode"
118
+ # end
119
+ # end
120
+ module Builder
121
+ include Core::EntityLookup
122
+
123
+ class << self
124
+ private
125
+
126
+ # @!macro def_item_method
127
+ # @!method $1_item(name, label = nil, **kwargs)
128
+ # Create a new $1 item
129
+ # @param name [String] The name for the new item
130
+ # @param label [String] The item label
131
+ # @yieldparam [ItemBuilder] builder Item for further customization
132
+ # @see ItemBuilder#initialize ItemBuilder#initialize for additional arguments.
133
+ def def_item_method(method)
134
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
135
+ def #{method}_item(*args, **kwargs, &block) # def dimmer_item(*args, **kwargs, &block)
136
+ item(#{method.inspect}, *args, **kwargs, &block) # item(:dimmer, *args, **kwargs, &block)
137
+ end
138
+ RUBY
139
+ end
140
+ end
141
+
142
+ # @return [ColorItem]
143
+ def_item_method(:color)
144
+ # @return [ContactItem]
145
+ def_item_method(:contact)
146
+ # @return [DateTimeItem]
147
+ def_item_method(:date_time)
148
+ # @return [DimmerItem]
149
+ def_item_method(:dimmer)
150
+ # @return [ImageItem]
151
+ def_item_method(:image)
152
+ # @return [LocationItem]
153
+ def_item_method(:location)
154
+ # @return [NumberItem]
155
+ def_item_method(:number)
156
+ # @return [PlayerItem]
157
+ def_item_method(:player)
158
+ # @return [RollershutterItem]
159
+ def_item_method(:rollershutter)
160
+ # @return [StringItem]
161
+ def_item_method(:string)
162
+ # @return [SwitchItem]
163
+ def_item_method(:switch)
164
+
165
+ # Create a new {GroupItem}
166
+ #
167
+ # @!method group_item(name, label = nil, **kwargs)
168
+ # @param name [String] The name for the new item
169
+ # @param label [String] The item label
170
+ # @param (see GroupItemBuilder#initialize)
171
+ # @yieldparam [GroupItemBuilder] builder Item for further customization
172
+ # @return [GroupItem]
173
+ def group_item(*args, **kwargs, &block)
174
+ item = GroupItemBuilder.new(*args, provider: provider, **kwargs)
175
+ item.instance_eval(&block) if block
176
+ result = provider.add(item)
177
+ item.members.each do |i|
178
+ provider.add(i)
179
+ end
180
+ result
181
+ end
182
+
183
+ include DSL
184
+
185
+ private
186
+
187
+ def item(*args, **kwargs, &block)
188
+ item = ItemBuilder.new(*args, provider: provider, **kwargs)
189
+ item.instance_eval(&block) if block
190
+ provider.add(item)
191
+ Core::Items::Proxy.new(item)
192
+ end
193
+ end
194
+
195
+ # @!visibility private
196
+ class BaseBuilderDSL
197
+ include Builder
198
+
199
+ private
200
+
201
+ def provider
202
+ ItemProvider.instance
203
+ end
204
+ end
205
+
206
+ # The ItemBuilder DSL allows you to customize an Item
207
+ class ItemBuilder
208
+ # The type of this item
209
+ # @example
210
+ # type #=> :switch
211
+ # @return [Symbol]
212
+ attr_reader :type
213
+ # Item name
214
+ # @return [String]
215
+ attr_accessor :name
216
+ # Item label
217
+ # @return [String, nil]
218
+ attr_accessor :label
219
+ # Unit dimension (for number items only)
220
+ # @return [String, nil]
221
+ attr_accessor :dimension
222
+ # The formatting pattern for the item's state
223
+ # @return [String, nil]
224
+ attr_accessor :format
225
+ # The icon to be associated with the item
226
+ # @return [Symbol, nil]
227
+ attr_accessor :icon
228
+ # Groups to which this item should be added
229
+ # @return [Array<String, GroupItem>]
230
+ attr_reader :groups
231
+ # Tags to apply to this item
232
+ # @return [Array<String, Semantics::Tag>]
233
+ attr_reader :tags
234
+ # Autoupdate setting
235
+ # @return [true, false, nil]
236
+ attr_accessor :autoupdate
237
+ # {Core::Things::ChannelUID Channel} to link the item to
238
+ # @return [String, Core::Things::ChannelUID, nil]
239
+ attr_accessor :channels
240
+ # @return [Core::Items::Metadata::NamespaceHash]
241
+ attr_reader :metadata
242
+ # Initial state
243
+ # @return [Core::Types::State]
244
+ attr_accessor :state
245
+
246
+ class << self
247
+ # @!visibility private
248
+ def item_factory
249
+ @item_factory ||= org.openhab.core.library.CoreItemFactory.new
250
+ end
251
+ end
252
+
253
+ # @param dimension [Symbol, nil] The unit dimension for a {NumberItem} (see {ItemBuilder#dimension})
254
+ # @param format [String, nil] The formatting pattern for the item's state (see {ItemBuilder#format})
255
+ # @param icon [Symbol, nil] The icon to be associated with the item (see {ItemBuilder#icon})
256
+ # @param group [String,
257
+ # GroupItem,
258
+ # GroupItemBuilder,
259
+ # Array<String, GroupItem, GroupItemBuilder>,
260
+ # nil]
261
+ # Group(s) to which this item should be added (see {ItemBuilder#group}).
262
+ # @param groups [String,
263
+ # GroupItem,
264
+ # GroupItemBuilder,
265
+ # Array<String, GroupItem, GroupItemBuilder>,
266
+ # nil]
267
+ # Fluent alias for `group`.
268
+ # @param tag [String, Symbol, Semantics::Tag, Array<String, Symbol, Semantics::Tag>, nil]
269
+ # Tag(s) to apply to this item (see {ItemBuilder#tag}).
270
+ # @param tags [String, Symbol, Semantics::Tag, Array<String, Symbol, Semantics::Tag>, nil]
271
+ # Fluent alias for `tag`.
272
+ # @param autoupdate [true, false, nil] Autoupdate setting (see {ItemBuilder#autoupdate})
273
+ # @param channel [String, Core::Things::ChannelUID, nil] Channel to link the item to
274
+ # @param expire [String] An expiration specification.
275
+ # @param alexa [String, Symbol, Array<(String, Hash<String, Object>)>, nil]
276
+ # Alexa metadata (see {ItemBuilder#alexa})
277
+ # @param ga [String, Symbol, Array<(String, Hash<String, Object>)>, nil]
278
+ # Google Assistant metadata (see {ItemBuilder#ga})
279
+ # @param homekit [String, Symbol, Array<(String, Hash<String, Object>)>, nil]
280
+ # Homekit metadata (see {ItemBuilder#homekit})
281
+ # @param metadata [Hash<String, Hash>] Generic metadata (see {ItemBuilder#metadata})
282
+ # @param state [State] Initial state
283
+ def initialize(type, name = nil, label = nil,
284
+ provider:,
285
+ dimension: nil,
286
+ format: nil,
287
+ icon: nil,
288
+ group: nil,
289
+ groups: nil,
290
+ tag: nil,
291
+ tags: nil,
292
+ autoupdate: nil,
293
+ channel: nil,
294
+ expire: nil,
295
+ alexa: nil,
296
+ ga: nil, # rubocop:disable Naming/MethodParameterName
297
+ homekit: nil,
298
+ metadata: nil,
299
+ state: nil)
300
+ raise ArgumentError, "`name` cannot be nil" if name.nil?
301
+ raise ArgumentError, "`dimension` can only be specified with NumberItem" if dimension && type != :number
302
+
303
+ if provider.is_a?(GroupItemBuilder)
304
+ name = "#{provider.name_base}#{name}"
305
+ label = "#{provider.label_base}#{label}".strip if label
306
+ end
307
+ @provider = provider
308
+ @type = type
309
+ @name = name.to_s
310
+ @label = label
311
+ @dimension = dimension
312
+ @format = format
313
+ @icon = icon
314
+ @groups = []
315
+ @tags = []
316
+ @metadata = Core::Items::Metadata::NamespaceHash.new
317
+ @metadata.merge!(metadata) if metadata
318
+ @autoupdate = autoupdate
319
+ @channels = []
320
+ @expire = nil
321
+ self.expire(*Array(expire)) if expire
322
+ self.alexa(alexa) if alexa
323
+ self.ga(ga) if ga
324
+ self.homekit(homekit) if homekit
325
+ @state = state
326
+
327
+ self.group(*group)
328
+ self.group(*groups)
329
+
330
+ self.tag(*tag)
331
+ self.tag(*tags)
332
+
333
+ self.channel(*channel) if channel
334
+ end
335
+
336
+ #
337
+ # The item's label if one is defined, otherwise its name.
338
+ #
339
+ # @return [String]
340
+ #
341
+ def to_s
342
+ label || name
343
+ end
344
+
345
+ #
346
+ # Tag item
347
+ #
348
+ # @param tags [String, Symbol, Semantics::Tag]
349
+ # @return [void]
350
+ #
351
+ def tag(*tags)
352
+ unless tags.all? do |tag|
353
+ tag.is_a?(String) ||
354
+ tag.is_a?(Symbol) ||
355
+ (tag.is_a?(Module) && tag < Semantics::Tag)
356
+ end
357
+ raise ArgumentError, "`tag` must be a subclass of Semantics::Tag, or a `String``."
358
+ end
359
+
360
+ tags.each do |tag|
361
+ tag = tag.name.split("::").last if tag.is_a?(Module) && tag < Semantics::Tag
362
+ @tags << tag.to_s
363
+ end
364
+ end
365
+
366
+ #
367
+ # Add this item to a group
368
+ #
369
+ # @param groups [String, GroupItemBuilder, GroupItem]
370
+ # @return [void]
371
+ #
372
+ def group(*groups)
373
+ unless groups.all? do |group|
374
+ group.is_a?(String) || group.is_a?(Core::Items::GroupItem) || group.is_a?(GroupItemBuilder)
375
+ end
376
+ raise ArgumentError, "`group` must be a `GroupItem`, `GroupItemBuilder`, or a `String`"
377
+ end
378
+
379
+ @groups.concat(groups)
380
+ end
381
+
382
+ #
383
+ # @!method alexa(value, config = nil)
384
+ # Shortcut for adding Alexa metadata
385
+ #
386
+ # @see https://www.openhab.org/docs/ecosystem/alexa/
387
+ #
388
+ # @param value [String, Symbol] Type of Alexa endpoint
389
+ # @param config [Hash, nil] Additional Alexa configuration
390
+ # @return [void]
391
+ #
392
+
393
+ #
394
+ # @!method ga(value, config = nil)
395
+ # Shortcut for adding Google Assistant metadata
396
+ #
397
+ # @see https://www.openhab.org/docs/ecosystem/google-assistant/
398
+ #
399
+ # @param value [String, Symbol] Type of Google Assistant endpoint
400
+ # @param config [Hash, nil] Additional Google Assistant configuration
401
+ # @return [void]
402
+ #
403
+
404
+ #
405
+ # @!method homekit(value, config = nil)
406
+ # Shortcut for adding Homekit metadata
407
+ #
408
+ # @see https://www.openhab.org/addons/integrations/homekit/
409
+ #
410
+ # @param value [String, Symbol] Type of Homekit accessory or characteristic
411
+ # @param config [Hash, nil] Additional Homekit configuration
412
+ # @return [void]
413
+ #
414
+
415
+ %i[alexa ga homekit].each do |shortcut|
416
+ define_method(shortcut) do |value = nil, config = nil|
417
+ value, config = value if value.is_a?(Array)
418
+ metadata[shortcut] = [value, config]
419
+ end
420
+ end
421
+
422
+ #
423
+ # Add a channel link to this item.
424
+ #
425
+ # @param config [Hash] Additional configuration, such as profile
426
+ # @return [void]
427
+ #
428
+ # @example
429
+ # items.build do
430
+ # date_time_item "Bedroom_Light_Updated" do
431
+ # channel "hue:0210:1:bulb1:color", profile: "system:timestamp-update"
432
+ # end
433
+ # end
434
+ #
435
+ def channel(channel, **config)
436
+ @channels << [channel, config]
437
+ end
438
+
439
+ #
440
+ # @!method expire(command: nil, state: nil)
441
+ #
442
+ # Configure item expiration
443
+ #
444
+ # @return [void]
445
+ #
446
+ # @example Get the current expire setting
447
+ # expire
448
+ # @example Clear any expire setting
449
+ # expire nil
450
+ # @example Use a duration
451
+ # expire 5.hours
452
+ # @example Use a string duration
453
+ # expire "5h"
454
+ # @example Set a specific state on expiration
455
+ # expire 5.minutes, NULL
456
+ # expire 5.minutes, state: NULL
457
+ # @example Send a command on expiration
458
+ # expire 5.minutes, command: OFF
459
+ def expire(*args, command: nil, state: nil)
460
+ unless (0..2).cover?(args.length)
461
+ raise ArgumentError,
462
+ "wrong number of arguments (given #{args.length}, expected 0..2)"
463
+ end
464
+ return @expire if args.empty?
465
+
466
+ state = args.last if args.length == 2
467
+ raise ArgumentError, "cannot provide both command and state" if command && state
468
+
469
+ duration = args.first
470
+ return @expire = nil if duration.nil?
471
+
472
+ duration = duration.to_s[2..].downcase if duration.is_a?(Duration)
473
+ state = "'#{state}'" if state.respond_to?(:to_str) && type == :string
474
+ @expire = duration
475
+ @expire += ",state=#{state}" if state
476
+ @expire += ",command=#{command}" if command
477
+ end
478
+
479
+ # @!visibility private
480
+ def build
481
+ item = create_item
482
+ item.label = label
483
+ item.category = icon.to_s if icon
484
+ groups.each do |group|
485
+ group = group.name if group.respond_to?(:name)
486
+ item.add_group_name(group.to_s)
487
+ end
488
+ tags.each do |tag|
489
+ item.add_tag(tag)
490
+ end
491
+ item.metadata.merge!(metadata)
492
+ item.metadata["autoupdate"] = autoupdate.to_s unless autoupdate.nil?
493
+ item.metadata["expire"] = expire if expire
494
+ item.metadata["stateDescription"] = { "pattern" => format } if format
495
+ item.state = item.format_update(state) unless state.nil?
496
+ item
497
+ end
498
+
499
+ # @return [String]
500
+ def inspect
501
+ s = "#<OpenHAB::Core::Items::#{inspect_type}ItemBuilder#{type_details} #{name} #{label.inspect}"
502
+ s += " category=#{icon.inspect}" if icon
503
+ s += " tags=#{tags.inspect}" unless tags.empty?
504
+ s += " groups=#{groups.map { |g| g.respond_to?(:name) ? g.name : g }.inspect}" unless groups.empty?
505
+ s += " metadata=#{metadata.to_h.inspect}" unless metadata.empty?
506
+ "#{s}>"
507
+ end
508
+
509
+ private
510
+
511
+ # @return [String]
512
+ def inspect_type
513
+ type.to_s.capitalize
514
+ end
515
+
516
+ # @return [String, nil]
517
+ def type_details
518
+ ":#{dimension}" if dimension
519
+ end
520
+
521
+ def create_item
522
+ type = @type.to_s.gsub(/(?:^|_)[a-z]/) { |match| match[-1].upcase }
523
+ type = "#{type}:#{dimension}" if dimension
524
+ self.class.item_factory.create_item(type, name)
525
+ end
526
+ end
527
+
528
+ # Allows customizing a group. You can also call any method from {Builder}, and those
529
+ # items will automatically be a member of this group.
530
+ class GroupItemBuilder < ItemBuilder
531
+ include Builder
532
+
533
+ Builder.public_instance_methods.each do |m|
534
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
535
+ def #{m}(*args, groups: nil, **kwargs) # def dimmer_item(*args, groups: nil, **kwargs)
536
+ groups ||= [] # groups ||= []
537
+ groups << self # groups << self
538
+ super # super
539
+ end # end
540
+ RUBY
541
+ end
542
+
543
+ FUNCTION_REGEX = /^([a-z]+)(?:\(([a-z]+)(?:,([a-z]+))*\))?/i.freeze
544
+ private_constant :FUNCTION_REGEX
545
+
546
+ # The combiner function for this group
547
+ # @return [String, nil]
548
+ attr_accessor :function
549
+ # A thing to be used as the base for the channel of any member items
550
+ # @return [Core::Things::ThingUID, Core::Things::Thing, String, nil]
551
+ attr_accessor :thing
552
+ # A prefix to be added to the name of any member items
553
+ # @return [String, nil]
554
+ attr_accessor :name_base
555
+ # A prefix to be added to the label of any member items
556
+ # @return [String, nil]
557
+ attr_accessor :label_base
558
+ # Members to be created in this group
559
+ # @return [Array<ItemBuilder>]
560
+ attr_reader :members
561
+
562
+ # @param type [Symbol, nil] The base type for the group
563
+ # @param function [String, nil] The combiner function for this group
564
+ # @param thing [Core::Things::ThingUID, Core::Things::Thing, String, nil]
565
+ # A Thing to be used as the base for the channel for any contained items.
566
+ # @param (see ItemBuilder#initialize)
567
+ def initialize(*args, type: nil, function: nil, thing: nil, **kwargs)
568
+ raise ArgumentError, "invalid function #{function}" if function && !function.match?(FUNCTION_REGEX)
569
+ raise ArgumentError, "state cannot be set on GroupItems" if kwargs[:state]
570
+
571
+ super(type, *args, **kwargs)
572
+ @function = function
573
+ @members = []
574
+ @thing = thing
575
+ end
576
+
577
+ # @!visibility private
578
+ def create_item
579
+ base_item = super if type
580
+ if function
581
+ match = function.match(FUNCTION_REGEX)
582
+
583
+ dto = org.openhab.core.items.dto.GroupFunctionDTO.new
584
+ dto.name = match[1]
585
+ dto.params = match[2..]
586
+ function = org.openhab.core.items.dto.ItemDTOMapper.map_function(base_item, dto)
587
+ Core::Items::GroupItem.new(name, base_item, function)
588
+ else
589
+ Core::Items::GroupItem.new(name, base_item)
590
+ end
591
+ end
592
+
593
+ # @!visibility private
594
+ def add(child_item)
595
+ @members << child_item
596
+ end
597
+
598
+ private
599
+
600
+ # @return [String]
601
+ def inspect_type
602
+ "Group"
603
+ end
604
+
605
+ # @return [String, nil]
606
+ def type_details
607
+ r = super
608
+ r = "#{r}:#{function}" if function
609
+ r
610
+ end
611
+
612
+ def provider
613
+ self
614
+ end
615
+ end
616
+ end
617
+ end
618
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module DSL
5
+ module Items
6
+ # Functionality to implement `ensure`/`ensure_states`
7
+ module Ensure
8
+ # Contains the `ensure` method mixed into {GenericItem} and {GroupItem::Members}
9
+ module Ensurable
10
+ # Fluent method call that you can chain commands on to, that will
11
+ # then automatically ensure that the item is not in the command's
12
+ # state before sending the command.
13
+ #
14
+ # @example Turn switch on only if it's not on
15
+ # MySwitch.ensure.on
16
+ # @example Turn on all switches in a group that aren't already on
17
+ # MySwitchGroup.members.ensure.on
18
+ def ensure
19
+ GenericItemDelegate.new(self)
20
+ end
21
+ end
22
+
23
+ # Extensions for {::GenericItem} to implement {Ensure}'s functionality
24
+ module GenericItem
25
+ include Ensurable
26
+
27
+ Core::Items::GenericItem.prepend(self)
28
+
29
+ # If `ensure_states` is active (by block or chained method), then
30
+ # check if this item is in the command's state before actually
31
+ # sending the command
32
+ %i[command update].each do |ensured_method|
33
+ # def command(state)
34
+ # return super(state) unless Thread.current[:openhab_ensure_states]
35
+ #
36
+ # logger.trace do
37
+ # "#{name} ensure #{state}, format_command: #{format_command(state)}, current state: #{self.state}"
38
+ # end
39
+ # return if self.state == format_command(state)
40
+ #
41
+ # super(state)
42
+ # end
43
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
44
+ def #{ensured_method}(state)
45
+ return super(state) unless Thread.current[:openhab_ensure_states]
46
+
47
+ logger.trace do
48
+ "\#{name} ensure \#{state}, format_#{ensured_method}: \#{format_#{ensured_method}(state)}, current state: \#{self.state}"
49
+ end
50
+ return if self.state == format_#{ensured_method}(state)
51
+
52
+ super(state)
53
+ end
54
+ RUBY
55
+ end
56
+ end
57
+
58
+ # "anonymous" class that wraps any method call in `ensure_states`
59
+ # before forwarding to the wrapped object
60
+ # @!visibility private
61
+ class GenericItemDelegate
62
+ def initialize(item)
63
+ @item = item
64
+ end
65
+
66
+ # @!visibility private
67
+ # this is explicitly defined, instead of aliased, because #command
68
+ # doesn't actually exist as a method, and will go through method_missing
69
+ def <<(command)
70
+ command(command)
71
+ end
72
+
73
+ # activate `ensure_states` before forwarding to the wrapped object
74
+ def method_missing(method, *args, &block)
75
+ return super unless @item.respond_to?(method)
76
+
77
+ DSL.ensure_states do
78
+ @item.__send__(method, *args, &block)
79
+ end
80
+ end
81
+ ruby2_keywords :method_missing if respond_to? :ruby2_keywords
82
+
83
+ # .
84
+ def respond_to_missing?(method, include_private = false)
85
+ @item.respond_to?(method, include_private) || super
86
+ end
87
+ end
88
+ end
89
+
90
+ Core::Items::GroupItem::Members.include(Ensure::Ensurable)
91
+ end
92
+ end
93
+ end