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.
- checksums.yaml +7 -0
- data/.github/workflows/workflow.yml +327 -0
- data/.gitignore +17 -0
- data/.java-version +1 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +113 -0
- data/Gemfile +28 -0
- data/Gemfile.lock +245 -0
- data/Guardfile +35 -0
- data/LICENSE +277 -0
- data/README.md +23 -0
- data/Rakefile +406 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/userdata/config/org/openhab/restauth.config +3 -0
- data/cucumber.yml +1 -0
- data/docs/_config.yml +135 -0
- data/docs/contributing/index.md +47 -0
- data/docs/examples/conversions.md +123 -0
- data/docs/examples/index.md +61 -0
- data/docs/index.md +19 -0
- data/docs/installation/index.md +26 -0
- data/docs/motivation/index.md +27 -0
- data/docs/usage/execution.md +9 -0
- data/docs/usage/execution/delay.md +48 -0
- data/docs/usage/execution/otherwise.md +30 -0
- data/docs/usage/execution/run.md +70 -0
- data/docs/usage/execution/triggered.md +48 -0
- data/docs/usage/guards.md +51 -0
- data/docs/usage/guards/between.md +30 -0
- data/docs/usage/guards/not_if.md +41 -0
- data/docs/usage/guards/only_if.md +40 -0
- data/docs/usage/index.md +11 -0
- data/docs/usage/items.md +66 -0
- data/docs/usage/items/contact.md +84 -0
- data/docs/usage/items/dimmer.md +147 -0
- data/docs/usage/items/groups.md +76 -0
- data/docs/usage/items/number.md +225 -0
- data/docs/usage/items/string.md +49 -0
- data/docs/usage/items/switch.md +85 -0
- data/docs/usage/misc.md +7 -0
- data/docs/usage/misc/actions.md +108 -0
- data/docs/usage/misc/duration.md +21 -0
- data/docs/usage/misc/gems.md +25 -0
- data/docs/usage/misc/logging.md +21 -0
- data/docs/usage/misc/metadata.md +128 -0
- data/docs/usage/misc/store_states.md +42 -0
- data/docs/usage/misc/time_of_day.md +69 -0
- data/docs/usage/misc/timers.md +67 -0
- data/docs/usage/rule.md +43 -0
- data/docs/usage/things.md +29 -0
- data/docs/usage/triggers.md +8 -0
- data/docs/usage/triggers/changed.md +57 -0
- data/docs/usage/triggers/channel.md +54 -0
- data/docs/usage/triggers/command.md +69 -0
- data/docs/usage/triggers/cron.md +19 -0
- data/docs/usage/triggers/every.md +76 -0
- data/docs/usage/triggers/updated.md +78 -0
- data/lib/openhab.rb +39 -0
- data/lib/openhab/configuration.rb +16 -0
- data/lib/openhab/core/cron.rb +27 -0
- data/lib/openhab/core/debug.rb +34 -0
- data/lib/openhab/core/dsl.rb +47 -0
- data/lib/openhab/core/dsl/actions.rb +107 -0
- data/lib/openhab/core/dsl/entities.rb +103 -0
- data/lib/openhab/core/dsl/gems.rb +29 -0
- data/lib/openhab/core/dsl/group.rb +91 -0
- data/lib/openhab/core/dsl/items/items.rb +39 -0
- data/lib/openhab/core/dsl/items/number_item.rb +217 -0
- data/lib/openhab/core/dsl/items/string_item.rb +102 -0
- data/lib/openhab/core/dsl/monkey_patch/actions/actions.rb +4 -0
- data/lib/openhab/core/dsl/monkey_patch/actions/script_thing_actions.rb +22 -0
- data/lib/openhab/core/dsl/monkey_patch/events.rb +5 -0
- data/lib/openhab/core/dsl/monkey_patch/events/item_command.rb +13 -0
- data/lib/openhab/core/dsl/monkey_patch/events/item_state_changed.rb +25 -0
- data/lib/openhab/core/dsl/monkey_patch/events/thing_status_info.rb +26 -0
- data/lib/openhab/core/dsl/monkey_patch/items/contact_item.rb +54 -0
- data/lib/openhab/core/dsl/monkey_patch/items/dimmer_item.rb +125 -0
- data/lib/openhab/core/dsl/monkey_patch/items/group_item.rb +27 -0
- data/lib/openhab/core/dsl/monkey_patch/items/items.rb +130 -0
- data/lib/openhab/core/dsl/monkey_patch/items/metadata.rb +259 -0
- data/lib/openhab/core/dsl/monkey_patch/items/switch_item.rb +86 -0
- data/lib/openhab/core/dsl/monkey_patch/ruby/number.rb +69 -0
- data/lib/openhab/core/dsl/monkey_patch/ruby/range.rb +46 -0
- data/lib/openhab/core/dsl/monkey_patch/ruby/ruby.rb +5 -0
- data/lib/openhab/core/dsl/monkey_patch/types/decimal_type.rb +24 -0
- data/lib/openhab/core/dsl/monkey_patch/types/on_off_type.rb +41 -0
- data/lib/openhab/core/dsl/monkey_patch/types/open_closed_type.rb +25 -0
- data/lib/openhab/core/dsl/monkey_patch/types/percent_type.rb +23 -0
- data/lib/openhab/core/dsl/monkey_patch/types/types.rb +7 -0
- data/lib/openhab/core/dsl/property.rb +85 -0
- data/lib/openhab/core/dsl/rule/channel.rb +41 -0
- data/lib/openhab/core/dsl/rule/cron.rb +115 -0
- data/lib/openhab/core/dsl/rule/guard.rb +99 -0
- data/lib/openhab/core/dsl/rule/item.rb +207 -0
- data/lib/openhab/core/dsl/rule/rule.rb +374 -0
- data/lib/openhab/core/dsl/rule/triggers.rb +77 -0
- data/lib/openhab/core/dsl/states.rb +63 -0
- data/lib/openhab/core/dsl/things.rb +93 -0
- data/lib/openhab/core/dsl/time_of_day.rb +203 -0
- data/lib/openhab/core/dsl/timers.rb +85 -0
- data/lib/openhab/core/dsl/types/quantity.rb +255 -0
- data/lib/openhab/core/dsl/units.rb +41 -0
- data/lib/openhab/core/duration.rb +69 -0
- data/lib/openhab/core/log.rb +175 -0
- data/lib/openhab/core/patch_load_path.rb +7 -0
- data/lib/openhab/core/startup_delay.rb +22 -0
- data/lib/openhab/osgi.rb +52 -0
- data/lib/openhab/version.rb +9 -0
- data/openhab-scripting.gemspec +30 -0
- data/openhab_rules/warmup.rb +5 -0
- 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
|