openhab-scripting 4.47.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 -38
@@ -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
|