openhab-jrubyscripting 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/lib/openhab/core/actions.rb +163 -0
  3. data/lib/openhab/core/entity_lookup.rb +144 -0
  4. data/lib/openhab/core/events/abstract_event.rb +17 -0
  5. data/lib/openhab/core/events/item_channel_link.rb +36 -0
  6. data/lib/openhab/core/events/item_command_event.rb +78 -0
  7. data/lib/openhab/core/events/item_event.rb +22 -0
  8. data/lib/openhab/core/events/item_state_changed_event.rb +52 -0
  9. data/lib/openhab/core/events/item_state_event.rb +51 -0
  10. data/lib/openhab/core/events/thing.rb +29 -0
  11. data/lib/openhab/core/events/thing_status_info_event.rb +53 -0
  12. data/lib/openhab/core/events.rb +10 -0
  13. data/lib/openhab/core/items/accepted_data_types.rb +29 -0
  14. data/lib/openhab/core/items/color_item.rb +52 -0
  15. data/lib/openhab/core/items/contact_item.rb +52 -0
  16. data/lib/openhab/core/items/date_time_item.rb +58 -0
  17. data/lib/openhab/core/items/dimmer_item.rb +148 -0
  18. data/lib/openhab/core/items/generic_item.rb +344 -0
  19. data/lib/openhab/core/items/group_item.rb +174 -0
  20. data/lib/openhab/core/items/image_item.rb +109 -0
  21. data/lib/openhab/core/items/location_item.rb +34 -0
  22. data/lib/openhab/core/items/metadata/hash.rb +390 -0
  23. data/lib/openhab/core/items/metadata/namespace_hash.rb +469 -0
  24. data/lib/openhab/core/items/metadata.rb +11 -0
  25. data/lib/openhab/core/items/number_item.rb +62 -0
  26. data/lib/openhab/core/items/numeric_item.rb +22 -0
  27. data/lib/openhab/core/items/persistence.rb +327 -0
  28. data/lib/openhab/core/items/player_item.rb +66 -0
  29. data/lib/openhab/core/items/proxy.rb +59 -0
  30. data/lib/openhab/core/items/registry.rb +66 -0
  31. data/lib/openhab/core/items/rollershutter_item.rb +68 -0
  32. data/lib/openhab/core/items/semantics/enumerable.rb +152 -0
  33. data/lib/openhab/core/items/semantics.rb +476 -0
  34. data/lib/openhab/core/items/state_storage.rb +53 -0
  35. data/lib/openhab/core/items/string_item.rb +28 -0
  36. data/lib/openhab/core/items/switch_item.rb +78 -0
  37. data/lib/openhab/core/items.rb +114 -0
  38. data/lib/openhab/core/lazy_array.rb +52 -0
  39. data/lib/openhab/core/profile_factory.rb +118 -0
  40. data/lib/openhab/core/script_handling.rb +55 -0
  41. data/lib/openhab/core/things/channel.rb +48 -0
  42. data/lib/openhab/core/things/channel_uid.rb +51 -0
  43. data/lib/openhab/core/things/item_channel_link.rb +33 -0
  44. data/lib/openhab/core/things/profile_callback.rb +52 -0
  45. data/lib/openhab/core/things/proxy.rb +69 -0
  46. data/lib/openhab/core/things/registry.rb +46 -0
  47. data/lib/openhab/core/things/thing.rb +194 -0
  48. data/lib/openhab/core/things.rb +22 -0
  49. data/lib/openhab/core/timer.rb +128 -0
  50. data/lib/openhab/core/types/comparable_type.rb +23 -0
  51. data/lib/openhab/core/types/date_time_type.rb +259 -0
  52. data/lib/openhab/core/types/decimal_type.rb +192 -0
  53. data/lib/openhab/core/types/hsb_type.rb +183 -0
  54. data/lib/openhab/core/types/increase_decrease_type.rb +34 -0
  55. data/lib/openhab/core/types/next_previous_type.rb +34 -0
  56. data/lib/openhab/core/types/numeric_type.rb +52 -0
  57. data/lib/openhab/core/types/on_off_type.rb +46 -0
  58. data/lib/openhab/core/types/open_closed_type.rb +41 -0
  59. data/lib/openhab/core/types/percent_type.rb +95 -0
  60. data/lib/openhab/core/types/play_pause_type.rb +38 -0
  61. data/lib/openhab/core/types/point_type.rb +117 -0
  62. data/lib/openhab/core/types/quantity_type.rb +327 -0
  63. data/lib/openhab/core/types/raw_type.rb +26 -0
  64. data/lib/openhab/core/types/refresh_type.rb +27 -0
  65. data/lib/openhab/core/types/rewind_fastforward_type.rb +38 -0
  66. data/lib/openhab/core/types/stop_move_type.rb +34 -0
  67. data/lib/openhab/core/types/string_type.rb +76 -0
  68. data/lib/openhab/core/types/type.rb +117 -0
  69. data/lib/openhab/core/types/un_def_type.rb +38 -0
  70. data/lib/openhab/core/types/up_down_type.rb +50 -0
  71. data/lib/openhab/core/types.rb +69 -0
  72. data/lib/openhab/core/uid.rb +36 -0
  73. data/lib/openhab/core.rb +85 -0
  74. data/lib/openhab/core_ext/java/duration.rb +115 -0
  75. data/lib/openhab/core_ext/java/local_date.rb +93 -0
  76. data/lib/openhab/core_ext/java/local_time.rb +106 -0
  77. data/lib/openhab/core_ext/java/month.rb +59 -0
  78. data/lib/openhab/core_ext/java/month_day.rb +105 -0
  79. data/lib/openhab/core_ext/java/period.rb +103 -0
  80. data/lib/openhab/core_ext/java/temporal_amount.rb +34 -0
  81. data/lib/openhab/core_ext/java/time.rb +58 -0
  82. data/lib/openhab/core_ext/java/unit.rb +15 -0
  83. data/lib/openhab/core_ext/java/zoned_date_time.rb +116 -0
  84. data/lib/openhab/core_ext/ruby/array.rb +21 -0
  85. data/lib/openhab/core_ext/ruby/class.rb +15 -0
  86. data/lib/openhab/core_ext/ruby/date.rb +89 -0
  87. data/lib/openhab/core_ext/ruby/numeric.rb +190 -0
  88. data/lib/openhab/core_ext/ruby/range.rb +70 -0
  89. data/lib/openhab/core_ext/ruby/time.rb +104 -0
  90. data/lib/openhab/core_ext.rb +18 -0
  91. data/lib/openhab/dsl/events/watch_event.rb +18 -0
  92. data/lib/openhab/dsl/events.rb +9 -0
  93. data/lib/openhab/dsl/gems.rb +3 -0
  94. data/lib/openhab/dsl/items/builder.rb +618 -0
  95. data/lib/openhab/dsl/items/ensure.rb +93 -0
  96. data/lib/openhab/dsl/items/timed_command.rb +236 -0
  97. data/lib/openhab/dsl/rules/automation_rule.rb +308 -0
  98. data/lib/openhab/dsl/rules/builder.rb +1373 -0
  99. data/lib/openhab/dsl/rules/guard.rb +115 -0
  100. data/lib/openhab/dsl/rules/name_inference.rb +160 -0
  101. data/lib/openhab/dsl/rules/property.rb +76 -0
  102. data/lib/openhab/dsl/rules/rule_triggers.rb +96 -0
  103. data/lib/openhab/dsl/rules/terse.rb +63 -0
  104. data/lib/openhab/dsl/rules/triggers/changed.rb +169 -0
  105. data/lib/openhab/dsl/rules/triggers/channel.rb +57 -0
  106. data/lib/openhab/dsl/rules/triggers/command.rb +107 -0
  107. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +161 -0
  108. data/lib/openhab/dsl/rules/triggers/conditions/proc.rb +164 -0
  109. data/lib/openhab/dsl/rules/triggers/cron/cron.rb +195 -0
  110. data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +127 -0
  111. data/lib/openhab/dsl/rules/triggers/trigger.rb +56 -0
  112. data/lib/openhab/dsl/rules/triggers/updated.rb +130 -0
  113. data/lib/openhab/dsl/rules/triggers/watch/watch.rb +55 -0
  114. data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +155 -0
  115. data/lib/openhab/dsl/rules/triggers.rb +12 -0
  116. data/lib/openhab/dsl/rules.rb +29 -0
  117. data/lib/openhab/dsl/script_handling.rb +55 -0
  118. data/lib/openhab/dsl/things/builder.rb +263 -0
  119. data/lib/openhab/dsl/thread_local.rb +48 -0
  120. data/lib/openhab/dsl/timer_manager.rb +191 -0
  121. data/lib/openhab/dsl/version.rb +9 -0
  122. data/lib/openhab/dsl.rb +686 -0
  123. data/lib/openhab/log.rb +348 -0
  124. data/lib/openhab/osgi.rb +70 -0
  125. data/lib/openhab/rspec/configuration.rb +56 -0
  126. data/lib/openhab/rspec/example_group.rb +90 -0
  127. data/lib/openhab/rspec/helpers.rb +439 -0
  128. data/lib/openhab/rspec/hooks.rb +93 -0
  129. data/lib/openhab/rspec/jruby.rb +46 -0
  130. data/lib/openhab/rspec/karaf.rb +811 -0
  131. data/lib/openhab/rspec/mocks/bundle_install_support.rb +25 -0
  132. data/lib/openhab/rspec/mocks/bundle_resolver.rb +30 -0
  133. data/lib/openhab/rspec/mocks/event_admin.rb +146 -0
  134. data/lib/openhab/rspec/mocks/metadata_provider.rb +75 -0
  135. data/lib/openhab/rspec/mocks/persistence_service.rb +140 -0
  136. data/lib/openhab/rspec/mocks/safe_caller.rb +40 -0
  137. data/lib/openhab/rspec/mocks/synchronous_executor.rb +56 -0
  138. data/lib/openhab/rspec/mocks/thing_handler.rb +76 -0
  139. data/lib/openhab/rspec/mocks/timer.rb +95 -0
  140. data/lib/openhab/rspec/openhab/core/actions.rb +26 -0
  141. data/lib/openhab/rspec/openhab/core/items/proxy.rb +27 -0
  142. data/lib/openhab/rspec/openhab/core/things/proxy.rb +27 -0
  143. data/lib/openhab/rspec/shell.rb +31 -0
  144. data/lib/openhab/rspec/suspend_rules.rb +60 -0
  145. data/lib/openhab/rspec.rb +17 -0
  146. data/lib/openhab/yard/cli/stats.rb +23 -0
  147. data/lib/openhab/yard/code_objects/group_object.rb +17 -0
  148. data/lib/openhab/yard/code_objects/java/base.rb +31 -0
  149. data/lib/openhab/yard/code_objects/java/class_object.rb +11 -0
  150. data/lib/openhab/yard/code_objects/java/field_object.rb +15 -0
  151. data/lib/openhab/yard/code_objects/java/interface_object.rb +15 -0
  152. data/lib/openhab/yard/code_objects/java/package_object.rb +11 -0
  153. data/lib/openhab/yard/code_objects/java/proxy.rb +23 -0
  154. data/lib/openhab/yard/handlers/jruby/base.rb +49 -0
  155. data/lib/openhab/yard/handlers/jruby/class_handler.rb +18 -0
  156. data/lib/openhab/yard/handlers/jruby/constant_handler.rb +18 -0
  157. data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +27 -0
  158. data/lib/openhab/yard/handlers/jruby/mixin_handler.rb +23 -0
  159. data/lib/openhab/yard/html_helper.rb +44 -0
  160. data/lib/openhab/yard/tags/constant_directive.rb +20 -0
  161. data/lib/openhab/yard/tags/group_directive.rb +24 -0
  162. data/lib/openhab/yard/tags/library.rb +3 -0
  163. data/lib/openhab/yard.rb +32 -0
  164. metadata +504 -0
@@ -0,0 +1,1373 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ require_relative "property"
6
+ require_relative "guard"
7
+ require_relative "rule_triggers"
8
+
9
+ Dir[File.expand_path("triggers/*.rb", __dir__)].sort.each do |f|
10
+ require f
11
+ end
12
+
13
+ module OpenHAB
14
+ module DSL
15
+ #
16
+ # Creates and manages OpenHAB Rules
17
+ #
18
+ module Rules
19
+ #
20
+ # Rule configuration for OpenHAB Rules engine
21
+ #
22
+ class Builder
23
+ include Core::EntityLookup
24
+ include DSL
25
+ prepend Triggers
26
+ extend Property
27
+ extend Forwardable
28
+
29
+ delegate %i[triggers trigger_conditions attachments] => :@rule_triggers
30
+
31
+ # @!visibility private
32
+ # @return [Array] Of trigger guards
33
+ attr_accessor :guard
34
+
35
+ # @!visibility private
36
+ # @return [Object] object that invoked rule method
37
+ attr_accessor :caller
38
+
39
+ # @!visibility private
40
+ # @return [Array] Of trigger definitions as passed in Ruby
41
+ attr_reader :ruby_triggers
42
+
43
+ # @!visibility private
44
+ Run = Struct.new(:block)
45
+
46
+ # @!visibility private
47
+ Script = Struct.new(:block)
48
+
49
+ # @!visibility private
50
+ Trigger = Struct.new(:block)
51
+
52
+ # @!visibility private
53
+ Otherwise = Struct.new(:block)
54
+
55
+ # @!visibility private
56
+ Delay = Struct.new(:duration)
57
+
58
+ # @!group Execution Blocks
59
+
60
+ #
61
+ # @!method run
62
+ #
63
+ # Add a block that will be passed event data.
64
+ #
65
+ # The run property is the automation code that is executed when a rule
66
+ # is triggered. This property accepts a block of code and executes it.
67
+ # The block is automatically passed an event object which can be used
68
+ # to access multiple properties about the triggering event. The code
69
+ # for the automation can be entirely within the run block and can call
70
+ # methods defined in the Ruby script.
71
+ #
72
+ # @yieldparam [Core::Events::AbstractEvent] event
73
+ # @return [void]
74
+ #
75
+ # @example `{}` style used for single line blocks.
76
+ # rule 'Access Event Properties' do
77
+ # changed TestSwitch
78
+ # run { |event| logger.info("#{event.item.name} triggered from #{event.was} to #{event.state}") }
79
+ # end
80
+ #
81
+ # @example `do/end` style used for multi-line blocks.
82
+ # rule 'Multi Line Run Block' do
83
+ # changed TestSwitch
84
+ # run do |event|
85
+ # logger.info("#{event.item.name} triggered")
86
+ # logger.info("from #{event.was}") if event.was?
87
+ # logger.info("to #{event.state}") if event.state?
88
+ # end
89
+ # end
90
+ #
91
+ # @example Rules can have multiple run blocks and they are executed in order. Useful when used in combination with {#delay}.
92
+ # rule 'Multiple Run Blocks' do
93
+ # changed TestSwitch
94
+ # run { |event| logger.info("#{event.item.name} triggered") }
95
+ # run { |event| logger.info("from #{event.was}") if event.was? }
96
+ # run { |event| logger.info("to #{event.state}") if event.state? }
97
+ # end
98
+ #
99
+ prop_array :run, array_name: :run_queue, wrapper: Run
100
+
101
+ prop_array :script, array_name: :run_queue, wrapper: Script
102
+
103
+ #
104
+ # @!method triggered
105
+ #
106
+ # Add a block that will be passed the triggering item.
107
+ #
108
+ # This property is the same as the {#run} property except rather than
109
+ # passing an event object to the automation block the triggered item is
110
+ # passed. This enables optimizations for simple cases and supports
111
+ # Ruby's [pretzel colon `&:` operator.](https://medium.com/@dcjones/the-pretzel-colon-75df46dde0c7).
112
+ #
113
+ # @yieldparam [Item] item
114
+ # @return [void]
115
+ #
116
+ # @example
117
+ # rule "motion sensor triggered" do
118
+ # changed MotionSensor.members, to: :OPEN
119
+ # triggered do |item|
120
+ # logger.info("#{item.name} detected motion")
121
+ # end
122
+ # end
123
+ #
124
+ # @example
125
+ # rule 'Triggered has access directly to item triggered' do
126
+ # changed TestSwitch
127
+ # triggered { |item| logger.info("#{item.name} triggered") }
128
+ # end
129
+ #
130
+ # @example Triggered items are highly useful when working with groups
131
+ # # Switches is a group of Switch items
132
+ # rule 'Triggered item is item changed when a group item is changed.' do
133
+ # changed Switches.members
134
+ # triggered { |item| logger.info("Switch #{item.name} changed to #{item.state}")}
135
+ # end
136
+ #
137
+ # rule 'Turn off any switch that changes' do
138
+ # changed Switches.members
139
+ # triggered(&:off)
140
+ # end
141
+ #
142
+ # @example Like other execution blocks, multiple triggered blocks are supported in a single rule
143
+ # rule 'Turn a switch off and log it, 5 seconds after turning it on' do
144
+ # changed Switches.members, to: ON
145
+ # delay 5.seconds
146
+ # triggered(&:off)
147
+ # triggered {|item| logger.info("#{item.label} turned off") }
148
+ # end
149
+ prop_array :triggered, array_name: :run_queue, wrapper: Trigger
150
+
151
+ #
152
+ # @!method delay(duration)
153
+ #
154
+ # Add a wait between or after run blocks.
155
+ #
156
+ # The delay property is a non thread-blocking element that is executed
157
+ # after, before, or between run blocks.
158
+ #
159
+ # @param [java.time.temporal.TemporalAmount] duration How long to delay for.
160
+ # @return [void]
161
+ #
162
+ # @example
163
+ # rule "delay execution" do
164
+ # changed MotionSensor, to: CLOSED
165
+ # delay 5.seconds
166
+ # run { Light.off }
167
+ # end
168
+ #
169
+ # @example
170
+ # rule 'Delay sleeps between execution elements' do
171
+ # on_start
172
+ # run { logger.info("Sleeping") }
173
+ # delay 5.seconds
174
+ # run { logger.info("Awake") }
175
+ # end
176
+ #
177
+ # @example Like other execution blocks, multiple can exist in a single rule.
178
+ # rule 'Multiple delays can exist in a rule' do
179
+ # on_start
180
+ # run { logger.info("Sleeping") }
181
+ # delay 5.seconds
182
+ # run { logger.info("Sleeping Again") }
183
+ # delay 5.seconds
184
+ # run { logger.info("Awake") }
185
+ # end
186
+ #
187
+ # @example You can use Ruby code in your rule across multiple execution blocks like a run and a delay.
188
+ # rule 'Dim a switch on system startup over 100 seconds' do
189
+ # on_start
190
+ # 100.times do
191
+ # run { DimmerSwitch.dim }
192
+ # delay 1.second
193
+ # end
194
+ # end
195
+ #
196
+ prop_array :delay, array_name: :run_queue, wrapper: Delay
197
+
198
+ #
199
+ # @!method otherwise
200
+ #
201
+ # Add a block that will be passed event data, to be run if guards are
202
+ # not satisfied.
203
+ #
204
+ # The {otherwise} property is the automation code that is executed when
205
+ # a rule is triggered and guards are not satisfied. This property
206
+ # accepts a block of code and executes it. The block is automatically
207
+ # passed an event object which can be used to access multiple
208
+ # properties about the triggering event.
209
+ #
210
+ # @yieldparam [Core::Events::AbstractEvent] event
211
+ #
212
+ # @example
213
+ # rule 'Turn switch ON or OFF based on value of another switch' do
214
+ # on_start
215
+ # run { TestSwitch << ON }
216
+ # otherwise { TestSwitch << OFF }
217
+ # only_if { OtherSwitch.on? }
218
+ # end
219
+ #
220
+ prop_array :otherwise, array_name: :run_queue, wrapper: Otherwise
221
+
222
+ # @!group Configuration
223
+
224
+ #
225
+ # @!method uid(id)
226
+ #
227
+ # Set the rule's UID.
228
+ #
229
+ # @param [String] id
230
+ # @return [void]
231
+ #
232
+ prop :uid
233
+
234
+ #
235
+ # @!method name(value)
236
+ #
237
+ # Set the rule's name.
238
+ #
239
+ # @param [String] value
240
+ # @return [void]
241
+ #
242
+ prop :name
243
+
244
+ #
245
+ # @!method description(value)
246
+ #
247
+ # Set the rule's description.
248
+ #
249
+ # @param [String] value
250
+ # @return [void]
251
+ #
252
+ prop :description
253
+
254
+ #
255
+ # @!method tags(tags)
256
+ #
257
+ # Set the rule's tags.
258
+ #
259
+ # @param [String, Class, Array<String, Class>] tags
260
+ # @return [void]
261
+ #
262
+ # @example
263
+ # rule "tagged rule" do
264
+ # tags "lighting", "security"
265
+ # end
266
+ #
267
+ prop :tags
268
+
269
+ #
270
+ # @!method enabled(value)
271
+ #
272
+ # Enable or disable the rule from executing
273
+ #
274
+ # @param [true,false] value
275
+ # @return [void]
276
+ #
277
+ # @example
278
+ # rule "disabled rule" do
279
+ # enabled(false)
280
+ # end
281
+ #
282
+ prop :enabled
283
+
284
+ # @!group Guards
285
+ # Guards exist to only permit rules to run if certain conditions are
286
+ # satisfied. Think of these as declarative `if` statements that keep
287
+ # the run block free of conditional logic, although you can of course
288
+ # still use conditional logic in run blocks if you prefer.
289
+ #
290
+ # ### Guard Combination
291
+ #
292
+ # {#only_if} and {#not_if} can be used on the same rule. Both must be
293
+ # satisfied for a rule to execute.
294
+ #
295
+ # @example
296
+ # rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is OFF and Door is CLOSED" do
297
+ # changed LightSwitch, to: ON
298
+ # run { OutsideDimmer << 50 }
299
+ # only_if { Door.closed? }
300
+ # not_if { OtherSwitch.on? }
301
+ # end
302
+ #
303
+
304
+ #
305
+ # @!method between(range)
306
+ #
307
+ # Only execute rule if current time is between supplied time ranges.
308
+ #
309
+ # If the range is of strings, it will be parsed to an appropriate time class.
310
+ #
311
+ # @param [Range] range
312
+ # @return [void]
313
+ #
314
+ # @example
315
+ # rule "Between guard" do
316
+ # changed MotionSensor, to: OPEN
317
+ # between "6:05".."14:05:05" # Include end
318
+ # run { Light.on }
319
+ # end
320
+ #
321
+ # @example
322
+ # rule "Between guard" do
323
+ # changed MotionSensor, to: OPEN
324
+ # between "6:05".."14:05:05" # Excludes end second
325
+ # run { Light.on }
326
+ # end
327
+ #
328
+ # @example
329
+ # rule "Between guard" do
330
+ # changed MotionSensor, to: OPEN
331
+ # between LocalTime.of(6, 5)..LocalTime.of(14, 15, 5)
332
+ # run { Light.on }
333
+ # end
334
+ #
335
+ # @example String of {LocalTime}
336
+ # rule 'Log an entry if started between 3:30:04 and midnight using strings' do
337
+ # on_start
338
+ # run { logger.info ("Started at #{LocalTime.now}")}
339
+ # between '3:30:04'..LocalTime::MIDNIGHT
340
+ # end
341
+ #
342
+ # @example {LocalTime}
343
+ # rule 'Log an entry if started between 3:30:04 and midnight using LocalTime objects' do
344
+ # on_start
345
+ # run { logger.info ("Started at #{LocalTime.now}")}
346
+ # between LocalTime.of(3, 30, 4)..LocalTime::MIDNIGHT
347
+ # end
348
+ #
349
+ # @example String of {MonthDay}
350
+ # rule 'Log an entry if started between March 9th and April 10ths' do
351
+ # on_start
352
+ # run { logger.info ("Started at #{Time.now}")}
353
+ # between '03-09'..'04-10'
354
+ # end
355
+ #
356
+ # @example {MonthDay}
357
+ # rule 'Log an entry if started between March 9th and April 10ths' do
358
+ # on_start
359
+ # run { logger.info ("Started at #{Time.now}")}
360
+ # between MonthDay.of(03,09)..'04-06'
361
+ # end
362
+ #
363
+ prop :between
364
+
365
+ #
366
+ # @!method only_if
367
+ #
368
+ # {only_if} allows rule execution when the block's is true and prevents it when it's false.
369
+ #
370
+ # @yieldparam [Core::Events::AbstractEvent] event The event data that is about to trigger the rule.
371
+ # @yieldreturn [Boolean] A value indicating if the rule should run.
372
+ # @return [void]
373
+ #
374
+ # @example
375
+ # rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is also ON" do
376
+ # changed LightSwitch, to: ON
377
+ # run { OutsideDimmer << 50 }
378
+ # only_if { OtherSwitch.on? }
379
+ # end
380
+ #
381
+ # @example Multiple {only_if} statements can be used and *all* must be true for the rule to run.
382
+ # rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is also ON and Door is closed" do
383
+ # changed LightSwitch, to: ON
384
+ # run { OutsideDimmer << 50 }
385
+ # only_if { OtherSwitch.on? }
386
+ # only_if { Door.closed? }
387
+ # end
388
+ #
389
+ # @example Guards have access to event information.
390
+ # rule "Set OutsideDimmer to 50% if any switch in group Switches starting with Outside is switched On" do
391
+ # changed Switches.items, to: ON
392
+ # run { OutsideDimmer << 50 }
393
+ # only_if { |event| event.item.name.start_with?("Outside") }
394
+ # end
395
+ #
396
+ prop_array(:only_if) do |item|
397
+ unless item.is_a?(Proc) || [item].flatten.all? { |it| it.respond_to?(:truthy?) }
398
+ raise ArgumentError, "Object passed to only_if must be a proc"
399
+ end
400
+ end
401
+
402
+ #
403
+ # @!method not_if
404
+ #
405
+ # {not_if} prevents execution of rules when the block's result is true and allows it when it's true.
406
+ #
407
+ # @yieldparam [Core::Events::AbstractEvent] event The event data that is about to trigger the rule.
408
+ # @yieldreturn [Boolean] A value indicating if the rule should _not_ run.
409
+ # @return [void]
410
+ #
411
+ # @example
412
+ # rule "Set OutsideDimmer to 50% if LightSwtich turned on and OtherSwitch is OFF" do
413
+ # changed LightSwitch, to: ON
414
+ # run { OutsideDimmer << 50 }
415
+ # not_if { OtherSwitch.on? }
416
+ # end
417
+ #
418
+ # @example Multiple {not_if} statements can be used and if **any** of them are not satisfied the rule will not run.
419
+ # rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is OFF and Door is not CLOSED" do
420
+ # changed LightSwitch, to: ON
421
+ # run { OutsideDimmer << 50 }
422
+ # not_if { OtherSwitch.on? }
423
+ # not_if { Door.closed? }
424
+ # end
425
+ #
426
+ prop_array(:not_if) do |item|
427
+ unless item.is_a?(Proc) || [item].flatten.all? { |it| it.respond_to?(:truthy?) }
428
+ raise ArgumentError, "Object passed to not_if must be a proc"
429
+ end
430
+ end
431
+
432
+ # @!endgroup
433
+
434
+ # @!visibility private
435
+ #
436
+ # Create a new DSL
437
+ #
438
+ # @param [Object] caller_binding The object initializing this configuration.
439
+ # Used to execute within the object's context
440
+ #
441
+ def initialize(caller_binding)
442
+ @rule_triggers = RuleTriggers.new
443
+ @caller = caller_binding.eval "self"
444
+ @ruby_triggers = []
445
+ enabled(true)
446
+ on_start(false)
447
+ tags([])
448
+ end
449
+
450
+ # @!group Triggers
451
+ # Triggers specify what will cause the execution blocks to run.
452
+ # Multiple triggers can be defined within the same rule.
453
+ #
454
+ # ### Trigger Attachments
455
+ #
456
+ # All triggers support event attachments that enable the association
457
+ # of an object to a trigger. This enables one to use the same rule
458
+ # and take different actions if the trigger is different. The
459
+ # attached object is passed to the execution block through the
460
+ # {Core::Events::AbstractEvent#attachment} accessor.
461
+ #
462
+ # @note The trigger attachment feature is not available for UI rules.
463
+ #
464
+ # @example
465
+ # rule 'Set Dark switch at sunrise and sunset' do
466
+ # channel 'astro:sun:home:rise#event', attach: OFF
467
+ # channel 'astro:sun:home:set#event', attach: ON
468
+ # run { |event| Dark << event.attachment }
469
+ # end
470
+
471
+ #
472
+ # Creates a channel trigger
473
+ #
474
+ # The channel trigger executes rule when a specific channel is triggered. The syntax
475
+ # supports one or more channels with one or more triggers. `thing` is an optional
476
+ # parameter that makes it easier to set triggers on multiple channels on the same thing.
477
+ #
478
+ # @param [String, Core::Things::Channel, Core::Things::ChannelUID] channels
479
+ # channels to create triggers for in form of 'binding_id:type_id:thing_id#channel_id'
480
+ # or 'channel_id' if thing is provided.
481
+ # @param [String, Core::Things::Thing, Core::Things::ThingUID] thing
482
+ # Thing(s) to create trigger for if not specified with the channel.
483
+ # @param [String, Array<String>] triggered
484
+ # Only execute rule if the event on the channel matches this/these event/events.
485
+ # @param [Object] attach object to be attached to the trigger
486
+ # @return [void]
487
+ #
488
+ # @example
489
+ # rule "Execute rule when channel is triggered" do
490
+ # channel "astro:sun:home:rise#event"
491
+ # run { logger.info("Channel triggered") }
492
+ # end
493
+ # # The above is the same as each of the below
494
+ #
495
+ # rule "Execute rule when channel is triggered" do
496
+ # channel "rise#event", thing: "astro:sun:home"
497
+ # run { logger.info("Channel triggered") }
498
+ # end
499
+ #
500
+ # rule "Execute rule when channel is triggered" do
501
+ # channel "rise#event", thing: things["astro:sun:home"]
502
+ # run { logger.info("Channel triggered") }
503
+ # end
504
+ #
505
+ # rule "Execute rule when channel is triggered" do
506
+ # channel "rise#event", thing: things["astro:sun:home"].uid
507
+ # run { logger.info("Channel triggered") }
508
+ # end
509
+ #
510
+ # rule "Execute rule when channel is triggered" do
511
+ # channel "rise#event", thing: ["astro:sun:home"]
512
+ # run { logger.info("Channel triggered") }
513
+ # end
514
+ #
515
+ # rule "Execute rule when channel is triggered" do
516
+ # channel things["astro:sun:home"].channels["rise#event"]
517
+ # run { logger.info("Channel triggered") }
518
+ # end
519
+ #
520
+ # rule "Execute rule when channel is triggered" do
521
+ # channel things["astro:sun:home"].channels["rise#event"].uid
522
+ # run { logger.info("Channel triggered") }
523
+ # end
524
+ #
525
+ # @example
526
+ # rule "Rule provides access to channel trigger events in run block" do
527
+ # channel "astro:sun:home:rise#event", triggered: 'START'
528
+ # run { |trigger| logger.info("Channel(#{trigger.channel}) triggered event: #{trigger.event}") }
529
+ # end
530
+ #
531
+ # @example
532
+ # rule "Keypad Code Received test" do
533
+ # channel "mqtt:homie300:mosquitto:backgate:keypad#code"
534
+ # run do |event|
535
+ # logger.info("Received keycode from #{event.channel.thing.uid.id}")
536
+ # end
537
+ # end
538
+ #
539
+ # @example
540
+ # rule "Rules support multiple channels" do
541
+ # channel "rise#event", "set#event", thing: "astro:sun:home"
542
+ # run { logger.info("Channel triggered") }
543
+ # end
544
+ #
545
+ # @example
546
+ # rule "Rules support multiple channels and triggers" do
547
+ # channel "rise#event", "set#event", thing: "astro:sun:home", triggered: ["START", "STOP"]
548
+ # run { logger.info("Channel triggered") }
549
+ # end
550
+ #
551
+ # @example
552
+ # rule "Rules support multiple things" do
553
+ # channel "keypad#code", thing: ["mqtt:homie300:keypad1", "mqtt:homie300:keypad2"]
554
+ # run { logger.info("Channel triggered") }
555
+ # end
556
+ #
557
+ def channel(*channels, thing: nil, triggered: nil, attach: nil)
558
+ channel_trigger = Channel.new(rule_triggers: @rule_triggers)
559
+ flattened_channels = Channel.channels(channels: channels, thing: thing)
560
+ triggers = [triggered].flatten
561
+ @ruby_triggers << [:channel, flattened_channels, { triggers: triggers }]
562
+ flattened_channels.each do |channel|
563
+ triggers.each do |trigger|
564
+ channel_trigger.trigger(channel: channel, trigger: trigger, attach: attach)
565
+ end
566
+ end
567
+ end
568
+
569
+ #
570
+ # Creates a channel linked trigger
571
+ #
572
+ # @param [Object] attach object to be attached to the trigger
573
+ # @return [void]
574
+ #
575
+ # @example
576
+ # rule "channel linked" do
577
+ # channel_linked
578
+ # run do |event|
579
+ # logger.info("#{event.link.item.name} linked to #{event.link.channel_uid}.")
580
+ # end
581
+ # end
582
+ def channel_linked(attach: nil)
583
+ @ruby_triggers << [:channel_linked]
584
+ trigger("core.GenericEventTrigger", eventTopic: "openhab/links/*/added",
585
+ eventTypes: "ItemChannelLinkAddedEvent", attach: attach)
586
+ end
587
+
588
+ #
589
+ # Creates a channel unlinked trigger
590
+ #
591
+ # Note that the item or the thing it's linked to may no longer exist,
592
+ # so if you try to access those objects they'll be nil.
593
+ #
594
+ # @param [Object] attach object to be attached to the trigger
595
+ # @return [void]
596
+ #
597
+ # @example
598
+ # rule "channel unlinked" do
599
+ # channel_unlinked
600
+ # run do |event|
601
+ # logger.info("#{event.link.item_name} unlinked from #{event.link.channel_uid}.")
602
+ # end
603
+ # end
604
+ def channel_unlinked(attach: nil)
605
+ @ruby_triggers << [:channel_linked]
606
+ trigger("core.GenericEventTrigger", eventTopic: "openhab/links/*/removed",
607
+ eventTypes: "ItemChannelLinkRemovedEvent", attach: attach)
608
+ end
609
+
610
+ #
611
+ # Creates a trigger when an item, member of a group, or a thing changed
612
+ # states.
613
+ #
614
+ # When the changed element is a {Core::Things::Thing Thing}, the `from`
615
+ # and `to` values will accept symbols and strings, where the symbol'
616
+ # matches the
617
+ # [supported status](https://www.openhab.org/docs/concepts/things.html#thing-status).
618
+ #
619
+ # The `event` passed to run blocks will be an
620
+ # {Core::Events::ItemStateChangedEvent} or a
621
+ # {Core::Events::ThingStatusInfoChangedEvent} depending on if the
622
+ # triggering element was an item or a thing.
623
+ #
624
+ # @param [Item, GroupItem::Members, Thing] items Objects to create trigger for.
625
+ # @param [State, Array<State>, Range, Proc] from
626
+ # Only execute rule if previous state matches `from` state(s).
627
+ # @param [State, Array<State>, Range, Proc] to State(s) for
628
+ # Only execute rule if new state matches `to` state(s).
629
+ # @param [java.time.temporal.TemporalAmount] for
630
+ # Duration item must remain in the same state before executing the execution blocks.
631
+ # @param [Object] attach object to be attached to the trigger
632
+ # @return [void]
633
+ #
634
+ # @example Multiple items can be separated with a comma:
635
+ # rule "Execute rule when either sensor changed" do
636
+ # changed FrontMotion_Sensor, RearMotion_Sensor
637
+ # run { |event| logger.info("Motion detected by #{event.item.name}") }
638
+ # end
639
+ #
640
+ # @example Group member trigger
641
+ # rule "Execute rule when member changed" do
642
+ # changed Sensors.members
643
+ # run { |event| logger.info("Motion detected by #{event.item.name}") }
644
+ # end
645
+ #
646
+ # @example `for` parameter can be a proc too:
647
+ # Alarm_Delay << 20
648
+ #
649
+ # rule "Execute rule when item is changed for specified duration" do
650
+ # changed Alarm_Mode, for: -> { Alarm_Delay.state }
651
+ # run { logger.info("Alarm Mode Updated") }
652
+ # end
653
+ #
654
+ # @example You can optionally provide `from` and `to` states to restrict the cases in which the rule executes:
655
+ # rule "Execute rule when item is changed to specific number, from specific number, for specified duration" do
656
+ # changed Alarm_Mode, from: 8, to: [14,12], for: 12.seconds
657
+ # run { logger.info("Alarm Mode Updated") }
658
+ # end
659
+ #
660
+ # @example Works with ranges:
661
+ # rule "Execute when item changed to a range of numbers, from a range of numbers, for specified duration" do
662
+ # changed Alarm_Mode, from: 8..10, to: 12..14, for: 12.seconds
663
+ # run { logger.info("Alarm Mode Updated") }
664
+ # end
665
+ #
666
+ # @example Works with endless ranges:
667
+ # rule "Execute rule when item is changed to any number greater than 12"
668
+ # changed Alarm_Mode, to: (12..) # Parenthesis required for endless ranges
669
+ # run { logger.info("Alarm Mode Updated") }
670
+ # end
671
+ #
672
+ # @example Works with procs:
673
+ # rule "Execute when item state is changed from an odd number, to an even number, for specified duration" do
674
+ # changed Alarm_Mode, from: proc { |from| from.odd? }, to: proc {|to| to.even? }, for: 12.seconds
675
+ # run { logger.info("Alarm Mode Updated") }
676
+ # end
677
+ #
678
+ # @example Works with lambdas:
679
+ # rule "Execute when item state is changed from an odd number, to an even number, for specified duration" do
680
+ # changed Alarm_Mode, from: -> from { from.odd? }, to: -> to { to.even? }, for: 12.seconds
681
+ # run { logger.info("Alarm Mode Updated") }
682
+ # end
683
+ #
684
+ # @example Works with Things:
685
+ # rule "Execute rule when thing is changed" do
686
+ # changed things["astro:sun:home"], :from => :online, :to => :uninitialized
687
+ # run { |event| logger.info("Thing #{event.uid} status <trigger> to #{event.status}") }
688
+ # end
689
+ #
690
+ # @example Real World Example
691
+ # rule "Log (or notify) when an exterior door is left open for more than 5 minutes" do
692
+ # changed ExteriorDoors.members, to: OPEN, for: 5.minutes
693
+ # triggered {|door| logger.info("#{door.name} has been left open!") }
694
+ # end
695
+ #
696
+ def changed(*items, to: nil, from: nil, for: nil, attach: nil)
697
+ changed = Changed.new(rule_triggers: @rule_triggers)
698
+ # for is a reserved word in ruby, so use local_variable_get :for
699
+ duration = binding.local_variable_get(:for)
700
+
701
+ from = [nil] if from.nil?
702
+ to = [nil] if to.nil?
703
+
704
+ @ruby_triggers << [:changed, items, { to: to, from: from, duration: duration }]
705
+ items.each do |item|
706
+ logger.trace("Creating changed trigger for entity(#{item}), to(#{to.inspect}), from(#{from.inspect})")
707
+
708
+ Array.wrap(from).each do |from_state|
709
+ Array.wrap(to).each do |to_state|
710
+ changed.trigger(item: item, from: from_state, to: to_state, duration: duration, attach: attach)
711
+ end
712
+ end
713
+ end
714
+ end
715
+
716
+ #
717
+ # Create a cron trigger
718
+ #
719
+ # @overload cron(expression, attach: nil)
720
+ # @param [String, nil] expression [OpenHAB style cron expression](https://www.openhab.org/docs/configuration/rules-dsl.html#time-based-triggers)
721
+ # @param [Object] attach object to be attached to the trigger
722
+ #
723
+ # @example Using a cron expression
724
+ # rule "cron expression" do
725
+ # cron "43 46 13 ? * ?"
726
+ # run { Light.on }
727
+ # end
728
+ #
729
+ # @overload cron(second: nil, minute: nil, hour: nil, dom: nil, month: nil, dow: nil, year: nil, attach: nil)
730
+ # The trigger can be created by specifying each field as keyword arguments.
731
+ # Omitted fields will default to `*` or `?` as appropriate.
732
+ #
733
+ # Each field is optional, but at least one must be specified.
734
+ #
735
+ # The same rules for the standard
736
+ # [cron expression](https://www.quartz-scheduler.org/documentation/quartz-2.2.2/tutorials/tutorial-lesson-06.html)
737
+ # apply for each field. For example, multiple values can be separated
738
+ # with a comma within a string.
739
+ #
740
+ # @param [Integer, String, nil] second
741
+ # @param [Integer, String, nil] minute
742
+ # @param [Integer, String, nil] hour
743
+ # @param [Integer, String, nil] dom
744
+ # @param [Integer, String, nil] month
745
+ # @param [Integer, String, nil] dow
746
+ # @param [Integer, String, nil] year
747
+ # @param [Object] attach object to be attached to the trigger
748
+ # @example
749
+ # # Run every 3 minutes on Monday to Friday
750
+ # # equivalent to the cron expression "0 */3 * ? * MON-FRI *"
751
+ # rule "Using cron fields" do
752
+ # cron second: 0, minute: "*/3", dow: "MON-FRI"
753
+ # run { logger.info "Cron rule executed" }
754
+ # end
755
+ #
756
+ # @return [void]
757
+ #
758
+ def cron(expression = nil, attach: nil, **fields)
759
+ if fields.any?
760
+ raise ArgumentError, "Cron elements cannot be used with a cron expression" if expression
761
+
762
+ cron_expression = Cron.from_fields(fields)
763
+ return cron(cron_expression, attach: attach)
764
+ end
765
+
766
+ raise ArgumentError, "Missing cron expression or elements" unless expression
767
+
768
+ cron = Cron.new(rule_triggers: @rule_triggers)
769
+ cron.trigger(config: { "cronExpression" => expression }, attach: attach)
770
+ end
771
+
772
+ #
773
+ # Create a rule that executes at the specified interval.
774
+ #
775
+ # @param [String,
776
+ # Duration,
777
+ # java.time.MonthDay,
778
+ # :second,
779
+ # :minute,
780
+ # :hour,
781
+ # :day,
782
+ # :week,
783
+ # :month,
784
+ # :year,
785
+ # :monday,
786
+ # :tuesday,
787
+ # :wednesday,
788
+ # :thursday,
789
+ # :friday,
790
+ # :saturday,
791
+ # :sunday] value
792
+ # When to execute rule.
793
+ # @param [LocalTime, String, nil] at What time of day to execute rule
794
+ # @param [Object] attach Object to be attached to the trigger
795
+ # @return [void]
796
+ #
797
+ # @example
798
+ # rule "Daily" do
799
+ # every :day, at: '5:15'
800
+ # run do
801
+ # Light.on
802
+ # end
803
+ # end
804
+ #
805
+ # @example The above rule could also be expressed using LocalTime class as below
806
+ # rule "Daily" do
807
+ # every :day, at: LocalTime.of(5, 15)
808
+ # run { Light.on }
809
+ # end
810
+ #
811
+ # @example
812
+ # rule "Weekly" do
813
+ # every :monday, at: '5:15'
814
+ # run do
815
+ # Light.on
816
+ # end
817
+ # end
818
+ #
819
+ # @example
820
+ # rule "Often" do
821
+ # every :minute
822
+ # run do
823
+ # Light.on
824
+ # end
825
+ # end
826
+ #
827
+ # @example
828
+ # rule "Hourly" do
829
+ # every :hour
830
+ # run do
831
+ # Light.on
832
+ # end
833
+ # end
834
+ #
835
+ # @example
836
+ # rule "Often" do
837
+ # every 5.minutes
838
+ # run do
839
+ # Light.on
840
+ # end
841
+ # end
842
+ #
843
+ # @example
844
+ # rule 'Every 14th of Feb at 2pm' do
845
+ # every '02-14', at: '2pm'
846
+ # run { logger.info "Happy Valentine's Day!" }
847
+ # end
848
+ #
849
+ def every(value, at: nil, attach: nil)
850
+ return every(java.time.MonthDay.parse(value), at: at, attach: attach) if value.is_a?(String)
851
+
852
+ @ruby_triggers << [:every, value, { at: at }]
853
+
854
+ cron_expression = case value
855
+ when Symbol then Cron.from_symbol(value, at)
856
+ when Duration then Cron.from_duration(value, at)
857
+ when java.time.MonthDay then Cron.from_monthday(value, at)
858
+ else raise ArgumentError, "Unknown interval"
859
+ end
860
+ cron(cron_expression, attach: attach)
861
+ end
862
+
863
+ #
864
+ # Run this rule when the script is loaded.
865
+ #
866
+ # Execute the rule on OpenHAB start up and whenever the script is
867
+ # reloaded. This is useful to perform initialization routines,
868
+ # especially when combined with other triggers.
869
+ #
870
+ # @param [true, false] run_on_start Run this rule on start, defaults to True
871
+ # @param [Object] attach object to be attached to the trigger
872
+ # @return [void]
873
+ #
874
+ # @example
875
+ # rule "startup rule" do
876
+ # on_start
877
+ # run do
878
+ # <calculate some item state>
879
+ # end
880
+ # end
881
+ #
882
+ # @example
883
+ # rule 'Ensure all security lights are on' do
884
+ # on_start
885
+ # run { Security_Lights.on }
886
+ # end
887
+ #
888
+ # rubocop:disable Style/OptionalBooleanParameter
889
+ def on_start(run_on_start = true, attach: nil)
890
+ @on_start = Struct.new(:enabled, :attach).new(run_on_start, attach)
891
+ end
892
+ # rubocop:enable Style/OptionalBooleanParameter
893
+
894
+ #
895
+ # Create a trigger for when an item or group receives a command
896
+ #
897
+ # The command/commands parameters are replicated for DSL fluency.
898
+ #
899
+ # The `event` passed to run blocks will be an
900
+ # {Core::Events::ItemCommandEvent}.
901
+ #
902
+ # @param [GenericItem, GroupItem::Members] items Items to create trigger for
903
+ # @param [Core::TypesCommand, Array<Command>, Range, Proc] command commands to match for trigger
904
+ # @param [Array<Command>, Range, Proc] commands Fluent alias for `command`
905
+ # @param [Object] attach object to be attached to the trigger
906
+ # @return [void]
907
+ #
908
+ # @example
909
+ # rule 'Execute rule when item received command' do
910
+ # received_command Alarm_Mode
911
+ # run { |event| logger.info("Item received command: #{event.command}" ) }
912
+ # end
913
+ #
914
+ # @example
915
+ # rule 'Execute rule when item receives specific command' do
916
+ # received_command Alarm_Mode, command: 7
917
+ # run { |event| logger.info("Item received command: #{event.command}" ) }
918
+ # end
919
+ #
920
+ # @example
921
+ # rule 'Execute rule when item receives one of many specific commands' do
922
+ # received_command Alarm_Mode, commands: [7,14]
923
+ # run { |event| logger.info("Item received command: #{event.command}" ) }
924
+ # end
925
+ #
926
+ # @example
927
+ # rule 'Execute rule when group receives a specific command' do
928
+ # received_command AlarmModes
929
+ # triggered { |item| logger.info("Group #{item.name} received command")}
930
+ # end
931
+ #
932
+ # @example
933
+ # rule 'Execute rule when member of group receives any command' do
934
+ # received_command AlarmModes.members
935
+ # triggered { |item| logger.info("Group item #{item.name} received command")}
936
+ # end
937
+ #
938
+ # @example
939
+ # rule 'Execute rule when member of group is changed to one of many states' do
940
+ # received_command AlarmModes.members, commands: [7, 14]
941
+ # triggered { |item| logger.info("Group item #{item.name} received command")}
942
+ # end
943
+ #
944
+ # @example
945
+ # rule 'Execute rule when item receives a range of commands' do
946
+ # received_command Alarm_Mode, commands: 7..14
947
+ # run { |event| logger.info("Item received command: #{event.command}" ) }
948
+ # end
949
+ #
950
+ # @example Works with procs
951
+ # rule 'Execute rule when Alarm Mode command is odd' do
952
+ # received_command Alarm_Mode, command: proc { |c| c.odd? }
953
+ # run { |event| logger.info("Item received command: #{event.command}" ) }
954
+ # end
955
+ #
956
+ # @example Works with lambdas
957
+ # rule 'Execute rule when Alarm Mode command is odd' do
958
+ # received_command Alarm_Mode, command: -> c { c.odd? }
959
+ # run { |event| logger.info("Item received command: #{event.command}" ) }
960
+ # end
961
+ #
962
+ def received_command(*items, command: nil, commands: nil, attach: nil)
963
+ command_trigger = Command.new(rule_triggers: @rule_triggers)
964
+
965
+ # if neither command nor commands is specified, ensure that we create
966
+ # a trigger that isn't looking for a specific command.
967
+ commands = [nil] if command.nil? && commands.nil?
968
+ commands = Array.wrap(command) | Array.wrap(commands)
969
+
970
+ @ruby_triggers << [:received_command, items, { command: commands }]
971
+
972
+ items.each do |item|
973
+ commands.each do |cmd|
974
+ logger.trace "Creating received command trigger for items #{item.inspect} and commands #{cmd.inspect}"
975
+
976
+ command_trigger.trigger(item: item, command: cmd, attach: attach)
977
+ end
978
+ end
979
+ end
980
+
981
+ #
982
+ # Creates a thing added trigger
983
+ #
984
+ # @param [Object] attach object to be attached to the trigger
985
+ # @return [void]
986
+ #
987
+ # @example
988
+ # rule "thing added" do
989
+ # thing_added
990
+ # run do |event|
991
+ # logger.info("#{event.thing.uid} added.")
992
+ # end
993
+ # end
994
+ def thing_added(attach: nil)
995
+ @ruby_triggers << [:thing_added]
996
+ trigger("core.GenericEventTrigger", eventTopic: "openhab/things/*/added",
997
+ eventTypes: "ThingAddedEvent", attach: attach)
998
+ end
999
+
1000
+ #
1001
+ # Creates a thing removed trigger
1002
+ #
1003
+ # @param [Object] attach object to be attached to the trigger
1004
+ # @return [void]
1005
+ #
1006
+ # @example
1007
+ # rule "thing removed" do
1008
+ # thing_removed
1009
+ # run do |event|
1010
+ # logger.info("#{event.thing.uid} removed.")
1011
+ # end
1012
+ # end
1013
+ def thing_removed(attach: nil)
1014
+ @ruby_triggers << [:thing_removed]
1015
+ trigger("core.GenericEventTrigger", eventTopic: "openhab/things/*/removed",
1016
+ eventTypes: "ThingRemovedEvent", attach: attach)
1017
+ end
1018
+
1019
+ #
1020
+ # Creates a thing updated trigger
1021
+ #
1022
+ # @param [Object] attach object to be attached to the trigger
1023
+ # @return [void]
1024
+ #
1025
+ # @example
1026
+ # rule "thing updated" do
1027
+ # thing_updated
1028
+ # run do |event|
1029
+ # logger.info("#{event.thing.uid} updated.")
1030
+ # end
1031
+ # end
1032
+ #
1033
+ def thing_updated(attach: nil)
1034
+ @ruby_triggers << [:thing_removed]
1035
+ trigger("core.GenericEventTrigger", eventTopic: "openhab/things/*/updated",
1036
+ eventTypes: "ThingUpdatedEvent", attach: attach)
1037
+ end
1038
+
1039
+ #
1040
+ # Create a generic trigger given the trigger type uid and a configuration hash
1041
+ #
1042
+ # This provides the ability to create a trigger type not already covered by the other methods.
1043
+ #
1044
+ # @param [String] type Trigger type UID
1045
+ # @param [Object] attach object to be attached to the trigger
1046
+ # @param [Hash] configuration A hash containing the trigger configuration entries
1047
+ # @return [void]
1048
+ #
1049
+ # @example Create a trigger for the [PID Controller Automation](https://www.openhab.org/addons/automation/pidcontroller/) add-on.
1050
+ # rule 'PID Control' do
1051
+ # trigger 'pidcontroller.trigger',
1052
+ # input: InputItem.name,
1053
+ # setpoint: SetPointItem.name,
1054
+ # kp: 10,
1055
+ # ki: 10,
1056
+ # kd: 10,
1057
+ # kdTimeConstant: 1,
1058
+ # loopTime: 1000
1059
+ #
1060
+ # run do |event|
1061
+ # logger.info("PID controller command: #{event.command}")
1062
+ # ControlItem << event.command
1063
+ # end
1064
+ # end
1065
+ #
1066
+ # @example DateTime Trigger
1067
+ # rule 'DateTime Trigger' do
1068
+ # description 'Triggers at a time specified in MyDateTimeItem'
1069
+ # trigger 'timer.DateTimeTrigger', itemName: MyDateTimeItem.name
1070
+ # run do
1071
+ # logger.info("DateTimeTrigger has been triggered")
1072
+ # end
1073
+ # end
1074
+ #
1075
+ def trigger(type, attach: nil, **configuration)
1076
+ logger.trace("Creating a generic trigger for type(#{type}) with configuration(#{configuration})")
1077
+ Triggers::Trigger.new(rule_triggers: @rule_triggers)
1078
+ .append_trigger(type: type, config: configuration, attach: attach)
1079
+ end
1080
+
1081
+ #
1082
+ # Create a trigger when item, group or thing is updated
1083
+ #
1084
+ # The `event` passed to run blocks will be an
1085
+ # {Core::Events::ItemStateEvent} or a
1086
+ # {Core::Events::ThingStatusInfoEvent} depending on if the triggering
1087
+ # element was an item or a thing.
1088
+ #
1089
+ # @param [GenericItem, GroupItem::Members, Thing] items
1090
+ # Objects to create trigger for.
1091
+ # @param [State, Array<State>, Range, Proc, Symbol, String] to
1092
+ # Only execute rule if the state matches `to` state(s). If the
1093
+ # updated element is a {Core::Things::Thing}, the `to` accepts
1094
+ # symbols and strings that match
1095
+ # [supported thing statuses](https://www.openhab.org/docs/concepts/things.html#thing-status).
1096
+ # @param [Object] attach object to be attached to the trigger
1097
+ # @return [void]
1098
+ #
1099
+ # @example
1100
+ # rule 'Execute rule when item is updated to any value' do
1101
+ # updated Alarm_Mode
1102
+ # run { logger.info("Alarm Mode Updated") }
1103
+ # end
1104
+ #
1105
+ # @example
1106
+ # rule 'Execute rule when item is updated to specific number' do
1107
+ # updated Alarm_Mode, to: 7
1108
+ # run { logger.info("Alarm Mode Updated") }
1109
+ # end
1110
+ #
1111
+ # @example
1112
+ # rule 'Execute rule when item is updated to one of many specific states' do
1113
+ # updated Alarm_Mode, to: [7, 14]
1114
+ # run { logger.info("Alarm Mode Updated")}
1115
+ # end
1116
+ #
1117
+ # @example
1118
+ # rule 'Execute rule when item is within a range' do
1119
+ # updated Alarm_Mode, to: 7..14
1120
+ # run { logger.info("Alarm Mode Updated to a value between 7 and 14")}
1121
+ # end
1122
+ #
1123
+ # @example
1124
+ # rule 'Execute rule when group is updated to any state' do
1125
+ # updated AlarmModes
1126
+ # triggered { |item| logger.info("Group #{item.name} updated")}
1127
+ # end
1128
+ #
1129
+ # @example
1130
+ # rule 'Execute rule when member of group is changed to any state' do
1131
+ # updated AlarmModes.members
1132
+ # triggered { |item| logger.info("Group item #{item.name} updated")}
1133
+ # end
1134
+ #
1135
+ # @example
1136
+ # rule 'Execute rule when member of group is changed to one of many states' do
1137
+ # updated AlarmModes.members, to: [7, 14]
1138
+ # triggered { |item| logger.info("Group item #{item.name} updated")}
1139
+ # end
1140
+ #
1141
+ # @example Works with procs
1142
+ # rule 'Execute rule when member of group is changed to an odd state' do
1143
+ # updated AlarmModes.members, to: proc { |t| t.odd? }
1144
+ # triggered { |item| logger.info("Group item #{item.name} updated")}
1145
+ # end
1146
+ #
1147
+ # @example Works with lambdas:
1148
+ # rule 'Execute rule when member of group is changed to an odd state' do
1149
+ # updated AlarmModes.members, to: -> t { t.odd? }
1150
+ # triggered { |item| logger.info("Group item #{item.name} updated")}
1151
+ # end
1152
+ #
1153
+ # @example Works with things as well
1154
+ # rule 'Execute rule when thing is updated' do
1155
+ # updated things['astro:sun:home'], :to => :uninitialized
1156
+ # run { |event| logger.info("Thing #{event.uid} status <trigger> to #{event.status}") }
1157
+ # end
1158
+ #
1159
+ def updated(*items, to: nil, attach: nil)
1160
+ updated = Updated.new(rule_triggers: @rule_triggers)
1161
+ @ruby_triggers << [:updated, items, { to: to }]
1162
+ items.map do |item|
1163
+ logger.trace("Creating updated trigger for item(#{item}) to(#{to})")
1164
+ [to].flatten.map do |to_state|
1165
+ updated.trigger(item: item, to: to_state, attach: attach)
1166
+ end
1167
+ end.flatten
1168
+ end
1169
+
1170
+ #
1171
+ # Create a trigger to watch a path
1172
+ #
1173
+ # It provides the ability to create a trigger on file and directory
1174
+ # changes.
1175
+ #
1176
+ # If a file or a path that does not exist is supplied as the argument
1177
+ # to watch, the parent directory will be watched and the file or
1178
+ # non-existent part of the supplied path will become the glob. For
1179
+ # example, if the directory given is `/tmp/foo/bar` and `/tmp/foo`
1180
+ # exists but `bar` does not exist inside of of `/tmp/foo` then the
1181
+ # directory `/tmp/foo` will be watched for any files that match
1182
+ # `*/bar`.
1183
+ #
1184
+ # If the last part of the path contains any glob characters e.g.
1185
+ # `/tmp/foo/*bar`, the parent directory will be watched and the last
1186
+ # part of the path will be treated as if it was passed as the `glob`
1187
+ # argument. In other words, `watch '/tmp/foo/*bar'` is equivalent to
1188
+ # `watch '/tmp/foo', glob: '*bar'`
1189
+ #
1190
+ # The `event` passed to run blocks will be a {Events::WatchEvent}.
1191
+ #
1192
+ # @param [String] path Path to watch. Can be a directory of a file.
1193
+ # @param [String] glob
1194
+ # Limit events to paths matching this glob. Globs are matched using
1195
+ # [File.fnmatch?](https://ruby-doc.org/core-2.6/File.html#method-c-fnmatch-3F)
1196
+ # rules.
1197
+ # @param [Array<:created, :deleted, :modified>, :created, :deleted, :modified] for
1198
+ # Types of changes to watch for.
1199
+ # @param [Object] attach object to be attached to the trigger
1200
+ # @return [void]
1201
+ #
1202
+ # @example Watch `items` directory inside of the OpenHAB configuration path and log any changes.
1203
+ # rule 'watch directory' do
1204
+ # watch OpenHAB::Core.config_folder / 'items'
1205
+ # run { |event| logger.info("#{event.path.basename} - #{event.type}") }
1206
+ # end
1207
+ #
1208
+ # @example Watch `items` directory for files that end in `*.erb` and log any changes
1209
+ # rule 'watch directory' do
1210
+ # watch OpenHAB::Core.config_folder / 'items', glob: '*.erb'
1211
+ # run { |event| logger.info("#{event.path.basename} - #{event.type}") }
1212
+ # end
1213
+ #
1214
+ # @example Watch `items/foo.items` log any changes
1215
+ # rule 'watch directory' do
1216
+ # watch OpenHAB::Core.config_folder / 'items/foo.items'
1217
+ # run { |event| logger.info("#{event.path.basename} - #{event.type}") }
1218
+ # end
1219
+ #
1220
+ # @example Watch `items/*.items` log any changes
1221
+ # rule 'watch directory' do
1222
+ # watch OpenHAB::Core.config_folder / 'items/*.items'
1223
+ # run { |event| logger.info("#{event.path.basename} - #{event.type}") }
1224
+ # end
1225
+ #
1226
+ # @example Watch `items/*.items` for when items files are deleted or created (ignore changes)
1227
+ # rule 'watch directory' do
1228
+ # watch OpenHAB::Core.config_folder / 'items/*.items', for: [:deleted, :created]
1229
+ # run { |event| logger.info("#{event.path.basename} - #{event.type}") }
1230
+ # end
1231
+ #
1232
+ def watch(path, glob: "*", for: %i[created deleted modified], attach: nil)
1233
+ glob, path = Watch.glob_for_path(Pathname.new(path), glob)
1234
+ types = [binding.local_variable_get(:for)].flatten
1235
+ config = { path: path.to_s, types: types.map(&:to_s), glob: glob.to_s }
1236
+
1237
+ logger.trace "Creating a watch trigger for #{path} with glob #{glob} on types #{types.inspect}"
1238
+ Watch.new(rule_triggers: @rule_triggers).trigger(config: config, attach: attach)
1239
+ end
1240
+
1241
+ # @!endgroup
1242
+
1243
+ #
1244
+ # Checks if this rule should run on start
1245
+ #
1246
+ # @return [true, false] True if rule should run on start, false otherwise.
1247
+ #
1248
+ def on_start?
1249
+ @on_start.enabled
1250
+ end
1251
+
1252
+ #
1253
+ # Get the optional start attachment
1254
+ #
1255
+ # @return [Object] optional user provided attachment to the on_start method
1256
+ #
1257
+ # @!visibility private
1258
+ def start_attachment
1259
+ @on_start.attach
1260
+ end
1261
+
1262
+ # @!visibility private
1263
+ #
1264
+ # Run the supplied block inside the object instance of the object that created the rule
1265
+ #
1266
+ # @yield [] Block executed in context of the object creating the rule
1267
+ #
1268
+ #
1269
+ def my(&block)
1270
+ @caller.instance_eval(&block)
1271
+ end
1272
+
1273
+ #
1274
+ # @return [String]
1275
+ #
1276
+ def inspect
1277
+ <<~TEXT.tr("\n", " ")
1278
+ #<OpenHAB::DSL::Rules::Builder: #{uid}
1279
+ triggers=#{triggers.inspect},
1280
+ run blocks=#{run.inspect},
1281
+ on_start=#{on_start?},
1282
+ Trigger Conditions=#{trigger_conditions.inspect},
1283
+ Trigger UIDs=#{triggers.map(&:id).inspect},
1284
+ Attachments=#{attachments.inspect}
1285
+ >
1286
+ TEXT
1287
+ end
1288
+
1289
+ #
1290
+ # Process a rule based on the supplied configuration
1291
+ #
1292
+ # @param [String] script The source code of the rule
1293
+ #
1294
+ # @!visibility private
1295
+ def build(script)
1296
+ return unless create_rule?
1297
+
1298
+ rule = AutomationRule.new(config: self)
1299
+ added_rule = add_rule(rule)
1300
+ Rules.script_rules[rule.uid] = rule
1301
+ # add config so that MainUI can show the script
1302
+ added_rule.actions.first.configuration.put("type", "application/x-ruby")
1303
+ added_rule.actions.first.configuration.put("script", script)
1304
+
1305
+ rule.execute(nil, { "event" => Struct.new(:attachment).new(start_attachment) }) if on_start?
1306
+ added_rule
1307
+ end
1308
+
1309
+ private
1310
+
1311
+ # delegate to the caller's logger
1312
+ def logger
1313
+ @caller.send(:logger)
1314
+ end
1315
+
1316
+ #
1317
+ # Should a rule be created based on rule configuration
1318
+ #
1319
+ # @return [true,false] true if it should be created, false otherwise
1320
+ #
1321
+ def create_rule?
1322
+ return true if tags.include?("Script")
1323
+
1324
+ if !triggers?
1325
+ logger.warn "Rule '#{uid}' has no triggers, not creating rule"
1326
+ elsif !execution_blocks?
1327
+ logger.warn "Rule '#{uid}' has no execution blocks, not creating rule"
1328
+ elsif !enabled
1329
+ logger.trace "Rule '#{uid}' marked as disabled, not creating rule."
1330
+ else
1331
+ return true
1332
+ end
1333
+ false
1334
+ end
1335
+
1336
+ #
1337
+ # Check if the rule has any triggers
1338
+ #
1339
+ # @return [true,false] True if rule has triggers, false otherwise
1340
+ #
1341
+ def triggers?
1342
+ on_start? || !triggers.empty?
1343
+ end
1344
+
1345
+ #
1346
+ # Check if the rule has any execution blocks
1347
+ #
1348
+ # @return [true,false] True if rule has execution blocks, false otherwise
1349
+ #
1350
+ def execution_blocks?
1351
+ !(run || []).empty?
1352
+ end
1353
+
1354
+ #
1355
+ # Add a rule to the automation manager
1356
+ #
1357
+ # @param [org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule] rule to add
1358
+ #
1359
+ #
1360
+ def add_rule(rule)
1361
+ base_uid = rule.uid
1362
+ duplicate_index = 1
1363
+ while $rules.get(rule.uid)
1364
+ duplicate_index += 1
1365
+ rule.uid = "#{base_uid} (#{duplicate_index})"
1366
+ end
1367
+ logger.trace("Adding rule: #{rule}")
1368
+ Core.automation_manager.add_rule(rule)
1369
+ end
1370
+ end
1371
+ end
1372
+ end
1373
+ end