openhab-jrubyscripting 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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