openhab-scripting 4.7.1 → 4.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5e39f0dd326751e6c61fcf44a85f6a9033c72fc6196f899d59305f7e77ff2cf
4
- data.tar.gz: 207a22d5633a711bdc333d1fb94befa787a6fa63a1b1050607c9f7665e1149f7
3
+ metadata.gz: 6f20bdd4334857cc9d65e95a218f509f90dd6f5b68d7acf550671f942385a426
4
+ data.tar.gz: a0bcdfba1ab5328f14b52ca16d9e56555fef6f479626aab11f655229a0c4f81a
5
5
  SHA512:
6
- metadata.gz: cc744a57eaad8487ffcc3bb31584bfd484ee27de0948c07a1ac47be5953e5ee2fda4a1a0713f49fda4c289ffb298908d32d69741ce83ee3c526f0bec7c5ae173
7
- data.tar.gz: 42a1fa4aa68bc0d81399f02de37761598005bed645b105f85f14af6d3419578938a3cbd33cd8a6c03b0fed46031c25730dca23074063d98ba76cc0eea43ccf24
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 = IO.binread(file)
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} # def on
69
- command(#{value}) # command(ON)
70
- end # 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
- logger.trace "Cancelling #{@trigger_delays.length} Trigger Delays(s) for rule '#{name}'"
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}, cancelling timer.")
231
+ logger.trace("Item changed to #{state} for #{trigger_delay}, canceling timer.")
234
232
  trigger_delay.timer.cancel
235
- # Reprocess trigger delay after cancelling to track new state (if guards matched, etc)
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
- # rubocop: disable Style/GlobalVars
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).each do |item|
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.each do |to_state|
42
- [from].flatten.each { |from_state| create_changed_trigger(item, from_state, to_state) }
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 [Array] Array of current TriggerDelay objects
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).each do |item|
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.each do |command|
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).each do |item|
23
+ separate_groups(items).map do |item|
24
24
  logger.trace("Creating updated trigger for item(#{item}) to(#{to})")
25
- [to].flatten.each do |to_state|
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
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'java'
4
- require 'delegate'
5
- require 'forwardable'
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
- # Tracks active timers
19
- @timers = Set.new
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
- private
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
- logger.trace("Cancelling #{@timers.length} timers")
117
- @timers.each(&:cancel)
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(::Units::DEGREE_ANGLE).to_big_decimal)
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, ::Units::DEGREE_ANGLE)
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.
@@ -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).reject { |line| INTERNAL_CALL_REGEX.match? line }
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 }
@@ -5,5 +5,5 @@
5
5
  #
6
6
  module OpenHAB
7
7
  # @return [String] Version of OpenHAB helper libraries
8
- VERSION = '4.7.1'
8
+ VERSION = '4.8.3'
9
9
  end
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.7.1
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-29 00:00:00.000000000 Z
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