openhab-scripting 2.9.1

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