openhab-jrubyscripting 5.0.0.rc9 → 5.0.0.rc11
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 +23 -120
- data/lib/openhab/core/{events → dto}/item_channel_link.rb +1 -4
- data/lib/openhab/core/{events → dto}/thing.rb +10 -12
- data/lib/openhab/core/dto.rb +11 -0
- data/lib/openhab/core/entity_lookup.rb +1 -1
- data/lib/openhab/core/events/abstract_event.rb +1 -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 +1 -1
- data/lib/openhab/core/events/item_state_changed_event.rb +6 -6
- data/lib/openhab/core/events/item_state_event.rb +6 -6
- data/lib/openhab/core/events/thing_status_info_event.rb +8 -6
- data/lib/openhab/core/items/generic_item.rb +2 -1
- data/lib/openhab/core/items/persistence.rb +52 -18
- data/lib/openhab/core/items/player_item.rb +1 -1
- data/lib/openhab/core/items/proxy.rb +20 -14
- data/lib/openhab/core/items/registry.rb +2 -0
- data/lib/openhab/core/items.rb +3 -3
- data/lib/openhab/core/profile_factory.rb +3 -1
- data/lib/openhab/core/proxy.rb +125 -0
- data/lib/openhab/core/things/links/provider.rb +1 -1
- data/lib/openhab/core/things/proxy.rb +8 -0
- data/lib/openhab/core/types/date_time_type.rb +2 -1
- data/lib/openhab/core/types/decimal_type.rb +1 -1
- data/lib/openhab/core/types/un_def_type.rb +2 -2
- data/lib/openhab/core/value_cache.rb +1 -1
- data/lib/openhab/core_ext/ephemeris.rb +53 -0
- data/lib/openhab/core_ext/java/class.rb +1 -1
- data/lib/openhab/core_ext/java/duration.rb +25 -1
- data/lib/openhab/core_ext/java/local_date.rb +2 -0
- data/lib/openhab/core_ext/java/month_day.rb +2 -0
- data/lib/openhab/core_ext/java/zoned_date_time.rb +85 -0
- data/lib/openhab/core_ext/ruby/date.rb +2 -0
- data/lib/openhab/core_ext/ruby/date_time.rb +1 -0
- data/lib/openhab/core_ext/ruby/time.rb +1 -0
- data/lib/openhab/dsl/debouncer.rb +259 -0
- data/lib/openhab/dsl/items/builder.rb +4 -2
- data/lib/openhab/dsl/items/timed_command.rb +31 -13
- data/lib/openhab/dsl/rules/automation_rule.rb +28 -21
- data/lib/openhab/dsl/rules/builder.rb +357 -37
- data/lib/openhab/dsl/rules/guard.rb +12 -54
- data/lib/openhab/dsl/rules/name_inference.rb +11 -0
- data/lib/openhab/dsl/rules/property.rb +3 -4
- data/lib/openhab/dsl/rules/terse.rb +4 -1
- data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +5 -6
- data/lib/openhab/dsl/rules/triggers/cron/cron.rb +1 -0
- data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +19 -31
- data/lib/openhab/dsl/rules/triggers/watch/watch.rb +1 -0
- data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +22 -30
- data/lib/openhab/dsl/things/builder.rb +1 -1
- data/lib/openhab/dsl/thread_local.rb +1 -0
- data/lib/openhab/dsl/version.rb +1 -1
- data/lib/openhab/dsl.rb +224 -3
- data/lib/openhab/rspec/hooks.rb +5 -2
- data/lib/openhab/rspec/karaf.rb +7 -0
- data/lib/openhab/rspec/mocks/instance_method_stasher.rb +22 -0
- data/lib/openhab/rspec/mocks/space.rb +23 -0
- data/lib/openhab/rspec/openhab/core/actions.rb +16 -4
- data/lib/openhab/rspec/openhab/core/items/proxy.rb +1 -13
- data/lib/openhab/rspec/suspend_rules.rb +1 -14
- data/lib/openhab/yard/base_helper.rb +19 -0
- data/lib/openhab/yard/code_objects/group_object.rb +9 -3
- data/lib/openhab/yard/coderay.rb +17 -0
- data/lib/openhab/yard/handlers/jruby/base.rb +10 -1
- data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +3 -0
- data/lib/openhab/yard/html_helper.rb +49 -15
- data/lib/openhab/yard/markdown_helper.rb +135 -0
- data/lib/openhab/yard.rb +6 -0
- metadata +36 -4
@@ -21,11 +21,11 @@ module OpenHAB
|
|
21
21
|
# Undef State
|
22
22
|
|
23
23
|
# @!method null?
|
24
|
-
# Check if `self == NULL
|
24
|
+
# Check if `self` == {NULL}
|
25
25
|
# @return [true,false]
|
26
26
|
|
27
27
|
# @!method undef?
|
28
|
-
# Check if `self == UNDEF
|
28
|
+
# Check if `self` == {UNDEF}
|
29
29
|
# @return [true,false]
|
30
30
|
end
|
31
31
|
end
|
@@ -26,7 +26,7 @@ module OpenHAB
|
|
26
26
|
#
|
27
27
|
# @note Only the {OpenHAB::DSL.shared_cache sharedCache} is exposed in Ruby.
|
28
28
|
# For a private cache, simply use an instance variable. See
|
29
|
-
# {file:docs/ruby-basics.md#
|
29
|
+
# {file:docs/ruby-basics.md#variables Instance Variables}.
|
30
30
|
#
|
31
31
|
# @note Because every script or UI rule gets it own JRuby engine instance,
|
32
32
|
# you cannot rely on being able to access Ruby objects between them. Only
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenHAB
|
4
|
+
module CoreExt
|
5
|
+
#
|
6
|
+
# Forwards ephemeris helper methods to `#to_zoned_date_time` provided by
|
7
|
+
# the mixed-in class.
|
8
|
+
#
|
9
|
+
# @note openHAB's built-in holiday definitions are based on _bank_
|
10
|
+
# holidays, so may give some unexpected results. For example, 2022-12-25
|
11
|
+
# is _not_ Christmas in England because it lands on a Sunday that year,
|
12
|
+
# so Christmas is considered to be 2022-12-26. See
|
13
|
+
# [the source](https://github.com/svendiedrichsen/jollyday/tree/master/src/main/resources/holidays)
|
14
|
+
# for exact definitions. You can always provide your own holiday
|
15
|
+
# definitions with {OpenHAB::DSL.holiday_file holiday_file} or
|
16
|
+
# {OpenHAB::DSL.holiday_file! holiday_file!}.
|
17
|
+
#
|
18
|
+
# @see https://www.openhab.org/docs/configuration/actions.html#ephemeris Ephemeris Action
|
19
|
+
# @see Core::Actions::Ephemeris.holiday_name Ephemeris.holiday_name
|
20
|
+
#
|
21
|
+
module Ephemeris
|
22
|
+
# (see Java::ZonedDateTime#holiday)
|
23
|
+
def holiday(holiday_file = nil)
|
24
|
+
to_zoned_date_time.holiday(holiday_file)
|
25
|
+
end
|
26
|
+
|
27
|
+
# (see Java::ZonedDateTime#holiday?)
|
28
|
+
def holiday?(holiday_file = nil)
|
29
|
+
to_zoned_date_time.holiday?(holiday_file)
|
30
|
+
end
|
31
|
+
|
32
|
+
# (see Java::ZonedDateTime#next_holiday)
|
33
|
+
def next_holiday(holiday_file = nil)
|
34
|
+
to_zoned_date_time.next_holiday(holiday_file)
|
35
|
+
end
|
36
|
+
|
37
|
+
# (see Java::ZonedDateTime#weekend?)
|
38
|
+
def weekend?
|
39
|
+
to_zoned_date_time.weekend?
|
40
|
+
end
|
41
|
+
|
42
|
+
# (see Java::ZonedDateTime#in_dayset?)
|
43
|
+
def in_dayset?(set)
|
44
|
+
to_zoned_date_time.in_dayset?(set)
|
45
|
+
end
|
46
|
+
|
47
|
+
# (see Java::ZonedDateTime#days_until)
|
48
|
+
def days_until(holiday, holiday_file = nil)
|
49
|
+
to_zoned_date_time.days_until(holiday, holiday_file)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -5,13 +5,37 @@ module OpenHAB
|
|
5
5
|
module Java
|
6
6
|
Duration = java.time.Duration
|
7
7
|
|
8
|
-
# Extensions to Duration
|
8
|
+
# Extensions to {java.time.Duration Java Duration}
|
9
9
|
class Duration
|
10
10
|
include Between
|
11
11
|
# @!parse include TemporalAmount
|
12
12
|
|
13
|
+
#
|
14
|
+
# Convert to integer number of seconds
|
15
|
+
#
|
16
|
+
# @return [Integer]
|
17
|
+
#
|
13
18
|
alias_method :to_i, :seconds
|
14
19
|
|
20
|
+
#
|
21
|
+
# @!method zero?
|
22
|
+
# @return [true,false] Returns true if the duration is zero length.
|
23
|
+
#
|
24
|
+
|
25
|
+
#
|
26
|
+
# @!method negative?
|
27
|
+
# @return [true,false] Returns true if the duration is less than zero.
|
28
|
+
#
|
29
|
+
|
30
|
+
unless instance_methods.include?(:positive?)
|
31
|
+
#
|
32
|
+
# @return [true, false] Returns true if the duration is greater than zero.
|
33
|
+
#
|
34
|
+
def positive?
|
35
|
+
self > 0 # rubocop:disable Style/NumericPredicate
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
15
39
|
#
|
16
40
|
# Convert to number of seconds
|
17
41
|
#
|
@@ -11,6 +11,7 @@ module OpenHAB
|
|
11
11
|
class LocalDate
|
12
12
|
include Time
|
13
13
|
include Between
|
14
|
+
include Ephemeris
|
14
15
|
|
15
16
|
class << self # rubocop:disable Lint/EmptyClass
|
16
17
|
# @!attribute [r] now
|
@@ -66,6 +67,7 @@ module OpenHAB
|
|
66
67
|
Date.new(year, month_value, day_of_month)
|
67
68
|
end
|
68
69
|
|
70
|
+
# @return [Month]
|
69
71
|
alias_method :to_month, :month
|
70
72
|
|
71
73
|
# @return [MonthDay]
|
@@ -10,6 +10,7 @@ module OpenHAB
|
|
10
10
|
# Extensions to MonthDay
|
11
11
|
class MonthDay
|
12
12
|
include Between
|
13
|
+
include Ephemeris
|
13
14
|
|
14
15
|
class << self
|
15
16
|
#
|
@@ -76,6 +77,7 @@ module OpenHAB
|
|
76
77
|
year.at_month_day(self)
|
77
78
|
end
|
78
79
|
|
80
|
+
# @return [Month]
|
79
81
|
alias_method :to_month, :month
|
80
82
|
|
81
83
|
# @param [Date, nil] context
|
@@ -17,7 +17,10 @@ module OpenHAB
|
|
17
17
|
# @return [ZonedDateTime]
|
18
18
|
end
|
19
19
|
|
20
|
+
# @return [LocalTime]
|
20
21
|
alias_method :to_local_time, :toLocalTime
|
22
|
+
|
23
|
+
# @return [Month]
|
21
24
|
alias_method :to_month, :month
|
22
25
|
|
23
26
|
# @param [TemporalAmount, #to_zoned_date_time, Numeric] other
|
@@ -87,6 +90,88 @@ module OpenHAB
|
|
87
90
|
self
|
88
91
|
end
|
89
92
|
|
93
|
+
# @group Ephemeris Methods
|
94
|
+
# (see CoreExt::Ephemeris)
|
95
|
+
|
96
|
+
#
|
97
|
+
# Name of the holiday for this date.
|
98
|
+
#
|
99
|
+
# @param [String, nil] holiday_file Optional path to XML file to use for holiday definitions.
|
100
|
+
# @return [Symbol, nil]
|
101
|
+
#
|
102
|
+
# @example
|
103
|
+
# MonthDay.parse("12-25").holiday # => :christmas
|
104
|
+
#
|
105
|
+
def holiday(holiday_file = nil)
|
106
|
+
::Ephemeris.get_bank_holiday_name(*[self, holiday_file || DSL.holiday_file].compact)&.downcase&.to_sym
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Determines if this date is on a holiday.
|
111
|
+
#
|
112
|
+
# @param [String, nil] holiday_file Optional path to XML file to use for holiday definitions.
|
113
|
+
# @return [true, false]
|
114
|
+
#
|
115
|
+
def holiday?(holiday_file = nil)
|
116
|
+
::Ephemeris.bank_holiday?(*[self, holiday_file || DSL.holiday_file].compact)
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Name of the closest holiday on or after this date.
|
121
|
+
#
|
122
|
+
# @param [String, nil] holiday_file Optional path to XML file to use for holiday definitions.
|
123
|
+
# @return [Symbol]
|
124
|
+
#
|
125
|
+
def next_holiday(holiday_file = nil)
|
126
|
+
::Ephemeris.get_next_bank_holiday(*[self, holiday_file || DSL.holiday_file].compact).downcase.to_sym
|
127
|
+
end
|
128
|
+
|
129
|
+
#
|
130
|
+
# Determines if this time is during a weekend.
|
131
|
+
#
|
132
|
+
# @return [true, false]
|
133
|
+
#
|
134
|
+
# @example
|
135
|
+
# Time.now.weekend?
|
136
|
+
#
|
137
|
+
def weekend?
|
138
|
+
::Ephemeris.weekend?(self)
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Determines if this time is during a specific dayset
|
143
|
+
#
|
144
|
+
# @param [String, Symbol] set
|
145
|
+
# @return [true, false]
|
146
|
+
#
|
147
|
+
# @example
|
148
|
+
# Time.now.in_dayset?("school")
|
149
|
+
#
|
150
|
+
def in_dayset?(set)
|
151
|
+
::Ephemeris.in_dayset?(set.to_s, self)
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# Calculate the number of days until a specific holiday
|
156
|
+
#
|
157
|
+
# @param [String, Symbol] holiday
|
158
|
+
# @param [String, nil] holiday_file Optional path to XML file to use for holiday definitions.
|
159
|
+
# @return [Integer]
|
160
|
+
# @raise [ArgumentError] if the holiday isn't valid
|
161
|
+
#
|
162
|
+
# @example
|
163
|
+
# Time.now.days_until(:christmas) # => 2
|
164
|
+
#
|
165
|
+
def days_until(holiday, holiday_file = nil)
|
166
|
+
holiday = holiday.to_s.upcase
|
167
|
+
r = ::Ephemeris.get_days_until(*[self, holiday, holiday_file || DSL.holiday_file].compact)
|
168
|
+
raise ArgumentError, "#{holiday.inspect} isn't a recognized holiday" if r == -1
|
169
|
+
|
170
|
+
r
|
171
|
+
end
|
172
|
+
|
173
|
+
# @endgroup
|
174
|
+
|
90
175
|
# @return [Integer, nil]
|
91
176
|
def <=>(other)
|
92
177
|
# compare instants, otherwise it will differ by timezone, which we don't want
|
@@ -5,6 +5,7 @@ require "date"
|
|
5
5
|
# Extensions to Date
|
6
6
|
class Date
|
7
7
|
include OpenHAB::CoreExt::Between
|
8
|
+
include OpenHAB::CoreExt::Ephemeris
|
8
9
|
|
9
10
|
#
|
10
11
|
# Extends {#+} to allow adding a {java.time.temporal.TemporalAmount TemporalAmount}
|
@@ -90,5 +91,6 @@ class Date
|
|
90
91
|
end
|
91
92
|
|
92
93
|
remove_method :inspect
|
94
|
+
# @return [String]
|
93
95
|
alias_method :inspect, :to_s
|
94
96
|
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenHAB
|
4
|
+
module DSL
|
5
|
+
#
|
6
|
+
# Provides the feature for debouncing calls to a given block.
|
7
|
+
#
|
8
|
+
# The debouncer can filter events and only allow the events on the leading or trailing edge
|
9
|
+
# of the given interval. Its behavior can be customized through settings passed to its
|
10
|
+
# {initialize constructor}.
|
11
|
+
#
|
12
|
+
# The following timing diagram illustrates the incoming triggers and the actual executions
|
13
|
+
# using various options.
|
14
|
+
#
|
15
|
+
# ```ruby
|
16
|
+
# 1 1 2 2 3 3 4 4
|
17
|
+
# 0 5 0 5 0 5 0 5 0 5
|
18
|
+
# Triggers : 'X.X...X...X..XX.X.X......XXXXXXXXXXX....X.....'
|
19
|
+
# leading: false
|
20
|
+
# for:5 : '|....X|....X |....X |....X|....X |....X'
|
21
|
+
# leading: true
|
22
|
+
# for:5 : 'X.....X......X....X......X....X....X....X.....'
|
23
|
+
#
|
24
|
+
# more options, leading: false
|
25
|
+
# Triggers : 'X.X...X...X..XX.X.X......XXXXXXXXXXX....X.....'
|
26
|
+
# for:5 idle:3 : '|....X|......X|......X...|............X.|....X'
|
27
|
+
# for:5 idle:5 : '|......................X.|..............X.....'
|
28
|
+
# for:5..5 idle:X : '|....X|....X.|....X......|....X|....X...|....X'
|
29
|
+
# for:5..6 idle:5 : '|.....X...|.....X.|....X.|.....X|.....X.|....X'
|
30
|
+
# for:5..7 idle:5 : '|......X..|......X|....X.|......X|......X.....'
|
31
|
+
# for:5..8 idle:5 : '|.......X.|.......X......|.......X|.....X.....'
|
32
|
+
# for:5..8 idle:3 : '|....X|......X|......X...|.......X|....X|....X'
|
33
|
+
# for:5..8 idle:2 : '|....X|.....X|......X....|.......X|....X|....X'
|
34
|
+
# ```
|
35
|
+
#
|
36
|
+
# Notes:
|
37
|
+
# - `|` indicates the start of the debounce period
|
38
|
+
# - With `for: 5..5` (a range with begin=end), the `idle_time` argument is irrelevant
|
39
|
+
# and be unset/set to any value as it will not alter the debouncer's behavior.
|
40
|
+
# - Without an `idle_time`, the range end in `for: X..Y` is irrelevant. It is equivalent to
|
41
|
+
# `for: X` without the end of the range.
|
42
|
+
#
|
43
|
+
class Debouncer
|
44
|
+
# @return [Range,nil] The range of accepted debounce period, or nil if debouncing is disabled.
|
45
|
+
attr_reader :interval
|
46
|
+
|
47
|
+
# @return [Duration, nil] The minimum idle time to stop debouncing.
|
48
|
+
attr_reader :idle_time
|
49
|
+
|
50
|
+
#
|
51
|
+
# Constructor to create a debouncer object.
|
52
|
+
#
|
53
|
+
# The constructor sets the options and behaviour of the debouncer when the {#call}
|
54
|
+
# method is called.
|
55
|
+
#
|
56
|
+
# Terminology:
|
57
|
+
# - `calls` are invocations of the {#call} method, i.e. the events that need to be throttled / debounced.
|
58
|
+
# - `executions` are the actual code executions of the given block. Executions usually occur
|
59
|
+
# less frequently than the call to the debounce method.
|
60
|
+
#
|
61
|
+
# @param [Duration,Range,nil] for The minimum and optional maximum execution interval.
|
62
|
+
# - {Duration}: The minimum interval between executions. The debouncer will not execute
|
63
|
+
# the given block more often than this.
|
64
|
+
# - {Range}: A range of {Duration}s for the minimum to maximum interval between executions.
|
65
|
+
# The range end defines the maximum duration from the initial trigger, at which
|
66
|
+
# the debouncer will execute the block, even when an `idle_time` argument was given and
|
67
|
+
# calls continue to occur at an interval less than `idle_time`.
|
68
|
+
# - `nil`: When `nil`, no debouncing is performed, all the other parameters are ignored,
|
69
|
+
# and every call will result in immediate execution of the given block.
|
70
|
+
#
|
71
|
+
# @param [true,false] leading
|
72
|
+
# - `true`: Perform leading edge "debouncing". Execute the first call then ignore
|
73
|
+
# subsequent calls that occur within the debounce period.
|
74
|
+
# - `false`: Perform trailing edge debouncing. Execute the last call at the end of
|
75
|
+
# the debounce period and ignore all the calls leading up to it.
|
76
|
+
#
|
77
|
+
# @param [Duration,nil] idle_time The minimum idle time between calls to stop debouncing.
|
78
|
+
# The debouncer will continue to hold until the interval between two calls is longer
|
79
|
+
# than the idle time or until the maximum interval between executions, when
|
80
|
+
# specified, is reached.
|
81
|
+
#
|
82
|
+
# @return [void]
|
83
|
+
#
|
84
|
+
def initialize(for:, leading: false, idle_time: nil)
|
85
|
+
@interval = binding.local_variable_get(:for)
|
86
|
+
return unless @interval
|
87
|
+
|
88
|
+
@interval = (@interval..) unless @interval.is_a?(Range)
|
89
|
+
|
90
|
+
@leading = leading
|
91
|
+
@idle_time = idle_time
|
92
|
+
@mutex = Mutex.new
|
93
|
+
@block = nil
|
94
|
+
@timer = nil
|
95
|
+
reset
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Debounces calls to the given block.
|
100
|
+
#
|
101
|
+
# This method is meant to be called repeatedly with the same given block.
|
102
|
+
# However, if no block is given, it will call and debounce the previously given block
|
103
|
+
#
|
104
|
+
# @yield Block to be debounced
|
105
|
+
#
|
106
|
+
# @return [void]
|
107
|
+
#
|
108
|
+
# @example Basic trailing edge debouncing
|
109
|
+
# debouncer = Debouncer.new(for: 1.minute)
|
110
|
+
# (1..100).each do
|
111
|
+
# debouncer.call { logger.info "I won't log more often than once a minute" }
|
112
|
+
# sleep 20 # call the debouncer every 20 seconds
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# @example Call the previous debounced block
|
116
|
+
# debouncer = Debouncer.new(for: 1.minute)
|
117
|
+
# debouncer.call { logger.info "Hello. It is #{Time.now}" } # First call to debounce
|
118
|
+
#
|
119
|
+
# after(20.seconds) do |timer|
|
120
|
+
# debouncer.call # Call the original block above
|
121
|
+
# timer.reschedule unless timer.cancelled?
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
def call(&block)
|
125
|
+
@block = block if block
|
126
|
+
raise ArgumentError, "No block has been provided" unless @block
|
127
|
+
|
128
|
+
return call! unless @interval # passthrough mode, no debouncing when @interval is nil
|
129
|
+
|
130
|
+
now = ZonedDateTime.now
|
131
|
+
if leading?
|
132
|
+
leading_edge_debounce(now)
|
133
|
+
else
|
134
|
+
trailing_edge_debounce(now)
|
135
|
+
end
|
136
|
+
@mutex.synchronize { @last_timestamp = now }
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# Executes the latest block passed to the {#debounce} call regardless of any debounce settings.
|
141
|
+
#
|
142
|
+
# @return [Object] The return value of the block
|
143
|
+
#
|
144
|
+
def call!
|
145
|
+
@block.call
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Resets the debounce period and cancels any outstanding block executions of a trailing edge debouncer.
|
150
|
+
#
|
151
|
+
# - A leading edge debouncer will execute its block on the next call and start a new debounce period.
|
152
|
+
# - A trailing edge debouncer will reset its debounce timer and the next call will become the start
|
153
|
+
# of a new debounce period.
|
154
|
+
#
|
155
|
+
# @return [Boolean] True if a pending execution was cancelled.
|
156
|
+
#
|
157
|
+
def reset
|
158
|
+
@mutex.synchronize do
|
159
|
+
@last_timestamp = @leading_timestamp = @interval.begin.ago - 1.second if leading?
|
160
|
+
@timer&.cancel
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# Immediately executes any outstanding event of a trailing edge debounce.
|
166
|
+
# The next call will start a new period.
|
167
|
+
#
|
168
|
+
# It has no effect on a leading edge debouncer - use {#reset} instead.
|
169
|
+
#
|
170
|
+
# @return [Boolean] True if an existing debounce timer was rescheduled to run immediately.
|
171
|
+
# False if there were no outstanding executions.
|
172
|
+
#
|
173
|
+
def flush
|
174
|
+
@mutex.synchronize do
|
175
|
+
if @timer&.cancel
|
176
|
+
call!
|
177
|
+
true
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
#
|
183
|
+
# Returns true to indicate that this is a leading edge debouncer.
|
184
|
+
#
|
185
|
+
# @return [true,false] True if this object was created to be a leading edge debouncer. False otherwise.
|
186
|
+
#
|
187
|
+
def leading?
|
188
|
+
@leading
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def too_soon?(now)
|
194
|
+
now < @leading_timestamp + @interval.begin
|
195
|
+
end
|
196
|
+
|
197
|
+
# @return [true,false] When max interval is not set/required, always returns false,
|
198
|
+
# because there is no maximum interval requirement.
|
199
|
+
# When it is set, return true if the max interval condition is met, or false otherwise
|
200
|
+
def max_interval?(now)
|
201
|
+
@interval.end && now >= @leading_timestamp + @interval.end
|
202
|
+
end
|
203
|
+
|
204
|
+
# @return [true,false] When idle_time is not set/required, always returns true,
|
205
|
+
# as if the idle time condition is met.
|
206
|
+
# When it is set, return true if the idle time condition is met, or false otherwise
|
207
|
+
def idle?(now)
|
208
|
+
@idle_time.nil? || now >= @last_timestamp + @idle_time
|
209
|
+
end
|
210
|
+
|
211
|
+
def leading_edge_debounce(now)
|
212
|
+
@mutex.synchronize do
|
213
|
+
next if too_soon?(now)
|
214
|
+
next unless idle?(now) || max_interval?(now)
|
215
|
+
|
216
|
+
@leading_timestamp = now
|
217
|
+
call!
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def start_timer(now)
|
222
|
+
@leading_timestamp = now
|
223
|
+
@timer = DSL.after(@interval.begin) { @mutex.synchronize { call! } }
|
224
|
+
end
|
225
|
+
|
226
|
+
def handle_leading_event(now)
|
227
|
+
@leading_timestamp = now
|
228
|
+
@initial_wait ||= [@interval.begin, @idle_time].compact.max
|
229
|
+
@timer.reschedule(@initial_wait)
|
230
|
+
end
|
231
|
+
|
232
|
+
def handle_intermediate_event(now)
|
233
|
+
execution_time = @leading_timestamp + @interval.begin
|
234
|
+
|
235
|
+
execution_time = [execution_time, now + @idle_time].max if @idle_time && (@last_timestamp + @idle_time != now)
|
236
|
+
if @interval.end
|
237
|
+
max_execution_time = @leading_timestamp + @interval.end
|
238
|
+
execution_time = max_execution_time if max_execution_time < execution_time
|
239
|
+
end
|
240
|
+
|
241
|
+
if execution_time <= now
|
242
|
+
@timer.cancel
|
243
|
+
call!
|
244
|
+
elsif execution_time > @timer.execution_time
|
245
|
+
@timer.reschedule(execution_time)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def trailing_edge_debounce(now)
|
250
|
+
@mutex.synchronize do
|
251
|
+
next start_timer(now) unless @timer
|
252
|
+
next handle_intermediate_event(now) if @timer.active?
|
253
|
+
|
254
|
+
handle_leading_event(now)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
@@ -97,8 +97,10 @@ module OpenHAB
|
|
97
97
|
def item(*args, **kwargs, &block)
|
98
98
|
item = ItemBuilder.new(*args, provider: provider, **kwargs)
|
99
99
|
item.instance_eval(&block) if block
|
100
|
-
provider.add(item)
|
101
|
-
Core::Items::Proxy.new(
|
100
|
+
r = provider.add(item)
|
101
|
+
return Core::Items::Proxy.new(r) if r.is_a?(Item)
|
102
|
+
|
103
|
+
item
|
102
104
|
end
|
103
105
|
end
|
104
106
|
|
@@ -17,10 +17,17 @@ module OpenHAB
|
|
17
17
|
# command. This is available on both the 'command' method and any
|
18
18
|
# command-specific methods, e.g. {SwitchItem#on}.
|
19
19
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
20
|
+
# The timer will be cancelled, and the item's state will not be changed
|
21
|
+
# to the on_expire state if:
|
22
|
+
# - The item receives any command within the timed command duration.
|
23
|
+
# - The item is updated to a different state, even if it is then updated
|
24
|
+
# back to the same state.
|
25
|
+
#
|
26
|
+
# For example, if you have a Switch on a timer and another rule sends
|
27
|
+
# a command to that item, even when it's commanded to the same state,
|
28
|
+
# the timer will be automatically canceled.
|
29
|
+
#
|
30
|
+
# Sending a different duration (for:) value for the timed
|
24
31
|
# command will reschedule the timed command for that new duration.
|
25
32
|
#
|
26
33
|
module TimedCommand
|
@@ -107,7 +114,7 @@ module OpenHAB
|
|
107
114
|
# no prior timed command
|
108
115
|
on_expire ||= default_on_expire(command)
|
109
116
|
super(command)
|
110
|
-
create_timed_command(duration: duration, on_expire: on_expire)
|
117
|
+
create_timed_command(command, duration: duration, on_expire: on_expire)
|
111
118
|
else
|
112
119
|
timed_command_details.mutex.synchronize do
|
113
120
|
if timed_command_details.resolution
|
@@ -116,7 +123,7 @@ module OpenHAB
|
|
116
123
|
# just create a new one
|
117
124
|
on_expire ||= default_on_expire(command)
|
118
125
|
super(command)
|
119
|
-
create_timed_command(duration: duration, on_expire: on_expire)
|
126
|
+
create_timed_command(command, duration: duration, on_expire: on_expire)
|
120
127
|
else
|
121
128
|
# timed command still pending; reset it
|
122
129
|
logger.trace "Outstanding Timed Command #{timed_command_details} encountered - rescheduling"
|
@@ -138,13 +145,13 @@ module OpenHAB
|
|
138
145
|
private
|
139
146
|
|
140
147
|
# Creates a new timed command and places it in the TimedCommand hash
|
141
|
-
def create_timed_command(duration:, on_expire:)
|
148
|
+
def create_timed_command(command, duration:, on_expire:)
|
142
149
|
timed_command_details = TimedCommandDetails.new(item: self,
|
143
150
|
on_expire: on_expire,
|
144
151
|
mutex: Mutex.new)
|
145
152
|
|
146
153
|
timed_command_details.timer = timed_command_timer(timed_command_details, duration)
|
147
|
-
cancel_rule = TimedCommandCancelRule.new(timed_command_details)
|
154
|
+
cancel_rule = TimedCommandCancelRule.new(command, timed_command_details)
|
148
155
|
unmanaged_rule = Core.automation_manager.add_unmanaged_rule(cancel_rule)
|
149
156
|
timed_command_details.rule_uid = unmanaged_rule.uid
|
150
157
|
Core::Rules::Provider.current.add(unmanaged_rule)
|
@@ -189,16 +196,27 @@ module OpenHAB
|
|
189
196
|
#
|
190
197
|
# @!visibility private
|
191
198
|
class TimedCommandCancelRule < org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule
|
192
|
-
def initialize(timed_command_details)
|
199
|
+
def initialize(command, timed_command_details)
|
193
200
|
super()
|
194
201
|
@timed_command_details = timed_command_details
|
195
202
|
# Capture rule name if known
|
196
203
|
@thread_locals = ThreadLocal.persist
|
197
204
|
self.name = "Cancel implicit timer for #{timed_command_details.item.name}"
|
198
|
-
self.triggers = [
|
199
|
-
|
200
|
-
|
201
|
-
|
205
|
+
self.triggers = [
|
206
|
+
Rules::RuleTriggers.trigger(
|
207
|
+
type: Rules::Triggers::Changed::ITEM_STATE_CHANGE,
|
208
|
+
config: {
|
209
|
+
"itemName" => timed_command_details.item.name,
|
210
|
+
"previousState" => timed_command_details.item.format_command(command).to_s
|
211
|
+
}
|
212
|
+
),
|
213
|
+
Rules::RuleTriggers.trigger(
|
214
|
+
type: Rules::Triggers::Command::ITEM_COMMAND,
|
215
|
+
config: {
|
216
|
+
"itemName" => timed_command_details.item.name
|
217
|
+
}
|
218
|
+
)
|
219
|
+
]
|
202
220
|
self.visibility = Core::Rules::Visibility::HIDDEN
|
203
221
|
end
|
204
222
|
|