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,686 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "java"
4
+ require "method_source"
5
+
6
+ require "bundler/inline"
7
+
8
+ require_relative "log"
9
+ require_relative "osgi"
10
+ require_relative "core"
11
+
12
+ Dir[File.expand_path("dsl/**/*.rb", __dir__)].sort.each do |f|
13
+ require f
14
+ end
15
+
16
+ require_relative "core_ext"
17
+
18
+ #
19
+ # Main OpenHAB Module
20
+ #
21
+ module OpenHAB
22
+ #
23
+ # The main DSL available to rules.
24
+ #
25
+ # Methods on this module are extended onto `main`, the top level `self` in
26
+ # any file. You can also access them as class methods on the module for use
27
+ # inside of other classes, or include the module.
28
+ #
29
+ module DSL
30
+ # include this before Core::Actions so that Core::Action's method_missing
31
+ # takes priority
32
+ include Core::EntityLookup
33
+ [Core::Actions, Rules::Terse, ScriptHandling].each do |mod|
34
+ # make these available both as regular and class methods
35
+ include mod
36
+ singleton_class.include mod
37
+ public_class_method(*mod.private_instance_methods)
38
+ end
39
+
40
+ module_function
41
+
42
+ # @!group Rule Creation
43
+
44
+ #
45
+ # Create a new rule
46
+ #
47
+ # @param [String] name The rule name
48
+ # @yield Block executed in the context of a {Rules::Builder}
49
+ # @yieldparam [Rules::Builder] rule
50
+ # Optional parameter to access the rule configuration from within execution blocks and guards.
51
+ # @return [org.openhab.core.automation.Rule] The OpenHAB Rule object
52
+ #
53
+ # @see OpenHAB::DSL::Rules::Builder Rule builder for details on rule triggers, guards and execution blocks
54
+ # @see Rules::Terse Terse Rules
55
+ #
56
+ # @example
57
+ # require "openhab/dsl"
58
+ #
59
+ # rule "name" do
60
+ # <zero or more triggers>
61
+ # <zero or more execution blocks>
62
+ # <zero or more guards>
63
+ # end
64
+ #
65
+ def rule(name = nil, id: nil, script: nil, binding: nil, &block)
66
+ raise ArgumentError, "Block is required" unless block
67
+
68
+ id ||= Rules::NameInference.infer_rule_id_from_name(name) if name
69
+ id ||= Rules::NameInference.infer_rule_id_from_block(block)
70
+ script ||= block.source rescue nil # rubocop:disable Style/RescueModifier
71
+
72
+ builder = nil
73
+
74
+ ThreadLocal.thread_local(openhab_rule_type: "rule", openhab_rule_uid: id) do
75
+ builder = Rules::Builder.new(binding || block.binding)
76
+ builder.uid(id)
77
+ builder.instance_exec(builder, &block)
78
+ builder.guard = Rules::Guard.new(run_context: builder.caller, only_if: builder.only_if,
79
+ not_if: builder.not_if)
80
+
81
+ name ||= Rules::NameInference.infer_rule_name(builder)
82
+ name ||= id
83
+
84
+ builder.name(name)
85
+ logger.trace { builder.inspect }
86
+ builder.build(script)
87
+ rescue Exception => e
88
+ if (defined?(::RSpec::Expectations::ExpectationNotMetError) &&
89
+ e.is_a?(::RSpec::Expectations::ExpectationNotMetError)) ||
90
+ (defined?(::RSpec::Mocks::MockExpectationError) && e.is_a?(::RSpec::Mocks::MockExpectationError))
91
+ raise e
92
+ end
93
+
94
+ builder.send(:logger).log_exception(e)
95
+ end
96
+ end
97
+
98
+ #
99
+ # Create a new script
100
+ #
101
+ # A script is a rule with no triggers. It can be called by various other actions,
102
+ # such as the Run Rules action.
103
+ #
104
+ # @param [String] id The script's ID
105
+ # @param [String] name A descriptive name
106
+ # @yield [] Block executed when the script is executed.
107
+ #
108
+ def script(name = nil, id: nil, script: nil, &block)
109
+ raise ArgumentError, "Block is required" unless block
110
+
111
+ id ||= Rules::NameInference.infer_rule_id_from_name(name) if name
112
+ id ||= Rules::NameInference.infer_rule_id_from_block(block)
113
+ name ||= id
114
+ script ||= block.source rescue nil # rubocop:disable Style/RescueModifier
115
+
116
+ builder = nil
117
+ ThreadLocal.thread_local(openhab_rule_type: "script", openhab_rule_uid: id) do
118
+ begin
119
+ builder = Rules::Builder.new(block.binding)
120
+ builder.uid(id)
121
+ builder.tags(["Script"])
122
+ builder.name(name)
123
+ builder.script(&block)
124
+ logger.trace { builder.inspect }
125
+ builder.build(script)
126
+ end
127
+ rescue Exception => e
128
+ builder.send(:logger).log_exception(e)
129
+ end
130
+ end
131
+
132
+ # @!group Rule Support
133
+
134
+ #
135
+ # Defines a new profile that can be applied to item channel links.
136
+ #
137
+ # @param [String, Symbol] id The id for the profile.
138
+ # @yield [event, command: nil, state: nil, link:, item:, channel_uid:, configuration:, context:]
139
+ # All keyword params are optional. Any that aren't defined won't be passed.
140
+ # @yieldparam [Core::Things::ProfileCallback] callback
141
+ # The callback to be used to customize the action taken.
142
+ # @yieldparam [:command_from_item, :state_from_item, :command_from_handler, :state_from_handler] event
143
+ # The event that needs to be processed.
144
+ # @yieldparam [Command, nil] command
145
+ # The command being sent for `:command_from_item` and `:command_from_handler` events.
146
+ # @yieldparam [State, nil] state
147
+ # The state being sent for `:state_from_item` and `:state_from_handler` events.
148
+ # @yieldparam [Core::Things::ItemChannelLink] link
149
+ # The link between the item and the channel, including its configuration.
150
+ # @yieldparam [GenericItem] item The linked item.
151
+ # @yieldparam [Core::Things::ChannelUID] channel_uid The linked channel.
152
+ # @yieldparam [Hash] configuration The profile configuration.
153
+ # @yieldparam [org.openhab.core.thing.profiles.ProfileContext] context The profile context.
154
+ # @yieldreturn [Boolean] Return true from the block in order to have default processing.
155
+ # @return [void]
156
+ #
157
+ # @see org.openhab.thing.Profile
158
+ # @see org.openhab.thing.StateProfile
159
+ #
160
+ # @example
161
+ # profile(:veto_closing_shades) do |event, item:, command: nil|
162
+ # next false if command&.down?
163
+ #
164
+ # true
165
+ # end
166
+ #
167
+ # items.build do
168
+ # rollershutter_item "MyShade" do
169
+ # channel "thing:rollershutter", profile: "ruby:veto_closing_shades"
170
+ # end
171
+ # end
172
+ # # can also be referenced from an `.items` file:
173
+ # # Rollershutter MyShade { channel="thing:rollershutter"[profile="ruby:veto_closing_shades"] }
174
+ #
175
+ def profile(id, &block)
176
+ raise ArgumentError, "Block is required" unless block
177
+
178
+ uid = org.openhab.core.thing.profiles.ProfileTypeUID.new("ruby", id)
179
+
180
+ ThreadLocal.thread_local(openhab_rule_type: "profile", openhab_rule_uid: id) do
181
+ Core::ProfileFactory.instance.register(uid, block)
182
+ end
183
+ end
184
+
185
+ #
186
+ # Remove a rule
187
+ #
188
+ # @param [String, org.openhab.core.automation.Rule] uid The rule UID or the Rule object to remove.
189
+ # @return [void]
190
+ #
191
+ # @example
192
+ # my_rule = rule do
193
+ # every :day
194
+ # run { nil }
195
+ # end
196
+ #
197
+ # remove_rule(my_rule)
198
+ #
199
+ def remove_rule(uid)
200
+ uid = uid.uid if uid.respond_to?(:uid)
201
+ automation_rule = Rules.script_rules.delete(uid)
202
+ raise "Rule #{uid} doesn't exist to remove" unless automation_rule
203
+
204
+ automation_rule.cleanup
205
+ # automation_manager doesn't have a remove method, so just have to
206
+ # remove it directly from the provider
207
+ Rules.scripted_rule_provider.remove_rule(uid)
208
+ end
209
+
210
+ #
211
+ # Manually trigger a rule by ID
212
+ #
213
+ # @param [String] uid The rule ID
214
+ # @param [Object, nil] event The event to pass to the rule's execution blocks.
215
+ # @return [void]
216
+ #
217
+ def trigger_rule(uid, event = nil)
218
+ Rules.script_rules.fetch(uid).execute(nil, { "event" => event })
219
+ end
220
+
221
+ # @!group Object Access
222
+
223
+ #
224
+ # Fetches all items from the item registry
225
+ #
226
+ # @return [Core::Items::Registry]
227
+ #
228
+ # The examples all assume the following items exist.
229
+ #
230
+ # ```xtend
231
+ # Dimmer DimmerTest "Test Dimmer"
232
+ # Switch SwitchTest "Test Switch"
233
+ # ```
234
+ #
235
+ # @example
236
+ # logger.info("Item Count: #{items.count}") # Item Count: 2
237
+ # logger.info("Items: #{items.map(&:label).sort.join(', ')}") # Items: Test Dimmer, Test Switch'
238
+ # logger.info("DimmerTest exists? #{items.key?('DimmerTest')}") # DimmerTest exists? true
239
+ # logger.info("StringTest exists? #{items.key?('StringTest')}") # StringTest exists? false
240
+ #
241
+ # @example
242
+ # rule 'Use dynamic item lookup to increase related dimmer brightness when switch is turned on' do
243
+ # changed SwitchTest, to: ON
244
+ # triggered { |item| items[item.name.gsub('Switch','Dimmer')].brighten(10) }
245
+ # end
246
+ #
247
+ # @example
248
+ # rule 'search for a suitable item' do
249
+ # on_start
250
+ # triggered do
251
+ # # Send ON to DimmerTest if it exists, otherwise send it to SwitchTest
252
+ # (items['DimmerTest'] || items['SwitchTest'])&.on
253
+ # end
254
+ # end
255
+ #
256
+ def items
257
+ Core::Items::Registry.instance
258
+ end
259
+
260
+ #
261
+ # Get all things known to OpenHAB
262
+ #
263
+ # @return [Core::Things::Registry] all Thing objects known to OpenHAB
264
+ #
265
+ # @example
266
+ # things.each { |thing| logger.info("Thing: #{thing.uid}")}
267
+ # logger.info("Thing: #{things['astro:sun:home'].uid}")
268
+ # homie_things = things.select { |t| t.thing_type_uid == "mqtt:homie300" }
269
+ # zwave_things = things.select { |t| t.binding_id == "zwave" }
270
+ # homeseer_dimmers = zwave_things.select { |t| t.thing_type_uid.id == "homeseer_hswd200_00_000" }
271
+ # things['zwave:device:512:node90'].uid.bridge_ids # => ["512"]
272
+ # things['mqtt:topic:4'].uid.bridge_ids # => []
273
+ #
274
+ def things
275
+ Core::Things::Registry.instance
276
+ end
277
+
278
+ #
279
+ # Provides access to timers created by {after}
280
+ #
281
+ # @return [TimerManager]
282
+ def timers
283
+ TimerManager.instance
284
+ end
285
+
286
+ # @!group Utilities
287
+
288
+ #
289
+ # Create a timer and execute the supplied block after the specified duration
290
+ #
291
+ # ### Reentrant Timers
292
+ #
293
+ # Timers with an id are reentrant by id. Reentrant means that when the same id is encountered,
294
+ # the timer is rescheduled rather than creating a second new timer.
295
+ #
296
+ # This removes the need for the usual boilerplate code to manually keep track of timer objects.
297
+ #
298
+ # Timers with `id` can be managed with the built-in {timers} object.
299
+ #
300
+ # When a timer is cancelled, it will be removed from the object.
301
+ #
302
+ # Be sure that your ids are unique. For example, if you're using {GenericItem items} as your
303
+ # ids, you either need to be sure you don't use the same item for multiple logical contexts,
304
+ # or you need to make your id more specific, by doing something like embedding the item in
305
+ # array with a symbol of the timer's purpose, like `[:vacancy, item]`. But also note that
306
+ # assuming default settings, every Ruby file (for file-based rules) or UI rule gets its
307
+ # own instance of the timers object, so you don't need to worry about collisions among
308
+ # different files.
309
+ #
310
+ # @see timers
311
+ # @see Rules::Builder#changed
312
+ # @see Items::TimedCommand
313
+ #
314
+ # @param [java.time.temporal.TemporalAmount, #to_zoned_date_time, Proc] duration
315
+ # Duration after which to execute the block
316
+ # @param [Object] id ID to associate with timer. The timer can be managed via {timers}.
317
+ # @param [true,false] reschedule Reschedule the timer if it already exists.
318
+ # @yield Block to execute when the timer is elapsed.
319
+ # @yieldparam [Core::Timer] timer
320
+ #
321
+ # @return [Core::Timer] if `reschedule` is false, the existing timer.
322
+ # Otherwise the new timer.
323
+ #
324
+ # @example Create a simple timer
325
+ # after 5.seconds do
326
+ # logger.info("Timer Fired")
327
+ # end
328
+ #
329
+ # @example Timers delegate methods to OpenHAB timer objects
330
+ # after 1.second do |timer|
331
+ # logger.info("Timer is active? #{timer.active?}")
332
+ # end
333
+ #
334
+ # @example Timers can be rescheduled to run again, waiting the original duration
335
+ # after 3.seconds do |timer|
336
+ # logger.info("Timer Fired")
337
+ # timer.reschedule
338
+ # end
339
+ #
340
+ # @example Timers can be rescheduled for different durations
341
+ # after 3.seconds do |timer|
342
+ # logger.info("Timer Fired")
343
+ # timer.reschedule 5.seconds
344
+ # end
345
+ #
346
+ # @example Timers can be manipulated through the returned object
347
+ # mytimer = after 1.minute do
348
+ # logger.info("It has been 1 minute")
349
+ # end
350
+ #
351
+ # mytimer.cancel
352
+ #
353
+ # @example Reentrant timers will automatically reschedule if the same id is encountered again
354
+ # rule "Turn off closet light after 10 minutes" do
355
+ # changed ClosetLights.members, to: ON
356
+ # triggered do |item|
357
+ # after 10.minutes, id: item do
358
+ # item.ensure.off
359
+ # end
360
+ # end
361
+ # end
362
+ #
363
+ # @example Timers with id can be managed through the built-in `timers` object
364
+ # after 1.minute, id: :foo do
365
+ # logger.info("managed timer has fired")
366
+ # end
367
+ #
368
+ # timers.cancel(:foo)
369
+ #
370
+ # if timers.include?(:foo)
371
+ # logger.info("The timer :foo is not active")
372
+ # end
373
+ #
374
+ # @example Only create a new timer if it isn't already scheduled
375
+ # after(1.minute, id: :foo, reschedule: false) do
376
+ # logger.info("Timer fired")
377
+ # end
378
+ #
379
+ def after(duration, id: nil, reschedule: true, &block)
380
+ raise ArgumentError, "Block is required" unless block
381
+
382
+ # Carry rule name to timer
383
+ thread_locals = ThreadLocal.persist
384
+ timers.create(duration, id: id, reschedule: reschedule, thread_locals: thread_locals, block: block)
385
+ end
386
+
387
+ #
388
+ # Convert a string based range into a range of LocalTime, LocalDate, MonthDay, or ZonedDateTime
389
+ # depending on the format of the string.
390
+ #
391
+ # @return [Range] converted range object
392
+ #
393
+ # @example Range#cover?
394
+ # logger.info("Within month-day range") if between('02-20'..'06-01').cover?(MonthDay.now)
395
+ #
396
+ # @example Use in a Case
397
+ # case MonthDay.now
398
+ # when between('01-01'..'03-31')
399
+ # logger.info("First quarter")
400
+ # when between('04-01'..'06-30')
401
+ # logger.info("Second quarter")
402
+ # end
403
+ #
404
+ # @example Create a time range
405
+ # between('7am'..'12pm').cover?(LocalTime.now)
406
+ #
407
+ def between(range)
408
+ raise ArgumentError, "Supplied object must be a range" unless range.is_a?(Range)
409
+
410
+ start = try_parse_time_like(range.begin)
411
+ finish = try_parse_time_like(range.end)
412
+ Range.new(start, finish, range.exclude_end?)
413
+ end
414
+
415
+ #
416
+ # Store states of supplied items
417
+ #
418
+ # Takes one or more items and returns a map `{Item => State}` with the
419
+ # current state of each item. It is implemented by calling OpenHAB's
420
+ # [events.storeStates()](https://www.openhab.org/docs/configuration/actions.html#event-bus-actions).
421
+ #
422
+ # @param [GenericItem] items Items to store states of.
423
+ #
424
+ # @return [Core::Items::StateStorage] item states
425
+ #
426
+ # @example
427
+ # states = store_states Item1, Item2
428
+ # ...
429
+ # states.restore
430
+ #
431
+ # @example With a block
432
+ # store_states Item1, Item2 do
433
+ # ...
434
+ # end # the states will be restored here
435
+ #
436
+ def store_states(*items)
437
+ items = items.flatten.map do |item|
438
+ item.respond_to?(:__getobj__) ? item.__getobj__ : item
439
+ end
440
+ states = Core::Items::StateStorage.from_items(*items)
441
+ if block_given?
442
+ yield
443
+ states.restore
444
+ end
445
+ states
446
+ end
447
+
448
+ #
449
+ # @!group Block Modifiers
450
+ # These methods allow certain operations to be grouped inside the given block
451
+ # to reduce repetitions
452
+ #
453
+
454
+ #
455
+ # Global method that takes a block and for the duration of the block
456
+ # all commands sent will check if the item is in the command's state
457
+ # before sending the command.
458
+ #
459
+ # @yield
460
+ # @return [Object] The result of the block.
461
+ #
462
+ # @example Turn on several switches only if they're not already on
463
+ # ensure_states do
464
+ # Switch1.on
465
+ # Switch2.on
466
+ # end
467
+ #
468
+ # @example
469
+ # # VirtualSwitch is in state `ON`
470
+ # ensure_states do
471
+ # VirtualSwitch << ON # No command will be sent
472
+ # VirtualSwitch.update(ON) # No update will be posted
473
+ # VirtualSwitch << OFF # Off command will be sent
474
+ # VirtualSwitch.update(OFF) # No update will be posted
475
+ # end
476
+ #
477
+ # @example
478
+ # ensure_states do
479
+ # rule 'Items in an execution block will have ensure_states applied to them' do
480
+ # changed VirtualSwitch
481
+ # run do
482
+ # VirtualSwitch.on
483
+ # VirtualSwitch2.on
484
+ # end
485
+ # end
486
+ # end
487
+ #
488
+ # @example
489
+ # rule 'ensure_states must be in an execution block' do
490
+ # changed VirtualSwitch
491
+ # run do
492
+ # ensure_states do
493
+ # VirtualSwitch.on
494
+ # VirtualSwitch2.on
495
+ # end
496
+ # end
497
+ # end
498
+ #
499
+ def ensure_states
500
+ old = Thread.current[:openhab_ensure_states]
501
+ Thread.current[:openhab_ensure_states] = true
502
+ yield
503
+ ensure
504
+ Thread.current[:openhab_ensure_states] = old
505
+ end
506
+
507
+ #
508
+ # Sets a thread local variable to set the default persistence service
509
+ # for method calls inside the block
510
+ #
511
+ # @example
512
+ # persistence(:influxdb) do
513
+ # Item1.persist
514
+ # Item1.changed_since(1.hour)
515
+ # Item1.average_since(12.hours)
516
+ # end
517
+ #
518
+ # @see OpenHAB::Core::Items::Persistence
519
+ #
520
+ # @param [Object] service service either as a String or a Symbol
521
+ # @yield [] Block executed in context of the supplied persistence service
522
+ # @return [Object] The return value from the block.
523
+ #
524
+ def persistence(service)
525
+ old = Thread.current[:openhab_persistence_service]
526
+ Thread.current[:openhab_persistence_service] = service
527
+ yield
528
+ ensure
529
+ Thread.current[:openhab_persistence_service] = old
530
+ end
531
+
532
+ #
533
+ # Sets the implicit unit(s) for operations inside the block.
534
+ #
535
+ # @yield
536
+ #
537
+ # @overload unit(*units)
538
+ # Sets the implicit unit(s) for this thread such that classes
539
+ # operating inside the block can perform automatic conversions to the
540
+ # supplied unit for {QuantityType}.
541
+ #
542
+ # To facilitate conversion of multiple dimensioned and dimensionless
543
+ # numbers the unit block may be used. The unit block attempts to do the
544
+ # _right thing_ based on the mix of dimensioned and dimensionless items
545
+ # within the block. Specifically all dimensionless items are converted to
546
+ # the supplied unit, except when they are used for multiplication or
547
+ # division.
548
+ #
549
+ # @param [String, javax.measure.Unit] units
550
+ # Unit or String representing unit
551
+ # @yield [] The block will be executed in the context of the specified unit(s).
552
+ # @return [Object] the result of the block
553
+ #
554
+ # @example Arithmetic Operations Between QuantityType and Numeric
555
+ # # Number:Temperature NumberC = 23 °C
556
+ # # Number:Temperature NumberF = 70 °F
557
+ # # Number Dimensionless = 2
558
+ # unit('°F') { NumberC.state - NumberF.state < 4 } # => true
559
+ # unit('°F') { NumberC.state - 24 | '°C' < 4 } # => true
560
+ # unit('°F') { (24 | '°C') - NumberC.state < 4 } # => true
561
+ # unit('°C') { NumberF.state - 20 < 2 } # => true
562
+ # unit('°C') { NumberF.state - Dimensionless.state } # => 19.11 °C
563
+ # unit('°C') { NumberF.state - Dimensionless.state < 20 } # => true
564
+ # unit('°C') { Dimensionless.state + NumberC.state == 25 } # => true
565
+ # unit('°C') { 2 + NumberC.state == 25 } # => true
566
+ # unit('°C') { Dimensionless.state * NumberC.state == 46 } # => true
567
+ # unit('°C') { 2 * NumberC.state == 46 } # => true
568
+ # unit('°C') { ( (2 * (NumberF.state + NumberC.state) ) / Dimensionless.state ) < 45 } # => true
569
+ # unit('°C') { [NumberC.state, NumberF.state, Dimensionless.state].min } # => 2
570
+ #
571
+ # @example Commands and Updates inside a unit block
572
+ # unit('°F') { NumberC << 32 }; NumberC.state # => 0 °C
573
+ # # Equivalent to
574
+ # NumberC << "32 °F"
575
+ # # or
576
+ # NumberC << 32 | "°F"
577
+ #
578
+ # @example Specifying Multiple Units
579
+ # unit("°C", "kW") do
580
+ # TemperatureItem.update("50 °F")
581
+ # TemperatureItem.state < 20 # => true. TemperatureItem.state < 20 °C
582
+ # PowerUsage.update("3000 W")
583
+ # PowerUsage.state < 10 # => true. PowerUsage.state < 10 kW
584
+ # end
585
+ #
586
+ # @overload unit(dimension)
587
+ # @param [javax.measure.Dimension] dimension The dimension to fetch the unit for.
588
+ # @return [javax.measure.Unit] The current unit for the thread of the specified dimensions
589
+ #
590
+ # @example
591
+ # unit(SIUnits::METRE.dimension) # => ImperialUnits::FOOT
592
+ #
593
+ def unit(*units)
594
+ if units.length == 1 && units.first.is_a?(javax.measure.Dimension)
595
+ return Thread.current[:openhab_units]&.[](units.first)
596
+ end
597
+
598
+ raise ArgumentError, "You must give a block to set the unit for the duration of" unless block_given?
599
+
600
+ begin
601
+ old_units = unit!(*units)
602
+ yield
603
+ ensure
604
+ Thread.current[:openhab_units] = old_units
605
+ end
606
+ end
607
+
608
+ #
609
+ # Permanently sets the implicit unit(s) for this thread
610
+ #
611
+ # @note This method is only intended for use at the top level of rule
612
+ # scripts. If it's used within library methods, or hap-hazardly within
613
+ # rules, things can get very confusing because the prior state won't be
614
+ # properly restored.
615
+ #
616
+ # {unit!} calls are cumulative - additional calls will not erase the effects
617
+ # previous calls unless they are for the same dimension.
618
+ #
619
+ # @return [Hash<javax.measure.Dimension=>javax.measure.Unit>]
620
+ # the prior unit configuration
621
+ #
622
+ # @overload unit!(*units)
623
+ # @param [String, javax.measure.Unit] units
624
+ # Unit or String representing unit.
625
+ #
626
+ # @example Set several defaults at once
627
+ # unit!("°F", "ft", "lbs")
628
+ # (50 | "°F") == 50 # => true
629
+ #
630
+ # @example Calls are cumulative
631
+ # unit!("°F")
632
+ # unit!("ft")
633
+ # (50 | "°F") == 50 # => true
634
+ # (2 | "yd") == 6 # => true
635
+ #
636
+ # @example Subsequent calls override the same dimension from previous calls
637
+ # unit!("yd")
638
+ # unit!("ft")
639
+ # (2 | "yd") == 6 # => true
640
+ #
641
+ # @overload unit!
642
+ # Clear all unit settings
643
+ #
644
+ # @example Clear all unit settings
645
+ # unit!("ft")
646
+ # unit!
647
+ # (2 | "yd") == 6 # => false
648
+ #
649
+ def unit!(*units)
650
+ units = units.each_with_object({}) do |unit, r|
651
+ unit = org.openhab.core.types.util.UnitUtils.parse_unit(unit) if unit.is_a?(String)
652
+ r[unit.dimension] = unit
653
+ end
654
+
655
+ old_units = Thread.current[:openhab_units] || {}
656
+ Thread.current[:openhab_units] = units.empty? ? {} : old_units.merge(units)
657
+ old_units
658
+ end
659
+
660
+ # @!visibility private
661
+ def try_parse_time_like(string)
662
+ return string unless string.is_a?(String)
663
+
664
+ exception = nil
665
+ [java.time.LocalTime, java.time.LocalDate, java.time.MonthDay, java.time.ZonedDateTime].each do |klass|
666
+ return klass.parse(string)
667
+ rescue ArgumentError => e
668
+ exception ||= e
669
+ next
670
+ end
671
+
672
+ raise exception
673
+ end
674
+ end
675
+ end
676
+
677
+ OpenHAB::Core.wait_till_openhab_ready
678
+ OpenHAB::Core.add_rubylib_to_load_path
679
+
680
+ # import Items classes into global namespace
681
+ OpenHAB::Core::Items.import_into_global_namespace
682
+
683
+ # Extend `main` with DSL methods
684
+ singleton_class.include(OpenHAB::DSL)
685
+
686
+ logger.debug "OpenHAB JRuby Scripting Library Version #{OpenHAB::DSL::VERSION} Loaded"