openhab-jrubyscripting 5.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/openhab/core/actions.rb +163 -0
- data/lib/openhab/core/entity_lookup.rb +144 -0
- data/lib/openhab/core/events/abstract_event.rb +17 -0
- data/lib/openhab/core/events/item_channel_link.rb +36 -0
- data/lib/openhab/core/events/item_command_event.rb +78 -0
- data/lib/openhab/core/events/item_event.rb +22 -0
- data/lib/openhab/core/events/item_state_changed_event.rb +52 -0
- data/lib/openhab/core/events/item_state_event.rb +51 -0
- data/lib/openhab/core/events/thing.rb +29 -0
- data/lib/openhab/core/events/thing_status_info_event.rb +53 -0
- data/lib/openhab/core/events.rb +10 -0
- data/lib/openhab/core/items/accepted_data_types.rb +29 -0
- data/lib/openhab/core/items/color_item.rb +52 -0
- data/lib/openhab/core/items/contact_item.rb +52 -0
- data/lib/openhab/core/items/date_time_item.rb +58 -0
- data/lib/openhab/core/items/dimmer_item.rb +148 -0
- data/lib/openhab/core/items/generic_item.rb +344 -0
- data/lib/openhab/core/items/group_item.rb +174 -0
- data/lib/openhab/core/items/image_item.rb +109 -0
- data/lib/openhab/core/items/location_item.rb +34 -0
- data/lib/openhab/core/items/metadata/hash.rb +390 -0
- data/lib/openhab/core/items/metadata/namespace_hash.rb +469 -0
- data/lib/openhab/core/items/metadata.rb +11 -0
- data/lib/openhab/core/items/number_item.rb +62 -0
- data/lib/openhab/core/items/numeric_item.rb +22 -0
- data/lib/openhab/core/items/persistence.rb +327 -0
- data/lib/openhab/core/items/player_item.rb +66 -0
- data/lib/openhab/core/items/proxy.rb +59 -0
- data/lib/openhab/core/items/registry.rb +66 -0
- data/lib/openhab/core/items/rollershutter_item.rb +68 -0
- data/lib/openhab/core/items/semantics/enumerable.rb +152 -0
- data/lib/openhab/core/items/semantics.rb +476 -0
- data/lib/openhab/core/items/state_storage.rb +53 -0
- data/lib/openhab/core/items/string_item.rb +28 -0
- data/lib/openhab/core/items/switch_item.rb +78 -0
- data/lib/openhab/core/items.rb +114 -0
- data/lib/openhab/core/lazy_array.rb +52 -0
- data/lib/openhab/core/profile_factory.rb +118 -0
- data/lib/openhab/core/script_handling.rb +55 -0
- data/lib/openhab/core/things/channel.rb +48 -0
- data/lib/openhab/core/things/channel_uid.rb +51 -0
- data/lib/openhab/core/things/item_channel_link.rb +33 -0
- data/lib/openhab/core/things/profile_callback.rb +52 -0
- data/lib/openhab/core/things/proxy.rb +69 -0
- data/lib/openhab/core/things/registry.rb +46 -0
- data/lib/openhab/core/things/thing.rb +194 -0
- data/lib/openhab/core/things.rb +22 -0
- data/lib/openhab/core/timer.rb +128 -0
- data/lib/openhab/core/types/comparable_type.rb +23 -0
- data/lib/openhab/core/types/date_time_type.rb +259 -0
- data/lib/openhab/core/types/decimal_type.rb +192 -0
- data/lib/openhab/core/types/hsb_type.rb +183 -0
- data/lib/openhab/core/types/increase_decrease_type.rb +34 -0
- data/lib/openhab/core/types/next_previous_type.rb +34 -0
- data/lib/openhab/core/types/numeric_type.rb +52 -0
- data/lib/openhab/core/types/on_off_type.rb +46 -0
- data/lib/openhab/core/types/open_closed_type.rb +41 -0
- data/lib/openhab/core/types/percent_type.rb +95 -0
- data/lib/openhab/core/types/play_pause_type.rb +38 -0
- data/lib/openhab/core/types/point_type.rb +117 -0
- data/lib/openhab/core/types/quantity_type.rb +327 -0
- data/lib/openhab/core/types/raw_type.rb +26 -0
- data/lib/openhab/core/types/refresh_type.rb +27 -0
- data/lib/openhab/core/types/rewind_fastforward_type.rb +38 -0
- data/lib/openhab/core/types/stop_move_type.rb +34 -0
- data/lib/openhab/core/types/string_type.rb +76 -0
- data/lib/openhab/core/types/type.rb +117 -0
- data/lib/openhab/core/types/un_def_type.rb +38 -0
- data/lib/openhab/core/types/up_down_type.rb +50 -0
- data/lib/openhab/core/types.rb +69 -0
- data/lib/openhab/core/uid.rb +36 -0
- data/lib/openhab/core.rb +85 -0
- data/lib/openhab/core_ext/java/duration.rb +115 -0
- data/lib/openhab/core_ext/java/local_date.rb +93 -0
- data/lib/openhab/core_ext/java/local_time.rb +106 -0
- data/lib/openhab/core_ext/java/month.rb +59 -0
- data/lib/openhab/core_ext/java/month_day.rb +105 -0
- data/lib/openhab/core_ext/java/period.rb +103 -0
- data/lib/openhab/core_ext/java/temporal_amount.rb +34 -0
- data/lib/openhab/core_ext/java/time.rb +58 -0
- data/lib/openhab/core_ext/java/unit.rb +15 -0
- data/lib/openhab/core_ext/java/zoned_date_time.rb +116 -0
- data/lib/openhab/core_ext/ruby/array.rb +21 -0
- data/lib/openhab/core_ext/ruby/class.rb +15 -0
- data/lib/openhab/core_ext/ruby/date.rb +89 -0
- data/lib/openhab/core_ext/ruby/numeric.rb +190 -0
- data/lib/openhab/core_ext/ruby/range.rb +70 -0
- data/lib/openhab/core_ext/ruby/time.rb +104 -0
- data/lib/openhab/core_ext.rb +18 -0
- data/lib/openhab/dsl/events/watch_event.rb +18 -0
- data/lib/openhab/dsl/events.rb +9 -0
- data/lib/openhab/dsl/gems.rb +3 -0
- data/lib/openhab/dsl/items/builder.rb +618 -0
- data/lib/openhab/dsl/items/ensure.rb +93 -0
- data/lib/openhab/dsl/items/timed_command.rb +236 -0
- data/lib/openhab/dsl/rules/automation_rule.rb +308 -0
- data/lib/openhab/dsl/rules/builder.rb +1373 -0
- data/lib/openhab/dsl/rules/guard.rb +115 -0
- data/lib/openhab/dsl/rules/name_inference.rb +160 -0
- data/lib/openhab/dsl/rules/property.rb +76 -0
- data/lib/openhab/dsl/rules/rule_triggers.rb +96 -0
- data/lib/openhab/dsl/rules/terse.rb +63 -0
- data/lib/openhab/dsl/rules/triggers/changed.rb +169 -0
- data/lib/openhab/dsl/rules/triggers/channel.rb +57 -0
- data/lib/openhab/dsl/rules/triggers/command.rb +107 -0
- data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +161 -0
- data/lib/openhab/dsl/rules/triggers/conditions/proc.rb +164 -0
- data/lib/openhab/dsl/rules/triggers/cron/cron.rb +195 -0
- data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +127 -0
- data/lib/openhab/dsl/rules/triggers/trigger.rb +56 -0
- data/lib/openhab/dsl/rules/triggers/updated.rb +130 -0
- data/lib/openhab/dsl/rules/triggers/watch/watch.rb +55 -0
- data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +155 -0
- data/lib/openhab/dsl/rules/triggers.rb +12 -0
- data/lib/openhab/dsl/rules.rb +29 -0
- data/lib/openhab/dsl/script_handling.rb +55 -0
- data/lib/openhab/dsl/things/builder.rb +263 -0
- data/lib/openhab/dsl/thread_local.rb +48 -0
- data/lib/openhab/dsl/timer_manager.rb +191 -0
- data/lib/openhab/dsl/version.rb +9 -0
- data/lib/openhab/dsl.rb +686 -0
- data/lib/openhab/log.rb +348 -0
- data/lib/openhab/osgi.rb +70 -0
- data/lib/openhab/rspec/configuration.rb +56 -0
- data/lib/openhab/rspec/example_group.rb +90 -0
- data/lib/openhab/rspec/helpers.rb +439 -0
- data/lib/openhab/rspec/hooks.rb +93 -0
- data/lib/openhab/rspec/jruby.rb +46 -0
- data/lib/openhab/rspec/karaf.rb +811 -0
- data/lib/openhab/rspec/mocks/bundle_install_support.rb +25 -0
- data/lib/openhab/rspec/mocks/bundle_resolver.rb +30 -0
- data/lib/openhab/rspec/mocks/event_admin.rb +146 -0
- data/lib/openhab/rspec/mocks/metadata_provider.rb +75 -0
- data/lib/openhab/rspec/mocks/persistence_service.rb +140 -0
- data/lib/openhab/rspec/mocks/safe_caller.rb +40 -0
- data/lib/openhab/rspec/mocks/synchronous_executor.rb +56 -0
- data/lib/openhab/rspec/mocks/thing_handler.rb +76 -0
- data/lib/openhab/rspec/mocks/timer.rb +95 -0
- data/lib/openhab/rspec/openhab/core/actions.rb +26 -0
- data/lib/openhab/rspec/openhab/core/items/proxy.rb +27 -0
- data/lib/openhab/rspec/openhab/core/things/proxy.rb +27 -0
- data/lib/openhab/rspec/shell.rb +31 -0
- data/lib/openhab/rspec/suspend_rules.rb +60 -0
- data/lib/openhab/rspec.rb +17 -0
- data/lib/openhab/yard/cli/stats.rb +23 -0
- data/lib/openhab/yard/code_objects/group_object.rb +17 -0
- data/lib/openhab/yard/code_objects/java/base.rb +31 -0
- data/lib/openhab/yard/code_objects/java/class_object.rb +11 -0
- data/lib/openhab/yard/code_objects/java/field_object.rb +15 -0
- data/lib/openhab/yard/code_objects/java/interface_object.rb +15 -0
- data/lib/openhab/yard/code_objects/java/package_object.rb +11 -0
- data/lib/openhab/yard/code_objects/java/proxy.rb +23 -0
- data/lib/openhab/yard/handlers/jruby/base.rb +49 -0
- data/lib/openhab/yard/handlers/jruby/class_handler.rb +18 -0
- data/lib/openhab/yard/handlers/jruby/constant_handler.rb +18 -0
- data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +27 -0
- data/lib/openhab/yard/handlers/jruby/mixin_handler.rb +23 -0
- data/lib/openhab/yard/html_helper.rb +44 -0
- data/lib/openhab/yard/tags/constant_directive.rb +20 -0
- data/lib/openhab/yard/tags/group_directive.rb +24 -0
- data/lib/openhab/yard/tags/library.rb +3 -0
- data/lib/openhab/yard.rb +32 -0
- metadata +504 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenHAB
|
|
4
|
+
module DSL
|
|
5
|
+
module Items
|
|
6
|
+
# Extensions for {GenericItem} to implement timed commands
|
|
7
|
+
#
|
|
8
|
+
# All items have an implicit timer associated with them, enabling to
|
|
9
|
+
# easily set an item into a specific state for a specified duration and
|
|
10
|
+
# then at the expiration of that duration have the item automatically
|
|
11
|
+
# change to another state. These timed commands are reentrant, meaning
|
|
12
|
+
# if the same timed command is triggered while an outstanding timed
|
|
13
|
+
# command exist, that timed command will be rescheduled rather than
|
|
14
|
+
# creating a distinct timed command.
|
|
15
|
+
#
|
|
16
|
+
# Timed commands are initiated by using the 'for:' argument with the
|
|
17
|
+
# command. This is available on both the 'command' method and any
|
|
18
|
+
# command-specific methods, e.g. {SwitchItem#on}.
|
|
19
|
+
#
|
|
20
|
+
# Any update to the timed command state will result in the timer being
|
|
21
|
+
# cancelled. For example, if you have a Switch on a timer and another
|
|
22
|
+
# rule sends OFF or ON to that item the timer will be automatically
|
|
23
|
+
# canceled. Sending a different duration (for:) value for the timed
|
|
24
|
+
# command will reschedule the timed command for that new duration.
|
|
25
|
+
#
|
|
26
|
+
module TimedCommand
|
|
27
|
+
#
|
|
28
|
+
# Provides information about why the expiration block of a
|
|
29
|
+
# {TimedCommand#command timed command} is being called.
|
|
30
|
+
#
|
|
31
|
+
# @attr [GenericItem] item
|
|
32
|
+
# @!visibility private
|
|
33
|
+
# @attr [Types::Type, Proc] on_expire
|
|
34
|
+
# @!visibility private
|
|
35
|
+
# @attr [Core::Timer] timer
|
|
36
|
+
# @!visibility private
|
|
37
|
+
# @attr [Symbol] resolution
|
|
38
|
+
# @!visibility private
|
|
39
|
+
# @attr [String] rule_uid
|
|
40
|
+
# @!visibility private
|
|
41
|
+
# @attr [Mutex] mutex
|
|
42
|
+
# @!visibility private
|
|
43
|
+
#
|
|
44
|
+
TimedCommandDetails = Struct.new(:item,
|
|
45
|
+
:on_expire,
|
|
46
|
+
:timer,
|
|
47
|
+
:resolution,
|
|
48
|
+
:rule_uid,
|
|
49
|
+
:mutex,
|
|
50
|
+
keyword_init: true) do
|
|
51
|
+
# @return [true, false]
|
|
52
|
+
def expired?
|
|
53
|
+
resolution == :expired
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [true, false]
|
|
57
|
+
def cancelled?
|
|
58
|
+
resolution == :cancelled
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
@timed_commands = java.util.concurrent.ConcurrentHashMap.new
|
|
63
|
+
|
|
64
|
+
class << self
|
|
65
|
+
# @!visibility private
|
|
66
|
+
attr_reader :timed_commands
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
Core::Items::GenericItem.prepend(self)
|
|
70
|
+
|
|
71
|
+
#
|
|
72
|
+
# Sends command to an item for specified duration, then on timer expiration sends
|
|
73
|
+
# the expiration command to the item
|
|
74
|
+
#
|
|
75
|
+
# @note If a block is provided, and the timer is canceled because the
|
|
76
|
+
# item changed state while it was waiting, the block will still be
|
|
77
|
+
# executed. Be sure to check {TimedCommandDetails#expired? #expired?}
|
|
78
|
+
# and/or {TimedCommandDetails#cancelled? #cancelled?} to determine why
|
|
79
|
+
# the block was called.
|
|
80
|
+
#
|
|
81
|
+
# @param [Command] command to send to object
|
|
82
|
+
# @param [Duration] for duration for item to be in command state
|
|
83
|
+
# @param [Command] on_expire Command to send when duration expires
|
|
84
|
+
# @yield If a block is provided, `on_expire` is ignored and the block
|
|
85
|
+
# is expected to set the item to the desired state or carry out some
|
|
86
|
+
# other action.
|
|
87
|
+
# @yieldparam [TimedCommandDetails] timed_command
|
|
88
|
+
# @return [self]
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# Switch.command(ON, for: 5.minutes)
|
|
92
|
+
# @example
|
|
93
|
+
# Switch.on for: 5.minutes
|
|
94
|
+
# @example
|
|
95
|
+
# Dimmer.on for: 5.minutes, on_expire: 50
|
|
96
|
+
# @example
|
|
97
|
+
# Dimmer.on(for: 5.minutes) { |event| Dimmer.off if Light.on? }
|
|
98
|
+
#
|
|
99
|
+
def command(command, for: nil, on_expire: nil, &block)
|
|
100
|
+
duration = binding.local_variable_get(:for)
|
|
101
|
+
return super(command) unless duration
|
|
102
|
+
|
|
103
|
+
on_expire = block if block
|
|
104
|
+
|
|
105
|
+
TimedCommand.timed_commands.compute(self) do |_key, timed_command_details|
|
|
106
|
+
if timed_command_details.nil?
|
|
107
|
+
# no prior timed command
|
|
108
|
+
on_expire ||= default_on_expire(command)
|
|
109
|
+
super(command)
|
|
110
|
+
create_timed_command(duration: duration, on_expire: on_expire)
|
|
111
|
+
else
|
|
112
|
+
timed_command_details.mutex.synchronize do
|
|
113
|
+
if timed_command_details.resolution
|
|
114
|
+
# timed command that finished, but hadn't removed itself from the map yet
|
|
115
|
+
# (it doesn't do so under the mutex to prevent a deadlock).
|
|
116
|
+
# just create a new one
|
|
117
|
+
on_expire ||= default_on_expire(command)
|
|
118
|
+
super(command)
|
|
119
|
+
create_timed_command(duration: duration, on_expire: on_expire)
|
|
120
|
+
else
|
|
121
|
+
# timed command still pending; reset it
|
|
122
|
+
logger.trace "Outstanding Timed Command #{timed_command_details} encountered - rescheduling"
|
|
123
|
+
timed_command_details.on_expire = on_expire unless on_expire.nil?
|
|
124
|
+
timed_command_details.timer.reschedule(duration)
|
|
125
|
+
# disable the cancel rule while we send the new command
|
|
126
|
+
Core.rule_manager.set_enabled(timed_command_details.rule_uid, false)
|
|
127
|
+
super(command)
|
|
128
|
+
Core.rule_manager.set_enabled(timed_command_details.rule_uid, true)
|
|
129
|
+
timed_command_details
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
self
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
|
|
140
|
+
# Creates a new timed command and places it in the TimedCommand hash
|
|
141
|
+
def create_timed_command(duration:, on_expire:)
|
|
142
|
+
timed_command_details = TimedCommandDetails.new(item: self,
|
|
143
|
+
on_expire: on_expire,
|
|
144
|
+
mutex: Mutex.new)
|
|
145
|
+
|
|
146
|
+
timed_command_details.timer = timed_command_timer(timed_command_details, duration)
|
|
147
|
+
cancel_rule = TimedCommandCancelRule.new(timed_command_details)
|
|
148
|
+
timed_command_details.rule_uid = Core.automation_manager
|
|
149
|
+
.add_rule(cancel_rule)
|
|
150
|
+
.uid
|
|
151
|
+
Rules.script_rules[timed_command_details.rule_uid] = cancel_rule
|
|
152
|
+
logger.trace "Created Timed Command #{timed_command_details}"
|
|
153
|
+
timed_command_details
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Creates the timer to handle changing the item state when timer expires or invoking user supplied block
|
|
157
|
+
# @param [TimedCommandDetailes] timed_command_details details about the timed command
|
|
158
|
+
# @return [Timer] Timer
|
|
159
|
+
def timed_command_timer(timed_command_details, duration)
|
|
160
|
+
DSL.after(duration) do
|
|
161
|
+
timed_command_details.mutex.synchronize do
|
|
162
|
+
logger.trace "Timed command expired - #{timed_command_details}"
|
|
163
|
+
DSL.remove_rule(timed_command_details.rule_uid)
|
|
164
|
+
timed_command_details.resolution = :expired
|
|
165
|
+
case timed_command_details.on_expire
|
|
166
|
+
when Proc
|
|
167
|
+
logger.trace "Invoking block #{timed_command_details.on_expire} after timed command for #{name} expired"
|
|
168
|
+
timed_command_details.on_expire.call(timed_command_details)
|
|
169
|
+
when Core::Types::UnDefType
|
|
170
|
+
update(timed_command_details.on_expire)
|
|
171
|
+
else
|
|
172
|
+
command(timed_command_details.on_expire)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
TimedCommand.timed_commands.delete(timed_command_details.item)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
#
|
|
180
|
+
# The default expire for ON/OFF is their inverse
|
|
181
|
+
#
|
|
182
|
+
def default_on_expire(command)
|
|
183
|
+
return !command if command.is_a?(Core::Types::OnOffType)
|
|
184
|
+
|
|
185
|
+
raw_state
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
#
|
|
189
|
+
# Rule to cancel timed commands
|
|
190
|
+
#
|
|
191
|
+
# @!visibility private
|
|
192
|
+
class TimedCommandCancelRule < org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule
|
|
193
|
+
def initialize(timed_command_details)
|
|
194
|
+
super()
|
|
195
|
+
@timed_command_details = timed_command_details
|
|
196
|
+
# Capture rule name if known
|
|
197
|
+
@thread_locals = ThreadLocal.persist
|
|
198
|
+
self.name = "Cancel implicit timer for #{timed_command_details.item.name}"
|
|
199
|
+
self.triggers = [Rules::RuleTriggers.trigger(
|
|
200
|
+
type: Rules::Triggers::Changed::ITEM_STATE_CHANGE,
|
|
201
|
+
config: { "itemName" => timed_command_details.item.name }
|
|
202
|
+
)]
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Cleanup the rule; nothing to do here.
|
|
206
|
+
def cleanup; end
|
|
207
|
+
|
|
208
|
+
#
|
|
209
|
+
# Execute the rule
|
|
210
|
+
#
|
|
211
|
+
# @param [Map] _mod map provided by OpenHAB rules engine
|
|
212
|
+
# @param [Map] inputs map provided by OpenHAB rules engine containing event and other information
|
|
213
|
+
#
|
|
214
|
+
def execute(_mod = nil, inputs = nil)
|
|
215
|
+
ThreadLocal.thread_local(**@thread_locals) do
|
|
216
|
+
@timed_command_details.mutex.synchronize do
|
|
217
|
+
logger.trace "Canceling implicit timer #{@timed_command_details.timer} for "\
|
|
218
|
+
"#{@timed_command_details.item.name} because received event #{inputs}"
|
|
219
|
+
@timed_command_details.timer.cancel
|
|
220
|
+
DSL.remove_rule(@timed_command_details.rule_uid)
|
|
221
|
+
@timed_command_details.resolution = :cancelled
|
|
222
|
+
if @timed_command_details.on_expire.is_a?(Proc)
|
|
223
|
+
logger.trace "Executing user supplied block on timed command cancelation"
|
|
224
|
+
@timed_command_details.on_expire.call(@timed_command_details)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
TimedCommand.timed_commands.delete(@timed_command_details.item)
|
|
228
|
+
rescue Exception => e
|
|
229
|
+
logger.log_exception(e)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenHAB
|
|
4
|
+
module DSL
|
|
5
|
+
module Rules
|
|
6
|
+
#
|
|
7
|
+
# OpenHAB rules engine object
|
|
8
|
+
#
|
|
9
|
+
# @!visibility private
|
|
10
|
+
class AutomationRule < org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule
|
|
11
|
+
field_writer :uid
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
# Create a new Rule
|
|
15
|
+
#
|
|
16
|
+
# @param [Config] config Rule configuration
|
|
17
|
+
#
|
|
18
|
+
# Constructor sets a number of variables, no further decomposition necessary
|
|
19
|
+
def initialize(config:)
|
|
20
|
+
# Metrics disabled because only setters are called or defaults set.
|
|
21
|
+
super()
|
|
22
|
+
set_name(config.name)
|
|
23
|
+
set_description(config.description)
|
|
24
|
+
set_tags(to_string_set(config.tags))
|
|
25
|
+
set_triggers(config.triggers)
|
|
26
|
+
self.uid = config.uid
|
|
27
|
+
@run_context = config.caller
|
|
28
|
+
@run_queue = config.run
|
|
29
|
+
@guard = config.guard
|
|
30
|
+
@between = config.between && DSL.between(config.between)
|
|
31
|
+
@trigger_conditions = config.trigger_conditions
|
|
32
|
+
@attachments = config.attachments
|
|
33
|
+
@thread_locals = ThreadLocal.persist
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Execute the rule
|
|
38
|
+
#
|
|
39
|
+
# @param [Map] mod map provided by OpenHAB rules engine
|
|
40
|
+
# @param [Map] inputs map provided by OpenHAB rules engine containing event and other information
|
|
41
|
+
#
|
|
42
|
+
#
|
|
43
|
+
def execute(mod = nil, inputs = nil)
|
|
44
|
+
ThreadLocal.thread_local(**@thread_locals) do
|
|
45
|
+
logger.trace { "Execute called with mod (#{mod&.to_string}) and inputs (#{inputs.inspect})" }
|
|
46
|
+
logger.trace { "Event details #{inputs["event"].inspect}" } if inputs&.key?("event")
|
|
47
|
+
trigger_conditions(inputs).process(mod: mod, inputs: inputs) do
|
|
48
|
+
process_queue(create_queue(inputs), mod, inputs)
|
|
49
|
+
end
|
|
50
|
+
rescue Exception => e
|
|
51
|
+
@run_context.send(:logger).log_exception(e)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
#
|
|
56
|
+
# Cleanup any resources associated with automation rule
|
|
57
|
+
#
|
|
58
|
+
def cleanup
|
|
59
|
+
@trigger_conditions.each_value(&:cleanup)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
# Create the run queue based on guards
|
|
66
|
+
#
|
|
67
|
+
# @param [Map] inputs rule inputs
|
|
68
|
+
# @return [Queue] <description>
|
|
69
|
+
#
|
|
70
|
+
def create_queue(inputs)
|
|
71
|
+
case check_guards(event: extract_event(inputs))
|
|
72
|
+
when true
|
|
73
|
+
@run_queue.dup.grep_v(Builder::Otherwise)
|
|
74
|
+
when false
|
|
75
|
+
@run_queue.dup.grep(Builder::Otherwise)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
#
|
|
80
|
+
# Extract the event object from inputs
|
|
81
|
+
# and merge other inputs keys/values into the event
|
|
82
|
+
#
|
|
83
|
+
# @param [Map] inputs rule inputs
|
|
84
|
+
#
|
|
85
|
+
# @return [Object] event object
|
|
86
|
+
#
|
|
87
|
+
def extract_event(inputs)
|
|
88
|
+
event = inputs&.dig("event")
|
|
89
|
+
unless event
|
|
90
|
+
event = Struct.new(:event, :attachment, :command).new
|
|
91
|
+
event.command = inputs&.dig("command")
|
|
92
|
+
end
|
|
93
|
+
add_attachment(event, inputs)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
#
|
|
97
|
+
# Get the trigger_id for the trigger that caused the rule creation
|
|
98
|
+
#
|
|
99
|
+
# @return [Hash] Input hash potentially containing trigger id
|
|
100
|
+
#
|
|
101
|
+
def trigger_id(inputs)
|
|
102
|
+
inputs&.dig("module")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#
|
|
106
|
+
# Returns trigger conditions from inputs if it exists
|
|
107
|
+
#
|
|
108
|
+
# @param [Map] inputs map from OpenHAB containing UID
|
|
109
|
+
#
|
|
110
|
+
# @return [Array] Array of trigger conditions that match rule UID
|
|
111
|
+
#
|
|
112
|
+
def trigger_conditions(inputs)
|
|
113
|
+
# Parse this to get the trigger UID:
|
|
114
|
+
# ["72698819-83cb-498a-8e61-5aab8b812623.event", "oldState", "module", \
|
|
115
|
+
# "72698819-83cb-498a-8e61-5aab8b812623.oldState", "event", "newState",\
|
|
116
|
+
# "72698819-83cb-498a-8e61-5aab8b812623.newState"]
|
|
117
|
+
@trigger_conditions[trigger_id(inputs)]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# If an attachment exists for the trigger for this event add it to the event object
|
|
121
|
+
# @param [Object] event Event
|
|
122
|
+
# @param [Hash] inputs Inputs into event
|
|
123
|
+
# @return [Object] Event with attachment added
|
|
124
|
+
#
|
|
125
|
+
def add_attachment(event, inputs)
|
|
126
|
+
attachment = @attachments[trigger_id(inputs)]
|
|
127
|
+
return event unless attachment
|
|
128
|
+
|
|
129
|
+
event.attachment = attachment
|
|
130
|
+
event
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
#
|
|
134
|
+
# Check if any guards prevent execution
|
|
135
|
+
#
|
|
136
|
+
# @param [Map] event OpenHAB rule trigger event
|
|
137
|
+
#
|
|
138
|
+
# @return [true,false] True if guards says rule should execute, false otherwise
|
|
139
|
+
#
|
|
140
|
+
# Loggging inflates method length
|
|
141
|
+
def check_guards(event:)
|
|
142
|
+
return true if @guard.nil?
|
|
143
|
+
|
|
144
|
+
if @guard.should_run? event
|
|
145
|
+
return true if @between.nil?
|
|
146
|
+
|
|
147
|
+
now = Time.now
|
|
148
|
+
return true if @between.cover? now
|
|
149
|
+
|
|
150
|
+
logger.trace("Skipped execution of rule '#{name}' because the current time #{now} "\
|
|
151
|
+
"is not between #{@between.begin} and #{@between.end}")
|
|
152
|
+
else
|
|
153
|
+
logger.trace("Skipped execution of rule '#{name}' because of guard #{@guard}")
|
|
154
|
+
end
|
|
155
|
+
false
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
#
|
|
159
|
+
# Process the run queue
|
|
160
|
+
#
|
|
161
|
+
# @param [Array] run_queue array of procs of various types to execute
|
|
162
|
+
# @param [Map] mod OpenHAB map object describing rule trigger
|
|
163
|
+
# @param [Map] inputs OpenHAB map object describing rule trigge
|
|
164
|
+
#
|
|
165
|
+
def process_queue(run_queue, mod, inputs)
|
|
166
|
+
event = extract_event(inputs)
|
|
167
|
+
|
|
168
|
+
while (task = run_queue.shift)
|
|
169
|
+
if task.is_a?(Builder::Delay)
|
|
170
|
+
process_delay_task(inputs, mod, run_queue, task)
|
|
171
|
+
else
|
|
172
|
+
process_task(event, task)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
#
|
|
178
|
+
# Dispatch execution block tasks to different methods
|
|
179
|
+
#
|
|
180
|
+
# @param [OpenHab Event] event that triggered the rule
|
|
181
|
+
# @param [Task] task task containing otherwise block to execute
|
|
182
|
+
#
|
|
183
|
+
def process_task(event, task)
|
|
184
|
+
ThreadLocal.thread_local(**@thread_locals) do
|
|
185
|
+
case task
|
|
186
|
+
when Builder::Run then process_run_task(event, task)
|
|
187
|
+
when Builder::Script then process_script_task(task)
|
|
188
|
+
when Builder::Trigger then process_trigger_task(event, task)
|
|
189
|
+
when Builder::Otherwise then process_otherwise_task(event, task)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
#
|
|
195
|
+
# Process an otherwise block
|
|
196
|
+
#
|
|
197
|
+
# @param [OpenHab Event] event that triggered the rule
|
|
198
|
+
# @param [Task] task task containing otherwise block to execute
|
|
199
|
+
#
|
|
200
|
+
#
|
|
201
|
+
def process_otherwise_task(event, task)
|
|
202
|
+
logger.trace { "Executing rule '#{name}' otherwise block with event(#{event})" }
|
|
203
|
+
@run_context.instance_exec(event, &task.block)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
#
|
|
207
|
+
# Process delay task
|
|
208
|
+
#
|
|
209
|
+
# @param [Map] inputs Rule trigger inputs
|
|
210
|
+
# @param [Map] mod Rule modes
|
|
211
|
+
# @param [Queue] run_queue Queue of tasks for this rule
|
|
212
|
+
# @param [Delay] task to process
|
|
213
|
+
#
|
|
214
|
+
#
|
|
215
|
+
def process_delay_task(inputs, mod, run_queue, task)
|
|
216
|
+
remaining_queue = run_queue.slice!(0, run_queue.length)
|
|
217
|
+
DSL.after(task.duration) { process_queue(remaining_queue, mod, inputs) }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
#
|
|
221
|
+
# Process a task that is caused by a group item
|
|
222
|
+
#
|
|
223
|
+
# @param [Map] event Rule event map
|
|
224
|
+
# @param [Trigger] task to execute
|
|
225
|
+
#
|
|
226
|
+
#
|
|
227
|
+
def process_trigger_task(event, task)
|
|
228
|
+
return unless event&.item
|
|
229
|
+
|
|
230
|
+
logger.trace { "Executing rule '#{name}' trigger block with item (#{event.item})" }
|
|
231
|
+
@run_context.instance_exec(event.item, &task.block)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
#
|
|
235
|
+
# Process a run task
|
|
236
|
+
#
|
|
237
|
+
# @param [OpenHab Event] event information
|
|
238
|
+
# @param [Run] task to execute
|
|
239
|
+
#
|
|
240
|
+
#
|
|
241
|
+
def process_run_task(event, task)
|
|
242
|
+
logger.trace { "Executing rule '#{name}' run block with event(#{event})" }
|
|
243
|
+
@run_context.instance_exec(event, &task.block)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
#
|
|
247
|
+
# Process a script task
|
|
248
|
+
#
|
|
249
|
+
# @param [Script] task to execute
|
|
250
|
+
#
|
|
251
|
+
def process_script_task(task)
|
|
252
|
+
logger.trace { "Executing script '#{name}' run block" }
|
|
253
|
+
@run_context.instance_exec(&task.block)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
#
|
|
257
|
+
# Convert the given array to a set of strings.
|
|
258
|
+
# Convert Semantics classes to their simple name
|
|
259
|
+
#
|
|
260
|
+
# @example
|
|
261
|
+
# to_string_set("tag1", Semantics::LivingRoom)
|
|
262
|
+
#
|
|
263
|
+
# @param tags [Array] An array of strings or Semantics classes
|
|
264
|
+
#
|
|
265
|
+
# @return [Set] A set of strings
|
|
266
|
+
#
|
|
267
|
+
def to_string_set(*tags)
|
|
268
|
+
tags = tags.flatten.map do |tag|
|
|
269
|
+
if tag.respond_to?(:java_class) && tag < org.openhab.core.semantics.Tag
|
|
270
|
+
tag.java_class.simple_name
|
|
271
|
+
else
|
|
272
|
+
tag.to_s
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
Set.new(tags)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
#
|
|
279
|
+
# Create a new hash in which all elements are converted to strings
|
|
280
|
+
#
|
|
281
|
+
# @param [Map] hash in which all elements should be converted to strings
|
|
282
|
+
#
|
|
283
|
+
# @return [Map] new map with values converted to strings
|
|
284
|
+
#
|
|
285
|
+
def inspect_hash(hash)
|
|
286
|
+
hash.each_with_object({}) do |(key, value), new_hash|
|
|
287
|
+
new_hash[inspect_item(key)] = inspect_item(value)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
#
|
|
292
|
+
# Convert an individual element into a string based on if it a Ruby or Java object
|
|
293
|
+
#
|
|
294
|
+
# @param [Object] item to convert to a string
|
|
295
|
+
#
|
|
296
|
+
# @return [String] representation of item
|
|
297
|
+
#
|
|
298
|
+
def inspect_item(item)
|
|
299
|
+
if item.respond_to? :to_string
|
|
300
|
+
item.to_string
|
|
301
|
+
elsif item.respond_to? :to_str
|
|
302
|
+
item.to_str
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|