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 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