openhab-scripting 4.6.2 → 4.8.1
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/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
|