openhab-scripting 2.9.1

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