openhab-scripting 4.7.1 → 4.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/openhab/dsl/items/items.rb +4 -3
- data/lib/openhab/dsl/items/timed_command.rb +189 -0
- data/lib/openhab/dsl/rules/automation_rule.rb +3 -4
- data/lib/openhab/dsl/rules/rule.rb +7 -4
- 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/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59b0441f0cce1fb707bfb5d74d051135ce37c63128359cfa98cdae2573b920d2
|
4
|
+
data.tar.gz: 0101f396a6bce5e6d52f3191973fb6db2f3cb3be93f00229b4cc5297f04488bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac154dd9d98ca0305c30770286554cb0b4bc863f1af95e7e03a32090aac7cc8f13633a42d2bcd8da7bc8b585f377a4db74d8a4ef668675ac0704fed9627f9973
|
7
|
+
data.tar.gz: bc241f0713e34124057fb124658dd13b4437bbe455bbf975c616aefd6a05b49a8d224039e23175dd7c1c350a17fa782eec2c8d22748e56f24cf8e618774396a2
|
@@ -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
|
@@ -230,9 +229,9 @@ module OpenHAB
|
|
230
229
|
logger.trace("Item changed to #{state} for #{trigger_delay}, rescheduling timer.")
|
231
230
|
trigger_delay.timer.reschedule(ZonedDateTime.now.plus(trigger_delay.duration))
|
232
231
|
else
|
233
|
-
logger.trace("Item changed to #{state} for #{trigger_delay},
|
232
|
+
logger.trace("Item changed to #{state} for #{trigger_delay}, canceling timer.")
|
234
233
|
trigger_delay.timer.cancel
|
235
|
-
# Reprocess trigger delay after
|
234
|
+
# Reprocess trigger delay after canceling to track new state (if guards matched, etc)
|
236
235
|
process_trigger_delay(trigger_delay, mod, inputs)
|
237
236
|
end
|
238
237
|
end
|
@@ -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
|
@@ -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
|
data/lib/openhab/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian O'Connell
|
@@ -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
|