openhab-scripting 4.18.0 → 4.21.0

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: 775d56e757a494c9b854b963f2c15393c2521597cd83e5d36452a41033508339
4
- data.tar.gz: 9dfd101285ad28b310156016e524a370352b192147f3fbf87d130135b5a48b9d
3
+ metadata.gz: 330baeb4ed7472cdf6c60ba060d0b4482f988fa72c8ff0dd33b00c3e2df28b4a
4
+ data.tar.gz: 35cab5382ecb542fcc90f02200c8ac977ccf4c1c880e2b644df2715c11fc36b1
5
5
  SHA512:
6
- metadata.gz: 773d6bc398e5b24a9aecfb265c6025c75c4863ddf80932e875e9cb60eea4e6fdbf8036803d17c25dfd429127fc5164f1264b4bc48f7fce2cee1cd3fde837372a
7
- data.tar.gz: 2e19d2dd05b40da151bd85a253e022209c93819acd20872bd1a30f02d9ad96cdb931c81499f7c222c2c487f962cb56f9db851dee6e3c038a671bac4d44f5481d
6
+ metadata.gz: a8c14ed23a21431328a55326313c06d16fdd0447426e741270d2f26ada96dee1425fb8e30da96b6d515b6c1406392ec56c2d3e598f5822b0a528273bacbbf441
7
+ data.tar.gz: 265d8939f4c02ccd883c006903d759c8f5208c6c80035a831344055c44c94880e703f76830ecd535464a8cbddd0d4e543cf5af23e35f379b3c532c9525cc9f2f
@@ -18,14 +18,14 @@ module OpenHAB
18
18
  #
19
19
  #
20
20
  def self.wait_till_openhab_ready
21
- logger.debug('Checking readyness of OpenHAB')
21
+ logger.trace('Checking readyness of OpenHAB')
22
22
  # rubocop: disable Style/GlobalVars
23
23
  until $scriptExtension.get('automationManager')
24
- logger.debug("Automation manager not loaded, checking again in #{CHECK_DELAY} seconds.")
24
+ logger.trace("Automation manager not loaded, checking again in #{CHECK_DELAY} seconds.")
25
25
  sleep CHECK_DELAY
26
26
  end
27
27
  # rubocop: enable Style/GlobalVars
28
- logger.debug 'Automation manager instantiated, OpenHAB ready for rule processing.'
28
+ logger.trace 'Automation manager instantiated, OpenHAB ready for rule processing.'
29
29
  end
30
30
  end
31
31
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'openhab/log/logger'
4
+
3
5
  # OpenHAB main module
4
6
  module OpenHAB
5
7
  module Core
@@ -7,15 +9,18 @@ module OpenHAB
7
9
  # Manages thread local varaibles for access inside of blocks
8
10
  #
9
11
  module ThreadLocal
12
+ include OpenHAB::Log
13
+
10
14
  #
11
15
  # Execute the supplied block with the supplied values set for the currently running thread
12
16
  # The previous values for each key are restored after the block is executed
13
17
  #
14
- # @param [Hash] Keys and values to set for running thread
18
+ # @param [Hash] Keys and values to set for running thread, if hash is nil no values are set
15
19
  #
16
20
  def thread_local(**values)
17
21
  old_values = values.map { |key, _value| [key, Thread.current[key]] }.to_h
18
22
  values.each { |key, value| Thread.current[key] = value }
23
+ logger.trace "Executing block with thread local context: #{values} - old context: #{old_values}"
19
24
  yield
20
25
  ensure
21
26
  old_values.each { |key, value| Thread.current[key] = value }
@@ -19,6 +19,7 @@ require 'openhab/dsl/things'
19
19
  require 'openhab/dsl/between'
20
20
  require 'openhab/dsl/gems'
21
21
  require 'openhab/dsl/persistence'
22
+ require 'openhab/dsl/uid'
22
23
  require 'openhab/dsl/units'
23
24
  require 'openhab/dsl/states'
24
25
 
@@ -33,7 +33,7 @@ module OpenHAB
33
33
  #
34
34
  #
35
35
  def update_from_url(uri)
36
- logger.debug("Downloading image from #{uri}")
36
+ logger.trace("Downloading image from #{uri}")
37
37
  response = Net::HTTP.get_response(URI(uri))
38
38
  mime_type = response['content-type']
39
39
  bytes = response.body
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'openhab/dsl/timers'
4
4
  require 'openhab/dsl/rules/triggers/trigger'
5
+ require 'openhab/log/logger'
5
6
  require 'java'
6
7
 
7
8
  require_relative 'generic_item'
@@ -140,11 +141,15 @@ module OpenHAB
140
141
  #
141
142
  class TimedCommandCancelRule < Java::OrgOpenhabCoreAutomationModuleScriptRulesupportSharedSimple::SimpleRule
142
143
  include OpenHAB::Log
144
+ include OpenHAB::Core::ThreadLocal
145
+
143
146
  def initialize(timed_command_details, semaphore, &block)
144
147
  super()
145
148
  @semaphore = semaphore
146
149
  @timed_command_details = timed_command_details
147
150
  @block = block
151
+ # Capture rule name if known
152
+ @thread_locals = Thread.current[:RULE_NAME] ? { RULE_NAME: Thread.current[:RULE_NAME] } : {}
148
153
  set_name("Cancels implicit timer for #{timed_command_details.item.id}")
149
154
  set_triggers([OpenHAB::DSL::Rules::Triggers::Trigger.trigger(
150
155
  type: OpenHAB::DSL::Rules::Triggers::Trigger::ITEM_STATE_UPDATE,
@@ -164,17 +169,19 @@ module OpenHAB
164
169
  # There is no feasible way to break this method into smaller components
165
170
  def execute(_mod = nil, inputs = nil)
166
171
  @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)
172
+ thread_local(@thread_locals) do
173
+ logger.trace "Canceling implicit timer #{@timed_command_details.timer} for "\
174
+ "#{@timed_command_details.item.id} because received event #{inputs}"
175
+ @timed_command_details.timer.cancel
176
+ # rubocop: disable Style/GlobalVars
177
+ # Disabled due to OpenHAB design
178
+ $scriptExtension.get('ruleRegistry').remove(@timed_command_details.rule_uid)
179
+ # rubocop: enable Style/GlobalVars
180
+ TimedCommand.timed_commands.delete(@timed_command_details.item)
181
+ if @block
182
+ logger.trace 'Executing user supplied block on timed command cancelation'
183
+ @block&.call(@timed_command_details)
184
+ end
178
185
  end
179
186
  end
180
187
  end
@@ -57,17 +57,19 @@ module OpenHAB
57
57
  # @param [Map] inputs map provided by OpenHAB rules engine containing event and other information
58
58
  #
59
59
  #
60
- def execute(mod = nil, inputs = nil)
61
- logger.trace { "Execute called with mod (#{mod&.to_string}) and inputs (#{inputs&.pretty_inspect})" }
62
- logger.trace { "Event details #{inputs['event'].pretty_inspect}" } if inputs&.key?('event')
63
- if trigger_delay inputs
64
- trigger_delay = trigger_delay(inputs)
65
- process_trigger_delay(trigger_delay, mod, inputs)
66
- else
67
- # If guards are satisfied execute the run type blocks
68
- # If they are not satisfied, execute the Othewise blocks
69
- queue = create_queue(inputs)
70
- process_queue(queue, mod, inputs)
60
+ def execute(mod = nil, inputs = nil) # rubocop:disable Metrics/MethodLength
61
+ thread_local(RULE_NAME: name) do
62
+ logger.trace { "Execute called with mod (#{mod&.to_string}) and inputs (#{inputs&.pretty_inspect})" }
63
+ logger.trace { "Event details #{inputs['event'].pretty_inspect}" } if inputs&.key?('event')
64
+ if trigger_delay inputs
65
+ trigger_delay = trigger_delay(inputs)
66
+ process_trigger_delay(trigger_delay, mod, inputs)
67
+ else
68
+ # If guards are satisfied execute the run type blocks
69
+ # If they are not satisfied, execute the Othewise blocks
70
+ queue = create_queue(inputs)
71
+ process_queue(queue, mod, inputs)
72
+ end
71
73
  end
72
74
  end
73
75
 
@@ -336,10 +338,12 @@ module OpenHAB
336
338
  # @param [Task] task task containing otherwise block to execute
337
339
  #
338
340
  def process_task(event, task)
339
- case task
340
- when RuleConfig::Run then process_run_task(event, task)
341
- when RuleConfig::Trigger then process_trigger_task(event, task)
342
- when RuleConfig::Otherwise then process_otherwise_task(event, task)
341
+ thread_local(RULE_NAME: name) do
342
+ case task
343
+ when RuleConfig::Run then process_run_task(event, task)
344
+ when RuleConfig::Trigger then process_trigger_task(event, task)
345
+ when RuleConfig::Otherwise then process_otherwise_task(event, task)
346
+ end
343
347
  end
344
348
  end
345
349
 
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'java'
4
+ require 'openhab/log/logger'
5
+
6
+ module OpenHAB
7
+ module DSL
8
+ #
9
+ # Creates and manages OpenHAB Rules
10
+ #
11
+ module Rules
12
+ #
13
+ # Specialized rule for cron triggers with attachments because OpenHAB does not provide trigger UID for cron rules
14
+ #
15
+ class CronTriggerRule < Java::OrgOpenhabCoreAutomationModuleScriptRulesupportSharedSimple::SimpleRule
16
+ include OpenHAB::Log
17
+
18
+ def initialize(rule_config:, rule:, trigger:)
19
+ super()
20
+ set_name("#{rule_config.name}-cron-#{trigger.id}")
21
+ set_triggers([trigger])
22
+ @rule = rule
23
+ @trigger = trigger
24
+ logger.trace("Created Cron Trigger Rule for #{@trigger}")
25
+ end
26
+
27
+ #
28
+ # Execute the rule
29
+ #
30
+ # @param [Map] mod map provided by OpenHAB rules engine
31
+ # @param [Map] inputs map provided by OpenHAB rules engine containing event and other information
32
+ #
33
+ #
34
+ def execute(mod = nil, _inputs = nil)
35
+ logger.trace "Trigger #{@trigger} fired for base rule #{@rule.inspect}"
36
+ inputs = { 'module' => @trigger.id }
37
+ @rule.execute(mod, inputs)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'openhab/dsl/rules/rule_config'
4
- require 'openhab/dsl/rules/automation_rule'
5
- require 'openhab/dsl/rules/guard'
3
+ require 'openhab/core/thread_local'
4
+ require 'openhab/log/logger'
5
+ require_relative 'rule_config'
6
+ require_relative 'automation_rule'
7
+ require_relative 'cron_trigger_rule'
8
+ require_relative 'guard'
6
9
 
7
10
  module OpenHAB
8
11
  #
@@ -13,6 +16,9 @@ module OpenHAB
13
16
  # Creates and manages OpenHAB Rules
14
17
  #
15
18
  module Rules
19
+ include OpenHAB::Core::ThreadLocal
20
+ include OpenHAB::Log
21
+
16
22
  @script_rules = []
17
23
 
18
24
  # rubocop: disable Style/GlobalVars
@@ -31,29 +37,22 @@ module OpenHAB
31
37
  # @yield [] Block executed in context of a RuleConfig
32
38
  #
33
39
  #
40
+ # rubocop: disable Metrics/MethodLength
34
41
  def rule(rule_name, &block)
35
- @rule_name = rule_name
36
- config = RuleConfig.new(rule_name, block.binding)
37
- config.instance_exec(config, &block)
38
- config.guard = Guard::Guard.new(only_if: config.only_if, not_if: config.not_if)
39
- logger.trace { config.inspect }
40
- process_rule_config(config)
42
+ thread_local(RULE_NAME: rule_name) do
43
+ @rule_name = rule_name
44
+ config = RuleConfig.new(rule_name, block.binding)
45
+ config.instance_exec(config, &block)
46
+ config.guard = Guard::Guard.new(only_if: config.only_if, not_if: config.not_if)
47
+ logger.trace { config.inspect }
48
+ process_rule_config(config)
49
+ nil # Must return something other than the rule object. See https://github.com/boc-tothefuture/openhab-jruby/issues/438
50
+ end
41
51
  rescue StandardError => e
52
+ puts "#{e.class}: #{e.message}"
42
53
  re_raise_with_backtrace(e)
43
54
  end
44
-
45
- #
46
- # Create a logger where name includes rule name if name is set
47
- #
48
- # @return [Log::Logger] Logger with name that appended with rule name if rule name is set
49
- #
50
- def logger
51
- if @rule_name
52
- Log.logger_for(@rule_name.chomp.gsub(/\s+/, '_'))
53
- else
54
- super
55
- end
56
- end
55
+ # rubocop: enable Metrics/MethodLength
57
56
 
58
57
  #
59
58
  # Cleanup rules in this script file
@@ -84,13 +83,44 @@ module OpenHAB
84
83
  def process_rule_config(config)
85
84
  return unless create_rule?(config)
86
85
 
86
+ cron_attach_triggers, other_triggers = partition_triggers(config)
87
+ logger.trace("Cron triggers: #{cron_attach_triggers} - Other triggers: #{other_triggers}")
88
+ config.triggers = other_triggers
89
+
87
90
  rule = AutomationRule.new(config: config)
88
91
  Rules.script_rules << rule
89
92
  add_rule(rule)
93
+
94
+ process_cron_attach(cron_attach_triggers, config, rule)
95
+
90
96
  rule.execute(nil, { 'event' => Struct.new(:attachment).new(config.start_attachment) }) if config.on_start?
91
97
  rule
92
98
  end
93
99
 
100
+ #
101
+ # Add cron triggers with attachments to rules
102
+ # @param [Array] cron_attach_triggers cron type triggers with attachments
103
+ #
104
+ def process_cron_attach(cron_attach_triggers, config, rule)
105
+ cron_attach_triggers&.map { |trigger| CronTriggerRule.new(rule_config: config, rule: rule, trigger: trigger) }
106
+ &.each { |trigger| add_rule(trigger) }
107
+ end
108
+
109
+ #
110
+ # Partitions triggers in a config, removing cron triggers with a corresponding attachment
111
+ # so they can be used with CronTriggerRules to support attachments
112
+ # @return [Array] Two element array the first element is cron triggers with attachments,
113
+ # second element is other triggers
114
+ #
115
+ def partition_triggers(config)
116
+ config
117
+ .triggers
118
+ .partition do |trigger|
119
+ trigger.typeUID == OpenHAB::DSL::Rules::Triggers::Trigger::CRON &&
120
+ config.attachments.key?(trigger.id)
121
+ end
122
+ end
123
+
94
124
  #
95
125
  # Should a rule be created based on rule configuration
96
126
  #
@@ -104,7 +134,7 @@ module OpenHAB
104
134
  elsif !execution_blocks?(config)
105
135
  logger.warn "Rule '#{config.name}' has no execution blocks, not creating rule"
106
136
  elsif !config.enabled
107
- logger.debug "Rule '#{config.name}' marked as disabled, not creating rule."
137
+ logger.trace "Rule '#{config.name}' marked as disabled, not creating rule."
108
138
  else
109
139
  return true
110
140
  end
@@ -140,6 +170,7 @@ module OpenHAB
140
170
  #
141
171
  #
142
172
  def add_rule(rule)
173
+ logger.trace("Adding rule: #{rule.inspect}")
143
174
  Rules.automation_manager.addRule(rule)
144
175
  end
145
176
  end
@@ -33,7 +33,7 @@ module OpenHAB
33
33
  extend OpenHAB::DSL
34
34
 
35
35
  # @return [Array] Of triggers
36
- attr_reader :triggers
36
+ attr_accessor :triggers
37
37
 
38
38
  # @return [Array] Of trigger delays
39
39
  attr_reader :trigger_delays
@@ -88,9 +88,9 @@ module OpenHAB
88
88
  @trigger_delays = {}
89
89
  @attachments = {}
90
90
  @caller = caller_binding.eval 'self'
91
+ name(rule_name)
91
92
  enabled(true)
92
93
  on_start(false)
93
- name(rule_name)
94
94
  end
95
95
 
96
96
  #
@@ -134,19 +134,6 @@ module OpenHAB
134
134
  @caller.instance_eval(&block)
135
135
  end
136
136
 
137
- #
138
- # Create a logger where name includes rule name if name is set
139
- #
140
- # @return [Log::Logger] Logger with name that appended with rule name if rule name is set
141
- #
142
- def logger
143
- if name
144
- Log.logger_for(name.chomp.gsub(/\s+/, '_'))
145
- else
146
- super
147
- end
148
- end
149
-
150
137
  #
151
138
  # Inspect the config object
152
139
  #
@@ -15,16 +15,19 @@ module OpenHAB
15
15
  #
16
16
  # Creates a channel trigger
17
17
  #
18
- # @param [Array] channels array to create triggers for on form of 'binding_id:type_id:thing_id#channel_id'
18
+ # @param [String, Channel, ChannelUID, Array<String, Channel, ChannelUID>] channels
19
+ # channels to create triggers for in form of 'binding_id:type_id:thing_id#channel_id'
19
20
  # or 'channel_id' if thing is provided
20
- # @param [thing] thing to create trigger for if not specified with the channel
21
- # @param [String] triggered specific triggering condition to match for trigger
22
- #
23
- #
24
- def channel(*channels, thing: nil, triggered: nil, attach: nil)
25
- channels.flatten.each do |channel|
26
- channel = [thing, channel].join(':') if thing
27
- logger.trace("Creating channel trigger for channel(#{channel}), thing(#{thing}), trigger(#{triggered})")
21
+ # @param [String, Thing, ThingUID, Array<String, Thing, ThingUID>] thing
22
+ # thing(s) to create trigger for if not specified with the channel
23
+ # @param [String, Array<String>] triggered specific triggering condition(s) to match for trigger
24
+ #
25
+ def channel(*channels, thing: nil, triggered: nil, attach: nil) # rubocop:disable Metrics/AbcSize
26
+ channels.flatten.product([thing].flatten).each do |(channel, t)|
27
+ channel = channel.uid if channel.is_a?(org.openhab.core.thing.Channel)
28
+ t = t.uid if t.is_a?(Thing)
29
+ channel = [t, channel].compact.join(':')
30
+ logger.trace("Creating channel trigger for channel(#{channel}), thing(#{t}), trigger(#{triggered})")
28
31
  [triggered].flatten.each do |trigger|
29
32
  create_channel_trigger(channel, trigger, attach)
30
33
  end
@@ -36,14 +39,13 @@ module OpenHAB
36
39
  #
37
40
  # Create a trigger for a channel
38
41
  #
39
- # @param [Channel] channel to look for triggers
40
- # @param [Trigger] trigger specific channel trigger to match
42
+ # @param [String] channel to look for triggers
43
+ # @param [String] trigger specific channel trigger to match
41
44
  #
42
45
  #
43
46
  def create_channel_trigger(channel, trigger, attach)
44
47
  config = { 'channelUID' => channel }
45
48
  config['event'] = trigger.to_s unless trigger.nil?
46
- config['channelUID'] = channel
47
49
  logger.trace("Creating Change Trigger for #{config}")
48
50
  append_trigger(Trigger::CHANNEL_EVENT, config, attach: attach)
49
51
  end
@@ -68,13 +68,13 @@ module OpenHAB
68
68
  # @param [Object] at TimeOfDay or String representing TimeOfDay in which to execute rule
69
69
  #
70
70
  #
71
- def every(value, at: nil)
71
+ def every(value, at: nil, attach: nil)
72
72
  cron_expression = case value
73
73
  when Symbol then cron_from_symbol(value, at)
74
74
  when Java::JavaTime::Duration then cron_from_duration(value, at)
75
75
  else raise ArgumentExpression, 'Unknown interval'
76
76
  end
77
- cron(cron_expression)
77
+ cron(cron_expression, attach: attach)
78
78
  end
79
79
 
80
80
  #
@@ -82,8 +82,8 @@ module OpenHAB
82
82
  #
83
83
  # @param [String] expression OpenHAB style cron expression
84
84
  #
85
- def cron(expression)
86
- @triggers << Trigger.trigger(type: Trigger::CRON, config: { 'cronExpression' => expression })
85
+ def cron(expression, attach: nil)
86
+ append_trigger(Trigger::CRON, { 'cronExpression' => expression }, attach: attach)
87
87
  end
88
88
 
89
89
  private
@@ -14,7 +14,7 @@ module OpenHAB
14
14
  # Support for OpenHAB Things
15
15
  #
16
16
  module Things
17
- java_import Java::OrgOpenhabCoreThing::ThingStatus
17
+ java_import org.openhab.core.thing.ThingStatus
18
18
  include OpenHAB::Log
19
19
 
20
20
  #
@@ -25,12 +25,12 @@ module OpenHAB
25
25
  # @param [Duration] duration Duration until timer should fire
26
26
  # @param [Block] block Block to execute when timer fires
27
27
  #
28
- def initialize(duration:, id:, &block)
28
+ def initialize(duration:, id:, thread_locals: {}, &block)
29
29
  raise 'Reentrant timers do not work in dynamically generated code' unless block.source_location
30
30
 
31
31
  @id = id
32
32
  @reentrant_id = self.class.reentrant_id(id: id, &block)
33
- super(duration: duration, &block)
33
+ super(duration: duration, thread_locals: thread_locals, &block)
34
34
  logger.trace("Created Reentrant Timer #{self} with reentrant Key #{@reentrant_id}")
35
35
  end
36
36
 
@@ -4,6 +4,7 @@ require 'java'
4
4
  require 'delegate'
5
5
  require 'forwardable'
6
6
  require 'openhab/log/logger'
7
+ require 'openhab/core/thread_local'
7
8
 
8
9
  module OpenHAB
9
10
  module DSL
@@ -22,6 +23,7 @@ module OpenHAB
22
23
  # @since 2.0.0
23
24
  class Timer < SimpleDelegator
24
25
  include OpenHAB::Log
26
+ include OpenHAB::Core::ThreadLocal
25
27
  extend Forwardable
26
28
 
27
29
  def_delegator :@timer, :has_terminated, :terminated?
@@ -32,8 +34,10 @@ module OpenHAB
32
34
  # @param [Duration] duration Duration until timer should fire
33
35
  # @param [Block] block Block to execute when timer fires
34
36
  #
35
- def initialize(duration:, &block)
37
+ # rubocop: disable Metrics/MethodLength
38
+ def initialize(duration:, thread_locals: {}, &block)
36
39
  @duration = duration
40
+ @thread_locals = thread_locals
37
41
 
38
42
  # A semaphore is used to prevent a race condition in which calling the block from the timer thread
39
43
  # occurs before the @timer variable can be set resulting in @timer being nil
@@ -41,13 +45,14 @@ module OpenHAB
41
45
 
42
46
  semaphore.synchronize do
43
47
  @timer = ScriptExecution.createTimer(
44
- ZonedDateTime.now.plus(@duration), timer_block(semaphore, &block)
48
+ ZonedDateTime.now.plus(to_duration(@duration)), timer_block(semaphore, &block)
45
49
  )
46
50
  @rule_timers = Thread.current[:rule_timers]
47
51
  super(@timer)
48
52
  Timers.timer_manager.add(self)
49
53
  end
50
54
  end
55
+ # rubocop: enable Metrics/MethodLength
51
56
 
52
57
  #
53
58
  # Reschedule timer
@@ -57,15 +62,13 @@ module OpenHAB
57
62
  # @return [Timer] Rescheduled timer instances
58
63
  #
59
64
  def reschedule(duration = nil)
60
- unless duration.nil? || duration.is_a?(Java::JavaTimeTemporal::TemporalAmount)
61
- raise ArgumentError, 'Supplied argument must be a duration'
62
- end
63
-
64
65
  duration ||= @duration
66
+
65
67
  Timers.timer_manager.add(self)
66
- @timer.reschedule(ZonedDateTime.now.plus(duration))
68
+ @timer.reschedule(ZonedDateTime.now.plus(to_duration(duration)))
67
69
  end
68
70
 
71
+ #
69
72
  # Cancel timer
70
73
  #
71
74
  # @return [Boolean] True if cancel was successful, false otherwise
@@ -88,10 +91,33 @@ module OpenHAB
88
91
  proc {
89
92
  semaphore.synchronize do
90
93
  Timers.timer_manager.delete(self)
91
- yield(self)
94
+ thread_local(@thread_locals) do
95
+ yield(self)
96
+ end
92
97
  end
93
98
  }
94
99
  end
100
+
101
+ #
102
+ # Convert argument to a duration
103
+ #
104
+ # @params [Java::JavaTimeTemporal::TemporalAmount, #to_f, #to_i, nil] duration Duration
105
+ #
106
+ # @raise if duration cannot be used for a timer
107
+ #
108
+ # @return Argument converted to seconds if it responds to #to_f or #to_i, otherwise duration unchanged
109
+ #
110
+ def to_duration(duration)
111
+ if duration.nil? || duration.is_a?(Java::JavaTimeTemporal::TemporalAmount)
112
+ duration
113
+ elsif duration.respond_to?(:to_f)
114
+ duration.to_f.seconds
115
+ elsif duration.respond_to?(:to_i)
116
+ duration.to_i.seconds
117
+ else
118
+ raise ArgumentError, "Supplied argument '#{duration}' cannot be converted to a duration"
119
+ end
120
+ end
95
121
  end
96
122
  end
97
123
  end
@@ -29,9 +29,12 @@ module OpenHAB
29
29
  # @return [Timer] Timer object
30
30
  #
31
31
  def after(duration, id: nil, &block)
32
- return Timers.reentrant_timer(duration: duration, id: id, &block) if id
32
+ # Carry rule name to timer thread
33
+ thread_locals = { RULE_NAME: Thread.current[:RULE_NAME] } if Thread.current[:RULE_NAME]
34
+ thread_locals ||= {}
35
+ return Timers.reentrant_timer(duration: duration, thread_locals: thread_locals, id: id, &block) if id
33
36
 
34
- Timer.new(duration: duration, &block)
37
+ Timer.new(duration: duration, thread_locals: thread_locals, &block)
35
38
  end
36
39
 
37
40
  #
@@ -55,7 +58,7 @@ module OpenHAB
55
58
  # @param [Object] id to associate with timer
56
59
  # @param [Block] block to execute, block is passed a Timer object
57
60
  # @return [ReentrantTimer] Timer object
58
- def self.reentrant_timer(duration:, id:, &block)
61
+ def self.reentrant_timer(duration:, id:, thread_locals: nil, &block)
59
62
  timer = @timer_manager.reentrant_timer(id: id, &block)
60
63
  if timer
61
64
  logger.trace("Reentrant timer found - #{timer}")
@@ -63,7 +66,7 @@ module OpenHAB
63
66
  else
64
67
  logger.trace('No reentrant timer found, creating new timer')
65
68
  end
66
- ReentrantTimer.new(duration: duration, id: id, &block)
69
+ ReentrantTimer.new(duration: duration, id: id, thread_locals: thread_locals, &block)
67
70
  end
68
71
  end
69
72
  end
@@ -41,6 +41,20 @@ module OpenHAB
41
41
  equals(other)
42
42
  end
43
43
 
44
+ #
45
+ # Case equality
46
+ #
47
+ # @return [Boolean] if the values are of the same Type
48
+ # or item state of the same type
49
+ #
50
+ def ===(other)
51
+ logger.trace("(#{self.class}) #{self} === #{other} (#{other.class})")
52
+ other = other.state if other.respond_to?(:state)
53
+ return false unless instance_of?(other.class)
54
+
55
+ eql?(other)
56
+ end
57
+
44
58
  #
45
59
  # Check equality, including type conversion
46
60
  #
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module DSL
5
+ java_import org.openhab.core.common.AbstractUID
6
+ java_import org.openhab.core.thing.ThingTypeUID
7
+
8
+ # Adds methods to core OpenHAB AbstractUID to make it more natural in Ruby
9
+ class AbstractUID
10
+ # implicit conversion to string
11
+ alias to_str to_s
12
+ # inspect result is just the string representation
13
+ alias inspect to_s
14
+
15
+ # compares if equal to `other`, including string conversion
16
+ # @return [true, false]
17
+ def ==(other)
18
+ return true if equals(other)
19
+
20
+ to_s == other
21
+ end
22
+ end
23
+
24
+ # Adds methods to core OpenHAB ThingUID to make it more natural in Ruby
25
+ class ThingUID
26
+ # Returns the id of the binding this thing belongs to
27
+ # @return [String]
28
+ def binding_id
29
+ get_segment(0)
30
+ end
31
+ end
32
+
33
+ # Adds methods to core OpenHAB ThingTypeUID to make it more natural in Ruby
34
+ class ThingTypeUID
35
+ # Returns the id of the binding this thing type belongs to
36
+ # @return [String]
37
+ def binding_id
38
+ get_segment(0)
39
+ end
40
+ end
41
+
42
+ # have to remove == from all descendant classes so that they'll inherit
43
+ # the new implementation
44
+ [org.openhab.core.items.MetadataKey,
45
+ org.openhab.core.thing.UID,
46
+ org.openhab.core.thing.ChannelUID,
47
+ org.openhab.core.thing.ChannelGroupUID,
48
+ org.openhab.core.thing.ThingUID,
49
+ org.openhab.core.thing.ThingTypeUID,
50
+ org.openhab.core.thing.type.ChannelTypeUID,
51
+ org.openhab.core.thing.type.ChannelGroupTypeUID].each do |klass|
52
+ klass.remove_method(:==)
53
+ end
54
+ end
55
+ end
@@ -127,6 +127,7 @@ module OpenHAB
127
127
  end
128
128
  end
129
129
 
130
+ # Logger caches
130
131
  @loggers = {}
131
132
 
132
133
  # Return a logger with the configured log prefix plus the calling scripts name
@@ -137,7 +138,7 @@ module OpenHAB
137
138
  # @return [Logger] for the current class
138
139
  #
139
140
  def logger
140
- Log.logger(self.class)
141
+ Log.logger(self)
141
142
  end
142
143
 
143
144
  class << self
@@ -148,29 +149,51 @@ module OpenHAB
148
149
  #
149
150
  # @return [Logger] for the supplied name
150
151
  #
151
- def logger(klass)
152
+ def logger(object)
153
+ # Cache logger instances for each object since construction
154
+ # of logger name requires lots of operations and logger
155
+ # names for some objects are specific to the class
156
+ logger_name = logger_name(object)
157
+ @loggers[logger_name] ||= Logger.new(logger_name)
158
+ end
159
+
160
+ private
161
+
162
+ # Construct the logger name from the supplied object
163
+ # @param [Object] object to construct logger name from
164
+ # @return name for logger based on object
165
+ def logger_name(object)
166
+ name = Configuration.log_prefix
167
+ name += rules_file || ''
168
+ name += rule_name || ''
169
+ name += klass_name(object) || ''
170
+ name.tr_s(' ', '_').gsub('::', '.')
171
+ end
172
+
173
+ # Get the class name for the supplied object
174
+ # @param [Object] object to derive class name for
175
+ # @return [String] name of class for logging
176
+ def klass_name(object)
177
+ object.then(&:class)
178
+ .then { |klass| java_klass(klass) }
179
+ .then(&:name)
180
+ .then { |name| filter_base_classes(name) }
181
+ .then { |name| name&.prepend('.') }
182
+ end
183
+
184
+ # Get the appropriate java class for the supplied klass if the supplied
185
+ # class is a java class
186
+ # @param [Class] klass to inspect
187
+ # @return Class or Java class of supplied class
188
+ def java_klass(klass)
152
189
  if klass.respond_to?(:java_class) &&
153
190
  klass.java_class &&
154
191
  !klass.java_class.name.start_with?('org.jruby.Ruby')
155
192
  klass = klass.java_class
156
193
  end
157
- name = klass.name
158
- @loggers[name] ||= Log.logger_for(name)
159
- end
160
-
161
- #
162
- # Configure a logger for the supplied class name
163
- #
164
- # @param [String] name to configure logger for
165
- #
166
- # @return [Logger] for the supplied classname
167
- #
168
- def logger_for(name)
169
- configure_logger_for(name)
194
+ klass
170
195
  end
171
196
 
172
- private
173
-
174
197
  #
175
198
  # Configure a logger for the supplied classname
176
199
  #
@@ -178,16 +201,33 @@ module OpenHAB
178
201
  #
179
202
  # @return [Logger] Logger for the supplied classname
180
203
  #
181
- def configure_logger_for(name)
182
- log_prefix = Configuration.log_prefix
183
- log_prefix += if name
184
- ".#{name}"
185
- else
186
- ".#{log_caller}"
187
- end
188
- Logger.new(log_prefix)
204
+ def rules_file
205
+ # Each rules file gets its own context
206
+ # Set it once as a class value so that threads not
207
+ # tied to a rules file pick up the rules file they
208
+ # were spawned from
209
+ @rules_file ||= log_caller&.downcase&.prepend('.')
189
210
  end
190
211
 
212
+ # Get the name of the rule from the thread context
213
+ def rule_name
214
+ Thread.current[:RULE_NAME]&.downcase&.prepend('.')
215
+ end
216
+
217
+ # Filter out the base classes of Object and Module from the log name
218
+ def filter_base_classes(klass_name)
219
+ return nil if %w[Object Module].include?(klass_name)
220
+
221
+ klass_name
222
+ end
223
+
224
+ # "#{rule_name.downcase}.#{klass_name}"
225
+ # if klass_name == 'Object'
226
+ # "rules.#{rules_file_name.downcase}"
227
+ # else
228
+ # "rules.#{rules_file_name.downcase}.#{klass_name}"
229
+ # end
230
+
191
231
  #
192
232
  # Figure out the log prefix
193
233
  #
@@ -199,7 +239,7 @@ module OpenHAB
199
239
  .grep_v(/rubygems/)
200
240
  .grep_v(%r{lib/ruby})
201
241
  .first
202
- .yield_self { |caller| File.basename(caller, '.*') }
242
+ .then { |caller| File.basename(caller, '.*') if caller }
203
243
  end
204
244
  end
205
245
 
@@ -5,5 +5,5 @@
5
5
  #
6
6
  module OpenHAB
7
7
  # @return [String] Version of OpenHAB helper libraries
8
- VERSION = '4.18.0'
8
+ VERSION = '4.21.0'
9
9
  end
data/lib/openhab.rb CHANGED
@@ -20,23 +20,18 @@ module OpenHAB
20
20
  #
21
21
  #
22
22
  # Number of extensions and includes requires more lines
23
- # rubocop: disable Metrics/MethodLength
24
23
  def self.extended(base)
25
24
  OpenHAB::Core.wait_till_openhab_ready
26
25
  base.extend Log
27
26
  base.extend OpenHAB::Core::ScriptHandling
28
27
  base.extend OpenHAB::Core::EntityLookup
29
28
  base.extend OpenHAB::DSL
30
- base.extend OpenHAB::DSL::Between
31
29
 
32
30
  base.send :include, OpenHAB::Core::ScriptHandlingCallbacks
33
- base.send :include, OpenHAB::DSL::Items
34
- base.send :include, OpenHAB::DSL::Types
35
- logger.info "OpenHAB JRuby Scripting Library Version #{OpenHAB::VERSION} Loaded"
31
+ logger.debug "OpenHAB JRuby Scripting Library Version #{OpenHAB::VERSION} Loaded"
36
32
 
37
33
  OpenHAB::Core.add_rubylib_to_load_path
38
34
  end
39
- # rubocop: enable Metrics/MethodLength
40
35
  end
41
36
 
42
37
  # Extend caller with OpenHAB methods
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.18.0
4
+ version: 4.21.0
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-11-30 00:00:00.000000000 Z
11
+ date: 2021-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,6 +94,7 @@ files:
94
94
  - lib/openhab/dsl/monkey_patch/ruby/string.rb
95
95
  - lib/openhab/dsl/persistence.rb
96
96
  - lib/openhab/dsl/rules/automation_rule.rb
97
+ - lib/openhab/dsl/rules/cron_trigger_rule.rb
97
98
  - lib/openhab/dsl/rules/guard.rb
98
99
  - lib/openhab/dsl/rules/item_event.rb
99
100
  - lib/openhab/dsl/rules/property.rb
@@ -136,6 +137,7 @@ files:
136
137
  - lib/openhab/dsl/types/types.rb
137
138
  - lib/openhab/dsl/types/un_def_type.rb
138
139
  - lib/openhab/dsl/types/up_down_type.rb
140
+ - lib/openhab/dsl/uid.rb
139
141
  - lib/openhab/dsl/units.rb
140
142
  - lib/openhab/log/configuration.rb
141
143
  - lib/openhab/log/logger.rb