openhab-scripting 4.28.0 → 4.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openhab/dsl/items/group_item.rb +7 -0
  3. data/lib/openhab/dsl/items/item_registry.rb +1 -1
  4. data/lib/openhab/dsl/rules/automation_rule.rb +10 -158
  5. data/lib/openhab/dsl/rules/rule.rb +4 -46
  6. data/lib/openhab/dsl/rules/rule_config.rb +4 -3
  7. data/lib/openhab/dsl/rules/triggers/changed.rb +51 -18
  8. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +158 -0
  9. data/lib/openhab/dsl/rules/triggers/conditions/proc.rb +150 -0
  10. data/lib/openhab/dsl/rules/triggers/cron.rb +118 -0
  11. data/lib/openhab/dsl/rules/triggers/trigger.rb +2 -1
  12. data/lib/openhab/dsl/rules/triggers/updated.rb +59 -16
  13. data/lib/openhab/dsl/rules/triggers/watch.rb +1 -3
  14. data/lib/openhab/dsl/types/date_time_type.rb +2 -2
  15. data/lib/openhab/dsl/types/decimal_type.rb +1 -1
  16. data/lib/openhab/dsl/types/hsb_type.rb +1 -1
  17. data/lib/openhab/dsl/types/increase_decrease_type.rb +1 -1
  18. data/lib/openhab/dsl/types/next_previous_type.rb +1 -1
  19. data/lib/openhab/dsl/types/on_off_type.rb +1 -1
  20. data/lib/openhab/dsl/types/open_closed_type.rb +1 -1
  21. data/lib/openhab/dsl/types/percent_type.rb +1 -1
  22. data/lib/openhab/dsl/types/play_pause_type.rb +1 -1
  23. data/lib/openhab/dsl/types/point_type.rb +1 -1
  24. data/lib/openhab/dsl/types/quantity_type.rb +1 -1
  25. data/lib/openhab/dsl/types/refresh_type.rb +1 -1
  26. data/lib/openhab/dsl/types/rewind_fastforward_type.rb +1 -1
  27. data/lib/openhab/dsl/types/stop_move_type.rb +1 -1
  28. data/lib/openhab/dsl/types/string_type.rb +1 -1
  29. data/lib/openhab/dsl/types/un_def_type.rb +1 -1
  30. data/lib/openhab/dsl/types/up_down_type.rb +1 -1
  31. data/lib/openhab/log/configuration.rb +1 -1
  32. data/lib/openhab/log/logger.rb +10 -0
  33. data/lib/openhab/version.rb +1 -1
  34. metadata +4 -3
  35. data/lib/openhab/dsl/rules/cron_trigger_rule.rb +0 -42
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 738583da2cd48296525b6b6a2999fe1daba82b0cbb1c410beb7e3575fdf6afbb
4
- data.tar.gz: e7d4ff645268380636f69bff6eec42d8b8f650784225afe9d456027b44e11d77
3
+ metadata.gz: a4e5de50bc22188eb5350aa71f00738703afab2e75703c9f5c2f4ffce54c49dc
4
+ data.tar.gz: e42484957fcea08aaf5d397af1ec5012b5595bd893eacca7bbc2db3149a1707b
5
5
  SHA512:
6
- metadata.gz: e06a080cedde9ffbb29983f087c93dff1533fe521112c1036015c1f42f93c5a9d164bd0c307d21109c8f3a6abe7cb74e0f63b144cd21d2f7086a1e03d0b9c545
7
- data.tar.gz: e23ccc89ee67bc2152efaa91ff33157c74d99d244da3cc11b497b8500b094f40dfc66b61fcb04c18763fe53afaabccba8baa6f2bf05c9f3d5d194915255f8078
6
+ metadata.gz: af5435c5804d2a58f1e8cf84647ee1813c80c958e9b1c3c6794d6879adeebd71ac6955454ad424522916375d7314719312b561d7b2a718ad43b48e76edd2801c
7
+ data.tar.gz: 2f3e46d17e305d1169980fdf2aa5ae8f5673e4730d73d2d10dbb445582642086522d3ab43e9bae5a411637ec1e787d465431423fb307a04ed09178be5953c25a
@@ -32,6 +32,13 @@ module OpenHAB
32
32
  group.get_members.to_a
33
33
  end
34
34
 
35
+ # Name of the group
36
+ #
37
+ # @return [String]
38
+ def name
39
+ group.name
40
+ end
41
+
35
42
  # Send a command to each member of the group
36
43
  #
37
44
  # @return [GroupMembers] +self+
@@ -35,7 +35,7 @@ module OpenHAB
35
35
  # Explicit conversion to array
36
36
  # @return [Array]
37
37
  def to_a
38
- $ir.items.grep_v(org.openhab.core.items.GroupItem) # rubocop:disable Style/GlobalVars
38
+ $ir.items.to_a # rubocop:disable Style/GlobalVars
39
39
  end
40
40
  end
41
41
 
@@ -45,7 +45,7 @@ module OpenHAB
45
45
  between = config.between&.yield_self { between(config.between) }
46
46
  @between = between || OpenHAB::DSL::Between::ALL_DAY
47
47
  # Convert between to correct range or nil if not set
48
- @trigger_delays = config.trigger_delays
48
+ @trigger_conditions = config.trigger_conditions
49
49
  @attachments = config.attachments
50
50
  end
51
51
  # rubocop:enable Metrics/MethodLength
@@ -57,18 +57,12 @@ module OpenHAB
57
57
  # @param [Map] inputs map provided by OpenHAB rules engine containing event and other information
58
58
  #
59
59
  #
60
- def execute(mod = nil, inputs = nil) # rubocop:disable Metrics/MethodLength
60
+ def execute(mod = nil, inputs = nil)
61
61
  thread_local(RULE_NAME: name) do
62
62
  logger.trace { "Execute called with mod (#{mod&.to_string}) and inputs (#{inputs&.pretty_inspect})" }
63
63
  logger.trace { "Event details #{inputs['event'].pretty_inspect}" } if inputs&.key?('event')
64
- if trigger_delay inputs
65
- trigger_delay = trigger_delay(inputs)
66
- process_trigger_delay(trigger_delay, mod, inputs)
67
- else
68
- # If guards are satisfied execute the run type blocks
69
- # If they are not satisfied, execute the Othewise blocks
70
- queue = create_queue(inputs)
71
- process_queue(queue, mod, inputs)
64
+ trigger_conditions(inputs).process(mod: mod, inputs: inputs) do
65
+ process_queue(create_queue(inputs), mod, inputs)
72
66
  end
73
67
  end
74
68
  end
@@ -124,18 +118,18 @@ module OpenHAB
124
118
  end
125
119
 
126
120
  #
127
- # Returns trigger delay from inputs if it exists
121
+ # Returns trigger conditions from inputs if it exists
128
122
  #
129
123
  # @param [Map] inputs map from OpenHAB containing UID
130
124
  #
131
- # @return [Array] Array of trigger delays that match rule UID
125
+ # @return [Array] Array of trigger conditions that match rule UID
132
126
  #
133
- def trigger_delay(inputs)
127
+ def trigger_conditions(inputs)
134
128
  # Parse this to get the trigger UID:
135
129
  # ["72698819-83cb-498a-8e61-5aab8b812623.event", "oldState", "module", \
136
130
  # "72698819-83cb-498a-8e61-5aab8b812623.oldState", "event", "newState",\
137
131
  # "72698819-83cb-498a-8e61-5aab8b812623.newState"]
138
- @trigger_delays[trigger_id(inputs)]
132
+ @trigger_conditions[trigger_id(inputs)]
139
133
  end
140
134
 
141
135
  # If an attachment exists for the trigger for this event add it to the event object
@@ -151,138 +145,6 @@ module OpenHAB
151
145
  event
152
146
  end
153
147
 
154
- #
155
- # Check if trigger guards prevent rule execution
156
- #
157
- # @param [Delay] trigger_delay rules delaying trigger because of
158
- # @param [Map] inputs OpenHAB map object describing rule trigger
159
- #
160
- # @return [Boolean] True if the rule should execute, false if trigger guard prevents execution
161
- #
162
- def check_trigger_guards(trigger_delay, inputs)
163
- new_state, old_state = retrieve_states(inputs)
164
- if check_from(trigger_delay, old_state)
165
- return true if check_to(trigger_delay, new_state)
166
-
167
- logger.trace("Skipped execution of rule '#{name}' because to state #{new_state}"\
168
- " does not equal specified state(#{trigger_delay.to})")
169
- else
170
- logger.trace("Skipped execution of rule '#{name}' because old state #{old_state}"\
171
- " does not equal specified state(#{trigger_delay.from})")
172
- end
173
- end
174
-
175
- #
176
- # Rerieve the newState and oldState, alternatively newStatus and oldStatus
177
- # from the input map
178
- #
179
- # @param [Map] inputs OpenHAB map object describing rule trigger
180
- #
181
- # @return [Array] An array of the values for [newState, oldState] or [newStatus, oldStatus]
182
- #
183
- def retrieve_states(inputs)
184
- old_state = inputs['oldState'] || thing_status_to_sym(inputs['oldStatus'])
185
- new_state = inputs['newState'] || thing_status_to_sym(inputs['newStatus'])
186
-
187
- [new_state, old_state]
188
- end
189
-
190
- #
191
- # Converts a ThingStatus object to a ruby Symbol
192
- #
193
- # @param [Java::OrgOpenhabCoreThing::ThingStatus] status A ThingStatus instance
194
- #
195
- # @return [Symbol] A corresponding symbol, in lower case
196
- #
197
- def thing_status_to_sym(status)
198
- status&.to_s&.downcase&.to_sym
199
- end
200
-
201
- #
202
- # Check the from state against the trigger delay
203
- #
204
- # @param [TriggerDelay] trigger_delay Information about the trigger delay
205
- # @param [Item State] state from state to check
206
- #
207
- # @return [Boolean] true if no from state is defined or defined state equals supplied state
208
- #
209
- def check_from(trigger_delay, state)
210
- trigger_delay.from.nil? || state == trigger_delay.from
211
- end
212
-
213
- #
214
- # Check the to state against the trigger delay
215
- #
216
- # @param [TriggerDelay] trigger_delay Information about the trigger delay
217
- # @param [Item State] state to-state to check
218
- #
219
- # @return [Boolean] true if no to state is defined or defined state equals supplied state
220
- #
221
- def check_to(trigger_delay, state)
222
- trigger_delay.to.nil? || state == trigger_delay.to
223
- end
224
-
225
- #
226
- # Process any matching trigger delays
227
- #
228
- # @param [Map] mod OpenHAB map object describing rule trigger
229
- # @param [Map] inputs OpenHAB map object describing rule trigger
230
- #
231
- #
232
- def process_trigger_delay(trigger_delay, mod, inputs)
233
- if trigger_delay.timer_active?
234
- process_active_timer(inputs, mod, trigger_delay)
235
- elsif check_trigger_guards(trigger_delay, inputs)
236
- logger.trace("Trigger Guards Matched for #{trigger_delay}, delaying rule execution")
237
- # Add timer and attach timer to delay object, and also state being tracked to so timer can be cancelled if
238
- # state changes
239
- # Also another timer should not be created if changed to same value again but instead rescheduled
240
- create_trigger_delay_timer(inputs, mod, trigger_delay)
241
- else
242
- logger.trace("Trigger Guards did not match for #{trigger_delay}, ignoring trigger.")
243
- end
244
- end
245
-
246
- #
247
- # Creatas a timer for trigger delays
248
- #
249
- # @param [Hash] inputs rule trigger inputs
250
- # @param [Hash] mod rule trigger mods
251
- # @param [TriggerDelay] trigger_delay specifications
252
- #
253
- #
254
- def create_trigger_delay_timer(inputs, mod, trigger_delay)
255
- logger.trace("Creating timer for rule #{name} and trigger delay #{trigger_delay}")
256
- trigger_delay.timer = after(trigger_delay.duration) do
257
- logger.trace("Delay Complete for #{trigger_delay}, executing rule")
258
- trigger_delay.timer = nil
259
- queue = create_queue(inputs)
260
- process_queue(queue, mod, inputs)
261
- end
262
- trigger_delay.tracking_to, = retrieve_states(inputs)
263
- end
264
-
265
- #
266
- # Process an active trigger timer
267
- #
268
- # @param [Hash] inputs rule trigger inputs
269
- # @param [Hash] mod rule trigger mods
270
- # @param [TriggerDelay] trigger_delay specifications
271
- #
272
- #
273
- def process_active_timer(inputs, mod, trigger_delay)
274
- state, = retrieve_states(inputs)
275
- if state == trigger_delay.tracking_to
276
- logger.trace("Item changed to #{state} for #{trigger_delay}, rescheduling timer.")
277
- trigger_delay.timer.reschedule(ZonedDateTime.now.plus(trigger_delay.duration))
278
- else
279
- logger.trace("Item changed to #{state} for #{trigger_delay}, canceling timer.")
280
- trigger_delay.timer.cancel
281
- # Reprocess trigger delay after canceling to track new state (if guards matched, etc)
282
- process_trigger_delay(trigger_delay, mod, inputs)
283
- end
284
- end
285
-
286
148
  #
287
149
  # Check if any guards prevent execution
288
150
  #
@@ -304,7 +166,7 @@ module OpenHAB
304
166
  end
305
167
  false
306
168
  rescue StandardError => e
307
- print_backtrace(e)
169
+ logger.log_exception(e, name)
308
170
  end
309
171
  # rubocop:enable Metrics/MethodLength
310
172
 
@@ -328,7 +190,7 @@ module OpenHAB
328
190
  end
329
191
  end
330
192
  rescue StandardError => e
331
- print_backtrace(e)
193
+ logger.log_exception(e, name)
332
194
  end
333
195
 
334
196
  #
@@ -399,16 +261,6 @@ module OpenHAB
399
261
  @run_context.instance_exec(event, &task.block)
400
262
  end
401
263
 
402
- #
403
- # Print error and stack trace without calls to internal classes
404
- #
405
- # @param [Exception] error A rescued error
406
- #
407
- def print_backtrace(error)
408
- error = logger.clean_backtrace(error)
409
- logger.error { "#{error.message} (#{error.class})\nIn rule: #{name}\n#{error.backtrace&.join("\n")}" }
410
- end
411
-
412
264
  #
413
265
  # Create a new hash in which all elements are converted to strings
414
266
  #
@@ -5,7 +5,6 @@ require 'openhab/core/services'
5
5
  require 'openhab/log/logger'
6
6
  require_relative 'rule_config'
7
7
  require_relative 'automation_rule'
8
- require_relative 'cron_trigger_rule'
9
8
  require_relative 'guard'
10
9
 
11
10
  module OpenHAB
@@ -26,7 +25,7 @@ module OpenHAB
26
25
  @registry = OpenHAB::Core.rule_registry
27
26
  class << self
28
27
  attr_reader :script_rules, :automation_manager, :registry
29
- end
28
+ end
30
29
 
31
30
  #
32
31
  # Create a new rule
@@ -35,7 +34,7 @@ end
35
34
  # @yield [] Block executed in context of a RuleConfig
36
35
  #
37
36
  #
38
- # rubocop: disable Metrics
37
+ # rubocop: disable Metrics/MethodLength
39
38
  def rule(rule_name, &block)
40
39
  thread_local(RULE_NAME: rule_name) do
41
40
  @rule_name = rule_name
@@ -47,10 +46,9 @@ end
47
46
  nil # Must return something other than the rule object. See https://github.com/boc-tothefuture/openhab-jruby/issues/438
48
47
  end
49
48
  rescue StandardError => e
50
- puts "#{e.class}: #{e.message}"
51
- re_raise_with_backtrace(e)
49
+ logger.log_exception(e, @rule_name)
52
50
  end
53
- # rubocop: enable Metrics
51
+ # rubocop: enable Metrics/MethodLength
54
52
 
55
53
  #
56
54
  # Cleanup rules in this script file
@@ -62,16 +60,6 @@ end
62
60
 
63
61
  private
64
62
 
65
- #
66
- # Re-raises a rescued error to OpenHAB with added rule name and stack trace
67
- #
68
- # @param [Exception] error A rescued error
69
- #
70
- def re_raise_with_backtrace(error)
71
- error = logger.clean_backtrace(error)
72
- raise error, "#{error.message}\nIn rule: #{@rule_name}\n#{error.backtrace.join("\n")}"
73
- end
74
-
75
63
  #
76
64
  # Process a rule based on the supplied configuration
77
65
  #
@@ -81,44 +69,14 @@ end
81
69
  def process_rule_config(config)
82
70
  return unless create_rule?(config)
83
71
 
84
- cron_attach_triggers, other_triggers = partition_triggers(config)
85
- logger.trace("Cron triggers: #{cron_attach_triggers} - Other triggers: #{other_triggers}")
86
- config.triggers = other_triggers
87
-
88
72
  rule = AutomationRule.new(config: config)
89
73
  Rules.script_rules << rule
90
74
  add_rule(rule)
91
75
 
92
- process_cron_attach(cron_attach_triggers, config, rule)
93
-
94
76
  rule.execute(nil, { 'event' => Struct.new(:attachment).new(config.start_attachment) }) if config.on_start?
95
77
  rule
96
78
  end
97
79
 
98
- #
99
- # Add cron triggers with attachments to rules
100
- # @param [Array] cron_attach_triggers cron type triggers with attachments
101
- #
102
- def process_cron_attach(cron_attach_triggers, config, rule)
103
- cron_attach_triggers&.map { |trigger| CronTriggerRule.new(rule_config: config, rule: rule, trigger: trigger) }
104
- &.each { |trigger| add_rule(trigger) }
105
- end
106
-
107
- #
108
- # Partitions triggers in a config, removing cron triggers with a corresponding attachment
109
- # so they can be used with CronTriggerRules to support attachments
110
- # @return [Array] Two element array the first element is cron triggers with attachments,
111
- # second element is other triggers
112
- #
113
- def partition_triggers(config)
114
- config
115
- .triggers
116
- .partition do |trigger|
117
- trigger.typeUID == OpenHAB::DSL::Rules::Triggers::Trigger::CRON &&
118
- config.attachments.key?(trigger.id)
119
- end
120
- end
121
-
122
80
  #
123
81
  # Should a rule be created based on rule configuration
124
82
  #
@@ -10,6 +10,7 @@ require_relative 'triggers/command'
10
10
  require_relative 'triggers/updated'
11
11
  require_relative 'triggers/generic'
12
12
  require_relative 'triggers/watch'
13
+ require_relative 'triggers/conditions/proc'
13
14
  require_relative 'guard'
14
15
  require 'openhab/core/entity_lookup'
15
16
  require 'openhab/dsl/between'
@@ -37,7 +38,7 @@ module OpenHAB
37
38
  attr_accessor :triggers
38
39
 
39
40
  # @return [Array] Of trigger delays
40
- attr_reader :trigger_delays
41
+ attr_reader :trigger_conditions
41
42
 
42
43
  # @return [Hash] Hash of trigger UIDs to attachments
43
44
  attr_reader :attachments
@@ -86,7 +87,7 @@ module OpenHAB
86
87
  #
87
88
  def initialize(rule_name, caller_binding)
88
89
  @triggers = []
89
- @trigger_delays = {}
90
+ @trigger_conditions = Hash.new(OpenHAB::DSL::Rules::Triggers::Conditions::Proc::ANY)
90
91
  @attachments = {}
91
92
  @caller = caller_binding.eval 'self'
92
93
  name(rule_name)
@@ -145,7 +146,7 @@ module OpenHAB
145
146
  "Triggers: (#{triggers}) " \
146
147
  "Run blocks: (#{run}) " \
147
148
  "on_start: (#{on_start?}) " \
148
- "Trigger Waits: #{trigger_delays} " \
149
+ "Trigger Conditions: #{trigger_conditions} " \
149
150
  "Trigger UIDs: #{triggers.map(&:id).join(', ')}" \
150
151
  "Attachments: #{attachments} "
151
152
  end
@@ -1,25 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'openhab/log/logger'
4
+ require_relative 'conditions/duration'
5
+ require_relative 'conditions/proc'
4
6
 
5
7
  module OpenHAB
6
8
  module DSL
7
9
  module Rules
8
10
  #
9
- # Module holds rule triggers
11
+ # Module for changed triggers
10
12
  #
13
+
11
14
  module Triggers
12
15
  include OpenHAB::Log
13
16
 
14
- #
15
- # Struct capturing data necessary for a conditional trigger
16
- #
17
- TriggerDelay = Struct.new(:to, :from, :duration, :timer, :tracking_to, keyword_init: true) do
18
- def timer_active?
19
- timer&.is_active
20
- end
21
- end
22
-
23
17
  #
24
18
  # Creates a trigger item, group and thing changed
25
19
  #
@@ -38,7 +32,7 @@ module OpenHAB
38
32
  wait_duration = binding.local_variable_get(:for)
39
33
 
40
34
  each_state(from, to) do |from_state, to_state|
41
- changed_or_wait(item, from_state, to_state, wait_duration, attach)
35
+ changed_trigger(item, from_state, to_state, wait_duration, attach)
42
36
  end
43
37
  end.flatten
44
38
  end
@@ -65,7 +59,7 @@ module OpenHAB
65
59
  end
66
60
 
67
61
  #
68
- # Create regular or delayed trigger based on duration
62
+ # Create the trigger
69
63
  #
70
64
  # @param [Object] item item to create trigger for
71
65
  # @param [Item State] from state to restrict trigger to
@@ -75,9 +69,13 @@ module OpenHAB
75
69
  #
76
70
  # @return [Trigger] OpenHAB triggers
77
71
  #
78
- def changed_or_wait(item, from, to, duration, attach)
72
+ def changed_trigger(item, from, to, duration, attach)
79
73
  if duration
80
74
  changed_wait(item, from: from, to: to, duration: duration, attach: attach)
75
+ elsif [to, from].grep(Range).any?
76
+ create_changed_range_trigger(item, from: from, to: to, attach: attach)
77
+ elsif [to, from].grep(Proc).any?
78
+ create_changed_proc_trigger(item, from: from, to: to, attach: attach)
81
79
  else
82
80
  create_changed_trigger(item, from, to, attach)
83
81
  end
@@ -90,13 +88,48 @@ module OpenHAB
90
88
  # @param [OpenHAB::Core::Duration] duration to delay trigger for until condition is met
91
89
  # @param [Item State] to OpenHAB Item State item or group needs to change to
92
90
  # @param [Item State] from OpenHAB Item State item or group needs to be coming from
91
+ # @param [Object] attach to trigger
93
92
  #
94
93
  # @return [Trigger] OpenHAB trigger
95
94
  #
96
95
  def changed_wait(item, duration:, to: nil, from: nil, attach: nil)
97
- trigger = create_changed_trigger(item, nil, nil, attach)
98
- logger.trace("Creating Changed Wait Change Trigger for #{item}")
99
- @trigger_delays[trigger.id] = TriggerDelay.new(to: to, from: from, duration: duration)
96
+ item_name = item.respond_to?(:name) ? item.name : item.to_s
97
+ logger.trace("Creating Changed Wait Change Trigger for Item(#{item_name}) Duration(#{duration}) "\
98
+ "To(#{to}) From(#{from}) Attach(#{attach})")
99
+ create_changed_trigger(item, nil, nil, attach).tap do |trigger|
100
+ @trigger_conditions[trigger.id] = Conditions::Duration.new(to: to, from: from, duration: duration)
101
+ end
102
+ end
103
+
104
+ #
105
+ # Creates a trigger with a range condition on either 'from' or 'to' field
106
+ # @param [Object] item to create changed trigger on
107
+ # @param [Object] from state to restrict trigger to
108
+ # @param [Object] to state restrict trigger to
109
+ # @param [Object] attach to trigger
110
+ # @return [Trigger] OpenHAB trigger
111
+ #
112
+ def create_changed_range_trigger(item, from:, to:, attach:)
113
+ from, to = Conditions::Proc.range_procs(from, to)
114
+ create_changed_proc_trigger(item, from: from, to: to, attach: attach)
115
+ end
116
+
117
+ #
118
+ # Creates a trigger with a proc condition on either 'from' or 'to' field
119
+ # @param [Object] item to create changed trigger on
120
+ # @param [Object] from state to restrict trigger to
121
+ # @param [Object] to state restrict trigger to
122
+ # @param [Object] attach to trigger
123
+ # @return [Trigger] OpenHAB trigger
124
+ #
125
+ def create_changed_proc_trigger(item, from:, to:, attach:)
126
+ # swap from/to w/ nil if from/to is a proc
127
+ # rubocop:disable Style/ParallelAssignment
128
+ from_proc, from = from, nil if from.is_a? Proc
129
+ to_proc, to = to, nil if to.is_a? Proc
130
+ # rubocop:enable Style/ParallelAssignment
131
+ trigger = create_changed_trigger(item, from, to, attach)
132
+ @trigger_conditions[trigger.id] = Conditions::Proc.new(to: to_proc, from: from_proc)
100
133
  trigger
101
134
  end
102
135
 
@@ -104,8 +137,8 @@ module OpenHAB
104
137
  # Create a changed trigger
105
138
  #
106
139
  # @param [Object] item to create changed trigger on
107
- # @param [String] from state to restrict trigger to
108
- # @param [String] to state restrict trigger to
140
+ # @param [Object] from state to restrict trigger to
141
+ # @param [Object] to state restrict trigger to
109
142
  #
110
143
  #
111
144
  def create_changed_trigger(item, from, to, attach)
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openhab/log/logger'
4
+
5
+ module OpenHAB
6
+ module DSL
7
+ module Rules
8
+ module Triggers
9
+ #
10
+ # Module for conditions for triggers
11
+ #
12
+ module Conditions
13
+ include OpenHAB::Log
14
+ #
15
+ # this is a no-op condition which simply executes the provided block
16
+ #
17
+
18
+ #
19
+ # Struct capturing data necessary for a conditional trigger
20
+ #
21
+ # TriggerDelay = Struct.new(:to, :from, :duration, :timer, :tracking_to, keyword_init: true) do
22
+ # def timer_active?
23
+ # timer&.is_active
24
+ # end
25
+ # end
26
+ class Duration
27
+ #
28
+ # Create a new duration condition
29
+ # @param [Object] to optional condition on to state
30
+ # @param [Object] from optional condition on from state
31
+ # @param [Duration] Duration to state must stay at specific value before triggering
32
+ #
33
+ def initialize(to:, from:, duration:)
34
+ to = Conditions::Proc.from_value(to)
35
+ from = Conditions::Proc.from_value(from)
36
+ @conditions = Conditions::Proc.new(to: to, from: from)
37
+ @duration = duration
38
+ logger.trace "Created Duration Condition To(#{to}) From(#{from}) "\
39
+ "Conditions(#{@conditions}) Duration(#{@duration})"
40
+ end
41
+
42
+ # Process rule
43
+ # @param [Hash] inputs inputs from trigger
44
+ #
45
+ def process(mod:, inputs:, &block)
46
+ process_trigger_delay(mod, inputs, &block)
47
+ end
48
+
49
+ private
50
+
51
+ #
52
+ # Checks if there is an active timer
53
+ # @return [true, false] true if the timer exists and is active, false otherwise
54
+ def timer_active?
55
+ @timer&.is_active
56
+ end
57
+
58
+ #
59
+ # Check if trigger guards prevent rule execution
60
+ #
61
+ # @param [Map] inputs OpenHAB map object describing rule trigger
62
+ #
63
+ # @return [Boolean] True if the rule should execute, false if trigger guard prevents execution
64
+ #
65
+ def check_trigger_guards(inputs)
66
+ new_state, old_state = retrieve_states(inputs)
67
+ @conditions.check_from(state: old_state) && @conditions.check_to(state: new_state)
68
+ end
69
+
70
+ #
71
+ # Rerieve the newState and oldState, alternatively newStatus and oldStatus
72
+ # from the input map
73
+ #
74
+ # @param [Map] inputs OpenHAB map object describing rule trigger
75
+ #
76
+ # @return [Array] An array of the values for [newState, oldState] or [newStatus, oldStatus]
77
+ #
78
+ def retrieve_states(inputs)
79
+ new_state = inputs['newState'] || thing_status_to_sym(inputs['newStatus'])
80
+ old_state = inputs['oldState'] || thing_status_to_sym(inputs['oldStatus'])
81
+
82
+ [new_state, old_state]
83
+ end
84
+
85
+ #
86
+ # Converts a ThingStatus object to a ruby Symbol
87
+ #
88
+ # @param [Java::OrgOpenhabCoreThing::ThingStatus] status A ThingStatus instance
89
+ #
90
+ # @return [Symbol] A corresponding symbol, in lower case
91
+ #
92
+ def thing_status_to_sym(status)
93
+ status&.to_s&.downcase&.to_sym
94
+ end
95
+
96
+ #
97
+ # Process any matching trigger delays
98
+ #
99
+ # @param [Map] mod OpenHAB map object describing rule trigger
100
+ # @param [Map] inputs OpenHAB map object describing rule trigger
101
+ #
102
+ #
103
+ def process_trigger_delay(mod, inputs, &block)
104
+ if timer_active?
105
+ process_active_timer(inputs, mod, &block)
106
+ elsif check_trigger_guards(inputs)
107
+ logger.trace("Trigger Guards Matched for #{self}, delaying rule execution")
108
+ # Add timer and attach timer to delay object, and also state being tracked to so
109
+ # timer can be cancelled if state changes
110
+ # Also another timer should not be created if changed to same value again but instead rescheduled
111
+ create_trigger_delay_timer(inputs, mod, &block)
112
+ else
113
+ logger.trace("Trigger Guards did not match for #{self}, ignoring trigger.")
114
+ end
115
+ end
116
+
117
+ #
118
+ # Creates a timer for trigger delays
119
+ #
120
+ # @param [Hash] inputs rule trigger inputs
121
+ # @param [Hash] mod rule trigger mods
122
+ #
123
+ #
124
+ def create_trigger_delay_timer(inputs, _mod)
125
+ logger.trace("Creating timer for trigger delay #{self}")
126
+ @timer = after(@duration) do
127
+ logger.trace("Delay Complete for #{self}, executing rule")
128
+ @timer = nil
129
+ yield
130
+ end
131
+ @tracking_to, = retrieve_states(inputs)
132
+ end
133
+
134
+ #
135
+ # Process an active trigger timer
136
+ #
137
+ # @param [Hash] inputs rule trigger inputs
138
+ # @param [Hash] mod rule trigger mods
139
+ #
140
+ #
141
+ def process_active_timer(inputs, mod, &block)
142
+ state, = retrieve_states(inputs)
143
+ if state == @tracking_to
144
+ logger.trace("Item changed to #{state} for #{self}, rescheduling timer.")
145
+ @timer.reschedule(ZonedDateTime.now.plus(@duration))
146
+ else
147
+ logger.trace("Item changed to #{state} for #{self}, canceling timer.")
148
+ @timer.cancel
149
+ # Reprocess trigger delay after canceling to track new state (if guards matched, etc)
150
+ process_trigger_delay(mod, inputs, &block)
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end