openhab-scripting 2.9.1

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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/workflow.yml +327 -0
  3. data/.gitignore +17 -0
  4. data/.java-version +1 -0
  5. data/.rspec +1 -0
  6. data/.yardopts +1 -0
  7. data/CHANGELOG.md +113 -0
  8. data/Gemfile +28 -0
  9. data/Gemfile.lock +245 -0
  10. data/Guardfile +35 -0
  11. data/LICENSE +277 -0
  12. data/README.md +23 -0
  13. data/Rakefile +406 -0
  14. data/bin/console +15 -0
  15. data/bin/setup +8 -0
  16. data/config/userdata/config/org/openhab/restauth.config +3 -0
  17. data/cucumber.yml +1 -0
  18. data/docs/_config.yml +135 -0
  19. data/docs/contributing/index.md +47 -0
  20. data/docs/examples/conversions.md +123 -0
  21. data/docs/examples/index.md +61 -0
  22. data/docs/index.md +19 -0
  23. data/docs/installation/index.md +26 -0
  24. data/docs/motivation/index.md +27 -0
  25. data/docs/usage/execution.md +9 -0
  26. data/docs/usage/execution/delay.md +48 -0
  27. data/docs/usage/execution/otherwise.md +30 -0
  28. data/docs/usage/execution/run.md +70 -0
  29. data/docs/usage/execution/triggered.md +48 -0
  30. data/docs/usage/guards.md +51 -0
  31. data/docs/usage/guards/between.md +30 -0
  32. data/docs/usage/guards/not_if.md +41 -0
  33. data/docs/usage/guards/only_if.md +40 -0
  34. data/docs/usage/index.md +11 -0
  35. data/docs/usage/items.md +66 -0
  36. data/docs/usage/items/contact.md +84 -0
  37. data/docs/usage/items/dimmer.md +147 -0
  38. data/docs/usage/items/groups.md +76 -0
  39. data/docs/usage/items/number.md +225 -0
  40. data/docs/usage/items/string.md +49 -0
  41. data/docs/usage/items/switch.md +85 -0
  42. data/docs/usage/misc.md +7 -0
  43. data/docs/usage/misc/actions.md +108 -0
  44. data/docs/usage/misc/duration.md +21 -0
  45. data/docs/usage/misc/gems.md +25 -0
  46. data/docs/usage/misc/logging.md +21 -0
  47. data/docs/usage/misc/metadata.md +128 -0
  48. data/docs/usage/misc/store_states.md +42 -0
  49. data/docs/usage/misc/time_of_day.md +69 -0
  50. data/docs/usage/misc/timers.md +67 -0
  51. data/docs/usage/rule.md +43 -0
  52. data/docs/usage/things.md +29 -0
  53. data/docs/usage/triggers.md +8 -0
  54. data/docs/usage/triggers/changed.md +57 -0
  55. data/docs/usage/triggers/channel.md +54 -0
  56. data/docs/usage/triggers/command.md +69 -0
  57. data/docs/usage/triggers/cron.md +19 -0
  58. data/docs/usage/triggers/every.md +76 -0
  59. data/docs/usage/triggers/updated.md +78 -0
  60. data/lib/openhab.rb +39 -0
  61. data/lib/openhab/configuration.rb +16 -0
  62. data/lib/openhab/core/cron.rb +27 -0
  63. data/lib/openhab/core/debug.rb +34 -0
  64. data/lib/openhab/core/dsl.rb +47 -0
  65. data/lib/openhab/core/dsl/actions.rb +107 -0
  66. data/lib/openhab/core/dsl/entities.rb +103 -0
  67. data/lib/openhab/core/dsl/gems.rb +29 -0
  68. data/lib/openhab/core/dsl/group.rb +91 -0
  69. data/lib/openhab/core/dsl/items/items.rb +39 -0
  70. data/lib/openhab/core/dsl/items/number_item.rb +217 -0
  71. data/lib/openhab/core/dsl/items/string_item.rb +102 -0
  72. data/lib/openhab/core/dsl/monkey_patch/actions/actions.rb +4 -0
  73. data/lib/openhab/core/dsl/monkey_patch/actions/script_thing_actions.rb +22 -0
  74. data/lib/openhab/core/dsl/monkey_patch/events.rb +5 -0
  75. data/lib/openhab/core/dsl/monkey_patch/events/item_command.rb +13 -0
  76. data/lib/openhab/core/dsl/monkey_patch/events/item_state_changed.rb +25 -0
  77. data/lib/openhab/core/dsl/monkey_patch/events/thing_status_info.rb +26 -0
  78. data/lib/openhab/core/dsl/monkey_patch/items/contact_item.rb +54 -0
  79. data/lib/openhab/core/dsl/monkey_patch/items/dimmer_item.rb +125 -0
  80. data/lib/openhab/core/dsl/monkey_patch/items/group_item.rb +27 -0
  81. data/lib/openhab/core/dsl/monkey_patch/items/items.rb +130 -0
  82. data/lib/openhab/core/dsl/monkey_patch/items/metadata.rb +259 -0
  83. data/lib/openhab/core/dsl/monkey_patch/items/switch_item.rb +86 -0
  84. data/lib/openhab/core/dsl/monkey_patch/ruby/number.rb +69 -0
  85. data/lib/openhab/core/dsl/monkey_patch/ruby/range.rb +46 -0
  86. data/lib/openhab/core/dsl/monkey_patch/ruby/ruby.rb +5 -0
  87. data/lib/openhab/core/dsl/monkey_patch/types/decimal_type.rb +24 -0
  88. data/lib/openhab/core/dsl/monkey_patch/types/on_off_type.rb +41 -0
  89. data/lib/openhab/core/dsl/monkey_patch/types/open_closed_type.rb +25 -0
  90. data/lib/openhab/core/dsl/monkey_patch/types/percent_type.rb +23 -0
  91. data/lib/openhab/core/dsl/monkey_patch/types/types.rb +7 -0
  92. data/lib/openhab/core/dsl/property.rb +85 -0
  93. data/lib/openhab/core/dsl/rule/channel.rb +41 -0
  94. data/lib/openhab/core/dsl/rule/cron.rb +115 -0
  95. data/lib/openhab/core/dsl/rule/guard.rb +99 -0
  96. data/lib/openhab/core/dsl/rule/item.rb +207 -0
  97. data/lib/openhab/core/dsl/rule/rule.rb +374 -0
  98. data/lib/openhab/core/dsl/rule/triggers.rb +77 -0
  99. data/lib/openhab/core/dsl/states.rb +63 -0
  100. data/lib/openhab/core/dsl/things.rb +93 -0
  101. data/lib/openhab/core/dsl/time_of_day.rb +203 -0
  102. data/lib/openhab/core/dsl/timers.rb +85 -0
  103. data/lib/openhab/core/dsl/types/quantity.rb +255 -0
  104. data/lib/openhab/core/dsl/units.rb +41 -0
  105. data/lib/openhab/core/duration.rb +69 -0
  106. data/lib/openhab/core/log.rb +175 -0
  107. data/lib/openhab/core/patch_load_path.rb +7 -0
  108. data/lib/openhab/core/startup_delay.rb +22 -0
  109. data/lib/openhab/osgi.rb +52 -0
  110. data/lib/openhab/version.rb +9 -0
  111. data/openhab-scripting.gemspec +30 -0
  112. data/openhab_rules/warmup.rb +5 -0
  113. metadata +157 -0
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pp'
4
+ require 'java'
5
+ require 'set'
6
+ require 'core/dsl/group'
7
+ require 'core/dsl/items/number_item'
8
+ require 'core/dsl/items/string_item'
9
+
10
+ # Automation lookup and injection of OpenHab entities
11
+ java_import org.openhab.core.items.GroupItem
12
+
13
+ #
14
+ # Implements const_missing to return OpenHAB items or things if mapping to missing name if they exist
15
+ #
16
+ # @param [String] name Capital string that was not set as a constant and to be looked up
17
+ #
18
+ # @return [Object] OpenHAB Item or Thing if their name exist in OpenHAB item and thing regestries
19
+ #
20
+ def Object.const_missing(name)
21
+ EntityLookup.lookup_item(name) || EntityLookup.lookup_thing(name) || super
22
+ end
23
+
24
+ #
25
+ # Manages access to OpenHAB entities
26
+ #
27
+ module EntityLookup
28
+ #
29
+ # Decorate items with Ruby wrappers
30
+ #
31
+ # @param [Array] items Array of items to decorate
32
+ #
33
+ # @return [Array] Array of decorated items
34
+ #
35
+ def self.decorate_items(*items)
36
+ items.flatten.map do |item|
37
+ case item
38
+ when GroupItem
39
+ group = item
40
+ item = OpenHAB::Core::DSL::Groups::Group.new(Set.new(EntityLookup.decorate_items(item.all_members.to_a)))
41
+ item.group = group
42
+ item
43
+ when Java::Org.openhab.core.library.items::NumberItem
44
+ OpenHAB::Core::DSL::Items::NumberItem.new(item)
45
+ when Java::Org.openhab.core.library.items::StringItem
46
+ OpenHAB::Core::DSL::Items::StringItem.new(item)
47
+ else
48
+ item
49
+ end
50
+ end
51
+ end
52
+
53
+ #
54
+ # Loops up a Thing in the OpenHAB registry replacing '_' with ':'
55
+ #
56
+ # @param [String] name of Thing to lookup in Thing registry
57
+ #
58
+ # @return [Thing] if found, nil otherwise
59
+ #
60
+ def self.lookup_thing(name)
61
+ # Convert from : syntax to underscore
62
+ name = name.to_s if name.is_a? Symbol
63
+
64
+ # Thing UIDs have at least 3 segements
65
+ return if name.count('_') < 3
66
+
67
+ name = name.gsub('_', ':')
68
+ # rubocop: disable Style/GlobalVars
69
+ $things.get(Java::OrgOpenhabCoreThing::ThingUID.new(name))
70
+ # rubocop: enable Style/GlobalVars
71
+ end
72
+
73
+ #
74
+ # Lookup OpenHAB items in item registry
75
+ #
76
+ # @param [String] name of item to lookup
77
+ #
78
+ # @return [Item] OpenHAB item if registry contains a matching item, nil othewise
79
+ #
80
+ def self.lookup_item(name)
81
+ name = name.to_s if name.is_a? Symbol
82
+ # rubocop: disable Style/GlobalVars
83
+ item = $ir.get(name)
84
+ # rubocop: enable Style/GlobalVars
85
+ EntityLookup.decorate_items(item).first
86
+ end
87
+
88
+ #
89
+ # Automatically looks up OpenHAB items and things in appropriate registries
90
+ #
91
+ # @param [method] method Name of item to lookup
92
+ # @param [<Type>] args method arguments
93
+ # @param [<Type>] block supplied to missing method
94
+ #
95
+ # @return [Object] Item or Thing if found in registry
96
+ #
97
+ def method_missing(method, *args, &block)
98
+ return if method.to_s == 'scriptLoaded'
99
+ return if method.to_s == 'scriptUnloaded'
100
+
101
+ EntityLookup.lookup_item(method) || EntityLookup.lookup_thing(method) || super
102
+ end
103
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'core/log'
4
+
5
+ begin
6
+ require 'bundler/inline'
7
+
8
+ include Logging
9
+ logger.debug('Bundler required')
10
+ rescue LoadError
11
+ include Logging
12
+ logger.debug('Bundler not found installing')
13
+
14
+ begin
15
+ require 'rubygems/commands/install_command'
16
+ cmd = Gem::Commands::InstallCommand.new
17
+ cmd.handle_options ['--no-document', 'bundler', '-v', '2.1.4']
18
+ cmd.execute
19
+ logger.debug('Bundler is installed')
20
+ require 'bundler/inline'
21
+ rescue Gem::SystemExitException => e
22
+ if e.exit_code.zero?
23
+ logger.debug('Bundler is installed')
24
+ require 'bundler/inline'
25
+ else
26
+ logger.error("Error installing bundler, exit code: #{e}")
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+ require 'forwardable'
5
+ require 'openhab/core/dsl/entities'
6
+
7
+ module OpenHAB
8
+ module Core
9
+ module DSL
10
+ #
11
+ # Provides access to OpenHAB Groups
12
+ #
13
+ module Groups
14
+ #
15
+ # Indicator struct interpreted by rules to trigger based on items contained in a group
16
+ #
17
+ GroupItems = Struct.new(:group, keyword_init: true)
18
+
19
+ #
20
+ # Provide access to groups as a set
21
+ #
22
+ class Groups < SimpleDelegator
23
+ #
24
+ # Get a OpenHAB Group by name
25
+ # @param [String] name of the group to retrieve
26
+ #
27
+ # @return [Set] of OpenHAB Groups
28
+ #
29
+ def[](name)
30
+ group = EntityLookup.lookup_item(name)
31
+ (group.is_a? Group) ? group : nil
32
+ end
33
+ end
34
+
35
+ #
36
+ # Retreive all OpenHAB groups
37
+ #
38
+ # @return [Set] of OpenHAB Groups
39
+ #
40
+ def groups
41
+ Groups.new(EntityLookup.decorate_items($ir.items.select { |item| item.is_a? GroupItem }))
42
+ end
43
+
44
+ # Group class that provides access to OpenHAB group object and delegates other methods to
45
+ # a set of group items
46
+ class Group < SimpleDelegator
47
+ extend Forwardable
48
+
49
+ java_import org.openhab.core.items.GroupItem
50
+
51
+ # @return [org.openhab.core.items.GroupItem] OpenHAB Java Group Item
52
+ attr_accessor :group
53
+
54
+ # @!macro [attach] def_delegators
55
+ # @!method $2
56
+ # Forwards to org.openhab.core.items.GroupItem
57
+ # @see org::openhab::core::items::GroupItem
58
+ def_delegator :@group, :name
59
+ def_delegator :@group, :label
60
+
61
+ #
62
+ # Gets members of this group that are themselves a group
63
+ #
64
+ # @return [Set] Set of members that are of type group
65
+ #
66
+ def groups
67
+ group.members.grep(org.openhab.core.items.GroupItem)
68
+ end
69
+
70
+ #
71
+ # Wraps the group in a struct, this method is intended to be called
72
+ # as an indicator to the rule method that the user wishes to trigger
73
+ # based on changes to group items
74
+ #
75
+ # @return [GroupItems] Indicator struct used by rules engine to trigger based on item changes
76
+ #
77
+ def items
78
+ GroupItems.new(group: group)
79
+ end
80
+
81
+ #
82
+ # @return [String] List of groups seperated by commas
83
+ #
84
+ def to_s
85
+ "[#{map(&:to_s).join(',')}]"
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'java'
4
+ require 'openhab/core/dsl/entities'
5
+
6
+ module OpenHAB
7
+ module Core
8
+ module DSL
9
+ #
10
+ # Manages OpenHAB items
11
+ #
12
+ module Items
13
+ #
14
+ # Delegates to underlying set of all OpenHAB Items, provides convenience methods
15
+ #
16
+ class Items < SimpleDelegator
17
+ # Fetches the named item from the the ItemRegistry
18
+ # @param [String] name
19
+ # @return Item from registry, nil if item missing or requested item is a Group Type
20
+ def[](name)
21
+ # rubocop: disable Style/GlobalVars
22
+ item = $ir.getItem(name)
23
+ # rubocop: enable Style/GlobalVars
24
+ (item.is_a? GroupItem) ? nil : item
25
+ end
26
+ end
27
+
28
+ java_import org.openhab.core.items.GroupItem
29
+ # Fetches all non-group items from the item registry
30
+ # @return [OpenHAB::Core::DSL::Items::Items]
31
+ def items
32
+ # rubocop: disable Style/GlobalVars
33
+ Items.new(EntityLookup.decorate_items($ir.items.reject { |item| item.is_a? GroupItem }))
34
+ # rubocop: enable Style/GlobalVars
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal'
4
+ require 'forwardable'
5
+ require 'java'
6
+ require 'openhab/core/dsl/types/quantity'
7
+
8
+ module OpenHAB
9
+ module Core
10
+ module DSL
11
+ module Items
12
+ #
13
+ # Delegation to OpenHAB Number Item
14
+ #
15
+ class NumberItem < Numeric
16
+ extend Forwardable
17
+
18
+ def_delegator :@number_item, :to_s
19
+
20
+ java_import org.openhab.core.library.types.DecimalType
21
+ java_import org.openhab.core.library.types.QuantityType
22
+ java_import 'tec.uom.se.format.SimpleUnitFormat'
23
+ java_import 'tec.uom.se.AbstractUnit'
24
+
25
+ #
26
+ # Create a new NumberItem
27
+ #
28
+ # @param [Java::Org::openhab::core::library::items::NumberItem] number_item OpenHAB number item to delegate to
29
+ #
30
+ def initialize(number_item)
31
+ @number_item = number_item
32
+ super()
33
+ end
34
+
35
+ #
36
+ # Check if NumberItem is truthy? as per defined by library
37
+ #
38
+ # @return [Boolean] True if item is not in state UNDEF or NULL and value is not zero.
39
+ #
40
+ def truthy?
41
+ @number_item.state? && @number_item.state != DecimalType::ZERO
42
+ end
43
+
44
+ #
45
+ # Coerce objects into a NumberItem
46
+ #
47
+ # @param [Object] other object to coerce to a NumberItem if possible
48
+ #
49
+ # @return [Object] NumberItem, QuantityTypes, BigDecimal or nil depending on NumberItem configuration and/or supplied object
50
+ #
51
+ def coerce(other)
52
+ logger.trace("Coercing #{self} as a request from #{other.class}")
53
+ case other
54
+ when Quantity
55
+ as_qt = to_qt
56
+ logger.trace("Converted #{self} to a Quantity #{as_qt}")
57
+ [other, as_qt]
58
+ when Numeric
59
+ if dimension
60
+ [Quantity.new(other), to_qt]
61
+ elsif @number_item.state?
62
+ [BigDecimal(other), @number_item.state.to_big_decimal.to_d]
63
+ end
64
+ else
65
+ logger.trace("#{self} cannot be coereced to #{other.class}")
66
+ nil
67
+ end
68
+ end
69
+
70
+ #
71
+ # Compare NumberItem to supplied object
72
+ #
73
+ # @param [Object] other object to compare to
74
+ #
75
+ # @return [Integer] -1,0,1 or nil depending on value supplied, nil comparison to supplied object is not possible.
76
+ #
77
+ def <=>(other)
78
+ logger.trace("Comparing #{self} to #{other}")
79
+ case other
80
+ when NumberItem
81
+ if other.dimension
82
+ logger.trace('Other is dimensioned, converting self and other to QuantityTypes to compare')
83
+ to_qt <=> other.to_qt
84
+ else
85
+ @number_item.state <=> other.state
86
+ end
87
+ when Numeric
88
+ @number_item.state.to_big_decimal.to_d <=> BigDecimal(other)
89
+ when String
90
+ @number_item.state <=> QuantityType.new(other) if dimension
91
+ end
92
+ end
93
+
94
+ #
95
+ # Convert NumberItem to a Quantity
96
+ #
97
+ # @param [Object] other String or Unit representing an OpenHAB Unit
98
+ #
99
+ # @return [OpenHAB::Core::DSL::Types::Quantity] NumberItem converted to supplied Unit
100
+ #
101
+ def |(other)
102
+ other = SimpleUnitFormat.instance.unitFor(other) if other.is_a? String
103
+
104
+ if dimension
105
+ to_qt | other
106
+ else
107
+ Quantity.new(QuantityType.new(to_d.to_java, other))
108
+ end
109
+ end
110
+
111
+ #
112
+ # Convert NumberItem to a Quantity
113
+ #
114
+ # @return [OpenHAB::Core::DSL::Types::Quantity] NumberItem converted to a QuantityUnit
115
+ #
116
+ def to_qt
117
+ if dimension
118
+ Quantity.new(@number_item.get_state_as(QuantityType))
119
+ else
120
+ Quantity.new(QuantityType.new(to_d.to_java, AbstractUnit::ONE))
121
+ end
122
+ end
123
+
124
+ #
125
+ # Converts the NumberItem to an Integer
126
+ #
127
+ # @return [Integer] NumberItem as an integer
128
+ #
129
+ def to_i
130
+ to_d&.to_i
131
+ end
132
+
133
+ #
134
+ # Converts the NumberItem to a float
135
+ #
136
+ # @return [Float] NumberItem as a float
137
+ #
138
+ def to_f
139
+ to_d&.to_f
140
+ end
141
+
142
+ #
143
+ # Converts the NumberItem to a BigDecimal
144
+ #
145
+ # @return [BigDecimal] NumberItem as a BigDecimal
146
+ #
147
+ def to_d
148
+ @number_item.state.to_big_decimal.to_d if @number_item.state.respond_to? :to_big_decimal
149
+ end
150
+
151
+ #
152
+ # Get the Dimension attached to the NumberItem
153
+ #
154
+ # @return [Java::org::openhab::core::library::types::QuantityType] dimension
155
+ #
156
+ def dimension
157
+ @number_item.dimension
158
+ end
159
+
160
+ #
161
+ # Forward missing methods to Openhab Number Item if they are defined
162
+ #
163
+ # @param [String] meth method name
164
+ # @param [Array] args arguments for method
165
+ # @param [Proc] block <description>
166
+ #
167
+ # @return [Object] Value from delegated method in OpenHAB NumberItem
168
+ #
169
+ def method_missing(meth, *args, &block)
170
+ if @number_item.respond_to?(meth)
171
+ @number_item.__send__(meth, *args, &block)
172
+ elsif ::Kernel.method_defined?(meth) || ::Kernel.private_method_defined?(meth)
173
+ ::Kernel.instance_method(meth).bind_call(self, *args, &block)
174
+ else
175
+ super(meth, *args, &block)
176
+ end
177
+ end
178
+
179
+ %w[+ - * /].each do |operation|
180
+ define_method(operation) do |other|
181
+ logger.trace("Execution math operation '#{operation}' on #{inspect} with #{other.inspect}")
182
+ if other.is_a? NumberItem
183
+ logger.trace('Math operations is between two NumberItems.')
184
+ if dimension && other.dimension
185
+ # If both numbers have dimensions, do the math on the quantity types.
186
+ logger.trace("Both objects have dimensions self='#{dimension}' other='#{other.dimension}'")
187
+ to_qt.public_send(operation, other.to_qt)
188
+ elsif dimension && !other.dimension
189
+ # If this number has dimension and the other does not, do math with this quantity type and the other as a big decimal
190
+ logger.trace("Self has dimension self='#{dimension}' other lacks dimension='#{other}'")
191
+ to_qt.public_send(operation, other)
192
+ elsif other.dimension
193
+ # If this number has no dimension and the other does, convert this into a dimensionless quantity
194
+ logger.trace("Self has no dimension self='#{self}' other has dimension='#{other.dimension}'")
195
+ to_qt.public_send(operation, other)
196
+ else
197
+ logger.trace("Both objects lack dimension, self='#{self}' other='#{other}'")
198
+ # If nothing has a dimension, just use BigDecimals
199
+ to_d.public_send(operation, other.to_d)
200
+ end
201
+ elsif other.is_a? Numeric
202
+ to_d.public_send(operation, BigDecimal(other))
203
+ elsif dimension && other.is_a?(String)
204
+ to_qt.public_send(operation, Quantity.new(other))
205
+ elsif other.respond_to? :coerce
206
+ a, b = other.coerce(to_d)
207
+ a.public_send(operation, b)
208
+ else
209
+ raise TypeError, "#{other.class} can't be coerced into a NumberItem"
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end