openhab-jrubyscripting 5.0.0.rc1

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.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/lib/openhab/core/actions.rb +163 -0
  3. data/lib/openhab/core/entity_lookup.rb +144 -0
  4. data/lib/openhab/core/events/abstract_event.rb +17 -0
  5. data/lib/openhab/core/events/item_channel_link.rb +36 -0
  6. data/lib/openhab/core/events/item_command_event.rb +78 -0
  7. data/lib/openhab/core/events/item_event.rb +22 -0
  8. data/lib/openhab/core/events/item_state_changed_event.rb +52 -0
  9. data/lib/openhab/core/events/item_state_event.rb +51 -0
  10. data/lib/openhab/core/events/thing.rb +29 -0
  11. data/lib/openhab/core/events/thing_status_info_event.rb +53 -0
  12. data/lib/openhab/core/events.rb +10 -0
  13. data/lib/openhab/core/items/accepted_data_types.rb +29 -0
  14. data/lib/openhab/core/items/color_item.rb +52 -0
  15. data/lib/openhab/core/items/contact_item.rb +52 -0
  16. data/lib/openhab/core/items/date_time_item.rb +58 -0
  17. data/lib/openhab/core/items/dimmer_item.rb +148 -0
  18. data/lib/openhab/core/items/generic_item.rb +344 -0
  19. data/lib/openhab/core/items/group_item.rb +174 -0
  20. data/lib/openhab/core/items/image_item.rb +109 -0
  21. data/lib/openhab/core/items/location_item.rb +34 -0
  22. data/lib/openhab/core/items/metadata/hash.rb +390 -0
  23. data/lib/openhab/core/items/metadata/namespace_hash.rb +469 -0
  24. data/lib/openhab/core/items/metadata.rb +11 -0
  25. data/lib/openhab/core/items/number_item.rb +62 -0
  26. data/lib/openhab/core/items/numeric_item.rb +22 -0
  27. data/lib/openhab/core/items/persistence.rb +327 -0
  28. data/lib/openhab/core/items/player_item.rb +66 -0
  29. data/lib/openhab/core/items/proxy.rb +59 -0
  30. data/lib/openhab/core/items/registry.rb +66 -0
  31. data/lib/openhab/core/items/rollershutter_item.rb +68 -0
  32. data/lib/openhab/core/items/semantics/enumerable.rb +152 -0
  33. data/lib/openhab/core/items/semantics.rb +476 -0
  34. data/lib/openhab/core/items/state_storage.rb +53 -0
  35. data/lib/openhab/core/items/string_item.rb +28 -0
  36. data/lib/openhab/core/items/switch_item.rb +78 -0
  37. data/lib/openhab/core/items.rb +114 -0
  38. data/lib/openhab/core/lazy_array.rb +52 -0
  39. data/lib/openhab/core/profile_factory.rb +118 -0
  40. data/lib/openhab/core/script_handling.rb +55 -0
  41. data/lib/openhab/core/things/channel.rb +48 -0
  42. data/lib/openhab/core/things/channel_uid.rb +51 -0
  43. data/lib/openhab/core/things/item_channel_link.rb +33 -0
  44. data/lib/openhab/core/things/profile_callback.rb +52 -0
  45. data/lib/openhab/core/things/proxy.rb +69 -0
  46. data/lib/openhab/core/things/registry.rb +46 -0
  47. data/lib/openhab/core/things/thing.rb +194 -0
  48. data/lib/openhab/core/things.rb +22 -0
  49. data/lib/openhab/core/timer.rb +128 -0
  50. data/lib/openhab/core/types/comparable_type.rb +23 -0
  51. data/lib/openhab/core/types/date_time_type.rb +259 -0
  52. data/lib/openhab/core/types/decimal_type.rb +192 -0
  53. data/lib/openhab/core/types/hsb_type.rb +183 -0
  54. data/lib/openhab/core/types/increase_decrease_type.rb +34 -0
  55. data/lib/openhab/core/types/next_previous_type.rb +34 -0
  56. data/lib/openhab/core/types/numeric_type.rb +52 -0
  57. data/lib/openhab/core/types/on_off_type.rb +46 -0
  58. data/lib/openhab/core/types/open_closed_type.rb +41 -0
  59. data/lib/openhab/core/types/percent_type.rb +95 -0
  60. data/lib/openhab/core/types/play_pause_type.rb +38 -0
  61. data/lib/openhab/core/types/point_type.rb +117 -0
  62. data/lib/openhab/core/types/quantity_type.rb +327 -0
  63. data/lib/openhab/core/types/raw_type.rb +26 -0
  64. data/lib/openhab/core/types/refresh_type.rb +27 -0
  65. data/lib/openhab/core/types/rewind_fastforward_type.rb +38 -0
  66. data/lib/openhab/core/types/stop_move_type.rb +34 -0
  67. data/lib/openhab/core/types/string_type.rb +76 -0
  68. data/lib/openhab/core/types/type.rb +117 -0
  69. data/lib/openhab/core/types/un_def_type.rb +38 -0
  70. data/lib/openhab/core/types/up_down_type.rb +50 -0
  71. data/lib/openhab/core/types.rb +69 -0
  72. data/lib/openhab/core/uid.rb +36 -0
  73. data/lib/openhab/core.rb +85 -0
  74. data/lib/openhab/core_ext/java/duration.rb +115 -0
  75. data/lib/openhab/core_ext/java/local_date.rb +93 -0
  76. data/lib/openhab/core_ext/java/local_time.rb +106 -0
  77. data/lib/openhab/core_ext/java/month.rb +59 -0
  78. data/lib/openhab/core_ext/java/month_day.rb +105 -0
  79. data/lib/openhab/core_ext/java/period.rb +103 -0
  80. data/lib/openhab/core_ext/java/temporal_amount.rb +34 -0
  81. data/lib/openhab/core_ext/java/time.rb +58 -0
  82. data/lib/openhab/core_ext/java/unit.rb +15 -0
  83. data/lib/openhab/core_ext/java/zoned_date_time.rb +116 -0
  84. data/lib/openhab/core_ext/ruby/array.rb +21 -0
  85. data/lib/openhab/core_ext/ruby/class.rb +15 -0
  86. data/lib/openhab/core_ext/ruby/date.rb +89 -0
  87. data/lib/openhab/core_ext/ruby/numeric.rb +190 -0
  88. data/lib/openhab/core_ext/ruby/range.rb +70 -0
  89. data/lib/openhab/core_ext/ruby/time.rb +104 -0
  90. data/lib/openhab/core_ext.rb +18 -0
  91. data/lib/openhab/dsl/events/watch_event.rb +18 -0
  92. data/lib/openhab/dsl/events.rb +9 -0
  93. data/lib/openhab/dsl/gems.rb +3 -0
  94. data/lib/openhab/dsl/items/builder.rb +618 -0
  95. data/lib/openhab/dsl/items/ensure.rb +93 -0
  96. data/lib/openhab/dsl/items/timed_command.rb +236 -0
  97. data/lib/openhab/dsl/rules/automation_rule.rb +308 -0
  98. data/lib/openhab/dsl/rules/builder.rb +1373 -0
  99. data/lib/openhab/dsl/rules/guard.rb +115 -0
  100. data/lib/openhab/dsl/rules/name_inference.rb +160 -0
  101. data/lib/openhab/dsl/rules/property.rb +76 -0
  102. data/lib/openhab/dsl/rules/rule_triggers.rb +96 -0
  103. data/lib/openhab/dsl/rules/terse.rb +63 -0
  104. data/lib/openhab/dsl/rules/triggers/changed.rb +169 -0
  105. data/lib/openhab/dsl/rules/triggers/channel.rb +57 -0
  106. data/lib/openhab/dsl/rules/triggers/command.rb +107 -0
  107. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +161 -0
  108. data/lib/openhab/dsl/rules/triggers/conditions/proc.rb +164 -0
  109. data/lib/openhab/dsl/rules/triggers/cron/cron.rb +195 -0
  110. data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +127 -0
  111. data/lib/openhab/dsl/rules/triggers/trigger.rb +56 -0
  112. data/lib/openhab/dsl/rules/triggers/updated.rb +130 -0
  113. data/lib/openhab/dsl/rules/triggers/watch/watch.rb +55 -0
  114. data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +155 -0
  115. data/lib/openhab/dsl/rules/triggers.rb +12 -0
  116. data/lib/openhab/dsl/rules.rb +29 -0
  117. data/lib/openhab/dsl/script_handling.rb +55 -0
  118. data/lib/openhab/dsl/things/builder.rb +263 -0
  119. data/lib/openhab/dsl/thread_local.rb +48 -0
  120. data/lib/openhab/dsl/timer_manager.rb +191 -0
  121. data/lib/openhab/dsl/version.rb +9 -0
  122. data/lib/openhab/dsl.rb +686 -0
  123. data/lib/openhab/log.rb +348 -0
  124. data/lib/openhab/osgi.rb +70 -0
  125. data/lib/openhab/rspec/configuration.rb +56 -0
  126. data/lib/openhab/rspec/example_group.rb +90 -0
  127. data/lib/openhab/rspec/helpers.rb +439 -0
  128. data/lib/openhab/rspec/hooks.rb +93 -0
  129. data/lib/openhab/rspec/jruby.rb +46 -0
  130. data/lib/openhab/rspec/karaf.rb +811 -0
  131. data/lib/openhab/rspec/mocks/bundle_install_support.rb +25 -0
  132. data/lib/openhab/rspec/mocks/bundle_resolver.rb +30 -0
  133. data/lib/openhab/rspec/mocks/event_admin.rb +146 -0
  134. data/lib/openhab/rspec/mocks/metadata_provider.rb +75 -0
  135. data/lib/openhab/rspec/mocks/persistence_service.rb +140 -0
  136. data/lib/openhab/rspec/mocks/safe_caller.rb +40 -0
  137. data/lib/openhab/rspec/mocks/synchronous_executor.rb +56 -0
  138. data/lib/openhab/rspec/mocks/thing_handler.rb +76 -0
  139. data/lib/openhab/rspec/mocks/timer.rb +95 -0
  140. data/lib/openhab/rspec/openhab/core/actions.rb +26 -0
  141. data/lib/openhab/rspec/openhab/core/items/proxy.rb +27 -0
  142. data/lib/openhab/rspec/openhab/core/things/proxy.rb +27 -0
  143. data/lib/openhab/rspec/shell.rb +31 -0
  144. data/lib/openhab/rspec/suspend_rules.rb +60 -0
  145. data/lib/openhab/rspec.rb +17 -0
  146. data/lib/openhab/yard/cli/stats.rb +23 -0
  147. data/lib/openhab/yard/code_objects/group_object.rb +17 -0
  148. data/lib/openhab/yard/code_objects/java/base.rb +31 -0
  149. data/lib/openhab/yard/code_objects/java/class_object.rb +11 -0
  150. data/lib/openhab/yard/code_objects/java/field_object.rb +15 -0
  151. data/lib/openhab/yard/code_objects/java/interface_object.rb +15 -0
  152. data/lib/openhab/yard/code_objects/java/package_object.rb +11 -0
  153. data/lib/openhab/yard/code_objects/java/proxy.rb +23 -0
  154. data/lib/openhab/yard/handlers/jruby/base.rb +49 -0
  155. data/lib/openhab/yard/handlers/jruby/class_handler.rb +18 -0
  156. data/lib/openhab/yard/handlers/jruby/constant_handler.rb +18 -0
  157. data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +27 -0
  158. data/lib/openhab/yard/handlers/jruby/mixin_handler.rb +23 -0
  159. data/lib/openhab/yard/html_helper.rb +44 -0
  160. data/lib/openhab/yard/tags/constant_directive.rb +20 -0
  161. data/lib/openhab/yard/tags/group_directive.rb +24 -0
  162. data/lib/openhab/yard/tags/library.rb +3 -0
  163. data/lib/openhab/yard.rb +32 -0
  164. metadata +504 -0
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module OpenHAB
6
+ module Core
7
+ module Things
8
+ #
9
+ # Provides access to all OpenHAB {Thing things}, and acts like an array.
10
+ #
11
+ class Registry
12
+ include LazyArray
13
+ include Singleton
14
+
15
+ #
16
+ # Gets a specific {Thing}
17
+ #
18
+ # @param [String, ThingUID] uid Thing UID in the format `binding_id:type_id:thing_id`
19
+ # or via the ThingUID
20
+ # @return [Thing, nil]
21
+ #
22
+ def [](uid)
23
+ EntityLookup.lookup_thing(uid)
24
+ end
25
+ alias_method :include?, :[]
26
+ alias_method :key?, :[]
27
+
28
+ #
29
+ # Explicit conversion to array
30
+ #
31
+ # @return [Array<Thing>]
32
+ #
33
+ def to_a
34
+ $things.all.map { |thing| Proxy.new(thing) }
35
+ end
36
+
37
+ # Enter the Thing Builder DSL.
38
+ # @yield Block executed in the context of a {DSL::Things::Builder}.
39
+ # @return [Object] The result of the block.
40
+ def build(&block)
41
+ DSL::Things::Builder.new.instance_eval(&block)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module Core
5
+ module Things
6
+ # @interface
7
+ java_import org.openhab.core.thing.Thing
8
+
9
+ #
10
+ # A {Thing} is a representation of a connected part (e.g. physical device
11
+ # or cloud service) from the real world. It contains a list of
12
+ # {Channel Channels}, which can be bound to {GenericItem Items}.
13
+ #
14
+ # @see OpenHAB::DSL.things things[]
15
+ # @see EntityLookup
16
+ #
17
+ # @example
18
+ # thing = things["chromecast:audiogroup:dd9f8622-eee-4eaf-b33f-cdcdcdeee001121"]
19
+ # logger.info("Audiogroup Status: #{thing&.status}")
20
+ # logger.info("Audiogroup Online? #{thing&.online?}")
21
+ # logger.info("Channel ids: #{thing.channels.map(&:uid)}")
22
+ # logger.info("Items linked to volume channel: #{thing.channels['volume']&.items&.map(&:name)&.join(', ')}")
23
+ # logger.info("Item linked to volume channel: #{thing.channels['volume']&.item&.name}")
24
+ #
25
+ # @example Thing actions can be called directly through a Thing object
26
+ # things["mqtt:broker:mosquitto"].publish_mqtt("zigbee2mqttt/bridge/config/permit_join", "true")
27
+ # things["mail:smtp:local"].send_mail("me@example.com", "Subject", "Email body")
28
+ #
29
+ # @example Thing can be accessed directly through {EntityLookup entity lookup}
30
+ # # replace ':' with '_' in thing uid
31
+ # mqtt_broker_mosquitto.online? # is mqtt:broker:mosquitto thing online?
32
+ #
33
+ # @!attribute [r] status
34
+ # Return the {https://www.openhab.org/docs/concepts/things.html#thing-status thing status}
35
+ # @return [org.openhab.core.thing.ThingStatus]
36
+ #
37
+ # @!attribute [r] channels
38
+ # @return [ChannelArray]
39
+ #
40
+ module Thing
41
+ # Array wrapper class to allow searching a list of channels
42
+ # by channel id
43
+ class ChannelsArray < Array
44
+ # Allows indexing by both integer as an array or channel id acting like a hash.
45
+ # @param [Integer, String] index Numeric index or string channel id to search for.
46
+ def [](index)
47
+ if index.respond_to?(:to_str)
48
+ key = index.to_str
49
+ return find { |channel| channel.uid.id == key }
50
+ end
51
+
52
+ super
53
+ end
54
+ end
55
+
56
+ class << self
57
+ # @!visibility private
58
+ #
59
+ # Override to support Proxy
60
+ #
61
+ def ===(other)
62
+ other.is_a?(self)
63
+ end
64
+ end
65
+
66
+ #
67
+ # @!method uninitialized?
68
+ # Check if thing status == UNINITIALIZED
69
+ # @return [true,false]
70
+ #
71
+
72
+ #
73
+ # @!method initialized?
74
+ # Check if thing status == INITIALIZED
75
+ # @return [true,false]
76
+ #
77
+
78
+ #
79
+ # @!method unknown?
80
+ # Check if thing status == UNKNOWN
81
+ # @return [true,false]
82
+ #
83
+
84
+ #
85
+ # @!method online?
86
+ # Check if thing status == ONLINE
87
+ # @return [true,false]
88
+ #
89
+
90
+ #
91
+ # @!method offline?
92
+ # Check if thing status == OFFLINE
93
+ # @return [true,false]
94
+ #
95
+
96
+ #
97
+ # @!method removing?
98
+ # Check if thing status == REMOVING
99
+ # @return [true,false]
100
+ #
101
+
102
+ #
103
+ # @!method removed?
104
+ # Check if thing status == REMOVED
105
+ # @return [true,false]
106
+ #
107
+
108
+ ThingStatus.constants.each do |thingstatus|
109
+ define_method("#{thingstatus.to_s.downcase}?") { status == ThingStatus.value_of(thingstatus) }
110
+ end
111
+
112
+ #
113
+ # Enable the Thing
114
+ #
115
+ # @param [true, false] enabled
116
+ # @return [void]
117
+ #
118
+ def enable(enabled: true)
119
+ Things.manager.set_enabled(uid, enabled)
120
+ end
121
+
122
+ #
123
+ # Disable the Thing
124
+ #
125
+ # @return [void]
126
+ #
127
+ def disable
128
+ enable(enabled: false)
129
+ end
130
+
131
+ # @return [String]
132
+ def inspect
133
+ r = "#<OpenHAB::Core::Things::Thing #{uid}"
134
+ r += " #{label.inspect}" if label
135
+ r += " (#{location.inspect})" if location
136
+ r += " #{status}"
137
+ unless status_info.status_detail == org.openhab.core.thing.ThingStatusDetail::NONE
138
+ r += " (#{status_info.status_detail})"
139
+ end
140
+ r += " configuration=#{configuration.properties.to_h}" unless configuration.properties.empty?
141
+ r += " properties=#{properties.to_h}" unless properties.empty?
142
+ "#{r}>"
143
+ end
144
+
145
+ #
146
+ # Return Thing's uid as a string
147
+ #
148
+ # @return [String]
149
+ #
150
+ def to_s
151
+ uid.to_s
152
+ end
153
+
154
+ #
155
+ # Fetches the actions available for this thing.
156
+ #
157
+ # Default scope actions are available directly on the thing object, via
158
+ # {#method_missing}.
159
+ #
160
+ # @param [String, nil] scope The action scope. Default's to the thing's binding.
161
+ # @return [Object, nil]
162
+ #
163
+ # @example
164
+ # things["max:thermostat:mybridge:thermostat"].actions("max-devices").delete_from_cube
165
+ #
166
+ # @example (see #method_missing)
167
+ #
168
+ def actions(scope = nil)
169
+ $actions.get(scope || uid.binding_id, uid.to_s)
170
+ end
171
+
172
+ #
173
+ # Delegate missing methods to the thing's default actions scope.
174
+ #
175
+ # @example
176
+ # things['mail:smtp:local'].send_email('me@example.com', 'subject', 'message')
177
+ #
178
+ def method_missing(method, *args, &block)
179
+ return actions.public_send(method, *args, &block) if actions.respond_to?(method)
180
+
181
+ super
182
+ end
183
+
184
+ # @!visibility private
185
+ def respond_to_missing?(method_name, _include_private = false)
186
+ logger.trace("Checking if Thing #{uid} supports #{method_name} action")
187
+ return true if actions.respond_to?(method_name)
188
+
189
+ super
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module Core
5
+ #
6
+ # Contains the core {Thing} that bindings use to represent connected devices,
7
+ # as well as related infrastructure.
8
+ #
9
+ module Things
10
+ java_import org.openhab.core.thing.ThingStatus,
11
+ org.openhab.core.thing.ThingUID,
12
+ org.openhab.core.thing.ThingTypeUID
13
+
14
+ class << self
15
+ # @!visibility private
16
+ def manager
17
+ @manager ||= OSGi.service("org.openhab.core.thing.ThingManager")
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module OpenHAB
6
+ module Core
7
+ #
8
+ # Timer allows you to administer the block of code that
9
+ # has been scheduled to run later with {OpenHAB::DSL.after after}.
10
+ #
11
+ # @!attribute [r] execution_time
12
+ # @return [ZonedDateTime] the scheduled execution time, or null if the timer was cancelled
13
+ class Timer
14
+ extend Forwardable
15
+
16
+ # @!method active?
17
+ # Check if the timer will execute in the future.
18
+ # @return [true,false]
19
+
20
+ # @!method cancelled?
21
+ # Check if the timer has been cancelled.
22
+ # @return [true,false]
23
+
24
+ # @!method running?
25
+ # Check if the timer code is currently running.
26
+ # @return [true,false]
27
+
28
+ # @!method terminated?
29
+ # Check if the timer has terminated.
30
+ # @return [true,false]
31
+
32
+ def_delegator :@timer, :has_terminated, :terminated?
33
+ def_delegators :@timer, :execution_time, :active?, :cancelled?, :running?
34
+
35
+ # @return [Object, nil]
36
+ attr_accessor :id
37
+
38
+ # @!visibility private
39
+ # @!visibility private
40
+ attr_reader :block
41
+
42
+ #
43
+ # Create a new Timer Object
44
+ #
45
+ # @param [java.time.temporal.TemporalAmount, #to_zoned_date_time, Proc] time When to execute the block
46
+ # @yield Block to execute when timer fires
47
+ # @yieldparam [self]
48
+ #
49
+ # @!visibility private
50
+ def initialize(time, id:, thread_locals:, block:)
51
+ @time = time
52
+ @id = id
53
+ @thread_locals = thread_locals
54
+ @block = block
55
+ @timer = org.openhab.core.model.script.actions.ScriptExecution.create_timer(
56
+ # create it far enough in the future so it won't execute until we finish setting it up
57
+ 1.minute.from_now,
58
+ # when running in rspec, it may have troubles finding this class
59
+ # for auto-conversion of block to interface, so use .impl
60
+ org.eclipse.xtext.xbase.lib.Procedures::Procedure0.impl { execute }
61
+ )
62
+ reschedule(@time)
63
+ end
64
+
65
+ # @return [String]
66
+ def inspect
67
+ r = "#<#{self.class.name} #{"#{id.inspect} " if id}#{block.source_location.join(":")}"
68
+ if cancelled?
69
+ r += " (cancelled)"
70
+ else
71
+ r += " @ #{execution_time}"
72
+ r += " (executed)" if terminated?
73
+ end
74
+ "#{r}>"
75
+ end
76
+ alias_method :to_s, :inspect
77
+
78
+ #
79
+ # Reschedule timer
80
+ #
81
+ # @param [java.time.temporal.TemporalAmount, ZonedDateTime, Proc, nil] time When to reschedule the timer for.
82
+ # If unspecified, the original time is used.
83
+ #
84
+ # @return [self]
85
+ #
86
+ def reschedule(time = nil)
87
+ DSL.timers.add(self)
88
+ @timer.reschedule(new_execution_time(time || @time))
89
+ self
90
+ end
91
+
92
+ #
93
+ # Cancel timer
94
+ #
95
+ # @return [true,false] True if cancel was successful, false otherwise
96
+ #
97
+ def cancel
98
+ DSL.timers.delete(self)
99
+ @timer.cancel
100
+ end
101
+
102
+ private
103
+
104
+ #
105
+ # Calls the block with thread locals set up, and cleans up after itself
106
+ #
107
+ # @return [void]
108
+ #
109
+ def execute
110
+ last_execution_time = execution_time
111
+ DSL::ThreadLocal.thread_local(**@thread_locals) do
112
+ @block.call(self)
113
+ end
114
+ # don't remove ourselves if we were rescheduled in the block
115
+ DSL.timers.delete(self) if execution_time == last_execution_time
116
+ end
117
+
118
+ #
119
+ # @return [ZonedDateTime]
120
+ #
121
+ def new_execution_time(time)
122
+ time = time.call if time.is_a?(Proc)
123
+ time = time.from_now if time.is_a?(java.time.temporal.TemporalAmount)
124
+ time.to_zoned_date_time
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "type"
4
+
5
+ module OpenHAB
6
+ module Core
7
+ # `Comparable#==` is overwritten by Type, because {DecimalType} etc.
8
+ # inherit from `Comparable` on the Java side, so it's in the wrong place
9
+ # in the ancestor list
10
+ # @!visibility private
11
+ module ComparableType
12
+ # re-implement
13
+ # @!visibility private
14
+ def ==(other)
15
+ r = self <=> other
16
+
17
+ return false if r.nil?
18
+
19
+ r.zero?
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,259 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "time"
5
+
6
+ require_relative "type"
7
+
8
+ module OpenHAB
9
+ module Core
10
+ module Types
11
+ DateTimeType = org.openhab.core.library.types.DateTimeType
12
+
13
+ # {DateTimeType} uses a {ZonedDateTime} internally.
14
+ class DateTimeType
15
+ # @!parse include Command, State
16
+
17
+ # remove the JRuby default == so that we can inherit the Ruby method
18
+ remove_method :==
19
+
20
+ extend Forwardable
21
+ include Comparable
22
+
23
+ #
24
+ # Regex expression to identify strings defining a time in hours, minutes and optionally seconds
25
+ #
26
+ TIME_ONLY_REGEX = /\A(?<hours>\d\d):(?<minutes>\d\d)(?<seconds>:\d\d)?\Z/.freeze
27
+
28
+ #
29
+ # Regex expression to identify strings defining a time in year, month, and day
30
+ #
31
+ DATE_ONLY_REGEX = /\A\d{4}-\d\d-\d\d\Z/.freeze
32
+ private_constant :TIME_ONLY_REGEX, :DATE_ONLY_REGEX
33
+
34
+ class << self
35
+ #
36
+ # Parses a String representing a time into an OpenHAB DateTimeType. First tries to parse it
37
+ # using java's DateTimeType's parser, then falls back to the Ruby Time.parse
38
+ #
39
+ # @param [String] time_string
40
+ #
41
+ # @return [DateTimeType]
42
+ #
43
+ def parse(time_string)
44
+ time_string = "#{time_string}Z" if TIME_ONLY_REGEX.match?(time_string)
45
+ DateTimeType.new(time_string)
46
+ rescue java.lang.StringIndexOutOfBoundsException, java.lang.IllegalArgumentException => e
47
+ # Try Ruby's Time.parse if OpenHAB's DateTimeType parser fails
48
+ begin
49
+ DateTimeType.new(::Time.parse(time_string))
50
+ rescue ArgumentError
51
+ raise ArgumentError, e.message
52
+ end
53
+ end
54
+ end
55
+
56
+ # @param [ZonedDateTime, nil] context
57
+ # A {ZonedDateTime} used to fill in missing
58
+ # fields during conversion. Not used in this class.
59
+ # @return [ZonedTimeTime]
60
+ def to_zoned_date_time(context = nil) # rubocop:disable Lint/UnusedMethodArgument
61
+ zoned_date_time
62
+ end
63
+
64
+ # act like a Ruby Time
65
+ def_delegator :zoned_date_time, :month_value, :month
66
+ def_delegator :zoned_date_time, :day_of_month, :mday
67
+ def_delegator :zoned_date_time, :day_of_year, :yday
68
+ def_delegator :zoned_date_time, :minute, :min
69
+ def_delegator :zoned_date_time, :second, :sec
70
+ def_delegator :zoned_date_time, :nano, :nsec
71
+ def_delegator :zoned_date_time, :to_epoch_second, :to_i
72
+ def_delegator :zoned_date_time, :to_time
73
+
74
+ alias_method :day, :mday
75
+
76
+ #
77
+ # Create a new instance of DateTimeType
78
+ #
79
+ # @param value [#to_zoned_date_time, #to_time, #to_str, #to_d, nil]
80
+ #
81
+ def initialize(value = nil)
82
+ if value.respond_to?(:to_zoned_date_time)
83
+ super(value.to_zoned_date_time)
84
+ return
85
+ elsif value.respond_to?(:to_time)
86
+ super(value.to_time.to_zoned_date_time)
87
+ return
88
+ elsif value.respond_to?(:to_str)
89
+ # strings respond_do?(:to_d), but we want to avoid that conversion
90
+ super(value.to_str)
91
+ return
92
+ elsif value.respond_to?(:to_d)
93
+ super(Time.at(value.to_d).to_zoned_date_time)
94
+ return
95
+ end
96
+
97
+ super
98
+ end
99
+
100
+ #
101
+ # Check equality without type conversion
102
+ #
103
+ # @return [true,false] if the same value is represented, without type
104
+ # conversion
105
+ def eql?(other)
106
+ return false unless other.instance_of?(self.class)
107
+
108
+ zoned_date_time.compare_to(other.zoned_date_time).zero?
109
+ end
110
+
111
+ #
112
+ # Comparison
113
+ #
114
+ # @param [Object] other object to compare to
115
+ #
116
+ # @return [Integer, nil] -1, 0, +1 depending on whether `other` is
117
+ # less than, equal to, or greater than self
118
+ #
119
+ # `nil` is returned if the two values are incomparable.
120
+ #
121
+ def <=>(other)
122
+ logger.trace("(#{self.class}) #{self} <=> #{other} (#{other.class})")
123
+ if other.is_a?(self.class)
124
+ zoned_date_time <=> other.zoned_date_time
125
+ elsif other.respond_to?(:to_time)
126
+ to_time <=> other.to_time
127
+ elsif other.respond_to?(:coerce)
128
+ return nil unless (lhs, rhs = other.coerce(self))
129
+
130
+ lhs <=> rhs
131
+ end
132
+ end
133
+
134
+ #
135
+ # Type Coercion
136
+ #
137
+ # Coerce object to a DateTimeType
138
+ #
139
+ # @param [Time] other object to coerce to a DateTimeType
140
+ #
141
+ # @return [[DateTimeType, DateTimeType], nil]
142
+ #
143
+ def coerce(other)
144
+ logger.trace("Coercing #{self} as a request from #{other.class}")
145
+ return [other, zoned_date_time] if other.respond_to?(:to_zoned_date_time)
146
+ return [DateTimeType.new(other), self] if other.respond_to?(:to_time)
147
+ end
148
+
149
+ #
150
+ # Returns the value of time as a floating point number of seconds since the Epoch
151
+ #
152
+ # @return [Float] Number of seconds since the Epoch, with nanosecond presicion
153
+ #
154
+ def to_f
155
+ zoned_date_time.to_epoch_second + (zoned_date_time.nano / 1_000_000_000)
156
+ end
157
+
158
+ #
159
+ # The offset in seconds from UTC
160
+ #
161
+ # @return [Integer] The offset from UTC, in seconds
162
+ #
163
+ def utc_offset
164
+ zoned_date_time.offset.total_seconds
165
+ end
166
+
167
+ #
168
+ # Returns true if time represents a time in UTC (GMT)
169
+ #
170
+ # @return [true,false] true if utc_offset == 0, false otherwise
171
+ #
172
+ def utc?
173
+ utc_offset.zero?
174
+ end
175
+
176
+ #
177
+ # Returns an integer representing the day of the week, 0..6, with Sunday == 0.
178
+ #
179
+ # @return [Integer] The day of week
180
+ #
181
+ def wday
182
+ zoned_date_time.day_of_week.value % 7
183
+ end
184
+
185
+ #
186
+ # The timezone
187
+ #
188
+ # @return [String] The timezone in `[+-]hh:mm(:ss)` format (`Z` for UTC)
189
+ #
190
+ def zone
191
+ zoned_date_time.zone.id
192
+ end
193
+
194
+ # @!visibility private
195
+ def respond_to_missing?(method, _include_private = false)
196
+ return true if zoned_date_time.respond_to?(method)
197
+ return true if ::Time.instance_methods.include?(method.to_sym)
198
+
199
+ super
200
+ end
201
+
202
+ #
203
+ # Forward missing methods to the `ZonedDateTime` object or a ruby `Time`
204
+ # object representing the same instant
205
+ #
206
+ def method_missing(method, *args, &block)
207
+ return zoned_date_time.send(method, *args, &block) if zoned_date_time.respond_to?(method)
208
+ return to_time.send(method, *args, &block) if ::Time.instance_methods.include?(method.to_sym)
209
+
210
+ super
211
+ end
212
+
213
+ # Add other to self
214
+ #
215
+ # @param other [Duration, Numeric]
216
+ #
217
+ # @return [DateTimeType]
218
+ def +(other)
219
+ if other.is_a?(Duration)
220
+ DateTimeType.new(zoned_date_time.plus(other))
221
+ elsif other.respond_to?(:to_d)
222
+ DateTimeType.new(zoned_date_time.plus_nanos((other.to_d * 1_000_000_000).to_i))
223
+ elsif other.respond_to?(:coerce) && (lhs, rhs = other.coerce(to_d))
224
+ lhs + rhs
225
+ else
226
+ raise TypeError, "\#{other.class} can't be coerced into \#{self.class}"
227
+ end
228
+ end
229
+
230
+ # Subtract other from self
231
+ #
232
+ # if other is a Duration-like object, the result is a new
233
+ # {DateTimeType} of duration seconds earlier in time.
234
+ #
235
+ # if other is a DateTime-like object, the result is a Duration
236
+ # representing how long between the two instants in time.
237
+ #
238
+ # @param other [Duration, Time, Numeric]
239
+ #
240
+ # @return [DateTimeType, Duration]
241
+ def -(other)
242
+ if other.is_a?(Duration)
243
+ DateTimeType.new(zoned_date_time.minus(other))
244
+ elsif other.respond_to?(:to_time)
245
+ to_time - other.to_time
246
+ elsif other.respond_to?(:to_d)
247
+ DateTimeType.new(zoned_date_time.minus_nanos((other.to_d * 1_000_000_000).to_i))
248
+ elsif other.respond_to?(:coerce) && (lhs, rhs = other.coerce(to_d))
249
+ lhs - rhs
250
+ else
251
+ raise TypeError, "\#{other.class} can't be coerced into \#{self.class}"
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ # @!parse DateTimeType = OpenHAB::Core::Types::DateTimeType