openhab-scripting 2.9.1

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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/workflow.yml +327 -0
  3. data/.gitignore +17 -0
  4. data/.java-version +1 -0
  5. data/.rspec +1 -0
  6. data/.yardopts +1 -0
  7. data/CHANGELOG.md +113 -0
  8. data/Gemfile +28 -0
  9. data/Gemfile.lock +245 -0
  10. data/Guardfile +35 -0
  11. data/LICENSE +277 -0
  12. data/README.md +23 -0
  13. data/Rakefile +406 -0
  14. data/bin/console +15 -0
  15. data/bin/setup +8 -0
  16. data/config/userdata/config/org/openhab/restauth.config +3 -0
  17. data/cucumber.yml +1 -0
  18. data/docs/_config.yml +135 -0
  19. data/docs/contributing/index.md +47 -0
  20. data/docs/examples/conversions.md +123 -0
  21. data/docs/examples/index.md +61 -0
  22. data/docs/index.md +19 -0
  23. data/docs/installation/index.md +26 -0
  24. data/docs/motivation/index.md +27 -0
  25. data/docs/usage/execution.md +9 -0
  26. data/docs/usage/execution/delay.md +48 -0
  27. data/docs/usage/execution/otherwise.md +30 -0
  28. data/docs/usage/execution/run.md +70 -0
  29. data/docs/usage/execution/triggered.md +48 -0
  30. data/docs/usage/guards.md +51 -0
  31. data/docs/usage/guards/between.md +30 -0
  32. data/docs/usage/guards/not_if.md +41 -0
  33. data/docs/usage/guards/only_if.md +40 -0
  34. data/docs/usage/index.md +11 -0
  35. data/docs/usage/items.md +66 -0
  36. data/docs/usage/items/contact.md +84 -0
  37. data/docs/usage/items/dimmer.md +147 -0
  38. data/docs/usage/items/groups.md +76 -0
  39. data/docs/usage/items/number.md +225 -0
  40. data/docs/usage/items/string.md +49 -0
  41. data/docs/usage/items/switch.md +85 -0
  42. data/docs/usage/misc.md +7 -0
  43. data/docs/usage/misc/actions.md +108 -0
  44. data/docs/usage/misc/duration.md +21 -0
  45. data/docs/usage/misc/gems.md +25 -0
  46. data/docs/usage/misc/logging.md +21 -0
  47. data/docs/usage/misc/metadata.md +128 -0
  48. data/docs/usage/misc/store_states.md +42 -0
  49. data/docs/usage/misc/time_of_day.md +69 -0
  50. data/docs/usage/misc/timers.md +67 -0
  51. data/docs/usage/rule.md +43 -0
  52. data/docs/usage/things.md +29 -0
  53. data/docs/usage/triggers.md +8 -0
  54. data/docs/usage/triggers/changed.md +57 -0
  55. data/docs/usage/triggers/channel.md +54 -0
  56. data/docs/usage/triggers/command.md +69 -0
  57. data/docs/usage/triggers/cron.md +19 -0
  58. data/docs/usage/triggers/every.md +76 -0
  59. data/docs/usage/triggers/updated.md +78 -0
  60. data/lib/openhab.rb +39 -0
  61. data/lib/openhab/configuration.rb +16 -0
  62. data/lib/openhab/core/cron.rb +27 -0
  63. data/lib/openhab/core/debug.rb +34 -0
  64. data/lib/openhab/core/dsl.rb +47 -0
  65. data/lib/openhab/core/dsl/actions.rb +107 -0
  66. data/lib/openhab/core/dsl/entities.rb +103 -0
  67. data/lib/openhab/core/dsl/gems.rb +29 -0
  68. data/lib/openhab/core/dsl/group.rb +91 -0
  69. data/lib/openhab/core/dsl/items/items.rb +39 -0
  70. data/lib/openhab/core/dsl/items/number_item.rb +217 -0
  71. data/lib/openhab/core/dsl/items/string_item.rb +102 -0
  72. data/lib/openhab/core/dsl/monkey_patch/actions/actions.rb +4 -0
  73. data/lib/openhab/core/dsl/monkey_patch/actions/script_thing_actions.rb +22 -0
  74. data/lib/openhab/core/dsl/monkey_patch/events.rb +5 -0
  75. data/lib/openhab/core/dsl/monkey_patch/events/item_command.rb +13 -0
  76. data/lib/openhab/core/dsl/monkey_patch/events/item_state_changed.rb +25 -0
  77. data/lib/openhab/core/dsl/monkey_patch/events/thing_status_info.rb +26 -0
  78. data/lib/openhab/core/dsl/monkey_patch/items/contact_item.rb +54 -0
  79. data/lib/openhab/core/dsl/monkey_patch/items/dimmer_item.rb +125 -0
  80. data/lib/openhab/core/dsl/monkey_patch/items/group_item.rb +27 -0
  81. data/lib/openhab/core/dsl/monkey_patch/items/items.rb +130 -0
  82. data/lib/openhab/core/dsl/monkey_patch/items/metadata.rb +259 -0
  83. data/lib/openhab/core/dsl/monkey_patch/items/switch_item.rb +86 -0
  84. data/lib/openhab/core/dsl/monkey_patch/ruby/number.rb +69 -0
  85. data/lib/openhab/core/dsl/monkey_patch/ruby/range.rb +46 -0
  86. data/lib/openhab/core/dsl/monkey_patch/ruby/ruby.rb +5 -0
  87. data/lib/openhab/core/dsl/monkey_patch/types/decimal_type.rb +24 -0
  88. data/lib/openhab/core/dsl/monkey_patch/types/on_off_type.rb +41 -0
  89. data/lib/openhab/core/dsl/monkey_patch/types/open_closed_type.rb +25 -0
  90. data/lib/openhab/core/dsl/monkey_patch/types/percent_type.rb +23 -0
  91. data/lib/openhab/core/dsl/monkey_patch/types/types.rb +7 -0
  92. data/lib/openhab/core/dsl/property.rb +85 -0
  93. data/lib/openhab/core/dsl/rule/channel.rb +41 -0
  94. data/lib/openhab/core/dsl/rule/cron.rb +115 -0
  95. data/lib/openhab/core/dsl/rule/guard.rb +99 -0
  96. data/lib/openhab/core/dsl/rule/item.rb +207 -0
  97. data/lib/openhab/core/dsl/rule/rule.rb +374 -0
  98. data/lib/openhab/core/dsl/rule/triggers.rb +77 -0
  99. data/lib/openhab/core/dsl/states.rb +63 -0
  100. data/lib/openhab/core/dsl/things.rb +93 -0
  101. data/lib/openhab/core/dsl/time_of_day.rb +203 -0
  102. data/lib/openhab/core/dsl/timers.rb +85 -0
  103. data/lib/openhab/core/dsl/types/quantity.rb +255 -0
  104. data/lib/openhab/core/dsl/units.rb +41 -0
  105. data/lib/openhab/core/duration.rb +69 -0
  106. data/lib/openhab/core/log.rb +175 -0
  107. data/lib/openhab/core/patch_load_path.rb +7 -0
  108. data/lib/openhab/core/startup_delay.rb +22 -0
  109. data/lib/openhab/osgi.rb +52 -0
  110. data/lib/openhab/version.rb +9 -0
  111. data/openhab-scripting.gemspec +30 -0
  112. data/openhab_rules/warmup.rb +5 -0
  113. metadata +157 -0
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'core/log'
4
+ require 'core/dsl/group'
5
+ require 'core/dsl/things'
6
+ require 'core/dsl/rule/triggers'
7
+ require 'openhab/core/dsl/rule/triggers'
8
+
9
+ module OpenHAB
10
+ module Core
11
+ module DSL
12
+ module Rule
13
+ #
14
+ # Triggers for items in rules
15
+ #
16
+ module Item
17
+ include Logging
18
+ include OpenHAB::Core::DSL::Rule
19
+ include OpenHAB::Core::DSL::Groups
20
+ include OpenHAB::Core::DSL::Things
21
+
22
+ #
23
+ # Struct capturing data necessary for a conditional trigger
24
+ #
25
+ TriggerDelay = Struct.new(:to, :from, :duration, :timer, :tracking_to, keyword_init: true)
26
+
27
+ #
28
+ # Create a TriggerDelay for for an item or group that is changed for a specific duration
29
+ #
30
+ # @param [Object] item to create trigger delay for
31
+ # @param [OpenHAB::Core::Duration] duration to delay trigger for until condition is met
32
+ # @param [Item State] to OpenHAB Item State item or group needs to change to
33
+ # @param [Item State] from OpenHAB Item State item or group needs to be coming from
34
+ #
35
+ # @return [Array] Array of current TriggerDelay objects
36
+ #
37
+ def changed_wait(item, duration:, to: nil, from: nil)
38
+ # Convert to testing the group if group specified rather than item
39
+ item = item.group if item.is_a? Group
40
+
41
+ # If GroupItems specified, use the group state trigger instead
42
+ if item.is_a? GroupItems
43
+ config = { 'groupName' => item.group.name }
44
+ trigger = Trigger::GROUP_STATE_CHANGE
45
+ else
46
+ config = { 'itemName' => item.name }
47
+ trigger = Trigger::ITEM_STATE_CHANGE
48
+ end
49
+ logger.trace("Creating Changed Wait Change Trigger for #{config}")
50
+ trigger = append_trigger(trigger, config)
51
+ @trigger_delays = { trigger.id => TriggerDelay.new(to: to, from: from, duration: duration) }
52
+ end
53
+
54
+ #
55
+ # Create a trigger for when an item or group receives a command
56
+ #
57
+ # The commands/commands parameters are replicated for DSL fluency
58
+ #
59
+ # @param [Array] items Array of items to create trigger for
60
+ # @param [Array] command commands to match for trigger
61
+ # @param [Array] commands commands to match for trigger
62
+ #
63
+ #
64
+ def received_command(*items, command: nil, commands: nil)
65
+ items.flatten.each do |item|
66
+ logger.trace("Creating received command trigger for item(#{item}) command(#{command}) commands(#{commands})")
67
+
68
+ # Combine command and commands, doing union so only a single nil will be in the combined array.
69
+ combined_commands = ([command] | [commands]).flatten
70
+
71
+ # If either command or commands has a value and one is nil, we need to remove nil from the array.
72
+ # If it is only now a single nil value, we leave the nil in place, so that we create a trigger
73
+ # That isn't looking for a specific command.
74
+ combined_commands = combined_commands.compact unless combined_commands.all?(&:nil?)
75
+
76
+ combined_commands.each do |cmd|
77
+ if item.is_a? GroupItems
78
+ config = { 'groupName' => item.group.name }
79
+ trigger = Trigger::GROUP_COMMAND
80
+ else
81
+ config = { 'itemName' => item.name }
82
+ trigger = Trigger::ITEM_COMMAND
83
+ end
84
+ config['command'] = cmd.to_s unless cmd.nil?
85
+ append_trigger(trigger, config)
86
+ end
87
+ end
88
+ end
89
+
90
+ #
91
+ # Create a trigger when item, group or thing is updated
92
+ #
93
+ # @param [Array] items array to trigger on updated
94
+ # @param [State] to to match for tigger
95
+ #
96
+ # @return [Trigger] Trigger for updated entity
97
+ #
98
+ def updated(*items, to: nil)
99
+ items.flatten.each do |item|
100
+ logger.trace("Creating updated trigger for item(#{item}) to(#{to})")
101
+ [to].flatten.each do |to_state|
102
+ case item
103
+ when GroupItems
104
+ config = { 'groupName' => item.group.name }
105
+ config['state'] = to_state.to_s unless to_state.nil?
106
+ trigger = Trigger::GROUP_STATE_UPDATE
107
+ when Thing
108
+ trigger, config = trigger_for_thing(item, Trigger::THING_UPDATE, to_state)
109
+ else
110
+ config = { 'itemName' => item.name }
111
+ config['state'] = to_state.to_s unless to_state.nil?
112
+ trigger = Trigger::ITEM_STATE_UPDATE
113
+ end
114
+ append_trigger(trigger, config)
115
+ end
116
+ end
117
+ end
118
+
119
+ #
120
+ # Creates a trigger item, group and thing changed
121
+ #
122
+ # @param [Object] items array of objects to create trigger for
123
+ # @param [to] to state for object to change for
124
+ # @param [from] from <description>
125
+ # @param [OpenHAB::Core::Duration] for Duration to delay trigger until to state is met
126
+ #
127
+ # @return [Trigger] OpenHAB trigger
128
+ #
129
+ def changed(*items, to: nil, from: nil, for: nil)
130
+ items.flatten.each do |item|
131
+ item = item.group if item.is_a? Group
132
+ logger.trace("Creating changed trigger for entity(#{item}), to(#{to}), from(#{from})")
133
+ # for is a reserved word in ruby, so use local_variable_get :for
134
+ if (wait_duration = binding.local_variable_get(:for))
135
+ changed_wait(item, to: to, from: from, duration: wait_duration)
136
+ else
137
+ # Place in array and flatten to support multiple to elements or single or nil
138
+ [to].flatten.each do |to_state|
139
+ case item
140
+ when GroupItems
141
+ config = { 'groupName' => item.group.name }
142
+ config['state'] = to_state.to_s if to_state
143
+ config['previousState'] = from.to_s if from
144
+ trigger = Trigger::GROUP_STATE_CHANGE
145
+ when Thing
146
+ trigger, config = trigger_for_thing(item, Trigger::THING_CHANGE, to_state, from)
147
+ else
148
+ config = { 'itemName' => item.name }
149
+ config['state'] = to_state.to_s if to_state
150
+ config['previousState'] = from.to_s if from
151
+ trigger = Trigger::ITEM_STATE_CHANGE
152
+ end
153
+ append_trigger(trigger, config)
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ #
162
+ # Append a trigger to the list of triggeres
163
+ #
164
+ # @param [String] type of trigger to create
165
+ # @param [Map] config map describing trigger configuration
166
+ #
167
+ # @return [Trigger] OpenHAB trigger
168
+ #
169
+ def append_trigger(type, config)
170
+ logger.trace("Creating trigger of type #{type} for #{config}")
171
+ trigger = Trigger.trigger(type: type, config: config)
172
+ @triggers << trigger
173
+ trigger
174
+ end
175
+
176
+ #
177
+ # Create a trigger for a thing
178
+ #
179
+ # @param [Thing] thing to create trigger for
180
+ # @param [Trigger] trigger to map with thing
181
+ # @param [State] to for thing
182
+ # @param [State] from state of thing
183
+ #
184
+ # @return [Array] Trigger and config for thing
185
+ #
186
+ def trigger_for_thing(thing, trigger, to = nil, from = nil)
187
+ config = { 'thingUID' => thing.uid.to_s }
188
+ config['status'] = trigger_state_from_symbol(to).to_s if to
189
+ config['previousStatus'] = trigger_state_from_symbol(from).to_s if from
190
+ [trigger, config]
191
+ end
192
+
193
+ #
194
+ # converts object to upcase string if its a symbol
195
+ #
196
+ # @param [sym] sym potential symbol to convert
197
+ #
198
+ # @return [String] Upcased symbol as string
199
+ #
200
+ def trigger_state_from_symbol(sym)
201
+ sym.to_s.upcase if (sym.is_a? Symbol) || sym
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,374 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'java'
4
+ require 'pp'
5
+ require 'core/dsl/property'
6
+ require 'core/dsl/rule/cron'
7
+ require 'core/dsl/rule/triggers'
8
+ require 'core/dsl/rule/item'
9
+ require 'core/dsl/rule/channel'
10
+ require 'core/dsl/rule/guard'
11
+ require 'core/dsl/entities'
12
+ require 'core/dsl/time_of_day'
13
+ require 'core/dsl'
14
+ require 'core/dsl/timers'
15
+
16
+ module OpenHAB
17
+ module Core
18
+ module DSL
19
+ #
20
+ # Creates and manages OpenHAB Rules
21
+ #
22
+ module Rule
23
+ #
24
+ # Rule configuration for OpenHAB Rules engine
25
+ #
26
+ class RuleConfig
27
+ include EntityLookup
28
+ include OpenHAB::Core::DSL::Rule::Cron
29
+ include Guard
30
+ include Item
31
+ include Channel
32
+ include DSLProperty
33
+ include Logging
34
+ extend OpenHAB::Core::DSL
35
+
36
+ java_import org.openhab.core.library.items.SwitchItem
37
+
38
+ # @return [Array] Of triggers
39
+ attr_reader :triggers
40
+
41
+ # @return [Array] Of trigger delays
42
+ attr_reader :trigger_delays
43
+
44
+ #
45
+ # Struct holding a run block
46
+ #
47
+ Run = Struct.new(:block)
48
+
49
+ #
50
+ # Struct holding a Triggered block
51
+ #
52
+ Trigger = Struct.new(:block)
53
+
54
+ #
55
+ # Struct holding an otherwise block
56
+ #
57
+ Otherwise = Struct.new(:block)
58
+
59
+ #
60
+ # Struct holding rule delays
61
+ #
62
+ Delay = Struct.new(:duration)
63
+
64
+ prop_array :run, array_name: :run_queue, wrapper: Run
65
+ prop_array :triggered, array_name: :run_queue, wrapper: Trigger
66
+ prop_array :delay, array_name: :run_queue, wrapper: Delay
67
+ prop_array :otherwise, array_name: :run_queue, wrapper: Otherwise
68
+
69
+ prop :name
70
+ prop :description
71
+ prop :enabled
72
+ prop :between
73
+
74
+ #
75
+ # Create a new RuleConfig
76
+ #
77
+ # @param [Object] caller_binding The object initializing this configuration.
78
+ # Used to execute within the object's context
79
+ #
80
+ def initialize(caller_binding)
81
+ @triggers = []
82
+ @trigger_delays = {}
83
+ @enabled = true
84
+ @on_start = false
85
+ @caller = caller_binding.eval 'self'
86
+ end
87
+
88
+ #
89
+ # Start this rule on system startup
90
+ #
91
+ # @param [Boolean] run_on_start Run this rule on start, defaults to True
92
+ #
93
+ #
94
+ def on_start(run_on_start = true)
95
+ @on_start = run_on_start
96
+ end
97
+
98
+ #
99
+ # Checks if this rule should run on start
100
+ #
101
+ # @return [Boolean] True if rule should run on start, false otherwise.
102
+ #
103
+ def on_start?
104
+ @on_start
105
+ end
106
+
107
+ #
108
+ # Run the supplied block inside the object instance of the object that created the rule config
109
+ #
110
+ # @yield [] Block executed in context of the object creating the rule config
111
+ #
112
+ #
113
+ def my(&block)
114
+ @caller.instance_eval(&block)
115
+ end
116
+ end
117
+
118
+ #
119
+ # Create a new rule
120
+ #
121
+ # @param [String] name <description>
122
+ # @yield [] Block executed in context of a RuleConfic
123
+ #
124
+ #
125
+ def rule(name, &block)
126
+ config = RuleConfig.new(block.binding)
127
+ config.name(name)
128
+ config.instance_eval(&block)
129
+ return if !config.on_start? && config.triggers.empty?
130
+
131
+ guard = Guard::Guard.new(only_if: config.only_if, not_if: config.not_if)
132
+
133
+ logger.trace do
134
+ "Triggers: #{config.triggers} Guard: #{guard} Runs: #{config.run} on_start: #{config.on_start?}"
135
+ end
136
+ config.triggers.each { |trigger| logger.trace { "Trigger UID: #{trigger.id}" } }
137
+ logger.trace { "Trigger Waits #{config.trigger_delays}" }
138
+
139
+ if config.enabled
140
+ # Convert between to correct range or nil if not set
141
+ between = config.between&.yield_self { between(config.between) }
142
+
143
+ rule = Rule.new(name: config.name, description: config.description, run_queue: config.run_queue,
144
+ guard: guard, between: between, trigger_delays: config.trigger_delays)
145
+
146
+ rule.set_triggers(config.triggers)
147
+ am = $scriptExtension.get('automationManager')
148
+ am.addRule(rule)
149
+ rule.execute(nil, nil) if config.on_start?
150
+ else
151
+ logger.debug "#{name} marked as disabled, not creating rule."
152
+ end
153
+ end
154
+
155
+ #
156
+ # JRuby extension to OpenHAB Rule
157
+ #
158
+ class Rule < Java::OrgOpenhabCoreAutomationModuleScriptRulesupportSharedSimple::SimpleRule
159
+ include Logging
160
+ include OpenHAB::Core::DSL::Tod
161
+ java_import java.time.ZonedDateTime
162
+
163
+ #
164
+ # Create a new Rule
165
+ #
166
+ # @param [String] name Name of the rule
167
+ # @param [String] description of the rule
168
+ # @param [Array] run_queue array of procs to execute for rule
169
+ # @param [Array] guard array of guards
170
+ # @param [Range] between range in which the rule will execute
171
+ # @param [Array] trigger_delays Array of delays for tiggers based on item config
172
+ #
173
+ def initialize(name:, description:, run_queue:, guard:, between:, trigger_delays:)
174
+ super()
175
+ setName(name)
176
+ setDescription(description)
177
+ @run_queue = run_queue
178
+ @guard = guard
179
+ @between = between || OpenHAB::Core::DSL::Tod::ALL_DAY
180
+ @trigger_delays = trigger_delays
181
+ end
182
+
183
+ #
184
+ # Execute the rule
185
+ #
186
+ # @param [Map] mod map provided by OpenHAB rules engine
187
+ # @param [Map] inputs map provided by OpenHAB rules engine containing event and other information
188
+ #
189
+ #
190
+ def execute(mod, inputs)
191
+ logger.trace { "Execute called with mod (#{mod&.to_string}) and inputs (#{inputs&.pretty_inspect}" }
192
+ logger.trace { "Event details #{inputs['event'].pretty_inspect}" } if inputs&.key?('event')
193
+ if trigger_delay inputs
194
+ process_trigger_delay(mod, inputs)
195
+ else
196
+ # If guards are satisfied execute the run type blocks
197
+ # If they are not satisfied, execute the Othewise blocks
198
+ queue = case check_guards(event: inputs&.dig('event'))
199
+ when true
200
+ @run_queue.dup
201
+ when false
202
+ @run_queue.dup.grep(RuleConfig::Otherwise)
203
+ end
204
+ process_queue(queue, mod, inputs)
205
+ end
206
+ end
207
+
208
+ private
209
+
210
+ #
211
+ # Returns trigger delay from inputs if it exists
212
+ #
213
+ # @param [Map] inputs map from OpenHAB containing UID
214
+ #
215
+ # @return [Array] Array of trigger delays that match rule UID
216
+ #
217
+ def trigger_delay(inputs)
218
+ # Parse this to get the trigger UID:
219
+ # ["72698819-83cb-498a-8e61-5aab8b812623.event", "oldState", "module", "72698819-83cb-498a-8e61-5aab8b812623.oldState", "event", "newState", "72698819-83cb-498a-8e61-5aab8b812623.newState"
220
+ @trigger_delays[inputs&.keys&.grep(/\.event$/)&.first&.chomp('.event')]
221
+ end
222
+
223
+ #
224
+ # Check if trigger guards prevent rule execution
225
+ #
226
+ # @param [Delay] trigger_delay rules delaying trigger because of
227
+ # @param [Map] inputs map from OpenHAB describing the rle trigger
228
+ #
229
+ # @return [Boolean] True if the rule should execute, false if trigger guard prevents execution
230
+ #
231
+ def check_trigger_guards(trigger_delay, inputs)
232
+ old_state = inputs['oldState']
233
+ new_state = inputs['newState']
234
+ if trigger_delay.from.nil? || trigger_delay.from == old_state
235
+ if trigger_delay.to.nil? || trigger_delay.to == new_state
236
+ return true
237
+ else
238
+ logger.trace("Skipped execution of rule '#{name}' because to state #{new_state} does not equal specified state(#{trigger_delay.to})")
239
+ end
240
+ else
241
+ logger.trace("Skipped execution of rule '#{name}' because old state #{old_state} does not equal specified state(#{trigger_delay.from})")
242
+ end
243
+ false
244
+ end
245
+
246
+ #
247
+ # Process any matching trigger delays
248
+ #
249
+ # @param [Map] mod OpenHAB map object describing rule trigger
250
+ # @param [Map] inputs OpenHAB map object describing rule trigge
251
+ #
252
+ #
253
+ def process_trigger_delay(mod, inputs)
254
+ trigger_delay = trigger_delay(inputs)
255
+ if check_trigger_guards(trigger_delay, inputs)
256
+ logger.trace("Trigger Guards Matched for #{trigger_delay}, delaying rule execution")
257
+ # Add timer and attach timer to delay object, and also state being tracked to so timer can be cancelled if state changes
258
+ # Also another timer should not be created if changed to same value again but instead rescheduled
259
+ if trigger_delay.timer.nil? || trigger_delay.timer.is_active == false
260
+ logger.trace("Creating timer for rule #{name} and trigger delay #{trigger_delay}")
261
+ trigger_delay.timer = after(trigger_delay.duration) do
262
+ logger.trace("Delay Complete for #{trigger_delay}, executing rule")
263
+ trigger_delay.timer = nil
264
+ process_queue(@run_queue.dup, mod, inputs)
265
+ end
266
+ trigger_delay.tracking_to = inputs['newState']
267
+ else
268
+ # Timer active
269
+ state = inputs['newState']
270
+ if state != trigger_delay.tracking_to
271
+ logger.trace("Item changed to #{state} for #{trigger_delay}, cancelling timer.")
272
+ trigger_delay.timer.cancel
273
+ # Reprocess trigger delay after cancelling to track new state (if guards matched, etc)
274
+ process_trigger_delay(mod, inputs)
275
+ else
276
+ logger.trace("Item changed to #{state} for #{trigger_delay}, rescheduling timer.")
277
+ trigger_delay.timer.reschedule(ZonedDateTime.now.plus(Java::JavaTime::Duration.ofMillis(duration.to_ms)))
278
+ end
279
+ end
280
+ else
281
+ logger.trace("Trigger Guards did not match for #{trigger_delay}, ignoring trigger.")
282
+ end
283
+ end
284
+
285
+ #
286
+ # Check if any guards prevent execution
287
+ #
288
+ # @param [Map] event OpenHAB rule trigger event
289
+ #
290
+ # @return [Boolean] True if guards says rule should execute, false otherwise
291
+ #
292
+ def check_guards(event:)
293
+ if @guard.should_run? event
294
+ now = TimeOfDay.now
295
+ if @between.cover? now
296
+ return true
297
+ else
298
+ logger.trace("Skipped execution of rule '#{name}' because the current time #{now} is not between #{@between.begin} and #{@between.end}")
299
+ end
300
+ else
301
+ logger.trace("Skipped execution of rule '#{name}' because of guard #{@guard}")
302
+ end
303
+ false
304
+ end
305
+
306
+ #
307
+ # Process the run queue
308
+ #
309
+ # @param [Array] run_queue array of procs of various types to execute
310
+ # @param [Map] mod OpenHAB map object describing rule trigger
311
+ # @param [Map] inputs OpenHAB map object describing rule trigge
312
+ #
313
+ #
314
+ def process_queue(run_queue, mod, inputs)
315
+ while (task = run_queue.shift)
316
+ case task
317
+ when RuleConfig::Run
318
+
319
+ event = inputs&.dig('event')
320
+
321
+ logger.trace { "Executing rule '#{name}' run block with event(#{event})" }
322
+ task.block.call(event)
323
+ when RuleConfig::Trigger
324
+
325
+ triggering_item = $ir.get(inputs&.dig('event')&.itemName)
326
+
327
+ logger.trace { "Executing rule '#{name}' trigger block with item (#{triggering_item})" }
328
+ task.block.call(triggering_item) if triggering_item
329
+
330
+ when RuleConfig::Delay
331
+ remaining_queue = run_queue.slice!(0, run_queue.length)
332
+ after(task.duration) { process_queue(remaining_queue, mod, inputs) }
333
+
334
+ when RuleConfig::Otherwise
335
+ event = inputs&.dig('event')
336
+ logger.trace { "Executing rule '#{name}' otherwise block with event(#{event})" }
337
+ task.block.call(event)
338
+
339
+ end
340
+ end
341
+ end
342
+
343
+ #
344
+ # Create a new hash in which all elements are converted to strings
345
+ #
346
+ # @param [Map] hash in which all elements should be converted to strings
347
+ #
348
+ # @return [Map] new map with values converted to strings
349
+ #
350
+ def inspect_hash(hash)
351
+ hash.each_with_object({}) do |(key, value), new_hash|
352
+ new_hash[inspect_item(key)] = inspect_item(value)
353
+ end
354
+ end
355
+
356
+ #
357
+ # Convert an individual element into a string based on if it a Ruby or Java object
358
+ #
359
+ # @param [Object] item to convert to a string
360
+ #
361
+ # @return [String] representation of item
362
+ #
363
+ def inspect_item(item)
364
+ if item.respond_to? :to_string
365
+ item.to_string
366
+ elsif item.respond_to? :to_str
367
+ item.to_str
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end
374
+ end