openhab-scripting 4.7.1 → 4.8.3
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/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
|