openhab-scripting 4.46.2 → 5.0.0
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 +4 -4
- data/lib/openhab/core/actions/audio.rb +47 -0
- data/lib/openhab/core/actions/ephemeris.rb +39 -0
- data/lib/openhab/core/actions/exec.rb +51 -0
- data/lib/openhab/core/actions/http.rb +80 -0
- data/lib/openhab/core/actions/ping.rb +30 -0
- data/lib/openhab/core/actions/transformation.rb +32 -0
- data/lib/openhab/core/actions/voice.rb +36 -0
- data/lib/openhab/core/actions.rb +82 -0
- data/lib/openhab/core/dependency_tracking.rb +34 -0
- data/lib/openhab/core/dto/item_channel_link.rb +33 -0
- data/lib/openhab/core/dto/thing.rb +27 -0
- data/lib/openhab/core/dto.rb +11 -0
- data/lib/openhab/core/entity_lookup.rb +152 -70
- data/lib/openhab/core/events/abstract_event.rb +18 -0
- data/lib/openhab/core/events/abstract_item_registry_event.rb +36 -0
- data/lib/openhab/core/events/abstract_thing_registry_event.rb +40 -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 +75 -0
- data/lib/openhab/core/events/item_state_event.rb +79 -0
- data/lib/openhab/core/events/thing_status_info_event.rb +55 -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 +59 -0
- data/lib/openhab/core/items/dimmer_item.rb +148 -0
- data/lib/openhab/core/items/generic_item.rb +292 -0
- data/lib/openhab/core/items/group_item.rb +176 -0
- data/lib/openhab/{dsl → core}/items/image_item.rb +35 -29
- data/lib/openhab/core/items/item.rb +273 -0
- data/lib/openhab/core/items/location_item.rb +34 -0
- data/lib/openhab/core/items/metadata/hash.rb +433 -0
- data/lib/openhab/core/items/metadata/namespace_hash.rb +475 -0
- data/lib/openhab/core/items/metadata/provider.rb +48 -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 +416 -0
- data/lib/openhab/core/items/player_item.rb +66 -0
- data/lib/openhab/core/items/provider.rb +44 -0
- data/lib/openhab/core/items/proxy.rb +136 -0
- data/lib/openhab/core/items/registry.rb +86 -0
- data/lib/openhab/core/items/rollershutter_item.rb +68 -0
- data/lib/openhab/core/items/semantics/enumerable.rb +177 -0
- data/lib/openhab/core/items/semantics.rb +473 -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 +108 -0
- data/lib/openhab/{dsl → core}/lazy_array.rb +9 -3
- data/lib/openhab/core/profile_factory.rb +132 -0
- data/lib/openhab/core/provider.rb +230 -0
- data/lib/openhab/core/proxy.rb +130 -0
- data/lib/openhab/core/registry.rb +40 -0
- data/lib/openhab/core/rules/module.rb +26 -0
- data/lib/openhab/core/rules/provider.rb +25 -0
- data/lib/openhab/core/rules/registry.rb +76 -0
- data/lib/openhab/core/rules/rule.rb +150 -0
- data/lib/openhab/core/rules.rb +25 -0
- data/lib/openhab/core/script_handling.rb +78 -20
- 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/links/provider.rb +78 -0
- data/lib/openhab/core/things/profile_callback.rb +52 -0
- data/lib/openhab/core/things/provider.rb +29 -0
- data/lib/openhab/core/things/proxy.rb +87 -0
- data/lib/openhab/core/things/registry.rb +73 -0
- data/lib/openhab/core/things/thing.rb +194 -0
- data/lib/openhab/core/things.rb +22 -0
- data/lib/openhab/core/timer.rb +148 -0
- data/lib/openhab/{dsl → core}/types/comparable_type.rb +5 -3
- data/lib/openhab/{dsl → core}/types/date_time_type.rb +55 -127
- data/lib/openhab/{dsl → core}/types/decimal_type.rb +50 -48
- data/lib/openhab/{dsl → core}/types/hsb_type.rb +35 -83
- 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/{dsl → core}/types/numeric_type.rb +20 -7
- 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/{dsl → core}/types/percent_type.rb +19 -20
- 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 +325 -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/{dsl → core}/types/string_type.rb +17 -28
- data/lib/openhab/{dsl → core}/types/type.rb +42 -40
- 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 +82 -0
- data/lib/openhab/{dsl → core}/uid.rb +4 -23
- data/lib/openhab/core/value_cache.rb +188 -0
- data/lib/openhab/core.rb +98 -0
- data/lib/openhab/core_ext/between.rb +32 -0
- data/lib/openhab/core_ext/ephemeris.rb +53 -0
- data/lib/openhab/core_ext/java/class.rb +34 -0
- data/lib/openhab/core_ext/java/duration.rb +142 -0
- data/lib/openhab/core_ext/java/list.rb +436 -0
- data/lib/openhab/core_ext/java/local_date.rb +104 -0
- data/lib/openhab/core_ext/java/local_time.rb +118 -0
- data/lib/openhab/core_ext/java/map.rb +66 -0
- data/lib/openhab/core_ext/java/month.rb +71 -0
- data/lib/openhab/core_ext/java/month_day.rb +119 -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 +62 -0
- data/lib/openhab/core_ext/java/unit.rb +15 -0
- data/lib/openhab/core_ext/java/zoned_date_time.rb +213 -0
- data/lib/openhab/core_ext/ruby/array.rb +21 -0
- data/lib/openhab/core_ext/ruby/date.rb +96 -0
- data/lib/openhab/core_ext/ruby/date_time.rb +55 -0
- data/lib/openhab/core_ext/ruby/module.rb +15 -0
- data/lib/openhab/core_ext/ruby/numeric.rb +195 -0
- data/lib/openhab/core_ext/ruby/range.rb +70 -0
- data/lib/openhab/core_ext/ruby/symbol.rb +7 -0
- data/lib/openhab/core_ext/ruby/time.rb +108 -0
- data/lib/openhab/core_ext.rb +18 -0
- data/lib/openhab/dsl/debouncer.rb +259 -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 +1 -1
- data/lib/openhab/dsl/items/builder.rb +578 -0
- data/lib/openhab/dsl/items/ensure.rb +73 -82
- data/lib/openhab/dsl/items/timed_command.rb +214 -159
- data/lib/openhab/dsl/rules/automation_rule.rb +126 -115
- data/lib/openhab/dsl/rules/builder.rb +1935 -0
- data/lib/openhab/dsl/rules/guard.rb +51 -114
- data/lib/openhab/dsl/rules/name_inference.rb +66 -25
- data/lib/openhab/dsl/rules/property.rb +48 -75
- data/lib/openhab/dsl/rules/rule_triggers.rb +22 -27
- data/lib/openhab/dsl/rules/terse.rb +58 -14
- data/lib/openhab/dsl/rules/triggers/changed.rb +48 -94
- data/lib/openhab/dsl/rules/triggers/channel.rb +9 -40
- data/lib/openhab/dsl/rules/triggers/command.rb +14 -63
- data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +34 -69
- data/lib/openhab/dsl/rules/triggers/conditions/proc.rb +6 -14
- data/lib/openhab/dsl/rules/triggers/cron/cron.rb +48 -82
- data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +30 -47
- data/lib/openhab/dsl/rules/triggers/trigger.rb +7 -28
- data/lib/openhab/dsl/rules/triggers/updated.rb +21 -45
- data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +257 -102
- data/lib/openhab/dsl/rules/triggers.rb +12 -0
- data/lib/openhab/dsl/rules.rb +8 -0
- data/lib/openhab/dsl/things/builder.rb +299 -0
- data/lib/openhab/{core → dsl}/thread_local.rb +27 -17
- data/lib/openhab/dsl/timer_manager.rb +204 -0
- data/lib/openhab/dsl/version.rb +9 -0
- data/lib/openhab/dsl.rb +979 -0
- data/lib/openhab/log.rb +355 -0
- data/lib/openhab/osgi.rb +68 -0
- data/lib/openhab/rspec/configuration.rb +56 -0
- data/lib/openhab/rspec/example_group.rb +132 -0
- data/lib/openhab/rspec/helpers.rb +458 -0
- data/lib/openhab/rspec/hooks.rb +113 -0
- data/lib/openhab/rspec/jruby.rb +46 -0
- data/lib/openhab/rspec/karaf.rb +851 -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/instance_method_stasher.rb +22 -0
- data/lib/openhab/rspec/mocks/persistence_service.rb +155 -0
- data/lib/openhab/rspec/mocks/safe_caller.rb +40 -0
- data/lib/openhab/rspec/mocks/space.rb +23 -0
- data/lib/openhab/rspec/mocks/synchronous_executor.rb +63 -0
- data/lib/openhab/rspec/mocks/thing_handler.rb +76 -0
- data/lib/openhab/rspec/mocks/timer.rb +134 -0
- data/lib/openhab/rspec/openhab/core/actions.rb +38 -0
- data/lib/openhab/rspec/openhab/core/items/proxy.rb +15 -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 +50 -0
- data/lib/openhab/rspec.rb +26 -0
- data/lib/openhab/yard/base_helper.rb +19 -0
- data/lib/openhab/yard/cli/stats.rb +23 -0
- data/lib/openhab/yard/code_objects/group_object.rb +23 -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/coderay.rb +17 -0
- data/lib/openhab/yard/handlers/jruby/base.rb +58 -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 +30 -0
- data/lib/openhab/yard/handlers/jruby/mixin_handler.rb +23 -0
- data/lib/openhab/yard/html_helper.rb +78 -0
- data/lib/openhab/yard/markdown_helper.rb +148 -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 +38 -0
- metadata +475 -106
- data/lib/openhab/core/item_proxy.rb +0 -29
- data/lib/openhab/core/load_path.rb +0 -19
- data/lib/openhab/core/openhab_setup.rb +0 -29
- data/lib/openhab/core/osgi.rb +0 -58
- data/lib/openhab/core/services.rb +0 -24
- data/lib/openhab/dsl/actions.rb +0 -114
- data/lib/openhab/dsl/between.rb +0 -25
- data/lib/openhab/dsl/channel.rb +0 -43
- data/lib/openhab/dsl/dsl.rb +0 -59
- data/lib/openhab/dsl/group.rb +0 -54
- data/lib/openhab/dsl/imports.rb +0 -21
- data/lib/openhab/dsl/items/color_item.rb +0 -76
- data/lib/openhab/dsl/items/comparable_item.rb +0 -62
- data/lib/openhab/dsl/items/contact_item.rb +0 -41
- data/lib/openhab/dsl/items/date_time_item.rb +0 -65
- data/lib/openhab/dsl/items/dimmer_item.rb +0 -65
- data/lib/openhab/dsl/items/generic_item.rb +0 -229
- data/lib/openhab/dsl/items/group_item.rb +0 -127
- data/lib/openhab/dsl/items/item_equality.rb +0 -59
- data/lib/openhab/dsl/items/item_registry.rb +0 -54
- data/lib/openhab/dsl/items/items.rb +0 -109
- data/lib/openhab/dsl/items/location_item.rb +0 -59
- data/lib/openhab/dsl/items/metadata.rb +0 -326
- data/lib/openhab/dsl/items/number_item.rb +0 -17
- data/lib/openhab/dsl/items/numeric_item.rb +0 -87
- data/lib/openhab/dsl/items/persistence.rb +0 -307
- data/lib/openhab/dsl/items/player_item.rb +0 -58
- data/lib/openhab/dsl/items/rollershutter_item.rb +0 -51
- data/lib/openhab/dsl/items/semantics/enumerable.rb +0 -91
- data/lib/openhab/dsl/items/semantics.rb +0 -227
- data/lib/openhab/dsl/items/string_item.rb +0 -51
- data/lib/openhab/dsl/items/switch_item.rb +0 -70
- data/lib/openhab/dsl/monkey_patch/actions/actions.rb +0 -4
- data/lib/openhab/dsl/monkey_patch/actions/script_thing_actions.rb +0 -39
- data/lib/openhab/dsl/monkey_patch/events/events.rb +0 -7
- data/lib/openhab/dsl/monkey_patch/events/item_command.rb +0 -85
- data/lib/openhab/dsl/monkey_patch/events/item_event.rb +0 -28
- data/lib/openhab/dsl/monkey_patch/events/item_state.rb +0 -61
- data/lib/openhab/dsl/monkey_patch/events/item_state_changed.rb +0 -60
- data/lib/openhab/dsl/monkey_patch/events/thing_status_info.rb +0 -33
- data/lib/openhab/dsl/monkey_patch/java/java.rb +0 -4
- data/lib/openhab/dsl/monkey_patch/java/local_time.rb +0 -44
- data/lib/openhab/dsl/monkey_patch/java/time_extensions.rb +0 -50
- data/lib/openhab/dsl/monkey_patch/java/zoned_date_time.rb +0 -45
- data/lib/openhab/dsl/monkey_patch/ruby/number.rb +0 -104
- data/lib/openhab/dsl/monkey_patch/ruby/ruby.rb +0 -6
- data/lib/openhab/dsl/monkey_patch/ruby/string.rb +0 -47
- data/lib/openhab/dsl/monkey_patch/ruby/time.rb +0 -61
- data/lib/openhab/dsl/openhab.rb +0 -30
- data/lib/openhab/dsl/persistence.rb +0 -27
- data/lib/openhab/dsl/rules/item_event.rb +0 -19
- data/lib/openhab/dsl/rules/rule.rb +0 -160
- data/lib/openhab/dsl/rules/rule_config.rb +0 -147
- data/lib/openhab/dsl/rules/triggers/generic.rb +0 -31
- data/lib/openhab/dsl/rules/triggers/triggers.rb +0 -11
- data/lib/openhab/dsl/rules/triggers/watch/watch.rb +0 -81
- data/lib/openhab/dsl/states.rb +0 -89
- data/lib/openhab/dsl/things.rb +0 -147
- data/lib/openhab/dsl/time/month_day.rb +0 -180
- data/lib/openhab/dsl/time/time_of_day.rb +0 -235
- data/lib/openhab/dsl/timers/manager.rb +0 -119
- data/lib/openhab/dsl/timers/reentrant_timer.rb +0 -38
- data/lib/openhab/dsl/timers/timer.rb +0 -132
- data/lib/openhab/dsl/timers.rb +0 -77
- data/lib/openhab/dsl/types/increase_decrease_type.rb +0 -23
- data/lib/openhab/dsl/types/next_previous_type.rb +0 -23
- data/lib/openhab/dsl/types/on_off_type.rb +0 -28
- data/lib/openhab/dsl/types/open_closed_type.rb +0 -29
- data/lib/openhab/dsl/types/play_pause_type.rb +0 -27
- data/lib/openhab/dsl/types/point_type.rb +0 -180
- data/lib/openhab/dsl/types/quantity_type.rb +0 -265
- data/lib/openhab/dsl/types/refresh_type.rb +0 -18
- data/lib/openhab/dsl/types/rewind_fastforward_type.rb +0 -33
- data/lib/openhab/dsl/types/stop_move_type.rb +0 -23
- data/lib/openhab/dsl/types/types.rb +0 -83
- data/lib/openhab/dsl/types/un_def_type.rb +0 -22
- data/lib/openhab/dsl/types/up_down_type.rb +0 -32
- data/lib/openhab/dsl/units.rb +0 -45
- data/lib/openhab/log/configuration.rb +0 -21
- data/lib/openhab/log/logger.rb +0 -282
- data/lib/openhab/version.rb +0 -9
- data/lib/openhab.rb +0 -36
|
@@ -0,0 +1,1935 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
5
|
+
require_relative "property"
|
|
6
|
+
require_relative "guard"
|
|
7
|
+
require_relative "rule_triggers"
|
|
8
|
+
require_relative "terse"
|
|
9
|
+
|
|
10
|
+
Dir[File.expand_path("triggers/*.rb", __dir__)].sort.each do |f|
|
|
11
|
+
require f
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module OpenHAB
|
|
15
|
+
module DSL
|
|
16
|
+
#
|
|
17
|
+
# Creates and manages openHAB Rules
|
|
18
|
+
#
|
|
19
|
+
module Rules
|
|
20
|
+
# A rules builder allows you to create openHAB rules.
|
|
21
|
+
#
|
|
22
|
+
# Note that all methods on this module are also availabe directly on {OpenHAB::DSL}.
|
|
23
|
+
#
|
|
24
|
+
class Builder
|
|
25
|
+
include Terse
|
|
26
|
+
include Core::EntityLookup
|
|
27
|
+
|
|
28
|
+
self.create_dummy_items = true
|
|
29
|
+
|
|
30
|
+
# @return [org.openhab.core.automation.RuleProvider]
|
|
31
|
+
attr_reader :provider
|
|
32
|
+
|
|
33
|
+
def initialize(provider)
|
|
34
|
+
@provider = Core::Rules::Provider.current(provider)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#
|
|
38
|
+
# Create a new rule
|
|
39
|
+
#
|
|
40
|
+
# The rule must have at least one trigger and one execution block.
|
|
41
|
+
# To create a "script" without any triggers, use {#script}.
|
|
42
|
+
#
|
|
43
|
+
# @param [String] name The rule name
|
|
44
|
+
# @yield Block executed in the context of a {Rules::BuilderDSL}
|
|
45
|
+
# @yieldparam [Rules::BuilderDSL] rule
|
|
46
|
+
# Optional parameter to access the rule configuration from within execution blocks and guards.
|
|
47
|
+
# @return [Core::Rules::Rule, nil] The rule object, or nil if no rule was created.
|
|
48
|
+
#
|
|
49
|
+
# @see OpenHAB::DSL::Rules::BuilderDSL Rule BuilderDSL for details on rule triggers, guards and execution blocks
|
|
50
|
+
# @see Rules::Terse Terse Rules
|
|
51
|
+
#
|
|
52
|
+
# @example
|
|
53
|
+
# require "openhab/dsl"
|
|
54
|
+
#
|
|
55
|
+
# rule "name" do
|
|
56
|
+
# <one or more triggers>
|
|
57
|
+
# <one or more execution blocks>
|
|
58
|
+
# <zero or more guards>
|
|
59
|
+
# end
|
|
60
|
+
#
|
|
61
|
+
def rule(name = nil, id: nil, script: nil, binding: nil, &block)
|
|
62
|
+
raise ArgumentError, "Block is required" unless block
|
|
63
|
+
|
|
64
|
+
id ||= NameInference.infer_rule_id_from_block(block)
|
|
65
|
+
script ||= block.source rescue nil # rubocop:disable Style/RescueModifier
|
|
66
|
+
|
|
67
|
+
builder = nil
|
|
68
|
+
|
|
69
|
+
ThreadLocal.thread_local(openhab_rule_type: "rule", openhab_rule_uid: id) do
|
|
70
|
+
builder = BuilderDSL.new(binding || block.binding)
|
|
71
|
+
builder.uid(id)
|
|
72
|
+
builder.instance_exec(builder, &block)
|
|
73
|
+
builder.guard = Guard.new(run_context: builder.caller, only_if: builder.only_if,
|
|
74
|
+
not_if: builder.not_if)
|
|
75
|
+
|
|
76
|
+
name ||= NameInference.infer_rule_name(builder)
|
|
77
|
+
name ||= id
|
|
78
|
+
|
|
79
|
+
builder.name(name)
|
|
80
|
+
logger.trace { builder.inspect }
|
|
81
|
+
builder.build(provider, script)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
# Create a new script
|
|
87
|
+
#
|
|
88
|
+
# A script is a rule with no triggers. It can be called by various other actions,
|
|
89
|
+
# such as the Run Rules action.
|
|
90
|
+
#
|
|
91
|
+
# @param [String] name A descriptive name
|
|
92
|
+
# @param [String] id The script's ID
|
|
93
|
+
# @yield [] Block executed when the script is executed.
|
|
94
|
+
# @return [Core::Rules::Rule]
|
|
95
|
+
#
|
|
96
|
+
def script(name = nil, id: nil, script: nil, &block)
|
|
97
|
+
raise ArgumentError, "Block is required" unless block
|
|
98
|
+
|
|
99
|
+
id ||= NameInference.infer_rule_id_from_block(block)
|
|
100
|
+
name ||= id
|
|
101
|
+
script ||= block.source rescue nil # rubocop:disable Style/RescueModifier
|
|
102
|
+
|
|
103
|
+
builder = nil
|
|
104
|
+
ThreadLocal.thread_local(openhab_rule_type: "script", openhab_rule_uid: id) do
|
|
105
|
+
builder = BuilderDSL.new(block.binding)
|
|
106
|
+
builder.uid(id)
|
|
107
|
+
builder.tags(["Script"])
|
|
108
|
+
builder.name(name)
|
|
109
|
+
builder.script(&block)
|
|
110
|
+
logger.trace { builder.inspect }
|
|
111
|
+
builder.build(provider, script)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
#
|
|
117
|
+
# Rule configuration for openHAB Rules engine
|
|
118
|
+
#
|
|
119
|
+
class BuilderDSL
|
|
120
|
+
include Core::EntityLookup
|
|
121
|
+
include DSL
|
|
122
|
+
prepend Triggers
|
|
123
|
+
extend Property
|
|
124
|
+
extend Forwardable
|
|
125
|
+
|
|
126
|
+
self.create_dummy_items = true
|
|
127
|
+
|
|
128
|
+
delegate %i[triggers trigger_conditions attachments] => :@rule_triggers
|
|
129
|
+
|
|
130
|
+
# @!visibility private
|
|
131
|
+
# @return [Array] Of trigger guards
|
|
132
|
+
attr_accessor :guard
|
|
133
|
+
|
|
134
|
+
# @!visibility private
|
|
135
|
+
# @return [Object] object that invoked rule method
|
|
136
|
+
attr_accessor :caller
|
|
137
|
+
|
|
138
|
+
# @!visibility private
|
|
139
|
+
# @return [Array] Of trigger definitions as passed in Ruby
|
|
140
|
+
attr_reader :ruby_triggers
|
|
141
|
+
|
|
142
|
+
# @!visibility private
|
|
143
|
+
attr_reader :debounce_settings
|
|
144
|
+
|
|
145
|
+
# @!visibility private
|
|
146
|
+
Run = Struct.new(:block)
|
|
147
|
+
|
|
148
|
+
# @!visibility private
|
|
149
|
+
Script = Struct.new(:block)
|
|
150
|
+
|
|
151
|
+
# @!visibility private
|
|
152
|
+
Trigger = Struct.new(:block)
|
|
153
|
+
|
|
154
|
+
# @!visibility private
|
|
155
|
+
Otherwise = Struct.new(:block)
|
|
156
|
+
|
|
157
|
+
# @!visibility private
|
|
158
|
+
Delay = Struct.new(:duration)
|
|
159
|
+
|
|
160
|
+
# @!group Execution Blocks
|
|
161
|
+
|
|
162
|
+
#
|
|
163
|
+
# @!method run
|
|
164
|
+
#
|
|
165
|
+
# Add a block that will be passed event data.
|
|
166
|
+
#
|
|
167
|
+
# The run property is the automation code that is executed when a rule
|
|
168
|
+
# is triggered. This property accepts a block of code and executes it.
|
|
169
|
+
# The block is automatically passed an event object which can be used
|
|
170
|
+
# to access multiple properties about the triggering event. The code
|
|
171
|
+
# for the automation can be entirely within the run block and can call
|
|
172
|
+
# methods defined in the Ruby script.
|
|
173
|
+
#
|
|
174
|
+
# @yieldparam [Core::Events::AbstractEvent] event
|
|
175
|
+
# @return [void]
|
|
176
|
+
#
|
|
177
|
+
# @example `{}` style used for single line blocks.
|
|
178
|
+
# rule 'Access Event Properties' do
|
|
179
|
+
# changed TestSwitch
|
|
180
|
+
# run { |event| logger.info("#{event.item.name} triggered from #{event.was} to #{event.state}") }
|
|
181
|
+
# end
|
|
182
|
+
#
|
|
183
|
+
# @example `do/end` style used for multi-line blocks.
|
|
184
|
+
# rule 'Multi Line Run Block' do
|
|
185
|
+
# changed TestSwitch
|
|
186
|
+
# run do |event|
|
|
187
|
+
# logger.info("#{event.item.name} triggered")
|
|
188
|
+
# logger.info("from #{event.was}") if event.was?
|
|
189
|
+
# logger.info("to #{event.state}") if event.state?
|
|
190
|
+
# end
|
|
191
|
+
# end
|
|
192
|
+
#
|
|
193
|
+
# @example Rules can have multiple run blocks and they are executed in order. Useful when used in combination with {#delay}.
|
|
194
|
+
# rule 'Multiple Run Blocks' do
|
|
195
|
+
# changed TestSwitch
|
|
196
|
+
# run { |event| logger.info("#{event.item.name} triggered") }
|
|
197
|
+
# run { |event| logger.info("from #{event.was}") if event.was? }
|
|
198
|
+
# run { |event| logger.info("to #{event.state}") if event.state? }
|
|
199
|
+
# end
|
|
200
|
+
#
|
|
201
|
+
prop_array :run, array_name: :run_queue, wrapper: Run
|
|
202
|
+
|
|
203
|
+
prop_array :script, array_name: :run_queue, wrapper: Script
|
|
204
|
+
|
|
205
|
+
#
|
|
206
|
+
# @!method triggered
|
|
207
|
+
#
|
|
208
|
+
# Add a block that will be passed the triggering item.
|
|
209
|
+
#
|
|
210
|
+
# This property is the same as the {#run} property except rather than
|
|
211
|
+
# passing an event object to the automation block the triggered item is
|
|
212
|
+
# passed. This enables optimizations for simple cases and supports
|
|
213
|
+
# Ruby's [pretzel colon `&:` operator.](https://medium.com/@dcjones/the-pretzel-colon-75df46dde0c7).
|
|
214
|
+
#
|
|
215
|
+
# @yieldparam [Item] item
|
|
216
|
+
# @return [void]
|
|
217
|
+
#
|
|
218
|
+
# @example
|
|
219
|
+
# rule "motion sensor triggered" do
|
|
220
|
+
# changed MotionSensor.members, to: :OPEN
|
|
221
|
+
# triggered do |item|
|
|
222
|
+
# logger.info("#{item.name} detected motion")
|
|
223
|
+
# end
|
|
224
|
+
# end
|
|
225
|
+
#
|
|
226
|
+
# @example
|
|
227
|
+
# rule 'Triggered has access directly to item triggered' do
|
|
228
|
+
# changed TestSwitch
|
|
229
|
+
# triggered { |item| logger.info("#{item.name} triggered") }
|
|
230
|
+
# end
|
|
231
|
+
#
|
|
232
|
+
# @example Triggered items are highly useful when working with groups
|
|
233
|
+
# # Switches is a group of Switch items
|
|
234
|
+
# rule 'Triggered item is item changed when a group item is changed.' do
|
|
235
|
+
# changed Switches.members
|
|
236
|
+
# triggered { |item| logger.info("Switch #{item.name} changed to #{item.state}")}
|
|
237
|
+
# end
|
|
238
|
+
#
|
|
239
|
+
# rule 'Turn off any switch that changes' do
|
|
240
|
+
# changed Switches.members
|
|
241
|
+
# triggered(&:off)
|
|
242
|
+
# end
|
|
243
|
+
#
|
|
244
|
+
# @example Like other execution blocks, multiple triggered blocks are supported in a single rule
|
|
245
|
+
# rule 'Turn a switch off and log it, 5 seconds after turning it on' do
|
|
246
|
+
# changed Switches.members, to: ON
|
|
247
|
+
# delay 5.seconds
|
|
248
|
+
# triggered(&:off)
|
|
249
|
+
# triggered {|item| logger.info("#{item.label} turned off") }
|
|
250
|
+
# end
|
|
251
|
+
prop_array :triggered, array_name: :run_queue, wrapper: Trigger
|
|
252
|
+
|
|
253
|
+
#
|
|
254
|
+
# @!method delay(duration)
|
|
255
|
+
#
|
|
256
|
+
# Add a wait between or after run blocks.
|
|
257
|
+
#
|
|
258
|
+
# The delay property is a non thread-blocking element that is executed
|
|
259
|
+
# after, before, or between run blocks.
|
|
260
|
+
#
|
|
261
|
+
# @param [java.time.temporal.TemporalAmount] duration How long to delay for.
|
|
262
|
+
# @return [void]
|
|
263
|
+
#
|
|
264
|
+
# @example
|
|
265
|
+
# rule "delay execution" do
|
|
266
|
+
# changed MotionSensor, to: CLOSED
|
|
267
|
+
# delay 5.seconds
|
|
268
|
+
# run { Light.off }
|
|
269
|
+
# end
|
|
270
|
+
#
|
|
271
|
+
# @example
|
|
272
|
+
# rule 'Delay sleeps between execution elements' do
|
|
273
|
+
# on_load
|
|
274
|
+
# run { logger.info("Sleeping") }
|
|
275
|
+
# delay 5.seconds
|
|
276
|
+
# run { logger.info("Awake") }
|
|
277
|
+
# end
|
|
278
|
+
#
|
|
279
|
+
# @example Like other execution blocks, multiple can exist in a single rule.
|
|
280
|
+
# rule 'Multiple delays can exist in a rule' do
|
|
281
|
+
# on_load
|
|
282
|
+
# run { logger.info("Sleeping") }
|
|
283
|
+
# delay 5.seconds
|
|
284
|
+
# run { logger.info("Sleeping Again") }
|
|
285
|
+
# delay 5.seconds
|
|
286
|
+
# run { logger.info("Awake") }
|
|
287
|
+
# end
|
|
288
|
+
#
|
|
289
|
+
# @example You can use Ruby code in your rule across multiple execution blocks like a run and a delay.
|
|
290
|
+
# rule 'Dim a switch on system startup over 100 seconds' do
|
|
291
|
+
# on_load
|
|
292
|
+
# 100.times do
|
|
293
|
+
# run { DimmerSwitch.dim }
|
|
294
|
+
# delay 1.second
|
|
295
|
+
# end
|
|
296
|
+
# end
|
|
297
|
+
#
|
|
298
|
+
prop_array :delay, array_name: :run_queue, wrapper: Delay
|
|
299
|
+
|
|
300
|
+
#
|
|
301
|
+
# @!method otherwise
|
|
302
|
+
#
|
|
303
|
+
# Add a block that will be passed event data, to be run if guards are
|
|
304
|
+
# not satisfied.
|
|
305
|
+
#
|
|
306
|
+
# The {otherwise} property is the automation code that is executed when
|
|
307
|
+
# a rule is triggered and guards are not satisfied. This property
|
|
308
|
+
# accepts a block of code and executes it. The block is automatically
|
|
309
|
+
# passed an event object which can be used to access multiple
|
|
310
|
+
# properties about the triggering event.
|
|
311
|
+
#
|
|
312
|
+
# @yieldparam [Core::Events::AbstractEvent] event
|
|
313
|
+
#
|
|
314
|
+
# @example
|
|
315
|
+
# rule 'Turn switch ON or OFF based on value of another switch' do
|
|
316
|
+
# on_load
|
|
317
|
+
# run { TestSwitch << ON }
|
|
318
|
+
# otherwise { TestSwitch << OFF }
|
|
319
|
+
# only_if { OtherSwitch.on? }
|
|
320
|
+
# end
|
|
321
|
+
#
|
|
322
|
+
prop_array :otherwise, array_name: :run_queue, wrapper: Otherwise
|
|
323
|
+
|
|
324
|
+
# @!group Configuration
|
|
325
|
+
|
|
326
|
+
#
|
|
327
|
+
# @!method uid(id)
|
|
328
|
+
#
|
|
329
|
+
# Set the rule's UID.
|
|
330
|
+
#
|
|
331
|
+
# @param [String] id
|
|
332
|
+
# @return [void]
|
|
333
|
+
#
|
|
334
|
+
prop :uid
|
|
335
|
+
|
|
336
|
+
#
|
|
337
|
+
# @!method name(value)
|
|
338
|
+
#
|
|
339
|
+
# Set the rule's name.
|
|
340
|
+
#
|
|
341
|
+
# @param [String] value
|
|
342
|
+
# @return [void]
|
|
343
|
+
#
|
|
344
|
+
prop :name
|
|
345
|
+
|
|
346
|
+
#
|
|
347
|
+
# @!method description(value)
|
|
348
|
+
#
|
|
349
|
+
# Set the rule's description.
|
|
350
|
+
#
|
|
351
|
+
# @param [String] value
|
|
352
|
+
# @return [void]
|
|
353
|
+
#
|
|
354
|
+
prop :description
|
|
355
|
+
|
|
356
|
+
#
|
|
357
|
+
# @!method tags(tags)
|
|
358
|
+
#
|
|
359
|
+
# Set the rule's tags.
|
|
360
|
+
#
|
|
361
|
+
# @param [String, Symbol, Semantics::Tag] tags A list of tags to assign to the rule.
|
|
362
|
+
# @return [void]
|
|
363
|
+
#
|
|
364
|
+
# @example
|
|
365
|
+
# rule "tagged rule" do
|
|
366
|
+
# tags "lighting", "security"
|
|
367
|
+
# end
|
|
368
|
+
#
|
|
369
|
+
prop :tags
|
|
370
|
+
|
|
371
|
+
#
|
|
372
|
+
# @!method enabled(value)
|
|
373
|
+
#
|
|
374
|
+
# Enable or disable the rule from executing
|
|
375
|
+
#
|
|
376
|
+
# @param [true,false] value
|
|
377
|
+
# @return [void]
|
|
378
|
+
#
|
|
379
|
+
# @example
|
|
380
|
+
# rule "disabled rule" do
|
|
381
|
+
# enabled(false)
|
|
382
|
+
# end
|
|
383
|
+
#
|
|
384
|
+
prop :enabled
|
|
385
|
+
|
|
386
|
+
#
|
|
387
|
+
# Returns all {Item Items} (or {GroupItem::Members GroupItem::Members}) referenced
|
|
388
|
+
# by the specified trigger types in this rule.
|
|
389
|
+
#
|
|
390
|
+
# @param [Symbol, Array<Symbol>] trigger_types Trigger types to search for dependencies
|
|
391
|
+
# @return [Array<Item, GroupItem::Members>]
|
|
392
|
+
#
|
|
393
|
+
# @example Ensure all dependencies have a state when executing a rule
|
|
394
|
+
# rule do |rule|
|
|
395
|
+
# changed Item1, Item2, Item3
|
|
396
|
+
# only_if { rule.dependencies.all?(&:state?) }
|
|
397
|
+
# run { FormulaItem.update(Item3.state - (Item1.state + Item2.state)) }
|
|
398
|
+
# end
|
|
399
|
+
#
|
|
400
|
+
def dependencies(trigger_types = %i[changed updated])
|
|
401
|
+
trigger_types = Array.wrap(trigger_types)
|
|
402
|
+
|
|
403
|
+
ruby_triggers.flat_map do |t|
|
|
404
|
+
next [] unless trigger_types.include?(t.first)
|
|
405
|
+
|
|
406
|
+
t[1].select { |i| i.is_a?(Item) || i.is_a?(GroupItem::Members) }
|
|
407
|
+
end.uniq
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# @!group Guards
|
|
411
|
+
# Guards exist to only permit rules to run if certain conditions are
|
|
412
|
+
# satisfied. Think of these as declarative `if` statements that keep
|
|
413
|
+
# the run block free of conditional logic, although you can of course
|
|
414
|
+
# still use conditional logic in run blocks if you prefer.
|
|
415
|
+
#
|
|
416
|
+
# ### Guard Combination
|
|
417
|
+
#
|
|
418
|
+
# Multiple guards can be used on the same rule. All must be satisfied
|
|
419
|
+
# for a rule to execute.
|
|
420
|
+
#
|
|
421
|
+
# @example
|
|
422
|
+
# rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is OFF and Door is CLOSED" do
|
|
423
|
+
# changed LightSwitch, to: ON
|
|
424
|
+
# run { OutsideDimmer << 50 }
|
|
425
|
+
# only_if { Door.closed? }
|
|
426
|
+
# not_if { OtherSwitch.on? }
|
|
427
|
+
# end
|
|
428
|
+
#
|
|
429
|
+
|
|
430
|
+
#
|
|
431
|
+
# @!method between(range)
|
|
432
|
+
#
|
|
433
|
+
# Only execute rule if the current time is between the supplied time ranges.
|
|
434
|
+
#
|
|
435
|
+
# If the range is of strings, it will be parsed to an appropriate time class.
|
|
436
|
+
#
|
|
437
|
+
# @param [Range] range
|
|
438
|
+
# @return [void]
|
|
439
|
+
#
|
|
440
|
+
# @example
|
|
441
|
+
# rule "Between guard" do
|
|
442
|
+
# changed MotionSensor, to: OPEN
|
|
443
|
+
# between "6:05".."14:05:05" # Include end
|
|
444
|
+
# run { Light.on }
|
|
445
|
+
# end
|
|
446
|
+
#
|
|
447
|
+
# @example
|
|
448
|
+
# rule "Between guard" do
|
|
449
|
+
# changed MotionSensor, to: OPEN
|
|
450
|
+
# between "6:05".."14:05:05" # Excludes end second
|
|
451
|
+
# run { Light.on }
|
|
452
|
+
# end
|
|
453
|
+
#
|
|
454
|
+
# @example
|
|
455
|
+
# rule "Between guard" do
|
|
456
|
+
# changed MotionSensor, to: OPEN
|
|
457
|
+
# between LocalTime.of(6, 5)..LocalTime.of(14, 15, 5)
|
|
458
|
+
# run { Light.on }
|
|
459
|
+
# end
|
|
460
|
+
#
|
|
461
|
+
# @example String of {LocalTime}
|
|
462
|
+
# rule 'Log an entry if started between 3:30:04 and midnight using strings' do
|
|
463
|
+
# on_load
|
|
464
|
+
# run { logger.info ("Started at #{LocalTime.now}")}
|
|
465
|
+
# between '3:30:04'..LocalTime::MIDNIGHT
|
|
466
|
+
# end
|
|
467
|
+
#
|
|
468
|
+
# @example {LocalTime}
|
|
469
|
+
# rule 'Log an entry if started between 3:30:04 and midnight using LocalTime objects' do
|
|
470
|
+
# on_load
|
|
471
|
+
# run { logger.info ("Started at #{LocalTime.now}")}
|
|
472
|
+
# between LocalTime.of(3, 30, 4)..LocalTime::MIDNIGHT
|
|
473
|
+
# end
|
|
474
|
+
#
|
|
475
|
+
# @example String of {MonthDay}
|
|
476
|
+
# rule 'Log an entry if started between March 9th and April 10ths' do
|
|
477
|
+
# on_load
|
|
478
|
+
# run { logger.info ("Started at #{Time.now}")}
|
|
479
|
+
# between '03-09'..'04-10'
|
|
480
|
+
# end
|
|
481
|
+
#
|
|
482
|
+
# @example {MonthDay}
|
|
483
|
+
# rule 'Log an entry if started between March 9th and April 10ths' do
|
|
484
|
+
# on_load
|
|
485
|
+
# run { logger.info ("Started at #{Time.now}")}
|
|
486
|
+
# between MonthDay.of(03,09)..'04-06'
|
|
487
|
+
# end
|
|
488
|
+
#
|
|
489
|
+
prop :between
|
|
490
|
+
|
|
491
|
+
#
|
|
492
|
+
# @!method only_if
|
|
493
|
+
#
|
|
494
|
+
# Allows rule execution when the block's result is true and prevents it when it's false.
|
|
495
|
+
#
|
|
496
|
+
# @yieldparam [Core::Events::AbstractEvent] event The event data that is about to trigger the rule.
|
|
497
|
+
# @yieldreturn [Boolean] A value indicating if the rule should run.
|
|
498
|
+
# @return [void]
|
|
499
|
+
#
|
|
500
|
+
# @example
|
|
501
|
+
# rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is also ON" do
|
|
502
|
+
# changed LightSwitch, to: ON
|
|
503
|
+
# run { OutsideDimmer << 50 }
|
|
504
|
+
# only_if { OtherSwitch.on? }
|
|
505
|
+
# end
|
|
506
|
+
#
|
|
507
|
+
# @example Multiple {only_if} statements can be used and *all* must be true for the rule to run.
|
|
508
|
+
# rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is also ON and Door is closed" do
|
|
509
|
+
# changed LightSwitch, to: ON
|
|
510
|
+
# run { OutsideDimmer << 50 }
|
|
511
|
+
# only_if { OtherSwitch.on? }
|
|
512
|
+
# only_if { Door.closed? }
|
|
513
|
+
# end
|
|
514
|
+
#
|
|
515
|
+
# @example Guards have access to event information.
|
|
516
|
+
# rule "Set OutsideDimmer to 50% if any switch in group Switches starting with Outside is switched On" do
|
|
517
|
+
# changed Switches.items, to: ON
|
|
518
|
+
# run { OutsideDimmer << 50 }
|
|
519
|
+
# only_if { |event| event.item.name.start_with?("Outside") }
|
|
520
|
+
# end
|
|
521
|
+
#
|
|
522
|
+
prop_array(:only_if) do |item|
|
|
523
|
+
raise ArgumentError, "Object passed to only_if must be a proc" unless item.is_a?(Proc)
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
#
|
|
527
|
+
# @!method not_if
|
|
528
|
+
#
|
|
529
|
+
# Prevents execution of rules when the block's result is true and allows it when it's true.
|
|
530
|
+
#
|
|
531
|
+
# @yieldparam [Core::Events::AbstractEvent] event The event data that is about to trigger the rule.
|
|
532
|
+
# @yieldreturn [Boolean] A value indicating if the rule should _not_ run.
|
|
533
|
+
# @return [void]
|
|
534
|
+
#
|
|
535
|
+
# @example
|
|
536
|
+
# rule "Set OutsideDimmer to 50% if LightSwtich turned on and OtherSwitch is OFF" do
|
|
537
|
+
# changed LightSwitch, to: ON
|
|
538
|
+
# run { OutsideDimmer << 50 }
|
|
539
|
+
# not_if { OtherSwitch.on? }
|
|
540
|
+
# end
|
|
541
|
+
#
|
|
542
|
+
# @example Multiple {not_if} statements can be used and if **any** of them are not satisfied the rule will not run.
|
|
543
|
+
# rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is OFF and Door is not CLOSED" do
|
|
544
|
+
# changed LightSwitch, to: ON
|
|
545
|
+
# run { OutsideDimmer << 50 }
|
|
546
|
+
# not_if { OtherSwitch.on? }
|
|
547
|
+
# not_if { Door.closed? }
|
|
548
|
+
# end
|
|
549
|
+
#
|
|
550
|
+
prop_array(:not_if) do |item|
|
|
551
|
+
raise ArgumentError, "Object passed to not_if must be a proc" unless item.is_a?(Proc)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# rubocop:disable Layout/LineLength
|
|
555
|
+
|
|
556
|
+
#
|
|
557
|
+
# Waits until triggers have stopped firing for a period of time before executing the rule.
|
|
558
|
+
#
|
|
559
|
+
# It ignores triggers that are "bouncing around" (rapidly firing) by ignoring
|
|
560
|
+
# them until they have quiesced (stopped triggering for a while).
|
|
561
|
+
#
|
|
562
|
+
# ## Comparison Table
|
|
563
|
+
# | Guard | Triggers Immediately | Description |
|
|
564
|
+
# | -------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
565
|
+
# | {debounce_for} | No | Waits until there is a minimum interval between triggers. |
|
|
566
|
+
# | {throttle_for} | No | Rate-limits the executions to a minimum interval, regardless of the interval between triggers. Waits until the end of the period before executing, ignores any leading triggers. |
|
|
567
|
+
# | {only_every} | Yes | Rate-limits the executions to a minimum interval. Immediately executes the first trigger, then ignores subsequent triggers for the period. |
|
|
568
|
+
#
|
|
569
|
+
# ## Timing Diagram
|
|
570
|
+
# The following timing diagram illustrates the difference between {debounce_for},
|
|
571
|
+
# {throttle_for}, and {only_every} guards:
|
|
572
|
+
#
|
|
573
|
+
# ```
|
|
574
|
+
# TIME INDEX ===> 1 1 2 2 3 3 4 4
|
|
575
|
+
# 0 5 0 5 0 5 0 5 0 5
|
|
576
|
+
# Triggers : "X.X...X...X..XX.X.X....X.XXXXXXXXXXX....X....."
|
|
577
|
+
# debounce_for 5 : "|......................X.|..............X....."
|
|
578
|
+
# debounce_for 5..5 : "|....X|....X.|....X....|....X|....X|....X....X"
|
|
579
|
+
# debounce_for 5..6 : "|.....X...|.....X.|....X.|.....X|.....X.|....X"
|
|
580
|
+
# debounce_for 5..7 : "|......X..|......X|....X.|......X|......X....."
|
|
581
|
+
# debounce_for 5..8 : "|.......X.|.......X....|.......X|.......X....."
|
|
582
|
+
# debounce_for 5..20: "|...................X..|................X....."
|
|
583
|
+
# # throttle_for will fire every 5 intervals after the "first" trigger
|
|
584
|
+
# throttle_for 5 : "|....X|....X.|....X....|....X|....X|....X....."
|
|
585
|
+
# only_every 5 : "X.....X......X....X....X....X....X......X....."
|
|
586
|
+
#
|
|
587
|
+
# Triggers : "X.X...X...X..XX.X.X..X...XXXXXXXXXXX.X..X.X..."
|
|
588
|
+
# debounce_for 5..44: "|...........................................X."
|
|
589
|
+
#
|
|
590
|
+
# # Notice above, triggers keep firing with intervals less than 5, so
|
|
591
|
+
# # debouncer keeps waiting, but puts a stop at 44 (the end of range).
|
|
592
|
+
# ```
|
|
593
|
+
#
|
|
594
|
+
# @param [Duration,Range] debounce_time The minimum interval between two consecutive
|
|
595
|
+
# triggers before the rules are allowed to run.
|
|
596
|
+
#
|
|
597
|
+
# When specified just as a Duration or an endless range, it sets the minimum interval
|
|
598
|
+
# between two consecutive triggers before rules are executed. It will
|
|
599
|
+
# wait endlessly unless this condition is met or an end of range was specified.
|
|
600
|
+
#
|
|
601
|
+
# When the end of the range is specified, it sets the maximum amount of time to wait
|
|
602
|
+
# from the first trigger before the rule will execute, even when triggers continue
|
|
603
|
+
# to occur more frequently than the minimum interval.
|
|
604
|
+
#
|
|
605
|
+
# When an equal beginning and ending values are given, it will behave just like
|
|
606
|
+
# {throttle_for}.
|
|
607
|
+
#
|
|
608
|
+
# @return [void]
|
|
609
|
+
#
|
|
610
|
+
# @see throttle_for
|
|
611
|
+
# @see only_every
|
|
612
|
+
#
|
|
613
|
+
# @example Wait until item stopped changing for at least 1 minute before running the rule
|
|
614
|
+
# rule do
|
|
615
|
+
# changed Item1
|
|
616
|
+
# debounce_for 1.minute
|
|
617
|
+
# run { ... }
|
|
618
|
+
# end
|
|
619
|
+
#
|
|
620
|
+
# @example Alert when door is open for a while
|
|
621
|
+
# # Note: When combined with a state check (only_if), this becomes functionally
|
|
622
|
+
# # equivalent to the changed duration feature.
|
|
623
|
+
# rule "Door alert" do
|
|
624
|
+
# changed Door_State
|
|
625
|
+
# debounce_for 10.minutes
|
|
626
|
+
# only_if { Door_State.open? }
|
|
627
|
+
# run { notify("The Door has been open for 10 minutes!") }
|
|
628
|
+
# end
|
|
629
|
+
#
|
|
630
|
+
def debounce_for(debounce_time)
|
|
631
|
+
idle_time = debounce_time.is_a?(Range) ? debounce_time.begin : debounce_time
|
|
632
|
+
debounce(for: debounce_time, idle_time: idle_time)
|
|
633
|
+
end
|
|
634
|
+
# rubocop:enable Layout/LineLength
|
|
635
|
+
|
|
636
|
+
#
|
|
637
|
+
# Rate-limits rule executions by delaying triggers and executing the last
|
|
638
|
+
# trigger within the given duration.
|
|
639
|
+
#
|
|
640
|
+
# When a new trigger occurs, it will hold the execution and start a fixed timer for
|
|
641
|
+
# the given duration. Should more triggers occur during this time, keep holding
|
|
642
|
+
# and once the wait time is over, execute the latest trigger.
|
|
643
|
+
#
|
|
644
|
+
# {throttle_for} will execute rules after it had waited
|
|
645
|
+
# for the given duration, regardless of how frequently the triggers were occuring.
|
|
646
|
+
# In contrast, {debounce_for} will wait until there is a minimum interval
|
|
647
|
+
# between two triggers.
|
|
648
|
+
#
|
|
649
|
+
# {throttle_for} is ideal in situations where regular status updates need to be made
|
|
650
|
+
# for frequently changing values. It is also useful when a rule responds to triggers
|
|
651
|
+
# from multiple related items that are updated at around the same time. Instead of
|
|
652
|
+
# executing the rule multiple times, {throttle_for} will wait for a pre-set amount
|
|
653
|
+
# of time since the first group of triggers occurred before executing the rule.
|
|
654
|
+
#
|
|
655
|
+
# @param [Duration] duration The minimum amount of time to wait inbetween rule
|
|
656
|
+
# executions.
|
|
657
|
+
#
|
|
658
|
+
# @return [void]
|
|
659
|
+
#
|
|
660
|
+
# @see debounce_for
|
|
661
|
+
# @see only_every
|
|
662
|
+
#
|
|
663
|
+
# @example Perform calculations from multiple items
|
|
664
|
+
# rule "Update Power Summary " do |rule|
|
|
665
|
+
# changed Power_From_Solar, Power_Load, Power_From_Grid
|
|
666
|
+
# throttle_for 1.second
|
|
667
|
+
# only_if { rule.dependencies.all?(&:state?) } # make sure all items have a state
|
|
668
|
+
# run do
|
|
669
|
+
# msg = []
|
|
670
|
+
# msg << Power_Load.state.negate.to_unit("kW").format("Load: %.2f %unit%")
|
|
671
|
+
# msg << Power_From_Solar.state.to_unit("kW").format("PV: %.2f %unit%")
|
|
672
|
+
# if Power_From_Grid.positive?
|
|
673
|
+
# msg << Power_From_Grid.state.to_unit("kW").format("From Grid: %.1f %unit%")
|
|
674
|
+
# else
|
|
675
|
+
# msg << Power_From_Grid.state.negate.to_unit("kW").format("To Grid: %.1f %unit%")
|
|
676
|
+
# end
|
|
677
|
+
# Power_Summary.update(msg.join(", "))
|
|
678
|
+
# end
|
|
679
|
+
# end
|
|
680
|
+
#
|
|
681
|
+
def throttle_for(duration)
|
|
682
|
+
debounce(for: duration)
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
#
|
|
686
|
+
# Executes the rule then ignores subsequent triggers for a given duration.
|
|
687
|
+
#
|
|
688
|
+
# Additional triggers that occur within the given duration after the rule execution
|
|
689
|
+
# will be ignored. This results in executions happening only at the specified interval or
|
|
690
|
+
# more.
|
|
691
|
+
#
|
|
692
|
+
# Unlike {throttle_for}, this guard will execute the rule as soon as a new trigger
|
|
693
|
+
# occurs instead of waiting for the specified duration. This is ideal for triggers
|
|
694
|
+
# such as a door bell where the rule should run as soon as a new trigger is detected
|
|
695
|
+
# but ignore subsequent triggers if they occur too soon after.
|
|
696
|
+
#
|
|
697
|
+
# @param [Duration,:second,:minute,:hour,:day] interval The period during which
|
|
698
|
+
# subsequent triggers are ignored.
|
|
699
|
+
# @return [void]
|
|
700
|
+
#
|
|
701
|
+
# @example Only allow executions every 10 minutes or more
|
|
702
|
+
# rule "Aircon Vent Control" do
|
|
703
|
+
# changed BedRoom_Temperature
|
|
704
|
+
# only_every 10.minutes
|
|
705
|
+
# run do
|
|
706
|
+
# # Adjust BedRoom_Aircon_Vent
|
|
707
|
+
# end
|
|
708
|
+
# end
|
|
709
|
+
#
|
|
710
|
+
# @example Run only on the first update and ignore subsequent triggers for the next minute
|
|
711
|
+
# # They can keep pressing the door bell as often as they like,
|
|
712
|
+
# # but the bell will only ring at most once every minute
|
|
713
|
+
# rule do
|
|
714
|
+
# updated DoorBell_Button, to: "single"
|
|
715
|
+
# only_every 1.minute
|
|
716
|
+
# run { Audio.play_stream "doorbell.mp3" }
|
|
717
|
+
# end
|
|
718
|
+
#
|
|
719
|
+
# @example Using symbolic duration
|
|
720
|
+
# rule "Display update" do
|
|
721
|
+
# updated Power_Usage
|
|
722
|
+
# only_every :minute
|
|
723
|
+
# run { Power_Usage_Display.update "Current power usage: #{Power_Usage.average_since(1.minute.ago)}" }
|
|
724
|
+
# end
|
|
725
|
+
#
|
|
726
|
+
# @see debounce_for
|
|
727
|
+
# @see throttle_for
|
|
728
|
+
#
|
|
729
|
+
def only_every(interval)
|
|
730
|
+
interval = 1.send(interval) if %i[second minute hour day].include?(interval)
|
|
731
|
+
debounce(for: interval, leading: true)
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
# @!endgroup
|
|
735
|
+
|
|
736
|
+
# @!visibility private
|
|
737
|
+
#
|
|
738
|
+
# Create a new DSL
|
|
739
|
+
#
|
|
740
|
+
# @param [Object] caller_binding The object initializing this configuration.
|
|
741
|
+
# Used to execute within the object's context
|
|
742
|
+
#
|
|
743
|
+
def initialize(caller_binding)
|
|
744
|
+
@rule_triggers = RuleTriggers.new
|
|
745
|
+
@caller = caller_binding.eval "self"
|
|
746
|
+
@ruby_triggers = []
|
|
747
|
+
@on_load = nil
|
|
748
|
+
@debounce_settings = nil
|
|
749
|
+
enabled(true)
|
|
750
|
+
tags([])
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
# @!group Triggers
|
|
754
|
+
# Triggers specify what will cause the execution blocks to run.
|
|
755
|
+
# Multiple triggers can be defined within the same rule.
|
|
756
|
+
#
|
|
757
|
+
# ### Trigger Attachments
|
|
758
|
+
#
|
|
759
|
+
# All triggers support event attachments that enable the association
|
|
760
|
+
# of an object to a trigger. This enables one to use the same rule
|
|
761
|
+
# and take different actions if the trigger is different. The
|
|
762
|
+
# attached object is passed to the execution block through the
|
|
763
|
+
# {Core::Events::AbstractEvent#attachment} accessor.
|
|
764
|
+
#
|
|
765
|
+
# @note The trigger attachment feature is not available for UI rules.
|
|
766
|
+
#
|
|
767
|
+
# @example
|
|
768
|
+
# rule 'Set Dark switch at sunrise and sunset' do
|
|
769
|
+
# channel 'astro:sun:home:rise#event', attach: OFF
|
|
770
|
+
# channel 'astro:sun:home:set#event', attach: ON
|
|
771
|
+
# run { |event| Dark << event.attachment }
|
|
772
|
+
# end
|
|
773
|
+
|
|
774
|
+
#
|
|
775
|
+
# Creates a channel trigger
|
|
776
|
+
#
|
|
777
|
+
# The channel trigger executes rule when a specific channel is triggered. The syntax
|
|
778
|
+
# supports one or more channels with one or more triggers. `thing` is an optional
|
|
779
|
+
# parameter that makes it easier to set triggers on multiple channels on the same thing.
|
|
780
|
+
#
|
|
781
|
+
# @param [String, Core::Things::Channel, Core::Things::ChannelUID] channels
|
|
782
|
+
# channels to create triggers for in form of 'binding_id:type_id:thing_id#channel_id'
|
|
783
|
+
# or 'channel_id' if thing is provided.
|
|
784
|
+
# @param [String, Core::Things::Thing, Core::Things::ThingUID] thing
|
|
785
|
+
# Thing(s) to create trigger for if not specified with the channel.
|
|
786
|
+
# @param [String, Array<String>] triggered
|
|
787
|
+
# Only execute rule if the event on the channel matches this/these event/events.
|
|
788
|
+
# @param [Object] attach object to be attached to the trigger
|
|
789
|
+
# @return [void]
|
|
790
|
+
#
|
|
791
|
+
# @example
|
|
792
|
+
# rule "Execute rule when channel is triggered" do
|
|
793
|
+
# channel "astro:sun:home:rise#event"
|
|
794
|
+
# run { logger.info("Channel triggered") }
|
|
795
|
+
# end
|
|
796
|
+
# # The above is the same as each of the below
|
|
797
|
+
#
|
|
798
|
+
# rule "Execute rule when channel is triggered" do
|
|
799
|
+
# channel "rise#event", thing: "astro:sun:home"
|
|
800
|
+
# run { logger.info("Channel triggered") }
|
|
801
|
+
# end
|
|
802
|
+
#
|
|
803
|
+
# rule "Execute rule when channel is triggered" do
|
|
804
|
+
# channel "rise#event", thing: things["astro:sun:home"]
|
|
805
|
+
# run { logger.info("Channel triggered") }
|
|
806
|
+
# end
|
|
807
|
+
#
|
|
808
|
+
# rule "Execute rule when channel is triggered" do
|
|
809
|
+
# channel "rise#event", thing: things["astro:sun:home"].uid
|
|
810
|
+
# run { logger.info("Channel triggered") }
|
|
811
|
+
# end
|
|
812
|
+
#
|
|
813
|
+
# rule "Execute rule when channel is triggered" do
|
|
814
|
+
# channel "rise#event", thing: ["astro:sun:home"]
|
|
815
|
+
# run { logger.info("Channel triggered") }
|
|
816
|
+
# end
|
|
817
|
+
#
|
|
818
|
+
# rule "Execute rule when channel is triggered" do
|
|
819
|
+
# channel things["astro:sun:home"].channels["rise#event"]
|
|
820
|
+
# run { logger.info("Channel triggered") }
|
|
821
|
+
# end
|
|
822
|
+
#
|
|
823
|
+
# rule "Execute rule when channel is triggered" do
|
|
824
|
+
# channel things["astro:sun:home"].channels["rise#event"].uid
|
|
825
|
+
# run { logger.info("Channel triggered") }
|
|
826
|
+
# end
|
|
827
|
+
#
|
|
828
|
+
# @example
|
|
829
|
+
# rule "Rule provides access to channel trigger events in run block" do
|
|
830
|
+
# channel "astro:sun:home:rise#event", triggered: 'START'
|
|
831
|
+
# run { |trigger| logger.info("Channel(#{trigger.channel}) triggered event: #{trigger.event}") }
|
|
832
|
+
# end
|
|
833
|
+
#
|
|
834
|
+
# @example
|
|
835
|
+
# rule "Keypad Code Received test" do
|
|
836
|
+
# channel "mqtt:homie300:mosquitto:backgate:keypad#code"
|
|
837
|
+
# run do |event|
|
|
838
|
+
# logger.info("Received keycode from #{event.channel.thing.uid.id}")
|
|
839
|
+
# end
|
|
840
|
+
# end
|
|
841
|
+
#
|
|
842
|
+
# @example
|
|
843
|
+
# rule "Rules support multiple channels" do
|
|
844
|
+
# channel "rise#event", "set#event", thing: "astro:sun:home"
|
|
845
|
+
# run { logger.info("Channel triggered") }
|
|
846
|
+
# end
|
|
847
|
+
#
|
|
848
|
+
# @example
|
|
849
|
+
# rule "Rules support multiple channels and triggers" do
|
|
850
|
+
# channel "rise#event", "set#event", thing: "astro:sun:home", triggered: ["START", "STOP"]
|
|
851
|
+
# run { logger.info("Channel triggered") }
|
|
852
|
+
# end
|
|
853
|
+
#
|
|
854
|
+
# @example
|
|
855
|
+
# rule "Rules support multiple things" do
|
|
856
|
+
# channel "keypad#code", thing: ["mqtt:homie300:keypad1", "mqtt:homie300:keypad2"]
|
|
857
|
+
# run { logger.info("Channel triggered") }
|
|
858
|
+
# end
|
|
859
|
+
#
|
|
860
|
+
def channel(*channels, thing: nil, triggered: nil, attach: nil)
|
|
861
|
+
channel_trigger = Channel.new(rule_triggers: @rule_triggers)
|
|
862
|
+
flattened_channels = Channel.channels(channels: channels, thing: thing)
|
|
863
|
+
triggers = [triggered].flatten
|
|
864
|
+
@ruby_triggers << [:channel, flattened_channels, { triggers: triggers }]
|
|
865
|
+
flattened_channels.each do |channel|
|
|
866
|
+
triggers.each do |trigger|
|
|
867
|
+
channel_trigger.trigger(channel: channel, trigger: trigger, attach: attach)
|
|
868
|
+
end
|
|
869
|
+
end
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
#
|
|
873
|
+
# Creates a channel linked trigger
|
|
874
|
+
#
|
|
875
|
+
# @param [Object] attach object to be attached to the trigger
|
|
876
|
+
# @return [void]
|
|
877
|
+
#
|
|
878
|
+
# @example
|
|
879
|
+
# rule "channel linked" do
|
|
880
|
+
# channel_linked
|
|
881
|
+
# run do |event|
|
|
882
|
+
# logger.info("#{event.link.item.name} linked to #{event.link.channel_uid}.")
|
|
883
|
+
# end
|
|
884
|
+
# end
|
|
885
|
+
def channel_linked(attach: nil)
|
|
886
|
+
@ruby_triggers << [:channel_linked]
|
|
887
|
+
event("openhab/links/*/added", types: "ItemChannelLinkAddedEvent", attach: attach)
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
#
|
|
891
|
+
# Creates a channel unlinked trigger
|
|
892
|
+
#
|
|
893
|
+
# Note that the item or the thing it's linked to may no longer exist,
|
|
894
|
+
# so if you try to access those objects they'll be nil.
|
|
895
|
+
#
|
|
896
|
+
# @param [Object] attach object to be attached to the trigger
|
|
897
|
+
# @return [void]
|
|
898
|
+
#
|
|
899
|
+
# @example
|
|
900
|
+
# rule "channel unlinked" do
|
|
901
|
+
# channel_unlinked
|
|
902
|
+
# run do |event|
|
|
903
|
+
# logger.info("#{event.link.item_name} unlinked from #{event.link.channel_uid}.")
|
|
904
|
+
# end
|
|
905
|
+
# end
|
|
906
|
+
def channel_unlinked(attach: nil)
|
|
907
|
+
@ruby_triggers << [:channel_linked]
|
|
908
|
+
event("openhab/links/*/removed", types: "ItemChannelLinkRemovedEvent", attach: attach)
|
|
909
|
+
end
|
|
910
|
+
|
|
911
|
+
#
|
|
912
|
+
# Creates a trigger when an item, member of a group, or a thing changed
|
|
913
|
+
# states.
|
|
914
|
+
#
|
|
915
|
+
# When the changed element is a {Core::Things::Thing Thing}, the `from`
|
|
916
|
+
# and `to` values will accept symbols and strings, where the symbol'
|
|
917
|
+
# matches the
|
|
918
|
+
# [supported status](https://www.openhab.org/docs/concepts/things.html#thing-status).
|
|
919
|
+
#
|
|
920
|
+
# The `event` passed to run blocks will be an
|
|
921
|
+
# {Core::Events::ItemStateChangedEvent} or a
|
|
922
|
+
# {Core::Events::ThingStatusInfoChangedEvent} depending on if the
|
|
923
|
+
# triggering element was an item or a thing.
|
|
924
|
+
#
|
|
925
|
+
# @param [Item, GroupItem::Members, Thing] items Objects to create trigger for.
|
|
926
|
+
# @param [State, Array<State>, Range, Proc] from
|
|
927
|
+
# Only execute rule if previous state matches `from` state(s).
|
|
928
|
+
# @param [State, Array<State>, Range, Proc] to State(s) for
|
|
929
|
+
# Only execute rule if new state matches `to` state(s).
|
|
930
|
+
# @param [java.time.temporal.TemporalAmount] for
|
|
931
|
+
# Duration item must remain in the same state before executing the execution blocks.
|
|
932
|
+
# @param [Object] attach object to be attached to the trigger
|
|
933
|
+
# @return [void]
|
|
934
|
+
#
|
|
935
|
+
# @example Multiple items can be separated with a comma:
|
|
936
|
+
# rule "Execute rule when either sensor changed" do
|
|
937
|
+
# changed FrontMotion_Sensor, RearMotion_Sensor
|
|
938
|
+
# run { |event| logger.info("Motion detected by #{event.item.name}") }
|
|
939
|
+
# end
|
|
940
|
+
#
|
|
941
|
+
# @example Group member trigger
|
|
942
|
+
# rule "Execute rule when member changed" do
|
|
943
|
+
# changed Sensors.members
|
|
944
|
+
# run { |event| logger.info("Motion detected by #{event.item.name}") }
|
|
945
|
+
# end
|
|
946
|
+
#
|
|
947
|
+
# @example `for` parameter can be a proc too:
|
|
948
|
+
# Alarm_Delay << 20
|
|
949
|
+
#
|
|
950
|
+
# rule "Execute rule when item is changed for specified duration" do
|
|
951
|
+
# changed Alarm_Mode, for: -> { Alarm_Delay.state }
|
|
952
|
+
# run { logger.info("Alarm Mode Updated") }
|
|
953
|
+
# end
|
|
954
|
+
#
|
|
955
|
+
# @example You can optionally provide `from` and `to` states to restrict the cases in which the rule executes:
|
|
956
|
+
# rule "Execute rule when item is changed to specific number, from specific number, for specified duration" do
|
|
957
|
+
# changed Alarm_Mode, from: 8, to: [14,12], for: 12.seconds
|
|
958
|
+
# run { logger.info("Alarm Mode Updated") }
|
|
959
|
+
# end
|
|
960
|
+
#
|
|
961
|
+
# @example Works with ranges:
|
|
962
|
+
# rule "Execute when item changed to a range of numbers, from a range of numbers, for specified duration" do
|
|
963
|
+
# changed Alarm_Mode, from: 8..10, to: 12..14, for: 12.seconds
|
|
964
|
+
# run { logger.info("Alarm Mode Updated") }
|
|
965
|
+
# end
|
|
966
|
+
#
|
|
967
|
+
# @example Works with endless ranges:
|
|
968
|
+
# rule "Execute rule when item is changed to any number greater than 12"
|
|
969
|
+
# changed Alarm_Mode, to: (12..) # Parenthesis required for endless ranges
|
|
970
|
+
# run { logger.info("Alarm Mode Updated") }
|
|
971
|
+
# end
|
|
972
|
+
#
|
|
973
|
+
# @example Works with procs:
|
|
974
|
+
# rule "Execute when item state is changed from an odd number, to an even number, for specified duration" do
|
|
975
|
+
# changed Alarm_Mode, from: proc { |from| from.odd? }, to: proc {|to| to.even? }, for: 12.seconds
|
|
976
|
+
# run { logger.info("Alarm Mode Updated") }
|
|
977
|
+
# end
|
|
978
|
+
#
|
|
979
|
+
# @example Works with lambdas:
|
|
980
|
+
# rule "Execute when item state is changed from an odd number, to an even number, for specified duration" do
|
|
981
|
+
# changed Alarm_Mode, from: -> from { from.odd? }, to: -> to { to.even? }, for: 12.seconds
|
|
982
|
+
# run { logger.info("Alarm Mode Updated") }
|
|
983
|
+
# end
|
|
984
|
+
#
|
|
985
|
+
# @example Works with Things:
|
|
986
|
+
# rule "Execute rule when thing is changed" do
|
|
987
|
+
# changed things["astro:sun:home"], :from => :online, :to => :uninitialized
|
|
988
|
+
# run { |event| logger.info("Thing #{event.uid} status <trigger> to #{event.status}") }
|
|
989
|
+
# end
|
|
990
|
+
#
|
|
991
|
+
# @example Real World Example
|
|
992
|
+
# rule "Log (or notify) when an exterior door is left open for more than 5 minutes" do
|
|
993
|
+
# changed ExteriorDoors.members, to: OPEN, for: 5.minutes
|
|
994
|
+
# triggered {|door| logger.info("#{door.name} has been left open!") }
|
|
995
|
+
# end
|
|
996
|
+
#
|
|
997
|
+
def changed(*items, to: nil, from: nil, for: nil, attach: nil)
|
|
998
|
+
changed = Changed.new(rule_triggers: @rule_triggers)
|
|
999
|
+
# for is a reserved word in ruby, so use local_variable_get :for
|
|
1000
|
+
duration = binding.local_variable_get(:for)
|
|
1001
|
+
|
|
1002
|
+
@ruby_triggers << [:changed, items, { to: to, from: from, duration: duration }]
|
|
1003
|
+
|
|
1004
|
+
from = [nil] if from.nil?
|
|
1005
|
+
to = [nil] if to.nil?
|
|
1006
|
+
items.each do |item|
|
|
1007
|
+
case item
|
|
1008
|
+
when Core::Things::Thing,
|
|
1009
|
+
Core::Things::ThingUID,
|
|
1010
|
+
Core::Items::Item,
|
|
1011
|
+
Core::Items::GroupItem::Members
|
|
1012
|
+
nil
|
|
1013
|
+
else
|
|
1014
|
+
raise ArgumentError, "items must be an Item, GroupItem::Members, Thing, or ThingUID"
|
|
1015
|
+
end
|
|
1016
|
+
|
|
1017
|
+
logger.trace("Creating changed trigger for entity(#{item}), to(#{to.inspect}), from(#{from.inspect})")
|
|
1018
|
+
|
|
1019
|
+
Array.wrap(from).each do |from_state|
|
|
1020
|
+
Array.wrap(to).each do |to_state|
|
|
1021
|
+
changed.trigger(item: item, from: from_state, to: to_state, duration: duration, attach: attach)
|
|
1022
|
+
end
|
|
1023
|
+
end
|
|
1024
|
+
end
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1027
|
+
#
|
|
1028
|
+
# Create a cron trigger
|
|
1029
|
+
#
|
|
1030
|
+
# @overload cron(expression, attach: nil)
|
|
1031
|
+
# @param [String, nil] expression [openHAB style cron expression](https://www.openhab.org/docs/configuration/rules-dsl.html#time-based-triggers)
|
|
1032
|
+
# @param [Object] attach object to be attached to the trigger
|
|
1033
|
+
#
|
|
1034
|
+
# @example Using a cron expression
|
|
1035
|
+
# rule "cron expression" do
|
|
1036
|
+
# cron "43 46 13 ? * ?"
|
|
1037
|
+
# run { Light.on }
|
|
1038
|
+
# end
|
|
1039
|
+
#
|
|
1040
|
+
# @overload cron(second: nil, minute: nil, hour: nil, dom: nil, month: nil, dow: nil, year: nil, attach: nil)
|
|
1041
|
+
# The trigger can be created by specifying each field as keyword arguments.
|
|
1042
|
+
# Omitted fields will default to `*` or `?` as appropriate.
|
|
1043
|
+
#
|
|
1044
|
+
# Each field is optional, but at least one must be specified.
|
|
1045
|
+
#
|
|
1046
|
+
# The same rules for the standard
|
|
1047
|
+
# [cron expression](https://www.quartz-scheduler.org/documentation/quartz-2.2.2/tutorials/tutorial-lesson-06.html)
|
|
1048
|
+
# apply for each field. For example, multiple values can be separated
|
|
1049
|
+
# with a comma within a string.
|
|
1050
|
+
#
|
|
1051
|
+
# @param [Integer, String, nil] second
|
|
1052
|
+
# @param [Integer, String, nil] minute
|
|
1053
|
+
# @param [Integer, String, nil] hour
|
|
1054
|
+
# @param [Integer, String, nil] dom
|
|
1055
|
+
# @param [Integer, String, nil] month
|
|
1056
|
+
# @param [Integer, String, nil] dow
|
|
1057
|
+
# @param [Integer, String, nil] year
|
|
1058
|
+
# @param [Object] attach object to be attached to the trigger
|
|
1059
|
+
# @example
|
|
1060
|
+
# # Run every 3 minutes on Monday to Friday
|
|
1061
|
+
# # equivalent to the cron expression "0 */3 * ? * MON-FRI *"
|
|
1062
|
+
# rule "Using cron fields" do
|
|
1063
|
+
# cron second: 0, minute: "*/3", dow: "MON-FRI"
|
|
1064
|
+
# run { logger.info "Cron rule executed" }
|
|
1065
|
+
# end
|
|
1066
|
+
#
|
|
1067
|
+
# @return [void]
|
|
1068
|
+
#
|
|
1069
|
+
def cron(expression = nil, attach: nil, **fields)
|
|
1070
|
+
if fields.any?
|
|
1071
|
+
raise ArgumentError, "Cron elements cannot be used with a cron expression" if expression
|
|
1072
|
+
|
|
1073
|
+
cron_expression = Cron.from_fields(fields)
|
|
1074
|
+
return cron(cron_expression, attach: attach)
|
|
1075
|
+
end
|
|
1076
|
+
|
|
1077
|
+
raise ArgumentError, "Missing cron expression or elements" unless expression
|
|
1078
|
+
|
|
1079
|
+
cron = Cron.new(rule_triggers: @rule_triggers)
|
|
1080
|
+
cron.trigger(config: { "cronExpression" => expression }, attach: attach)
|
|
1081
|
+
end
|
|
1082
|
+
|
|
1083
|
+
#
|
|
1084
|
+
# Create a rule that executes at the specified interval.
|
|
1085
|
+
#
|
|
1086
|
+
# @param [String,
|
|
1087
|
+
# Duration,
|
|
1088
|
+
# java.time.MonthDay,
|
|
1089
|
+
# :second,
|
|
1090
|
+
# :minute,
|
|
1091
|
+
# :hour,
|
|
1092
|
+
# :day,
|
|
1093
|
+
# :week,
|
|
1094
|
+
# :month,
|
|
1095
|
+
# :year,
|
|
1096
|
+
# :monday,
|
|
1097
|
+
# :tuesday,
|
|
1098
|
+
# :wednesday,
|
|
1099
|
+
# :thursday,
|
|
1100
|
+
# :friday,
|
|
1101
|
+
# :saturday,
|
|
1102
|
+
# :sunday] value
|
|
1103
|
+
# When to execute rule.
|
|
1104
|
+
# @param [LocalTime, String, Core::Items::DateTimeItem, nil] at What time of day to execute rule
|
|
1105
|
+
# If `value` is `:day`, `at` can be a {Core::Items::DateTimeItem DateTimeItem}, and
|
|
1106
|
+
# the trigger will run every day at the (time only portion of) current state of the
|
|
1107
|
+
# item. If the item is {NULL} or {UNDEF}, the trigger will not run.
|
|
1108
|
+
# @param [Object] attach Object to be attached to the trigger
|
|
1109
|
+
# @return [void]
|
|
1110
|
+
#
|
|
1111
|
+
# @example
|
|
1112
|
+
# rule "Daily" do
|
|
1113
|
+
# every :day, at: '5:15'
|
|
1114
|
+
# run do
|
|
1115
|
+
# Light.on
|
|
1116
|
+
# end
|
|
1117
|
+
# end
|
|
1118
|
+
#
|
|
1119
|
+
# @example The above rule could also be expressed using LocalTime class as below
|
|
1120
|
+
# rule "Daily" do
|
|
1121
|
+
# every :day, at: LocalTime.of(5, 15)
|
|
1122
|
+
# run { Light.on }
|
|
1123
|
+
# end
|
|
1124
|
+
#
|
|
1125
|
+
# @example
|
|
1126
|
+
# rule "Weekly" do
|
|
1127
|
+
# every :monday, at: '5:15'
|
|
1128
|
+
# run do
|
|
1129
|
+
# Light.on
|
|
1130
|
+
# end
|
|
1131
|
+
# end
|
|
1132
|
+
#
|
|
1133
|
+
# @example
|
|
1134
|
+
# rule "Often" do
|
|
1135
|
+
# every :minute
|
|
1136
|
+
# run do
|
|
1137
|
+
# Light.on
|
|
1138
|
+
# end
|
|
1139
|
+
# end
|
|
1140
|
+
#
|
|
1141
|
+
# @example
|
|
1142
|
+
# rule "Hourly" do
|
|
1143
|
+
# every :hour
|
|
1144
|
+
# run do
|
|
1145
|
+
# Light.on
|
|
1146
|
+
# end
|
|
1147
|
+
# end
|
|
1148
|
+
#
|
|
1149
|
+
# @example
|
|
1150
|
+
# rule "Often" do
|
|
1151
|
+
# every 5.minutes
|
|
1152
|
+
# run do
|
|
1153
|
+
# Light.on
|
|
1154
|
+
# end
|
|
1155
|
+
# end
|
|
1156
|
+
#
|
|
1157
|
+
# @example
|
|
1158
|
+
# rule 'Every 14th of Feb at 2pm' do
|
|
1159
|
+
# every '02-14', at: '2pm'
|
|
1160
|
+
# run { logger.info "Happy Valentine's Day!" }
|
|
1161
|
+
# end
|
|
1162
|
+
#
|
|
1163
|
+
# @example
|
|
1164
|
+
# rule "Every day at sunset" do
|
|
1165
|
+
# every :day, at: Sunset_Time
|
|
1166
|
+
# run { logger.info "It's getting dark" }
|
|
1167
|
+
# end
|
|
1168
|
+
#
|
|
1169
|
+
def every(value, at: nil, attach: nil)
|
|
1170
|
+
return every(java.time.MonthDay.parse(value), at: at, attach: attach) if value.is_a?(String)
|
|
1171
|
+
|
|
1172
|
+
@ruby_triggers << [:every, value, { at: at }]
|
|
1173
|
+
|
|
1174
|
+
if value == :day && at.is_a?(Item)
|
|
1175
|
+
raise ArgumentError, "Attachments are not supported with dynamic datetime triggers" unless attach.nil?
|
|
1176
|
+
|
|
1177
|
+
return trigger("timer.DateTimeTrigger", itemName: at.name, timeOnly: true)
|
|
1178
|
+
end
|
|
1179
|
+
|
|
1180
|
+
cron_expression = case value
|
|
1181
|
+
when Symbol then Cron.from_symbol(value, at)
|
|
1182
|
+
when Duration then Cron.from_duration(value, at)
|
|
1183
|
+
when java.time.MonthDay then Cron.from_monthday(value, at)
|
|
1184
|
+
else raise ArgumentError, "Unknown interval"
|
|
1185
|
+
end
|
|
1186
|
+
cron(cron_expression, attach: attach)
|
|
1187
|
+
end
|
|
1188
|
+
|
|
1189
|
+
#
|
|
1190
|
+
# Creates a trigger that executes when the script is loaded
|
|
1191
|
+
#
|
|
1192
|
+
# Execute the rule whenever the script is first loaded, including on openHAB start up,
|
|
1193
|
+
# and on subsequent reloads on file modifications.
|
|
1194
|
+
# This is useful to perform initialization routines, especially when combined with other triggers.
|
|
1195
|
+
#
|
|
1196
|
+
# @param [Duration] delay The amount of time to wait before executing the rule.
|
|
1197
|
+
# When nil, execute immediately.
|
|
1198
|
+
# @param [Object] attach Object to be attached to the trigger
|
|
1199
|
+
# @return [void]
|
|
1200
|
+
#
|
|
1201
|
+
# @example
|
|
1202
|
+
# rule "script startup rule" do
|
|
1203
|
+
# on_load
|
|
1204
|
+
# run do
|
|
1205
|
+
# <calculate some item state>
|
|
1206
|
+
# end
|
|
1207
|
+
# end
|
|
1208
|
+
#
|
|
1209
|
+
# @example
|
|
1210
|
+
# rule "Ensure all security lights are on" do
|
|
1211
|
+
# on_load
|
|
1212
|
+
# run { Security_Lights.on }
|
|
1213
|
+
# end
|
|
1214
|
+
#
|
|
1215
|
+
def on_load(delay: nil, attach: nil)
|
|
1216
|
+
# prevent overwriting @on_load
|
|
1217
|
+
raise ArgumentError, "on_load can only be used once within a rule" if @on_load
|
|
1218
|
+
|
|
1219
|
+
@on_load = { module: SecureRandom.uuid, delay: delay }
|
|
1220
|
+
attachments[@on_load[:module]] = attach
|
|
1221
|
+
end
|
|
1222
|
+
|
|
1223
|
+
#
|
|
1224
|
+
# Creates a trigger that executes when openHAB reaches a certain start level
|
|
1225
|
+
#
|
|
1226
|
+
# This will only trigger once during openHAB start up. It won't trigger on script reloads.
|
|
1227
|
+
#
|
|
1228
|
+
# @param [Integer,:rules,:ruleengine,:ui,:things,:complete] at_level
|
|
1229
|
+
# Zero or more start levels. Note that Startlevels less than 40 are not available as triggers
|
|
1230
|
+
# because the rule engine needs to start up first before it can execute any rules
|
|
1231
|
+
#
|
|
1232
|
+
# | Symbol | Start Level |
|
|
1233
|
+
# | ------------- | ----------- |
|
|
1234
|
+
# | `:osgi` | 10 |
|
|
1235
|
+
# | `:model` | 20 |
|
|
1236
|
+
# | `:state` | 30 |
|
|
1237
|
+
# | `:rules` | 40 |
|
|
1238
|
+
# | `:ruleengine` | 50 |
|
|
1239
|
+
# | `:ui` | 70 |
|
|
1240
|
+
# | `:things` | 80 |
|
|
1241
|
+
# | `:complete` | 100 |
|
|
1242
|
+
# @param [Array<Integer,:rules,:ruleengine,:ui,:things,:complete>] at_levels Fluent alias for `at_level`
|
|
1243
|
+
# @param [Object] attach Object to be attached to the trigger
|
|
1244
|
+
# @return [void]
|
|
1245
|
+
#
|
|
1246
|
+
# @example
|
|
1247
|
+
# rule "Trigger at openHAB system start" do
|
|
1248
|
+
# on_start # trigger at the default startlevel 100
|
|
1249
|
+
# run { logger.info "openHAB start up complete." }
|
|
1250
|
+
# end
|
|
1251
|
+
#
|
|
1252
|
+
# @example Trigger at a specific start level
|
|
1253
|
+
# rule "Trigger after things are loaded" do
|
|
1254
|
+
# on_start at_level: :things
|
|
1255
|
+
# run { logger.info "Things are ready!" }
|
|
1256
|
+
# end
|
|
1257
|
+
#
|
|
1258
|
+
# @example Trigger at multiple levels
|
|
1259
|
+
# rule "Multiple start up triggers" do
|
|
1260
|
+
# on_start at_levels: %i[ui things complete]
|
|
1261
|
+
# run do |event|
|
|
1262
|
+
# logger.info "openHAB startlevel has reached level #{event.startlevel}"
|
|
1263
|
+
# end
|
|
1264
|
+
# end
|
|
1265
|
+
#
|
|
1266
|
+
# @see https://www.openhab.org/docs/configuration/rules-dsl.html#system-based-triggers System based triggers
|
|
1267
|
+
#
|
|
1268
|
+
def on_start(at_level: nil, at_levels: nil, attach: nil)
|
|
1269
|
+
levels = Array.wrap(at_level) | Array.wrap(at_levels)
|
|
1270
|
+
levels = [100] if levels.empty?
|
|
1271
|
+
|
|
1272
|
+
levels.map! do |level|
|
|
1273
|
+
next level unless level.is_a?(Symbol)
|
|
1274
|
+
|
|
1275
|
+
begin
|
|
1276
|
+
klass = org.openhab.core.service.StartLevelService.java_class
|
|
1277
|
+
klass.declared_field("STARTLEVEL_#{level.upcase}").get_int(klass)
|
|
1278
|
+
rescue java.lang.NoSuchFieldException
|
|
1279
|
+
raise ArgumentError, "Invalid symbol for at_level: :#{level}"
|
|
1280
|
+
end
|
|
1281
|
+
end
|
|
1282
|
+
|
|
1283
|
+
@ruby_triggers << [:on_start, levels]
|
|
1284
|
+
levels.each do |level|
|
|
1285
|
+
logger.warn "Rule engine doesn't start until start level 40" if level < 40
|
|
1286
|
+
config = { startlevel: level }
|
|
1287
|
+
logger.trace("Creating a SystemStartlevelTrigger with startlevel=#{level}")
|
|
1288
|
+
Triggers::Trigger.new(rule_triggers: @rule_triggers)
|
|
1289
|
+
.append_trigger(type: "core.SystemStartlevelTrigger", config: config, attach: attach)
|
|
1290
|
+
end
|
|
1291
|
+
end
|
|
1292
|
+
|
|
1293
|
+
#
|
|
1294
|
+
# Creates a trigger for when an item or group receives a command.
|
|
1295
|
+
#
|
|
1296
|
+
# The command/commands parameters are replicated for DSL fluency.
|
|
1297
|
+
#
|
|
1298
|
+
# The `event` passed to run blocks will be an
|
|
1299
|
+
# {Core::Events::ItemCommandEvent}.
|
|
1300
|
+
#
|
|
1301
|
+
# @param [Item, GroupItem::Members] items Items to create trigger for
|
|
1302
|
+
# @param [Core::TypesCommand, Array<Command>, Range, Proc] command commands to match for trigger
|
|
1303
|
+
# @param [Array<Command>, Range, Proc] commands Fluent alias for `command`
|
|
1304
|
+
# @param [Object] attach object to be attached to the trigger
|
|
1305
|
+
# @return [void]
|
|
1306
|
+
#
|
|
1307
|
+
# @example
|
|
1308
|
+
# rule 'Execute rule when item received command' do
|
|
1309
|
+
# received_command Alarm_Mode
|
|
1310
|
+
# run { |event| logger.info("Item received command: #{event.command}" ) }
|
|
1311
|
+
# end
|
|
1312
|
+
#
|
|
1313
|
+
# @example
|
|
1314
|
+
# rule 'Execute rule when item receives specific command' do
|
|
1315
|
+
# received_command Alarm_Mode, command: 7
|
|
1316
|
+
# run { |event| logger.info("Item received command: #{event.command}" ) }
|
|
1317
|
+
# end
|
|
1318
|
+
#
|
|
1319
|
+
# @example
|
|
1320
|
+
# rule 'Execute rule when item receives one of many specific commands' do
|
|
1321
|
+
# received_command Alarm_Mode, commands: [7,14]
|
|
1322
|
+
# run { |event| logger.info("Item received command: #{event.command}" ) }
|
|
1323
|
+
# end
|
|
1324
|
+
#
|
|
1325
|
+
# @example
|
|
1326
|
+
# rule 'Execute rule when group receives a specific command' do
|
|
1327
|
+
# received_command AlarmModes
|
|
1328
|
+
# triggered { |item| logger.info("Group #{item.name} received command")}
|
|
1329
|
+
# end
|
|
1330
|
+
#
|
|
1331
|
+
# @example
|
|
1332
|
+
# rule 'Execute rule when member of group receives any command' do
|
|
1333
|
+
# received_command AlarmModes.members
|
|
1334
|
+
# triggered { |item| logger.info("Group item #{item.name} received command")}
|
|
1335
|
+
# end
|
|
1336
|
+
#
|
|
1337
|
+
# @example
|
|
1338
|
+
# rule 'Execute rule when member of group is changed to one of many states' do
|
|
1339
|
+
# received_command AlarmModes.members, commands: [7, 14]
|
|
1340
|
+
# triggered { |item| logger.info("Group item #{item.name} received command")}
|
|
1341
|
+
# end
|
|
1342
|
+
#
|
|
1343
|
+
# @example
|
|
1344
|
+
# rule 'Execute rule when item receives a range of commands' do
|
|
1345
|
+
# received_command Alarm_Mode, commands: 7..14
|
|
1346
|
+
# run { |event| logger.info("Item received command: #{event.command}" ) }
|
|
1347
|
+
# end
|
|
1348
|
+
#
|
|
1349
|
+
# @example Works with procs
|
|
1350
|
+
# rule 'Execute rule when Alarm Mode command is odd' do
|
|
1351
|
+
# received_command Alarm_Mode, command: proc { |c| c.odd? }
|
|
1352
|
+
# run { |event| logger.info("Item received command: #{event.command}" ) }
|
|
1353
|
+
# end
|
|
1354
|
+
#
|
|
1355
|
+
# @example Works with lambdas
|
|
1356
|
+
# rule 'Execute rule when Alarm Mode command is odd' do
|
|
1357
|
+
# received_command Alarm_Mode, command: -> c { c.odd? }
|
|
1358
|
+
# run { |event| logger.info("Item received command: #{event.command}" ) }
|
|
1359
|
+
# end
|
|
1360
|
+
#
|
|
1361
|
+
def received_command(*items, command: nil, commands: nil, attach: nil)
|
|
1362
|
+
command_trigger = Command.new(rule_triggers: @rule_triggers)
|
|
1363
|
+
|
|
1364
|
+
# if neither command nor commands is specified, ensure that we create
|
|
1365
|
+
# a trigger that isn't looking for a specific command.
|
|
1366
|
+
commands = [nil] if command.nil? && commands.nil?
|
|
1367
|
+
commands = Array.wrap(command) | Array.wrap(commands)
|
|
1368
|
+
|
|
1369
|
+
@ruby_triggers << [:received_command, items, { command: commands }]
|
|
1370
|
+
|
|
1371
|
+
items.each do |item|
|
|
1372
|
+
case item
|
|
1373
|
+
when Core::Items::Item,
|
|
1374
|
+
Core::Items::GroupItem::Members
|
|
1375
|
+
nil
|
|
1376
|
+
else
|
|
1377
|
+
raise ArgumentError, "items must be an Item or GroupItem::Members"
|
|
1378
|
+
end
|
|
1379
|
+
commands.each do |cmd|
|
|
1380
|
+
logger.trace "Creating received command trigger for items #{item.inspect} and commands #{cmd.inspect}"
|
|
1381
|
+
|
|
1382
|
+
command_trigger.trigger(item: item, command: cmd, attach: attach)
|
|
1383
|
+
end
|
|
1384
|
+
end
|
|
1385
|
+
end
|
|
1386
|
+
|
|
1387
|
+
#
|
|
1388
|
+
# Creates an item added trigger
|
|
1389
|
+
#
|
|
1390
|
+
# @param [Object] attach object to be attached to the trigger
|
|
1391
|
+
# @return [void]
|
|
1392
|
+
#
|
|
1393
|
+
# @example
|
|
1394
|
+
# rule "item added" do
|
|
1395
|
+
# item_added
|
|
1396
|
+
# run do |event|
|
|
1397
|
+
# logger.info("#{event.item.name} added.")
|
|
1398
|
+
# end
|
|
1399
|
+
# end
|
|
1400
|
+
def item_added(attach: nil)
|
|
1401
|
+
@ruby_triggers << [:item_added]
|
|
1402
|
+
event("openhab/items/*/added", types: "ItemAddedEvent", attach: attach)
|
|
1403
|
+
end
|
|
1404
|
+
|
|
1405
|
+
#
|
|
1406
|
+
# Creates an item removed trigger
|
|
1407
|
+
#
|
|
1408
|
+
# @param [Object] attach object to be attached to the trigger
|
|
1409
|
+
# @return [void]
|
|
1410
|
+
#
|
|
1411
|
+
# @example
|
|
1412
|
+
# rule "item removed" do
|
|
1413
|
+
# item_removed
|
|
1414
|
+
# run do |event|
|
|
1415
|
+
# logger.info("#{event.item.name} removed.")
|
|
1416
|
+
# end
|
|
1417
|
+
# end
|
|
1418
|
+
def item_removed(attach: nil)
|
|
1419
|
+
@ruby_triggers << [:item_removed]
|
|
1420
|
+
event("openhab/items/*/removed", types: "ItemRemovedEvent", attach: attach)
|
|
1421
|
+
end
|
|
1422
|
+
|
|
1423
|
+
#
|
|
1424
|
+
# Creates an item updated trigger
|
|
1425
|
+
#
|
|
1426
|
+
# @param [Object] attach object to be attached to the trigger
|
|
1427
|
+
# @return [void]
|
|
1428
|
+
#
|
|
1429
|
+
# @example
|
|
1430
|
+
# rule "item updated" do
|
|
1431
|
+
# item_updated
|
|
1432
|
+
# run do |event|
|
|
1433
|
+
# logger.info("#{event.item.name} updated.")
|
|
1434
|
+
# end
|
|
1435
|
+
# end
|
|
1436
|
+
#
|
|
1437
|
+
def item_updated(attach: nil)
|
|
1438
|
+
@ruby_triggers << [:item_updated]
|
|
1439
|
+
event("openhab/items/*/updated", types: "ItemUpdatedEvent", attach: attach)
|
|
1440
|
+
end
|
|
1441
|
+
|
|
1442
|
+
#
|
|
1443
|
+
# Creates a thing added trigger
|
|
1444
|
+
#
|
|
1445
|
+
# @param [Object] attach object to be attached to the trigger
|
|
1446
|
+
# @return [void]
|
|
1447
|
+
#
|
|
1448
|
+
# @example
|
|
1449
|
+
# rule "thing added" do
|
|
1450
|
+
# thing_added
|
|
1451
|
+
# run do |event|
|
|
1452
|
+
# logger.info("#{event.thing.uid} added.")
|
|
1453
|
+
# end
|
|
1454
|
+
# end
|
|
1455
|
+
def thing_added(attach: nil)
|
|
1456
|
+
@ruby_triggers << [:thing_added]
|
|
1457
|
+
event("openhab/things/*/added", types: "ThingAddedEvent", attach: attach)
|
|
1458
|
+
end
|
|
1459
|
+
|
|
1460
|
+
#
|
|
1461
|
+
# Creates a thing removed trigger
|
|
1462
|
+
#
|
|
1463
|
+
# @param [Object] attach object to be attached to the trigger
|
|
1464
|
+
# @return [void]
|
|
1465
|
+
#
|
|
1466
|
+
# @example
|
|
1467
|
+
# rule "thing removed" do
|
|
1468
|
+
# thing_removed
|
|
1469
|
+
# run do |event|
|
|
1470
|
+
# logger.info("#{event.thing.uid} removed.")
|
|
1471
|
+
# end
|
|
1472
|
+
# end
|
|
1473
|
+
def thing_removed(attach: nil)
|
|
1474
|
+
@ruby_triggers << [:thing_removed]
|
|
1475
|
+
event("openhab/things/*/removed", types: "ThingRemovedEvent", attach: attach)
|
|
1476
|
+
end
|
|
1477
|
+
|
|
1478
|
+
#
|
|
1479
|
+
# Creates a thing updated trigger
|
|
1480
|
+
#
|
|
1481
|
+
# @param [Object] attach object to be attached to the trigger
|
|
1482
|
+
# @return [void]
|
|
1483
|
+
#
|
|
1484
|
+
# @example
|
|
1485
|
+
# rule "thing updated" do
|
|
1486
|
+
# thing_updated
|
|
1487
|
+
# run do |event|
|
|
1488
|
+
# logger.info("#{event.thing.uid} updated.")
|
|
1489
|
+
# end
|
|
1490
|
+
# end
|
|
1491
|
+
#
|
|
1492
|
+
def thing_updated(attach: nil)
|
|
1493
|
+
@ruby_triggers << [:thing_updated]
|
|
1494
|
+
event("openhab/things/*/updated", types: "ThingUpdatedEvent", attach: attach)
|
|
1495
|
+
end
|
|
1496
|
+
|
|
1497
|
+
#
|
|
1498
|
+
# Creates a trigger on events coming through the event bus
|
|
1499
|
+
#
|
|
1500
|
+
# @param [String] topic The topic to trigger on; can contain the wildcard `*`.
|
|
1501
|
+
# @param [String, nil] source The sender of the event to trigger on.
|
|
1502
|
+
# Default does not filter on source.
|
|
1503
|
+
# @param [String, Array<String>, nil] types Only subscribe to certain event types.
|
|
1504
|
+
# Default does not filter on event types.
|
|
1505
|
+
# @return [void]
|
|
1506
|
+
#
|
|
1507
|
+
# @example
|
|
1508
|
+
# rule "thing updated" do
|
|
1509
|
+
# event("openhab/things/*/updated", types: "ThingUpdatedEvent")
|
|
1510
|
+
# run do |event|
|
|
1511
|
+
# logger.info("#{event.thing.uid} updated")
|
|
1512
|
+
# end
|
|
1513
|
+
# end
|
|
1514
|
+
#
|
|
1515
|
+
def event(topic, source: nil, types: nil, attach: nil)
|
|
1516
|
+
types = types.join(",") if types.is_a?(Enumerable)
|
|
1517
|
+
# @deprecated OH3.4 - OH3 config uses eventXXX vs OH4 uses `topic`, `source`, and `types`
|
|
1518
|
+
# See https://github.com/openhab/openhab-core/pull/3299
|
|
1519
|
+
trigger("core.GenericEventTrigger",
|
|
1520
|
+
eventTopic: topic, eventSource: source, eventTypes: types, # @deprecated OH3.4
|
|
1521
|
+
topic: topic, source: source, types: types,
|
|
1522
|
+
attach: attach)
|
|
1523
|
+
end
|
|
1524
|
+
|
|
1525
|
+
#
|
|
1526
|
+
# Creates a trigger based on the time stored in a {DateTimeItem}
|
|
1527
|
+
#
|
|
1528
|
+
# The trigger will dynamically update any time the state of the item
|
|
1529
|
+
# changes. If the item is {NULL} or {UNDEF}, the trigger will not run.
|
|
1530
|
+
#
|
|
1531
|
+
# @param [Item, String, Symbol] item The item (or it's name)
|
|
1532
|
+
# @return [void]
|
|
1533
|
+
#
|
|
1534
|
+
# @example
|
|
1535
|
+
# rule "say hello when the kids get home from school" do
|
|
1536
|
+
# at HomeFromSchool_Time
|
|
1537
|
+
# run do
|
|
1538
|
+
# KitchenEcho_TTS << "hi kids! how was school?"
|
|
1539
|
+
# end
|
|
1540
|
+
# end
|
|
1541
|
+
#
|
|
1542
|
+
# rule "set home from school time" do
|
|
1543
|
+
# on_load
|
|
1544
|
+
# every :day, at: "5:00am" do
|
|
1545
|
+
# run do
|
|
1546
|
+
# HomeFromSchool_Time.ensure.update(school_day? ? LocalTime.parse("3:30pm") : NULL)
|
|
1547
|
+
# end
|
|
1548
|
+
# end
|
|
1549
|
+
#
|
|
1550
|
+
def at(item)
|
|
1551
|
+
item = item.name if item.is_a?(Item)
|
|
1552
|
+
trigger("timer.DateTimeTrigger", itemName: item.to_s)
|
|
1553
|
+
end
|
|
1554
|
+
|
|
1555
|
+
#
|
|
1556
|
+
# Create a generic trigger given the trigger type uid and a configuration hash
|
|
1557
|
+
#
|
|
1558
|
+
# This provides the ability to create a trigger type not already covered by the other methods.
|
|
1559
|
+
#
|
|
1560
|
+
# @param [String] type Trigger type UID
|
|
1561
|
+
# @param [Object] attach object to be attached to the trigger
|
|
1562
|
+
# @param [Hash] configuration A hash containing the trigger configuration entries
|
|
1563
|
+
# @return [void]
|
|
1564
|
+
#
|
|
1565
|
+
# @example Create a trigger for the [PID Controller Automation](https://www.openhab.org/addons/automation/pidcontroller/) add-on.
|
|
1566
|
+
# rule 'PID Control' do
|
|
1567
|
+
# trigger 'pidcontroller.trigger',
|
|
1568
|
+
# input: InputItem.name,
|
|
1569
|
+
# setpoint: SetPointItem.name,
|
|
1570
|
+
# kp: 10,
|
|
1571
|
+
# ki: 10,
|
|
1572
|
+
# kd: 10,
|
|
1573
|
+
# kdTimeConstant: 1,
|
|
1574
|
+
# loopTime: 1000
|
|
1575
|
+
#
|
|
1576
|
+
# run do |event|
|
|
1577
|
+
# logger.info("PID controller command: #{event.command}")
|
|
1578
|
+
# ControlItem << event.command
|
|
1579
|
+
# end
|
|
1580
|
+
# end
|
|
1581
|
+
#
|
|
1582
|
+
# @example DateTime Trigger
|
|
1583
|
+
# rule 'DateTime Trigger' do
|
|
1584
|
+
# description 'Triggers at a time specified in MyDateTimeItem'
|
|
1585
|
+
# trigger 'timer.DateTimeTrigger', itemName: MyDateTimeItem.name
|
|
1586
|
+
# run do
|
|
1587
|
+
# logger.info("DateTimeTrigger has been triggered")
|
|
1588
|
+
# end
|
|
1589
|
+
# end
|
|
1590
|
+
#
|
|
1591
|
+
def trigger(type, attach: nil, **configuration)
|
|
1592
|
+
logger.trace("Creating trigger (#{type}) with configuration(#{configuration})")
|
|
1593
|
+
Triggers::Trigger.new(rule_triggers: @rule_triggers)
|
|
1594
|
+
.append_trigger(type: type, config: configuration, attach: attach)
|
|
1595
|
+
end
|
|
1596
|
+
|
|
1597
|
+
#
|
|
1598
|
+
# Create a trigger when item, group or thing is updated
|
|
1599
|
+
#
|
|
1600
|
+
# The `event` passed to run blocks will be an
|
|
1601
|
+
# {Core::Events::ItemStateEvent} or a
|
|
1602
|
+
# {Core::Events::ThingStatusInfoEvent} depending on if the triggering
|
|
1603
|
+
# element was an item or a thing.
|
|
1604
|
+
#
|
|
1605
|
+
# @param [Item, GroupItem::Members, Thing] items
|
|
1606
|
+
# Objects to create trigger for.
|
|
1607
|
+
# @param [State, Array<State>, Range, Proc, Symbol, String] to
|
|
1608
|
+
# Only execute rule if the state matches `to` state(s). If the
|
|
1609
|
+
# updated element is a {Core::Things::Thing}, the `to` accepts
|
|
1610
|
+
# symbols and strings that match
|
|
1611
|
+
# [supported thing statuses](https://www.openhab.org/docs/concepts/things.html#thing-status).
|
|
1612
|
+
# @param [Object] attach object to be attached to the trigger
|
|
1613
|
+
# @return [void]
|
|
1614
|
+
#
|
|
1615
|
+
# @example
|
|
1616
|
+
# rule 'Execute rule when item is updated to any value' do
|
|
1617
|
+
# updated Alarm_Mode
|
|
1618
|
+
# run { logger.info("Alarm Mode Updated") }
|
|
1619
|
+
# end
|
|
1620
|
+
#
|
|
1621
|
+
# @example
|
|
1622
|
+
# rule 'Execute rule when item is updated to specific number' do
|
|
1623
|
+
# updated Alarm_Mode, to: 7
|
|
1624
|
+
# run { logger.info("Alarm Mode Updated") }
|
|
1625
|
+
# end
|
|
1626
|
+
#
|
|
1627
|
+
# @example
|
|
1628
|
+
# rule 'Execute rule when item is updated to one of many specific states' do
|
|
1629
|
+
# updated Alarm_Mode, to: [7, 14]
|
|
1630
|
+
# run { logger.info("Alarm Mode Updated")}
|
|
1631
|
+
# end
|
|
1632
|
+
#
|
|
1633
|
+
# @example
|
|
1634
|
+
# rule 'Execute rule when item is within a range' do
|
|
1635
|
+
# updated Alarm_Mode, to: 7..14
|
|
1636
|
+
# run { logger.info("Alarm Mode Updated to a value between 7 and 14")}
|
|
1637
|
+
# end
|
|
1638
|
+
#
|
|
1639
|
+
# @example
|
|
1640
|
+
# rule 'Execute rule when group is updated to any state' do
|
|
1641
|
+
# updated AlarmModes
|
|
1642
|
+
# triggered { |item| logger.info("Group #{item.name} updated")}
|
|
1643
|
+
# end
|
|
1644
|
+
#
|
|
1645
|
+
# @example
|
|
1646
|
+
# rule 'Execute rule when member of group is changed to any state' do
|
|
1647
|
+
# updated AlarmModes.members
|
|
1648
|
+
# triggered { |item| logger.info("Group item #{item.name} updated")}
|
|
1649
|
+
# end
|
|
1650
|
+
#
|
|
1651
|
+
# @example
|
|
1652
|
+
# rule 'Execute rule when member of group is changed to one of many states' do
|
|
1653
|
+
# updated AlarmModes.members, to: [7, 14]
|
|
1654
|
+
# triggered { |item| logger.info("Group item #{item.name} updated")}
|
|
1655
|
+
# end
|
|
1656
|
+
#
|
|
1657
|
+
# @example Works with procs
|
|
1658
|
+
# rule 'Execute rule when member of group is changed to an odd state' do
|
|
1659
|
+
# updated AlarmModes.members, to: proc { |t| t.odd? }
|
|
1660
|
+
# triggered { |item| logger.info("Group item #{item.name} updated")}
|
|
1661
|
+
# end
|
|
1662
|
+
#
|
|
1663
|
+
# @example Works with lambdas:
|
|
1664
|
+
# rule 'Execute rule when member of group is changed to an odd state' do
|
|
1665
|
+
# updated AlarmModes.members, to: -> t { t.odd? }
|
|
1666
|
+
# triggered { |item| logger.info("Group item #{item.name} updated")}
|
|
1667
|
+
# end
|
|
1668
|
+
#
|
|
1669
|
+
# @example Works with things as well
|
|
1670
|
+
# rule 'Execute rule when thing is updated' do
|
|
1671
|
+
# updated things['astro:sun:home'], :to => :uninitialized
|
|
1672
|
+
# run { |event| logger.info("Thing #{event.uid} status <trigger> to #{event.status}") }
|
|
1673
|
+
# end
|
|
1674
|
+
#
|
|
1675
|
+
def updated(*items, to: nil, attach: nil)
|
|
1676
|
+
updated = Updated.new(rule_triggers: @rule_triggers)
|
|
1677
|
+
@ruby_triggers << [:updated, items, { to: to }]
|
|
1678
|
+
items.map do |item|
|
|
1679
|
+
case item
|
|
1680
|
+
when Core::Things::Thing,
|
|
1681
|
+
Core::Things::ThingUID,
|
|
1682
|
+
Core::Items::Item,
|
|
1683
|
+
Core::Items::GroupItem::Members
|
|
1684
|
+
nil
|
|
1685
|
+
else
|
|
1686
|
+
raise ArgumentError, "items must be an Item, GroupItem::Members, Thing, or ThingUID"
|
|
1687
|
+
end
|
|
1688
|
+
|
|
1689
|
+
logger.trace("Creating updated trigger for item(#{item}) to(#{to})")
|
|
1690
|
+
[to].flatten.map do |to_state|
|
|
1691
|
+
updated.trigger(item: item, to: to_state, attach: attach)
|
|
1692
|
+
end
|
|
1693
|
+
end.flatten
|
|
1694
|
+
end
|
|
1695
|
+
|
|
1696
|
+
#
|
|
1697
|
+
# Create a trigger to watch a path
|
|
1698
|
+
#
|
|
1699
|
+
# It provides the ability to create a trigger on file and directory
|
|
1700
|
+
# changes.
|
|
1701
|
+
#
|
|
1702
|
+
# If a file or a path that does not exist is supplied as the argument
|
|
1703
|
+
# to watch, the parent directory will be watched and the file or
|
|
1704
|
+
# non-existent part of the supplied path will become the glob. For
|
|
1705
|
+
# example, if the path given is `/tmp/foo/bar` and `/tmp/foo`
|
|
1706
|
+
# exists but `bar` does not exist inside of of `/tmp/foo` then the
|
|
1707
|
+
# directory `/tmp/foo` will be watched for any files that match
|
|
1708
|
+
# `*/bar`.
|
|
1709
|
+
#
|
|
1710
|
+
# If the last part of the path contains any glob characters e.g.
|
|
1711
|
+
# `/tmp/foo/*bar`, the parent directory will be watched and the last
|
|
1712
|
+
# part of the path will be treated as if it was passed as the `glob`
|
|
1713
|
+
# argument. In other words, `watch '/tmp/foo/*bar'` is equivalent to
|
|
1714
|
+
# `watch '/tmp/foo', glob: '*bar'`
|
|
1715
|
+
#
|
|
1716
|
+
# ### Watching inside subdirectories
|
|
1717
|
+
#
|
|
1718
|
+
# Subdirectories aren't watched unless:
|
|
1719
|
+
# - One of the glob patterns include the recursive match pattern `**`, or
|
|
1720
|
+
# - The glob patterns include subdirectories, see examples below.
|
|
1721
|
+
#
|
|
1722
|
+
# The `event` passed to run blocks will be a {Events::WatchEvent}.
|
|
1723
|
+
#
|
|
1724
|
+
# @param [String] path Path to watch. Can be a directory or a file.
|
|
1725
|
+
# @param [String] glob
|
|
1726
|
+
# Limit events to paths matching this glob. Globs are matched using `glob`
|
|
1727
|
+
# [PathMatcher](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileSystem.html#getPathMatcher(java.lang.String))
|
|
1728
|
+
# rules.
|
|
1729
|
+
# @param [Array<:created, :deleted, :modified>, :created, :deleted, :modified] for
|
|
1730
|
+
# Types of changes to watch for.
|
|
1731
|
+
# @param [Object] attach object to be attached to the trigger
|
|
1732
|
+
# @return [void]
|
|
1733
|
+
#
|
|
1734
|
+
# @example Watch `items` directory inside of the openHAB configuration path and log any changes.
|
|
1735
|
+
# rule 'watch directory' do
|
|
1736
|
+
# watch OpenHAB::Core.config_folder / 'items'
|
|
1737
|
+
# run { |event| logger.info("#{event.path.basename} - #{event.type}") }
|
|
1738
|
+
# end
|
|
1739
|
+
#
|
|
1740
|
+
# @example Watch `items` directory for files that end in `*.erb` and log any changes
|
|
1741
|
+
# rule 'watch directory' do
|
|
1742
|
+
# watch OpenHAB::Core.config_folder / 'items', glob: '*.erb'
|
|
1743
|
+
# run { |event| logger.info("#{event.path.basename} - #{event.type}") }
|
|
1744
|
+
# end
|
|
1745
|
+
#
|
|
1746
|
+
# @example Watch `items/foo.items` log any changes
|
|
1747
|
+
# rule 'watch directory' do
|
|
1748
|
+
# watch OpenHAB::Core.config_folder / 'items/foo.items'
|
|
1749
|
+
# run { |event| logger.info("#{event.path.basename} - #{event.type}") }
|
|
1750
|
+
# end
|
|
1751
|
+
#
|
|
1752
|
+
# @example Watch `items/*.items` log any changes
|
|
1753
|
+
# rule 'watch directory' do
|
|
1754
|
+
# watch OpenHAB::Core.config_folder / 'items/*.items'
|
|
1755
|
+
# run { |event| logger.info("#{event.path.basename} - #{event.type}") }
|
|
1756
|
+
# end
|
|
1757
|
+
#
|
|
1758
|
+
# @example Watch `items/*.items` for when items files are deleted or created (ignore changes)
|
|
1759
|
+
# rule 'watch directory' do
|
|
1760
|
+
# watch OpenHAB::Core.config_folder / 'items/*.items', for: [:deleted, :created]
|
|
1761
|
+
# run { |event| logger.info("#{event.path.basename} - #{event.type}") }
|
|
1762
|
+
# end
|
|
1763
|
+
#
|
|
1764
|
+
# @example Watch for changes inside all subdirs of `config_folder/automation/ruby/lib`
|
|
1765
|
+
# rule "Watch recursively" do
|
|
1766
|
+
# watch OpenHAB::Core.config_folder / "automation/ruby/lib/**"
|
|
1767
|
+
# run { |event| logger.info("#{event.path} - #{event.type}") }
|
|
1768
|
+
# end
|
|
1769
|
+
#
|
|
1770
|
+
# @example Recursively watch using subdirectory in glob
|
|
1771
|
+
# rule "Monitor changes in the list of installed gems" do
|
|
1772
|
+
# watch ENV['GEM_HOME'], glob: "gems/*"
|
|
1773
|
+
# run { |event| logger.info("#{event.path} - #{event.type}") }
|
|
1774
|
+
# end
|
|
1775
|
+
#
|
|
1776
|
+
# @example Recursively watch using glob option
|
|
1777
|
+
# rule "Watch recursively" do
|
|
1778
|
+
# watch OpenHAB::Core.config_folder / "automation/ruby/lib", glob: "**"
|
|
1779
|
+
# run { |event| logger.info("#{event.path} - #{event.type}") }
|
|
1780
|
+
# end
|
|
1781
|
+
#
|
|
1782
|
+
def watch(path, glob: "*", for: %i[created deleted modified], attach: nil)
|
|
1783
|
+
types = [binding.local_variable_get(:for)].flatten
|
|
1784
|
+
|
|
1785
|
+
WatchHandler::WatchTriggerHandlerFactory.instance # ensure it's registered
|
|
1786
|
+
trigger(WatchHandler::WATCH_TRIGGER_MODULE_ID, path: path.to_s,
|
|
1787
|
+
types: types.map(&:to_s),
|
|
1788
|
+
glob: glob.to_s,
|
|
1789
|
+
attach: attach)
|
|
1790
|
+
end
|
|
1791
|
+
|
|
1792
|
+
# @!endgroup
|
|
1793
|
+
|
|
1794
|
+
#
|
|
1795
|
+
# @return [String]
|
|
1796
|
+
#
|
|
1797
|
+
def inspect
|
|
1798
|
+
<<~TEXT.tr("\n", " ")
|
|
1799
|
+
#<OpenHAB::DSL::Rules::Builder: #{uid}
|
|
1800
|
+
triggers=#{triggers.inspect},
|
|
1801
|
+
run blocks=#{run.inspect},
|
|
1802
|
+
on_load=#{!@on_load.nil?},
|
|
1803
|
+
Trigger Conditions=#{trigger_conditions.inspect},
|
|
1804
|
+
Trigger UIDs=#{triggers.map(&:id).inspect},
|
|
1805
|
+
Attachments=#{attachments.inspect}
|
|
1806
|
+
>
|
|
1807
|
+
TEXT
|
|
1808
|
+
end
|
|
1809
|
+
|
|
1810
|
+
#
|
|
1811
|
+
# Process a rule based on the supplied configuration
|
|
1812
|
+
#
|
|
1813
|
+
# @param [String] script The source code of the rule
|
|
1814
|
+
#
|
|
1815
|
+
# @!visibility private
|
|
1816
|
+
def build(provider, script)
|
|
1817
|
+
return unless create_rule?
|
|
1818
|
+
|
|
1819
|
+
rule = AutomationRule.new(self)
|
|
1820
|
+
added_rule = add_rule(provider, rule)
|
|
1821
|
+
# add config so that MainUI can show the script
|
|
1822
|
+
added_rule.actions.first.configuration.put("type", "application/x-ruby")
|
|
1823
|
+
added_rule.actions.first.configuration.put("script", script) if script
|
|
1824
|
+
|
|
1825
|
+
process_on_load { |module_id| rule.execute(nil, { "module" => module_id }) }
|
|
1826
|
+
|
|
1827
|
+
added_rule
|
|
1828
|
+
end
|
|
1829
|
+
|
|
1830
|
+
private
|
|
1831
|
+
|
|
1832
|
+
# Calls the on_load block, with a delay if specified
|
|
1833
|
+
# @yield block to execute on load time
|
|
1834
|
+
# @yieldparam [String] module The module ID that identifies this on_load event
|
|
1835
|
+
def process_on_load
|
|
1836
|
+
return unless @on_load
|
|
1837
|
+
|
|
1838
|
+
if @on_load[:delay]
|
|
1839
|
+
after(@on_load[:delay]) { yield @on_load[:module] }
|
|
1840
|
+
else
|
|
1841
|
+
yield @on_load[:module]
|
|
1842
|
+
end
|
|
1843
|
+
end
|
|
1844
|
+
|
|
1845
|
+
# delegate to the caller's logger
|
|
1846
|
+
def logger
|
|
1847
|
+
@caller.send(:logger)
|
|
1848
|
+
end
|
|
1849
|
+
|
|
1850
|
+
#
|
|
1851
|
+
# Should a rule be created based on rule configuration
|
|
1852
|
+
#
|
|
1853
|
+
# @return [true,false] true if it should be created, false otherwise
|
|
1854
|
+
#
|
|
1855
|
+
def create_rule?
|
|
1856
|
+
return true if tags.include?("Script")
|
|
1857
|
+
|
|
1858
|
+
if !triggers?
|
|
1859
|
+
logger.warn "Rule '#{uid}' has no triggers, not creating rule"
|
|
1860
|
+
elsif !execution_blocks?
|
|
1861
|
+
logger.warn "Rule '#{uid}' has no execution blocks, not creating rule"
|
|
1862
|
+
elsif !enabled
|
|
1863
|
+
logger.trace "Rule '#{uid}' marked as disabled, not creating rule."
|
|
1864
|
+
else
|
|
1865
|
+
return true
|
|
1866
|
+
end
|
|
1867
|
+
false
|
|
1868
|
+
end
|
|
1869
|
+
|
|
1870
|
+
#
|
|
1871
|
+
# Check if the rule has any triggers
|
|
1872
|
+
#
|
|
1873
|
+
# @return [true,false] True if rule has triggers, false otherwise
|
|
1874
|
+
#
|
|
1875
|
+
def triggers?
|
|
1876
|
+
!(@on_load.nil? && triggers.empty?)
|
|
1877
|
+
end
|
|
1878
|
+
|
|
1879
|
+
#
|
|
1880
|
+
# Check if the rule has any execution blocks
|
|
1881
|
+
#
|
|
1882
|
+
# @return [true,false] True if rule has execution blocks, false otherwise
|
|
1883
|
+
#
|
|
1884
|
+
def execution_blocks?
|
|
1885
|
+
!(run || []).empty?
|
|
1886
|
+
end
|
|
1887
|
+
|
|
1888
|
+
#
|
|
1889
|
+
# Add a rule to the automation manager
|
|
1890
|
+
#
|
|
1891
|
+
# @param [org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule] rule to add
|
|
1892
|
+
#
|
|
1893
|
+
def add_rule(provider, rule)
|
|
1894
|
+
base_uid = rule.uid
|
|
1895
|
+
duplicate_index = 1
|
|
1896
|
+
while $rules.get(rule.uid)
|
|
1897
|
+
duplicate_index += 1
|
|
1898
|
+
rule.uid = "#{base_uid} (#{duplicate_index})"
|
|
1899
|
+
end
|
|
1900
|
+
logger.trace("Adding rule: #{rule}")
|
|
1901
|
+
unmanaged_rule = Core.automation_manager.add_unmanaged_rule(rule)
|
|
1902
|
+
provider.add(unmanaged_rule)
|
|
1903
|
+
unmanaged_rule
|
|
1904
|
+
end
|
|
1905
|
+
|
|
1906
|
+
#
|
|
1907
|
+
# Prevents or delays executions of rules to within a specified interval.
|
|
1908
|
+
# Debounce handling is done after the from/to/command types are filtered, but before only_if/not_if
|
|
1909
|
+
# guards.
|
|
1910
|
+
#
|
|
1911
|
+
# For a more detailed timing diagram, see {Debouncer}.
|
|
1912
|
+
#
|
|
1913
|
+
# Note the trailing edge debouncer delays the triggers so that they were postponed for the given interval after
|
|
1914
|
+
# the first detection of the trigger.
|
|
1915
|
+
#
|
|
1916
|
+
# @param (see Debouncer#initialize)
|
|
1917
|
+
#
|
|
1918
|
+
# @return [void]
|
|
1919
|
+
#
|
|
1920
|
+
# @see Debouncer Debouncer class
|
|
1921
|
+
# @see OpenHAB::DSL.debounce DSL.debounce method
|
|
1922
|
+
# @see only_every
|
|
1923
|
+
#
|
|
1924
|
+
# @!visibility private
|
|
1925
|
+
def debounce(for: 1.second, leading: false, idle_time: nil)
|
|
1926
|
+
raise ArgumentError, "Debounce guard can only be specified once" if @debounce_settings
|
|
1927
|
+
|
|
1928
|
+
interval = binding.local_variable_get(:for)
|
|
1929
|
+
# This hash structure must match the parameter signature for Debouncer.new
|
|
1930
|
+
@debounce_settings = { for: interval, leading: leading, idle_time: idle_time }
|
|
1931
|
+
end
|
|
1932
|
+
end
|
|
1933
|
+
end
|
|
1934
|
+
end
|
|
1935
|
+
end
|