openhab-scripting 4.6.2 → 4.8.1
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/metadata.rb +3 -1
- 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 +8 -5
- data/lib/openhab/dsl/rules/rule_config.rb +1 -1
- data/lib/openhab/dsl/rules/triggers/changed.rb +1 -1
- 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 +7 -2
- data/lib/openhab/log/logger.rb +16 -11
- 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: 4d0594ce30afe7ee9653b705d23d24bc48489f7c0d264a0dd246d59d4266dc82
|
4
|
+
data.tar.gz: 2bec61de8afbaf4bbe180f510f6b797a9ab59449c506fc41f374d485c128698c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 419fd04010222b680c6d8a3c6c5c78752add3a7d0912333e9623a20b4ffe83d8fcc5a3ec62c9df811e3fb794611e3d5bf8961776af9ee3d74c2239fd8c10160e
|
7
|
+
data.tar.gz: f4e06ae879aee439acc8c6103093edd871f06e9c9ed89fb04fa74e5550849ea7e897e2e26b12c776aa93a2eacc4d9f0c00f2c3039ca73351e857cabd105d8577
|
@@ -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}")
|
@@ -25,6 +25,7 @@ module OpenHAB
|
|
25
25
|
extend Forwardable
|
26
26
|
|
27
27
|
def_delegator :@metadata, :value
|
28
|
+
def_delegator :__getobj__, :to_h, :to_hash
|
28
29
|
|
29
30
|
def initialize(metadata: nil, key: nil, value: nil, config: nil)
|
30
31
|
@metadata = metadata || Metadata.new(key || MetadataKey.new('', ''), value&.to_s, config)
|
@@ -68,6 +69,7 @@ module OpenHAB
|
|
68
69
|
# @return [Java::Org::openhab::core::items::Metadata] the old metadata
|
69
70
|
#
|
70
71
|
def config=(config)
|
72
|
+
config = config.to_hash if config.respond_to?(:to_hash)
|
71
73
|
raise ArgumentError, 'Configuration must be a hash' unless config.is_a? Hash
|
72
74
|
|
73
75
|
metadata = Metadata.new(@metadata&.uID, @metadata&.value, config)
|
@@ -158,7 +160,7 @@ module OpenHAB
|
|
158
160
|
meta_value, configuration = update_from_value(value)
|
159
161
|
|
160
162
|
key = MetadataKey.new(namespace, @item_name)
|
161
|
-
metadata = Metadata.new(key, meta_value&.to_s, configuration)
|
163
|
+
metadata = Metadata.new(key, meta_value&.to_s, configuration.to_h)
|
162
164
|
# registry.get can be omitted, but registry.update will log a warning for nonexistent metadata
|
163
165
|
if NamespaceAccessor.registry.get(key)
|
164
166
|
NamespaceAccessor.registry.update(metadata)
|
@@ -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
|
#
|
@@ -45,7 +50,7 @@ module OpenHAB
|
|
45
50
|
#
|
46
51
|
def logger
|
47
52
|
if @rule_name
|
48
|
-
Log.
|
53
|
+
Log.logger_for(@rule_name.chomp.gsub(/\s+/, '_'))
|
49
54
|
else
|
50
55
|
super
|
51
56
|
end
|
@@ -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
|
@@ -39,7 +39,7 @@ module OpenHAB
|
|
39
39
|
else
|
40
40
|
# Place in array and flatten to support multiple to elements or single or nil
|
41
41
|
[to].flatten.each do |to_state|
|
42
|
-
create_changed_trigger(item,
|
42
|
+
[from].flatten.each { |from_state| create_changed_trigger(item, from_state, to_state) }
|
43
43
|
end
|
44
44
|
end
|
45
45
|
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
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'java'
|
3
4
|
require_relative 'percent_type'
|
4
5
|
|
5
6
|
module OpenHAB
|
@@ -65,6 +66,10 @@ module OpenHAB
|
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
69
|
+
# Convert strings using java class
|
70
|
+
return value_of(args.first) if args.length == 1 && args.first.is_a?(String)
|
71
|
+
|
72
|
+
# use super constructor for empty args
|
68
73
|
return super unless args.length == 3
|
69
74
|
|
70
75
|
# convert from several numeric-like types to the exact types
|
@@ -73,7 +78,7 @@ module OpenHAB
|
|
73
78
|
args[0] = if hue.is_a?(DecimalType)
|
74
79
|
hue
|
75
80
|
elsif hue.is_a?(QuantityType)
|
76
|
-
DecimalType.new(hue.to_unit(
|
81
|
+
DecimalType.new(hue.to_unit(org.openhab.core.library.unit.Units::DEGREE_ANGLE).to_big_decimal)
|
77
82
|
elsif hue.respond_to?(:to_d)
|
78
83
|
DecimalType.new(hue)
|
79
84
|
end
|
@@ -145,7 +150,7 @@ module OpenHAB
|
|
145
150
|
# @!attribute [r] hue
|
146
151
|
# @return [QuantityType]
|
147
152
|
def hue
|
148
|
-
QuantityType.new(raw_hue.to_big_decimal,
|
153
|
+
QuantityType.new(raw_hue.to_big_decimal, org.openhab.core.library.unit.Units::DEGREE_ANGLE)
|
149
154
|
end
|
150
155
|
|
151
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
@@ -137,31 +137,36 @@ module OpenHAB
|
|
137
137
|
# @return [Logger] for the current class
|
138
138
|
#
|
139
139
|
def logger
|
140
|
-
Log.logger(self.class
|
140
|
+
Log.logger(self.class)
|
141
141
|
end
|
142
142
|
|
143
143
|
class << self
|
144
144
|
#
|
145
145
|
# Injects a logger into the base class
|
146
146
|
#
|
147
|
-
# @param [
|
147
|
+
# @param [Class] class the logger is for
|
148
148
|
#
|
149
149
|
# @return [Logger] for the supplied name
|
150
150
|
#
|
151
|
-
def logger(
|
152
|
-
|
151
|
+
def logger(klass)
|
152
|
+
if klass.respond_to?(:java_class) &&
|
153
|
+
klass.java_class &&
|
154
|
+
!klass.java_class.name.start_with?('org.jruby.Ruby')
|
155
|
+
klass = klass.java_class
|
156
|
+
end
|
157
|
+
name = klass.name
|
153
158
|
@loggers[name] ||= Log.logger_for(name)
|
154
159
|
end
|
155
160
|
|
156
161
|
#
|
157
162
|
# Configure a logger for the supplied class name
|
158
163
|
#
|
159
|
-
# @param [String]
|
164
|
+
# @param [String] name to configure logger for
|
160
165
|
#
|
161
166
|
# @return [Logger] for the supplied classname
|
162
167
|
#
|
163
|
-
def logger_for(
|
164
|
-
configure_logger_for(
|
168
|
+
def logger_for(name)
|
169
|
+
configure_logger_for(name)
|
165
170
|
end
|
166
171
|
|
167
172
|
private
|
@@ -173,10 +178,10 @@ module OpenHAB
|
|
173
178
|
#
|
174
179
|
# @return [Logger] Logger for the supplied classname
|
175
180
|
#
|
176
|
-
def configure_logger_for(
|
181
|
+
def configure_logger_for(name)
|
177
182
|
log_prefix = Configuration.log_prefix
|
178
|
-
log_prefix += if
|
179
|
-
".#{
|
183
|
+
log_prefix += if name
|
184
|
+
".#{name}"
|
180
185
|
else
|
181
186
|
".#{log_caller}"
|
182
187
|
end
|
@@ -207,7 +212,7 @@ module OpenHAB
|
|
207
212
|
def self.included(base)
|
208
213
|
class << base
|
209
214
|
def logger
|
210
|
-
Log.logger(self
|
215
|
+
Log.logger(self)
|
211
216
|
end
|
212
217
|
end
|
213
218
|
end
|
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.1
|
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-30 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
|