openhab-scripting 2.16.2 → 2.19.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.rb +12 -16
- data/lib/openhab/core/entity_lookup.rb +173 -0
- data/lib/openhab/core/openhab_setup.rb +31 -0
- data/lib/openhab/core/osgi.rb +61 -0
- data/lib/openhab/dsl/actions.rb +105 -0
- data/lib/openhab/dsl/dsl.rb +49 -0
- data/lib/openhab/{core/dsl → dsl}/gems.rb +0 -1
- data/lib/openhab/dsl/group.rb +100 -0
- data/lib/openhab/dsl/items/datetime_item.rb +97 -0
- data/lib/openhab/dsl/items/items.rb +46 -0
- data/lib/openhab/dsl/items/number_item.rb +352 -0
- data/lib/openhab/dsl/items/rollershutter_item.rb +179 -0
- data/lib/openhab/dsl/items/string_item.rb +120 -0
- data/lib/openhab/dsl/monkey_patch/actions/actions.rb +4 -0
- data/lib/openhab/dsl/monkey_patch/actions/script_thing_actions.rb +32 -0
- data/lib/openhab/dsl/monkey_patch/events/events.rb +5 -0
- data/lib/openhab/dsl/monkey_patch/events/item_command.rb +23 -0
- data/lib/openhab/dsl/monkey_patch/events/item_state_changed.rb +35 -0
- data/lib/openhab/dsl/monkey_patch/events/thing_status_info.rb +33 -0
- data/lib/openhab/dsl/monkey_patch/items/contact_item.rb +61 -0
- data/lib/openhab/dsl/monkey_patch/items/dimmer_item.rb +193 -0
- data/lib/openhab/dsl/monkey_patch/items/group_item.rb +37 -0
- data/lib/openhab/dsl/monkey_patch/items/items.rb +133 -0
- data/lib/openhab/dsl/monkey_patch/items/metadata.rb +281 -0
- data/lib/openhab/dsl/monkey_patch/items/persistence.rb +70 -0
- data/lib/openhab/dsl/monkey_patch/items/switch_item.rb +95 -0
- data/lib/openhab/dsl/monkey_patch/ruby/number.rb +39 -0
- data/lib/openhab/dsl/monkey_patch/ruby/range.rb +47 -0
- data/lib/openhab/dsl/monkey_patch/ruby/ruby.rb +8 -0
- data/lib/openhab/dsl/monkey_patch/ruby/string.rb +41 -0
- data/lib/openhab/dsl/monkey_patch/ruby/time.rb +32 -0
- data/lib/openhab/dsl/monkey_patch/types/decimal_type.rb +70 -0
- data/lib/openhab/dsl/monkey_patch/types/on_off_type.rb +51 -0
- data/lib/openhab/dsl/monkey_patch/types/open_closed_type.rb +36 -0
- data/lib/openhab/dsl/monkey_patch/types/percent_type.rb +32 -0
- data/lib/openhab/dsl/monkey_patch/types/quantity_type.rb +69 -0
- data/lib/openhab/dsl/monkey_patch/types/types.rb +9 -0
- data/lib/openhab/dsl/monkey_patch/types/up_down_type.rb +33 -0
- data/lib/openhab/dsl/persistence.rb +25 -0
- data/lib/openhab/dsl/rules/automation_rule.rb +342 -0
- data/lib/openhab/dsl/rules/guard.rb +134 -0
- data/lib/openhab/dsl/rules/property.rb +102 -0
- data/lib/openhab/dsl/rules/rule.rb +116 -0
- data/lib/openhab/dsl/rules/rule_config.rb +151 -0
- data/lib/openhab/dsl/rules/triggers/changed.rb +143 -0
- data/lib/openhab/dsl/rules/triggers/channel.rb +53 -0
- data/lib/openhab/dsl/rules/triggers/command.rb +104 -0
- data/lib/openhab/dsl/rules/triggers/cron.rb +177 -0
- data/lib/openhab/dsl/rules/triggers/trigger.rb +124 -0
- data/lib/openhab/dsl/rules/triggers/updated.rb +98 -0
- data/lib/openhab/dsl/states.rb +61 -0
- data/lib/openhab/dsl/things.rb +91 -0
- data/lib/openhab/dsl/time_of_day.rb +232 -0
- data/lib/openhab/dsl/timers.rb +77 -0
- data/lib/openhab/dsl/types/datetime.rb +326 -0
- data/lib/openhab/dsl/types/quantity.rb +290 -0
- data/lib/openhab/dsl/units.rb +39 -0
- data/lib/openhab/log/configuration.rb +21 -0
- data/lib/openhab/log/logger.rb +172 -0
- data/lib/openhab/version.rb +1 -1
- metadata +60 -58
- data/lib/openhab/configuration.rb +0 -16
- data/lib/openhab/core/cron.rb +0 -27
- data/lib/openhab/core/debug.rb +0 -34
- data/lib/openhab/core/dsl.rb +0 -51
- data/lib/openhab/core/dsl/actions.rb +0 -107
- data/lib/openhab/core/dsl/entities.rb +0 -147
- data/lib/openhab/core/dsl/group.rb +0 -102
- data/lib/openhab/core/dsl/items/items.rb +0 -51
- data/lib/openhab/core/dsl/items/number_item.rb +0 -323
- data/lib/openhab/core/dsl/items/string_item.rb +0 -122
- data/lib/openhab/core/dsl/monkey_patch/actions/actions.rb +0 -4
- data/lib/openhab/core/dsl/monkey_patch/actions/script_thing_actions.rb +0 -22
- data/lib/openhab/core/dsl/monkey_patch/events.rb +0 -5
- data/lib/openhab/core/dsl/monkey_patch/events/item_command.rb +0 -13
- data/lib/openhab/core/dsl/monkey_patch/events/item_state_changed.rb +0 -25
- data/lib/openhab/core/dsl/monkey_patch/events/thing_status_info.rb +0 -26
- data/lib/openhab/core/dsl/monkey_patch/items/contact_item.rb +0 -54
- data/lib/openhab/core/dsl/monkey_patch/items/dimmer_item.rb +0 -182
- data/lib/openhab/core/dsl/monkey_patch/items/group_item.rb +0 -27
- data/lib/openhab/core/dsl/monkey_patch/items/items.rb +0 -132
- data/lib/openhab/core/dsl/monkey_patch/items/metadata.rb +0 -283
- data/lib/openhab/core/dsl/monkey_patch/items/persistence.rb +0 -72
- data/lib/openhab/core/dsl/monkey_patch/items/switch_item.rb +0 -87
- data/lib/openhab/core/dsl/monkey_patch/ruby/number.rb +0 -41
- data/lib/openhab/core/dsl/monkey_patch/ruby/range.rb +0 -47
- data/lib/openhab/core/dsl/monkey_patch/ruby/ruby.rb +0 -7
- data/lib/openhab/core/dsl/monkey_patch/ruby/string.rb +0 -43
- data/lib/openhab/core/dsl/monkey_patch/types/decimal_type.rb +0 -60
- data/lib/openhab/core/dsl/monkey_patch/types/on_off_type.rb +0 -41
- data/lib/openhab/core/dsl/monkey_patch/types/open_closed_type.rb +0 -25
- data/lib/openhab/core/dsl/monkey_patch/types/percent_type.rb +0 -23
- data/lib/openhab/core/dsl/monkey_patch/types/quantity_type.rb +0 -58
- data/lib/openhab/core/dsl/monkey_patch/types/types.rb +0 -8
- data/lib/openhab/core/dsl/persistence.rb +0 -27
- data/lib/openhab/core/dsl/property.rb +0 -96
- data/lib/openhab/core/dsl/rule/automation_rule.rb +0 -345
- data/lib/openhab/core/dsl/rule/guard.rb +0 -136
- data/lib/openhab/core/dsl/rule/rule.rb +0 -117
- data/lib/openhab/core/dsl/rule/rule_config.rb +0 -153
- data/lib/openhab/core/dsl/rule/triggers/changed.rb +0 -145
- data/lib/openhab/core/dsl/rule/triggers/channel.rb +0 -55
- data/lib/openhab/core/dsl/rule/triggers/command.rb +0 -106
- data/lib/openhab/core/dsl/rule/triggers/cron.rb +0 -160
- data/lib/openhab/core/dsl/rule/triggers/trigger.rb +0 -126
- data/lib/openhab/core/dsl/rule/triggers/updated.rb +0 -100
- data/lib/openhab/core/dsl/states.rb +0 -63
- data/lib/openhab/core/dsl/things.rb +0 -93
- data/lib/openhab/core/dsl/time_of_day.rb +0 -231
- data/lib/openhab/core/dsl/timers.rb +0 -79
- data/lib/openhab/core/dsl/types/quantity.rb +0 -292
- data/lib/openhab/core/dsl/units.rb +0 -41
- data/lib/openhab/core/log.rb +0 -170
- data/lib/openhab/core/patch_load_path.rb +0 -7
- data/lib/openhab/core/startup_delay.rb +0 -23
- data/lib/openhab/osgi.rb +0 -59
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'java'
|
|
4
|
+
require 'delegate'
|
|
5
|
+
require 'forwardable'
|
|
6
|
+
|
|
7
|
+
module OpenHAB
|
|
8
|
+
module DSL
|
|
9
|
+
#
|
|
10
|
+
# Provides access to and ruby wrappers around OpenHAB timers
|
|
11
|
+
#
|
|
12
|
+
module Timers
|
|
13
|
+
java_import org.openhab.core.model.script.actions.ScriptExecution
|
|
14
|
+
java_import java.time.ZonedDateTime
|
|
15
|
+
|
|
16
|
+
# Ruby wrapper for OpenHAB Timer
|
|
17
|
+
# This class implements delegator to delegate methods to the OpenHAB timer
|
|
18
|
+
#
|
|
19
|
+
# @author Brian O'Connell
|
|
20
|
+
# @since 2.0.0
|
|
21
|
+
class Timer < SimpleDelegator
|
|
22
|
+
extend Forwardable
|
|
23
|
+
|
|
24
|
+
def_delegator :@timer, :is_active, :active?
|
|
25
|
+
def_delegator :@timer, :is_running, :running?
|
|
26
|
+
def_delegator :@timer, :has_terminated, :terminated?
|
|
27
|
+
|
|
28
|
+
#
|
|
29
|
+
# Create a new Timer Object
|
|
30
|
+
#
|
|
31
|
+
# @param [Duration] duration Duration until timer should fire
|
|
32
|
+
# @param [Block] block Block to execute when timer fires
|
|
33
|
+
#
|
|
34
|
+
def initialize(duration:, &block)
|
|
35
|
+
@duration = duration
|
|
36
|
+
|
|
37
|
+
# A semaphore is used to prevent a race condition in which calling the block from the timer thread
|
|
38
|
+
# occurs before the @timer variable can be set resulting in @timer being nil
|
|
39
|
+
semaphore = Mutex.new
|
|
40
|
+
|
|
41
|
+
timer_block = proc { semaphore.synchronize { block.call(self) } }
|
|
42
|
+
|
|
43
|
+
semaphore.synchronize do
|
|
44
|
+
@timer = ScriptExecution.createTimer(
|
|
45
|
+
ZonedDateTime.now.plus(@duration), timer_block
|
|
46
|
+
)
|
|
47
|
+
super(@timer)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
#
|
|
52
|
+
# Reschedule timer
|
|
53
|
+
#
|
|
54
|
+
# @param [Duration] duration
|
|
55
|
+
#
|
|
56
|
+
# @return [<Type>] <description>
|
|
57
|
+
#
|
|
58
|
+
def reschedule(duration = nil)
|
|
59
|
+
duration ||= @duration
|
|
60
|
+
@timer.reschedule(ZonedDateTime.now.plus(duration))
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
# Execute the supplied block after the specified duration
|
|
66
|
+
#
|
|
67
|
+
# @param [Duration] duration after which to execute the block
|
|
68
|
+
# @param [Block] block to execute, block is passed a Timer object
|
|
69
|
+
#
|
|
70
|
+
# @return [Timer] Timer object
|
|
71
|
+
#
|
|
72
|
+
def after(duration, &block)
|
|
73
|
+
Timer.new(duration: duration, &block)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'java'
|
|
4
|
+
require 'forwardable'
|
|
5
|
+
require 'time'
|
|
6
|
+
|
|
7
|
+
module OpenHAB
|
|
8
|
+
module DSL
|
|
9
|
+
module Types
|
|
10
|
+
#
|
|
11
|
+
# Ruby implementation for OpenHAB DateTimeType
|
|
12
|
+
#
|
|
13
|
+
# @author Anders Alfredsson
|
|
14
|
+
#
|
|
15
|
+
# rubocop: disable Metrics/ClassLength
|
|
16
|
+
# Disabled because this class has a single responsibility, there does not appear a logical
|
|
17
|
+
# way of breaking it up into multiple classes
|
|
18
|
+
class DateTime
|
|
19
|
+
extend Forwardable
|
|
20
|
+
include Comparable
|
|
21
|
+
include OpenHAB::Log
|
|
22
|
+
|
|
23
|
+
def_delegator :datetime, :to_s
|
|
24
|
+
def_delegator :zoned_date_time, :month_value, :month
|
|
25
|
+
def_delegator :zoned_date_time, :minute, :min
|
|
26
|
+
def_delegator :zoned_date_time, :second, :sec
|
|
27
|
+
def_delegator :zoned_date_time, :nano, :nsec
|
|
28
|
+
def_delegator :zoned_date_time, :to_epoch_second, :to_i
|
|
29
|
+
alias inspect to_s
|
|
30
|
+
|
|
31
|
+
java_import Java::OrgOpenhabCoreLibraryTypes::DateTimeType
|
|
32
|
+
java_import java.time.ZonedDateTime
|
|
33
|
+
java_import java.time.Instant
|
|
34
|
+
java_import java.time.ZoneId
|
|
35
|
+
java_import java.time.ZoneOffset
|
|
36
|
+
java_import java.time.Duration
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# Regex expression to identify strings defining a time in hours, minutes and optionally seconds
|
|
40
|
+
#
|
|
41
|
+
TIME_ONLY_REGEX = /\A\d\d:\d\d(:\d\d)?\Z/.freeze
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# Regex expression to identify strings defining a time in hours, minutes and optionally seconds
|
|
45
|
+
#
|
|
46
|
+
DATE_ONLY_REGEX = /\A\d{4}-\d\d-\d\d\Z/.freeze
|
|
47
|
+
|
|
48
|
+
attr_reader :datetime
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
# Create a new DateTime instance wrapping an OpenHAB DateTimeType
|
|
52
|
+
#
|
|
53
|
+
# @param [Java::org::openhab::core::library::types::DateTimeType] datetime The DateTimeType instance to
|
|
54
|
+
# delegate to, or an object that can be converted to a DateTimeType
|
|
55
|
+
#
|
|
56
|
+
def initialize(datetime)
|
|
57
|
+
@datetime = case datetime
|
|
58
|
+
when DateTimeType
|
|
59
|
+
datetime
|
|
60
|
+
when ZonedDateTime
|
|
61
|
+
DateTimeType.new(datetime)
|
|
62
|
+
else
|
|
63
|
+
raise "Unexpected type #{datetime.class} provided to DateTime initializer"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
#
|
|
68
|
+
# Compare thes DateTime object to another
|
|
69
|
+
#
|
|
70
|
+
# @param [Object] other Other object to compare against
|
|
71
|
+
#
|
|
72
|
+
# @return [Integer] -1, 0 or 1 depending on the outcome
|
|
73
|
+
#
|
|
74
|
+
def <=>(other)
|
|
75
|
+
case other
|
|
76
|
+
when DateTime, DateTimeType, DateTimeItem
|
|
77
|
+
zoned_date_time.to_instant.compare_to(other.zoned_date_time.to_instant)
|
|
78
|
+
when TimeOfDay::TimeOfDay, TimeOfDay::TimeOfDayRangeElement
|
|
79
|
+
to_tod <=> other
|
|
80
|
+
when String
|
|
81
|
+
self <=> DateTime.parse(DATE_ONLY_REGEX =~ other ? "#{other}'T'00:00:00#{zone}" : other)
|
|
82
|
+
else
|
|
83
|
+
self <=> DateTime.from(other)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
#
|
|
88
|
+
# Adds another object to this DateTime
|
|
89
|
+
#
|
|
90
|
+
# @param [Object] other Object to add to this. Can be a Numeric, another DateTime/Time/DateTimeType, a
|
|
91
|
+
# Duration or a String that can be parsed into a DateTimeType or Time object
|
|
92
|
+
#
|
|
93
|
+
# @return [DateTime] A new DateTime object representing the result of the calculation
|
|
94
|
+
#
|
|
95
|
+
def +(other)
|
|
96
|
+
logger.trace("Adding #{other} (#{other.class}) to #{self}")
|
|
97
|
+
case other
|
|
98
|
+
when Numeric then DateTime.from(to_time + other)
|
|
99
|
+
when DateTime, Time then self + other.to_f
|
|
100
|
+
when DateTimeType, String then self + DateTime.from(other).to_f
|
|
101
|
+
when Duration then DateTime.new(zoned_date_time.plus(other))
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#
|
|
106
|
+
# Subtracts another object from this DateTime
|
|
107
|
+
#
|
|
108
|
+
# @param [Object] other Object to subtract fom this. Can be a Numeric, another DateTime/Time/DateTimeType, a
|
|
109
|
+
# Duration or a String that can be parsed into a DateTimeType or Time object
|
|
110
|
+
#
|
|
111
|
+
# @return [DateTime, Float] A new DateTime object representing the result of the calculation, or a Float
|
|
112
|
+
# representing the time difference in seconds if the subtraction is between two time objects
|
|
113
|
+
#
|
|
114
|
+
def -(other)
|
|
115
|
+
logger.trace("Subtracting #{other} (#{other.class}) from self")
|
|
116
|
+
case other
|
|
117
|
+
when Numeric then DateTime.from(to_time - other)
|
|
118
|
+
when String
|
|
119
|
+
dt = DateTime.parse(other)
|
|
120
|
+
TIME_ONLY_REGEX =~ other ? self - dt.to_f : time_diff(dt)
|
|
121
|
+
when Duration then DateTime.new(zoned_date_time.minus(other))
|
|
122
|
+
when Time, DateTime, DateTimeType, DateTimeItem then time_diff(other)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
#
|
|
127
|
+
# Convert this DateTime to a ruby Time object
|
|
128
|
+
#
|
|
129
|
+
# @return [Time] A Time object representing the same instant and timezone
|
|
130
|
+
#
|
|
131
|
+
def to_time
|
|
132
|
+
Time.at(to_i, nsec, :nsec).localtime(utc_offset)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
#
|
|
136
|
+
# Convert the time part of this DateTime to a TimeOfDay object
|
|
137
|
+
#
|
|
138
|
+
# @return [TimeOfDay] A TimeOfDay object representing the time
|
|
139
|
+
#
|
|
140
|
+
def to_time_of_day
|
|
141
|
+
TimeOfDay::TimeOfDay.new(h: hour, m: minute, s: second)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
alias to_tod to_time_of_day
|
|
145
|
+
|
|
146
|
+
#
|
|
147
|
+
# Returns the value of time as a floating point number of seconds since the Epoch
|
|
148
|
+
#
|
|
149
|
+
# @return [Float] Number of seconds since the Epoch, with nanosecond presicion
|
|
150
|
+
#
|
|
151
|
+
def to_f
|
|
152
|
+
zoned_date_time.to_epoch_second + zoned_date_time.nano / 1_000_000_000
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
#
|
|
156
|
+
# The ZonedDateTime representing the state
|
|
157
|
+
#
|
|
158
|
+
# @return [Java::java::time::ZonedDateTime] ZonedDateTime representing the state
|
|
159
|
+
#
|
|
160
|
+
def zoned_date_time
|
|
161
|
+
@datetime.zonedDateTime
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
alias to_zdt zoned_date_time
|
|
165
|
+
|
|
166
|
+
#
|
|
167
|
+
# The offset in seconds from UTC
|
|
168
|
+
#
|
|
169
|
+
# @return [Integer] The offset from UTC, in seconds
|
|
170
|
+
#
|
|
171
|
+
def utc_offset
|
|
172
|
+
zoned_date_time.offset.total_seconds
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
#
|
|
176
|
+
# Returns true if time represents a time in UTC (GMT)
|
|
177
|
+
#
|
|
178
|
+
# @return [Boolean] true if utc_offset == 0, false otherwise
|
|
179
|
+
#
|
|
180
|
+
def utc?
|
|
181
|
+
utc_offset.zero?
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
#
|
|
185
|
+
# The timezone
|
|
186
|
+
#
|
|
187
|
+
# @return [String] The timezone in `[+-]hh:mm(:ss)` format ('Z' for UTC) or nil if the Item has no state
|
|
188
|
+
#
|
|
189
|
+
def zone
|
|
190
|
+
zoned_date_time.zone.id
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
#
|
|
194
|
+
# Check if missing method can be delegated to other contained objects
|
|
195
|
+
#
|
|
196
|
+
# @param [String, Symbol] meth the method name to check for
|
|
197
|
+
#
|
|
198
|
+
# @return [Boolean] true if DateTimeType, ZonedDateTime or Time responds to the method, false otherwise
|
|
199
|
+
#
|
|
200
|
+
def respond_to_missing?(meth, *)
|
|
201
|
+
@datetime.respond_to?(meth) ||
|
|
202
|
+
zoned_date_time.respond_to?(meth) ||
|
|
203
|
+
Time.instance_methods.include?(meth.to_sym)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
#
|
|
207
|
+
# Forward missing methods to the OpenHAB DateTimeType, its ZonedDateTime object or a ruby Time
|
|
208
|
+
# object representing the same instant
|
|
209
|
+
#
|
|
210
|
+
# @param [String] meth method name
|
|
211
|
+
# @param [Array] args arguments for method
|
|
212
|
+
# @param [Proc] block <description>
|
|
213
|
+
#
|
|
214
|
+
# @return [Object] Value from delegated method in OpenHAB NumberItem
|
|
215
|
+
#
|
|
216
|
+
def method_missing(meth, *args, &block)
|
|
217
|
+
if @datetime.respond_to?(meth)
|
|
218
|
+
@datetime.__send__(meth, *args, &block)
|
|
219
|
+
elsif zoned_date_time.respond_to?(meth)
|
|
220
|
+
zoned_date_time.__send__(meth, *args, &block)
|
|
221
|
+
elsif Time.instance_methods.include?(meth.to_sym)
|
|
222
|
+
to_time.send(meth, *args, &block)
|
|
223
|
+
else
|
|
224
|
+
raise NoMethodError, "undefined method `#{meth}' for #{self.class}"
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
#
|
|
229
|
+
# Converts other objects to a DateTimeType
|
|
230
|
+
#
|
|
231
|
+
# @param [String, Numeric, Time] datetime an object that can be parsed or converted into
|
|
232
|
+
# a DateTimeType
|
|
233
|
+
#
|
|
234
|
+
# @return [Java::org::openhab::core::library::types::DateTimeType] Object representing the same time
|
|
235
|
+
#
|
|
236
|
+
def self.from(datetime)
|
|
237
|
+
case datetime
|
|
238
|
+
when String
|
|
239
|
+
parse(datetime)
|
|
240
|
+
when Numeric
|
|
241
|
+
from_numeric(datetime)
|
|
242
|
+
when Time
|
|
243
|
+
from_time(datetime)
|
|
244
|
+
else
|
|
245
|
+
raise "Cannot convert #{datetime.class} to DateTime"
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
#
|
|
250
|
+
# Converts a Numeric into a DateTimeType
|
|
251
|
+
#
|
|
252
|
+
# @param [Numeric] numeric A Integer or Float representing the number of seconds since the epoch
|
|
253
|
+
#
|
|
254
|
+
# @return [Java::org::openhab::core::library::types::DateTimeType] Object representing the same time
|
|
255
|
+
#
|
|
256
|
+
def self.from_numeric(numeric)
|
|
257
|
+
case numeric
|
|
258
|
+
when Integer
|
|
259
|
+
DateTime.new(ZonedDateTime.ofInstant(Instant.ofEpochSecond(datetime), ZoneId.systemDefault))
|
|
260
|
+
else
|
|
261
|
+
DateTime.new(ZonedDateTime.ofInstant(Instant.ofEpochSecond(datetime.to_i,
|
|
262
|
+
((datetime % 1) * 1_000_000_000).to_i),
|
|
263
|
+
ZoneId.systemDefault))
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
#
|
|
268
|
+
# Converts a ruby Time object to an OpenHAB DateTimeType
|
|
269
|
+
#
|
|
270
|
+
# @param [Time] time The Time object to be converted
|
|
271
|
+
#
|
|
272
|
+
# @return [Java::org::openhab::core::library::types::DateTimeType] Object representing the same time
|
|
273
|
+
#
|
|
274
|
+
def self.from_time(time)
|
|
275
|
+
instant = Instant.ofEpochSecond(time.to_i, time.nsec)
|
|
276
|
+
zone_id = ZoneId.of_offset('UTC', ZoneOffset.of_total_seconds(time.utc_offset))
|
|
277
|
+
DateTime.new(ZonedDateTime.ofInstant(instant, zone_id))
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
#
|
|
281
|
+
# Parses a string representing a time into an OpenHAB DateTimeType. First tries to parse it
|
|
282
|
+
# using the DateTimeType's parser, then falls back to the ruby Time.parse
|
|
283
|
+
#
|
|
284
|
+
# @param [String] time_string The string to be parsed
|
|
285
|
+
#
|
|
286
|
+
# @return [Java::org::openhab::core::library::types::DateTimeType] Object representing the same time
|
|
287
|
+
#
|
|
288
|
+
def self.parse(time_string)
|
|
289
|
+
time_string += 'Z' if TIME_ONLY_REGEX =~ time_string
|
|
290
|
+
DateTime.new(DateTimeType.new(time_string))
|
|
291
|
+
rescue Java::JavaLang::StringIndexOutOfBoundsException, Java::JavaLang::IllegalArgumentException
|
|
292
|
+
# Try ruby's Time.parse if OpenHAB's DateTimeType parser fails
|
|
293
|
+
begin
|
|
294
|
+
time = Time.parse(time_string)
|
|
295
|
+
DateTime.from(time)
|
|
296
|
+
rescue ArgumentError
|
|
297
|
+
raise "Unable to parse #{time_string} into a DateTime"
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
private
|
|
302
|
+
|
|
303
|
+
#
|
|
304
|
+
# Calculates the difference in time between this instance and another time object
|
|
305
|
+
#
|
|
306
|
+
# @param [Time, DateTime, DateTimeItem, Java::org::openhab::core::library::types::DateTimeType] time_obj
|
|
307
|
+
# The other time object to subtract from self
|
|
308
|
+
#
|
|
309
|
+
# @return [Float] The time difference between the two objects, in seconds
|
|
310
|
+
#
|
|
311
|
+
def time_diff(time_obj)
|
|
312
|
+
logger.trace("Calculate time difference between #{self} and #{time_obj}")
|
|
313
|
+
case time_obj
|
|
314
|
+
when Time
|
|
315
|
+
to_time - time_obj
|
|
316
|
+
when DateTime, DateTimeItem
|
|
317
|
+
self - time_obj.to_time
|
|
318
|
+
when DateTimeType
|
|
319
|
+
self - DateTime.new(time_obj).to_time
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
# rubocop: enable Metrics/ClassLength
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'java'
|
|
4
|
+
require 'forwardable'
|
|
5
|
+
|
|
6
|
+
module OpenHAB
|
|
7
|
+
module DSL
|
|
8
|
+
#
|
|
9
|
+
# Ruby implementation of OpenHAB Types
|
|
10
|
+
#
|
|
11
|
+
module Types
|
|
12
|
+
#
|
|
13
|
+
# Ruby implementation for OpenHAB quantities
|
|
14
|
+
#
|
|
15
|
+
# rubocop: disable Metrics/ClassLength
|
|
16
|
+
# Disabled because this class has a single responsibility, there does not appear a logical
|
|
17
|
+
# way of breaking it up into multiple classes
|
|
18
|
+
class Quantity < Numeric
|
|
19
|
+
extend Forwardable
|
|
20
|
+
include OpenHAB::Log
|
|
21
|
+
|
|
22
|
+
def_delegator :@quantity, :to_s
|
|
23
|
+
|
|
24
|
+
java_import org.openhab.core.library.types.QuantityType
|
|
25
|
+
java_import 'tec.uom.se.format.SimpleUnitFormat'
|
|
26
|
+
java_import 'tec.uom.se.AbstractUnit'
|
|
27
|
+
|
|
28
|
+
# @return [Hash] Mapping of operation symbols to BigDecimal methods
|
|
29
|
+
OPERATIONS = {
|
|
30
|
+
'+' => 'add',
|
|
31
|
+
'-' => 'subtract',
|
|
32
|
+
'*' => 'multiply',
|
|
33
|
+
'/' => 'divide'
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
private_constant :OPERATIONS
|
|
37
|
+
|
|
38
|
+
attr_reader :quantity
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
# Create a new Quantity
|
|
42
|
+
#
|
|
43
|
+
# @param [object] quantity String,QuantityType or Numeric to be this quantity
|
|
44
|
+
#
|
|
45
|
+
# Cop disabled, case statement is compact and idiomatic
|
|
46
|
+
def initialize(quantity)
|
|
47
|
+
@quantity = case quantity
|
|
48
|
+
when String then QuantityType.new(quantity)
|
|
49
|
+
when QuantityType then quantity
|
|
50
|
+
when NumberItem, Numeric then QuantityType.new(quantity.to_d.to_java, AbstractUnit::ONE)
|
|
51
|
+
else raise ArgumentError, "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
|
+
my_qt, other_qt = unitize(*to_qt(coerce(other).reverse))
|
|
80
|
+
my_qt.compare_to(other_qt)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# Coerce other object into a Quantity
|
|
85
|
+
#
|
|
86
|
+
# @param [Object] other object to convert to Quantity
|
|
87
|
+
#
|
|
88
|
+
# @return [Array] of self and other object as Quantity types, nil if object cannot be coerced
|
|
89
|
+
#
|
|
90
|
+
def coerce(other)
|
|
91
|
+
logger.trace("Coercing #{self} as a request from #{other.class}")
|
|
92
|
+
case other
|
|
93
|
+
when Quantity then [other.quantity, quantity]
|
|
94
|
+
when QuantityType then [other, quantity]
|
|
95
|
+
when NumberItem then [other.to_qt.quantity, quantity]
|
|
96
|
+
when Numeric, String then [Quantity.new(other), self]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
#
|
|
101
|
+
# Forward missing methods to Openhab Quantity Item if they are defined
|
|
102
|
+
#
|
|
103
|
+
# @param [String] meth name of method invoked
|
|
104
|
+
# @param [Array] args arguments to invoked method
|
|
105
|
+
# @param [Proc] block block passed ot method
|
|
106
|
+
#
|
|
107
|
+
# @return [Object] result of delegation
|
|
108
|
+
#
|
|
109
|
+
def method_missing(meth, *args, &block)
|
|
110
|
+
logger.trace("Method missing, performing dynamic lookup for: #{meth}")
|
|
111
|
+
if quantity.respond_to?(meth)
|
|
112
|
+
quantity.__send__(meth, *args, &block)
|
|
113
|
+
elsif ::Kernel.method_defined?(meth) || ::Kernel.private_method_defined?(meth)
|
|
114
|
+
::Kernel.instance_method(meth).bind_call(self, *args, &block)
|
|
115
|
+
else
|
|
116
|
+
super(meth, *args, &block)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
#
|
|
121
|
+
# Checks if this method responds to the missing method
|
|
122
|
+
#
|
|
123
|
+
# @param [String] method_name Name of the method to check
|
|
124
|
+
# @param [Boolean] _include_private boolean if private methods should be checked
|
|
125
|
+
#
|
|
126
|
+
# @return [Boolean] true if this object will respond to the supplied method, false otherwise
|
|
127
|
+
#
|
|
128
|
+
def respond_to_missing?(method_name, _include_private = false)
|
|
129
|
+
quantity.respond_to?(method_name) ||
|
|
130
|
+
::Kernel.method_defined?(method_name) ||
|
|
131
|
+
::Kernel.private_method_defined?(method_name)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
#
|
|
135
|
+
# Negate the quantity
|
|
136
|
+
#
|
|
137
|
+
# @return [Quantity] This quantity negated
|
|
138
|
+
#
|
|
139
|
+
def -@
|
|
140
|
+
Quantity.new(quantity.negate)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
OPERATIONS.each do |operation, method|
|
|
144
|
+
define_method(operation) do |other|
|
|
145
|
+
logger.trace("Executing math operation '#{operation}' on quantity #{inspect} "\
|
|
146
|
+
"with other type #{other.class} and value #{other.inspect}")
|
|
147
|
+
|
|
148
|
+
a, b = to_qt(coerce(other).reverse)
|
|
149
|
+
logger.trace("Coerced a='#{a}' with b='#{b}'")
|
|
150
|
+
a, b = unitize(a, b, operation)
|
|
151
|
+
logger.trace("Unitized a='#{a}' b='#{b}'")
|
|
152
|
+
logger.trace("Performing operation '#{operation}' with method '#{method}' on a='#{a}' with b='#{b}'")
|
|
153
|
+
Quantity.new(a.public_send(method, b))
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
#
|
|
158
|
+
# Provide details about quantity object
|
|
159
|
+
#
|
|
160
|
+
# @return [String] Representing details about the quantity object
|
|
161
|
+
#
|
|
162
|
+
def inspect
|
|
163
|
+
if @quantity.unit == AbstractUnit::ONE
|
|
164
|
+
"unit=#{@quantity.unit}, value=#{@quantity.to_string}"
|
|
165
|
+
else
|
|
166
|
+
@quantity.to_string
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
# @return [Array] Array of strings for operations for which the operands will not be unitized
|
|
173
|
+
DIMENSIONLESS_NON_UNITIZED_OPERATIONS = %w[* /].freeze
|
|
174
|
+
|
|
175
|
+
# Dimensionless numbers should only be unitzed for addition and subtraction
|
|
176
|
+
|
|
177
|
+
#
|
|
178
|
+
# Convert one or more Quantity obects to the underlying quantitytypes
|
|
179
|
+
#
|
|
180
|
+
# @param [Array] quanities Array of either Quantity or QuantityType objects
|
|
181
|
+
#
|
|
182
|
+
# @return [Array] Array of QuantityType objects
|
|
183
|
+
#
|
|
184
|
+
def to_qt(*quanities)
|
|
185
|
+
[quanities].flatten.compact.map { |item| item.is_a?(Quantity) ? item.quantity : item }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
#
|
|
189
|
+
# Checks if an item should be unitized
|
|
190
|
+
#
|
|
191
|
+
# @param [Quantity] quantity to check
|
|
192
|
+
# @param [String] operation quantity is being used with
|
|
193
|
+
#
|
|
194
|
+
# @return [Boolean] True if the quantity should be unitzed based on the unit and operation, false otherwise
|
|
195
|
+
#
|
|
196
|
+
def unitize?(quantity, operation)
|
|
197
|
+
!(quantity.unit == AbstractUnit::ONE && DIMENSIONLESS_NON_UNITIZED_OPERATIONS.include?(operation))
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
#
|
|
201
|
+
# Convert the unit for the quantity
|
|
202
|
+
#
|
|
203
|
+
# @param [Quantity] quantity being converted
|
|
204
|
+
#
|
|
205
|
+
# @return [Quantity] Quantity coverted to unit set by unit block
|
|
206
|
+
#
|
|
207
|
+
def convert_unit(quantity)
|
|
208
|
+
return quantity unless unit?
|
|
209
|
+
|
|
210
|
+
case quantity.unit
|
|
211
|
+
when unit
|
|
212
|
+
quantity
|
|
213
|
+
when AbstractUnit::ONE
|
|
214
|
+
convert_unit_from_dimensionless(quantity, unit)
|
|
215
|
+
else
|
|
216
|
+
convert_unit_from_dimensioned(quantity, unit)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
#
|
|
221
|
+
# Converts a dimensioned quantity to a specific unit
|
|
222
|
+
#
|
|
223
|
+
# @param [Quantity] quantity to convert
|
|
224
|
+
# @param [Unit] unit to convert to
|
|
225
|
+
#
|
|
226
|
+
# @return [Java::org::openhab::core::library::types::QuantityType] converted quantity
|
|
227
|
+
#
|
|
228
|
+
def convert_unit_from_dimensioned(quantity, unit)
|
|
229
|
+
logger.trace("Converting dimensioned item #{inspect} to #{unit}")
|
|
230
|
+
quantity.to_unit(unit).tap do |converted|
|
|
231
|
+
raise "Conversion from #{quantity.unit} to #{unit} failed" unless converted
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
#
|
|
236
|
+
# Converts a dimensionless quantity to a unit
|
|
237
|
+
#
|
|
238
|
+
# @param [Quantity] quantity to convert
|
|
239
|
+
# @param [Unit] unit to convert to
|
|
240
|
+
#
|
|
241
|
+
# @return [Java::org::openhab::core::library::types::QuantityType] converted quantity
|
|
242
|
+
#
|
|
243
|
+
def convert_unit_from_dimensionless(quantity, unit)
|
|
244
|
+
logger.trace("Converting dimensionless #{quantity} to #{unit}")
|
|
245
|
+
QuantityType.new(quantity.to_big_decimal, unit)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
#
|
|
249
|
+
# Convert quantities to appropriate units
|
|
250
|
+
#
|
|
251
|
+
# @param [Quantity] quantity_a Quantity on left side of operation
|
|
252
|
+
# @param [Quantity] quantity_b Quantity on right side of operation
|
|
253
|
+
# @param [String] operation Math operation
|
|
254
|
+
# @yield [quantity_a, quantity_b] yields unitized versions of supplied quantities
|
|
255
|
+
#
|
|
256
|
+
# @return [Array, Object] of quantites in correct units for the supplied operation and the unit
|
|
257
|
+
# or the result of the block if a block is given
|
|
258
|
+
#
|
|
259
|
+
def unitize(quantity_a, quantity_b, operation = nil)
|
|
260
|
+
logger.trace("Unitizing (#{quantity_a}) and (#{quantity_b})")
|
|
261
|
+
quantity_a, quantity_b = [quantity_a, quantity_b].map do |qt|
|
|
262
|
+
unitize?(qt, operation) ? convert_unit(qt) : qt
|
|
263
|
+
end
|
|
264
|
+
return yield quantity_a, quantity_b if block_given?
|
|
265
|
+
|
|
266
|
+
[quantity_a, quantity_b]
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
#
|
|
270
|
+
# Get the unit from the current thread local variable
|
|
271
|
+
#
|
|
272
|
+
# @return [Object] Unit or string representation of Unit, or nil if not set
|
|
273
|
+
#
|
|
274
|
+
def unit
|
|
275
|
+
Thread.current.thread_variable_get(:unit)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
#
|
|
279
|
+
# Is a unit set for this thread
|
|
280
|
+
#
|
|
281
|
+
# @return [boolean] true if a unit is set by this thread, false otherwise
|
|
282
|
+
#
|
|
283
|
+
def unit?
|
|
284
|
+
unit != nil
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
# rubocop: enable Metrics/ClassLength
|