openhab-jrubyscripting 5.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/openhab/core/actions.rb +163 -0
- data/lib/openhab/core/entity_lookup.rb +144 -0
- data/lib/openhab/core/events/abstract_event.rb +17 -0
- data/lib/openhab/core/events/item_channel_link.rb +36 -0
- data/lib/openhab/core/events/item_command_event.rb +78 -0
- data/lib/openhab/core/events/item_event.rb +22 -0
- data/lib/openhab/core/events/item_state_changed_event.rb +52 -0
- data/lib/openhab/core/events/item_state_event.rb +51 -0
- data/lib/openhab/core/events/thing.rb +29 -0
- data/lib/openhab/core/events/thing_status_info_event.rb +53 -0
- data/lib/openhab/core/events.rb +10 -0
- data/lib/openhab/core/items/accepted_data_types.rb +29 -0
- data/lib/openhab/core/items/color_item.rb +52 -0
- data/lib/openhab/core/items/contact_item.rb +52 -0
- data/lib/openhab/core/items/date_time_item.rb +58 -0
- data/lib/openhab/core/items/dimmer_item.rb +148 -0
- data/lib/openhab/core/items/generic_item.rb +344 -0
- data/lib/openhab/core/items/group_item.rb +174 -0
- data/lib/openhab/core/items/image_item.rb +109 -0
- data/lib/openhab/core/items/location_item.rb +34 -0
- data/lib/openhab/core/items/metadata/hash.rb +390 -0
- data/lib/openhab/core/items/metadata/namespace_hash.rb +469 -0
- data/lib/openhab/core/items/metadata.rb +11 -0
- data/lib/openhab/core/items/number_item.rb +62 -0
- data/lib/openhab/core/items/numeric_item.rb +22 -0
- data/lib/openhab/core/items/persistence.rb +327 -0
- data/lib/openhab/core/items/player_item.rb +66 -0
- data/lib/openhab/core/items/proxy.rb +59 -0
- data/lib/openhab/core/items/registry.rb +66 -0
- data/lib/openhab/core/items/rollershutter_item.rb +68 -0
- data/lib/openhab/core/items/semantics/enumerable.rb +152 -0
- data/lib/openhab/core/items/semantics.rb +476 -0
- data/lib/openhab/core/items/state_storage.rb +53 -0
- data/lib/openhab/core/items/string_item.rb +28 -0
- data/lib/openhab/core/items/switch_item.rb +78 -0
- data/lib/openhab/core/items.rb +114 -0
- data/lib/openhab/core/lazy_array.rb +52 -0
- data/lib/openhab/core/profile_factory.rb +118 -0
- data/lib/openhab/core/script_handling.rb +55 -0
- data/lib/openhab/core/things/channel.rb +48 -0
- data/lib/openhab/core/things/channel_uid.rb +51 -0
- data/lib/openhab/core/things/item_channel_link.rb +33 -0
- data/lib/openhab/core/things/profile_callback.rb +52 -0
- data/lib/openhab/core/things/proxy.rb +69 -0
- data/lib/openhab/core/things/registry.rb +46 -0
- data/lib/openhab/core/things/thing.rb +194 -0
- data/lib/openhab/core/things.rb +22 -0
- data/lib/openhab/core/timer.rb +128 -0
- data/lib/openhab/core/types/comparable_type.rb +23 -0
- data/lib/openhab/core/types/date_time_type.rb +259 -0
- data/lib/openhab/core/types/decimal_type.rb +192 -0
- data/lib/openhab/core/types/hsb_type.rb +183 -0
- data/lib/openhab/core/types/increase_decrease_type.rb +34 -0
- data/lib/openhab/core/types/next_previous_type.rb +34 -0
- data/lib/openhab/core/types/numeric_type.rb +52 -0
- data/lib/openhab/core/types/on_off_type.rb +46 -0
- data/lib/openhab/core/types/open_closed_type.rb +41 -0
- data/lib/openhab/core/types/percent_type.rb +95 -0
- data/lib/openhab/core/types/play_pause_type.rb +38 -0
- data/lib/openhab/core/types/point_type.rb +117 -0
- data/lib/openhab/core/types/quantity_type.rb +327 -0
- data/lib/openhab/core/types/raw_type.rb +26 -0
- data/lib/openhab/core/types/refresh_type.rb +27 -0
- data/lib/openhab/core/types/rewind_fastforward_type.rb +38 -0
- data/lib/openhab/core/types/stop_move_type.rb +34 -0
- data/lib/openhab/core/types/string_type.rb +76 -0
- data/lib/openhab/core/types/type.rb +117 -0
- data/lib/openhab/core/types/un_def_type.rb +38 -0
- data/lib/openhab/core/types/up_down_type.rb +50 -0
- data/lib/openhab/core/types.rb +69 -0
- data/lib/openhab/core/uid.rb +36 -0
- data/lib/openhab/core.rb +85 -0
- data/lib/openhab/core_ext/java/duration.rb +115 -0
- data/lib/openhab/core_ext/java/local_date.rb +93 -0
- data/lib/openhab/core_ext/java/local_time.rb +106 -0
- data/lib/openhab/core_ext/java/month.rb +59 -0
- data/lib/openhab/core_ext/java/month_day.rb +105 -0
- data/lib/openhab/core_ext/java/period.rb +103 -0
- data/lib/openhab/core_ext/java/temporal_amount.rb +34 -0
- data/lib/openhab/core_ext/java/time.rb +58 -0
- data/lib/openhab/core_ext/java/unit.rb +15 -0
- data/lib/openhab/core_ext/java/zoned_date_time.rb +116 -0
- data/lib/openhab/core_ext/ruby/array.rb +21 -0
- data/lib/openhab/core_ext/ruby/class.rb +15 -0
- data/lib/openhab/core_ext/ruby/date.rb +89 -0
- data/lib/openhab/core_ext/ruby/numeric.rb +190 -0
- data/lib/openhab/core_ext/ruby/range.rb +70 -0
- data/lib/openhab/core_ext/ruby/time.rb +104 -0
- data/lib/openhab/core_ext.rb +18 -0
- data/lib/openhab/dsl/events/watch_event.rb +18 -0
- data/lib/openhab/dsl/events.rb +9 -0
- data/lib/openhab/dsl/gems.rb +3 -0
- data/lib/openhab/dsl/items/builder.rb +618 -0
- data/lib/openhab/dsl/items/ensure.rb +93 -0
- data/lib/openhab/dsl/items/timed_command.rb +236 -0
- data/lib/openhab/dsl/rules/automation_rule.rb +308 -0
- data/lib/openhab/dsl/rules/builder.rb +1373 -0
- data/lib/openhab/dsl/rules/guard.rb +115 -0
- data/lib/openhab/dsl/rules/name_inference.rb +160 -0
- data/lib/openhab/dsl/rules/property.rb +76 -0
- data/lib/openhab/dsl/rules/rule_triggers.rb +96 -0
- data/lib/openhab/dsl/rules/terse.rb +63 -0
- data/lib/openhab/dsl/rules/triggers/changed.rb +169 -0
- data/lib/openhab/dsl/rules/triggers/channel.rb +57 -0
- data/lib/openhab/dsl/rules/triggers/command.rb +107 -0
- data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +161 -0
- data/lib/openhab/dsl/rules/triggers/conditions/proc.rb +164 -0
- data/lib/openhab/dsl/rules/triggers/cron/cron.rb +195 -0
- data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +127 -0
- data/lib/openhab/dsl/rules/triggers/trigger.rb +56 -0
- data/lib/openhab/dsl/rules/triggers/updated.rb +130 -0
- data/lib/openhab/dsl/rules/triggers/watch/watch.rb +55 -0
- data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +155 -0
- data/lib/openhab/dsl/rules/triggers.rb +12 -0
- data/lib/openhab/dsl/rules.rb +29 -0
- data/lib/openhab/dsl/script_handling.rb +55 -0
- data/lib/openhab/dsl/things/builder.rb +263 -0
- data/lib/openhab/dsl/thread_local.rb +48 -0
- data/lib/openhab/dsl/timer_manager.rb +191 -0
- data/lib/openhab/dsl/version.rb +9 -0
- data/lib/openhab/dsl.rb +686 -0
- data/lib/openhab/log.rb +348 -0
- data/lib/openhab/osgi.rb +70 -0
- data/lib/openhab/rspec/configuration.rb +56 -0
- data/lib/openhab/rspec/example_group.rb +90 -0
- data/lib/openhab/rspec/helpers.rb +439 -0
- data/lib/openhab/rspec/hooks.rb +93 -0
- data/lib/openhab/rspec/jruby.rb +46 -0
- data/lib/openhab/rspec/karaf.rb +811 -0
- data/lib/openhab/rspec/mocks/bundle_install_support.rb +25 -0
- data/lib/openhab/rspec/mocks/bundle_resolver.rb +30 -0
- data/lib/openhab/rspec/mocks/event_admin.rb +146 -0
- data/lib/openhab/rspec/mocks/metadata_provider.rb +75 -0
- data/lib/openhab/rspec/mocks/persistence_service.rb +140 -0
- data/lib/openhab/rspec/mocks/safe_caller.rb +40 -0
- data/lib/openhab/rspec/mocks/synchronous_executor.rb +56 -0
- data/lib/openhab/rspec/mocks/thing_handler.rb +76 -0
- data/lib/openhab/rspec/mocks/timer.rb +95 -0
- data/lib/openhab/rspec/openhab/core/actions.rb +26 -0
- data/lib/openhab/rspec/openhab/core/items/proxy.rb +27 -0
- data/lib/openhab/rspec/openhab/core/things/proxy.rb +27 -0
- data/lib/openhab/rspec/shell.rb +31 -0
- data/lib/openhab/rspec/suspend_rules.rb +60 -0
- data/lib/openhab/rspec.rb +17 -0
- data/lib/openhab/yard/cli/stats.rb +23 -0
- data/lib/openhab/yard/code_objects/group_object.rb +17 -0
- data/lib/openhab/yard/code_objects/java/base.rb +31 -0
- data/lib/openhab/yard/code_objects/java/class_object.rb +11 -0
- data/lib/openhab/yard/code_objects/java/field_object.rb +15 -0
- data/lib/openhab/yard/code_objects/java/interface_object.rb +15 -0
- data/lib/openhab/yard/code_objects/java/package_object.rb +11 -0
- data/lib/openhab/yard/code_objects/java/proxy.rb +23 -0
- data/lib/openhab/yard/handlers/jruby/base.rb +49 -0
- data/lib/openhab/yard/handlers/jruby/class_handler.rb +18 -0
- data/lib/openhab/yard/handlers/jruby/constant_handler.rb +18 -0
- data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +27 -0
- data/lib/openhab/yard/handlers/jruby/mixin_handler.rb +23 -0
- data/lib/openhab/yard/html_helper.rb +44 -0
- data/lib/openhab/yard/tags/constant_directive.rb +20 -0
- data/lib/openhab/yard/tags/group_directive.rb +24 -0
- data/lib/openhab/yard/tags/library.rb +3 -0
- data/lib/openhab/yard.rb +32 -0
- metadata +504 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "trigger"
|
4
|
+
|
5
|
+
module OpenHAB
|
6
|
+
module DSL
|
7
|
+
module Rules
|
8
|
+
module Triggers
|
9
|
+
# @!visibility private
|
10
|
+
#
|
11
|
+
# Creates channel triggers
|
12
|
+
#
|
13
|
+
class Channel < Trigger
|
14
|
+
# @return [String] A channel event trigger
|
15
|
+
CHANNEL_EVENT = "core.ChannelEventTrigger"
|
16
|
+
|
17
|
+
#
|
18
|
+
# Get an enumerator over the product of the channels and things and map them to a channel id
|
19
|
+
# @param [Object] channels to iterate over
|
20
|
+
# @param [Object] thing to combine with channels and iterate over
|
21
|
+
# @return [Enumerable] enumerable channel ids to trigger on
|
22
|
+
def self.channels(channels:, thing:)
|
23
|
+
logger.trace "Creating Channel/Thing Pairs for channels #{channels.inspect} and things #{thing.inspect}"
|
24
|
+
channels.flatten.product([thing].flatten)
|
25
|
+
.map { |channel_thing| channel_id(*channel_thing) }
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Get a channel id from a channel and thing
|
30
|
+
# @param [Object] channel part of channel id, get UID if object is a Channel
|
31
|
+
# @param [Object] thing part of channel id, get UID if object is a Thing
|
32
|
+
#
|
33
|
+
def self.channel_id(channel, thing)
|
34
|
+
channel = channel.uid if channel.is_a?(org.openhab.core.thing.Channel)
|
35
|
+
thing = thing.uid if thing.is_a?(Core::Things::Thing)
|
36
|
+
[thing, channel].compact.join(":")
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Create a trigger for a channel
|
41
|
+
#
|
42
|
+
# @param [String] channel to look for triggers
|
43
|
+
# @param [String] trigger specific channel trigger to match
|
44
|
+
# @param [Object] attach object to be attached to the trigger
|
45
|
+
#
|
46
|
+
#
|
47
|
+
def trigger(channel:, trigger:, attach:)
|
48
|
+
config = { "channelUID" => channel }
|
49
|
+
config["event"] = trigger.to_s unless trigger.nil?
|
50
|
+
logger.trace "Creating Channel Trigger for channels #{channel.inspect} and config #{config.inspect}"
|
51
|
+
append_trigger(type: CHANNEL_EVENT, config: config, attach: attach)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "trigger"
|
4
|
+
|
5
|
+
module OpenHAB
|
6
|
+
module DSL
|
7
|
+
module Rules
|
8
|
+
module Triggers
|
9
|
+
# @!visibility private
|
10
|
+
#
|
11
|
+
# Creates command triggers
|
12
|
+
#
|
13
|
+
class Command < Trigger
|
14
|
+
#
|
15
|
+
# Create a received command trigger
|
16
|
+
#
|
17
|
+
# @param [Object] item item to create trigger for
|
18
|
+
# @param [Object] command to check against
|
19
|
+
# @param [Object] attach object to be attached to the trigger
|
20
|
+
#
|
21
|
+
# @return [Trigger] OpenHAB triggers
|
22
|
+
#
|
23
|
+
def trigger(item:, command:, attach:)
|
24
|
+
case command
|
25
|
+
when Range then range_trigger(item: item, command: command, attach: attach)
|
26
|
+
when Proc then proc_trigger(item: item, command: command, attach: attach)
|
27
|
+
else command_trigger(item: item, command: command, attach: attach)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Creates a trigger with a range condition on the 'command' field
|
33
|
+
# @param [Object] item to create changed trigger on
|
34
|
+
# @param [Object] command to restrict trigger to
|
35
|
+
# @param [Object] attach object to be attached to the trigger
|
36
|
+
# @return [Trigger] OpenHAB trigger
|
37
|
+
#
|
38
|
+
def range_trigger(item:, command:, attach:)
|
39
|
+
command_range, * = Conditions::Proc.range_procs(command)
|
40
|
+
proc_trigger(item: item, command: command_range, attach: attach)
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Creates a trigger with a proc condition on the 'command' field
|
45
|
+
# @param [Object] item to create changed trigger on
|
46
|
+
# @param [Object] command to restrict trigger to
|
47
|
+
# @param [Object] attach object to be attached to the trigger
|
48
|
+
# @return [Trigger] OpenHAB trigger
|
49
|
+
#
|
50
|
+
def proc_trigger(item:, command:, attach:)
|
51
|
+
conditions = Conditions::Proc.new(command: command)
|
52
|
+
command_trigger(item: item, command: nil, attach: attach, conditions: conditions)
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Create a received trigger based on item type
|
57
|
+
#
|
58
|
+
# @param [Object] item to create trigger for
|
59
|
+
# @param [String] command to create trigger for
|
60
|
+
# @param [Object] attach object to be attached to the trigger
|
61
|
+
#
|
62
|
+
def command_trigger(item:, command:, attach: nil, conditions: nil)
|
63
|
+
type, config = if item.is_a?(GroupItem::Members)
|
64
|
+
group(group: item)
|
65
|
+
else
|
66
|
+
item(item: item)
|
67
|
+
end
|
68
|
+
config["command"] = command.to_s unless command.nil?
|
69
|
+
append_trigger(type: type, config: config, attach: attach, conditions: conditions)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# @return [String] item command trigger
|
75
|
+
ITEM_COMMAND = "core.ItemCommandTrigger"
|
76
|
+
|
77
|
+
# @return [String] A group command trigger for items in the group
|
78
|
+
GROUP_COMMAND = "core.GroupCommandTrigger"
|
79
|
+
|
80
|
+
#
|
81
|
+
# Create trigger for item commands
|
82
|
+
#
|
83
|
+
# @param [Item] item to create trigger for
|
84
|
+
#
|
85
|
+
# @return [Array<Hash,Trigger>] first element is hash of trigger config properties
|
86
|
+
# second element is trigger type
|
87
|
+
#
|
88
|
+
def item(item:)
|
89
|
+
[ITEM_COMMAND, { "itemName" => item.name }]
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# Create trigger for group items
|
94
|
+
#
|
95
|
+
# @param [Group] group to create trigger for
|
96
|
+
#
|
97
|
+
# @return [Array<Hash,Trigger>] first element is hash of trigger config properties
|
98
|
+
# second element is trigger type
|
99
|
+
#
|
100
|
+
def group(group:)
|
101
|
+
[GROUP_COMMAND, { "groupName" => group.group.name }]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenHAB
|
4
|
+
module DSL
|
5
|
+
module Rules
|
6
|
+
module Triggers
|
7
|
+
# @!visibility private
|
8
|
+
module Conditions
|
9
|
+
#
|
10
|
+
# this is a no-op condition which simply executes the provided block
|
11
|
+
#
|
12
|
+
|
13
|
+
#
|
14
|
+
# Struct capturing data necessary for a conditional trigger
|
15
|
+
#
|
16
|
+
# TriggerDelay = Struct.new(:to, :from, :duration, :timer, :tracking_to, keyword_init: true) do
|
17
|
+
# def timer_active?
|
18
|
+
# timer&.active?
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
class Duration
|
22
|
+
#
|
23
|
+
# Create a new duration condition
|
24
|
+
# @param [Object] to optional condition on to state
|
25
|
+
# @param [Object] from optional condition on from state
|
26
|
+
# @param [java.time.temporal.TemporalAmount] duration to state must stay at specific value before triggering
|
27
|
+
#
|
28
|
+
def initialize(to:, from:, duration:)
|
29
|
+
to = Conditions::Proc.from_value(to)
|
30
|
+
from = Conditions::Proc.from_value(from)
|
31
|
+
@conditions = Conditions::Proc.new(to: to, from: from)
|
32
|
+
@duration = duration
|
33
|
+
@timer = nil
|
34
|
+
logger.trace "Created Duration Condition To(#{to}) From(#{from}) "\
|
35
|
+
"Conditions(#{@conditions}) Duration(#{@duration})"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Process rule
|
39
|
+
# @param [Hash] inputs inputs from trigger
|
40
|
+
#
|
41
|
+
def process(mod:, inputs:, &block)
|
42
|
+
process_trigger_delay(mod, inputs, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Cleanup any resources from the condition
|
46
|
+
#
|
47
|
+
# Cancels the timer, if it's active
|
48
|
+
def cleanup
|
49
|
+
@timer&.cancel
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
#
|
55
|
+
# Checks if there is an active timer
|
56
|
+
# @return [true, false] true if the timer exists and is active, false otherwise
|
57
|
+
def timer_active?
|
58
|
+
@timer&.active?
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Check if trigger guards prevent rule execution
|
63
|
+
#
|
64
|
+
# @param [Map] inputs OpenHAB map object describing rule trigger
|
65
|
+
#
|
66
|
+
# @return [true,false] True if the rule should execute, false if trigger guard prevents execution
|
67
|
+
#
|
68
|
+
def check_trigger_guards(inputs)
|
69
|
+
new_state, old_state = retrieve_states(inputs)
|
70
|
+
@conditions.check_from(state: old_state) && @conditions.check_to(state: new_state)
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Rerieve the newState and oldState, alternatively newStatus and oldStatus
|
75
|
+
# from the input map
|
76
|
+
#
|
77
|
+
# @param [Map] inputs OpenHAB map object describing rule trigger
|
78
|
+
#
|
79
|
+
# @return [Array] An array of the values for [newState, oldState] or [newStatus, oldStatus]
|
80
|
+
#
|
81
|
+
def retrieve_states(inputs)
|
82
|
+
new_state = inputs["newState"] || thing_status_to_sym(inputs["newStatus"])
|
83
|
+
old_state = inputs["oldState"] || thing_status_to_sym(inputs["oldStatus"])
|
84
|
+
|
85
|
+
[new_state, old_state]
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Converts a ThingStatus object to a ruby Symbol
|
90
|
+
#
|
91
|
+
# @param [org.openhab.core.thing.ThingStatus] status A ThingStatus instance
|
92
|
+
#
|
93
|
+
# @return [Symbol] A corresponding symbol, in lower case
|
94
|
+
#
|
95
|
+
def thing_status_to_sym(status)
|
96
|
+
status&.to_s&.downcase&.to_sym
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# Process any matching trigger delays
|
101
|
+
#
|
102
|
+
# @param [Map] mod OpenHAB map object describing rule trigger
|
103
|
+
# @param [Map] inputs OpenHAB map object describing rule trigger
|
104
|
+
#
|
105
|
+
#
|
106
|
+
def process_trigger_delay(mod, inputs, &block)
|
107
|
+
if timer_active?
|
108
|
+
process_active_timer(inputs, mod, &block)
|
109
|
+
elsif check_trigger_guards(inputs)
|
110
|
+
logger.trace("Trigger Guards Matched for #{self}, delaying rule execution")
|
111
|
+
# Add timer and attach timer to delay object, and also state being tracked to so
|
112
|
+
# timer can be cancelled if state changes
|
113
|
+
# Also another timer should not be created if changed to same value again but instead rescheduled
|
114
|
+
create_trigger_delay_timer(inputs, mod, &block)
|
115
|
+
else
|
116
|
+
logger.trace("Trigger Guards did not match for #{self}, ignoring trigger.")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Creates a timer for trigger delays
|
122
|
+
#
|
123
|
+
# @param [Hash] inputs rule trigger inputs
|
124
|
+
# @param [Hash] _mod rule trigger mods
|
125
|
+
#
|
126
|
+
#
|
127
|
+
def create_trigger_delay_timer(inputs, _mod)
|
128
|
+
logger.trace("Creating timer for trigger delay #{self}")
|
129
|
+
@timer = DSL.after(@duration) do
|
130
|
+
logger.trace("Delay Complete for #{self}, executing rule")
|
131
|
+
@timer = nil
|
132
|
+
yield
|
133
|
+
end
|
134
|
+
@tracking_to, = retrieve_states(inputs)
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Process an active trigger timer
|
139
|
+
#
|
140
|
+
# @param [Hash] inputs rule trigger inputs
|
141
|
+
# @param [Hash] mod rule trigger mods
|
142
|
+
#
|
143
|
+
#
|
144
|
+
def process_active_timer(inputs, mod, &block)
|
145
|
+
state, = retrieve_states(inputs)
|
146
|
+
if state == @tracking_to
|
147
|
+
logger.trace("Item changed to #{state} for #{self}, rescheduling timer.")
|
148
|
+
@timer.reschedule(ZonedDateTime.now.plus(@duration))
|
149
|
+
else
|
150
|
+
logger.trace("Item changed to #{state} for #{self}, canceling timer.")
|
151
|
+
@timer.cancel
|
152
|
+
# Reprocess trigger delay after canceling to track new state (if guards matched, etc)
|
153
|
+
process_trigger_delay(mod, inputs, &block)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenHAB
|
4
|
+
module DSL
|
5
|
+
module Rules
|
6
|
+
module Triggers
|
7
|
+
# @!visibility private
|
8
|
+
module Conditions
|
9
|
+
#
|
10
|
+
# This creates trigger conditions that work on procs
|
11
|
+
# @param [Proc] from Proc
|
12
|
+
# @param [Proc] to Proc
|
13
|
+
#
|
14
|
+
class Proc
|
15
|
+
#
|
16
|
+
# Converts supplied ranges to procs that check range
|
17
|
+
# @param [Array] ranges objects to convert to range proc if they are ranges
|
18
|
+
# @return [Array] of procs or supplied arguments if argument was not of type Range
|
19
|
+
#
|
20
|
+
def self.range_procs(*ranges)
|
21
|
+
ranges.map { |range| range.is_a?(Range) ? range_proc(range) : range }
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Create a range proc for the supplied range object
|
26
|
+
# @param [Range] range to build proc for
|
27
|
+
#
|
28
|
+
def self.range_proc(range)
|
29
|
+
logger.trace("Creating range proc for #{range}")
|
30
|
+
lambda do |val|
|
31
|
+
logger.trace("Range proc checking if #{val} is in #{range}")
|
32
|
+
range.cover? val
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Create an equality proc for the supplied range object
|
38
|
+
# @param [State] value to build proc for
|
39
|
+
#
|
40
|
+
def self.equality_proc(value)
|
41
|
+
logger.trace("Creating equality proc for #{value}")
|
42
|
+
lambda do |state|
|
43
|
+
logger.trace("Equality proc comparing #{value} against #{state}")
|
44
|
+
value == state
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Constructs a proc for the specific value type
|
50
|
+
# if the value is a proc return the proc
|
51
|
+
# if the value is a range create a range proc
|
52
|
+
# if the value is nil, return nil
|
53
|
+
# otherwise create an equality proc
|
54
|
+
# @param [Object] value to construct proc from
|
55
|
+
def self.from_value(value)
|
56
|
+
logger.trace("Creating proc for Value(#{value})")
|
57
|
+
return value if value.nil?
|
58
|
+
return value if value.is_a?(::Proc)
|
59
|
+
return range_proc(value) if value.is_a?(Range)
|
60
|
+
|
61
|
+
equality_proc(value)
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Create a new Proc Condition that executes only if procs return true
|
66
|
+
# @param [Proc] from Proc to check against from value
|
67
|
+
# @param [Proc] to Proc to check against to value
|
68
|
+
#
|
69
|
+
def initialize(from: nil, to: nil, command: nil)
|
70
|
+
@from = from
|
71
|
+
@to = to
|
72
|
+
@command = command
|
73
|
+
end
|
74
|
+
|
75
|
+
# Proc that doesn't check any fields
|
76
|
+
ANY = Proc.new.freeze # this needs to be defined _after_ initialize so its instance variables are set
|
77
|
+
|
78
|
+
#
|
79
|
+
# Process rule
|
80
|
+
# @param [Hash] inputs inputs from trigger
|
81
|
+
#
|
82
|
+
def process(mod:, inputs:) # rubocop:disable Lint/UnusedMethodArgument - mod is unused here but required
|
83
|
+
logger.trace("Checking #{inputs} against condition trigger #{self}")
|
84
|
+
yield if check_procs(inputs: inputs)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Cleanup any resources from the condition
|
88
|
+
def cleanup; end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Check if command condition match the proc
|
92
|
+
# @param [Hash] inputs from trigger must be supplied if state is not supplied
|
93
|
+
# @return [true/false] depending on if from is set and matches supplied conditions
|
94
|
+
#
|
95
|
+
def check_command(inputs: nil)
|
96
|
+
command = input_state(inputs, "command")
|
97
|
+
logger.trace "Checking command(#{@command}) against command(#{command})"
|
98
|
+
check_proc(proc: @command, value: command)
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Check if from condition match the proc
|
103
|
+
# @param [Hash] inputs from trigger must be supplied if state is not supplied
|
104
|
+
# @param [String] state if supplied proc will be passed state value for comparision
|
105
|
+
# @return [true/false] depending on if from is set and matches supplied conditions
|
106
|
+
#
|
107
|
+
def check_from(inputs: nil, state: nil)
|
108
|
+
state ||= input_state(inputs, "oldState")
|
109
|
+
logger.trace "Checking from(#{@from}) against state(#{state})"
|
110
|
+
check_proc(proc: @from, value: state)
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Check if to conditions match the proc
|
115
|
+
# @param [Hash] inputs from trigger must be supplied if state is not supplied
|
116
|
+
# @param [String] state if supplied proc will be passed state value for comparision
|
117
|
+
# @return [true/false] depending on if from is set and matches supplied conditions
|
118
|
+
#
|
119
|
+
def check_to(inputs: nil, state: nil)
|
120
|
+
state ||= input_state(inputs, "newState", "state")
|
121
|
+
logger.trace "Checking to(#{@to}) against state(#{state})"
|
122
|
+
check_proc(proc: @to, value: state)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Describe the Proc Condition as a string
|
126
|
+
# @return [String] string representation of proc condition
|
127
|
+
#
|
128
|
+
def to_s
|
129
|
+
"From:(#{@from}) To:(#{@to}) Command:(#{@command})"
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
#
|
135
|
+
# Check all procs
|
136
|
+
# @param [Hash] inputs from event
|
137
|
+
# @return [true/false] true if all procs return true, false otherwise
|
138
|
+
def check_procs(inputs:)
|
139
|
+
check_from(inputs: inputs) && check_to(inputs: inputs) && check_command(inputs: inputs)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Check if a field matches the proc condition
|
143
|
+
# @param [Proc] proc to call
|
144
|
+
# @param [Hash] value to check
|
145
|
+
# @return [true,false] true if proc is nil or proc.call returns true, false otherwise
|
146
|
+
def check_proc(proc:, value:)
|
147
|
+
return true if proc.nil? || proc.call(value)
|
148
|
+
|
149
|
+
logger.trace("Skipped execution of rule because value #{value} evaluated false for (#{proc})")
|
150
|
+
false
|
151
|
+
end
|
152
|
+
|
153
|
+
# Get the first field from supplied fields in inputs
|
154
|
+
# @param [Hash] inputs containing fields
|
155
|
+
# @param [Array] fields array of fields to extract from inputs, first one found is returned
|
156
|
+
def input_state(inputs, *fields)
|
157
|
+
fields.map { |f| inputs[f] }.compact.first
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openhab/dsl/rules/triggers/trigger"
|
4
|
+
|
5
|
+
module OpenHAB
|
6
|
+
module DSL
|
7
|
+
module Rules
|
8
|
+
module Triggers
|
9
|
+
# @!visibility private
|
10
|
+
#
|
11
|
+
# Creates cron triggers
|
12
|
+
#
|
13
|
+
class Cron < Trigger
|
14
|
+
# Trigger ID for Watch Triggers
|
15
|
+
CRON_TRIGGER_MODULE_ID = "jsr223.jruby.CronTrigger"
|
16
|
+
|
17
|
+
#
|
18
|
+
# Returns a default map for cron expressions that fires every second
|
19
|
+
# This map is usually updated via merge by other methods to refine cron type triggers.
|
20
|
+
#
|
21
|
+
# @return [Hash] Map with symbols for :seconds, :minute, :hour, :dom, :month, :dow
|
22
|
+
# configured to fire every second
|
23
|
+
#
|
24
|
+
CRON_EXPRESSION_MAP =
|
25
|
+
{
|
26
|
+
second: "*",
|
27
|
+
minute: "*",
|
28
|
+
hour: "*",
|
29
|
+
dom: "?",
|
30
|
+
month: "*",
|
31
|
+
dow: "?",
|
32
|
+
year: "*"
|
33
|
+
}.freeze
|
34
|
+
private_constant :CRON_EXPRESSION_MAP
|
35
|
+
|
36
|
+
# @return [Hash] Map of days of the week from symbols to to OpenHAB cron strings
|
37
|
+
DAY_OF_WEEK_MAP = {
|
38
|
+
monday: "MON",
|
39
|
+
tuesday: "TUE",
|
40
|
+
wednesday: "WED",
|
41
|
+
thursday: "THU",
|
42
|
+
friday: "FRI",
|
43
|
+
saturday: "SAT",
|
44
|
+
sunday: "SUN"
|
45
|
+
}.freeze
|
46
|
+
private_constant :DAY_OF_WEEK_MAP
|
47
|
+
|
48
|
+
# @return [Hash] Converts the DAY_OF_WEEK_MAP to map used by Cron Expression
|
49
|
+
DAY_OF_WEEK_EXPRESSION_MAP = DAY_OF_WEEK_MAP.transform_values { |v| CRON_EXPRESSION_MAP.merge(dow: v) }
|
50
|
+
|
51
|
+
private_constant :DAY_OF_WEEK_EXPRESSION_MAP
|
52
|
+
|
53
|
+
# @return [Hash] Create a set of cron expressions based on different time intervals
|
54
|
+
EXPRESSION_MAP = {
|
55
|
+
second: CRON_EXPRESSION_MAP,
|
56
|
+
minute: CRON_EXPRESSION_MAP.merge(second: "0"),
|
57
|
+
hour: CRON_EXPRESSION_MAP.merge(second: "0", minute: "0"),
|
58
|
+
day: CRON_EXPRESSION_MAP.merge(second: "0", minute: "0", hour: "0"),
|
59
|
+
week: CRON_EXPRESSION_MAP.merge(second: "0", minute: "0", hour: "0", dow: "MON"),
|
60
|
+
month: CRON_EXPRESSION_MAP.merge(second: "0", minute: "0", hour: "0", dom: "1"),
|
61
|
+
year: CRON_EXPRESSION_MAP.merge(second: "0", minute: "0", hour: "0", dom: "1", month: "1")
|
62
|
+
}.merge(DAY_OF_WEEK_EXPRESSION_MAP).freeze
|
63
|
+
|
64
|
+
private_constant :EXPRESSION_MAP
|
65
|
+
|
66
|
+
#
|
67
|
+
# Create a cron map from a duration
|
68
|
+
#
|
69
|
+
# @param [Duration] duration
|
70
|
+
# @param [Object] at LocalTime or String representing time of day
|
71
|
+
#
|
72
|
+
# @return [Hash] map describing cron expression
|
73
|
+
#
|
74
|
+
def self.from_duration(duration, at)
|
75
|
+
raise ArgumentError, '"at" cannot be used with duration' if at
|
76
|
+
|
77
|
+
map_to_cron(duration_to_map(duration))
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Create a cron map from a MonthDay
|
82
|
+
#
|
83
|
+
# @param [MonthDay] monthday a {MonthDay} object
|
84
|
+
# @param [Object] at LocalTime or String representing time of day
|
85
|
+
#
|
86
|
+
# @return [Hash] map describing cron expression
|
87
|
+
#
|
88
|
+
def self.from_monthday(monthday, at)
|
89
|
+
expression_map = EXPRESSION_MAP[:day].merge(month: monthday.month_value, dom: monthday.day_of_month)
|
90
|
+
expression_map = at_condition(expression_map, at) if at
|
91
|
+
map_to_cron(expression_map)
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Create a cron map from a symbol
|
96
|
+
#
|
97
|
+
# @param [Symbol] symbol
|
98
|
+
# @param [Object] at LocalTime or String representing time of day
|
99
|
+
#
|
100
|
+
# @return [Hash] map describing cron expression created from symbol
|
101
|
+
#
|
102
|
+
def self.from_symbol(symbol, at)
|
103
|
+
expression_map = EXPRESSION_MAP[symbol]
|
104
|
+
expression_map = at_condition(expression_map, at) if at
|
105
|
+
map_to_cron(expression_map)
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# Create a cron map from cron elements
|
110
|
+
#
|
111
|
+
# @param [Hash] fields Cron fields (second, minute, hour, dom, month, dow, year)
|
112
|
+
#
|
113
|
+
# @return [Hash] map describing cron expression
|
114
|
+
#
|
115
|
+
def self.from_fields(fields)
|
116
|
+
extra_fields = fields.keys - CRON_EXPRESSION_MAP.keys
|
117
|
+
unless extra_fields.empty?
|
118
|
+
raise ArgumentError,
|
119
|
+
"unknown keyword#{"s" if extra_fields.size > 1}: #{extra_fields.map(&:inspect).join(", ")}"
|
120
|
+
end
|
121
|
+
|
122
|
+
fields = fields.transform_values { |value| value.to_s.delete(" ") }
|
123
|
+
# find the first expression map that has a field from fields.
|
124
|
+
# this ensure more-specific fields get set to 0, not *
|
125
|
+
base_key = EXPRESSION_MAP.keys.find { |field, _| fields.key?(field) }
|
126
|
+
base_expression = EXPRESSION_MAP[base_key]
|
127
|
+
expression_map = base_expression.merge(fields)
|
128
|
+
|
129
|
+
map_to_cron(expression_map)
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Map cron expression to to cron string
|
134
|
+
#
|
135
|
+
# @param [Map] map of cron expression
|
136
|
+
#
|
137
|
+
# @return [String] OpenHAB cron string
|
138
|
+
#
|
139
|
+
def self.map_to_cron(map)
|
140
|
+
%i[second minute hour dom month dow year].map { |field| map.fetch(field) }.join(" ")
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Convert a Java Duration to a map for the map_to_cron method
|
145
|
+
#
|
146
|
+
# @param duration [Duration] The duration object
|
147
|
+
#
|
148
|
+
# @return [Hash] a map suitable for map_to_cron
|
149
|
+
#
|
150
|
+
def self.duration_to_map(duration)
|
151
|
+
if duration.to_millis_part.zero? && duration.to_nanos_part.zero? && duration.to_days.zero?
|
152
|
+
%i[second minute hour].each do |unit|
|
153
|
+
to_unit_part = duration.public_send("to_#{unit}s_part")
|
154
|
+
next unless to_unit_part.positive?
|
155
|
+
|
156
|
+
to_unit = duration.public_send("to_#{unit}s")
|
157
|
+
break unless to_unit_part == to_unit
|
158
|
+
|
159
|
+
return EXPRESSION_MAP[unit].merge(unit => "*/#{to_unit}")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
raise ArgumentError, "Cron Expression not supported for duration: #{duration}"
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# If an at time is provided, parse that and merge the new fields into the expression.
|
167
|
+
#
|
168
|
+
# @param [<Type>] expression_map <description>
|
169
|
+
# @param [<Type>] at_time <description>
|
170
|
+
#
|
171
|
+
# @return [<Type>] <description>
|
172
|
+
#
|
173
|
+
def self.at_condition(expression_map, at_time)
|
174
|
+
if at_time
|
175
|
+
tod = at_time.is_a?(LocalTime) ? at_time : LocalTime.parse(at_time)
|
176
|
+
expression_map = expression_map.merge(hour: tod.hour, minute: tod.minute, second: tod.second)
|
177
|
+
end
|
178
|
+
expression_map
|
179
|
+
end
|
180
|
+
|
181
|
+
#
|
182
|
+
# Create a cron trigger based on item type
|
183
|
+
#
|
184
|
+
# @param [Config] config Rule configuration
|
185
|
+
# @param [Object] attach object to be attached to the trigger
|
186
|
+
#
|
187
|
+
#
|
188
|
+
def trigger(config:, attach:)
|
189
|
+
append_trigger(type: CRON_TRIGGER_MODULE_ID, config: config, attach: attach)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|