openhab-scripting 2.9.1
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 +7 -0
- data/.github/workflows/workflow.yml +327 -0
- data/.gitignore +17 -0
- data/.java-version +1 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +113 -0
- data/Gemfile +28 -0
- data/Gemfile.lock +245 -0
- data/Guardfile +35 -0
- data/LICENSE +277 -0
- data/README.md +23 -0
- data/Rakefile +406 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/userdata/config/org/openhab/restauth.config +3 -0
- data/cucumber.yml +1 -0
- data/docs/_config.yml +135 -0
- data/docs/contributing/index.md +47 -0
- data/docs/examples/conversions.md +123 -0
- data/docs/examples/index.md +61 -0
- data/docs/index.md +19 -0
- data/docs/installation/index.md +26 -0
- data/docs/motivation/index.md +27 -0
- data/docs/usage/execution.md +9 -0
- data/docs/usage/execution/delay.md +48 -0
- data/docs/usage/execution/otherwise.md +30 -0
- data/docs/usage/execution/run.md +70 -0
- data/docs/usage/execution/triggered.md +48 -0
- data/docs/usage/guards.md +51 -0
- data/docs/usage/guards/between.md +30 -0
- data/docs/usage/guards/not_if.md +41 -0
- data/docs/usage/guards/only_if.md +40 -0
- data/docs/usage/index.md +11 -0
- data/docs/usage/items.md +66 -0
- data/docs/usage/items/contact.md +84 -0
- data/docs/usage/items/dimmer.md +147 -0
- data/docs/usage/items/groups.md +76 -0
- data/docs/usage/items/number.md +225 -0
- data/docs/usage/items/string.md +49 -0
- data/docs/usage/items/switch.md +85 -0
- data/docs/usage/misc.md +7 -0
- data/docs/usage/misc/actions.md +108 -0
- data/docs/usage/misc/duration.md +21 -0
- data/docs/usage/misc/gems.md +25 -0
- data/docs/usage/misc/logging.md +21 -0
- data/docs/usage/misc/metadata.md +128 -0
- data/docs/usage/misc/store_states.md +42 -0
- data/docs/usage/misc/time_of_day.md +69 -0
- data/docs/usage/misc/timers.md +67 -0
- data/docs/usage/rule.md +43 -0
- data/docs/usage/things.md +29 -0
- data/docs/usage/triggers.md +8 -0
- data/docs/usage/triggers/changed.md +57 -0
- data/docs/usage/triggers/channel.md +54 -0
- data/docs/usage/triggers/command.md +69 -0
- data/docs/usage/triggers/cron.md +19 -0
- data/docs/usage/triggers/every.md +76 -0
- data/docs/usage/triggers/updated.md +78 -0
- data/lib/openhab.rb +39 -0
- data/lib/openhab/configuration.rb +16 -0
- data/lib/openhab/core/cron.rb +27 -0
- data/lib/openhab/core/debug.rb +34 -0
- data/lib/openhab/core/dsl.rb +47 -0
- data/lib/openhab/core/dsl/actions.rb +107 -0
- data/lib/openhab/core/dsl/entities.rb +103 -0
- data/lib/openhab/core/dsl/gems.rb +29 -0
- data/lib/openhab/core/dsl/group.rb +91 -0
- data/lib/openhab/core/dsl/items/items.rb +39 -0
- data/lib/openhab/core/dsl/items/number_item.rb +217 -0
- data/lib/openhab/core/dsl/items/string_item.rb +102 -0
- data/lib/openhab/core/dsl/monkey_patch/actions/actions.rb +4 -0
- data/lib/openhab/core/dsl/monkey_patch/actions/script_thing_actions.rb +22 -0
- data/lib/openhab/core/dsl/monkey_patch/events.rb +5 -0
- data/lib/openhab/core/dsl/monkey_patch/events/item_command.rb +13 -0
- data/lib/openhab/core/dsl/monkey_patch/events/item_state_changed.rb +25 -0
- data/lib/openhab/core/dsl/monkey_patch/events/thing_status_info.rb +26 -0
- data/lib/openhab/core/dsl/monkey_patch/items/contact_item.rb +54 -0
- data/lib/openhab/core/dsl/monkey_patch/items/dimmer_item.rb +125 -0
- data/lib/openhab/core/dsl/monkey_patch/items/group_item.rb +27 -0
- data/lib/openhab/core/dsl/monkey_patch/items/items.rb +130 -0
- data/lib/openhab/core/dsl/monkey_patch/items/metadata.rb +259 -0
- data/lib/openhab/core/dsl/monkey_patch/items/switch_item.rb +86 -0
- data/lib/openhab/core/dsl/monkey_patch/ruby/number.rb +69 -0
- data/lib/openhab/core/dsl/monkey_patch/ruby/range.rb +46 -0
- data/lib/openhab/core/dsl/monkey_patch/ruby/ruby.rb +5 -0
- data/lib/openhab/core/dsl/monkey_patch/types/decimal_type.rb +24 -0
- data/lib/openhab/core/dsl/monkey_patch/types/on_off_type.rb +41 -0
- data/lib/openhab/core/dsl/monkey_patch/types/open_closed_type.rb +25 -0
- data/lib/openhab/core/dsl/monkey_patch/types/percent_type.rb +23 -0
- data/lib/openhab/core/dsl/monkey_patch/types/types.rb +7 -0
- data/lib/openhab/core/dsl/property.rb +85 -0
- data/lib/openhab/core/dsl/rule/channel.rb +41 -0
- data/lib/openhab/core/dsl/rule/cron.rb +115 -0
- data/lib/openhab/core/dsl/rule/guard.rb +99 -0
- data/lib/openhab/core/dsl/rule/item.rb +207 -0
- data/lib/openhab/core/dsl/rule/rule.rb +374 -0
- data/lib/openhab/core/dsl/rule/triggers.rb +77 -0
- data/lib/openhab/core/dsl/states.rb +63 -0
- data/lib/openhab/core/dsl/things.rb +93 -0
- data/lib/openhab/core/dsl/time_of_day.rb +203 -0
- data/lib/openhab/core/dsl/timers.rb +85 -0
- data/lib/openhab/core/dsl/types/quantity.rb +255 -0
- data/lib/openhab/core/dsl/units.rb +41 -0
- data/lib/openhab/core/duration.rb +69 -0
- data/lib/openhab/core/log.rb +175 -0
- data/lib/openhab/core/patch_load_path.rb +7 -0
- data/lib/openhab/core/startup_delay.rb +22 -0
- data/lib/openhab/osgi.rb +52 -0
- data/lib/openhab/version.rb +9 -0
- data/openhab-scripting.gemspec +30 -0
- data/openhab_rules/warmup.rb +5 -0
- metadata +157 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'java'
|
|
4
|
+
require 'forwardable'
|
|
5
|
+
|
|
6
|
+
module OpenHAB
|
|
7
|
+
module Core
|
|
8
|
+
module DSL
|
|
9
|
+
#
|
|
10
|
+
# Ruby implementation of OpenHAB Types
|
|
11
|
+
#
|
|
12
|
+
module Types
|
|
13
|
+
#
|
|
14
|
+
# Ruby implementation for OpenHAB quantities
|
|
15
|
+
#
|
|
16
|
+
class Quantity < Numeric
|
|
17
|
+
extend Forwardable
|
|
18
|
+
include Logging
|
|
19
|
+
|
|
20
|
+
def_delegator :@quantity, :to_s
|
|
21
|
+
|
|
22
|
+
java_import org.openhab.core.library.types.QuantityType
|
|
23
|
+
java_import 'tec.uom.se.format.SimpleUnitFormat'
|
|
24
|
+
java_import 'tec.uom.se.AbstractUnit'
|
|
25
|
+
|
|
26
|
+
OPERATIONS = {
|
|
27
|
+
'+' => 'add',
|
|
28
|
+
'-' => 'subtract',
|
|
29
|
+
'*' => 'multiply',
|
|
30
|
+
'/' => 'divide'
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
private_constant :OPERATIONS
|
|
34
|
+
|
|
35
|
+
attr_reader :quantity
|
|
36
|
+
|
|
37
|
+
#
|
|
38
|
+
# Create a new Quantity
|
|
39
|
+
#
|
|
40
|
+
# @param [Java::org::openhab::core::library::types::QuantityType] quantity OpenHAB quantity to delegate to
|
|
41
|
+
#
|
|
42
|
+
def initialize(quantity)
|
|
43
|
+
@quantity = case quantity
|
|
44
|
+
when String
|
|
45
|
+
QuantityType.new(quantity)
|
|
46
|
+
when QuantityType
|
|
47
|
+
quantity
|
|
48
|
+
when Numeric
|
|
49
|
+
QuantityType.new(BigDecimal(quantity).to_java, AbstractUnit::ONE)
|
|
50
|
+
else
|
|
51
|
+
raise "Unexpected type #{quantity.class} provided to Quantity initializer"
|
|
52
|
+
end
|
|
53
|
+
super()
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#
|
|
57
|
+
# Convert this quantity into a another unit
|
|
58
|
+
#
|
|
59
|
+
# @param [Object] other String or Unit to convert to
|
|
60
|
+
#
|
|
61
|
+
# @return [Quantity] This quantity converted to another unit
|
|
62
|
+
#
|
|
63
|
+
def |(other)
|
|
64
|
+
other = SimpleUnitFormat.instance.unitFor(other) if other.is_a? String
|
|
65
|
+
|
|
66
|
+
Quantity.new(quantity.to_unit(other))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# Compare this quantity
|
|
71
|
+
#
|
|
72
|
+
# @param [Object] other object to compare to
|
|
73
|
+
#
|
|
74
|
+
# @return [Integer] -1,0,1 if this object is less than, equal to, or greater than the supplied object,
|
|
75
|
+
# nil if it cannot be compared
|
|
76
|
+
#
|
|
77
|
+
def <=>(other)
|
|
78
|
+
logger.trace("Comparing #{self} to #{other}")
|
|
79
|
+
case other
|
|
80
|
+
when Quantity
|
|
81
|
+
logger.trace("Comparing Quantity #{self} to Quantity #{other}")
|
|
82
|
+
convert_unit(quantity).compare_to(convert_unit(other.quantity))
|
|
83
|
+
when QuantityType
|
|
84
|
+
other = convert_unit(other)
|
|
85
|
+
quantity.compare_to(other)
|
|
86
|
+
when String
|
|
87
|
+
other = QuantityType.new(other)
|
|
88
|
+
other = convert_unit(other)
|
|
89
|
+
quantity.compare_to(other)
|
|
90
|
+
when Numeric
|
|
91
|
+
quantity.compare_to(QuantityType.new(other, unit)) if unit
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
#
|
|
96
|
+
# Coerce other object into a Quantity
|
|
97
|
+
#
|
|
98
|
+
# @param [Object] other object to convert to Quantity
|
|
99
|
+
#
|
|
100
|
+
# @return [Array] of self and other object as Quantity types, nil if object cannot be coerced
|
|
101
|
+
#
|
|
102
|
+
def coerce(other)
|
|
103
|
+
logger.trace("Coercing #{self} as a request from #{other.class}")
|
|
104
|
+
case other
|
|
105
|
+
when Quantity
|
|
106
|
+
[other.quantity, quantity]
|
|
107
|
+
when QuantityType
|
|
108
|
+
[other, quantity]
|
|
109
|
+
when Numeric
|
|
110
|
+
[Quantity.new(other), self]
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
#
|
|
115
|
+
# Forward missing methods to Openhab Quantity Item if they are defined
|
|
116
|
+
#
|
|
117
|
+
# @param [String] meth name of method invoked
|
|
118
|
+
# @param [Array] args arguments to invoked method
|
|
119
|
+
# @param [Proc] block block passed ot method
|
|
120
|
+
#
|
|
121
|
+
# @return [Object] result of delegation
|
|
122
|
+
#
|
|
123
|
+
def method_missing(meth, *args, &block)
|
|
124
|
+
if quantity.respond_to?(meth)
|
|
125
|
+
quantity.__send__(meth, *args, &block)
|
|
126
|
+
elsif ::Kernel.method_defined?(meth) || ::Kernel.private_method_defined?(meth)
|
|
127
|
+
::Kernel.instance_method(meth).bind_call(self, *args, &block)
|
|
128
|
+
else
|
|
129
|
+
super(meth, *args, &block)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
#
|
|
134
|
+
# Negate the quantity
|
|
135
|
+
#
|
|
136
|
+
# @return [Quantity] This quantity negated
|
|
137
|
+
#
|
|
138
|
+
def -@
|
|
139
|
+
Quantity.new(quantity.negate)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
OPERATIONS.each do |operation, method|
|
|
143
|
+
define_method(operation) do |other|
|
|
144
|
+
logger.trace("Executing math operation '#{operation}' on quantity #{inspect} with other type #{other.class} and value #{other.inspect}")
|
|
145
|
+
a, b = case other
|
|
146
|
+
when Quantity
|
|
147
|
+
[quantity, other.quantity]
|
|
148
|
+
when String
|
|
149
|
+
[quantity, QuantityType.new(other)]
|
|
150
|
+
when NumberItem
|
|
151
|
+
a, b = other.coerce(self)
|
|
152
|
+
logger.trace("Number Item coerced result a(#{a.class})='#{a}' b(#{b.class})='#{b}'")
|
|
153
|
+
[a.quantity, b.quantity]
|
|
154
|
+
when Numeric
|
|
155
|
+
[quantity, QuantityType.new(BigDecimal(other).to_java, AbstractUnit::ONE)]
|
|
156
|
+
else
|
|
157
|
+
raise TypeError,
|
|
158
|
+
"Operation '#{operation}' cannot be performed between #{self} and #{other.class}"
|
|
159
|
+
end
|
|
160
|
+
logger.trace("Coerced a='#{a}' with b='#{b}'")
|
|
161
|
+
a, b = unitize(a, b, operation)
|
|
162
|
+
logger.trace("Unitized a='#{a}' b='#{b}'")
|
|
163
|
+
logger.trace("Performing operation '#{operation}' with method '#{method}' on a='#{a}' with b='#{b}'")
|
|
164
|
+
Quantity.new(a.public_send(method, b))
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
#
|
|
169
|
+
# Provide details about quantity object
|
|
170
|
+
#
|
|
171
|
+
# @return [String] Representing details about the quantity object
|
|
172
|
+
#
|
|
173
|
+
def inspect
|
|
174
|
+
if @quantity.unit == AbstractUnit::ONE
|
|
175
|
+
"unit=#{@quantity.unit}, value=#{@quantity.to_string}"
|
|
176
|
+
else
|
|
177
|
+
@quantity.to_string
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
private
|
|
182
|
+
|
|
183
|
+
DIMENSIONLESS_NON_UNITIZED_OPERATIONS = %w[* /].freeze
|
|
184
|
+
|
|
185
|
+
# Dimensionless numbers should only be unitzed for addition and subtraction
|
|
186
|
+
|
|
187
|
+
#
|
|
188
|
+
# Checks if an item should be unitized
|
|
189
|
+
#
|
|
190
|
+
# @param [Quantity] quantity to check
|
|
191
|
+
# @param [String] operation quantity is being used with
|
|
192
|
+
#
|
|
193
|
+
# @return [Boolean] True if the quantity should be unitzed based on the unit and operation, false otherwise
|
|
194
|
+
#
|
|
195
|
+
def unitize?(quantity, operation)
|
|
196
|
+
if quantity.unit == AbstractUnit::ONE && DIMENSIONLESS_NON_UNITIZED_OPERATIONS.include?(operation)
|
|
197
|
+
false
|
|
198
|
+
else
|
|
199
|
+
true
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
#
|
|
204
|
+
# Convert the unit for the quantity
|
|
205
|
+
#
|
|
206
|
+
# @param [Quantity] quantity being converted
|
|
207
|
+
#
|
|
208
|
+
# @return [Quantity] Quantity coverted to unit set by unit block
|
|
209
|
+
#
|
|
210
|
+
def convert_unit(quantity)
|
|
211
|
+
if unit
|
|
212
|
+
case quantity.unit
|
|
213
|
+
when AbstractUnit::ONE
|
|
214
|
+
logger.trace("Converting dimensionless #{quantity} to #{unit}")
|
|
215
|
+
QuantityType.new(quantity.to_big_decimal, unit)
|
|
216
|
+
when unit
|
|
217
|
+
quantity
|
|
218
|
+
else
|
|
219
|
+
logger.trace("Converting dimensioned item #{inspect} to #{unit}")
|
|
220
|
+
converted = quantity.to_unit(unit)
|
|
221
|
+
raise "Conversion from #{quantity.unit} to #{unit} failed" if converted.nil?
|
|
222
|
+
|
|
223
|
+
converted
|
|
224
|
+
end
|
|
225
|
+
else
|
|
226
|
+
quantity
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
#
|
|
231
|
+
# Convert quantities to appropriate units
|
|
232
|
+
#
|
|
233
|
+
# @param [Quantity] quantity_a Quantity on left side of operation
|
|
234
|
+
# @param [Quantity] quantity_b Quantity on right side of operation
|
|
235
|
+
# @param [String] operation Math operation
|
|
236
|
+
#
|
|
237
|
+
# @return [Array] of quantites in correct units for the supplied operation and set unit
|
|
238
|
+
#
|
|
239
|
+
def unitize(quantity_a, quantity_b, operation)
|
|
240
|
+
[quantity_a, quantity_b].map { |qt| unitize?(qt, operation) ? convert_unit(qt) : qt }
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
#
|
|
244
|
+
# Get the unit from the current thread local variable
|
|
245
|
+
#
|
|
246
|
+
# @return [Object] Unit or string representation of Unit, or nil if not set
|
|
247
|
+
#
|
|
248
|
+
def unit
|
|
249
|
+
Thread.current.thread_variable_get(:unit)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'java'
|
|
4
|
+
|
|
5
|
+
# Import Imperial and SI Units overriding provided values
|
|
6
|
+
%i[ImperialUnits SIUnits].each do |type|
|
|
7
|
+
Object.send(:remove_const, type)
|
|
8
|
+
java_import "org.openhab.core.library.unit.#{type}"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
Object.send(:remove_const, :QuantityType)
|
|
12
|
+
java_import org.openhab.core.library.types.QuantityType
|
|
13
|
+
|
|
14
|
+
module OpenHAB
|
|
15
|
+
module Core
|
|
16
|
+
module DSL
|
|
17
|
+
#
|
|
18
|
+
# Provides support for interacting with OpenHAB Units of Measurement
|
|
19
|
+
#
|
|
20
|
+
module Units
|
|
21
|
+
java_import 'tec.uom.se.format.SimpleUnitFormat'
|
|
22
|
+
|
|
23
|
+
#
|
|
24
|
+
# Sets a thread local variable to the supplied unit such that classes operating inside the block
|
|
25
|
+
# can perform automatic conversions to the supplied unit for NumberItems
|
|
26
|
+
#
|
|
27
|
+
# @param [Object] unit OpenHAB Unit or String representing unit
|
|
28
|
+
# @yield [] Block executed in context of the supplied unit
|
|
29
|
+
#
|
|
30
|
+
#
|
|
31
|
+
def unit(unit)
|
|
32
|
+
unit = SimpleUnitFormat.instance.unitFor(unit) if unit.is_a? String
|
|
33
|
+
Thread.current.thread_variable_set(:unit, unit)
|
|
34
|
+
yield
|
|
35
|
+
ensure
|
|
36
|
+
Thread.current.thread_variable_set(:unit, nil)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openhab/core/cron'
|
|
4
|
+
|
|
5
|
+
module OpenHAB
|
|
6
|
+
module Core
|
|
7
|
+
#
|
|
8
|
+
# This class represents a duration of time
|
|
9
|
+
#
|
|
10
|
+
class Duration
|
|
11
|
+
include OpenHAB::Core::Cron
|
|
12
|
+
|
|
13
|
+
# @return [Array] of supported temperal units (milliseconds, seconds, minutes and hours)
|
|
14
|
+
TEMPORAL_UNITS = %i[MILLISECONDS SECONDS MINUTES HOURS].freeze
|
|
15
|
+
|
|
16
|
+
#
|
|
17
|
+
# Create a new Duration object
|
|
18
|
+
#
|
|
19
|
+
# @param [Symbol] temporal_unit Unit for duration
|
|
20
|
+
# @param [Integer] amount of that unit
|
|
21
|
+
#
|
|
22
|
+
def initialize(temporal_unit:, amount:)
|
|
23
|
+
unless TEMPORAL_UNITS.include? temporal_unit
|
|
24
|
+
raise ArgumentError,
|
|
25
|
+
"Unexpected Temporal Unit: #{temporal_unit}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@temporal_unit = temporal_unit
|
|
29
|
+
@amount = amount
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#
|
|
33
|
+
# Return a map
|
|
34
|
+
#
|
|
35
|
+
# @return [Map] Map with fields representing this duration @see OpenHAB::Core::Cron
|
|
36
|
+
#
|
|
37
|
+
def cron_map
|
|
38
|
+
case @temporal_unit
|
|
39
|
+
when :SECONDS
|
|
40
|
+
cron_expression_map.merge(second: "*/#{@amount}")
|
|
41
|
+
when :MINUTES
|
|
42
|
+
cron_expression_map.merge(minute: "*/#{@amount}")
|
|
43
|
+
when :HOURS
|
|
44
|
+
cron_expression_map.merge(hour: "*/#{@amount}")
|
|
45
|
+
else
|
|
46
|
+
raise ArgumentError, "Cron Expression not supported for temporal unit: #{temporal_unit}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
# Convert the duration to milliseconds
|
|
52
|
+
#
|
|
53
|
+
# @return [Integer] Duration in milliseconds
|
|
54
|
+
#
|
|
55
|
+
def to_ms
|
|
56
|
+
case @temporal_unit
|
|
57
|
+
when :MILLISECONDS
|
|
58
|
+
@amount
|
|
59
|
+
when :SECONDS
|
|
60
|
+
@amount * 1000
|
|
61
|
+
when :MINUTES
|
|
62
|
+
@amount * 1000 * 60
|
|
63
|
+
when :HOURS
|
|
64
|
+
@amount * 1000 * 60 * 60
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openhab/configuration'
|
|
4
|
+
require 'java'
|
|
5
|
+
require 'pp'
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# Provides access to the OpenHAB logging using a Ruby logging methods
|
|
9
|
+
#
|
|
10
|
+
module Logging
|
|
11
|
+
#
|
|
12
|
+
# Ruby Logger that forwards messages at appropriate levels to OpenHAB Logger
|
|
13
|
+
#
|
|
14
|
+
class Logger
|
|
15
|
+
java_import org.slf4j.LoggerFactory
|
|
16
|
+
|
|
17
|
+
# @return [Array] Supported logging levels
|
|
18
|
+
LEVELS = %i[TRACE DEBUG WARN INFO ERROR].freeze
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Create a new logger
|
|
22
|
+
#
|
|
23
|
+
# @param [String] name of the logger
|
|
24
|
+
#
|
|
25
|
+
def initialize(name)
|
|
26
|
+
@sl4fj_logger = LoggerFactory.getLogger(name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Dynamically define the methods for each level as identified by the levels constant
|
|
30
|
+
# This creates a method for each level that looks like this
|
|
31
|
+
# def <level>(msg=nil, &block)
|
|
32
|
+
# log(severity: <level>, msg: msg, &block)
|
|
33
|
+
# end
|
|
34
|
+
LEVELS.each do |level|
|
|
35
|
+
method = level.to_s.downcase
|
|
36
|
+
define_method(method.to_s) do |msg = nil, &block|
|
|
37
|
+
log(severity: level, msg: msg, &block)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# Log a message to the OpenHAB Logger
|
|
45
|
+
#
|
|
46
|
+
# @param [Symbol] severity Severity to log message at
|
|
47
|
+
# @param [Object] msg to log, if no msg supplied and a block is provided, the msg is taken from the result of the block
|
|
48
|
+
#
|
|
49
|
+
def log(severity:, msg: nil)
|
|
50
|
+
severity = severity.to_sym
|
|
51
|
+
|
|
52
|
+
raise ArgumentError, "Unknown Severity #{severity}" unless LEVELS.include? severity
|
|
53
|
+
|
|
54
|
+
# Dynamically check enablement of underlying logger, this expands to "is_<level>_enabled"
|
|
55
|
+
return unless @sl4fj_logger.send("is_#{severity.to_s.downcase}_enabled")
|
|
56
|
+
|
|
57
|
+
# Process block if no message provided
|
|
58
|
+
if msg.nil?
|
|
59
|
+
if block_given?
|
|
60
|
+
msg = yield
|
|
61
|
+
else
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
msg = message_to_string(msg: msg)
|
|
67
|
+
|
|
68
|
+
# Dynamically invoke underlying logger, this expands to "<level>(message)"
|
|
69
|
+
@sl4fj_logger.send(severity.to_s.downcase, msg)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
#
|
|
73
|
+
# Conver the supplied message object to a String
|
|
74
|
+
#
|
|
75
|
+
# @param [object] msg object to convert
|
|
76
|
+
#
|
|
77
|
+
# @return [String] Msg object as a string
|
|
78
|
+
#
|
|
79
|
+
def message_to_string(msg:)
|
|
80
|
+
case msg
|
|
81
|
+
when ::String
|
|
82
|
+
msg
|
|
83
|
+
when ::Exception
|
|
84
|
+
"#{msg.message} (#{msg.class})\n#{msg.backtrace&.join("\n")}"
|
|
85
|
+
else
|
|
86
|
+
msg.inspect
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
@loggers = {}
|
|
92
|
+
|
|
93
|
+
# Return a logger with the configured log prefix plus the calling scripts name
|
|
94
|
+
|
|
95
|
+
#
|
|
96
|
+
# Create a logger for the current class
|
|
97
|
+
#
|
|
98
|
+
# @return [Logger] for the current class
|
|
99
|
+
#
|
|
100
|
+
def logger
|
|
101
|
+
Logging.logger(self.class.name)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class << self
|
|
105
|
+
#
|
|
106
|
+
# Injects a logger into the base class
|
|
107
|
+
#
|
|
108
|
+
# @param [String] name of the logger
|
|
109
|
+
#
|
|
110
|
+
# @return [Logger] for the supplied name
|
|
111
|
+
#
|
|
112
|
+
def logger(name)
|
|
113
|
+
name ||= self.class.name
|
|
114
|
+
@loggers[name] ||= Logging.logger_for(name)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
#
|
|
118
|
+
# Configure a logger for the supplied class name
|
|
119
|
+
#
|
|
120
|
+
# @param [String] classname to configure logger for
|
|
121
|
+
#
|
|
122
|
+
# @return [Logger] for the supplied classname
|
|
123
|
+
#
|
|
124
|
+
def logger_for(classname)
|
|
125
|
+
configure_logger_for(classname)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
#
|
|
131
|
+
# Configure a logger for the supplied classname
|
|
132
|
+
#
|
|
133
|
+
# @param [String] classname to create logger for
|
|
134
|
+
#
|
|
135
|
+
# @return [Logger] Logger for the supplied classname
|
|
136
|
+
#
|
|
137
|
+
def configure_logger_for(classname)
|
|
138
|
+
log_prefix = Configuration.log_prefix
|
|
139
|
+
log_prefix += if classname
|
|
140
|
+
".#{classname}"
|
|
141
|
+
else
|
|
142
|
+
".#{log_caller}"
|
|
143
|
+
end
|
|
144
|
+
Logger.new(log_prefix)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
#
|
|
148
|
+
# Figure out the log prefix
|
|
149
|
+
#
|
|
150
|
+
# @return [String] Prefix for log messages
|
|
151
|
+
#
|
|
152
|
+
def log_caller
|
|
153
|
+
caller_locations.map(&:path)
|
|
154
|
+
.grep_v(%r{openhab/core/})
|
|
155
|
+
.grep_v(/rubygems/)
|
|
156
|
+
.grep_v(%r{lib/ruby})
|
|
157
|
+
.first
|
|
158
|
+
.yield_self { |caller| File.basename(caller, '.*') }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
#
|
|
163
|
+
# Add logger method to the object that includes this module
|
|
164
|
+
#
|
|
165
|
+
# @param [Object] base Object to add method to
|
|
166
|
+
#
|
|
167
|
+
#
|
|
168
|
+
def self.included(base)
|
|
169
|
+
class << base
|
|
170
|
+
def logger
|
|
171
|
+
Logging.logger(self.class.name)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|