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,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "trigger"
4
+
5
+ module OpenHAB
6
+ module DSL
7
+ module Rules
8
+ module Triggers
9
+ # @!visibility private
10
+ #
11
+ # Creates channel triggers
12
+ #
13
+ class Channel < Trigger
14
+ # @return [String] A channel event trigger
15
+ CHANNEL_EVENT = "core.ChannelEventTrigger"
16
+
17
+ #
18
+ # Get an enumerator over the product of the channels and things and map them to a channel id
19
+ # @param [Object] channels to iterate over
20
+ # @param [Object] thing to combine with channels and iterate over
21
+ # @return [Enumerable] enumerable channel ids to trigger on
22
+ def self.channels(channels:, thing:)
23
+ logger.trace "Creating Channel/Thing Pairs for channels #{channels.inspect} and things #{thing.inspect}"
24
+ channels.flatten.product([thing].flatten)
25
+ .map { |channel_thing| channel_id(*channel_thing) }
26
+ end
27
+
28
+ #
29
+ # Get a channel id from a channel and thing
30
+ # @param [Object] channel part of channel id, get UID if object is a Channel
31
+ # @param [Object] thing part of channel id, get UID if object is a Thing
32
+ #
33
+ def self.channel_id(channel, thing)
34
+ channel = channel.uid if channel.is_a?(org.openhab.core.thing.Channel)
35
+ thing = thing.uid if thing.is_a?(Core::Things::Thing)
36
+ [thing, channel].compact.join(":")
37
+ end
38
+
39
+ #
40
+ # Create a trigger for a channel
41
+ #
42
+ # @param [String] channel to look for triggers
43
+ # @param [String] trigger specific channel trigger to match
44
+ # @param [Object] attach object to be attached to the trigger
45
+ #
46
+ #
47
+ def trigger(channel:, trigger:, attach:)
48
+ config = { "channelUID" => channel }
49
+ config["event"] = trigger.to_s unless trigger.nil?
50
+ logger.trace "Creating Channel Trigger for channels #{channel.inspect} and config #{config.inspect}"
51
+ append_trigger(type: CHANNEL_EVENT, config: config, attach: attach)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "trigger"
4
+
5
+ module OpenHAB
6
+ module DSL
7
+ module Rules
8
+ module Triggers
9
+ # @!visibility private
10
+ #
11
+ # Creates command triggers
12
+ #
13
+ class Command < Trigger
14
+ #
15
+ # Create a received command trigger
16
+ #
17
+ # @param [Object] item item to create trigger for
18
+ # @param [Object] command to check against
19
+ # @param [Object] attach object to be attached to the trigger
20
+ #
21
+ # @return [Trigger] OpenHAB triggers
22
+ #
23
+ def trigger(item:, command:, attach:)
24
+ case command
25
+ when Range then range_trigger(item: item, command: command, attach: attach)
26
+ when Proc then proc_trigger(item: item, command: command, attach: attach)
27
+ else command_trigger(item: item, command: command, attach: attach)
28
+ end
29
+ end
30
+
31
+ #
32
+ # Creates a trigger with a range condition on the 'command' field
33
+ # @param [Object] item to create changed trigger on
34
+ # @param [Object] command to restrict trigger to
35
+ # @param [Object] attach object to be attached to the trigger
36
+ # @return [Trigger] OpenHAB trigger
37
+ #
38
+ def range_trigger(item:, command:, attach:)
39
+ command_range, * = Conditions::Proc.range_procs(command)
40
+ proc_trigger(item: item, command: command_range, attach: attach)
41
+ end
42
+
43
+ #
44
+ # Creates a trigger with a proc condition on the 'command' field
45
+ # @param [Object] item to create changed trigger on
46
+ # @param [Object] command to restrict trigger to
47
+ # @param [Object] attach object to be attached to the trigger
48
+ # @return [Trigger] OpenHAB trigger
49
+ #
50
+ def proc_trigger(item:, command:, attach:)
51
+ conditions = Conditions::Proc.new(command: command)
52
+ command_trigger(item: item, command: nil, attach: attach, conditions: conditions)
53
+ end
54
+
55
+ #
56
+ # Create a received trigger based on item type
57
+ #
58
+ # @param [Object] item to create trigger for
59
+ # @param [String] command to create trigger for
60
+ # @param [Object] attach object to be attached to the trigger
61
+ #
62
+ def command_trigger(item:, command:, attach: nil, conditions: nil)
63
+ type, config = if item.is_a?(GroupItem::Members)
64
+ group(group: item)
65
+ else
66
+ item(item: item)
67
+ end
68
+ config["command"] = command.to_s unless command.nil?
69
+ append_trigger(type: type, config: config, attach: attach, conditions: conditions)
70
+ end
71
+
72
+ private
73
+
74
+ # @return [String] item command trigger
75
+ ITEM_COMMAND = "core.ItemCommandTrigger"
76
+
77
+ # @return [String] A group command trigger for items in the group
78
+ GROUP_COMMAND = "core.GroupCommandTrigger"
79
+
80
+ #
81
+ # Create trigger for item commands
82
+ #
83
+ # @param [Item] item to create trigger for
84
+ #
85
+ # @return [Array<Hash,Trigger>] first element is hash of trigger config properties
86
+ # second element is trigger type
87
+ #
88
+ def item(item:)
89
+ [ITEM_COMMAND, { "itemName" => item.name }]
90
+ end
91
+
92
+ #
93
+ # Create trigger for group items
94
+ #
95
+ # @param [Group] group to create trigger for
96
+ #
97
+ # @return [Array<Hash,Trigger>] first element is hash of trigger config properties
98
+ # second element is trigger type
99
+ #
100
+ def group(group:)
101
+ [GROUP_COMMAND, { "groupName" => group.group.name }]
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module DSL
5
+ module Rules
6
+ module Triggers
7
+ # @!visibility private
8
+ module Conditions
9
+ #
10
+ # this is a no-op condition which simply executes the provided block
11
+ #
12
+
13
+ #
14
+ # Struct capturing data necessary for a conditional trigger
15
+ #
16
+ # TriggerDelay = Struct.new(:to, :from, :duration, :timer, :tracking_to, keyword_init: true) do
17
+ # def timer_active?
18
+ # timer&.active?
19
+ # end
20
+ # end
21
+ class Duration
22
+ #
23
+ # Create a new duration condition
24
+ # @param [Object] to optional condition on to state
25
+ # @param [Object] from optional condition on from state
26
+ # @param [java.time.temporal.TemporalAmount] duration to state must stay at specific value before triggering
27
+ #
28
+ def initialize(to:, from:, duration:)
29
+ to = Conditions::Proc.from_value(to)
30
+ from = Conditions::Proc.from_value(from)
31
+ @conditions = Conditions::Proc.new(to: to, from: from)
32
+ @duration = duration
33
+ @timer = nil
34
+ logger.trace "Created Duration Condition To(#{to}) From(#{from}) "\
35
+ "Conditions(#{@conditions}) Duration(#{@duration})"
36
+ end
37
+
38
+ # Process rule
39
+ # @param [Hash] inputs inputs from trigger
40
+ #
41
+ def process(mod:, inputs:, &block)
42
+ process_trigger_delay(mod, inputs, &block)
43
+ end
44
+
45
+ # Cleanup any resources from the condition
46
+ #
47
+ # Cancels the timer, if it's active
48
+ def cleanup
49
+ @timer&.cancel
50
+ end
51
+
52
+ private
53
+
54
+ #
55
+ # Checks if there is an active timer
56
+ # @return [true, false] true if the timer exists and is active, false otherwise
57
+ def timer_active?
58
+ @timer&.active?
59
+ end
60
+
61
+ #
62
+ # Check if trigger guards prevent rule execution
63
+ #
64
+ # @param [Map] inputs OpenHAB map object describing rule trigger
65
+ #
66
+ # @return [true,false] True if the rule should execute, false if trigger guard prevents execution
67
+ #
68
+ def check_trigger_guards(inputs)
69
+ new_state, old_state = retrieve_states(inputs)
70
+ @conditions.check_from(state: old_state) && @conditions.check_to(state: new_state)
71
+ end
72
+
73
+ #
74
+ # Rerieve the newState and oldState, alternatively newStatus and oldStatus
75
+ # from the input map
76
+ #
77
+ # @param [Map] inputs OpenHAB map object describing rule trigger
78
+ #
79
+ # @return [Array] An array of the values for [newState, oldState] or [newStatus, oldStatus]
80
+ #
81
+ def retrieve_states(inputs)
82
+ new_state = inputs["newState"] || thing_status_to_sym(inputs["newStatus"])
83
+ old_state = inputs["oldState"] || thing_status_to_sym(inputs["oldStatus"])
84
+
85
+ [new_state, old_state]
86
+ end
87
+
88
+ #
89
+ # Converts a ThingStatus object to a ruby Symbol
90
+ #
91
+ # @param [org.openhab.core.thing.ThingStatus] status A ThingStatus instance
92
+ #
93
+ # @return [Symbol] A corresponding symbol, in lower case
94
+ #
95
+ def thing_status_to_sym(status)
96
+ status&.to_s&.downcase&.to_sym
97
+ end
98
+
99
+ #
100
+ # Process any matching trigger delays
101
+ #
102
+ # @param [Map] mod OpenHAB map object describing rule trigger
103
+ # @param [Map] inputs OpenHAB map object describing rule trigger
104
+ #
105
+ #
106
+ def process_trigger_delay(mod, inputs, &block)
107
+ if timer_active?
108
+ process_active_timer(inputs, mod, &block)
109
+ elsif check_trigger_guards(inputs)
110
+ logger.trace("Trigger Guards Matched for #{self}, delaying rule execution")
111
+ # Add timer and attach timer to delay object, and also state being tracked to so
112
+ # timer can be cancelled if state changes
113
+ # Also another timer should not be created if changed to same value again but instead rescheduled
114
+ create_trigger_delay_timer(inputs, mod, &block)
115
+ else
116
+ logger.trace("Trigger Guards did not match for #{self}, ignoring trigger.")
117
+ end
118
+ end
119
+
120
+ #
121
+ # Creates a timer for trigger delays
122
+ #
123
+ # @param [Hash] inputs rule trigger inputs
124
+ # @param [Hash] _mod rule trigger mods
125
+ #
126
+ #
127
+ def create_trigger_delay_timer(inputs, _mod)
128
+ logger.trace("Creating timer for trigger delay #{self}")
129
+ @timer = DSL.after(@duration) do
130
+ logger.trace("Delay Complete for #{self}, executing rule")
131
+ @timer = nil
132
+ yield
133
+ end
134
+ @tracking_to, = retrieve_states(inputs)
135
+ end
136
+
137
+ #
138
+ # Process an active trigger timer
139
+ #
140
+ # @param [Hash] inputs rule trigger inputs
141
+ # @param [Hash] mod rule trigger mods
142
+ #
143
+ #
144
+ def process_active_timer(inputs, mod, &block)
145
+ state, = retrieve_states(inputs)
146
+ if state == @tracking_to
147
+ logger.trace("Item changed to #{state} for #{self}, rescheduling timer.")
148
+ @timer.reschedule(ZonedDateTime.now.plus(@duration))
149
+ else
150
+ logger.trace("Item changed to #{state} for #{self}, canceling timer.")
151
+ @timer.cancel
152
+ # Reprocess trigger delay after canceling to track new state (if guards matched, etc)
153
+ process_trigger_delay(mod, inputs, &block)
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module DSL
5
+ module Rules
6
+ module Triggers
7
+ # @!visibility private
8
+ module Conditions
9
+ #
10
+ # This creates trigger conditions that work on procs
11
+ # @param [Proc] from Proc
12
+ # @param [Proc] to Proc
13
+ #
14
+ class Proc
15
+ #
16
+ # Converts supplied ranges to procs that check range
17
+ # @param [Array] ranges objects to convert to range proc if they are ranges
18
+ # @return [Array] of procs or supplied arguments if argument was not of type Range
19
+ #
20
+ def self.range_procs(*ranges)
21
+ ranges.map { |range| range.is_a?(Range) ? range_proc(range) : range }
22
+ end
23
+
24
+ #
25
+ # Create a range proc for the supplied range object
26
+ # @param [Range] range to build proc for
27
+ #
28
+ def self.range_proc(range)
29
+ logger.trace("Creating range proc for #{range}")
30
+ lambda do |val|
31
+ logger.trace("Range proc checking if #{val} is in #{range}")
32
+ range.cover? val
33
+ end
34
+ end
35
+
36
+ #
37
+ # Create an equality proc for the supplied range object
38
+ # @param [State] value to build proc for
39
+ #
40
+ def self.equality_proc(value)
41
+ logger.trace("Creating equality proc for #{value}")
42
+ lambda do |state|
43
+ logger.trace("Equality proc comparing #{value} against #{state}")
44
+ value == state
45
+ end
46
+ end
47
+
48
+ #
49
+ # Constructs a proc for the specific value type
50
+ # if the value is a proc return the proc
51
+ # if the value is a range create a range proc
52
+ # if the value is nil, return nil
53
+ # otherwise create an equality proc
54
+ # @param [Object] value to construct proc from
55
+ def self.from_value(value)
56
+ logger.trace("Creating proc for Value(#{value})")
57
+ return value if value.nil?
58
+ return value if value.is_a?(::Proc)
59
+ return range_proc(value) if value.is_a?(Range)
60
+
61
+ equality_proc(value)
62
+ end
63
+
64
+ #
65
+ # Create a new Proc Condition that executes only if procs return true
66
+ # @param [Proc] from Proc to check against from value
67
+ # @param [Proc] to Proc to check against to value
68
+ #
69
+ def initialize(from: nil, to: nil, command: nil)
70
+ @from = from
71
+ @to = to
72
+ @command = command
73
+ end
74
+
75
+ # Proc that doesn't check any fields
76
+ ANY = Proc.new.freeze # this needs to be defined _after_ initialize so its instance variables are set
77
+
78
+ #
79
+ # Process rule
80
+ # @param [Hash] inputs inputs from trigger
81
+ #
82
+ def process(mod:, inputs:) # rubocop:disable Lint/UnusedMethodArgument - mod is unused here but required
83
+ logger.trace("Checking #{inputs} against condition trigger #{self}")
84
+ yield if check_procs(inputs: inputs)
85
+ end
86
+
87
+ # Cleanup any resources from the condition
88
+ def cleanup; end
89
+
90
+ #
91
+ # Check if command condition match the proc
92
+ # @param [Hash] inputs from trigger must be supplied if state is not supplied
93
+ # @return [true/false] depending on if from is set and matches supplied conditions
94
+ #
95
+ def check_command(inputs: nil)
96
+ command = input_state(inputs, "command")
97
+ logger.trace "Checking command(#{@command}) against command(#{command})"
98
+ check_proc(proc: @command, value: command)
99
+ end
100
+
101
+ #
102
+ # Check if from condition match the proc
103
+ # @param [Hash] inputs from trigger must be supplied if state is not supplied
104
+ # @param [String] state if supplied proc will be passed state value for comparision
105
+ # @return [true/false] depending on if from is set and matches supplied conditions
106
+ #
107
+ def check_from(inputs: nil, state: nil)
108
+ state ||= input_state(inputs, "oldState")
109
+ logger.trace "Checking from(#{@from}) against state(#{state})"
110
+ check_proc(proc: @from, value: state)
111
+ end
112
+
113
+ #
114
+ # Check if to conditions match the proc
115
+ # @param [Hash] inputs from trigger must be supplied if state is not supplied
116
+ # @param [String] state if supplied proc will be passed state value for comparision
117
+ # @return [true/false] depending on if from is set and matches supplied conditions
118
+ #
119
+ def check_to(inputs: nil, state: nil)
120
+ state ||= input_state(inputs, "newState", "state")
121
+ logger.trace "Checking to(#{@to}) against state(#{state})"
122
+ check_proc(proc: @to, value: state)
123
+ end
124
+
125
+ # Describe the Proc Condition as a string
126
+ # @return [String] string representation of proc condition
127
+ #
128
+ def to_s
129
+ "From:(#{@from}) To:(#{@to}) Command:(#{@command})"
130
+ end
131
+
132
+ private
133
+
134
+ #
135
+ # Check all procs
136
+ # @param [Hash] inputs from event
137
+ # @return [true/false] true if all procs return true, false otherwise
138
+ def check_procs(inputs:)
139
+ check_from(inputs: inputs) && check_to(inputs: inputs) && check_command(inputs: inputs)
140
+ end
141
+
142
+ # Check if a field matches the proc condition
143
+ # @param [Proc] proc to call
144
+ # @param [Hash] value to check
145
+ # @return [true,false] true if proc is nil or proc.call returns true, false otherwise
146
+ def check_proc(proc:, value:)
147
+ return true if proc.nil? || proc.call(value)
148
+
149
+ logger.trace("Skipped execution of rule because value #{value} evaluated false for (#{proc})")
150
+ false
151
+ end
152
+
153
+ # Get the first field from supplied fields in inputs
154
+ # @param [Hash] inputs containing fields
155
+ # @param [Array] fields array of fields to extract from inputs, first one found is returned
156
+ def input_state(inputs, *fields)
157
+ fields.map { |f| inputs[f] }.compact.first
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openhab/dsl/rules/triggers/trigger"
4
+
5
+ module OpenHAB
6
+ module DSL
7
+ module Rules
8
+ module Triggers
9
+ # @!visibility private
10
+ #
11
+ # Creates cron triggers
12
+ #
13
+ class Cron < Trigger
14
+ # Trigger ID for Watch Triggers
15
+ CRON_TRIGGER_MODULE_ID = "jsr223.jruby.CronTrigger"
16
+
17
+ #
18
+ # Returns a default map for cron expressions that fires every second
19
+ # This map is usually updated via merge by other methods to refine cron type triggers.
20
+ #
21
+ # @return [Hash] Map with symbols for :seconds, :minute, :hour, :dom, :month, :dow
22
+ # configured to fire every second
23
+ #
24
+ CRON_EXPRESSION_MAP =
25
+ {
26
+ second: "*",
27
+ minute: "*",
28
+ hour: "*",
29
+ dom: "?",
30
+ month: "*",
31
+ dow: "?",
32
+ year: "*"
33
+ }.freeze
34
+ private_constant :CRON_EXPRESSION_MAP
35
+
36
+ # @return [Hash] Map of days of the week from symbols to to OpenHAB cron strings
37
+ DAY_OF_WEEK_MAP = {
38
+ monday: "MON",
39
+ tuesday: "TUE",
40
+ wednesday: "WED",
41
+ thursday: "THU",
42
+ friday: "FRI",
43
+ saturday: "SAT",
44
+ sunday: "SUN"
45
+ }.freeze
46
+ private_constant :DAY_OF_WEEK_MAP
47
+
48
+ # @return [Hash] Converts the DAY_OF_WEEK_MAP to map used by Cron Expression
49
+ DAY_OF_WEEK_EXPRESSION_MAP = DAY_OF_WEEK_MAP.transform_values { |v| CRON_EXPRESSION_MAP.merge(dow: v) }
50
+
51
+ private_constant :DAY_OF_WEEK_EXPRESSION_MAP
52
+
53
+ # @return [Hash] Create a set of cron expressions based on different time intervals
54
+ EXPRESSION_MAP = {
55
+ second: CRON_EXPRESSION_MAP,
56
+ minute: CRON_EXPRESSION_MAP.merge(second: "0"),
57
+ hour: CRON_EXPRESSION_MAP.merge(second: "0", minute: "0"),
58
+ day: CRON_EXPRESSION_MAP.merge(second: "0", minute: "0", hour: "0"),
59
+ week: CRON_EXPRESSION_MAP.merge(second: "0", minute: "0", hour: "0", dow: "MON"),
60
+ month: CRON_EXPRESSION_MAP.merge(second: "0", minute: "0", hour: "0", dom: "1"),
61
+ year: CRON_EXPRESSION_MAP.merge(second: "0", minute: "0", hour: "0", dom: "1", month: "1")
62
+ }.merge(DAY_OF_WEEK_EXPRESSION_MAP).freeze
63
+
64
+ private_constant :EXPRESSION_MAP
65
+
66
+ #
67
+ # Create a cron map from a duration
68
+ #
69
+ # @param [Duration] duration
70
+ # @param [Object] at LocalTime or String representing time of day
71
+ #
72
+ # @return [Hash] map describing cron expression
73
+ #
74
+ def self.from_duration(duration, at)
75
+ raise ArgumentError, '"at" cannot be used with duration' if at
76
+
77
+ map_to_cron(duration_to_map(duration))
78
+ end
79
+
80
+ #
81
+ # Create a cron map from a MonthDay
82
+ #
83
+ # @param [MonthDay] monthday a {MonthDay} object
84
+ # @param [Object] at LocalTime or String representing time of day
85
+ #
86
+ # @return [Hash] map describing cron expression
87
+ #
88
+ def self.from_monthday(monthday, at)
89
+ expression_map = EXPRESSION_MAP[:day].merge(month: monthday.month_value, dom: monthday.day_of_month)
90
+ expression_map = at_condition(expression_map, at) if at
91
+ map_to_cron(expression_map)
92
+ end
93
+
94
+ #
95
+ # Create a cron map from a symbol
96
+ #
97
+ # @param [Symbol] symbol
98
+ # @param [Object] at LocalTime or String representing time of day
99
+ #
100
+ # @return [Hash] map describing cron expression created from symbol
101
+ #
102
+ def self.from_symbol(symbol, at)
103
+ expression_map = EXPRESSION_MAP[symbol]
104
+ expression_map = at_condition(expression_map, at) if at
105
+ map_to_cron(expression_map)
106
+ end
107
+
108
+ #
109
+ # Create a cron map from cron elements
110
+ #
111
+ # @param [Hash] fields Cron fields (second, minute, hour, dom, month, dow, year)
112
+ #
113
+ # @return [Hash] map describing cron expression
114
+ #
115
+ def self.from_fields(fields)
116
+ extra_fields = fields.keys - CRON_EXPRESSION_MAP.keys
117
+ unless extra_fields.empty?
118
+ raise ArgumentError,
119
+ "unknown keyword#{"s" if extra_fields.size > 1}: #{extra_fields.map(&:inspect).join(", ")}"
120
+ end
121
+
122
+ fields = fields.transform_values { |value| value.to_s.delete(" ") }
123
+ # find the first expression map that has a field from fields.
124
+ # this ensure more-specific fields get set to 0, not *
125
+ base_key = EXPRESSION_MAP.keys.find { |field, _| fields.key?(field) }
126
+ base_expression = EXPRESSION_MAP[base_key]
127
+ expression_map = base_expression.merge(fields)
128
+
129
+ map_to_cron(expression_map)
130
+ end
131
+
132
+ #
133
+ # Map cron expression to to cron string
134
+ #
135
+ # @param [Map] map of cron expression
136
+ #
137
+ # @return [String] OpenHAB cron string
138
+ #
139
+ def self.map_to_cron(map)
140
+ %i[second minute hour dom month dow year].map { |field| map.fetch(field) }.join(" ")
141
+ end
142
+
143
+ #
144
+ # Convert a Java Duration to a map for the map_to_cron method
145
+ #
146
+ # @param duration [Duration] The duration object
147
+ #
148
+ # @return [Hash] a map suitable for map_to_cron
149
+ #
150
+ def self.duration_to_map(duration)
151
+ if duration.to_millis_part.zero? && duration.to_nanos_part.zero? && duration.to_days.zero?
152
+ %i[second minute hour].each do |unit|
153
+ to_unit_part = duration.public_send("to_#{unit}s_part")
154
+ next unless to_unit_part.positive?
155
+
156
+ to_unit = duration.public_send("to_#{unit}s")
157
+ break unless to_unit_part == to_unit
158
+
159
+ return EXPRESSION_MAP[unit].merge(unit => "*/#{to_unit}")
160
+ end
161
+ end
162
+ raise ArgumentError, "Cron Expression not supported for duration: #{duration}"
163
+ end
164
+
165
+ #
166
+ # If an at time is provided, parse that and merge the new fields into the expression.
167
+ #
168
+ # @param [<Type>] expression_map <description>
169
+ # @param [<Type>] at_time <description>
170
+ #
171
+ # @return [<Type>] <description>
172
+ #
173
+ def self.at_condition(expression_map, at_time)
174
+ if at_time
175
+ tod = at_time.is_a?(LocalTime) ? at_time : LocalTime.parse(at_time)
176
+ expression_map = expression_map.merge(hour: tod.hour, minute: tod.minute, second: tod.second)
177
+ end
178
+ expression_map
179
+ end
180
+
181
+ #
182
+ # Create a cron trigger based on item type
183
+ #
184
+ # @param [Config] config Rule configuration
185
+ # @param [Object] attach object to be attached to the trigger
186
+ #
187
+ #
188
+ def trigger(config:, attach:)
189
+ append_trigger(type: CRON_TRIGGER_MODULE_ID, config: config, attach: attach)
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end