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,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "property"
4
+
5
+ module OpenHAB
6
+ module DSL
7
+ module Rules
8
+ #
9
+ # Guard that can prevent execution of a rule if not satisfied
10
+ #
11
+ # @!visibility private
12
+ class Guard
13
+ #
14
+ # Create a new Guard
15
+ #
16
+ # @param [Object] only_if Item or Proc to use as guard
17
+ # @param [Object] not_if Item or Proc to use as guard
18
+ #
19
+ def initialize(run_context:, only_if: nil, not_if: nil)
20
+ @run_context = run_context
21
+ @only_if = only_if
22
+ @not_if = not_if
23
+ end
24
+
25
+ #
26
+ # Convert the guard into a string
27
+ #
28
+ # @return [String] describing the only_of and not_if guards
29
+ #
30
+ def to_s
31
+ "only_if: #{@only_if}, not_if: #{@not_if}"
32
+ end
33
+
34
+ #
35
+ # Checks if a guard should run
36
+ #
37
+ # @param [OpenHAB Trigger Event] event OpenHAB Trigger Event
38
+ #
39
+ # @return [true,false] True if guard is satisfied, false otherwise
40
+ #
41
+ def should_run?(event)
42
+ logger.trace("Checking guards #{self}")
43
+ check(@only_if, check_type: :only_if,
44
+ event: event) && check(@not_if, check_type: :not_if, event: event)
45
+ end
46
+
47
+ private
48
+
49
+ #
50
+ # Check if guard is satisfied
51
+ #
52
+ # @param [Array] conditions to check
53
+ # @param [Symbol] check_type type of check to perform (:only_if or :not_if)
54
+ # @param [Event] event OpenHAB event to see if it satisfies the guard
55
+ #
56
+ # @return [true,false] True if guard is satisfied, false otherwise
57
+ #
58
+ def check(conditions, check_type:, event:)
59
+ return true if conditions.nil? || conditions.empty?
60
+
61
+ procs, items = conditions.flatten.partition { |condition| condition.is_a?(Proc) }
62
+ logger.trace("Procs: #{procs} Items: #{items}")
63
+
64
+ items.each { |item| logger.trace { "#{item} truthy? #{item.truthy?}" } }
65
+
66
+ process_check(check_type: check_type, event: event, items: items, procs: procs)
67
+ end
68
+
69
+ #
70
+ # Execute the guard check
71
+ #
72
+ # @param [Symbol] check_type :only_if or :not_if to check
73
+ # @param [OpenHAB Event] event event to check if meets guard
74
+ # @param [Array<Item>] items to check if satisfy criteria
75
+ # @param [Array] procs to check if satisfy criteria
76
+ #
77
+ # @return [true,false] True if criteria are satisfied, false otherwise
78
+ #
79
+ def process_check(check_type:, event:, items:, procs:)
80
+ case check_type
81
+ when :only_if then process_only_if(event, items, procs)
82
+ when :not_if then process_not_if(event, items, procs)
83
+ else raise ArgumentError, "Unexpected check type: #{check_type}"
84
+ end
85
+ end
86
+
87
+ #
88
+ # Check not_if guard
89
+ #
90
+ # @param [OpenHAB Event] event to check if meets guard
91
+ # @param [Array<Item>] items to check if satisfy criteria
92
+ # @param [Array] procs to check if satisfy criteria
93
+ #
94
+ # @return [true,false] True if criteria are satisfied, false otherwise
95
+ #
96
+ def process_not_if(event, items, procs)
97
+ items.flatten.none?(&:truthy?) && procs.none? { |proc| @run_context.instance_exec(event, &proc) }
98
+ end
99
+
100
+ #
101
+ # Check only_if guard
102
+ #
103
+ # @param [OpenHAB Event] event to check if meets guard
104
+ # @param [Array<Item>] items to check if satisfy criteria
105
+ # @param [Array] procs to check if satisfy criteria
106
+ #
107
+ # @return [true,false] True if criteria are satisfied, false otherwise
108
+ #
109
+ def process_only_if(event, items, procs)
110
+ items.flatten.all?(&:truthy?) && procs.all? { |proc| @run_context.instance_exec(event, &proc) }
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "triggers/cron/cron"
4
+
5
+ module OpenHAB
6
+ module DSL
7
+ module Rules
8
+ # Contains helper methods for inferring a rule name from its triggers
9
+ # @!visibility private
10
+ module NameInference
11
+ # Trigger Type UIDs that we know how to generate a name for
12
+ KNOWN_TRIGGER_TYPES = [
13
+ "core.ChannelEventTrigger",
14
+ "core.GenericEventTrigger",
15
+ "core.GroupCommandTrigger",
16
+ "core.GroupStateChangeTrigger",
17
+ "core.GroupStateUpdateTrigger",
18
+ "core.ItemCommandTrigger",
19
+ "core.ItemStateChangeTrigger",
20
+ "core.ItemStateUpdateTrigger",
21
+ Triggers::Cron::CRON_TRIGGER_MODULE_ID
22
+ ].freeze
23
+ private_constant :KNOWN_TRIGGER_TYPES
24
+
25
+ class << self
26
+ # takes a freeform rule name and makes it more palatable as an id
27
+ def infer_rule_id_from_name(name)
28
+ name.downcase.delete("'\"").gsub(/\W+/, "_")
29
+ end
30
+
31
+ # get the block's source location, and simplify to a simple filename
32
+ def infer_rule_id_from_block(block)
33
+ file = File.basename(block.source_location.first)
34
+ "#{file}:#{block.source_location.last}"
35
+ end
36
+
37
+ # formulate a readable rule name such as "TestSwitch received command ON" if possible
38
+ def infer_rule_name(config)
39
+ known_triggers, unknown_triggers = config.triggers.partition do |t|
40
+ KNOWN_TRIGGER_TYPES.include?(t.type_uid)
41
+ end
42
+ return nil unless unknown_triggers.empty?
43
+
44
+ cron_triggers = known_triggers.select { |t| t.type_uid == "jsr223.jruby.CronTrigger" }
45
+ ruby_every_triggers = config.ruby_triggers.select { |t| t.first == :every }
46
+
47
+ # makes sure there aren't any true cron triggers cause we can't format them
48
+ return nil unless cron_triggers.length == ruby_every_triggers.length
49
+ return nil unless config.ruby_triggers.length == 1
50
+
51
+ infer_rule_name_from_trigger(*config.ruby_triggers.first)
52
+ end
53
+
54
+ private
55
+
56
+ # formulate a readable rule name from a single trigger if possible
57
+ def infer_rule_name_from_trigger(trigger, items = nil, kwargs = {})
58
+ case trigger
59
+ when :every
60
+ infer_rule_name_from_every_trigger(items, **kwargs)
61
+ when :channel
62
+ infer_rule_name_from_channel_trigger(items, **kwargs)
63
+ when :changed, :updated, :received_command
64
+ infer_rule_name_from_item_trigger(trigger, items, kwargs)
65
+ when :channel_linked, :channel_unlinked
66
+ infer_rule_name_from_channel_link_trigger(trigger)
67
+ when :thing_added, :thing_removed, :thing_updated
68
+ infer_rule_name_from_thing_trigger(trigger)
69
+ end
70
+ end
71
+
72
+ # formulate a readable rule name from an item-type trigger
73
+ def infer_rule_name_from_item_trigger(trigger, items, kwargs)
74
+ kwargs.delete(:command) if kwargs[:command] == [nil]
75
+ return unless items.length <= 3 &&
76
+ (kwargs.keys - %i[from to command duration]).empty?
77
+ return if kwargs.values_at(:from, :to, :command).compact.any? do |v|
78
+ next false if v.is_a?(Array) && v.length <= 4 # arbitrary length
79
+ next false if v.is_a?(Range)
80
+
81
+ v.is_a?(Proc) || v.is_a?(Enumerable)
82
+ end
83
+
84
+ trigger_name = trigger.to_s.tr("_", " ")
85
+ item_names = items.map do |item|
86
+ if item.is_a?(GroupItem::Members)
87
+ "#{item.group.name}.members"
88
+ else
89
+ item.name
90
+ end
91
+ end
92
+ name = "#{format_beginning_of_sentence_array(item_names)} #{trigger_name}"
93
+
94
+ name += " from #{format_inspected_array(kwargs[:from])}" if kwargs[:from]
95
+ name += " to #{format_inspected_array(kwargs[:to])}" if kwargs[:to]
96
+ name += " #{format_inspected_array(kwargs[:command])}" if kwargs[:command]
97
+ name += " for #{kwargs[:duration]}" if kwargs[:duration]
98
+ name.freeze
99
+ end
100
+
101
+ # formulate a readable rule name from an every-style cron trigger
102
+ def infer_rule_name_from_every_trigger(value, at:)
103
+ name = "Every #{value}"
104
+ name += " at #{at}" if at
105
+ name
106
+ end
107
+
108
+ # formulate a readable rule name from a channel trigger
109
+ def infer_rule_name_from_channel_trigger(channels, triggers:)
110
+ triggers = [] if triggers == [nil]
111
+ name = "#{format_beginning_of_sentence_array(channels)} triggered"
112
+ name += " #{format_inspected_array(triggers)}" unless triggers.empty?
113
+ name
114
+ end
115
+
116
+ # formulate a readable rule name from a channel link trigger
117
+ def infer_rule_name_from_channel_link_trigger(trigger)
118
+ trigger == :channel_linked ? "Channel linked to item" : "Channel unlinked from item"
119
+ end
120
+
121
+ # formulate a readable rule name from a thing added/updated/remove trigger
122
+ def infer_rule_name_from_thing_trigger(trigger)
123
+ {
124
+ thing_added: "Thing Added",
125
+ thing_updated: "Thing updated",
126
+ thing_removed: "Thing removed"
127
+ }[trigger]
128
+ end
129
+
130
+ # format an array of words that will be the beginning of a sentence
131
+ def format_beginning_of_sentence_array(array)
132
+ result = format_array(array)
133
+ if array.length > 2
134
+ result = result.dup
135
+ result[0] = "A"
136
+ result.freeze
137
+ end
138
+ result
139
+ end
140
+
141
+ # format an array of items that need to be inspected individually
142
+ def format_inspected_array(array)
143
+ return array.inspect if array.is_a?(Range)
144
+
145
+ array = [array] unless array.is_a?(Array)
146
+ format_array(array.map(&:inspect))
147
+ end
148
+
149
+ # format an array of words in a friendly way
150
+ def format_array(array)
151
+ return array[0] if array.length == 1
152
+ return "#{array[0]} or #{array[1]}" if array.length == 2
153
+
154
+ "any of #{array.join(", ")}"
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module DSL
5
+ module Rules
6
+ #
7
+ # Provides methods to support DSL properties
8
+ #
9
+ # @visibility private
10
+ module Property
11
+ #
12
+ # Dynamically creates a property that acts and an accessor with no arguments
13
+ # and a setter with any number of arguments or a block.
14
+ #
15
+ # @param [String] name of the property
16
+ #
17
+ #
18
+ def prop(name)
19
+ # rubocop rules are disabled because this method is dynamically defined on the calling
20
+ # object making calls to other methods in this module impossible, or if done on methods
21
+ # in this module than instance variable belong to the module not the calling class
22
+ define_method(name) do |*args, &block|
23
+ if args.length.zero? && block.nil? == true
24
+ instance_variable_get("@#{name}")
25
+ else
26
+ logger.trace("Property '#{name}' called with args(#{args}) and block(#{block})")
27
+ if args.length == 1
28
+ instance_variable_set("@#{name}", args.first)
29
+ elsif args.length > 1
30
+ instance_variable_set("@#{name}", args)
31
+ elsif block
32
+ instance_variable_set("@#{name}", block)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ #
39
+ # Dynamically creates a property array acts and an accessor with no arguments
40
+ # and a pushes any number of arguments or a block onto they property array
41
+ # You can provide a block to this method which can be used to check if the provided value is acceptable.
42
+ #
43
+ # @param [String] name of the property
44
+ # @param [String] array_name name of the array to use, defaults to name of property
45
+ # @param [Class] wrapper object to put around elements added to the array
46
+ #
47
+ def prop_array(name, array_name: nil, wrapper: nil)
48
+ define_method(name) do |*args, &block|
49
+ array_name ||= name
50
+ if args.length.zero? && block.nil? == true
51
+ instance_variable_get("@#{array_name}")
52
+ else
53
+ logger.trace("Property '#{name}' called with args(#{args}) and block(#{block})")
54
+ if args.length == 1
55
+ insert = args.first
56
+ elsif args.length > 1
57
+ insert = args
58
+ elsif block
59
+ insert = block
60
+ end
61
+ yield insert if block_given?
62
+ insert = wrapper.new(insert) if wrapper
63
+ instance_variable_set("@#{array_name}", (instance_variable_get("@#{array_name}") || []) << insert)
64
+ end
65
+ end
66
+
67
+ return unless array_name
68
+
69
+ define_method(array_name) do
70
+ instance_variable_get("@#{array_name}")
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ require "securerandom"
6
+
7
+ require_relative "triggers/conditions/proc"
8
+
9
+ module OpenHAB
10
+ module DSL
11
+ module Rules
12
+ #
13
+ # Rule configuration for OpenHAB Rules engine
14
+ #
15
+ # @!visibility private
16
+ class RuleTriggers
17
+ # @return [Array] Of triggers
18
+ attr_accessor :triggers
19
+
20
+ # @return [Hash] Of trigger conditions
21
+ attr_reader :trigger_conditions
22
+
23
+ # @return [Hash] Hash of trigger UIDs to attachments
24
+ attr_reader :attachments
25
+
26
+ #
27
+ # Create a new RuleTrigger
28
+ #
29
+ def initialize
30
+ @triggers = []
31
+ @trigger_conditions = Hash.new(Triggers::Conditions::Proc::ANY)
32
+ @attachments = {}
33
+ end
34
+
35
+ #
36
+ # Append a trigger to the list of triggers
37
+ #
38
+ # @param [String] type of trigger to create
39
+ # @param [Map] config map describing trigger configuration
40
+ # @param [Object] attach object to be attached to the trigger
41
+ #
42
+ # @return [Trigger] OpenHAB trigger
43
+ #
44
+ def append_trigger(type:, config:, attach: nil, conditions: nil)
45
+ config.transform_keys!(&:to_s)
46
+ RuleTriggers.trigger(type: type, config: config).tap do |trigger|
47
+ logger.trace("Appending trigger (#{trigger}) attach (#{attach}) conditions(#{conditions})")
48
+ @triggers << trigger
49
+ @attachments[trigger.id] = attach if attach
50
+ @trigger_conditions[trigger.id] = conditions if conditions
51
+ end
52
+ end
53
+
54
+ #
55
+ # Create a trigger
56
+ #
57
+ # @param [String] type of trigger
58
+ # @param [Map] config map
59
+ #
60
+ # @return [OpenHAB Trigger] configured by type and supplied config
61
+ #
62
+ def self.trigger(type:, config:)
63
+ logger.trace("Creating trigger of type '#{type}' config: #{config}")
64
+ org.openhab.core.automation.util.TriggerBuilder.create
65
+ .with_id(uuid)
66
+ .with_type_uid(type)
67
+ .with_configuration(org.openhab.core.config.core.Configuration.new(config))
68
+ .build
69
+ end
70
+
71
+ #
72
+ # Generate a UUID for triggers
73
+ #
74
+ # @return [String] UUID
75
+ #
76
+ def self.uuid
77
+ SecureRandom.uuid
78
+ end
79
+
80
+ #
81
+ # Inspect the config object
82
+ #
83
+ # @return [String] details of the config object
84
+ #
85
+ def inspect
86
+ <<~TEXT.tr("\n", " ")
87
+ #<RuleTriggers #{triggers.inspect}
88
+ Conditions: #{trigger_conditions.inspect}
89
+ UIDs: #{triggers.map(&:id).inspect}
90
+ Attachments: #{attachments.inspect}>
91
+ TEXT
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module DSL
5
+ module Rules
6
+ # If you have a single trigger and execution block, you can use a terse rule:
7
+ # All parameters to the trigger are passed through, and an optional `name:` parameter is added.
8
+ #
9
+ # @example
10
+ # changed TestSwitch do |event|
11
+ # logger.info("TestSwitch changed to #{event.state}")
12
+ # end
13
+ #
14
+ # @example
15
+ # received_command TestSwitch, name: "My Test Switch Rule", command: ON do
16
+ # logger.info("TestSwitch received command ON")
17
+ # end
18
+ #
19
+ module Terse
20
+ class << self
21
+ # @!visibility private
22
+ # @!macro def_terse_rule
23
+ # @!method $1(*args, name :nil, id: nil, **kwargs, &block)
24
+ # Create a new rule with a $1 trigger.
25
+ # @param name [String] The name for the rule.
26
+ # @param id [String] The ID for the rule.
27
+ # @yield The execution block for the rule.
28
+ # @return [void]
29
+ # @see Builder#$1
30
+ def def_terse_rule(trigger)
31
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
32
+ def #{trigger}(*args, name: nil, id: nil, **kwargs, &block) # def changed(*args, name: nil, id: nil, **kwargs, &block)
33
+ raise ArgumentError, "Block is required" unless block # raise ArgumentError, "Block is required" unless block
34
+ #
35
+ id ||= NameInference.infer_rule_id_from_name(name) if name # id ||= NameInference.infer_rule_id_from_name(name) if name
36
+ id ||= NameInference.infer_rule_id_from_block(block) # id ||= NameInference.infer_rule_id_from_block(block)
37
+ script = block.source rescue nil # script = block.source rescue nil
38
+ caller_binding = block.binding # caller_binding = block.binding
39
+ rule name, id: id, script: script, binding: caller_binding do # rule name, id: id, script: script, binding: caller_binding do
40
+ #{trigger}(*args, **kwargs) # changed(*args, **kwargs)
41
+ run(&block) # run(&block)
42
+ end # end
43
+ end # end
44
+ module_function #{trigger.inspect} # module_function :changed
45
+ RUBY
46
+ end
47
+ end
48
+
49
+ def_terse_rule(:changed)
50
+ def_terse_rule(:channel)
51
+ def_terse_rule(:channel_linked)
52
+ def_terse_rule(:channel_unlinked)
53
+ def_terse_rule(:cron)
54
+ def_terse_rule(:every)
55
+ def_terse_rule(:received_command)
56
+ def_terse_rule(:thing_added)
57
+ def_terse_rule(:thing_updated)
58
+ def_terse_rule(:thing_removed)
59
+ def_terse_rule(:updated)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "conditions/duration"
4
+ require_relative "conditions/proc"
5
+ require_relative "trigger"
6
+
7
+ module OpenHAB
8
+ module DSL
9
+ module Rules
10
+ module Triggers
11
+ # @!visibility private
12
+ #
13
+ # Creates changed triggers
14
+ #
15
+ class Changed < Trigger
16
+ #
17
+ # Create the trigger
18
+ #
19
+ # @param [Object] item item to create trigger for
20
+ # @param [Item State] from state to restrict trigger to
21
+ # @param [Item State] to state to restrict trigger to
22
+ # @param [Duration, nil] duration ruration to delay trigger until to state is met
23
+ # @param [Object] attach object to be attached to the trigger
24
+ #
25
+ # @return [Trigger] OpenHAB triggers
26
+ #
27
+ def trigger(item:, from:, to:, duration:, attach:)
28
+ if duration
29
+ wait_trigger(item: item, from: from, to: to, duration: duration, attach: attach)
30
+ elsif [to, from].grep(Range).any?
31
+ range_trigger(item: item, from: from, to: to, attach: attach)
32
+ elsif [to, from].grep(Proc).any?
33
+ proc_trigger(item: item, from: from, to: to, attach: attach)
34
+ else
35
+ changed_trigger(item: item, from: from, to: to, attach: attach)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ # @return [String] A thing status Change trigger
42
+ THING_CHANGE = "core.ThingStatusChangeTrigger"
43
+
44
+ # @return [String] An item state change trigger
45
+ ITEM_STATE_CHANGE = "core.ItemStateChangeTrigger"
46
+
47
+ # @return [String] A group state change trigger for items in the group
48
+ GROUP_STATE_CHANGE = "core.GroupStateChangeTrigger"
49
+
50
+ #
51
+ # Create a TriggerDelay for for an item or group that is changed for a specific duration
52
+ #
53
+ # @param [Object] item to create trigger delay for
54
+ # @param [Duration] duration to delay trigger for until condition is met
55
+ # @param [Item State] to OpenHAB Item State item or group needs to change to
56
+ # @param [Item State] from OpenHAB Item State item or group needs to be coming from
57
+ # @param [Object] attach object to be attached to the trigger
58
+ #
59
+ # @return [Trigger] OpenHAB trigger
60
+ #
61
+ def wait_trigger(item:, duration:, to: nil, from: nil, attach: nil)
62
+ item_name = item.respond_to?(:name) ? item.name : item.to_s
63
+ logger.trace("Creating Changed Wait Change Trigger for Item(#{item_name}) Duration(#{duration}) "\
64
+ "To(#{to}) From(#{from}) Attach(#{attach})")
65
+ conditions = Conditions::Duration.new(to: to, from: from, duration: duration)
66
+ changed_trigger(item: item, to: nil, from: nil, attach: attach, conditions: conditions)
67
+ end
68
+
69
+ #
70
+ # Creates a trigger with a range condition on either 'from' or 'to' field
71
+ # @param [Object] item to create changed trigger on
72
+ # @param [Object] from state to restrict trigger to
73
+ # @param [Object] to state restrict trigger to
74
+ # @param [Object] attach object to be attached to the trigger
75
+ # @return [Trigger] OpenHAB trigger
76
+ #
77
+ def range_trigger(item:, from:, to:, attach:)
78
+ from, to = Conditions::Proc.range_procs(from, to)
79
+ proc_trigger(item: item, from: from, to: to, attach: attach)
80
+ end
81
+
82
+ #
83
+ # Creates a trigger with a proc condition on either 'from' or 'to' field
84
+ # @param [Object] item to create changed trigger on
85
+ # @param [Object] from state to restrict trigger to
86
+ # @param [Object] to state restrict trigger to
87
+ # @param [Object] attach object to be attached to the trigger
88
+ # @return [Trigger] OpenHAB trigger
89
+ #
90
+ def proc_trigger(item:, from:, to:, attach:)
91
+ # swap from/to w/ nil if from/to is a proc
92
+ # rubocop:disable Style/ParallelAssignment
93
+ from_proc, from = from, nil if from.is_a?(Proc)
94
+ to_proc, to = to, nil if to.is_a?(Proc)
95
+ # rubocop:enable Style/ParallelAssignment
96
+ conditions = Conditions::Proc.new(to: to_proc, from: from_proc)
97
+ changed_trigger(item: item, from: from, to: to, attach: attach, conditions: conditions)
98
+ end
99
+
100
+ #
101
+ # Create a changed trigger
102
+ #
103
+ # @param [Object] item to create changed trigger on
104
+ # @param [Object] from state to restrict trigger to
105
+ # @param [Object] to state restrict trigger to
106
+ # @param [Object] attach object to be attached to the trigger
107
+ #
108
+ def changed_trigger(item:, from:, to:, attach: nil, conditions: nil)
109
+ type, config = case item
110
+ when GroupItem::Members then group(group: item, from: from,
111
+ to: to)
112
+ when Core::Things::Thing then thing(thing: item, from: from, to: to)
113
+ else item(item: item, from: from, to: to)
114
+ end
115
+ append_trigger(type: type, config: config, attach: attach, conditions: conditions)
116
+ end
117
+
118
+ #
119
+ # Create a changed trigger for a thing
120
+ #
121
+ # @param [Thing] thing to detected changed states on
122
+ # @param [String] from state to restrict trigger to
123
+ # @param [String] to state to restrict trigger to
124
+ #
125
+ # @return [Array<Hash,String>] first element is a String specifying trigger type
126
+ # second element is a Hash configuring trigger
127
+ #
128
+ def thing(thing:, from:, to:)
129
+ trigger_for_thing(thing: thing, type: THING_CHANGE, to: to, from: from)
130
+ end
131
+
132
+ #
133
+ # Create a changed trigger for an item
134
+ #
135
+ # @param [Item] item to detected changed states on
136
+ # @param [String] from state to restrict trigger to
137
+ # @param [String] to to restrict trigger to
138
+ #
139
+ # @return [Array<Hash,String>] first element is a String specifying trigger type
140
+ # second element is a Hash configuring trigger
141
+ #
142
+ def item(item:, from:, to:)
143
+ config = { "itemName" => item.name }
144
+ config["state"] = to.to_s if to
145
+ config["previousState"] = from.to_s if from
146
+ [ITEM_STATE_CHANGE, config]
147
+ end
148
+
149
+ #
150
+ # Create a changed trigger for group items
151
+ #
152
+ # @param [Group] group to detected changed states on
153
+ # @param [String] from state to restrict trigger to
154
+ # @param [String] to to restrict trigger to
155
+ #
156
+ # @return [Array<Hash,String>] first element is a String specifying trigger type
157
+ # second element is a Hash configuring trigger
158
+ #
159
+ def group(group:, from:, to:)
160
+ config = { "groupName" => group.group.name }
161
+ config["state"] = to.to_s if to
162
+ config["previousState"] = from.to_s if from
163
+ [GROUP_STATE_CHANGE, config]
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end