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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 287e243222ea07f7a3157b208722d47f489f14456713ad86401292f6b2fda942
4
- data.tar.gz: 44968c29cf24415646ebb27ce64c3894966c4e9f9707edcd303eb89d4a4b1f04
3
+ metadata.gz: 4d0594ce30afe7ee9653b705d23d24bc48489f7c0d264a0dd246d59d4266dc82
4
+ data.tar.gz: 2bec61de8afbaf4bbe180f510f6b797a9ab59449c506fc41f374d485c128698c
5
5
  SHA512:
6
- metadata.gz: 12ce91f43449208a374b04ab92171a93c4ab3b68f2fee8ce7f948f7bf21f2be4a8d967b8f5758be957f5ca36cac831ebcb5f184204d650ed50d1fb345b1c2f7a
7
- data.tar.gz: d7da5a64ab6aef028308a998ea83a77da1d77d12cbccb24c34875f6e51a9c6360e204efd75287de4d4f1fbc17b9bcd722057da227821f519452945fb17fd1666
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} # 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}")
@@ -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
- 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
@@ -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}, cancelling timer.")
232
+ logger.trace("Item changed to #{state} for #{trigger_delay}, canceling timer.")
234
233
  trigger_delay.timer.cancel
235
- # Reprocess trigger delay after cancelling to track new state (if guards matched, etc)
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.logger(@rule_name.chomp.gsub(/\s+/, '_'))
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
- # 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
@@ -127,7 +127,7 @@ module OpenHAB
127
127
  #
128
128
  def logger
129
129
  if name
130
- Log.logger(name.chomp.gsub(/\s+/, '_'))
130
+ Log.logger_for(name.chomp.gsub(/\s+/, '_'))
131
131
  else
132
132
  super
133
133
  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, from, to_state)
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
@@ -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
@@ -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(::Units::DEGREE_ANGLE).to_big_decimal)
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, ::Units::DEGREE_ANGLE)
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.
@@ -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.name)
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 [String] name of the logger
147
+ # @param [Class] class the logger is for
148
148
  #
149
149
  # @return [Logger] for the supplied name
150
150
  #
151
- def logger(name)
152
- name ||= self.class.name
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] classname to configure logger for
164
+ # @param [String] name to configure logger for
160
165
  #
161
166
  # @return [Logger] for the supplied classname
162
167
  #
163
- def logger_for(classname)
164
- configure_logger_for(classname)
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(classname)
181
+ def configure_logger_for(name)
177
182
  log_prefix = Configuration.log_prefix
178
- log_prefix += if classname
179
- ".#{classname}"
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.class.name)
215
+ Log.logger(self)
211
216
  end
212
217
  end
213
218
  end
@@ -5,5 +5,5 @@
5
5
  #
6
6
  module OpenHAB
7
7
  # @return [String] Version of OpenHAB helper libraries
8
- VERSION = '4.6.2'
8
+ VERSION = '4.8.1'
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.6.2
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-24 00:00:00.000000000 Z
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