openhab-jrubyscripting 5.0.0.rc10 → 5.0.0.rc12
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/date_time_item.rb +3 -2
- data/lib/openhab/core/items/generic_item.rb +92 -1
- data/lib/openhab/core/items/item.rb +9 -8
- data/lib/openhab/core/items/metadata/hash.rb +1 -1
- data/lib/openhab/core/items/metadata/namespace_hash.rb +10 -2
- data/lib/openhab/core/items/metadata/provider.rb +2 -2
- data/lib/openhab/core/items/persistence.rb +99 -21
- 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 +12 -1
- data/lib/openhab/core/items/state_storage.rb +2 -2
- data/lib/openhab/core/items.rb +3 -3
- data/lib/openhab/core/profile_factory.rb +3 -1
- data/lib/openhab/core/proxy.rb +130 -0
- data/lib/openhab/core/registry.rb +12 -2
- data/lib/openhab/core/rules.rb +1 -1
- data/lib/openhab/core/things/links/provider.rb +39 -1
- data/lib/openhab/core/things/proxy.rb +8 -0
- data/lib/openhab/core/things/registry.rb +4 -0
- data/lib/openhab/core/timer.rb +3 -19
- data/lib/openhab/core/types/date_time_type.rb +3 -2
- 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.rb +3 -3
- 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 +27 -1
- data/lib/openhab/core_ext/java/local_date.rb +17 -7
- data/lib/openhab/core_ext/java/local_time.rb +13 -3
- data/lib/openhab/core_ext/java/month.rb +1 -1
- data/lib/openhab/core_ext/java/month_day.rb +15 -3
- data/lib/openhab/core_ext/java/period.rb +1 -1
- data/lib/openhab/core_ext/java/temporal_amount.rb +1 -1
- data/lib/openhab/core_ext/java/time.rb +5 -1
- data/lib/openhab/core_ext/java/zoned_date_time.rb +100 -2
- data/lib/openhab/core_ext/ruby/date.rb +4 -2
- data/lib/openhab/core_ext/ruby/date_time.rb +1 -0
- data/lib/openhab/core_ext/ruby/numeric.rb +6 -1
- 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 +29 -14
- data/lib/openhab/dsl/items/timed_command.rb +31 -13
- data/lib/openhab/dsl/rules/automation_rule.rb +30 -44
- data/lib/openhab/dsl/rules/builder.rb +404 -39
- 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 +251 -14
- data/lib/openhab/rspec/helpers.rb +3 -2
- data/lib/openhab/rspec/hooks.rb +6 -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/mocks/timer.rb +33 -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/rspec.rb +9 -0
- 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
@@ -7,15 +7,24 @@ module OpenHAB
|
|
7
7
|
module Java
|
8
8
|
java_import java.time.LocalDate
|
9
9
|
|
10
|
-
# Extensions to LocalDate
|
10
|
+
# Extensions to {java.time.LocalDate}
|
11
11
|
class LocalDate
|
12
12
|
include Time
|
13
13
|
include Between
|
14
|
+
include Ephemeris
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
# @!scope class
|
17
|
+
|
18
|
+
# @!attribute [r] now
|
19
|
+
# @return [LocalDate]
|
20
|
+
|
21
|
+
# @!method parse(text, formatter=nil)
|
22
|
+
# Converts the given text into a LocalDate.
|
23
|
+
# @param [String] text The text to parse
|
24
|
+
# @param [java.time.format.DateTimeFormatter] formatter The formatter to use
|
25
|
+
# @return [LocalDate]
|
26
|
+
|
27
|
+
# @!scope instance
|
19
28
|
|
20
29
|
# @param [TemporalAmount, LocalDate, Numeric] other
|
21
30
|
# If other is a Numeric, it's interpreted as days.
|
@@ -24,11 +33,11 @@ module OpenHAB
|
|
24
33
|
def -(other)
|
25
34
|
case other
|
26
35
|
when Date
|
27
|
-
|
36
|
+
self - other.to_local_date
|
28
37
|
when MonthDay
|
29
38
|
self - other.at_year(year)
|
30
39
|
when LocalDate
|
31
|
-
Period.
|
40
|
+
Period.between(other, self)
|
32
41
|
when Duration
|
33
42
|
minus_days(other.to_days)
|
34
43
|
when Numeric
|
@@ -66,6 +75,7 @@ module OpenHAB
|
|
66
75
|
Date.new(year, month_value, day_of_month)
|
67
76
|
end
|
68
77
|
|
78
|
+
# @return [Month]
|
69
79
|
alias_method :to_month, :month
|
70
80
|
|
71
81
|
# @return [MonthDay]
|
@@ -7,6 +7,8 @@ module OpenHAB
|
|
7
7
|
module Java
|
8
8
|
java_import java.time.LocalTime
|
9
9
|
|
10
|
+
#
|
11
|
+
# Extensions to {java.time.LocalTime}
|
10
12
|
#
|
11
13
|
# @example
|
12
14
|
# break_time = LocalTime::NOON
|
@@ -36,15 +38,23 @@ module OpenHAB
|
|
36
38
|
include Between
|
37
39
|
# @!parse include Time
|
38
40
|
|
39
|
-
# @!visibility private
|
40
41
|
class << self
|
42
|
+
# @!attribute [r] now
|
43
|
+
# @return [LocalTime]
|
44
|
+
|
45
|
+
# @!visibility private
|
46
|
+
alias_method :raw_parse, :parse
|
47
|
+
|
41
48
|
#
|
42
|
-
# Parses strings in the form "h[:mm[:ss]] [am/pm]"
|
49
|
+
# Parses strings in the form "h[:mm[:ss]] [am/pm]" when no formatter is given.
|
43
50
|
#
|
44
51
|
# @param [String] string
|
52
|
+
# @param [java.time.format.DateTimeFormatter] formatter The formatter to use
|
45
53
|
# @return [LocalTime]
|
46
54
|
#
|
47
|
-
def parse(string)
|
55
|
+
def parse(string, formatter = nil)
|
56
|
+
return raw_parse(string, formatter) if formatter
|
57
|
+
|
48
58
|
format = /(am|pm)$/i.match?(string) ? "h[:mm[:ss][.S]][ ]a" : "H[:mm[:ss][.S]]"
|
49
59
|
java_send(:parse, [java.lang.CharSequence, java.time.format.DateTimeFormatter],
|
50
60
|
string, java.time.format.DateTimeFormatterBuilder.new
|
@@ -7,9 +7,10 @@ module OpenHAB
|
|
7
7
|
module Java
|
8
8
|
java_import java.time.MonthDay
|
9
9
|
|
10
|
-
# Extensions to MonthDay
|
10
|
+
# Extensions to {java.time.MonthDay}
|
11
11
|
class MonthDay
|
12
12
|
include Between
|
13
|
+
include Ephemeris
|
13
14
|
|
14
15
|
class << self
|
15
16
|
#
|
@@ -38,12 +39,22 @@ module OpenHAB
|
|
38
39
|
|
39
40
|
# @return [MonthDay]
|
40
41
|
def +(other)
|
41
|
-
|
42
|
+
case other
|
43
|
+
when java.time.temporal.TemporalAmount, Numeric
|
44
|
+
(LocalDate.of(1900, month, day_of_month) + other).to_month_day
|
45
|
+
else
|
46
|
+
(to_local_date(other.to_local_date) + other).to_month_day
|
47
|
+
end
|
42
48
|
end
|
43
49
|
|
44
50
|
# @return [MonthDay, Period]
|
45
51
|
def -(other)
|
46
|
-
d =
|
52
|
+
d = case other
|
53
|
+
when java.time.temporal.TemporalAmount, Numeric
|
54
|
+
LocalDate.of(1900, month, day_of_month) - other
|
55
|
+
else
|
56
|
+
to_local_date(other.to_local_date) - other
|
57
|
+
end
|
47
58
|
return d if d.is_a?(java.time.Period)
|
48
59
|
|
49
60
|
d.to_month_day
|
@@ -76,6 +87,7 @@ module OpenHAB
|
|
76
87
|
year.at_month_day(self)
|
77
88
|
end
|
78
89
|
|
90
|
+
# @return [Month]
|
79
91
|
alias_method :to_month, :month
|
80
92
|
|
81
93
|
# @param [Date, nil] context
|
@@ -50,7 +50,11 @@ module OpenHAB
|
|
50
50
|
# Convert `other` to this class, if possible
|
51
51
|
# @return [Array, nil]
|
52
52
|
def coerce(other)
|
53
|
-
|
53
|
+
coercion_method = self.class.coercion_method
|
54
|
+
return unless other.respond_to?(coercion_method)
|
55
|
+
return [other.send(coercion_method), self] if other.method(coercion_method).arity.zero?
|
56
|
+
|
57
|
+
[other.send(coercion_method, self), self]
|
54
58
|
end
|
55
59
|
end
|
56
60
|
end
|
@@ -7,17 +7,33 @@ module OpenHAB
|
|
7
7
|
module Java
|
8
8
|
ZonedDateTime = java.time.ZonedDateTime
|
9
9
|
|
10
|
-
# Extensions to ZonedDateTime
|
10
|
+
# Extensions to {java.time.ZonedDateTime}
|
11
11
|
class ZonedDateTime
|
12
12
|
include Time
|
13
13
|
include Between
|
14
14
|
|
15
15
|
class << self # rubocop:disable Lint/EmptyClass
|
16
|
+
# @!scope class
|
17
|
+
|
16
18
|
# @!attribute [r] now
|
17
19
|
# @return [ZonedDateTime]
|
20
|
+
|
21
|
+
# @!method parse(text, formatter = nil)
|
22
|
+
# Parses a string into a ZonedDateTime object.
|
23
|
+
#
|
24
|
+
# @param [String] text The text to parse.
|
25
|
+
# @param [java.time.format.DateTimeFormatter] formatter The formatter to use.
|
26
|
+
# @return [ZonedDateTime]
|
27
|
+
end
|
28
|
+
|
29
|
+
# @!scope instance
|
30
|
+
|
31
|
+
# @return [LocalTime]
|
32
|
+
def to_local_time(_context = nil)
|
33
|
+
toLocalTime
|
18
34
|
end
|
19
35
|
|
20
|
-
|
36
|
+
# @return [Month]
|
21
37
|
alias_method :to_month, :month
|
22
38
|
|
23
39
|
# @param [TemporalAmount, #to_zoned_date_time, Numeric] other
|
@@ -87,6 +103,88 @@ module OpenHAB
|
|
87
103
|
self
|
88
104
|
end
|
89
105
|
|
106
|
+
# @group Ephemeris Methods
|
107
|
+
# (see CoreExt::Ephemeris)
|
108
|
+
|
109
|
+
#
|
110
|
+
# Name of the holiday for this date.
|
111
|
+
#
|
112
|
+
# @param [String, nil] holiday_file Optional path to XML file to use for holiday definitions.
|
113
|
+
# @return [Symbol, nil]
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# MonthDay.parse("12-25").holiday # => :christmas
|
117
|
+
#
|
118
|
+
def holiday(holiday_file = nil)
|
119
|
+
::Ephemeris.get_bank_holiday_name(*[self, holiday_file || DSL.holiday_file].compact)&.downcase&.to_sym
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# Determines if this date is on a holiday.
|
124
|
+
#
|
125
|
+
# @param [String, nil] holiday_file Optional path to XML file to use for holiday definitions.
|
126
|
+
# @return [true, false]
|
127
|
+
#
|
128
|
+
def holiday?(holiday_file = nil)
|
129
|
+
::Ephemeris.bank_holiday?(*[self, holiday_file || DSL.holiday_file].compact)
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Name of the closest holiday on or after this date.
|
134
|
+
#
|
135
|
+
# @param [String, nil] holiday_file Optional path to XML file to use for holiday definitions.
|
136
|
+
# @return [Symbol]
|
137
|
+
#
|
138
|
+
def next_holiday(holiday_file = nil)
|
139
|
+
::Ephemeris.get_next_bank_holiday(*[self, holiday_file || DSL.holiday_file].compact).downcase.to_sym
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Determines if this time is during a weekend.
|
144
|
+
#
|
145
|
+
# @return [true, false]
|
146
|
+
#
|
147
|
+
# @example
|
148
|
+
# Time.now.weekend?
|
149
|
+
#
|
150
|
+
def weekend?
|
151
|
+
::Ephemeris.weekend?(self)
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# Determines if this time is during a specific dayset
|
156
|
+
#
|
157
|
+
# @param [String, Symbol] set
|
158
|
+
# @return [true, false]
|
159
|
+
#
|
160
|
+
# @example
|
161
|
+
# Time.now.in_dayset?("school")
|
162
|
+
#
|
163
|
+
def in_dayset?(set)
|
164
|
+
::Ephemeris.in_dayset?(set.to_s, self)
|
165
|
+
end
|
166
|
+
|
167
|
+
#
|
168
|
+
# Calculate the number of days until a specific holiday
|
169
|
+
#
|
170
|
+
# @param [String, Symbol] holiday
|
171
|
+
# @param [String, nil] holiday_file Optional path to XML file to use for holiday definitions.
|
172
|
+
# @return [Integer]
|
173
|
+
# @raise [ArgumentError] if the holiday isn't valid
|
174
|
+
#
|
175
|
+
# @example
|
176
|
+
# Time.now.days_until(:christmas) # => 2
|
177
|
+
#
|
178
|
+
def days_until(holiday, holiday_file = nil)
|
179
|
+
holiday = holiday.to_s.upcase
|
180
|
+
r = ::Ephemeris.get_days_until(*[self, holiday, holiday_file || DSL.holiday_file].compact)
|
181
|
+
raise ArgumentError, "#{holiday.inspect} isn't a recognized holiday" if r == -1
|
182
|
+
|
183
|
+
r
|
184
|
+
end
|
185
|
+
|
186
|
+
# @endgroup
|
187
|
+
|
90
188
|
# @return [Integer, nil]
|
91
189
|
def <=>(other)
|
92
190
|
# 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}
|
@@ -84,11 +85,12 @@ class Date
|
|
84
85
|
#
|
85
86
|
def coerce(other)
|
86
87
|
return nil unless other.respond_to?(:to_date)
|
87
|
-
return [other.to_date
|
88
|
+
return [other.to_date, self] if other.method(:to_date).arity.zero?
|
88
89
|
|
89
|
-
[other.to_date, self]
|
90
|
+
[other.to_date(self), self]
|
90
91
|
end
|
91
92
|
|
92
93
|
remove_method :inspect
|
94
|
+
# @return [String]
|
93
95
|
alias_method :inspect, :to_s
|
94
96
|
end
|
@@ -151,7 +151,12 @@ module OpenHAB
|
|
151
151
|
# @return [QuantityType] `self` as a {QuantityType} of the supplied Unit
|
152
152
|
#
|
153
153
|
def |(unit) # rubocop:disable Naming/BinaryOperatorParameterName
|
154
|
-
|
154
|
+
if unit.respond_to?(:to_str)
|
155
|
+
parsed_unit = org.openhab.core.types.util.UnitUtils.parse_unit(unit.to_str)
|
156
|
+
raise ArgumentError, "Unknown unit #{unit}" unless parsed_unit
|
157
|
+
|
158
|
+
unit = parsed_unit
|
159
|
+
end
|
155
160
|
|
156
161
|
return super unless unit.is_a?(javax.measure.Unit)
|
157
162
|
|
@@ -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
|
|
@@ -185,6 +187,30 @@ module OpenHAB
|
|
185
187
|
def item_factory
|
186
188
|
@item_factory ||= org.openhab.core.library.CoreItemFactory.new
|
187
189
|
end
|
190
|
+
|
191
|
+
#
|
192
|
+
# Convert the given array to an array of strings.
|
193
|
+
# Convert Semantics classes to their simple name.
|
194
|
+
#
|
195
|
+
# @param [String,Symbol,Semantics::Tag] tags A list of strings, symbols, or Semantics classes
|
196
|
+
# @return [Array] An array of strings
|
197
|
+
#
|
198
|
+
# @example
|
199
|
+
# tags = normalize_tags("tag1", Semantics::LivingRoom)
|
200
|
+
#
|
201
|
+
# @!visibility private
|
202
|
+
def normalize_tags(*tags)
|
203
|
+
semantics = proc { |tag| tag.respond_to?(:java_class) && tag < Semantics::Tag }
|
204
|
+
|
205
|
+
tags.compact.map do |tag|
|
206
|
+
case tag
|
207
|
+
when String then tag
|
208
|
+
when Symbol then tag.to_s
|
209
|
+
when semantics then tag.java_class.simple_name
|
210
|
+
else raise ArgumentError, "`#{tag}` must be a subclass of Semantics::Tag, a `Symbol`, or a `String`."
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
188
214
|
end
|
189
215
|
|
190
216
|
# @param dimension [Symbol, nil] The unit dimension for a {NumberItem} (see {ItemBuilder#dimension})
|
@@ -292,18 +318,7 @@ module OpenHAB
|
|
292
318
|
# @return [void]
|
293
319
|
#
|
294
320
|
def tag(*tags)
|
295
|
-
|
296
|
-
tag.is_a?(String) ||
|
297
|
-
tag.is_a?(Symbol) ||
|
298
|
-
(tag.is_a?(Module) && tag < Semantics::Tag)
|
299
|
-
end
|
300
|
-
raise ArgumentError, "`tag` must be a subclass of Semantics::Tag, or a `String``."
|
301
|
-
end
|
302
|
-
|
303
|
-
tags.each do |tag|
|
304
|
-
tag = tag.name.split("::").last if tag.is_a?(Module) && tag < Semantics::Tag
|
305
|
-
@tags << tag.to_s
|
306
|
-
end
|
321
|
+
@tags += self.class.normalize_tags(*tags)
|
307
322
|
end
|
308
323
|
|
309
324
|
#
|