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 +4 -4
- data/lib/openhab/core/openhab_setup.rb +3 -3
- data/lib/openhab/core/thread_local.rb +6 -1
- data/lib/openhab/dsl/dsl.rb +1 -0
- data/lib/openhab/dsl/items/image_item.rb +1 -1
- data/lib/openhab/dsl/items/timed_command.rb +18 -11
- data/lib/openhab/dsl/rules/automation_rule.rb +19 -15
- data/lib/openhab/dsl/rules/cron_trigger_rule.rb +42 -0
- data/lib/openhab/dsl/rules/rule.rb +54 -23
- data/lib/openhab/dsl/rules/rule_config.rb +2 -15
- data/lib/openhab/dsl/rules/triggers/channel.rb +14 -12
- data/lib/openhab/dsl/rules/triggers/cron.rb +4 -4
- data/lib/openhab/dsl/things.rb +1 -1
- data/lib/openhab/dsl/timers/reentrant_timer.rb +2 -2
- data/lib/openhab/dsl/timers/timer.rb +34 -8
- data/lib/openhab/dsl/timers.rb +7 -4
- data/lib/openhab/dsl/types/type.rb +14 -0
- data/lib/openhab/dsl/uid.rb +55 -0
- data/lib/openhab/log/logger.rb +66 -26
- data/lib/openhab/version.rb +1 -1
- data/lib/openhab.rb +1 -6
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 330baeb4ed7472cdf6c60ba060d0b4482f988fa72c8ff0dd33b00c3e2df28b4a
|
4
|
+
data.tar.gz: 35cab5382ecb542fcc90f02200c8ac977ccf4c1c880e2b644df2715c11fc36b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
21
|
+
logger.trace('Checking readyness of OpenHAB')
|
22
22
|
# rubocop: disable Style/GlobalVars
|
23
23
|
until $scriptExtension.get('automationManager')
|
24
|
-
logger.
|
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.
|
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 }
|
data/lib/openhab/dsl/dsl.rb
CHANGED
@@ -33,7 +33,7 @@ module OpenHAB
|
|
33
33
|
#
|
34
34
|
#
|
35
35
|
def update_from_url(uri)
|
36
|
-
logger.
|
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
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
trigger_delay
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
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/
|
4
|
-
require 'openhab/
|
5
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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.
|
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
|
-
|
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 [
|
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 [
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
def channel(*channels, thing: nil, triggered: nil, attach: nil)
|
25
|
-
channels.flatten.each do |channel|
|
26
|
-
channel =
|
27
|
-
|
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 [
|
40
|
-
# @param [
|
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
|
-
|
85
|
+
def cron(expression, attach: nil)
|
86
|
+
append_trigger(Trigger::CRON, { 'cronExpression' => expression }, attach: attach)
|
87
87
|
end
|
88
88
|
|
89
89
|
private
|
data/lib/openhab/dsl/things.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/openhab/dsl/timers.rb
CHANGED
@@ -29,9 +29,12 @@ module OpenHAB
|
|
29
29
|
# @return [Timer] Timer object
|
30
30
|
#
|
31
31
|
def after(duration, id: nil, &block)
|
32
|
-
|
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
|
data/lib/openhab/log/logger.rb
CHANGED
@@ -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
|
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(
|
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
|
-
|
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
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
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
|
-
.
|
242
|
+
.then { |caller| File.basename(caller, '.*') if caller }
|
203
243
|
end
|
204
244
|
end
|
205
245
|
|
data/lib/openhab/version.rb
CHANGED
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
|
-
|
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.
|
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
|
+
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
|