openhab-scripting 4.47.0 → 5.0.0

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