openhab-scripting 4.46.2 → 5.0.0

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 (281) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openhab/core/actions/audio.rb +47 -0
  3. data/lib/openhab/core/actions/ephemeris.rb +39 -0
  4. data/lib/openhab/core/actions/exec.rb +51 -0
  5. data/lib/openhab/core/actions/http.rb +80 -0
  6. data/lib/openhab/core/actions/ping.rb +30 -0
  7. data/lib/openhab/core/actions/transformation.rb +32 -0
  8. data/lib/openhab/core/actions/voice.rb +36 -0
  9. data/lib/openhab/core/actions.rb +82 -0
  10. data/lib/openhab/core/dependency_tracking.rb +34 -0
  11. data/lib/openhab/core/dto/item_channel_link.rb +33 -0
  12. data/lib/openhab/core/dto/thing.rb +27 -0
  13. data/lib/openhab/core/dto.rb +11 -0
  14. data/lib/openhab/core/entity_lookup.rb +152 -70
  15. data/lib/openhab/core/events/abstract_event.rb +18 -0
  16. data/lib/openhab/core/events/abstract_item_registry_event.rb +36 -0
  17. data/lib/openhab/core/events/abstract_thing_registry_event.rb +40 -0
  18. data/lib/openhab/core/events/item_command_event.rb +78 -0
  19. data/lib/openhab/core/events/item_event.rb +22 -0
  20. data/lib/openhab/core/events/item_state_changed_event.rb +75 -0
  21. data/lib/openhab/core/events/item_state_event.rb +79 -0
  22. data/lib/openhab/core/events/thing_status_info_event.rb +55 -0
  23. data/lib/openhab/core/events.rb +10 -0
  24. data/lib/openhab/core/items/accepted_data_types.rb +29 -0
  25. data/lib/openhab/core/items/color_item.rb +52 -0
  26. data/lib/openhab/core/items/contact_item.rb +52 -0
  27. data/lib/openhab/core/items/date_time_item.rb +59 -0
  28. data/lib/openhab/core/items/dimmer_item.rb +148 -0
  29. data/lib/openhab/core/items/generic_item.rb +292 -0
  30. data/lib/openhab/core/items/group_item.rb +176 -0
  31. data/lib/openhab/{dsl → core}/items/image_item.rb +35 -29
  32. data/lib/openhab/core/items/item.rb +273 -0
  33. data/lib/openhab/core/items/location_item.rb +34 -0
  34. data/lib/openhab/core/items/metadata/hash.rb +433 -0
  35. data/lib/openhab/core/items/metadata/namespace_hash.rb +475 -0
  36. data/lib/openhab/core/items/metadata/provider.rb +48 -0
  37. data/lib/openhab/core/items/metadata.rb +11 -0
  38. data/lib/openhab/core/items/number_item.rb +62 -0
  39. data/lib/openhab/core/items/numeric_item.rb +22 -0
  40. data/lib/openhab/core/items/persistence.rb +416 -0
  41. data/lib/openhab/core/items/player_item.rb +66 -0
  42. data/lib/openhab/core/items/provider.rb +44 -0
  43. data/lib/openhab/core/items/proxy.rb +136 -0
  44. data/lib/openhab/core/items/registry.rb +86 -0
  45. data/lib/openhab/core/items/rollershutter_item.rb +68 -0
  46. data/lib/openhab/core/items/semantics/enumerable.rb +177 -0
  47. data/lib/openhab/core/items/semantics.rb +473 -0
  48. data/lib/openhab/core/items/state_storage.rb +53 -0
  49. data/lib/openhab/core/items/string_item.rb +28 -0
  50. data/lib/openhab/core/items/switch_item.rb +78 -0
  51. data/lib/openhab/core/items.rb +108 -0
  52. data/lib/openhab/{dsl → core}/lazy_array.rb +9 -3
  53. data/lib/openhab/core/profile_factory.rb +132 -0
  54. data/lib/openhab/core/provider.rb +230 -0
  55. data/lib/openhab/core/proxy.rb +130 -0
  56. data/lib/openhab/core/registry.rb +40 -0
  57. data/lib/openhab/core/rules/module.rb +26 -0
  58. data/lib/openhab/core/rules/provider.rb +25 -0
  59. data/lib/openhab/core/rules/registry.rb +76 -0
  60. data/lib/openhab/core/rules/rule.rb +150 -0
  61. data/lib/openhab/core/rules.rb +25 -0
  62. data/lib/openhab/core/script_handling.rb +78 -20
  63. data/lib/openhab/core/things/channel.rb +48 -0
  64. data/lib/openhab/core/things/channel_uid.rb +51 -0
  65. data/lib/openhab/core/things/item_channel_link.rb +33 -0
  66. data/lib/openhab/core/things/links/provider.rb +78 -0
  67. data/lib/openhab/core/things/profile_callback.rb +52 -0
  68. data/lib/openhab/core/things/provider.rb +29 -0
  69. data/lib/openhab/core/things/proxy.rb +87 -0
  70. data/lib/openhab/core/things/registry.rb +73 -0
  71. data/lib/openhab/core/things/thing.rb +194 -0
  72. data/lib/openhab/core/things.rb +22 -0
  73. data/lib/openhab/core/timer.rb +148 -0
  74. data/lib/openhab/{dsl → core}/types/comparable_type.rb +5 -3
  75. data/lib/openhab/{dsl → core}/types/date_time_type.rb +55 -127
  76. data/lib/openhab/{dsl → core}/types/decimal_type.rb +50 -48
  77. data/lib/openhab/{dsl → core}/types/hsb_type.rb +35 -83
  78. data/lib/openhab/core/types/increase_decrease_type.rb +34 -0
  79. data/lib/openhab/core/types/next_previous_type.rb +34 -0
  80. data/lib/openhab/{dsl → core}/types/numeric_type.rb +20 -7
  81. data/lib/openhab/core/types/on_off_type.rb +46 -0
  82. data/lib/openhab/core/types/open_closed_type.rb +41 -0
  83. data/lib/openhab/{dsl → core}/types/percent_type.rb +19 -20
  84. data/lib/openhab/core/types/play_pause_type.rb +38 -0
  85. data/lib/openhab/core/types/point_type.rb +117 -0
  86. data/lib/openhab/core/types/quantity_type.rb +325 -0
  87. data/lib/openhab/core/types/raw_type.rb +26 -0
  88. data/lib/openhab/core/types/refresh_type.rb +27 -0
  89. data/lib/openhab/core/types/rewind_fastforward_type.rb +38 -0
  90. data/lib/openhab/core/types/stop_move_type.rb +34 -0
  91. data/lib/openhab/{dsl → core}/types/string_type.rb +17 -28
  92. data/lib/openhab/{dsl → core}/types/type.rb +42 -40
  93. data/lib/openhab/core/types/un_def_type.rb +38 -0
  94. data/lib/openhab/core/types/up_down_type.rb +50 -0
  95. data/lib/openhab/core/types.rb +82 -0
  96. data/lib/openhab/{dsl → core}/uid.rb +4 -23
  97. data/lib/openhab/core/value_cache.rb +188 -0
  98. data/lib/openhab/core.rb +98 -0
  99. data/lib/openhab/core_ext/between.rb +32 -0
  100. data/lib/openhab/core_ext/ephemeris.rb +53 -0
  101. data/lib/openhab/core_ext/java/class.rb +34 -0
  102. data/lib/openhab/core_ext/java/duration.rb +142 -0
  103. data/lib/openhab/core_ext/java/list.rb +436 -0
  104. data/lib/openhab/core_ext/java/local_date.rb +104 -0
  105. data/lib/openhab/core_ext/java/local_time.rb +118 -0
  106. data/lib/openhab/core_ext/java/map.rb +66 -0
  107. data/lib/openhab/core_ext/java/month.rb +71 -0
  108. data/lib/openhab/core_ext/java/month_day.rb +119 -0
  109. data/lib/openhab/core_ext/java/period.rb +103 -0
  110. data/lib/openhab/core_ext/java/temporal_amount.rb +34 -0
  111. data/lib/openhab/core_ext/java/time.rb +62 -0
  112. data/lib/openhab/core_ext/java/unit.rb +15 -0
  113. data/lib/openhab/core_ext/java/zoned_date_time.rb +213 -0
  114. data/lib/openhab/core_ext/ruby/array.rb +21 -0
  115. data/lib/openhab/core_ext/ruby/date.rb +96 -0
  116. data/lib/openhab/core_ext/ruby/date_time.rb +55 -0
  117. data/lib/openhab/core_ext/ruby/module.rb +15 -0
  118. data/lib/openhab/core_ext/ruby/numeric.rb +195 -0
  119. data/lib/openhab/core_ext/ruby/range.rb +70 -0
  120. data/lib/openhab/core_ext/ruby/symbol.rb +7 -0
  121. data/lib/openhab/core_ext/ruby/time.rb +108 -0
  122. data/lib/openhab/core_ext.rb +18 -0
  123. data/lib/openhab/dsl/debouncer.rb +259 -0
  124. data/lib/openhab/dsl/events/watch_event.rb +18 -0
  125. data/lib/openhab/dsl/events.rb +9 -0
  126. data/lib/openhab/dsl/gems.rb +1 -1
  127. data/lib/openhab/dsl/items/builder.rb +578 -0
  128. data/lib/openhab/dsl/items/ensure.rb +73 -82
  129. data/lib/openhab/dsl/items/timed_command.rb +214 -159
  130. data/lib/openhab/dsl/rules/automation_rule.rb +126 -115
  131. data/lib/openhab/dsl/rules/builder.rb +1935 -0
  132. data/lib/openhab/dsl/rules/guard.rb +51 -114
  133. data/lib/openhab/dsl/rules/name_inference.rb +66 -25
  134. data/lib/openhab/dsl/rules/property.rb +48 -75
  135. data/lib/openhab/dsl/rules/rule_triggers.rb +22 -27
  136. data/lib/openhab/dsl/rules/terse.rb +58 -14
  137. data/lib/openhab/dsl/rules/triggers/changed.rb +48 -94
  138. data/lib/openhab/dsl/rules/triggers/channel.rb +9 -40
  139. data/lib/openhab/dsl/rules/triggers/command.rb +14 -63
  140. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +34 -69
  141. data/lib/openhab/dsl/rules/triggers/conditions/proc.rb +6 -14
  142. data/lib/openhab/dsl/rules/triggers/cron/cron.rb +48 -82
  143. data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +30 -47
  144. data/lib/openhab/dsl/rules/triggers/trigger.rb +7 -28
  145. data/lib/openhab/dsl/rules/triggers/updated.rb +21 -45
  146. data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +257 -102
  147. data/lib/openhab/dsl/rules/triggers.rb +12 -0
  148. data/lib/openhab/dsl/rules.rb +8 -0
  149. data/lib/openhab/dsl/things/builder.rb +299 -0
  150. data/lib/openhab/{core → dsl}/thread_local.rb +27 -17
  151. data/lib/openhab/dsl/timer_manager.rb +204 -0
  152. data/lib/openhab/dsl/version.rb +9 -0
  153. data/lib/openhab/dsl.rb +979 -0
  154. data/lib/openhab/log.rb +355 -0
  155. data/lib/openhab/osgi.rb +68 -0
  156. data/lib/openhab/rspec/configuration.rb +56 -0
  157. data/lib/openhab/rspec/example_group.rb +132 -0
  158. data/lib/openhab/rspec/helpers.rb +458 -0
  159. data/lib/openhab/rspec/hooks.rb +113 -0
  160. data/lib/openhab/rspec/jruby.rb +46 -0
  161. data/lib/openhab/rspec/karaf.rb +851 -0
  162. data/lib/openhab/rspec/mocks/bundle_install_support.rb +25 -0
  163. data/lib/openhab/rspec/mocks/bundle_resolver.rb +30 -0
  164. data/lib/openhab/rspec/mocks/event_admin.rb +146 -0
  165. data/lib/openhab/rspec/mocks/instance_method_stasher.rb +22 -0
  166. data/lib/openhab/rspec/mocks/persistence_service.rb +155 -0
  167. data/lib/openhab/rspec/mocks/safe_caller.rb +40 -0
  168. data/lib/openhab/rspec/mocks/space.rb +23 -0
  169. data/lib/openhab/rspec/mocks/synchronous_executor.rb +63 -0
  170. data/lib/openhab/rspec/mocks/thing_handler.rb +76 -0
  171. data/lib/openhab/rspec/mocks/timer.rb +134 -0
  172. data/lib/openhab/rspec/openhab/core/actions.rb +38 -0
  173. data/lib/openhab/rspec/openhab/core/items/proxy.rb +15 -0
  174. data/lib/openhab/rspec/openhab/core/things/proxy.rb +27 -0
  175. data/lib/openhab/rspec/shell.rb +31 -0
  176. data/lib/openhab/rspec/suspend_rules.rb +50 -0
  177. data/lib/openhab/rspec.rb +26 -0
  178. data/lib/openhab/yard/base_helper.rb +19 -0
  179. data/lib/openhab/yard/cli/stats.rb +23 -0
  180. data/lib/openhab/yard/code_objects/group_object.rb +23 -0
  181. data/lib/openhab/yard/code_objects/java/base.rb +31 -0
  182. data/lib/openhab/yard/code_objects/java/class_object.rb +11 -0
  183. data/lib/openhab/yard/code_objects/java/field_object.rb +15 -0
  184. data/lib/openhab/yard/code_objects/java/interface_object.rb +15 -0
  185. data/lib/openhab/yard/code_objects/java/package_object.rb +11 -0
  186. data/lib/openhab/yard/code_objects/java/proxy.rb +23 -0
  187. data/lib/openhab/yard/coderay.rb +17 -0
  188. data/lib/openhab/yard/handlers/jruby/base.rb +58 -0
  189. data/lib/openhab/yard/handlers/jruby/class_handler.rb +18 -0
  190. data/lib/openhab/yard/handlers/jruby/constant_handler.rb +18 -0
  191. data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +30 -0
  192. data/lib/openhab/yard/handlers/jruby/mixin_handler.rb +23 -0
  193. data/lib/openhab/yard/html_helper.rb +78 -0
  194. data/lib/openhab/yard/markdown_helper.rb +148 -0
  195. data/lib/openhab/yard/tags/constant_directive.rb +20 -0
  196. data/lib/openhab/yard/tags/group_directive.rb +24 -0
  197. data/lib/openhab/yard/tags/library.rb +3 -0
  198. data/lib/openhab/yard.rb +38 -0
  199. metadata +475 -106
  200. data/lib/openhab/core/item_proxy.rb +0 -29
  201. data/lib/openhab/core/load_path.rb +0 -19
  202. data/lib/openhab/core/openhab_setup.rb +0 -29
  203. data/lib/openhab/core/osgi.rb +0 -58
  204. data/lib/openhab/core/services.rb +0 -24
  205. data/lib/openhab/dsl/actions.rb +0 -114
  206. data/lib/openhab/dsl/between.rb +0 -25
  207. data/lib/openhab/dsl/channel.rb +0 -43
  208. data/lib/openhab/dsl/dsl.rb +0 -59
  209. data/lib/openhab/dsl/group.rb +0 -54
  210. data/lib/openhab/dsl/imports.rb +0 -21
  211. data/lib/openhab/dsl/items/color_item.rb +0 -76
  212. data/lib/openhab/dsl/items/comparable_item.rb +0 -62
  213. data/lib/openhab/dsl/items/contact_item.rb +0 -41
  214. data/lib/openhab/dsl/items/date_time_item.rb +0 -65
  215. data/lib/openhab/dsl/items/dimmer_item.rb +0 -65
  216. data/lib/openhab/dsl/items/generic_item.rb +0 -229
  217. data/lib/openhab/dsl/items/group_item.rb +0 -127
  218. data/lib/openhab/dsl/items/item_equality.rb +0 -59
  219. data/lib/openhab/dsl/items/item_registry.rb +0 -54
  220. data/lib/openhab/dsl/items/items.rb +0 -109
  221. data/lib/openhab/dsl/items/location_item.rb +0 -59
  222. data/lib/openhab/dsl/items/metadata.rb +0 -326
  223. data/lib/openhab/dsl/items/number_item.rb +0 -17
  224. data/lib/openhab/dsl/items/numeric_item.rb +0 -87
  225. data/lib/openhab/dsl/items/persistence.rb +0 -307
  226. data/lib/openhab/dsl/items/player_item.rb +0 -58
  227. data/lib/openhab/dsl/items/rollershutter_item.rb +0 -51
  228. data/lib/openhab/dsl/items/semantics/enumerable.rb +0 -91
  229. data/lib/openhab/dsl/items/semantics.rb +0 -227
  230. data/lib/openhab/dsl/items/string_item.rb +0 -51
  231. data/lib/openhab/dsl/items/switch_item.rb +0 -70
  232. data/lib/openhab/dsl/monkey_patch/actions/actions.rb +0 -4
  233. data/lib/openhab/dsl/monkey_patch/actions/script_thing_actions.rb +0 -39
  234. data/lib/openhab/dsl/monkey_patch/events/events.rb +0 -7
  235. data/lib/openhab/dsl/monkey_patch/events/item_command.rb +0 -85
  236. data/lib/openhab/dsl/monkey_patch/events/item_event.rb +0 -28
  237. data/lib/openhab/dsl/monkey_patch/events/item_state.rb +0 -61
  238. data/lib/openhab/dsl/monkey_patch/events/item_state_changed.rb +0 -60
  239. data/lib/openhab/dsl/monkey_patch/events/thing_status_info.rb +0 -33
  240. data/lib/openhab/dsl/monkey_patch/java/java.rb +0 -4
  241. data/lib/openhab/dsl/monkey_patch/java/local_time.rb +0 -44
  242. data/lib/openhab/dsl/monkey_patch/java/time_extensions.rb +0 -50
  243. data/lib/openhab/dsl/monkey_patch/java/zoned_date_time.rb +0 -45
  244. data/lib/openhab/dsl/monkey_patch/ruby/number.rb +0 -104
  245. data/lib/openhab/dsl/monkey_patch/ruby/ruby.rb +0 -6
  246. data/lib/openhab/dsl/monkey_patch/ruby/string.rb +0 -47
  247. data/lib/openhab/dsl/monkey_patch/ruby/time.rb +0 -61
  248. data/lib/openhab/dsl/openhab.rb +0 -30
  249. data/lib/openhab/dsl/persistence.rb +0 -27
  250. data/lib/openhab/dsl/rules/item_event.rb +0 -19
  251. data/lib/openhab/dsl/rules/rule.rb +0 -160
  252. data/lib/openhab/dsl/rules/rule_config.rb +0 -147
  253. data/lib/openhab/dsl/rules/triggers/generic.rb +0 -31
  254. data/lib/openhab/dsl/rules/triggers/triggers.rb +0 -11
  255. data/lib/openhab/dsl/rules/triggers/watch/watch.rb +0 -81
  256. data/lib/openhab/dsl/states.rb +0 -89
  257. data/lib/openhab/dsl/things.rb +0 -147
  258. data/lib/openhab/dsl/time/month_day.rb +0 -180
  259. data/lib/openhab/dsl/time/time_of_day.rb +0 -235
  260. data/lib/openhab/dsl/timers/manager.rb +0 -119
  261. data/lib/openhab/dsl/timers/reentrant_timer.rb +0 -38
  262. data/lib/openhab/dsl/timers/timer.rb +0 -132
  263. data/lib/openhab/dsl/timers.rb +0 -77
  264. data/lib/openhab/dsl/types/increase_decrease_type.rb +0 -23
  265. data/lib/openhab/dsl/types/next_previous_type.rb +0 -23
  266. data/lib/openhab/dsl/types/on_off_type.rb +0 -28
  267. data/lib/openhab/dsl/types/open_closed_type.rb +0 -29
  268. data/lib/openhab/dsl/types/play_pause_type.rb +0 -27
  269. data/lib/openhab/dsl/types/point_type.rb +0 -180
  270. data/lib/openhab/dsl/types/quantity_type.rb +0 -265
  271. data/lib/openhab/dsl/types/refresh_type.rb +0 -18
  272. data/lib/openhab/dsl/types/rewind_fastforward_type.rb +0 -33
  273. data/lib/openhab/dsl/types/stop_move_type.rb +0 -23
  274. data/lib/openhab/dsl/types/types.rb +0 -83
  275. data/lib/openhab/dsl/types/un_def_type.rb +0 -22
  276. data/lib/openhab/dsl/types/up_down_type.rb +0 -32
  277. data/lib/openhab/dsl/units.rb +0 -45
  278. data/lib/openhab/log/configuration.rb +0 -21
  279. data/lib/openhab/log/logger.rb +0 -282
  280. data/lib/openhab/version.rb +0 -9
  281. data/lib/openhab.rb +0 -36
@@ -0,0 +1,1935 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ require_relative "property"
6
+ require_relative "guard"
7
+ require_relative "rule_triggers"
8
+ require_relative "terse"
9
+
10
+ Dir[File.expand_path("triggers/*.rb", __dir__)].sort.each do |f|
11
+ require f
12
+ end
13
+
14
+ module OpenHAB
15
+ module DSL
16
+ #
17
+ # Creates and manages openHAB Rules
18
+ #
19
+ module Rules
20
+ # A rules builder allows you to create openHAB rules.
21
+ #
22
+ # Note that all methods on this module are also availabe directly on {OpenHAB::DSL}.
23
+ #
24
+ class Builder
25
+ include Terse
26
+ include Core::EntityLookup
27
+
28
+ self.create_dummy_items = true
29
+
30
+ # @return [org.openhab.core.automation.RuleProvider]
31
+ attr_reader :provider
32
+
33
+ def initialize(provider)
34
+ @provider = Core::Rules::Provider.current(provider)
35
+ end
36
+
37
+ #
38
+ # Create a new rule
39
+ #
40
+ # The rule must have at least one trigger and one execution block.
41
+ # To create a "script" without any triggers, use {#script}.
42
+ #
43
+ # @param [String] name The rule name
44
+ # @yield Block executed in the context of a {Rules::BuilderDSL}
45
+ # @yieldparam [Rules::BuilderDSL] rule
46
+ # Optional parameter to access the rule configuration from within execution blocks and guards.
47
+ # @return [Core::Rules::Rule, nil] The rule object, or nil if no rule was created.
48
+ #
49
+ # @see OpenHAB::DSL::Rules::BuilderDSL Rule BuilderDSL for details on rule triggers, guards and execution blocks
50
+ # @see Rules::Terse Terse Rules
51
+ #
52
+ # @example
53
+ # require "openhab/dsl"
54
+ #
55
+ # rule "name" do
56
+ # <one or more triggers>
57
+ # <one or more execution blocks>
58
+ # <zero or more guards>
59
+ # end
60
+ #
61
+ def rule(name = nil, id: nil, script: nil, binding: nil, &block)
62
+ raise ArgumentError, "Block is required" unless block
63
+
64
+ id ||= NameInference.infer_rule_id_from_block(block)
65
+ script ||= block.source rescue nil # rubocop:disable Style/RescueModifier
66
+
67
+ builder = nil
68
+
69
+ ThreadLocal.thread_local(openhab_rule_type: "rule", openhab_rule_uid: id) do
70
+ builder = BuilderDSL.new(binding || block.binding)
71
+ builder.uid(id)
72
+ builder.instance_exec(builder, &block)
73
+ builder.guard = Guard.new(run_context: builder.caller, only_if: builder.only_if,
74
+ not_if: builder.not_if)
75
+
76
+ name ||= NameInference.infer_rule_name(builder)
77
+ name ||= id
78
+
79
+ builder.name(name)
80
+ logger.trace { builder.inspect }
81
+ builder.build(provider, script)
82
+ end
83
+ end
84
+
85
+ #
86
+ # Create a new script
87
+ #
88
+ # A script is a rule with no triggers. It can be called by various other actions,
89
+ # such as the Run Rules action.
90
+ #
91
+ # @param [String] name A descriptive name
92
+ # @param [String] id The script's ID
93
+ # @yield [] Block executed when the script is executed.
94
+ # @return [Core::Rules::Rule]
95
+ #
96
+ def script(name = nil, id: nil, script: nil, &block)
97
+ raise ArgumentError, "Block is required" unless block
98
+
99
+ id ||= NameInference.infer_rule_id_from_block(block)
100
+ name ||= id
101
+ script ||= block.source rescue nil # rubocop:disable Style/RescueModifier
102
+
103
+ builder = nil
104
+ ThreadLocal.thread_local(openhab_rule_type: "script", openhab_rule_uid: id) do
105
+ builder = BuilderDSL.new(block.binding)
106
+ builder.uid(id)
107
+ builder.tags(["Script"])
108
+ builder.name(name)
109
+ builder.script(&block)
110
+ logger.trace { builder.inspect }
111
+ builder.build(provider, script)
112
+ end
113
+ end
114
+ end
115
+
116
+ #
117
+ # Rule configuration for openHAB Rules engine
118
+ #
119
+ class BuilderDSL
120
+ include Core::EntityLookup
121
+ include DSL
122
+ prepend Triggers
123
+ extend Property
124
+ extend Forwardable
125
+
126
+ self.create_dummy_items = true
127
+
128
+ delegate %i[triggers trigger_conditions attachments] => :@rule_triggers
129
+
130
+ # @!visibility private
131
+ # @return [Array] Of trigger guards
132
+ attr_accessor :guard
133
+
134
+ # @!visibility private
135
+ # @return [Object] object that invoked rule method
136
+ attr_accessor :caller
137
+
138
+ # @!visibility private
139
+ # @return [Array] Of trigger definitions as passed in Ruby
140
+ attr_reader :ruby_triggers
141
+
142
+ # @!visibility private
143
+ attr_reader :debounce_settings
144
+
145
+ # @!visibility private
146
+ Run = Struct.new(:block)
147
+
148
+ # @!visibility private
149
+ Script = Struct.new(:block)
150
+
151
+ # @!visibility private
152
+ Trigger = Struct.new(:block)
153
+
154
+ # @!visibility private
155
+ Otherwise = Struct.new(:block)
156
+
157
+ # @!visibility private
158
+ Delay = Struct.new(:duration)
159
+
160
+ # @!group Execution Blocks
161
+
162
+ #
163
+ # @!method run
164
+ #
165
+ # Add a block that will be passed event data.
166
+ #
167
+ # The run property is the automation code that is executed when a rule
168
+ # is triggered. This property accepts a block of code and executes it.
169
+ # The block is automatically passed an event object which can be used
170
+ # to access multiple properties about the triggering event. The code
171
+ # for the automation can be entirely within the run block and can call
172
+ # methods defined in the Ruby script.
173
+ #
174
+ # @yieldparam [Core::Events::AbstractEvent] event
175
+ # @return [void]
176
+ #
177
+ # @example `{}` style used for single line blocks.
178
+ # rule 'Access Event Properties' do
179
+ # changed TestSwitch
180
+ # run { |event| logger.info("#{event.item.name} triggered from #{event.was} to #{event.state}") }
181
+ # end
182
+ #
183
+ # @example `do/end` style used for multi-line blocks.
184
+ # rule 'Multi Line Run Block' do
185
+ # changed TestSwitch
186
+ # run do |event|
187
+ # logger.info("#{event.item.name} triggered")
188
+ # logger.info("from #{event.was}") if event.was?
189
+ # logger.info("to #{event.state}") if event.state?
190
+ # end
191
+ # end
192
+ #
193
+ # @example Rules can have multiple run blocks and they are executed in order. Useful when used in combination with {#delay}.
194
+ # rule 'Multiple Run Blocks' do
195
+ # changed TestSwitch
196
+ # run { |event| logger.info("#{event.item.name} triggered") }
197
+ # run { |event| logger.info("from #{event.was}") if event.was? }
198
+ # run { |event| logger.info("to #{event.state}") if event.state? }
199
+ # end
200
+ #
201
+ prop_array :run, array_name: :run_queue, wrapper: Run
202
+
203
+ prop_array :script, array_name: :run_queue, wrapper: Script
204
+
205
+ #
206
+ # @!method triggered
207
+ #
208
+ # Add a block that will be passed the triggering item.
209
+ #
210
+ # This property is the same as the {#run} property except rather than
211
+ # passing an event object to the automation block the triggered item is
212
+ # passed. This enables optimizations for simple cases and supports
213
+ # Ruby's [pretzel colon `&:` operator.](https://medium.com/@dcjones/the-pretzel-colon-75df46dde0c7).
214
+ #
215
+ # @yieldparam [Item] item
216
+ # @return [void]
217
+ #
218
+ # @example
219
+ # rule "motion sensor triggered" do
220
+ # changed MotionSensor.members, to: :OPEN
221
+ # triggered do |item|
222
+ # logger.info("#{item.name} detected motion")
223
+ # end
224
+ # end
225
+ #
226
+ # @example
227
+ # rule 'Triggered has access directly to item triggered' do
228
+ # changed TestSwitch
229
+ # triggered { |item| logger.info("#{item.name} triggered") }
230
+ # end
231
+ #
232
+ # @example Triggered items are highly useful when working with groups
233
+ # # Switches is a group of Switch items
234
+ # rule 'Triggered item is item changed when a group item is changed.' do
235
+ # changed Switches.members
236
+ # triggered { |item| logger.info("Switch #{item.name} changed to #{item.state}")}
237
+ # end
238
+ #
239
+ # rule 'Turn off any switch that changes' do
240
+ # changed Switches.members
241
+ # triggered(&:off)
242
+ # end
243
+ #
244
+ # @example Like other execution blocks, multiple triggered blocks are supported in a single rule
245
+ # rule 'Turn a switch off and log it, 5 seconds after turning it on' do
246
+ # changed Switches.members, to: ON
247
+ # delay 5.seconds
248
+ # triggered(&:off)
249
+ # triggered {|item| logger.info("#{item.label} turned off") }
250
+ # end
251
+ prop_array :triggered, array_name: :run_queue, wrapper: Trigger
252
+
253
+ #
254
+ # @!method delay(duration)
255
+ #
256
+ # Add a wait between or after run blocks.
257
+ #
258
+ # The delay property is a non thread-blocking element that is executed
259
+ # after, before, or between run blocks.
260
+ #
261
+ # @param [java.time.temporal.TemporalAmount] duration How long to delay for.
262
+ # @return [void]
263
+ #
264
+ # @example
265
+ # rule "delay execution" do
266
+ # changed MotionSensor, to: CLOSED
267
+ # delay 5.seconds
268
+ # run { Light.off }
269
+ # end
270
+ #
271
+ # @example
272
+ # rule 'Delay sleeps between execution elements' do
273
+ # on_load
274
+ # run { logger.info("Sleeping") }
275
+ # delay 5.seconds
276
+ # run { logger.info("Awake") }
277
+ # end
278
+ #
279
+ # @example Like other execution blocks, multiple can exist in a single rule.
280
+ # rule 'Multiple delays can exist in a rule' do
281
+ # on_load
282
+ # run { logger.info("Sleeping") }
283
+ # delay 5.seconds
284
+ # run { logger.info("Sleeping Again") }
285
+ # delay 5.seconds
286
+ # run { logger.info("Awake") }
287
+ # end
288
+ #
289
+ # @example You can use Ruby code in your rule across multiple execution blocks like a run and a delay.
290
+ # rule 'Dim a switch on system startup over 100 seconds' do
291
+ # on_load
292
+ # 100.times do
293
+ # run { DimmerSwitch.dim }
294
+ # delay 1.second
295
+ # end
296
+ # end
297
+ #
298
+ prop_array :delay, array_name: :run_queue, wrapper: Delay
299
+
300
+ #
301
+ # @!method otherwise
302
+ #
303
+ # Add a block that will be passed event data, to be run if guards are
304
+ # not satisfied.
305
+ #
306
+ # The {otherwise} property is the automation code that is executed when
307
+ # a rule is triggered and guards are not satisfied. This property
308
+ # accepts a block of code and executes it. The block is automatically
309
+ # passed an event object which can be used to access multiple
310
+ # properties about the triggering event.
311
+ #
312
+ # @yieldparam [Core::Events::AbstractEvent] event
313
+ #
314
+ # @example
315
+ # rule 'Turn switch ON or OFF based on value of another switch' do
316
+ # on_load
317
+ # run { TestSwitch << ON }
318
+ # otherwise { TestSwitch << OFF }
319
+ # only_if { OtherSwitch.on? }
320
+ # end
321
+ #
322
+ prop_array :otherwise, array_name: :run_queue, wrapper: Otherwise
323
+
324
+ # @!group Configuration
325
+
326
+ #
327
+ # @!method uid(id)
328
+ #
329
+ # Set the rule's UID.
330
+ #
331
+ # @param [String] id
332
+ # @return [void]
333
+ #
334
+ prop :uid
335
+
336
+ #
337
+ # @!method name(value)
338
+ #
339
+ # Set the rule's name.
340
+ #
341
+ # @param [String] value
342
+ # @return [void]
343
+ #
344
+ prop :name
345
+
346
+ #
347
+ # @!method description(value)
348
+ #
349
+ # Set the rule's description.
350
+ #
351
+ # @param [String] value
352
+ # @return [void]
353
+ #
354
+ prop :description
355
+
356
+ #
357
+ # @!method tags(tags)
358
+ #
359
+ # Set the rule's tags.
360
+ #
361
+ # @param [String, Symbol, Semantics::Tag] tags A list of tags to assign to the rule.
362
+ # @return [void]
363
+ #
364
+ # @example
365
+ # rule "tagged rule" do
366
+ # tags "lighting", "security"
367
+ # end
368
+ #
369
+ prop :tags
370
+
371
+ #
372
+ # @!method enabled(value)
373
+ #
374
+ # Enable or disable the rule from executing
375
+ #
376
+ # @param [true,false] value
377
+ # @return [void]
378
+ #
379
+ # @example
380
+ # rule "disabled rule" do
381
+ # enabled(false)
382
+ # end
383
+ #
384
+ prop :enabled
385
+
386
+ #
387
+ # Returns all {Item Items} (or {GroupItem::Members GroupItem::Members}) referenced
388
+ # by the specified trigger types in this rule.
389
+ #
390
+ # @param [Symbol, Array<Symbol>] trigger_types Trigger types to search for dependencies
391
+ # @return [Array<Item, GroupItem::Members>]
392
+ #
393
+ # @example Ensure all dependencies have a state when executing a rule
394
+ # rule do |rule|
395
+ # changed Item1, Item2, Item3
396
+ # only_if { rule.dependencies.all?(&:state?) }
397
+ # run { FormulaItem.update(Item3.state - (Item1.state + Item2.state)) }
398
+ # end
399
+ #
400
+ def dependencies(trigger_types = %i[changed updated])
401
+ trigger_types = Array.wrap(trigger_types)
402
+
403
+ ruby_triggers.flat_map do |t|
404
+ next [] unless trigger_types.include?(t.first)
405
+
406
+ t[1].select { |i| i.is_a?(Item) || i.is_a?(GroupItem::Members) }
407
+ end.uniq
408
+ end
409
+
410
+ # @!group Guards
411
+ # Guards exist to only permit rules to run if certain conditions are
412
+ # satisfied. Think of these as declarative `if` statements that keep
413
+ # the run block free of conditional logic, although you can of course
414
+ # still use conditional logic in run blocks if you prefer.
415
+ #
416
+ # ### Guard Combination
417
+ #
418
+ # Multiple guards can be used on the same rule. All must be satisfied
419
+ # for a rule to execute.
420
+ #
421
+ # @example
422
+ # rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is OFF and Door is CLOSED" do
423
+ # changed LightSwitch, to: ON
424
+ # run { OutsideDimmer << 50 }
425
+ # only_if { Door.closed? }
426
+ # not_if { OtherSwitch.on? }
427
+ # end
428
+ #
429
+
430
+ #
431
+ # @!method between(range)
432
+ #
433
+ # Only execute rule if the current time is between the supplied time ranges.
434
+ #
435
+ # If the range is of strings, it will be parsed to an appropriate time class.
436
+ #
437
+ # @param [Range] range
438
+ # @return [void]
439
+ #
440
+ # @example
441
+ # rule "Between guard" do
442
+ # changed MotionSensor, to: OPEN
443
+ # between "6:05".."14:05:05" # Include end
444
+ # run { Light.on }
445
+ # end
446
+ #
447
+ # @example
448
+ # rule "Between guard" do
449
+ # changed MotionSensor, to: OPEN
450
+ # between "6:05".."14:05:05" # Excludes end second
451
+ # run { Light.on }
452
+ # end
453
+ #
454
+ # @example
455
+ # rule "Between guard" do
456
+ # changed MotionSensor, to: OPEN
457
+ # between LocalTime.of(6, 5)..LocalTime.of(14, 15, 5)
458
+ # run { Light.on }
459
+ # end
460
+ #
461
+ # @example String of {LocalTime}
462
+ # rule 'Log an entry if started between 3:30:04 and midnight using strings' do
463
+ # on_load
464
+ # run { logger.info ("Started at #{LocalTime.now}")}
465
+ # between '3:30:04'..LocalTime::MIDNIGHT
466
+ # end
467
+ #
468
+ # @example {LocalTime}
469
+ # rule 'Log an entry if started between 3:30:04 and midnight using LocalTime objects' do
470
+ # on_load
471
+ # run { logger.info ("Started at #{LocalTime.now}")}
472
+ # between LocalTime.of(3, 30, 4)..LocalTime::MIDNIGHT
473
+ # end
474
+ #
475
+ # @example String of {MonthDay}
476
+ # rule 'Log an entry if started between March 9th and April 10ths' do
477
+ # on_load
478
+ # run { logger.info ("Started at #{Time.now}")}
479
+ # between '03-09'..'04-10'
480
+ # end
481
+ #
482
+ # @example {MonthDay}
483
+ # rule 'Log an entry if started between March 9th and April 10ths' do
484
+ # on_load
485
+ # run { logger.info ("Started at #{Time.now}")}
486
+ # between MonthDay.of(03,09)..'04-06'
487
+ # end
488
+ #
489
+ prop :between
490
+
491
+ #
492
+ # @!method only_if
493
+ #
494
+ # Allows rule execution when the block's result is true and prevents it when it's false.
495
+ #
496
+ # @yieldparam [Core::Events::AbstractEvent] event The event data that is about to trigger the rule.
497
+ # @yieldreturn [Boolean] A value indicating if the rule should run.
498
+ # @return [void]
499
+ #
500
+ # @example
501
+ # rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is also ON" do
502
+ # changed LightSwitch, to: ON
503
+ # run { OutsideDimmer << 50 }
504
+ # only_if { OtherSwitch.on? }
505
+ # end
506
+ #
507
+ # @example Multiple {only_if} statements can be used and *all* must be true for the rule to run.
508
+ # rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is also ON and Door is closed" do
509
+ # changed LightSwitch, to: ON
510
+ # run { OutsideDimmer << 50 }
511
+ # only_if { OtherSwitch.on? }
512
+ # only_if { Door.closed? }
513
+ # end
514
+ #
515
+ # @example Guards have access to event information.
516
+ # rule "Set OutsideDimmer to 50% if any switch in group Switches starting with Outside is switched On" do
517
+ # changed Switches.items, to: ON
518
+ # run { OutsideDimmer << 50 }
519
+ # only_if { |event| event.item.name.start_with?("Outside") }
520
+ # end
521
+ #
522
+ prop_array(:only_if) do |item|
523
+ raise ArgumentError, "Object passed to only_if must be a proc" unless item.is_a?(Proc)
524
+ end
525
+
526
+ #
527
+ # @!method not_if
528
+ #
529
+ # Prevents execution of rules when the block's result is true and allows it when it's true.
530
+ #
531
+ # @yieldparam [Core::Events::AbstractEvent] event The event data that is about to trigger the rule.
532
+ # @yieldreturn [Boolean] A value indicating if the rule should _not_ run.
533
+ # @return [void]
534
+ #
535
+ # @example
536
+ # rule "Set OutsideDimmer to 50% if LightSwtich turned on and OtherSwitch is OFF" do
537
+ # changed LightSwitch, to: ON
538
+ # run { OutsideDimmer << 50 }
539
+ # not_if { OtherSwitch.on? }
540
+ # end
541
+ #
542
+ # @example Multiple {not_if} statements can be used and if **any** of them are not satisfied the rule will not run.
543
+ # rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is OFF and Door is not CLOSED" do
544
+ # changed LightSwitch, to: ON
545
+ # run { OutsideDimmer << 50 }
546
+ # not_if { OtherSwitch.on? }
547
+ # not_if { Door.closed? }
548
+ # end
549
+ #
550
+ prop_array(:not_if) do |item|
551
+ raise ArgumentError, "Object passed to not_if must be a proc" unless item.is_a?(Proc)
552
+ end
553
+
554
+ # rubocop:disable Layout/LineLength
555
+
556
+ #
557
+ # Waits until triggers have stopped firing for a period of time before executing the rule.
558
+ #
559
+ # It ignores triggers that are "bouncing around" (rapidly firing) by ignoring
560
+ # them until they have quiesced (stopped triggering for a while).
561
+ #
562
+ # ## Comparison Table
563
+ # | Guard | Triggers Immediately | Description |
564
+ # | -------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
565
+ # | {debounce_for} | No | Waits until there is a minimum interval between triggers. |
566
+ # | {throttle_for} | No | Rate-limits the executions to a minimum interval, regardless of the interval between triggers. Waits until the end of the period before executing, ignores any leading triggers. |
567
+ # | {only_every} | Yes | Rate-limits the executions to a minimum interval. Immediately executes the first trigger, then ignores subsequent triggers for the period. |
568
+ #
569
+ # ## Timing Diagram
570
+ # The following timing diagram illustrates the difference between {debounce_for},
571
+ # {throttle_for}, and {only_every} guards:
572
+ #
573
+ # ```
574
+ # TIME INDEX ===> 1 1 2 2 3 3 4 4
575
+ # 0 5 0 5 0 5 0 5 0 5
576
+ # Triggers : "X.X...X...X..XX.X.X....X.XXXXXXXXXXX....X....."
577
+ # debounce_for 5 : "|......................X.|..............X....."
578
+ # debounce_for 5..5 : "|....X|....X.|....X....|....X|....X|....X....X"
579
+ # debounce_for 5..6 : "|.....X...|.....X.|....X.|.....X|.....X.|....X"
580
+ # debounce_for 5..7 : "|......X..|......X|....X.|......X|......X....."
581
+ # debounce_for 5..8 : "|.......X.|.......X....|.......X|.......X....."
582
+ # debounce_for 5..20: "|...................X..|................X....."
583
+ # # throttle_for will fire every 5 intervals after the "first" trigger
584
+ # throttle_for 5 : "|....X|....X.|....X....|....X|....X|....X....."
585
+ # only_every 5 : "X.....X......X....X....X....X....X......X....."
586
+ #
587
+ # Triggers : "X.X...X...X..XX.X.X..X...XXXXXXXXXXX.X..X.X..."
588
+ # debounce_for 5..44: "|...........................................X."
589
+ #
590
+ # # Notice above, triggers keep firing with intervals less than 5, so
591
+ # # debouncer keeps waiting, but puts a stop at 44 (the end of range).
592
+ # ```
593
+ #
594
+ # @param [Duration,Range] debounce_time The minimum interval between two consecutive
595
+ # triggers before the rules are allowed to run.
596
+ #
597
+ # When specified just as a Duration or an endless range, it sets the minimum interval
598
+ # between two consecutive triggers before rules are executed. It will
599
+ # wait endlessly unless this condition is met or an end of range was specified.
600
+ #
601
+ # When the end of the range is specified, it sets the maximum amount of time to wait
602
+ # from the first trigger before the rule will execute, even when triggers continue
603
+ # to occur more frequently than the minimum interval.
604
+ #
605
+ # When an equal beginning and ending values are given, it will behave just like
606
+ # {throttle_for}.
607
+ #
608
+ # @return [void]
609
+ #
610
+ # @see throttle_for
611
+ # @see only_every
612
+ #
613
+ # @example Wait until item stopped changing for at least 1 minute before running the rule
614
+ # rule do
615
+ # changed Item1
616
+ # debounce_for 1.minute
617
+ # run { ... }
618
+ # end
619
+ #
620
+ # @example Alert when door is open for a while
621
+ # # Note: When combined with a state check (only_if), this becomes functionally
622
+ # # equivalent to the changed duration feature.
623
+ # rule "Door alert" do
624
+ # changed Door_State
625
+ # debounce_for 10.minutes
626
+ # only_if { Door_State.open? }
627
+ # run { notify("The Door has been open for 10 minutes!") }
628
+ # end
629
+ #
630
+ def debounce_for(debounce_time)
631
+ idle_time = debounce_time.is_a?(Range) ? debounce_time.begin : debounce_time
632
+ debounce(for: debounce_time, idle_time: idle_time)
633
+ end
634
+ # rubocop:enable Layout/LineLength
635
+
636
+ #
637
+ # Rate-limits rule executions by delaying triggers and executing the last
638
+ # trigger within the given duration.
639
+ #
640
+ # When a new trigger occurs, it will hold the execution and start a fixed timer for
641
+ # the given duration. Should more triggers occur during this time, keep holding
642
+ # and once the wait time is over, execute the latest trigger.
643
+ #
644
+ # {throttle_for} will execute rules after it had waited
645
+ # for the given duration, regardless of how frequently the triggers were occuring.
646
+ # In contrast, {debounce_for} will wait until there is a minimum interval
647
+ # between two triggers.
648
+ #
649
+ # {throttle_for} is ideal in situations where regular status updates need to be made
650
+ # for frequently changing values. It is also useful when a rule responds to triggers
651
+ # from multiple related items that are updated at around the same time. Instead of
652
+ # executing the rule multiple times, {throttle_for} will wait for a pre-set amount
653
+ # of time since the first group of triggers occurred before executing the rule.
654
+ #
655
+ # @param [Duration] duration The minimum amount of time to wait inbetween rule
656
+ # executions.
657
+ #
658
+ # @return [void]
659
+ #
660
+ # @see debounce_for
661
+ # @see only_every
662
+ #
663
+ # @example Perform calculations from multiple items
664
+ # rule "Update Power Summary " do |rule|
665
+ # changed Power_From_Solar, Power_Load, Power_From_Grid
666
+ # throttle_for 1.second
667
+ # only_if { rule.dependencies.all?(&:state?) } # make sure all items have a state
668
+ # run do
669
+ # msg = []
670
+ # msg << Power_Load.state.negate.to_unit("kW").format("Load: %.2f %unit%")
671
+ # msg << Power_From_Solar.state.to_unit("kW").format("PV: %.2f %unit%")
672
+ # if Power_From_Grid.positive?
673
+ # msg << Power_From_Grid.state.to_unit("kW").format("From Grid: %.1f %unit%")
674
+ # else
675
+ # msg << Power_From_Grid.state.negate.to_unit("kW").format("To Grid: %.1f %unit%")
676
+ # end
677
+ # Power_Summary.update(msg.join(", "))
678
+ # end
679
+ # end
680
+ #
681
+ def throttle_for(duration)
682
+ debounce(for: duration)
683
+ end
684
+
685
+ #
686
+ # Executes the rule then ignores subsequent triggers for a given duration.
687
+ #
688
+ # Additional triggers that occur within the given duration after the rule execution
689
+ # will be ignored. This results in executions happening only at the specified interval or
690
+ # more.
691
+ #
692
+ # Unlike {throttle_for}, this guard will execute the rule as soon as a new trigger
693
+ # occurs instead of waiting for the specified duration. This is ideal for triggers
694
+ # such as a door bell where the rule should run as soon as a new trigger is detected
695
+ # but ignore subsequent triggers if they occur too soon after.
696
+ #
697
+ # @param [Duration,:second,:minute,:hour,:day] interval The period during which
698
+ # subsequent triggers are ignored.
699
+ # @return [void]
700
+ #
701
+ # @example Only allow executions every 10 minutes or more
702
+ # rule "Aircon Vent Control" do
703
+ # changed BedRoom_Temperature
704
+ # only_every 10.minutes
705
+ # run do
706
+ # # Adjust BedRoom_Aircon_Vent
707
+ # end
708
+ # end
709
+ #
710
+ # @example Run only on the first update and ignore subsequent triggers for the next minute
711
+ # # They can keep pressing the door bell as often as they like,
712
+ # # but the bell will only ring at most once every minute
713
+ # rule do
714
+ # updated DoorBell_Button, to: "single"
715
+ # only_every 1.minute
716
+ # run { Audio.play_stream "doorbell.mp3" }
717
+ # end
718
+ #
719
+ # @example Using symbolic duration
720
+ # rule "Display update" do
721
+ # updated Power_Usage
722
+ # only_every :minute
723
+ # run { Power_Usage_Display.update "Current power usage: #{Power_Usage.average_since(1.minute.ago)}" }
724
+ # end
725
+ #
726
+ # @see debounce_for
727
+ # @see throttle_for
728
+ #
729
+ def only_every(interval)
730
+ interval = 1.send(interval) if %i[second minute hour day].include?(interval)
731
+ debounce(for: interval, leading: true)
732
+ end
733
+
734
+ # @!endgroup
735
+
736
+ # @!visibility private
737
+ #
738
+ # Create a new DSL
739
+ #
740
+ # @param [Object] caller_binding The object initializing this configuration.
741
+ # Used to execute within the object's context
742
+ #
743
+ def initialize(caller_binding)
744
+ @rule_triggers = RuleTriggers.new
745
+ @caller = caller_binding.eval "self"
746
+ @ruby_triggers = []
747
+ @on_load = nil
748
+ @debounce_settings = nil
749
+ enabled(true)
750
+ tags([])
751
+ end
752
+
753
+ # @!group Triggers
754
+ # Triggers specify what will cause the execution blocks to run.
755
+ # Multiple triggers can be defined within the same rule.
756
+ #
757
+ # ### Trigger Attachments
758
+ #
759
+ # All triggers support event attachments that enable the association
760
+ # of an object to a trigger. This enables one to use the same rule
761
+ # and take different actions if the trigger is different. The
762
+ # attached object is passed to the execution block through the
763
+ # {Core::Events::AbstractEvent#attachment} accessor.
764
+ #
765
+ # @note The trigger attachment feature is not available for UI rules.
766
+ #
767
+ # @example
768
+ # rule 'Set Dark switch at sunrise and sunset' do
769
+ # channel 'astro:sun:home:rise#event', attach: OFF
770
+ # channel 'astro:sun:home:set#event', attach: ON
771
+ # run { |event| Dark << event.attachment }
772
+ # end
773
+
774
+ #
775
+ # Creates a channel trigger
776
+ #
777
+ # The channel trigger executes rule when a specific channel is triggered. The syntax
778
+ # supports one or more channels with one or more triggers. `thing` is an optional
779
+ # parameter that makes it easier to set triggers on multiple channels on the same thing.
780
+ #
781
+ # @param [String, Core::Things::Channel, Core::Things::ChannelUID] channels
782
+ # channels to create triggers for in form of 'binding_id:type_id:thing_id#channel_id'
783
+ # or 'channel_id' if thing is provided.
784
+ # @param [String, Core::Things::Thing, Core::Things::ThingUID] thing
785
+ # Thing(s) to create trigger for if not specified with the channel.
786
+ # @param [String, Array<String>] triggered
787
+ # Only execute rule if the event on the channel matches this/these event/events.
788
+ # @param [Object] attach object to be attached to the trigger
789
+ # @return [void]
790
+ #
791
+ # @example
792
+ # rule "Execute rule when channel is triggered" do
793
+ # channel "astro:sun:home:rise#event"
794
+ # run { logger.info("Channel triggered") }
795
+ # end
796
+ # # The above is the same as each of the below
797
+ #
798
+ # rule "Execute rule when channel is triggered" do
799
+ # channel "rise#event", thing: "astro:sun:home"
800
+ # run { logger.info("Channel triggered") }
801
+ # end
802
+ #
803
+ # rule "Execute rule when channel is triggered" do
804
+ # channel "rise#event", thing: things["astro:sun:home"]
805
+ # run { logger.info("Channel triggered") }
806
+ # end
807
+ #
808
+ # rule "Execute rule when channel is triggered" do
809
+ # channel "rise#event", thing: things["astro:sun:home"].uid
810
+ # run { logger.info("Channel triggered") }
811
+ # end
812
+ #
813
+ # rule "Execute rule when channel is triggered" do
814
+ # channel "rise#event", thing: ["astro:sun:home"]
815
+ # run { logger.info("Channel triggered") }
816
+ # end
817
+ #
818
+ # rule "Execute rule when channel is triggered" do
819
+ # channel things["astro:sun:home"].channels["rise#event"]
820
+ # run { logger.info("Channel triggered") }
821
+ # end
822
+ #
823
+ # rule "Execute rule when channel is triggered" do
824
+ # channel things["astro:sun:home"].channels["rise#event"].uid
825
+ # run { logger.info("Channel triggered") }
826
+ # end
827
+ #
828
+ # @example
829
+ # rule "Rule provides access to channel trigger events in run block" do
830
+ # channel "astro:sun:home:rise#event", triggered: 'START'
831
+ # run { |trigger| logger.info("Channel(#{trigger.channel}) triggered event: #{trigger.event}") }
832
+ # end
833
+ #
834
+ # @example
835
+ # rule "Keypad Code Received test" do
836
+ # channel "mqtt:homie300:mosquitto:backgate:keypad#code"
837
+ # run do |event|
838
+ # logger.info("Received keycode from #{event.channel.thing.uid.id}")
839
+ # end
840
+ # end
841
+ #
842
+ # @example
843
+ # rule "Rules support multiple channels" do
844
+ # channel "rise#event", "set#event", thing: "astro:sun:home"
845
+ # run { logger.info("Channel triggered") }
846
+ # end
847
+ #
848
+ # @example
849
+ # rule "Rules support multiple channels and triggers" do
850
+ # channel "rise#event", "set#event", thing: "astro:sun:home", triggered: ["START", "STOP"]
851
+ # run { logger.info("Channel triggered") }
852
+ # end
853
+ #
854
+ # @example
855
+ # rule "Rules support multiple things" do
856
+ # channel "keypad#code", thing: ["mqtt:homie300:keypad1", "mqtt:homie300:keypad2"]
857
+ # run { logger.info("Channel triggered") }
858
+ # end
859
+ #
860
+ def channel(*channels, thing: nil, triggered: nil, attach: nil)
861
+ channel_trigger = Channel.new(rule_triggers: @rule_triggers)
862
+ flattened_channels = Channel.channels(channels: channels, thing: thing)
863
+ triggers = [triggered].flatten
864
+ @ruby_triggers << [:channel, flattened_channels, { triggers: triggers }]
865
+ flattened_channels.each do |channel|
866
+ triggers.each do |trigger|
867
+ channel_trigger.trigger(channel: channel, trigger: trigger, attach: attach)
868
+ end
869
+ end
870
+ end
871
+
872
+ #
873
+ # Creates a channel linked trigger
874
+ #
875
+ # @param [Object] attach object to be attached to the trigger
876
+ # @return [void]
877
+ #
878
+ # @example
879
+ # rule "channel linked" do
880
+ # channel_linked
881
+ # run do |event|
882
+ # logger.info("#{event.link.item.name} linked to #{event.link.channel_uid}.")
883
+ # end
884
+ # end
885
+ def channel_linked(attach: nil)
886
+ @ruby_triggers << [:channel_linked]
887
+ event("openhab/links/*/added", types: "ItemChannelLinkAddedEvent", attach: attach)
888
+ end
889
+
890
+ #
891
+ # Creates a channel unlinked trigger
892
+ #
893
+ # Note that the item or the thing it's linked to may no longer exist,
894
+ # so if you try to access those objects they'll be nil.
895
+ #
896
+ # @param [Object] attach object to be attached to the trigger
897
+ # @return [void]
898
+ #
899
+ # @example
900
+ # rule "channel unlinked" do
901
+ # channel_unlinked
902
+ # run do |event|
903
+ # logger.info("#{event.link.item_name} unlinked from #{event.link.channel_uid}.")
904
+ # end
905
+ # end
906
+ def channel_unlinked(attach: nil)
907
+ @ruby_triggers << [:channel_linked]
908
+ event("openhab/links/*/removed", types: "ItemChannelLinkRemovedEvent", attach: attach)
909
+ end
910
+
911
+ #
912
+ # Creates a trigger when an item, member of a group, or a thing changed
913
+ # states.
914
+ #
915
+ # When the changed element is a {Core::Things::Thing Thing}, the `from`
916
+ # and `to` values will accept symbols and strings, where the symbol'
917
+ # matches the
918
+ # [supported status](https://www.openhab.org/docs/concepts/things.html#thing-status).
919
+ #
920
+ # The `event` passed to run blocks will be an
921
+ # {Core::Events::ItemStateChangedEvent} or a
922
+ # {Core::Events::ThingStatusInfoChangedEvent} depending on if the
923
+ # triggering element was an item or a thing.
924
+ #
925
+ # @param [Item, GroupItem::Members, Thing] items Objects to create trigger for.
926
+ # @param [State, Array<State>, Range, Proc] from
927
+ # Only execute rule if previous state matches `from` state(s).
928
+ # @param [State, Array<State>, Range, Proc] to State(s) for
929
+ # Only execute rule if new state matches `to` state(s).
930
+ # @param [java.time.temporal.TemporalAmount] for
931
+ # Duration item must remain in the same state before executing the execution blocks.
932
+ # @param [Object] attach object to be attached to the trigger
933
+ # @return [void]
934
+ #
935
+ # @example Multiple items can be separated with a comma:
936
+ # rule "Execute rule when either sensor changed" do
937
+ # changed FrontMotion_Sensor, RearMotion_Sensor
938
+ # run { |event| logger.info("Motion detected by #{event.item.name}") }
939
+ # end
940
+ #
941
+ # @example Group member trigger
942
+ # rule "Execute rule when member changed" do
943
+ # changed Sensors.members
944
+ # run { |event| logger.info("Motion detected by #{event.item.name}") }
945
+ # end
946
+ #
947
+ # @example `for` parameter can be a proc too:
948
+ # Alarm_Delay << 20
949
+ #
950
+ # rule "Execute rule when item is changed for specified duration" do
951
+ # changed Alarm_Mode, for: -> { Alarm_Delay.state }
952
+ # run { logger.info("Alarm Mode Updated") }
953
+ # end
954
+ #
955
+ # @example You can optionally provide `from` and `to` states to restrict the cases in which the rule executes:
956
+ # rule "Execute rule when item is changed to specific number, from specific number, for specified duration" do
957
+ # changed Alarm_Mode, from: 8, to: [14,12], for: 12.seconds
958
+ # run { logger.info("Alarm Mode Updated") }
959
+ # end
960
+ #
961
+ # @example Works with ranges:
962
+ # rule "Execute when item changed to a range of numbers, from a range of numbers, for specified duration" do
963
+ # changed Alarm_Mode, from: 8..10, to: 12..14, for: 12.seconds
964
+ # run { logger.info("Alarm Mode Updated") }
965
+ # end
966
+ #
967
+ # @example Works with endless ranges:
968
+ # rule "Execute rule when item is changed to any number greater than 12"
969
+ # changed Alarm_Mode, to: (12..) # Parenthesis required for endless ranges
970
+ # run { logger.info("Alarm Mode Updated") }
971
+ # end
972
+ #
973
+ # @example Works with procs:
974
+ # rule "Execute when item state is changed from an odd number, to an even number, for specified duration" do
975
+ # changed Alarm_Mode, from: proc { |from| from.odd? }, to: proc {|to| to.even? }, for: 12.seconds
976
+ # run { logger.info("Alarm Mode Updated") }
977
+ # end
978
+ #
979
+ # @example Works with lambdas:
980
+ # rule "Execute when item state is changed from an odd number, to an even number, for specified duration" do
981
+ # changed Alarm_Mode, from: -> from { from.odd? }, to: -> to { to.even? }, for: 12.seconds
982
+ # run { logger.info("Alarm Mode Updated") }
983
+ # end
984
+ #
985
+ # @example Works with Things:
986
+ # rule "Execute rule when thing is changed" do
987
+ # changed things["astro:sun:home"], :from => :online, :to => :uninitialized
988
+ # run { |event| logger.info("Thing #{event.uid} status <trigger> to #{event.status}") }
989
+ # end
990
+ #
991
+ # @example Real World Example
992
+ # rule "Log (or notify) when an exterior door is left open for more than 5 minutes" do
993
+ # changed ExteriorDoors.members, to: OPEN, for: 5.minutes
994
+ # triggered {|door| logger.info("#{door.name} has been left open!") }
995
+ # end
996
+ #
997
+ def changed(*items, to: nil, from: nil, for: nil, attach: nil)
998
+ changed = Changed.new(rule_triggers: @rule_triggers)
999
+ # for is a reserved word in ruby, so use local_variable_get :for
1000
+ duration = binding.local_variable_get(:for)
1001
+
1002
+ @ruby_triggers << [:changed, items, { to: to, from: from, duration: duration }]
1003
+
1004
+ from = [nil] if from.nil?
1005
+ to = [nil] if to.nil?
1006
+ items.each do |item|
1007
+ case item
1008
+ when Core::Things::Thing,
1009
+ Core::Things::ThingUID,
1010
+ Core::Items::Item,
1011
+ Core::Items::GroupItem::Members
1012
+ nil
1013
+ else
1014
+ raise ArgumentError, "items must be an Item, GroupItem::Members, Thing, or ThingUID"
1015
+ end
1016
+
1017
+ logger.trace("Creating changed trigger for entity(#{item}), to(#{to.inspect}), from(#{from.inspect})")
1018
+
1019
+ Array.wrap(from).each do |from_state|
1020
+ Array.wrap(to).each do |to_state|
1021
+ changed.trigger(item: item, from: from_state, to: to_state, duration: duration, attach: attach)
1022
+ end
1023
+ end
1024
+ end
1025
+ end
1026
+
1027
+ #
1028
+ # Create a cron trigger
1029
+ #
1030
+ # @overload cron(expression, attach: nil)
1031
+ # @param [String, nil] expression [openHAB style cron expression](https://www.openhab.org/docs/configuration/rules-dsl.html#time-based-triggers)
1032
+ # @param [Object] attach object to be attached to the trigger
1033
+ #
1034
+ # @example Using a cron expression
1035
+ # rule "cron expression" do
1036
+ # cron "43 46 13 ? * ?"
1037
+ # run { Light.on }
1038
+ # end
1039
+ #
1040
+ # @overload cron(second: nil, minute: nil, hour: nil, dom: nil, month: nil, dow: nil, year: nil, attach: nil)
1041
+ # The trigger can be created by specifying each field as keyword arguments.
1042
+ # Omitted fields will default to `*` or `?` as appropriate.
1043
+ #
1044
+ # Each field is optional, but at least one must be specified.
1045
+ #
1046
+ # The same rules for the standard
1047
+ # [cron expression](https://www.quartz-scheduler.org/documentation/quartz-2.2.2/tutorials/tutorial-lesson-06.html)
1048
+ # apply for each field. For example, multiple values can be separated
1049
+ # with a comma within a string.
1050
+ #
1051
+ # @param [Integer, String, nil] second
1052
+ # @param [Integer, String, nil] minute
1053
+ # @param [Integer, String, nil] hour
1054
+ # @param [Integer, String, nil] dom
1055
+ # @param [Integer, String, nil] month
1056
+ # @param [Integer, String, nil] dow
1057
+ # @param [Integer, String, nil] year
1058
+ # @param [Object] attach object to be attached to the trigger
1059
+ # @example
1060
+ # # Run every 3 minutes on Monday to Friday
1061
+ # # equivalent to the cron expression "0 */3 * ? * MON-FRI *"
1062
+ # rule "Using cron fields" do
1063
+ # cron second: 0, minute: "*/3", dow: "MON-FRI"
1064
+ # run { logger.info "Cron rule executed" }
1065
+ # end
1066
+ #
1067
+ # @return [void]
1068
+ #
1069
+ def cron(expression = nil, attach: nil, **fields)
1070
+ if fields.any?
1071
+ raise ArgumentError, "Cron elements cannot be used with a cron expression" if expression
1072
+
1073
+ cron_expression = Cron.from_fields(fields)
1074
+ return cron(cron_expression, attach: attach)
1075
+ end
1076
+
1077
+ raise ArgumentError, "Missing cron expression or elements" unless expression
1078
+
1079
+ cron = Cron.new(rule_triggers: @rule_triggers)
1080
+ cron.trigger(config: { "cronExpression" => expression }, attach: attach)
1081
+ end
1082
+
1083
+ #
1084
+ # Create a rule that executes at the specified interval.
1085
+ #
1086
+ # @param [String,
1087
+ # Duration,
1088
+ # java.time.MonthDay,
1089
+ # :second,
1090
+ # :minute,
1091
+ # :hour,
1092
+ # :day,
1093
+ # :week,
1094
+ # :month,
1095
+ # :year,
1096
+ # :monday,
1097
+ # :tuesday,
1098
+ # :wednesday,
1099
+ # :thursday,
1100
+ # :friday,
1101
+ # :saturday,
1102
+ # :sunday] value
1103
+ # When to execute rule.
1104
+ # @param [LocalTime, String, Core::Items::DateTimeItem, nil] at What time of day to execute rule
1105
+ # If `value` is `:day`, `at` can be a {Core::Items::DateTimeItem DateTimeItem}, and
1106
+ # the trigger will run every day at the (time only portion of) current state of the
1107
+ # item. If the item is {NULL} or {UNDEF}, the trigger will not run.
1108
+ # @param [Object] attach Object to be attached to the trigger
1109
+ # @return [void]
1110
+ #
1111
+ # @example
1112
+ # rule "Daily" do
1113
+ # every :day, at: '5:15'
1114
+ # run do
1115
+ # Light.on
1116
+ # end
1117
+ # end
1118
+ #
1119
+ # @example The above rule could also be expressed using LocalTime class as below
1120
+ # rule "Daily" do
1121
+ # every :day, at: LocalTime.of(5, 15)
1122
+ # run { Light.on }
1123
+ # end
1124
+ #
1125
+ # @example
1126
+ # rule "Weekly" do
1127
+ # every :monday, at: '5:15'
1128
+ # run do
1129
+ # Light.on
1130
+ # end
1131
+ # end
1132
+ #
1133
+ # @example
1134
+ # rule "Often" do
1135
+ # every :minute
1136
+ # run do
1137
+ # Light.on
1138
+ # end
1139
+ # end
1140
+ #
1141
+ # @example
1142
+ # rule "Hourly" do
1143
+ # every :hour
1144
+ # run do
1145
+ # Light.on
1146
+ # end
1147
+ # end
1148
+ #
1149
+ # @example
1150
+ # rule "Often" do
1151
+ # every 5.minutes
1152
+ # run do
1153
+ # Light.on
1154
+ # end
1155
+ # end
1156
+ #
1157
+ # @example
1158
+ # rule 'Every 14th of Feb at 2pm' do
1159
+ # every '02-14', at: '2pm'
1160
+ # run { logger.info "Happy Valentine's Day!" }
1161
+ # end
1162
+ #
1163
+ # @example
1164
+ # rule "Every day at sunset" do
1165
+ # every :day, at: Sunset_Time
1166
+ # run { logger.info "It's getting dark" }
1167
+ # end
1168
+ #
1169
+ def every(value, at: nil, attach: nil)
1170
+ return every(java.time.MonthDay.parse(value), at: at, attach: attach) if value.is_a?(String)
1171
+
1172
+ @ruby_triggers << [:every, value, { at: at }]
1173
+
1174
+ if value == :day && at.is_a?(Item)
1175
+ raise ArgumentError, "Attachments are not supported with dynamic datetime triggers" unless attach.nil?
1176
+
1177
+ return trigger("timer.DateTimeTrigger", itemName: at.name, timeOnly: true)
1178
+ end
1179
+
1180
+ cron_expression = case value
1181
+ when Symbol then Cron.from_symbol(value, at)
1182
+ when Duration then Cron.from_duration(value, at)
1183
+ when java.time.MonthDay then Cron.from_monthday(value, at)
1184
+ else raise ArgumentError, "Unknown interval"
1185
+ end
1186
+ cron(cron_expression, attach: attach)
1187
+ end
1188
+
1189
+ #
1190
+ # Creates a trigger that executes when the script is loaded
1191
+ #
1192
+ # Execute the rule whenever the script is first loaded, including on openHAB start up,
1193
+ # and on subsequent reloads on file modifications.
1194
+ # This is useful to perform initialization routines, especially when combined with other triggers.
1195
+ #
1196
+ # @param [Duration] delay The amount of time to wait before executing the rule.
1197
+ # When nil, execute immediately.
1198
+ # @param [Object] attach Object to be attached to the trigger
1199
+ # @return [void]
1200
+ #
1201
+ # @example
1202
+ # rule "script startup rule" do
1203
+ # on_load
1204
+ # run do
1205
+ # <calculate some item state>
1206
+ # end
1207
+ # end
1208
+ #
1209
+ # @example
1210
+ # rule "Ensure all security lights are on" do
1211
+ # on_load
1212
+ # run { Security_Lights.on }
1213
+ # end
1214
+ #
1215
+ def on_load(delay: nil, attach: nil)
1216
+ # prevent overwriting @on_load
1217
+ raise ArgumentError, "on_load can only be used once within a rule" if @on_load
1218
+
1219
+ @on_load = { module: SecureRandom.uuid, delay: delay }
1220
+ attachments[@on_load[:module]] = attach
1221
+ end
1222
+
1223
+ #
1224
+ # Creates a trigger that executes when openHAB reaches a certain start level
1225
+ #
1226
+ # This will only trigger once during openHAB start up. It won't trigger on script reloads.
1227
+ #
1228
+ # @param [Integer,:rules,:ruleengine,:ui,:things,:complete] at_level
1229
+ # Zero or more start levels. Note that Startlevels less than 40 are not available as triggers
1230
+ # because the rule engine needs to start up first before it can execute any rules
1231
+ #
1232
+ # | Symbol | Start Level |
1233
+ # | ------------- | ----------- |
1234
+ # | `:osgi` | 10 |
1235
+ # | `:model` | 20 |
1236
+ # | `:state` | 30 |
1237
+ # | `:rules` | 40 |
1238
+ # | `:ruleengine` | 50 |
1239
+ # | `:ui` | 70 |
1240
+ # | `:things` | 80 |
1241
+ # | `:complete` | 100 |
1242
+ # @param [Array<Integer,:rules,:ruleengine,:ui,:things,:complete>] at_levels Fluent alias for `at_level`
1243
+ # @param [Object] attach Object to be attached to the trigger
1244
+ # @return [void]
1245
+ #
1246
+ # @example
1247
+ # rule "Trigger at openHAB system start" do
1248
+ # on_start # trigger at the default startlevel 100
1249
+ # run { logger.info "openHAB start up complete." }
1250
+ # end
1251
+ #
1252
+ # @example Trigger at a specific start level
1253
+ # rule "Trigger after things are loaded" do
1254
+ # on_start at_level: :things
1255
+ # run { logger.info "Things are ready!" }
1256
+ # end
1257
+ #
1258
+ # @example Trigger at multiple levels
1259
+ # rule "Multiple start up triggers" do
1260
+ # on_start at_levels: %i[ui things complete]
1261
+ # run do |event|
1262
+ # logger.info "openHAB startlevel has reached level #{event.startlevel}"
1263
+ # end
1264
+ # end
1265
+ #
1266
+ # @see https://www.openhab.org/docs/configuration/rules-dsl.html#system-based-triggers System based triggers
1267
+ #
1268
+ def on_start(at_level: nil, at_levels: nil, attach: nil)
1269
+ levels = Array.wrap(at_level) | Array.wrap(at_levels)
1270
+ levels = [100] if levels.empty?
1271
+
1272
+ levels.map! do |level|
1273
+ next level unless level.is_a?(Symbol)
1274
+
1275
+ begin
1276
+ klass = org.openhab.core.service.StartLevelService.java_class
1277
+ klass.declared_field("STARTLEVEL_#{level.upcase}").get_int(klass)
1278
+ rescue java.lang.NoSuchFieldException
1279
+ raise ArgumentError, "Invalid symbol for at_level: :#{level}"
1280
+ end
1281
+ end
1282
+
1283
+ @ruby_triggers << [:on_start, levels]
1284
+ levels.each do |level|
1285
+ logger.warn "Rule engine doesn't start until start level 40" if level < 40
1286
+ config = { startlevel: level }
1287
+ logger.trace("Creating a SystemStartlevelTrigger with startlevel=#{level}")
1288
+ Triggers::Trigger.new(rule_triggers: @rule_triggers)
1289
+ .append_trigger(type: "core.SystemStartlevelTrigger", config: config, attach: attach)
1290
+ end
1291
+ end
1292
+
1293
+ #
1294
+ # Creates a trigger for when an item or group receives a command.
1295
+ #
1296
+ # The command/commands parameters are replicated for DSL fluency.
1297
+ #
1298
+ # The `event` passed to run blocks will be an
1299
+ # {Core::Events::ItemCommandEvent}.
1300
+ #
1301
+ # @param [Item, GroupItem::Members] items Items to create trigger for
1302
+ # @param [Core::TypesCommand, Array<Command>, Range, Proc] command commands to match for trigger
1303
+ # @param [Array<Command>, Range, Proc] commands Fluent alias for `command`
1304
+ # @param [Object] attach object to be attached to the trigger
1305
+ # @return [void]
1306
+ #
1307
+ # @example
1308
+ # rule 'Execute rule when item received command' do
1309
+ # received_command Alarm_Mode
1310
+ # run { |event| logger.info("Item received command: #{event.command}" ) }
1311
+ # end
1312
+ #
1313
+ # @example
1314
+ # rule 'Execute rule when item receives specific command' do
1315
+ # received_command Alarm_Mode, command: 7
1316
+ # run { |event| logger.info("Item received command: #{event.command}" ) }
1317
+ # end
1318
+ #
1319
+ # @example
1320
+ # rule 'Execute rule when item receives one of many specific commands' do
1321
+ # received_command Alarm_Mode, commands: [7,14]
1322
+ # run { |event| logger.info("Item received command: #{event.command}" ) }
1323
+ # end
1324
+ #
1325
+ # @example
1326
+ # rule 'Execute rule when group receives a specific command' do
1327
+ # received_command AlarmModes
1328
+ # triggered { |item| logger.info("Group #{item.name} received command")}
1329
+ # end
1330
+ #
1331
+ # @example
1332
+ # rule 'Execute rule when member of group receives any command' do
1333
+ # received_command AlarmModes.members
1334
+ # triggered { |item| logger.info("Group item #{item.name} received command")}
1335
+ # end
1336
+ #
1337
+ # @example
1338
+ # rule 'Execute rule when member of group is changed to one of many states' do
1339
+ # received_command AlarmModes.members, commands: [7, 14]
1340
+ # triggered { |item| logger.info("Group item #{item.name} received command")}
1341
+ # end
1342
+ #
1343
+ # @example
1344
+ # rule 'Execute rule when item receives a range of commands' do
1345
+ # received_command Alarm_Mode, commands: 7..14
1346
+ # run { |event| logger.info("Item received command: #{event.command}" ) }
1347
+ # end
1348
+ #
1349
+ # @example Works with procs
1350
+ # rule 'Execute rule when Alarm Mode command is odd' do
1351
+ # received_command Alarm_Mode, command: proc { |c| c.odd? }
1352
+ # run { |event| logger.info("Item received command: #{event.command}" ) }
1353
+ # end
1354
+ #
1355
+ # @example Works with lambdas
1356
+ # rule 'Execute rule when Alarm Mode command is odd' do
1357
+ # received_command Alarm_Mode, command: -> c { c.odd? }
1358
+ # run { |event| logger.info("Item received command: #{event.command}" ) }
1359
+ # end
1360
+ #
1361
+ def received_command(*items, command: nil, commands: nil, attach: nil)
1362
+ command_trigger = Command.new(rule_triggers: @rule_triggers)
1363
+
1364
+ # if neither command nor commands is specified, ensure that we create
1365
+ # a trigger that isn't looking for a specific command.
1366
+ commands = [nil] if command.nil? && commands.nil?
1367
+ commands = Array.wrap(command) | Array.wrap(commands)
1368
+
1369
+ @ruby_triggers << [:received_command, items, { command: commands }]
1370
+
1371
+ items.each do |item|
1372
+ case item
1373
+ when Core::Items::Item,
1374
+ Core::Items::GroupItem::Members
1375
+ nil
1376
+ else
1377
+ raise ArgumentError, "items must be an Item or GroupItem::Members"
1378
+ end
1379
+ commands.each do |cmd|
1380
+ logger.trace "Creating received command trigger for items #{item.inspect} and commands #{cmd.inspect}"
1381
+
1382
+ command_trigger.trigger(item: item, command: cmd, attach: attach)
1383
+ end
1384
+ end
1385
+ end
1386
+
1387
+ #
1388
+ # Creates an item added trigger
1389
+ #
1390
+ # @param [Object] attach object to be attached to the trigger
1391
+ # @return [void]
1392
+ #
1393
+ # @example
1394
+ # rule "item added" do
1395
+ # item_added
1396
+ # run do |event|
1397
+ # logger.info("#{event.item.name} added.")
1398
+ # end
1399
+ # end
1400
+ def item_added(attach: nil)
1401
+ @ruby_triggers << [:item_added]
1402
+ event("openhab/items/*/added", types: "ItemAddedEvent", attach: attach)
1403
+ end
1404
+
1405
+ #
1406
+ # Creates an item removed trigger
1407
+ #
1408
+ # @param [Object] attach object to be attached to the trigger
1409
+ # @return [void]
1410
+ #
1411
+ # @example
1412
+ # rule "item removed" do
1413
+ # item_removed
1414
+ # run do |event|
1415
+ # logger.info("#{event.item.name} removed.")
1416
+ # end
1417
+ # end
1418
+ def item_removed(attach: nil)
1419
+ @ruby_triggers << [:item_removed]
1420
+ event("openhab/items/*/removed", types: "ItemRemovedEvent", attach: attach)
1421
+ end
1422
+
1423
+ #
1424
+ # Creates an item updated trigger
1425
+ #
1426
+ # @param [Object] attach object to be attached to the trigger
1427
+ # @return [void]
1428
+ #
1429
+ # @example
1430
+ # rule "item updated" do
1431
+ # item_updated
1432
+ # run do |event|
1433
+ # logger.info("#{event.item.name} updated.")
1434
+ # end
1435
+ # end
1436
+ #
1437
+ def item_updated(attach: nil)
1438
+ @ruby_triggers << [:item_updated]
1439
+ event("openhab/items/*/updated", types: "ItemUpdatedEvent", attach: attach)
1440
+ end
1441
+
1442
+ #
1443
+ # Creates a thing added trigger
1444
+ #
1445
+ # @param [Object] attach object to be attached to the trigger
1446
+ # @return [void]
1447
+ #
1448
+ # @example
1449
+ # rule "thing added" do
1450
+ # thing_added
1451
+ # run do |event|
1452
+ # logger.info("#{event.thing.uid} added.")
1453
+ # end
1454
+ # end
1455
+ def thing_added(attach: nil)
1456
+ @ruby_triggers << [:thing_added]
1457
+ event("openhab/things/*/added", types: "ThingAddedEvent", attach: attach)
1458
+ end
1459
+
1460
+ #
1461
+ # Creates a thing removed trigger
1462
+ #
1463
+ # @param [Object] attach object to be attached to the trigger
1464
+ # @return [void]
1465
+ #
1466
+ # @example
1467
+ # rule "thing removed" do
1468
+ # thing_removed
1469
+ # run do |event|
1470
+ # logger.info("#{event.thing.uid} removed.")
1471
+ # end
1472
+ # end
1473
+ def thing_removed(attach: nil)
1474
+ @ruby_triggers << [:thing_removed]
1475
+ event("openhab/things/*/removed", types: "ThingRemovedEvent", attach: attach)
1476
+ end
1477
+
1478
+ #
1479
+ # Creates a thing updated trigger
1480
+ #
1481
+ # @param [Object] attach object to be attached to the trigger
1482
+ # @return [void]
1483
+ #
1484
+ # @example
1485
+ # rule "thing updated" do
1486
+ # thing_updated
1487
+ # run do |event|
1488
+ # logger.info("#{event.thing.uid} updated.")
1489
+ # end
1490
+ # end
1491
+ #
1492
+ def thing_updated(attach: nil)
1493
+ @ruby_triggers << [:thing_updated]
1494
+ event("openhab/things/*/updated", types: "ThingUpdatedEvent", attach: attach)
1495
+ end
1496
+
1497
+ #
1498
+ # Creates a trigger on events coming through the event bus
1499
+ #
1500
+ # @param [String] topic The topic to trigger on; can contain the wildcard `*`.
1501
+ # @param [String, nil] source The sender of the event to trigger on.
1502
+ # Default does not filter on source.
1503
+ # @param [String, Array<String>, nil] types Only subscribe to certain event types.
1504
+ # Default does not filter on event types.
1505
+ # @return [void]
1506
+ #
1507
+ # @example
1508
+ # rule "thing updated" do
1509
+ # event("openhab/things/*/updated", types: "ThingUpdatedEvent")
1510
+ # run do |event|
1511
+ # logger.info("#{event.thing.uid} updated")
1512
+ # end
1513
+ # end
1514
+ #
1515
+ def event(topic, source: nil, types: nil, attach: nil)
1516
+ types = types.join(",") if types.is_a?(Enumerable)
1517
+ # @deprecated OH3.4 - OH3 config uses eventXXX vs OH4 uses `topic`, `source`, and `types`
1518
+ # See https://github.com/openhab/openhab-core/pull/3299
1519
+ trigger("core.GenericEventTrigger",
1520
+ eventTopic: topic, eventSource: source, eventTypes: types, # @deprecated OH3.4
1521
+ topic: topic, source: source, types: types,
1522
+ attach: attach)
1523
+ end
1524
+
1525
+ #
1526
+ # Creates a trigger based on the time stored in a {DateTimeItem}
1527
+ #
1528
+ # The trigger will dynamically update any time the state of the item
1529
+ # changes. If the item is {NULL} or {UNDEF}, the trigger will not run.
1530
+ #
1531
+ # @param [Item, String, Symbol] item The item (or it's name)
1532
+ # @return [void]
1533
+ #
1534
+ # @example
1535
+ # rule "say hello when the kids get home from school" do
1536
+ # at HomeFromSchool_Time
1537
+ # run do
1538
+ # KitchenEcho_TTS << "hi kids! how was school?"
1539
+ # end
1540
+ # end
1541
+ #
1542
+ # rule "set home from school time" do
1543
+ # on_load
1544
+ # every :day, at: "5:00am" do
1545
+ # run do
1546
+ # HomeFromSchool_Time.ensure.update(school_day? ? LocalTime.parse("3:30pm") : NULL)
1547
+ # end
1548
+ # end
1549
+ #
1550
+ def at(item)
1551
+ item = item.name if item.is_a?(Item)
1552
+ trigger("timer.DateTimeTrigger", itemName: item.to_s)
1553
+ end
1554
+
1555
+ #
1556
+ # Create a generic trigger given the trigger type uid and a configuration hash
1557
+ #
1558
+ # This provides the ability to create a trigger type not already covered by the other methods.
1559
+ #
1560
+ # @param [String] type Trigger type UID
1561
+ # @param [Object] attach object to be attached to the trigger
1562
+ # @param [Hash] configuration A hash containing the trigger configuration entries
1563
+ # @return [void]
1564
+ #
1565
+ # @example Create a trigger for the [PID Controller Automation](https://www.openhab.org/addons/automation/pidcontroller/) add-on.
1566
+ # rule 'PID Control' do
1567
+ # trigger 'pidcontroller.trigger',
1568
+ # input: InputItem.name,
1569
+ # setpoint: SetPointItem.name,
1570
+ # kp: 10,
1571
+ # ki: 10,
1572
+ # kd: 10,
1573
+ # kdTimeConstant: 1,
1574
+ # loopTime: 1000
1575
+ #
1576
+ # run do |event|
1577
+ # logger.info("PID controller command: #{event.command}")
1578
+ # ControlItem << event.command
1579
+ # end
1580
+ # end
1581
+ #
1582
+ # @example DateTime Trigger
1583
+ # rule 'DateTime Trigger' do
1584
+ # description 'Triggers at a time specified in MyDateTimeItem'
1585
+ # trigger 'timer.DateTimeTrigger', itemName: MyDateTimeItem.name
1586
+ # run do
1587
+ # logger.info("DateTimeTrigger has been triggered")
1588
+ # end
1589
+ # end
1590
+ #
1591
+ def trigger(type, attach: nil, **configuration)
1592
+ logger.trace("Creating trigger (#{type}) with configuration(#{configuration})")
1593
+ Triggers::Trigger.new(rule_triggers: @rule_triggers)
1594
+ .append_trigger(type: type, config: configuration, attach: attach)
1595
+ end
1596
+
1597
+ #
1598
+ # Create a trigger when item, group or thing is updated
1599
+ #
1600
+ # The `event` passed to run blocks will be an
1601
+ # {Core::Events::ItemStateEvent} or a
1602
+ # {Core::Events::ThingStatusInfoEvent} depending on if the triggering
1603
+ # element was an item or a thing.
1604
+ #
1605
+ # @param [Item, GroupItem::Members, Thing] items
1606
+ # Objects to create trigger for.
1607
+ # @param [State, Array<State>, Range, Proc, Symbol, String] to
1608
+ # Only execute rule if the state matches `to` state(s). If the
1609
+ # updated element is a {Core::Things::Thing}, the `to` accepts
1610
+ # symbols and strings that match
1611
+ # [supported thing statuses](https://www.openhab.org/docs/concepts/things.html#thing-status).
1612
+ # @param [Object] attach object to be attached to the trigger
1613
+ # @return [void]
1614
+ #
1615
+ # @example
1616
+ # rule 'Execute rule when item is updated to any value' do
1617
+ # updated Alarm_Mode
1618
+ # run { logger.info("Alarm Mode Updated") }
1619
+ # end
1620
+ #
1621
+ # @example
1622
+ # rule 'Execute rule when item is updated to specific number' do
1623
+ # updated Alarm_Mode, to: 7
1624
+ # run { logger.info("Alarm Mode Updated") }
1625
+ # end
1626
+ #
1627
+ # @example
1628
+ # rule 'Execute rule when item is updated to one of many specific states' do
1629
+ # updated Alarm_Mode, to: [7, 14]
1630
+ # run { logger.info("Alarm Mode Updated")}
1631
+ # end
1632
+ #
1633
+ # @example
1634
+ # rule 'Execute rule when item is within a range' do
1635
+ # updated Alarm_Mode, to: 7..14
1636
+ # run { logger.info("Alarm Mode Updated to a value between 7 and 14")}
1637
+ # end
1638
+ #
1639
+ # @example
1640
+ # rule 'Execute rule when group is updated to any state' do
1641
+ # updated AlarmModes
1642
+ # triggered { |item| logger.info("Group #{item.name} updated")}
1643
+ # end
1644
+ #
1645
+ # @example
1646
+ # rule 'Execute rule when member of group is changed to any state' do
1647
+ # updated AlarmModes.members
1648
+ # triggered { |item| logger.info("Group item #{item.name} updated")}
1649
+ # end
1650
+ #
1651
+ # @example
1652
+ # rule 'Execute rule when member of group is changed to one of many states' do
1653
+ # updated AlarmModes.members, to: [7, 14]
1654
+ # triggered { |item| logger.info("Group item #{item.name} updated")}
1655
+ # end
1656
+ #
1657
+ # @example Works with procs
1658
+ # rule 'Execute rule when member of group is changed to an odd state' do
1659
+ # updated AlarmModes.members, to: proc { |t| t.odd? }
1660
+ # triggered { |item| logger.info("Group item #{item.name} updated")}
1661
+ # end
1662
+ #
1663
+ # @example Works with lambdas:
1664
+ # rule 'Execute rule when member of group is changed to an odd state' do
1665
+ # updated AlarmModes.members, to: -> t { t.odd? }
1666
+ # triggered { |item| logger.info("Group item #{item.name} updated")}
1667
+ # end
1668
+ #
1669
+ # @example Works with things as well
1670
+ # rule 'Execute rule when thing is updated' do
1671
+ # updated things['astro:sun:home'], :to => :uninitialized
1672
+ # run { |event| logger.info("Thing #{event.uid} status <trigger> to #{event.status}") }
1673
+ # end
1674
+ #
1675
+ def updated(*items, to: nil, attach: nil)
1676
+ updated = Updated.new(rule_triggers: @rule_triggers)
1677
+ @ruby_triggers << [:updated, items, { to: to }]
1678
+ items.map do |item|
1679
+ case item
1680
+ when Core::Things::Thing,
1681
+ Core::Things::ThingUID,
1682
+ Core::Items::Item,
1683
+ Core::Items::GroupItem::Members
1684
+ nil
1685
+ else
1686
+ raise ArgumentError, "items must be an Item, GroupItem::Members, Thing, or ThingUID"
1687
+ end
1688
+
1689
+ logger.trace("Creating updated trigger for item(#{item}) to(#{to})")
1690
+ [to].flatten.map do |to_state|
1691
+ updated.trigger(item: item, to: to_state, attach: attach)
1692
+ end
1693
+ end.flatten
1694
+ end
1695
+
1696
+ #
1697
+ # Create a trigger to watch a path
1698
+ #
1699
+ # It provides the ability to create a trigger on file and directory
1700
+ # changes.
1701
+ #
1702
+ # If a file or a path that does not exist is supplied as the argument
1703
+ # to watch, the parent directory will be watched and the file or
1704
+ # non-existent part of the supplied path will become the glob. For
1705
+ # example, if the path given is `/tmp/foo/bar` and `/tmp/foo`
1706
+ # exists but `bar` does not exist inside of of `/tmp/foo` then the
1707
+ # directory `/tmp/foo` will be watched for any files that match
1708
+ # `*/bar`.
1709
+ #
1710
+ # If the last part of the path contains any glob characters e.g.
1711
+ # `/tmp/foo/*bar`, the parent directory will be watched and the last
1712
+ # part of the path will be treated as if it was passed as the `glob`
1713
+ # argument. In other words, `watch '/tmp/foo/*bar'` is equivalent to
1714
+ # `watch '/tmp/foo', glob: '*bar'`
1715
+ #
1716
+ # ### Watching inside subdirectories
1717
+ #
1718
+ # Subdirectories aren't watched unless:
1719
+ # - One of the glob patterns include the recursive match pattern `**`, or
1720
+ # - The glob patterns include subdirectories, see examples below.
1721
+ #
1722
+ # The `event` passed to run blocks will be a {Events::WatchEvent}.
1723
+ #
1724
+ # @param [String] path Path to watch. Can be a directory or a file.
1725
+ # @param [String] glob
1726
+ # Limit events to paths matching this glob. Globs are matched using `glob`
1727
+ # [PathMatcher](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher(java.lang.String))
1728
+ # rules.
1729
+ # @param [Array<:created, :deleted, :modified>, :created, :deleted, :modified] for
1730
+ # Types of changes to watch for.
1731
+ # @param [Object] attach object to be attached to the trigger
1732
+ # @return [void]
1733
+ #
1734
+ # @example Watch `items` directory inside of the openHAB configuration path and log any changes.
1735
+ # rule 'watch directory' do
1736
+ # watch OpenHAB::Core.config_folder / 'items'
1737
+ # run { |event| logger.info("#{event.path.basename} - #{event.type}") }
1738
+ # end
1739
+ #
1740
+ # @example Watch `items` directory for files that end in `*.erb` and log any changes
1741
+ # rule 'watch directory' do
1742
+ # watch OpenHAB::Core.config_folder / 'items', glob: '*.erb'
1743
+ # run { |event| logger.info("#{event.path.basename} - #{event.type}") }
1744
+ # end
1745
+ #
1746
+ # @example Watch `items/foo.items` log any changes
1747
+ # rule 'watch directory' do
1748
+ # watch OpenHAB::Core.config_folder / 'items/foo.items'
1749
+ # run { |event| logger.info("#{event.path.basename} - #{event.type}") }
1750
+ # end
1751
+ #
1752
+ # @example Watch `items/*.items` log any changes
1753
+ # rule 'watch directory' do
1754
+ # watch OpenHAB::Core.config_folder / 'items/*.items'
1755
+ # run { |event| logger.info("#{event.path.basename} - #{event.type}") }
1756
+ # end
1757
+ #
1758
+ # @example Watch `items/*.items` for when items files are deleted or created (ignore changes)
1759
+ # rule 'watch directory' do
1760
+ # watch OpenHAB::Core.config_folder / 'items/*.items', for: [:deleted, :created]
1761
+ # run { |event| logger.info("#{event.path.basename} - #{event.type}") }
1762
+ # end
1763
+ #
1764
+ # @example Watch for changes inside all subdirs of `config_folder/automation/ruby/lib`
1765
+ # rule "Watch recursively" do
1766
+ # watch OpenHAB::Core.config_folder / "automation/ruby/lib/**"
1767
+ # run { |event| logger.info("#{event.path} - #{event.type}") }
1768
+ # end
1769
+ #
1770
+ # @example Recursively watch using subdirectory in glob
1771
+ # rule "Monitor changes in the list of installed gems" do
1772
+ # watch ENV['GEM_HOME'], glob: "gems/*"
1773
+ # run { |event| logger.info("#{event.path} - #{event.type}") }
1774
+ # end
1775
+ #
1776
+ # @example Recursively watch using glob option
1777
+ # rule "Watch recursively" do
1778
+ # watch OpenHAB::Core.config_folder / "automation/ruby/lib", glob: "**"
1779
+ # run { |event| logger.info("#{event.path} - #{event.type}") }
1780
+ # end
1781
+ #
1782
+ def watch(path, glob: "*", for: %i[created deleted modified], attach: nil)
1783
+ types = [binding.local_variable_get(:for)].flatten
1784
+
1785
+ WatchHandler::WatchTriggerHandlerFactory.instance # ensure it's registered
1786
+ trigger(WatchHandler::WATCH_TRIGGER_MODULE_ID, path: path.to_s,
1787
+ types: types.map(&:to_s),
1788
+ glob: glob.to_s,
1789
+ attach: attach)
1790
+ end
1791
+
1792
+ # @!endgroup
1793
+
1794
+ #
1795
+ # @return [String]
1796
+ #
1797
+ def inspect
1798
+ <<~TEXT.tr("\n", " ")
1799
+ #<OpenHAB::DSL::Rules::Builder: #{uid}
1800
+ triggers=#{triggers.inspect},
1801
+ run blocks=#{run.inspect},
1802
+ on_load=#{!@on_load.nil?},
1803
+ Trigger Conditions=#{trigger_conditions.inspect},
1804
+ Trigger UIDs=#{triggers.map(&:id).inspect},
1805
+ Attachments=#{attachments.inspect}
1806
+ >
1807
+ TEXT
1808
+ end
1809
+
1810
+ #
1811
+ # Process a rule based on the supplied configuration
1812
+ #
1813
+ # @param [String] script The source code of the rule
1814
+ #
1815
+ # @!visibility private
1816
+ def build(provider, script)
1817
+ return unless create_rule?
1818
+
1819
+ rule = AutomationRule.new(self)
1820
+ added_rule = add_rule(provider, rule)
1821
+ # add config so that MainUI can show the script
1822
+ added_rule.actions.first.configuration.put("type", "application/x-ruby")
1823
+ added_rule.actions.first.configuration.put("script", script) if script
1824
+
1825
+ process_on_load { |module_id| rule.execute(nil, { "module" => module_id }) }
1826
+
1827
+ added_rule
1828
+ end
1829
+
1830
+ private
1831
+
1832
+ # Calls the on_load block, with a delay if specified
1833
+ # @yield block to execute on load time
1834
+ # @yieldparam [String] module The module ID that identifies this on_load event
1835
+ def process_on_load
1836
+ return unless @on_load
1837
+
1838
+ if @on_load[:delay]
1839
+ after(@on_load[:delay]) { yield @on_load[:module] }
1840
+ else
1841
+ yield @on_load[:module]
1842
+ end
1843
+ end
1844
+
1845
+ # delegate to the caller's logger
1846
+ def logger
1847
+ @caller.send(:logger)
1848
+ end
1849
+
1850
+ #
1851
+ # Should a rule be created based on rule configuration
1852
+ #
1853
+ # @return [true,false] true if it should be created, false otherwise
1854
+ #
1855
+ def create_rule?
1856
+ return true if tags.include?("Script")
1857
+
1858
+ if !triggers?
1859
+ logger.warn "Rule '#{uid}' has no triggers, not creating rule"
1860
+ elsif !execution_blocks?
1861
+ logger.warn "Rule '#{uid}' has no execution blocks, not creating rule"
1862
+ elsif !enabled
1863
+ logger.trace "Rule '#{uid}' marked as disabled, not creating rule."
1864
+ else
1865
+ return true
1866
+ end
1867
+ false
1868
+ end
1869
+
1870
+ #
1871
+ # Check if the rule has any triggers
1872
+ #
1873
+ # @return [true,false] True if rule has triggers, false otherwise
1874
+ #
1875
+ def triggers?
1876
+ !(@on_load.nil? && triggers.empty?)
1877
+ end
1878
+
1879
+ #
1880
+ # Check if the rule has any execution blocks
1881
+ #
1882
+ # @return [true,false] True if rule has execution blocks, false otherwise
1883
+ #
1884
+ def execution_blocks?
1885
+ !(run || []).empty?
1886
+ end
1887
+
1888
+ #
1889
+ # Add a rule to the automation manager
1890
+ #
1891
+ # @param [org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule] rule to add
1892
+ #
1893
+ def add_rule(provider, rule)
1894
+ base_uid = rule.uid
1895
+ duplicate_index = 1
1896
+ while $rules.get(rule.uid)
1897
+ duplicate_index += 1
1898
+ rule.uid = "#{base_uid} (#{duplicate_index})"
1899
+ end
1900
+ logger.trace("Adding rule: #{rule}")
1901
+ unmanaged_rule = Core.automation_manager.add_unmanaged_rule(rule)
1902
+ provider.add(unmanaged_rule)
1903
+ unmanaged_rule
1904
+ end
1905
+
1906
+ #
1907
+ # Prevents or delays executions of rules to within a specified interval.
1908
+ # Debounce handling is done after the from/to/command types are filtered, but before only_if/not_if
1909
+ # guards.
1910
+ #
1911
+ # For a more detailed timing diagram, see {Debouncer}.
1912
+ #
1913
+ # Note the trailing edge debouncer delays the triggers so that they were postponed for the given interval after
1914
+ # the first detection of the trigger.
1915
+ #
1916
+ # @param (see Debouncer#initialize)
1917
+ #
1918
+ # @return [void]
1919
+ #
1920
+ # @see Debouncer Debouncer class
1921
+ # @see OpenHAB::DSL.debounce DSL.debounce method
1922
+ # @see only_every
1923
+ #
1924
+ # @!visibility private
1925
+ def debounce(for: 1.second, leading: false, idle_time: nil)
1926
+ raise ArgumentError, "Debounce guard can only be specified once" if @debounce_settings
1927
+
1928
+ interval = binding.local_variable_get(:for)
1929
+ # This hash structure must match the parameter signature for Debouncer.new
1930
+ @debounce_settings = { for: interval, leading: leading, idle_time: idle_time }
1931
+ end
1932
+ end
1933
+ end
1934
+ end
1935
+ end