openhab-scripting 4.7.1 → 4.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/openhab/dsl/items/image_item.rb +1 -1
- data/lib/openhab/dsl/items/items.rb +4 -3
- data/lib/openhab/dsl/items/timed_command.rb +189 -0
- data/lib/openhab/dsl/rules/automation_rule.rb +8 -5
- data/lib/openhab/dsl/rules/rule.rb +7 -4
- data/lib/openhab/dsl/rules/triggers/changed.rb +6 -5
- data/lib/openhab/dsl/rules/triggers/command.rb +3 -3
- data/lib/openhab/dsl/rules/triggers/updated.rb +3 -3
- data/lib/openhab/dsl/timers/manager.rb +88 -0
- data/lib/openhab/dsl/timers/reentrant_timer.rb +43 -0
- data/lib/openhab/dsl/timers/timer.rb +96 -0
- data/lib/openhab/dsl/timers.rb +36 -88
- data/lib/openhab/dsl/types/hsb_type.rb +2 -2
- data/lib/openhab/log/logger.rb +1 -1
- data/lib/openhab/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f20bdd4334857cc9d65e95a218f509f90dd6f5b68d7acf550671f942385a426
|
4
|
+
data.tar.gz: a0bcdfba1ab5328f14b52ca16d9e56555fef6f479626aab11f655229a0c4f81a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd22e0e28fce41231eb34a2e0ed4c567b2457584f40852f9af7d684de6d981094bb8da5771b616191827fb42817e32cda3796b97aba8730de38fb95a80bb51b3
|
7
|
+
data.tar.gz: 00aeb56148a1c1117724515596a6ccdf8112246de26418b0ab0e978b75f2efc7766e545458e663a66f196d391c5eb9a281432d9aba6b5ef9b49250e1eae3702e
|
@@ -21,7 +21,7 @@ module OpenHAB
|
|
21
21
|
#
|
22
22
|
#
|
23
23
|
def update_from_file(file, mime_type: nil)
|
24
|
-
file_data =
|
24
|
+
file_data = File.binread(file)
|
25
25
|
mime_type ||= Marcel::MimeType.for(Pathname.new(file)) || Marcel::MimeType.for(file_data)
|
26
26
|
update_from_bytes(file_data, mime_type: mime_type)
|
27
27
|
end
|
@@ -22,6 +22,7 @@ require_relative 'rollershutter_item'
|
|
22
22
|
require_relative 'string_item'
|
23
23
|
|
24
24
|
require_relative 'ensure'
|
25
|
+
require_relative 'timed_command'
|
25
26
|
|
26
27
|
module OpenHAB
|
27
28
|
module DSL
|
@@ -65,9 +66,9 @@ module OpenHAB
|
|
65
66
|
|
66
67
|
logger.trace("Defining #{klass}##{command} for #{value}")
|
67
68
|
klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
68
|
-
def #{command}
|
69
|
-
command(#{value}) # command(ON)
|
70
|
-
end
|
69
|
+
def #{command}(for: nil, on_expire: nil, &block) # def on(for: nil, on_expire: nil, &block )
|
70
|
+
command(#{value}, for: binding.local_variable_get(:for), on_expire: on_expire, &block) # command(ON, for: nil, expire: nil, &block)
|
71
|
+
end # end
|
71
72
|
RUBY
|
72
73
|
|
73
74
|
logger.trace("Defining GroupItem::GroupMembers##{command} for #{value}")
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openhab/dsl/timers'
|
4
|
+
require 'openhab/dsl/rules/triggers/trigger'
|
5
|
+
require 'java'
|
6
|
+
|
7
|
+
require_relative 'generic_item'
|
8
|
+
|
9
|
+
module OpenHAB
|
10
|
+
module DSL
|
11
|
+
module Items
|
12
|
+
# Module enables timed commands e.g. Switch.on for: 3.minutes
|
13
|
+
module TimedCommand
|
14
|
+
# Stores information about timed commands
|
15
|
+
TimedCommandDetails = Struct.new(:item, :command, :was, :duration, :on_expire, :timer, :expired, :cancel_rule,
|
16
|
+
:rule_uid, keyword_init: true) do
|
17
|
+
def expired?
|
18
|
+
expired
|
19
|
+
end
|
20
|
+
|
21
|
+
def canceled?
|
22
|
+
!expired?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
@timed_commands = {}
|
27
|
+
|
28
|
+
class << self
|
29
|
+
attr_reader :timed_commands
|
30
|
+
end
|
31
|
+
|
32
|
+
# Extensions for {Items::GenericItem} to implement timed commands
|
33
|
+
module GenericItem
|
34
|
+
#
|
35
|
+
# Sends command to an item for specified duration, then on timer expiration sends
|
36
|
+
# the expiration command to the item
|
37
|
+
#
|
38
|
+
# @param [Types::Type] command to send to object
|
39
|
+
# @param [Duration] for duration for item to be in command state
|
40
|
+
# @param [Types::Type] on_expire Command to send when duration expires
|
41
|
+
#
|
42
|
+
#
|
43
|
+
# rubocop: disable Metrics/MethodLength
|
44
|
+
# The mutex makes this over 10 lines, but there is usable way to break thsi method up
|
45
|
+
def command(command, for: nil, on_expire: nil, &block)
|
46
|
+
duration = binding.local_variable_get(:for)
|
47
|
+
return super(command) unless duration
|
48
|
+
|
49
|
+
# Timer needs access to rule to disable, rule needs access to timer to cancel.
|
50
|
+
# Using a mutux to ensure neither fires before the other is constructed
|
51
|
+
semaphore = Mutex.new
|
52
|
+
|
53
|
+
semaphore.synchronize do
|
54
|
+
timed_command_details = TimedCommand.timed_commands[self]
|
55
|
+
if timed_command_details.nil?
|
56
|
+
create_timed_command(command: command, duration: duration,
|
57
|
+
semaphore: semaphore, on_expire: on_expire, &block)
|
58
|
+
else
|
59
|
+
logger.trace "Outstanding Timed Command #{timed_command_details} encountered - rescheduling"
|
60
|
+
timed_command_details.duration = duration # Capture updated duration
|
61
|
+
timed_command_details.timer.reschedule duration
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
# rubocop: enable Metrics/MethodLength
|
68
|
+
alias << command
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Creates a new timed command and places it in the TimedCommand hash
|
73
|
+
# rubocop: disable Metrics/AbcSize
|
74
|
+
# rubocop: disable Metrics/MethodLength
|
75
|
+
# There is no feasible way to break this method into smaller components
|
76
|
+
def create_timed_command(command:, duration:, semaphore:, on_expire:, &block)
|
77
|
+
on_expire ||= default_on_expire(command)
|
78
|
+
timed_command_details = TimedCommandDetails.new(item: self, command: command, was: state,
|
79
|
+
on_expire: on_expire, duration: duration)
|
80
|
+
|
81
|
+
# Send specified command after capturing current state
|
82
|
+
command(command)
|
83
|
+
|
84
|
+
timed_command_details.timer = timed_command_timer(timed_command_details, semaphore, &block)
|
85
|
+
timed_command_details.cancel_rule = TimedCommandCancelRule.new(timed_command_details, semaphore,
|
86
|
+
&block)
|
87
|
+
timed_command_details.rule_uid = OpenHAB::DSL::Rules.automation_manager
|
88
|
+
.addRule(timed_command_details.cancel_rule).getUID
|
89
|
+
logger.trace "Created Timed Command #{timed_command_details}"
|
90
|
+
TimedCommand.timed_commands[self] = timed_command_details
|
91
|
+
end
|
92
|
+
# rubocop: enable Metrics/AbcSize
|
93
|
+
# rubocop: enable Metrics/MethodLength
|
94
|
+
|
95
|
+
# Creates the timer to handle changing the item state when timer expires or invoking user supplied block
|
96
|
+
# @param [TimedCommandDetailes] timed_command_details details about the timed command
|
97
|
+
# @param [Mutex] semaphore Semaphore to lock on to prevent race condition between rule and timer
|
98
|
+
# @return [Timer] Timer
|
99
|
+
# rubocop: disable Metrics/MethodLength
|
100
|
+
# There is no feasible way to break this method into smaller components
|
101
|
+
def timed_command_timer(timed_command_details, semaphore, &block)
|
102
|
+
after(timed_command_details.duration, id: self) do
|
103
|
+
semaphore.synchronize do
|
104
|
+
logger.trace "Timed command expired - #{timed_command_details}"
|
105
|
+
cancel_timed_command_rule(timed_command_details)
|
106
|
+
timed_command_details.expired = true
|
107
|
+
if block
|
108
|
+
logger.trace "Invoking block #{block} after timed command for #{id} expired"
|
109
|
+
yield(timed_command_details)
|
110
|
+
else
|
111
|
+
command(timed_command_details.on_expire)
|
112
|
+
end
|
113
|
+
|
114
|
+
TimedCommand.timed_commands.delete(timed_command_details.item)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
# rubocop: enable Metrics/MethodLength
|
119
|
+
|
120
|
+
# Cancels timed command rule
|
121
|
+
# @param [TimedCommandDetailed] timed_command details about the timed command
|
122
|
+
def cancel_timed_command_rule(timed_command_details)
|
123
|
+
logger.trace "Removing rule: #{timed_command_details.rule_uid}"
|
124
|
+
OpenHAB::DSL::Rules.registry.remove(timed_command_details.rule_uid)
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# The default expire for ON/OFF is their inverse
|
129
|
+
#
|
130
|
+
def default_on_expire(command)
|
131
|
+
case format_type_pre(command)
|
132
|
+
when ON then OFF
|
133
|
+
when OFF then ON
|
134
|
+
else state
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# Rule to cancel timed commands
|
140
|
+
#
|
141
|
+
class TimedCommandCancelRule < Java::OrgOpenhabCoreAutomationModuleScriptRulesupportSharedSimple::SimpleRule
|
142
|
+
include OpenHAB::Log
|
143
|
+
def initialize(timed_command_details, semaphore, &block)
|
144
|
+
super()
|
145
|
+
@semaphore = semaphore
|
146
|
+
@timed_command_details = timed_command_details
|
147
|
+
@block = block
|
148
|
+
set_name("Cancels implicit timer for #{timed_command_details.item.id}")
|
149
|
+
set_triggers([OpenHAB::DSL::Rules::Triggers::Trigger.trigger(
|
150
|
+
type: OpenHAB::DSL::Rules::Triggers::Trigger::ITEM_STATE_UPDATE,
|
151
|
+
config: { 'itemName' => timed_command_details.item.name }
|
152
|
+
)])
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# Execute the rule
|
157
|
+
#
|
158
|
+
# @param [Map] mod map provided by OpenHAB rules engine
|
159
|
+
# @param [Map] inputs map provided by OpenHAB rules engine containing event and other information
|
160
|
+
#
|
161
|
+
#
|
162
|
+
# rubocop: disable Metrics/MethodLength
|
163
|
+
# rubocop: disable Metrics/AbcSize
|
164
|
+
# There is no feasible way to break this method into smaller components
|
165
|
+
def execute(_mod = nil, inputs = nil)
|
166
|
+
@semaphore.synchronize do
|
167
|
+
logger.trace "Canceling implicit timer #{@timed_command_details.timer} for "\
|
168
|
+
"#{@timed_command_details.item.id} because received event #{inputs}"
|
169
|
+
@timed_command_details.timer.cancel
|
170
|
+
# rubocop: disable Style/GlobalVars
|
171
|
+
# Disabled due to OpenHAB design
|
172
|
+
$scriptExtension.get('ruleRegistry').remove(@timed_command_details.rule_uid)
|
173
|
+
# rubocop: enable Style/GlobalVars
|
174
|
+
TimedCommand.timed_commands.delete(@timed_command_details.item)
|
175
|
+
if @block
|
176
|
+
logger.trace 'Executing user supplied block on timed command cancelation'
|
177
|
+
@block&.call(@timed_command_details)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
# rubocop: enable Metrics/MethodLength
|
182
|
+
# rubocop: enable Metrics/AbcSize
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
GenericItem.prepend(TimedCommand::GenericItem)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -68,8 +68,7 @@ module OpenHAB
|
|
68
68
|
# Cleanup any resources associated with automation rule
|
69
69
|
#
|
70
70
|
def cleanup
|
71
|
-
|
72
|
-
@trigger_delays.each_value { |trigger_delay| trigger_delay.timer&.cancel }
|
71
|
+
# No cleanup is necessary right now, trigger delays are tracked and cancelled by timers library
|
73
72
|
end
|
74
73
|
|
75
74
|
private
|
@@ -78,7 +77,6 @@ module OpenHAB
|
|
78
77
|
# Create the run queue based on guards
|
79
78
|
#
|
80
79
|
# @param [Map] inputs rule inputs
|
81
|
-
#
|
82
80
|
# @return [Queue] <description>
|
83
81
|
#
|
84
82
|
def create_queue(inputs)
|
@@ -230,9 +228,9 @@ module OpenHAB
|
|
230
228
|
logger.trace("Item changed to #{state} for #{trigger_delay}, rescheduling timer.")
|
231
229
|
trigger_delay.timer.reschedule(ZonedDateTime.now.plus(trigger_delay.duration))
|
232
230
|
else
|
233
|
-
logger.trace("Item changed to #{state} for #{trigger_delay},
|
231
|
+
logger.trace("Item changed to #{state} for #{trigger_delay}, canceling timer.")
|
234
232
|
trigger_delay.timer.cancel
|
235
|
-
# Reprocess trigger delay after
|
233
|
+
# Reprocess trigger delay after canceling to track new state (if guards matched, etc)
|
236
234
|
process_trigger_delay(trigger_delay, mod, inputs)
|
237
235
|
end
|
238
236
|
end
|
@@ -244,6 +242,8 @@ module OpenHAB
|
|
244
242
|
#
|
245
243
|
# @return [Boolean] True if guards says rule should execute, false otherwise
|
246
244
|
#
|
245
|
+
# rubocop:disable Metrics/MethodLength
|
246
|
+
# Loggging inflates method length
|
247
247
|
def check_guards(event:)
|
248
248
|
if @guard.should_run? event
|
249
249
|
now = TimeOfDay::TimeOfDay.now
|
@@ -255,7 +255,10 @@ module OpenHAB
|
|
255
255
|
logger.trace("Skipped execution of rule '#{name}' because of guard #{@guard}")
|
256
256
|
end
|
257
257
|
false
|
258
|
+
rescue StandardError => e
|
259
|
+
print_backtrace(e)
|
258
260
|
end
|
261
|
+
# rubocop:enable Metrics/MethodLength
|
259
262
|
|
260
263
|
#
|
261
264
|
# Process the run queue
|
@@ -15,8 +15,13 @@ module OpenHAB
|
|
15
15
|
module Rules
|
16
16
|
@script_rules = []
|
17
17
|
|
18
|
+
# rubocop: disable Style/GlobalVars
|
19
|
+
@automation_manager = $scriptExtension.get('automationManager')
|
20
|
+
@registry = $scriptExtension.get('ruleRegistry')
|
21
|
+
# rubocop: enable Style/GlobalVars
|
22
|
+
|
18
23
|
class << self
|
19
|
-
attr_reader :script_rules
|
24
|
+
attr_reader :script_rules, :automation_manager, :registry
|
20
25
|
end
|
21
26
|
|
22
27
|
#
|
@@ -134,9 +139,7 @@ module OpenHAB
|
|
134
139
|
#
|
135
140
|
#
|
136
141
|
def add_rule(rule)
|
137
|
-
|
138
|
-
$scriptExtension.get('automationManager').addRule(rule)
|
139
|
-
# rubocop: enable Style/GlobalVars
|
142
|
+
Rules.automation_manager.addRule(rule)
|
140
143
|
end
|
141
144
|
end
|
142
145
|
end
|
@@ -31,18 +31,18 @@ module OpenHAB
|
|
31
31
|
# @return [Trigger] OpenHAB trigger
|
32
32
|
#
|
33
33
|
def changed(*items, to: nil, from: nil, for: nil)
|
34
|
-
separate_groups(items).
|
34
|
+
separate_groups(items).map do |item|
|
35
35
|
logger.trace("Creating changed trigger for entity(#{item}), to(#{to}), from(#{from})")
|
36
36
|
# for is a reserved word in ruby, so use local_variable_get :for
|
37
37
|
if (wait_duration = binding.local_variable_get(:for))
|
38
38
|
changed_wait(item, to: to, from: from, duration: wait_duration)
|
39
39
|
else
|
40
40
|
# Place in array and flatten to support multiple to elements or single or nil
|
41
|
-
[to].flatten.
|
42
|
-
[from].flatten.
|
41
|
+
[to].flatten.map do |to_state|
|
42
|
+
[from].flatten.map { |from_state| create_changed_trigger(item, from_state, to_state) }
|
43
43
|
end
|
44
44
|
end
|
45
|
-
end
|
45
|
+
end.flatten
|
46
46
|
end
|
47
47
|
|
48
48
|
private
|
@@ -55,12 +55,13 @@ module OpenHAB
|
|
55
55
|
# @param [Item State] to OpenHAB Item State item or group needs to change to
|
56
56
|
# @param [Item State] from OpenHAB Item State item or group needs to be coming from
|
57
57
|
#
|
58
|
-
# @return [
|
58
|
+
# @return [Trigger] OpenHAB trigger
|
59
59
|
#
|
60
60
|
def changed_wait(item, duration:, to: nil, from: nil)
|
61
61
|
trigger = create_changed_trigger(item, nil, nil)
|
62
62
|
logger.trace("Creating Changed Wait Change Trigger for #{item}")
|
63
63
|
@trigger_delays[trigger.id] = TriggerDelay.new(to: to, from: from, duration: duration)
|
64
|
+
trigger
|
64
65
|
end
|
65
66
|
|
66
67
|
#
|
@@ -22,14 +22,14 @@ module OpenHAB
|
|
22
22
|
#
|
23
23
|
#
|
24
24
|
def received_command(*items, command: nil, commands: nil)
|
25
|
-
separate_groups(items).
|
25
|
+
separate_groups(items).map do |item|
|
26
26
|
logger.trace("Creating received command trigger for item(#{item})"\
|
27
27
|
"command(#{command}) commands(#{commands})")
|
28
28
|
|
29
29
|
# Combine command and commands, doing union so only a single nil will be in the combined array.
|
30
30
|
combined_commands = combine_commands(command, commands)
|
31
31
|
create_received_trigger(combined_commands, item)
|
32
|
-
end
|
32
|
+
end.flatten
|
33
33
|
end
|
34
34
|
|
35
35
|
private
|
@@ -42,7 +42,7 @@ module OpenHAB
|
|
42
42
|
#
|
43
43
|
#
|
44
44
|
def create_received_trigger(commands, item)
|
45
|
-
commands.
|
45
|
+
commands.map do |command|
|
46
46
|
if item.is_a? OpenHAB::DSL::Items::GroupItem::GroupMembers
|
47
47
|
config, trigger = create_group_command_trigger(item)
|
48
48
|
else
|
@@ -20,13 +20,13 @@ module OpenHAB
|
|
20
20
|
# @return [Trigger] Trigger for updated entity
|
21
21
|
#
|
22
22
|
def updated(*items, to: nil)
|
23
|
-
separate_groups(items).
|
23
|
+
separate_groups(items).map do |item|
|
24
24
|
logger.trace("Creating updated trigger for item(#{item}) to(#{to})")
|
25
|
-
[to].flatten.
|
25
|
+
[to].flatten.map do |to_state|
|
26
26
|
trigger, config = create_update_trigger(item, to_state)
|
27
27
|
append_trigger(trigger, config)
|
28
28
|
end
|
29
|
-
end
|
29
|
+
end.flatten
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openhab/log/logger'
|
4
|
+
require_relative 'reentrant_timer'
|
5
|
+
|
6
|
+
module OpenHAB
|
7
|
+
module DSL
|
8
|
+
#
|
9
|
+
# Provides access to and ruby wrappers around OpenHAB timers
|
10
|
+
#
|
11
|
+
module Timers
|
12
|
+
#
|
13
|
+
# Manages data structures that track timers
|
14
|
+
#
|
15
|
+
class TimerManager
|
16
|
+
include OpenHAB::Log
|
17
|
+
|
18
|
+
attr_reader :timer_ids
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
# Track timer IDs
|
22
|
+
@timer_ids = Hash.new { |hash, key| hash[key] = Set.new }
|
23
|
+
|
24
|
+
# Reentrant timer lookups
|
25
|
+
@reentrant_timers = {}
|
26
|
+
|
27
|
+
# Tracks active timers
|
28
|
+
@timers = Set.new
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Adds the current timer to the set of rule timers if being tracked
|
33
|
+
#
|
34
|
+
# rubocop: disable Metrics/AbcSize
|
35
|
+
# It does not make sense to break this up into seperate components
|
36
|
+
def add(timer)
|
37
|
+
logger.trace("Adding #{timer} to timers")
|
38
|
+
@timers << timer
|
39
|
+
|
40
|
+
if timer.respond_to? :id
|
41
|
+
logger.trace("Adding #{timer} with id #{timer.id.inspect} timer ids")
|
42
|
+
@timer_ids[timer.id] << timer
|
43
|
+
end
|
44
|
+
|
45
|
+
return unless timer.respond_to? :reentrant_id
|
46
|
+
|
47
|
+
logger.trace("Adding reeentrant #{timer} with reentrant id #{timer.reentrant_id} timer ids")
|
48
|
+
@reentrant_timers[timer.reentrant_id] = timer
|
49
|
+
end
|
50
|
+
# rubocop: enable Metrics/AbcSize
|
51
|
+
|
52
|
+
# Fetches the reentrant timer that matches the supplied id and block if it exists
|
53
|
+
#
|
54
|
+
# @param [Object] Object to associate with timer
|
55
|
+
# @param [Block] block to execute, block is passed a Timer object
|
56
|
+
#
|
57
|
+
# @return [RentrantTimer] Timer object if it exists, nil otherwise
|
58
|
+
#
|
59
|
+
def reentrant_timer(id:, &block)
|
60
|
+
reentrant_key = ReentrantTimer.reentrant_id(id: id, &block)
|
61
|
+
logger.trace("Checking for existing reentrant timer for #{reentrant_key}")
|
62
|
+
@reentrant_timers[reentrant_key]
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Delete the current timer to the set of rule timers if being tracked
|
67
|
+
#
|
68
|
+
def delete(timer)
|
69
|
+
logger.trace("Removing #{timer} from timers")
|
70
|
+
@timers.delete(timer)
|
71
|
+
@timer_ids[timer.id].delete(timer) if (timer.respond_to? :id) && (@timer_ids.key? timer.id)
|
72
|
+
@reentrant_timers.delete(timer.reentrant_id) if timer.respond_to? :reentrant_id
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# Cancels all active timers
|
77
|
+
#
|
78
|
+
def cancel_all
|
79
|
+
logger.trace("Canceling #{@timers.length} timers")
|
80
|
+
@timers.each(&:cancel)
|
81
|
+
@timer_ids.clear
|
82
|
+
@reentrant_timers.clear
|
83
|
+
@timers.clear
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openhab/log/logger'
|
4
|
+
require_relative 'timer'
|
5
|
+
|
6
|
+
module OpenHAB
|
7
|
+
module DSL
|
8
|
+
#
|
9
|
+
# Provides access to and ruby wrappers around OpenHAB timers
|
10
|
+
#
|
11
|
+
module Timers
|
12
|
+
# A reentrant timer is a timer that is automatically rescheduled
|
13
|
+
# when the block it refers to is encountered again
|
14
|
+
#
|
15
|
+
# @author Brian O'Connell
|
16
|
+
# @since 2.0.0
|
17
|
+
class ReentrantTimer < Timer
|
18
|
+
include OpenHAB::Log
|
19
|
+
|
20
|
+
attr_reader :id, :reentrant_id
|
21
|
+
|
22
|
+
#
|
23
|
+
# Create a new Timer Object
|
24
|
+
#
|
25
|
+
# @param [Duration] duration Duration until timer should fire
|
26
|
+
# @param [Block] block Block to execute when timer fires
|
27
|
+
#
|
28
|
+
def initialize(duration:, id:, &block)
|
29
|
+
raise 'Reentrant timers do not work in dynamically generated code' unless block.source_location
|
30
|
+
|
31
|
+
@id = id
|
32
|
+
@reentrant_id = self.class.reentrant_id(id: id, &block)
|
33
|
+
super(duration: duration, &block)
|
34
|
+
logger.trace("Created Reentrant Timer #{self} with reentrant Key #{@reentrant_id}")
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.reentrant_id(id:, &block)
|
38
|
+
[id, block.source_location].flatten
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'java'
|
4
|
+
require 'delegate'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'openhab/log/logger'
|
7
|
+
|
8
|
+
module OpenHAB
|
9
|
+
module DSL
|
10
|
+
#
|
11
|
+
# Provides access to and ruby wrappers around OpenHAB timers
|
12
|
+
#
|
13
|
+
module Timers
|
14
|
+
include OpenHAB::Log
|
15
|
+
java_import org.openhab.core.model.script.actions.ScriptExecution
|
16
|
+
java_import java.time.ZonedDateTime
|
17
|
+
|
18
|
+
# Ruby wrapper for OpenHAB Timer
|
19
|
+
# This class implements delegator to delegate methods to the OpenHAB timer
|
20
|
+
#
|
21
|
+
# @author Brian O'Connell
|
22
|
+
# @since 2.0.0
|
23
|
+
class Timer < SimpleDelegator
|
24
|
+
include OpenHAB::Log
|
25
|
+
extend Forwardable
|
26
|
+
|
27
|
+
def_delegator :@timer, :is_active, :active?
|
28
|
+
def_delegator :@timer, :is_running, :running?
|
29
|
+
def_delegator :@timer, :has_terminated, :terminated?
|
30
|
+
|
31
|
+
#
|
32
|
+
# Create a new Timer Object
|
33
|
+
#
|
34
|
+
# @param [Duration] duration Duration until timer should fire
|
35
|
+
# @param [Block] block Block to execute when timer fires
|
36
|
+
#
|
37
|
+
def initialize(duration:, &block)
|
38
|
+
@duration = duration
|
39
|
+
|
40
|
+
# A semaphore is used to prevent a race condition in which calling the block from the timer thread
|
41
|
+
# occurs before the @timer variable can be set resulting in @timer being nil
|
42
|
+
semaphore = Mutex.new
|
43
|
+
|
44
|
+
semaphore.synchronize do
|
45
|
+
@timer = ScriptExecution.createTimer(
|
46
|
+
ZonedDateTime.now.plus(@duration), timer_block(semaphore, &block)
|
47
|
+
)
|
48
|
+
@rule_timers = Thread.current[:rule_timers]
|
49
|
+
super(@timer)
|
50
|
+
Timers.timer_manager.add(self)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Reschedule timer
|
56
|
+
#
|
57
|
+
# @param [Duration] duration
|
58
|
+
#
|
59
|
+
# @return [Timer] Rescheduled timer instances
|
60
|
+
#
|
61
|
+
def reschedule(duration = nil)
|
62
|
+
duration ||= @duration
|
63
|
+
Timers.timer_manager.add(self)
|
64
|
+
@timer.reschedule(ZonedDateTime.now.plus(duration))
|
65
|
+
end
|
66
|
+
|
67
|
+
# Cancel timer
|
68
|
+
#
|
69
|
+
# @return [Boolean] True if cancel was successful, false otherwise
|
70
|
+
#
|
71
|
+
def cancel
|
72
|
+
Timers.timer_manager.delete(self)
|
73
|
+
@timer.cancel
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
#
|
79
|
+
# Constructs a block to execute timer within
|
80
|
+
#
|
81
|
+
# @param [Semaphore] Semaphore to obtain before executing
|
82
|
+
#
|
83
|
+
# @return [Proc] Block for timer to execute
|
84
|
+
#
|
85
|
+
def timer_block(semaphore)
|
86
|
+
proc {
|
87
|
+
semaphore.synchronize do
|
88
|
+
Timers.timer_manager.delete(self)
|
89
|
+
yield(self)
|
90
|
+
end
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/openhab/dsl/timers.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
require 'openhab/log/logger'
|
3
|
+
require_relative 'timers/timer'
|
4
|
+
require_relative 'timers/manager'
|
5
|
+
require_relative 'timers/reentrant_timer'
|
7
6
|
|
8
7
|
module OpenHAB
|
9
8
|
module DSL
|
@@ -12,109 +11,58 @@ module OpenHAB
|
|
12
11
|
#
|
13
12
|
module Timers
|
14
13
|
include OpenHAB::Log
|
15
|
-
java_import org.openhab.core.model.script.actions.ScriptExecution
|
16
|
-
java_import java.time.ZonedDateTime
|
17
14
|
|
18
|
-
#
|
19
|
-
@
|
20
|
-
class << self
|
21
|
-
attr_accessor :timers
|
22
|
-
end
|
23
|
-
|
24
|
-
# Ruby wrapper for OpenHAB Timer
|
25
|
-
# This class implements delegator to delegate methods to the OpenHAB timer
|
26
|
-
#
|
27
|
-
# @author Brian O'Connell
|
28
|
-
# @since 2.0.0
|
29
|
-
class Timer < SimpleDelegator
|
30
|
-
include OpenHAB::Log
|
31
|
-
extend Forwardable
|
32
|
-
|
33
|
-
def_delegator :@timer, :is_active, :active?
|
34
|
-
def_delegator :@timer, :is_running, :running?
|
35
|
-
def_delegator :@timer, :has_terminated, :terminated?
|
36
|
-
|
37
|
-
#
|
38
|
-
# Create a new Timer Object
|
39
|
-
#
|
40
|
-
# @param [Duration] duration Duration until timer should fire
|
41
|
-
# @param [Block] block Block to execute when timer fires
|
42
|
-
#
|
43
|
-
def initialize(duration:, &block)
|
44
|
-
@duration = duration
|
45
|
-
|
46
|
-
# A semaphore is used to prevent a race condition in which calling the block from the timer thread
|
47
|
-
# occurs before the @timer variable can be set resulting in @timer being nil
|
48
|
-
semaphore = Mutex.new
|
49
|
-
|
50
|
-
semaphore.synchronize do
|
51
|
-
@timer = ScriptExecution.createTimer(
|
52
|
-
ZonedDateTime.now.plus(@duration), timer_block(semaphore, &block)
|
53
|
-
)
|
54
|
-
super(@timer)
|
55
|
-
Timers.timers << self
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
#
|
60
|
-
# Reschedule timer
|
61
|
-
#
|
62
|
-
# @param [Duration] duration
|
63
|
-
#
|
64
|
-
# @return [Timer] Rescheduled timer instances
|
65
|
-
#
|
66
|
-
def reschedule(duration = nil)
|
67
|
-
duration ||= @duration
|
68
|
-
Timers.timers << self
|
69
|
-
@timer.reschedule(ZonedDateTime.now.plus(duration))
|
70
|
-
end
|
71
|
-
|
72
|
-
# Cancel timer
|
73
|
-
#
|
74
|
-
# @return [Boolean] True if cancel was successful, false otherwise
|
75
|
-
#
|
76
|
-
def cancel
|
77
|
-
Timers.timers.delete(self)
|
78
|
-
@timer.cancel
|
79
|
-
end
|
15
|
+
# Manages timers
|
16
|
+
@timer_manager = TimerManager.new
|
80
17
|
|
81
|
-
|
82
|
-
|
83
|
-
#
|
84
|
-
# Constructs a block to execute timer within
|
85
|
-
#
|
86
|
-
# @param [Semaphore] Semaphore to obtain before executing
|
87
|
-
#
|
88
|
-
# @return [Proc] Block for timer to execute
|
89
|
-
#
|
90
|
-
def timer_block(semaphore)
|
91
|
-
proc {
|
92
|
-
semaphore.synchronize do
|
93
|
-
Timers.timers.delete(self)
|
94
|
-
yield(self)
|
95
|
-
end
|
96
|
-
}
|
97
|
-
end
|
18
|
+
class << self
|
19
|
+
attr_reader :timer_manager
|
98
20
|
end
|
99
21
|
|
100
22
|
#
|
101
23
|
# Execute the supplied block after the specified duration
|
102
24
|
#
|
103
25
|
# @param [Duration] duration after which to execute the block
|
26
|
+
# @param [Object] id to associate with timer
|
104
27
|
# @param [Block] block to execute, block is passed a Timer object
|
105
28
|
#
|
106
29
|
# @return [Timer] Timer object
|
107
30
|
#
|
108
|
-
def after(duration, &block)
|
31
|
+
def after(duration, id: nil, &block)
|
32
|
+
return Timers.reentrant_timer(duration: duration, id: id, &block) if id
|
33
|
+
|
109
34
|
Timer.new(duration: duration, &block)
|
110
35
|
end
|
111
36
|
|
37
|
+
#
|
38
|
+
# Provdes access to the hash for mapping timer ids to the set of active timers associated with that id
|
39
|
+
# @return [Hash] hash of user specified ids to sets of times
|
40
|
+
def timers
|
41
|
+
Timers.timer_manager.timer_ids
|
42
|
+
end
|
43
|
+
|
112
44
|
#
|
113
45
|
# Cancels all active timers
|
114
46
|
#
|
115
47
|
def self.cancel_all
|
116
|
-
|
117
|
-
|
48
|
+
@timer_manager.cancel_all
|
49
|
+
end
|
50
|
+
|
51
|
+
# Create or reschedule a reentrant time
|
52
|
+
#
|
53
|
+
# @param [Duration] duration after which to execute the block
|
54
|
+
# @param [Object] id to associate with timer
|
55
|
+
# @param [Block] block to execute, block is passed a Timer object
|
56
|
+
# @return [ReentrantTimer] Timer object
|
57
|
+
def self.reentrant_timer(duration:, id:, &block)
|
58
|
+
timer = @timer_manager.reentrant_timer(id: id, &block)
|
59
|
+
if timer
|
60
|
+
logger.trace("Reentrant timer found - #{timer}")
|
61
|
+
timer.reschedule
|
62
|
+
else
|
63
|
+
logger.trace('No reentrant timer found, creating new timer')
|
64
|
+
ReentrantTimer.new(duration: duration, id: id, &block)
|
65
|
+
end
|
118
66
|
end
|
119
67
|
end
|
120
68
|
end
|
@@ -78,7 +78,7 @@ module OpenHAB
|
|
78
78
|
args[0] = if hue.is_a?(DecimalType)
|
79
79
|
hue
|
80
80
|
elsif hue.is_a?(QuantityType)
|
81
|
-
DecimalType.new(hue.to_unit(
|
81
|
+
DecimalType.new(hue.to_unit(org.openhab.core.library.unit.Units::DEGREE_ANGLE).to_big_decimal)
|
82
82
|
elsif hue.respond_to?(:to_d)
|
83
83
|
DecimalType.new(hue)
|
84
84
|
end
|
@@ -150,7 +150,7 @@ module OpenHAB
|
|
150
150
|
# @!attribute [r] hue
|
151
151
|
# @return [QuantityType]
|
152
152
|
def hue
|
153
|
-
QuantityType.new(raw_hue.to_big_decimal,
|
153
|
+
QuantityType.new(raw_hue.to_big_decimal, org.openhab.core.library.unit.Units::DEGREE_ANGLE)
|
154
154
|
end
|
155
155
|
|
156
156
|
# Convert to a packed 32-bit RGB value representing the color in the default sRGB color model.
|
data/lib/openhab/log/logger.rb
CHANGED
@@ -73,7 +73,7 @@ module OpenHAB
|
|
73
73
|
return error if debug_enabled?
|
74
74
|
|
75
75
|
if error.respond_to? :backtrace_locations
|
76
|
-
backtrace = error.backtrace_locations.map(&:to_s).
|
76
|
+
backtrace = error.backtrace_locations.map(&:to_s).grep_v(INTERNAL_CALL_REGEX)
|
77
77
|
error.set_backtrace(backtrace)
|
78
78
|
elsif error.respond_to? :stack_trace
|
79
79
|
backtrace = error.stack_trace.reject { |line| JAVA_INTERNAL_CALL_REGEX.match? line.to_s }
|
data/lib/openhab/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openhab-scripting
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.8.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian O'Connell
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-10-
|
11
|
+
date: 2021-10-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -77,6 +77,7 @@ files:
|
|
77
77
|
- lib/openhab/dsl/items/rollershutter_item.rb
|
78
78
|
- lib/openhab/dsl/items/string_item.rb
|
79
79
|
- lib/openhab/dsl/items/switch_item.rb
|
80
|
+
- lib/openhab/dsl/items/timed_command.rb
|
80
81
|
- lib/openhab/dsl/lazy_array.rb
|
81
82
|
- lib/openhab/dsl/monkey_patch/actions/actions.rb
|
82
83
|
- lib/openhab/dsl/monkey_patch/actions/script_thing_actions.rb
|
@@ -106,6 +107,9 @@ files:
|
|
106
107
|
- lib/openhab/dsl/things.rb
|
107
108
|
- lib/openhab/dsl/time_of_day.rb
|
108
109
|
- lib/openhab/dsl/timers.rb
|
110
|
+
- lib/openhab/dsl/timers/manager.rb
|
111
|
+
- lib/openhab/dsl/timers/reentrant_timer.rb
|
112
|
+
- lib/openhab/dsl/timers/timer.rb
|
109
113
|
- lib/openhab/dsl/types/comparable_type.rb
|
110
114
|
- lib/openhab/dsl/types/date_time_type.rb
|
111
115
|
- lib/openhab/dsl/types/decimal_type.rb
|