openhab-jrubyscripting 5.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/lib/openhab/core/actions.rb +163 -0
  3. data/lib/openhab/core/entity_lookup.rb +144 -0
  4. data/lib/openhab/core/events/abstract_event.rb +17 -0
  5. data/lib/openhab/core/events/item_channel_link.rb +36 -0
  6. data/lib/openhab/core/events/item_command_event.rb +78 -0
  7. data/lib/openhab/core/events/item_event.rb +22 -0
  8. data/lib/openhab/core/events/item_state_changed_event.rb +52 -0
  9. data/lib/openhab/core/events/item_state_event.rb +51 -0
  10. data/lib/openhab/core/events/thing.rb +29 -0
  11. data/lib/openhab/core/events/thing_status_info_event.rb +53 -0
  12. data/lib/openhab/core/events.rb +10 -0
  13. data/lib/openhab/core/items/accepted_data_types.rb +29 -0
  14. data/lib/openhab/core/items/color_item.rb +52 -0
  15. data/lib/openhab/core/items/contact_item.rb +52 -0
  16. data/lib/openhab/core/items/date_time_item.rb +58 -0
  17. data/lib/openhab/core/items/dimmer_item.rb +148 -0
  18. data/lib/openhab/core/items/generic_item.rb +344 -0
  19. data/lib/openhab/core/items/group_item.rb +174 -0
  20. data/lib/openhab/core/items/image_item.rb +109 -0
  21. data/lib/openhab/core/items/location_item.rb +34 -0
  22. data/lib/openhab/core/items/metadata/hash.rb +390 -0
  23. data/lib/openhab/core/items/metadata/namespace_hash.rb +469 -0
  24. data/lib/openhab/core/items/metadata.rb +11 -0
  25. data/lib/openhab/core/items/number_item.rb +62 -0
  26. data/lib/openhab/core/items/numeric_item.rb +22 -0
  27. data/lib/openhab/core/items/persistence.rb +327 -0
  28. data/lib/openhab/core/items/player_item.rb +66 -0
  29. data/lib/openhab/core/items/proxy.rb +59 -0
  30. data/lib/openhab/core/items/registry.rb +66 -0
  31. data/lib/openhab/core/items/rollershutter_item.rb +68 -0
  32. data/lib/openhab/core/items/semantics/enumerable.rb +152 -0
  33. data/lib/openhab/core/items/semantics.rb +476 -0
  34. data/lib/openhab/core/items/state_storage.rb +53 -0
  35. data/lib/openhab/core/items/string_item.rb +28 -0
  36. data/lib/openhab/core/items/switch_item.rb +78 -0
  37. data/lib/openhab/core/items.rb +114 -0
  38. data/lib/openhab/core/lazy_array.rb +52 -0
  39. data/lib/openhab/core/profile_factory.rb +118 -0
  40. data/lib/openhab/core/script_handling.rb +55 -0
  41. data/lib/openhab/core/things/channel.rb +48 -0
  42. data/lib/openhab/core/things/channel_uid.rb +51 -0
  43. data/lib/openhab/core/things/item_channel_link.rb +33 -0
  44. data/lib/openhab/core/things/profile_callback.rb +52 -0
  45. data/lib/openhab/core/things/proxy.rb +69 -0
  46. data/lib/openhab/core/things/registry.rb +46 -0
  47. data/lib/openhab/core/things/thing.rb +194 -0
  48. data/lib/openhab/core/things.rb +22 -0
  49. data/lib/openhab/core/timer.rb +128 -0
  50. data/lib/openhab/core/types/comparable_type.rb +23 -0
  51. data/lib/openhab/core/types/date_time_type.rb +259 -0
  52. data/lib/openhab/core/types/decimal_type.rb +192 -0
  53. data/lib/openhab/core/types/hsb_type.rb +183 -0
  54. data/lib/openhab/core/types/increase_decrease_type.rb +34 -0
  55. data/lib/openhab/core/types/next_previous_type.rb +34 -0
  56. data/lib/openhab/core/types/numeric_type.rb +52 -0
  57. data/lib/openhab/core/types/on_off_type.rb +46 -0
  58. data/lib/openhab/core/types/open_closed_type.rb +41 -0
  59. data/lib/openhab/core/types/percent_type.rb +95 -0
  60. data/lib/openhab/core/types/play_pause_type.rb +38 -0
  61. data/lib/openhab/core/types/point_type.rb +117 -0
  62. data/lib/openhab/core/types/quantity_type.rb +327 -0
  63. data/lib/openhab/core/types/raw_type.rb +26 -0
  64. data/lib/openhab/core/types/refresh_type.rb +27 -0
  65. data/lib/openhab/core/types/rewind_fastforward_type.rb +38 -0
  66. data/lib/openhab/core/types/stop_move_type.rb +34 -0
  67. data/lib/openhab/core/types/string_type.rb +76 -0
  68. data/lib/openhab/core/types/type.rb +117 -0
  69. data/lib/openhab/core/types/un_def_type.rb +38 -0
  70. data/lib/openhab/core/types/up_down_type.rb +50 -0
  71. data/lib/openhab/core/types.rb +69 -0
  72. data/lib/openhab/core/uid.rb +36 -0
  73. data/lib/openhab/core.rb +85 -0
  74. data/lib/openhab/core_ext/java/duration.rb +115 -0
  75. data/lib/openhab/core_ext/java/local_date.rb +93 -0
  76. data/lib/openhab/core_ext/java/local_time.rb +106 -0
  77. data/lib/openhab/core_ext/java/month.rb +59 -0
  78. data/lib/openhab/core_ext/java/month_day.rb +105 -0
  79. data/lib/openhab/core_ext/java/period.rb +103 -0
  80. data/lib/openhab/core_ext/java/temporal_amount.rb +34 -0
  81. data/lib/openhab/core_ext/java/time.rb +58 -0
  82. data/lib/openhab/core_ext/java/unit.rb +15 -0
  83. data/lib/openhab/core_ext/java/zoned_date_time.rb +116 -0
  84. data/lib/openhab/core_ext/ruby/array.rb +21 -0
  85. data/lib/openhab/core_ext/ruby/class.rb +15 -0
  86. data/lib/openhab/core_ext/ruby/date.rb +89 -0
  87. data/lib/openhab/core_ext/ruby/numeric.rb +190 -0
  88. data/lib/openhab/core_ext/ruby/range.rb +70 -0
  89. data/lib/openhab/core_ext/ruby/time.rb +104 -0
  90. data/lib/openhab/core_ext.rb +18 -0
  91. data/lib/openhab/dsl/events/watch_event.rb +18 -0
  92. data/lib/openhab/dsl/events.rb +9 -0
  93. data/lib/openhab/dsl/gems.rb +3 -0
  94. data/lib/openhab/dsl/items/builder.rb +618 -0
  95. data/lib/openhab/dsl/items/ensure.rb +93 -0
  96. data/lib/openhab/dsl/items/timed_command.rb +236 -0
  97. data/lib/openhab/dsl/rules/automation_rule.rb +308 -0
  98. data/lib/openhab/dsl/rules/builder.rb +1373 -0
  99. data/lib/openhab/dsl/rules/guard.rb +115 -0
  100. data/lib/openhab/dsl/rules/name_inference.rb +160 -0
  101. data/lib/openhab/dsl/rules/property.rb +76 -0
  102. data/lib/openhab/dsl/rules/rule_triggers.rb +96 -0
  103. data/lib/openhab/dsl/rules/terse.rb +63 -0
  104. data/lib/openhab/dsl/rules/triggers/changed.rb +169 -0
  105. data/lib/openhab/dsl/rules/triggers/channel.rb +57 -0
  106. data/lib/openhab/dsl/rules/triggers/command.rb +107 -0
  107. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +161 -0
  108. data/lib/openhab/dsl/rules/triggers/conditions/proc.rb +164 -0
  109. data/lib/openhab/dsl/rules/triggers/cron/cron.rb +195 -0
  110. data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +127 -0
  111. data/lib/openhab/dsl/rules/triggers/trigger.rb +56 -0
  112. data/lib/openhab/dsl/rules/triggers/updated.rb +130 -0
  113. data/lib/openhab/dsl/rules/triggers/watch/watch.rb +55 -0
  114. data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +155 -0
  115. data/lib/openhab/dsl/rules/triggers.rb +12 -0
  116. data/lib/openhab/dsl/rules.rb +29 -0
  117. data/lib/openhab/dsl/script_handling.rb +55 -0
  118. data/lib/openhab/dsl/things/builder.rb +263 -0
  119. data/lib/openhab/dsl/thread_local.rb +48 -0
  120. data/lib/openhab/dsl/timer_manager.rb +191 -0
  121. data/lib/openhab/dsl/version.rb +9 -0
  122. data/lib/openhab/dsl.rb +686 -0
  123. data/lib/openhab/log.rb +348 -0
  124. data/lib/openhab/osgi.rb +70 -0
  125. data/lib/openhab/rspec/configuration.rb +56 -0
  126. data/lib/openhab/rspec/example_group.rb +90 -0
  127. data/lib/openhab/rspec/helpers.rb +439 -0
  128. data/lib/openhab/rspec/hooks.rb +93 -0
  129. data/lib/openhab/rspec/jruby.rb +46 -0
  130. data/lib/openhab/rspec/karaf.rb +811 -0
  131. data/lib/openhab/rspec/mocks/bundle_install_support.rb +25 -0
  132. data/lib/openhab/rspec/mocks/bundle_resolver.rb +30 -0
  133. data/lib/openhab/rspec/mocks/event_admin.rb +146 -0
  134. data/lib/openhab/rspec/mocks/metadata_provider.rb +75 -0
  135. data/lib/openhab/rspec/mocks/persistence_service.rb +140 -0
  136. data/lib/openhab/rspec/mocks/safe_caller.rb +40 -0
  137. data/lib/openhab/rspec/mocks/synchronous_executor.rb +56 -0
  138. data/lib/openhab/rspec/mocks/thing_handler.rb +76 -0
  139. data/lib/openhab/rspec/mocks/timer.rb +95 -0
  140. data/lib/openhab/rspec/openhab/core/actions.rb +26 -0
  141. data/lib/openhab/rspec/openhab/core/items/proxy.rb +27 -0
  142. data/lib/openhab/rspec/openhab/core/things/proxy.rb +27 -0
  143. data/lib/openhab/rspec/shell.rb +31 -0
  144. data/lib/openhab/rspec/suspend_rules.rb +60 -0
  145. data/lib/openhab/rspec.rb +17 -0
  146. data/lib/openhab/yard/cli/stats.rb +23 -0
  147. data/lib/openhab/yard/code_objects/group_object.rb +17 -0
  148. data/lib/openhab/yard/code_objects/java/base.rb +31 -0
  149. data/lib/openhab/yard/code_objects/java/class_object.rb +11 -0
  150. data/lib/openhab/yard/code_objects/java/field_object.rb +15 -0
  151. data/lib/openhab/yard/code_objects/java/interface_object.rb +15 -0
  152. data/lib/openhab/yard/code_objects/java/package_object.rb +11 -0
  153. data/lib/openhab/yard/code_objects/java/proxy.rb +23 -0
  154. data/lib/openhab/yard/handlers/jruby/base.rb +49 -0
  155. data/lib/openhab/yard/handlers/jruby/class_handler.rb +18 -0
  156. data/lib/openhab/yard/handlers/jruby/constant_handler.rb +18 -0
  157. data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +27 -0
  158. data/lib/openhab/yard/handlers/jruby/mixin_handler.rb +23 -0
  159. data/lib/openhab/yard/html_helper.rb +44 -0
  160. data/lib/openhab/yard/tags/constant_directive.rb +20 -0
  161. data/lib/openhab/yard/tags/group_directive.rb +24 -0
  162. data/lib/openhab/yard/tags/library.rb +3 -0
  163. data/lib/openhab/yard.rb +32 -0
  164. metadata +504 -0
@@ -0,0 +1,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