openhab-jrubyscripting 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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