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,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"