openhab-scripting 5.22.1 → 5.23.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2c5b8b2a28cbbda196f66340982141af673362693181e88588e89f9c4b1da29
4
- data.tar.gz: 6a0483d0d94ae2a9492870e8089efd0b2a1afc400337d790a271b6fd9ca05ae3
3
+ metadata.gz: 55ff8734004f7befde1f1d3bb27560cfc8c5dbb7287f8dbb5e19a50fadec0d23
4
+ data.tar.gz: dba9276e8b4845e1b265973440aa0e036da3137176ba44c5c256ddfcd5dad227
5
5
  SHA512:
6
- metadata.gz: 2115e707e47980b212827203b1401aeceb90c3c11fd0b8e91494581f12e728f4bc68d46c7f3b2991ff63a6f3db68ee45eb0b617e1a94ae642f6b2267de797f8c
7
- data.tar.gz: ba2fd9524d6387287cbde50a8ff6cbc449069889836ced9e5b8cb79d8c7e3a4fe029804387d4866fed7b56b79e6aaba1584180fadc61d424555e0c755666cbbd
6
+ metadata.gz: 6c4692d9a8ce6104b511e1475e01fcae49f0fda6bdf04f498c26b023237f8332e87166cbdc5e2754f1375b4e2cbf31f70c706e68beda3657d6f09b28d37b59bf
7
+ data.tar.gz: 133e78f26b440c5ffbb628ee323d56d2e4b4e74c1a65762cec75818f7ebfe5eeda4275e7cd6157fb68d90d551c5e2b4c984016c149abf222dc1917428f4c7e49
@@ -29,8 +29,11 @@ module OpenHAB
29
29
  # @param on_click [String, nil] The action to be performed when the user clicks on the notification.
30
30
  # Specified using the {https://next.openhab.org/addons/integrations/openhabcloud/#action-syntax
31
31
  # action syntax}.
32
- # @param attachment [String, nil] The URL of the media attachment to be displayed with the notification.
33
- # This URL must be reachable by the push notification client.
32
+ # @param attachment [String, Item, nil] The URL of the media attachment to be displayed with the notification.
33
+ # This can either be a fully qualified URL, prefixed with
34
+ # `http://` or `https://` and reachable by the client device,
35
+ # a relative path on the user's openHAB instance starting with `/`,
36
+ # or an image item.
34
37
  # @param buttons [Array<String>, Hash<String, String>, nil] Buttons to include in the notification.
35
38
  # - In array form, each element is specified as `Title=$action`, where `$action` follows the
36
39
  # {https://next.openhab.org/addons/integrations/openhabcloud/#action-syntax action syntax}.
@@ -94,6 +97,8 @@ module OpenHAB
94
97
  buttons = buttons.map { |title, action| "#{title}=#{action}" } if buttons.is_a?(Hash)
95
98
  raise ArgumentError, "buttons must contain (0..3) elements." unless (0..3).cover?(buttons.size)
96
99
 
100
+ attachment = "item:#{attachment.name}" if attachment.is_a?(Item) && attachment.image_item?
101
+
97
102
  args.push(title&.to_s,
98
103
  id&.to_s,
99
104
  on_click&.to_s,
@@ -146,7 +146,7 @@ module OpenHAB
146
146
  base_item.format_type(command)
147
147
  end
148
148
 
149
- %w[color contact date_time dimmer image location number player rollershutter string switch].each do |type|
149
+ %w[call color contact date_time dimmer image location number player rollershutter string switch].each do |type|
150
150
  type_class = type.gsub(/(^[a-z]|_[a-z])/) { |letter| letter[-1].upcase }
151
151
  class_eval <<~RUBY, __FILE__, __LINE__ + 1
152
152
  def #{type}_item? # def date_time_item?
@@ -4,6 +4,7 @@
4
4
  return unless OpenHAB::Core.version >= OpenHAB::Core::V4_1
5
5
 
6
6
  require "forwardable"
7
+ require "openhab/core/lazy_array"
7
8
 
8
9
  module OpenHAB
9
10
  module Core
@@ -30,7 +31,7 @@ module OpenHAB
30
31
  # @see DSL::Rules::BuilderDSL#time_series_updated #time_series_updated rule trigger
31
32
  #
32
33
  class TimeSeries
33
- extend Forwardable
34
+ include LazyArray
34
35
 
35
36
  # @!attribute [r] policy
36
37
  # Returns the persistence policy of this series.
@@ -81,12 +82,19 @@ module OpenHAB
81
82
  "size=#{size}>"
82
83
  end
83
84
 
85
+ # Explicit conversion to Array
86
+ #
87
+ # @return [Array]
88
+ def to_a
89
+ get_states.to_array.to_a.freeze
90
+ end
91
+
84
92
  #
85
93
  # Returns the content of this series.
86
94
  # @return [Array<org.openhab.core.types.TimeSeries.Entry>]
87
95
  #
88
96
  def states
89
- get_states.to_array.to_a.freeze
97
+ to_a
90
98
  end
91
99
 
92
100
  # rename raw methods so we can overwrite them
@@ -100,29 +108,62 @@ module OpenHAB
100
108
  #
101
109
  # @note This method returns self so it can be chained, unlike the Java version.
102
110
  #
103
- # @param [Instant, #to_zoned_date_time, #to_instant] instant An instant for the given state.
111
+ # @param [Instant, #to_zoned_date_time, #to_instant] timestamp An instant for the given state.
104
112
  # @param [State, String, Numeric] state The State at the given timestamp.
105
113
  # If a String is given, it will be converted to {StringType}.
106
114
  # If a {Numeric} is given, it will be converted to {DecimalType}.
107
115
  # @return [self]
108
116
  # @raise [ArgumentError] if state is not a {State}, String or {Numeric}
109
117
  #
110
- def add(instant, state)
111
- instant = instant.to_zoned_date_time if instant.respond_to?(:to_zoned_date_time)
112
- instant = instant.to_instant if instant.respond_to?(:to_instant)
113
- state = case state
114
- when State then state
115
- when String then StringType.new(state)
116
- when Numeric then DecimalType.new(state)
117
- else
118
- raise ArgumentError, "state must be a State, String or Numeric, but was #{state.class}"
119
- end
120
- add_instant(instant, state)
118
+ def add(timestamp, state)
119
+ timestamp = to_instant(timestamp)
120
+ state = format_state(state)
121
+ add_instant(timestamp, state)
121
122
  self
122
123
  end
123
124
 
124
- # any method that exists on Array gets forwarded to states
125
- delegate (Array.instance_methods - instance_methods) => :states
125
+ #
126
+ # Appends an entry to self, returns self
127
+ #
128
+ # @param [Array<Instant, State>] entry a two-element array with the timestamp and state.
129
+ # The timestamp can be an {Instant} or any object that responds to #to_zoned_date_time.
130
+ # @return [self]
131
+ #
132
+ # @example Append an entry
133
+ # time_series << [Time.at(2), 2]
134
+ #
135
+ def <<(entry)
136
+ raise ArgumentError, "entry must be an Array, but was #{entry.class}" unless entry.respond_to?(:to_ary)
137
+
138
+ entry = entry.to_ary
139
+ raise ArgumentError, "entry must be an Array of size 2, but was #{entry.size}" unless entry.size == 2
140
+
141
+ add(entry[0], entry[1])
142
+ end
143
+
144
+ private
145
+
146
+ def to_instant(timestamp)
147
+ if timestamp.is_a?(Instant)
148
+ timestamp
149
+ elsif timestamp.respond_to?(:to_instant)
150
+ timestamp.to_instant
151
+ elsif timestamp.respond_to?(:to_zoned_date_time)
152
+ timestamp.to_zoned_date_time.to_instant
153
+ else
154
+ raise ArgumentError, "timestamp must be an Instant, or convertible to one, but was #{timestamp.class}"
155
+ end
156
+ end
157
+
158
+ def format_state(state)
159
+ case state
160
+ when State then state
161
+ when String then StringType.new(state)
162
+ when Numeric then DecimalType.new(state)
163
+ else
164
+ raise ArgumentError, "state must be a State, String or Numeric, but was #{state.class}"
165
+ end
166
+ end
126
167
  end
127
168
  end
128
169
  end
@@ -64,6 +64,29 @@ module OpenHAB
64
64
  def cancelled?
65
65
  resolution == :cancelled
66
66
  end
67
+
68
+ #
69
+ # Reschedule the timed command.
70
+ #
71
+ # If the timed command was cancelled, this will also resume it.
72
+ #
73
+ # @param [java.time.temporal.TemporalAmount, #to_zoned_date_time, Proc, nil] time
74
+ # When to reschedule the timer for. If unspecified, the original time is used.
75
+ # @return [void]
76
+ #
77
+ def reschedule(time = nil)
78
+ self.resolution = nil
79
+ timer.reschedule(time)
80
+ end
81
+
82
+ #
83
+ # Resume a cancelled timed command to its original expiration time.
84
+ #
85
+ # @return [void]
86
+ #
87
+ def resume
88
+ self.resolution = nil
89
+ end
67
90
  end
68
91
 
69
92
  @timed_commands = java.util.concurrent.ConcurrentHashMap.new
@@ -82,7 +105,13 @@ module OpenHAB
82
105
  #
83
106
  # @note If a block is provided, and the timer is canceled because the
84
107
  # item changed state while it was waiting, the block will still be
85
- # executed. Be sure to check {TimedCommandDetails#expired? #expired?}
108
+ # executed. The timed command can be reinstated by calling {TimedCommandDetails#resume #resume} or
109
+ # {TimedCommandDetails#reschedule #reschedule}.
110
+ #
111
+ # If the timer expired, the timed command can be rescheduled from inside the block by calling
112
+ # {TimedCommandDetails#reschedule #reschedule}.
113
+ #
114
+ # Be sure to check {TimedCommandDetails#expired? #expired?}
86
115
  # and/or {TimedCommandDetails#cancelled? #cancelled?} to determine why
87
116
  # the block was called.
88
117
  #
@@ -181,25 +210,30 @@ module OpenHAB
181
210
  end
182
211
 
183
212
  # Creates the timer to handle changing the item state when timer expires or invoking user supplied block
184
- # @param [TimedCommandDetailes] timed_command_details details about the timed command
213
+ # @param [TimedCommandDetails] timed_command_details details about the timed command
185
214
  # @return [Timer] Timer
186
215
  def timed_command_timer(timed_command_details, duration)
187
216
  DSL.after(duration) do
188
217
  timed_command_details.mutex.synchronize do
189
218
  logger.trace "Timed command expired - #{timed_command_details}"
190
- DSL.rules.remove(timed_command_details.rule_uid)
191
219
  timed_command_details.resolution = :expired
192
220
  case timed_command_details.on_expire
193
221
  when Proc
194
222
  logger.trace "Invoking block #{timed_command_details.on_expire} after timed command for #{name} expired"
195
223
  timed_command_details.on_expire.call(timed_command_details)
224
+ if timed_command_details.resolution.nil?
225
+ logger.trace { "Block rescheduled the timer to #{timed_command_details.timer.execution_time}" }
226
+ end
196
227
  when Core::Types::UnDefType
197
228
  update(timed_command_details.on_expire)
198
229
  else
199
230
  command(timed_command_details.on_expire)
200
231
  end
232
+ if timed_command_details.resolution
233
+ DSL.rules.remove(timed_command_details.rule_uid)
234
+ TimedCommand.timed_commands.delete(timed_command_details.item)
235
+ end
201
236
  end
202
- TimedCommand.timed_commands.delete(timed_command_details.item)
203
237
  end
204
238
  end
205
239
 
@@ -250,17 +284,26 @@ module OpenHAB
250
284
  def execute(_mod = nil, inputs = nil)
251
285
  ThreadLocal.thread_local(**@thread_locals) do
252
286
  @timed_command_details.mutex.synchronize do
253
- logger.trace "Canceling implicit timer #{@timed_command_details.timer} for " \
254
- "#{@timed_command_details.item.name} because received event #{inputs}"
287
+ logger.trace do
288
+ "Canceling implicit timer #{@timed_command_details.timer} for " \
289
+ "#{@timed_command_details.item.name} because of received event #{inputs}"
290
+ end
291
+ original_execution_time = @timed_command_details.timer.execution_time
255
292
  @timed_command_details.timer.cancel
256
- DSL.rules.remove(@timed_command_details.rule_uid)
293
+ DSL.rules[@timed_command_details.rule_uid].disable
257
294
  @timed_command_details.resolution = :cancelled
258
295
  if @timed_command_details.on_expire.is_a?(Proc)
259
296
  logger.trace "Executing user supplied block on timed command cancelation"
260
297
  @timed_command_details.on_expire.call(@timed_command_details)
261
298
  end
299
+ if @timed_command_details.resolution
300
+ DSL.rules.remove(@timed_command_details.rule_uid)
301
+ TimedCommand.timed_commands.delete(@timed_command_details.item)
302
+ else
303
+ DSL.rules[@timed_command_details.rule_uid].enable
304
+ @timed_command_details.timer.reschedule(original_execution_time)
305
+ end
262
306
  end
263
- TimedCommand.timed_commands.delete(@timed_command_details.item)
264
307
  rescue Exception => e
265
308
  raise if defined?(::RSpec)
266
309
 
@@ -1278,6 +1278,8 @@ module OpenHAB
1278
1278
  #
1279
1279
  # The `event` passed to run blocks will be a {Core::Events::TimerEvent}.
1280
1280
  #
1281
+ # For a more complex schedule, use {cron}.
1282
+ #
1281
1283
  # @param [String,
1282
1284
  # Duration,
1283
1285
  # java.time.MonthDay,
@@ -1294,8 +1296,8 @@ module OpenHAB
1294
1296
  # :thursday,
1295
1297
  # :friday,
1296
1298
  # :saturday,
1297
- # :sunday] value
1298
- # When to execute rule.
1299
+ # :sunday] values
1300
+ # When to execute rule. Multiple day-of-week can be specified. Otherwise, only one value is allowed.
1299
1301
  # @param [LocalTime, String, Core::Items::DateTimeItem, nil] at What time of day to execute rule
1300
1302
  # If `value` is `:day`, `at` can be a {Core::Items::DateTimeItem DateTimeItem}, and
1301
1303
  # the trigger will run every day at the (time only portion of) current state of the
@@ -1304,6 +1306,7 @@ module OpenHAB
1304
1306
  # @return [void]
1305
1307
  #
1306
1308
  # @see at
1309
+ # @see cron
1307
1310
  #
1308
1311
  # @example
1309
1312
  # rule "Daily" do
@@ -1363,15 +1366,38 @@ module OpenHAB
1363
1366
  # run { logger.info "Happy Valentine's Day!" }
1364
1367
  # end
1365
1368
  #
1366
- def every(value, at: nil, attach: nil)
1367
- return every(java.time.MonthDay.parse(value), at: at, attach: attach) if value.is_a?(String)
1369
+ # @example Multiple day-of-week
1370
+ # rule "Weekend" do
1371
+ # every :saturday, :sunday, at: "10:00"
1372
+ # run { logger.info "It's the weekend!" }
1373
+ # end
1374
+ #
1375
+ def every(*values, at: nil, attach: nil)
1376
+ raise ArgumentError, "Missing values" if values.empty?
1377
+
1378
+ if Cron.all_dow_symbols?(values)
1379
+ @ruby_triggers << [:every, values.join(", "), { at: at }]
1380
+ return cron(Cron.from_dow_symbols(values, at), attach: attach)
1381
+ end
1382
+
1383
+ if values.size != 1
1384
+ raise ArgumentError,
1385
+ "Multiple values are only allowed for day-of-week. " \
1386
+ "Otherwise only one value is allowed, given: #{values.size}"
1387
+ end
1388
+
1389
+ value = values.first
1390
+ value = java.time.MonthDay.parse(value.to_str) if value.respond_to?(:to_str)
1368
1391
 
1369
1392
  @ruby_triggers << [:every, value, { at: at }]
1370
1393
 
1371
1394
  if value == :day && at.is_a?(Item)
1372
- raise ArgumentError, "Attachments are not supported with dynamic datetime triggers" unless attach.nil?
1395
+ # @!deprecated OH 3.4 - attachments are supported in OH 4.0+
1396
+ if Core.version <= Core::V4_0 && !attach.nil?
1397
+ raise ArgumentError, "Attachments are not supported with dynamic datetime triggers in openHAB 3.x"
1398
+ end
1373
1399
 
1374
- return trigger("timer.DateTimeTrigger", itemName: at.name, timeOnly: true)
1400
+ return trigger("timer.DateTimeTrigger", itemName: at.name, timeOnly: true, attach: attach)
1375
1401
  end
1376
1402
 
1377
1403
  cron_expression = case value
@@ -50,8 +50,13 @@ module OpenHAB
50
50
  }.freeze
51
51
  private_constant :DAY_OF_WEEK_MAP
52
52
 
53
+ DAY_OF_WEEK = DAY_OF_WEEK_MAP.keys.freeze
54
+ private_constant :DAY_OF_WEEK
55
+
53
56
  # @return [Hash] Converts the DAY_OF_WEEK_MAP to map used by Cron Expression
54
- DAY_OF_WEEK_EXPRESSION_MAP = DAY_OF_WEEK_MAP.transform_values { |v| CRON_EXPRESSION_MAP.merge(dow: v) }
57
+ DAY_OF_WEEK_EXPRESSION_MAP = DAY_OF_WEEK_MAP.transform_values do |v|
58
+ CRON_EXPRESSION_MAP.merge(second: 0, minute: 0, hour: 0, dow: v)
59
+ end.freeze
55
60
  private_constant :DAY_OF_WEEK_EXPRESSION_MAP
56
61
 
57
62
  # @return [Hash] Create a set of cron expressions based on different time intervals
@@ -76,7 +81,7 @@ module OpenHAB
76
81
  # @param [Duration] duration
77
82
  # @param [Object] at LocalTime or String representing time of day
78
83
  #
79
- # @return [Hash] map describing cron expression
84
+ # @return [String] cron expression
80
85
  #
81
86
  def self.from_duration(duration, at)
82
87
  raise ArgumentError, '"at" cannot be used with duration' if at
@@ -90,7 +95,7 @@ module OpenHAB
90
95
  # @param [MonthDay] monthday a {MonthDay} object
91
96
  # @param [Object] at LocalTime or String representing time of day
92
97
  #
93
- # @return [Hash] map describing cron expression
98
+ # @return [String] cron expression
94
99
  #
95
100
  def self.from_monthday(monthday, at)
96
101
  expression_map = EXPRESSION_MAP[:day].merge(month: monthday.month_value, dom: monthday.day_of_month)
@@ -104,7 +109,7 @@ module OpenHAB
104
109
  # @param [Symbol] symbol
105
110
  # @param [Object] at LocalTime or String representing time of day
106
111
  #
107
- # @return [Hash] map describing cron expression created from symbol
112
+ # @return [String] cron expression created from symbol
108
113
  #
109
114
  def self.from_symbol(symbol, at)
110
115
  expression_map = EXPRESSION_MAP[symbol]
@@ -112,12 +117,38 @@ module OpenHAB
112
117
  map_to_cron(expression_map)
113
118
  end
114
119
 
120
+ #
121
+ # Checks if all symbols are day-of-week symbols
122
+ #
123
+ # @param [Array<Symbol>] symbols
124
+ #
125
+ # @return [Boolean] true if all symbols are day-of-week symbols
126
+ #
127
+ def self.all_dow_symbols?(symbols)
128
+ (symbols & DAY_OF_WEEK) == symbols
129
+ end
130
+
131
+ #
132
+ # Create a cron map from a list of day-of-week symbols
133
+ #
134
+ # @param [Symbol] symbols
135
+ # @param [Object] at LocalTime or String representing time of day
136
+ #
137
+ # @return [String] cron expression created from symbols
138
+ #
139
+ def self.from_dow_symbols(symbols, at)
140
+ dow = DAY_OF_WEEK_MAP.fetch_values(*symbols).join(",")
141
+ expression_map = CRON_EXPRESSION_MAP.merge(dow: dow, hour: 0, minute: 0, second: 0)
142
+ expression_map = at_condition(expression_map, at) if at
143
+ map_to_cron(expression_map)
144
+ end
145
+
115
146
  #
116
147
  # Create a cron map from cron elements
117
148
  #
118
149
  # @param [Hash] fields Cron fields (second, minute, hour, dom, month, dow, year)
119
150
  #
120
- # @return [Hash] map describing cron expression
151
+ # @return [String] cron expression
121
152
  #
122
153
  def self.from_fields(fields)
123
154
  extra_fields = fields.keys - CRON_EXPRESSION_MAP.keys
@@ -190,17 +221,14 @@ module OpenHAB
190
221
  #
191
222
  # If an at time is provided, parse that and merge the new fields into the expression.
192
223
  #
193
- # @param [<Type>] expression_map <description>
194
- # @param [<Type>] at_time <description>
224
+ # @param [Hash] expression_map
225
+ # @param [LocalTime,String] at_time
195
226
  #
196
- # @return [<Type>] <description>
227
+ # @return [Hash] expression map with at time merged in
197
228
  #
198
229
  def self.at_condition(expression_map, at_time)
199
- if at_time
200
- tod = at_time.is_a?(LocalTime) ? at_time : LocalTime.parse(at_time)
201
- expression_map = expression_map.merge(hour: tod.hour, minute: tod.minute, second: tod.second)
202
- end
203
- expression_map
230
+ tod = at_time.is_a?(LocalTime) ? at_time : LocalTime.parse(at_time)
231
+ expression_map.merge(hour: tod.hour, minute: tod.minute, second: tod.second)
204
232
  end
205
233
 
206
234
  #
@@ -9,7 +9,32 @@ module OpenHAB
9
9
  # @!visibility private
10
10
  org.openhab.core.model.sitemap.sitemap.impl.SitemapImpl.alias_method :uid, :name
11
11
 
12
- # Base sitemap builder DSL
12
+ #
13
+ # A sitemap builder allows you to dynamically create openHAB sitemaps at runtime.
14
+ #
15
+ # @example
16
+ # sitemaps.build do
17
+ # sitemap "demo", label: "My home automation" do
18
+ # frame label: "Date" do
19
+ # text item: Date
20
+ # end
21
+ # frame label: "Demo" do
22
+ # switch item: Lights, icon: "light"
23
+ # text item: LR_Temperature, label: "Livingroom [%.1f °C]"
24
+ # group item: Heating
25
+ # text item: LR_Multimedia_Summary, label: "Multimedia [%s]", static_icon: "video" do
26
+ # selection item: LR_TV_Channel,
27
+ # mappings: { 0 => "off", 1 => "DasErste", 2 => "BBC One", 3 => "Cartoon Network" }
28
+ # slider item: LR_TV_Volume
29
+ # end
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ # @see https://www.openhab.org/docs/ui/sitemaps.html
35
+ # @see OpenHAB::DSL.sitemaps
36
+ # @see OpenHAB::Core::Sitemaps::Provider#build sitemaps.build
37
+ #
13
38
  class Builder
14
39
  # @!visibility private
15
40
  def initialize(provider, builder_proxy, update:)
@@ -698,88 +723,92 @@ module OpenHAB
698
723
  end
699
724
  end
700
725
 
701
- # Builds a `Buttongrid` element
702
- # @since openHAB 4.1
703
- # @see https://www.openhab.org/docs/ui/sitemaps.html#element-type-buttongrid
704
- # @see org.openhab.core.model.sitemap.sitemap.Buttongrid
705
- class ButtongridBuilder < WidgetBuilder
706
- # @return [Array<Array<int, int, Command, String, String>>]
707
- # An array of buttons to display
708
- attr_reader :buttons
726
+ # Builds a `Button` element
727
+ #
728
+ # This element can only exist within a `Buttongrid` element.
729
+ #
730
+ # @since openHAB 4.2
731
+ # @see https://www.openhab.org/docs/ui/sitemaps.html#element-type-button
732
+ # @see org.openhab.core.model.sitemap.sitemap.Button
733
+ class ButtonBuilder < WidgetBuilder
734
+ # The row in which the button is placed
735
+ # @return [Integer]
736
+ attr_accessor :row
737
+
738
+ # The column in which the button is placed
739
+ # @return [Integer]
740
+ attr_accessor :column
741
+
742
+ # The command to send when the button is pressed
743
+ # @return [String, Command]
744
+ attr_accessor :click
745
+
746
+ # The command to send when the button is released
747
+ # @return [String, Command, nil]
748
+ attr_accessor :release
749
+
750
+ # Whether the button is stateless
751
+ # @return [true, false, nil]
752
+ attr_writer :stateless
709
753
 
710
754
  # (see WidgetBuilder#initialize)
711
- # @!method initialize(item: nil, label: nil, icon: nil, static_icon: nil, buttons: nil, label_color: nil, value_color: nil, icon_color: nil, visibility: nil)
712
- # @param [Array<Array<int, int, Command, String, String>>] buttons An array of buttons to display.
713
- # Each element is an array with the following elements:
714
- # - row: 1-12
715
- # - column: 1-12
716
- # - command: The command to send when the button is pressed
717
- # - label: The label to display on the button
718
- # - icon: The icon to display on the button (optional)
719
- #
720
- # @example
721
- # # This creates a buttongrid to emulate a TV remote control
722
- # sitemaps.build do
723
- # sitemap "remote", label: "TV Remote Control" do
724
- # buttongrid item: LivingRoom_TV_RCButton, buttons: [
725
- # [1, 1, "BACK", "Back", "f7:return"],
726
- # [1, 2, "HOME", "Menu", "material:apps"],
727
- # [1, 3, "YELLOW", "Search", "f7:search"],
728
- # [2, 2, "UP", "Up", "f7:arrowtriangle_up"],
729
- # [4, 2, "DOWN", "Down", "f7:arrowtriangle_down"],
730
- # [3, 1, "LEFT", "Left", "f7:arrowtriangle_left"],
731
- # [3, 3, "RIGHT", "Right", "f7:arrowtriangle_right"],
732
- # [3, 2, "ENTER", "Enter", "material:adjust"]
733
- # ]
734
- # end
735
- # end
755
+ # @!method initialize(item: nil, label: nil, icon: nil, static_icon: nil, row:, column:, click:, release: nil, stateless: nil, label_color: nil, value_color: nil, icon_color: nil, visibility: nil)
756
+ # @param [Integer] row
757
+ # The row in which the button is placed (see {ButtonBuilder#row})
758
+ # @param [Integer] column
759
+ # The column in which the button is placed (see {ButtonBuilder#column})
760
+ # @param [String, Command] click
761
+ # The command to send when the button is pressed (see {ButtonBuilder#click})
762
+ # @param [String, Command, nil] release
763
+ # The command to send when the button is released (see {ButtonBuilder#release})
764
+ # @param [true, false, nil] stateless
765
+ # Whether the button is stateless (see {ButtonBuilder#stateless=})
736
766
  #
737
- # @see https://www.openhab.org/docs/ui/sitemaps.html#element-type-buttongrid
738
767
  # @!visibility private
739
- def initialize(type, builder_proxy, buttons: [], **kwargs, &block)
740
- @buttons = buttons
741
- super(type, builder_proxy, **kwargs, &block)
742
- buttons.each { |button| validate_button(button) }
768
+ def initialize(builder_proxy,
769
+ row:,
770
+ column:,
771
+ click:,
772
+ release: nil,
773
+ stateless: nil,
774
+ **kwargs,
775
+ &block)
776
+
777
+ super(:button, builder_proxy, **kwargs, &block)
778
+
779
+ @row = row
780
+ @column = column
781
+ @click = click
782
+ @release = release
783
+ @stateless = stateless
743
784
  end
744
785
 
745
- #
746
- # Adds a button to the buttongrid
747
- #
748
- # @param [Array<int, int, Command, String, String>] button the button to add
749
- # @return [Array<Array<int, int, Command, String, String>>] the current buttons
750
- #
751
- def button(button)
752
- validate_button(button)
753
- @buttons << button
786
+ # (see #stateless=)
787
+ def stateless?
788
+ @stateless
754
789
  end
755
790
 
756
791
  # @!visibility private
757
792
  def build
758
- widget = super
759
- buttons.each do |button|
760
- button_object = if SitemapBuilder.factory.respond_to?(:create_button_definition)
761
- SitemapBuilder.factory.create_button_definition
762
- else
763
- # @deprecated OH 4.1 in OH 4.2 this clause is not needed
764
- SitemapBuilder.factory.create_button
765
- end
766
- button_object.row = button[0]
767
- button_object.column = button[1]
768
- button_object.cmd = button[2]
769
- button_object.label = button[3]
770
- button_object.icon = button[4] if button[4]
771
- widget.buttons.add(button_object)
793
+ if Core.version >= Core::V4_2
794
+ super.tap do |widget|
795
+ widget.row = row
796
+ widget.column = column
797
+ widget.cmd = click.to_s
798
+ widget.release_cmd = release.to_s unless release.nil?
799
+ widget.stateless = stateless? unless @stateless.nil?
800
+ end
801
+ else
802
+ # @deprecated OH 4.1
803
+ # in OH 4.1, the button is a property of the Buttongrid, not a widget
804
+ SitemapBuilder.factory.create_button.tap do |button|
805
+ button.row = row
806
+ button.column = column
807
+ button.cmd = click.to_s
808
+ button.label = label
809
+ button.icon = icon if icon
810
+ end
772
811
  end
773
-
774
- widget
775
- end
776
-
777
- private
778
-
779
- def validate_button(button)
780
- return if (4..5).cover?(button.size)
781
-
782
- raise ArgumentError, "Invalid button: '#{button.inspect}'. It must be an array with (4..5) elements"
783
812
  end
784
813
  end
785
814
 
@@ -1169,6 +1198,209 @@ module OpenHAB
1169
1198
  class FrameBuilder < LinkableWidgetBuilder
1170
1199
  end
1171
1200
 
1201
+ # Builds a `Buttongrid` element
1202
+ # @since openHAB 4.1
1203
+ # @see https://www.openhab.org/docs/ui/sitemaps.html#element-type-buttongrid
1204
+ # @see org.openhab.core.model.sitemap.sitemap.Buttongrid
1205
+ class ButtongridBuilder < LinkableWidgetBuilder
1206
+ REQUIRED_BUTTON_ARGS = %i[row column click].freeze
1207
+ private_constant :REQUIRED_BUTTON_ARGS
1208
+
1209
+ # @!deprecated OH 4.1 in OH 4.1, Buttongrid is not a LinkableWidget.
1210
+ # Pretend that the buttons property is its children so we can add to it in LinkableWidgetBuilder#build
1211
+ if (Core::V4_1...Core::V4_2).cover?(Core.version)
1212
+ java_import org.openhab.core.model.sitemap.sitemap.Buttongrid
1213
+ module Buttongrid
1214
+ def children
1215
+ buttons
1216
+ end
1217
+ end
1218
+ end
1219
+
1220
+ # (see WidgetBuilder#initialize)
1221
+ # @!method initialize(item: nil, label: nil, icon: nil, static_icon: nil, buttons: nil, label_color: nil, value_color: nil, icon_color: nil, visibility: nil)
1222
+ # @param [Array<Array<int, int, Command, String, String>>] buttons An array of buttons to display.
1223
+ # Each element can be a hash with keyword arguments (see {Sitemaps::ButtongridBuilder#button}),
1224
+ # or an array with the following elements:
1225
+ # - row: 1-12
1226
+ # - column: 1-12
1227
+ # - click: The command to send when the button is pressed
1228
+ # - label: The label to display on the button (optional)
1229
+ # - icon: The icon to display on the button (optional)
1230
+ #
1231
+ # @example Create a buttongrid with buttons as an argument
1232
+ # # This creates a buttongrid to emulate a TV remote control
1233
+ # sitemaps.build do
1234
+ # sitemap "remote", label: "TV Remote Control" do
1235
+ # buttongrid item: LivingRoom_TV_RCButton, buttons: [
1236
+ # [1, 1, "BACK", "Back", "f7:return"],
1237
+ # [1, 2, "HOME", "Menu", "material:apps"],
1238
+ # [1, 3, "YELLOW", "Search", "f7:search"],
1239
+ # [2, 2, "UP", "Up", "f7:arrowtriangle_up"],
1240
+ # [4, 2, "DOWN", "Down", "f7:arrowtriangle_down"],
1241
+ # [3, 1, "LEFT", "Left", "f7:arrowtriangle_left"],
1242
+ # [3, 3, "RIGHT", "Right", "f7:arrowtriangle_right"],
1243
+ #
1244
+ # # Using keyword arguments:
1245
+ # {row: 3, column: 2, click: "ENTER", label: "Enter", icon: "material:adjust" }
1246
+ # ]
1247
+ # end
1248
+ # end
1249
+ #
1250
+ # @example Create a buttongrid with button widgets
1251
+ # sitemaps.build do
1252
+ # sitemap "remote", label: "TV Remote Control" do
1253
+ # buttongrid item: LivingRoom_TV_RCButton do
1254
+ # button 1, 1, click: "BACK", icon: "f7:return"
1255
+ # button 1, 2, click: "HOME", icon: "material:apps"
1256
+ # button 1, 3, click: "YELLOW", icon: "f7:search"
1257
+ # button 2, 2, click: "UP", icon: "f7:arrowtriangle_up"
1258
+ # button 4, 2, click: "DOWN", icon: "f7:arrowtriangle_down"
1259
+ # button 3, 1, click: "LEFT", icon: "f7:arrowtriangle_left"
1260
+ # button 3, 3, click: "RIGHT", icon: "f7:arrowtriangle_right"
1261
+ # button 3, 2, click: "ENTER", icon: "material:adjust"
1262
+ # end
1263
+ #
1264
+ # # The following buttons use widget features introduced in openHAB 4.2+
1265
+ # buttongrid item: LivingRoom_Curtain do
1266
+ # button 1, 1, click: "up", release: "stop", icon: "f7:arrowtriangle_up"
1267
+ # button 2, 1, click: "down", release: "stop", icon: "f7:arrowtriangle_up"
1268
+ # end
1269
+ # end
1270
+ # end
1271
+ #
1272
+ # @see https://www.openhab.org/docs/ui/sitemaps.html#element-type-buttongrid
1273
+ # @!visibility private
1274
+ def initialize(type, builder_proxy, buttons: [], **kwargs, &block)
1275
+ super(type, builder_proxy, **kwargs, &block)
1276
+
1277
+ # Put the buttons given in the constructor before those added in the block
1278
+ # We can't do this before calling the super constructor because `children` is initialized there
1279
+ children.slice!(0..).then do |buttons_from_block|
1280
+ buttons.each do |b|
1281
+ if b.is_a?(Array)
1282
+ button(*b)
1283
+ else
1284
+ button(**b)
1285
+ end
1286
+ end
1287
+ children.concat(buttons_from_block)
1288
+ end
1289
+ end
1290
+
1291
+ #
1292
+ # @!method button(row = nil, column = nil, click = nil, label = nil, icon = nil, item: nil, label: nil, icon: nil, static_icon: nil, row:, column:, click:, release: nil, stateless: nil, label_color: nil, value_color: nil, icon_color: nil, visibility: nil)
1293
+ # Adds a button inside the buttongrid
1294
+ #
1295
+ # - In openHAB 4.1, buttons are direct properties of the buttongrid.
1296
+ # Only `row`, `column`, `click`, `label` (optional), and `icon` (optional) are used.
1297
+ # All the other parameters are ignored.
1298
+ # All the buttons will send commands to the same item assigned to the buttongrid.
1299
+ #
1300
+ # - In openHAB 4.2+, buttons are widgets within the containing buttongrid, and they
1301
+ # support all the parameters listed in the method signature such as
1302
+ # `release`, `label_color`, `visibility`, etc.
1303
+ # Each Button element has an item associated with that button.
1304
+ # When an item is not specified for the button, it will default to the containing buttongrid's item.
1305
+ #
1306
+ # This method supports positional arguments and/or keyword arguments.
1307
+ # Their use can be mixed, however, the keyword arguments will override the positional arguments
1308
+ # when both are specified.
1309
+ #
1310
+ # @param (see ButtonBuilder#initialize)
1311
+ # @return [ButtonBuilder]
1312
+ #
1313
+ # @example Adding buttons to a buttongrid with positional arguments
1314
+ # sitemaps.build do
1315
+ # sitemap "remote" do
1316
+ # buttongrid item: RCButton do
1317
+ # button 1, 1, "BACK", "Back", "f7:return"
1318
+ # button 1, 2, "HOME", "Menu", "material:apps"
1319
+ # button 1, 3, "YELLOW", "Search", "f7:search"
1320
+ # button 2, 2, "UP", "Up", "f7:arrowtriangle_up"
1321
+ # button 4, 2, "DOWN", "Down", "f7:arrowtriangle_down"
1322
+ # button 3, 1, "LEFT", "Left", "f7:arrowtriangle_left"
1323
+ # button 3, 3, "RIGHT", "Right", "f7:arrowtriangle_right"
1324
+ # button 3, 2, "ENTER", "Enter", "material:adjust"
1325
+ # end
1326
+ # end
1327
+ # end
1328
+ #
1329
+ # @example Adding buttons to a buttongrid with keyword arguments
1330
+ # sitemaps.build do
1331
+ # sitemap "remote" do
1332
+ # buttongrid item: RCButton do
1333
+ # # These buttons will use the default item assigned to the buttongrid (RCButton)
1334
+ # button row: 1, column: 1, click: "BACK", icon: "f7:return"
1335
+ # button row: 1, column: 2, click: "HOME", icon: "material:apps"
1336
+ # button row: 1, column: 3, click: "YELLOW", icon: "f7:search"
1337
+ # button row: 2, column: 2, click: "UP", icon: "f7:arrowtriangle_up"
1338
+ # button row: 4, column: 2, click: "DOWN", icon: "f7:arrowtriangle_down"
1339
+ # button row: 3, column: 1, click: "LEFT", icon: "f7:arrowtriangle_left"
1340
+ # button row: 3, column: 3, click: "RIGHT", icon: "f7:arrowtriangle_right"
1341
+ # button row: 3, column: 2, click: "ENTER", icon: "material:adjust"
1342
+ # end
1343
+ # end
1344
+ # end
1345
+ #
1346
+ # @example Mixing positional and keyword arguments
1347
+ # sitemaps.build do
1348
+ # sitemap "remote" do
1349
+ # buttongrid item: RCButton do
1350
+ # button 1, 1, click: "BACK", icon: "f7:return"
1351
+ # button 1, 2, click: "HOME", icon: "material:apps"
1352
+ # button 1, 3, click: "YELLOW", icon: "f7:search"
1353
+ # button 2, 2, click: "UP", icon: "f7:arrowtriangle_up"
1354
+ # button 4, 2, click: "DOWN", icon: "f7:arrowtriangle_down"
1355
+ # button 3, 1, click: "LEFT", icon: "f7:arrowtriangle_left"
1356
+ # button 3, 3, click: "RIGHT", icon: "f7:arrowtriangle_right"
1357
+ # button 3, 2, click: "ENTER", icon: "material:adjust"
1358
+ # end
1359
+ # end
1360
+ # end
1361
+ #
1362
+ # @example openHAB 4.2+ supports assigning different items to buttons, along with additional features
1363
+ # sitemaps.build do
1364
+ # sitemap "remote" do
1365
+ # buttongrid item: RCButton do
1366
+ # button 1, 1, click: "BACK", icon: "f7:return"
1367
+ # button 1, 2, click: "HOME", icon: "material:apps"
1368
+ # button 1, 3, click: "YELLOW", icon: "f7:search", icon_color: "yellow"
1369
+ # button 2, 2, click: "UP", icon: "f7:arrowtriangle_up"
1370
+ # button 4, 2, click: "DOWN", icon: "f7:arrowtriangle_down"
1371
+ # button 3, 1, click: "LEFT", icon: "f7:arrowtriangle_left"
1372
+ # button 3, 3, click: "RIGHT", icon: "f7:arrowtriangle_right"
1373
+ # button 3, 2, click: "ENTER", icon: "material:adjust", icon_color: "red"
1374
+ #
1375
+ # # These buttons will use the specified item, only supported in openHAB 4.2+
1376
+ # button 4, 3, click: ON, static_icon: "switch-off", visibility: "TVPower!=ON", item: TVPower
1377
+ # button 4, 3, click: OFF, static_icon: "switch-on", visibility: "TVPower==ON", item: TVPower
1378
+ # end
1379
+ # end
1380
+ # end
1381
+ #
1382
+ def button(row = nil, column = nil, click = nil, label = nil, icon = nil, **kwargs, &block)
1383
+ args = [row, column, click, label, icon].compact
1384
+
1385
+ args = args.first if args.first.is_a?(Array)
1386
+ kwargs = %i[row column click label icon].zip(args).to_h.compact.merge(kwargs)
1387
+
1388
+ missing_args = (REQUIRED_BUTTON_ARGS - kwargs.keys).compact
1389
+ unless missing_args.empty?
1390
+ args = kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
1391
+ missing_args = missing_args.map(&:to_s).join(", ")
1392
+ raise ArgumentError, "button(#{args}) missing required parameters: #{missing_args}"
1393
+ end
1394
+
1395
+ kwargs[:item] ||= item if item # default to the buttongrid's item
1396
+ kwargs[:label] ||= kwargs[:click].to_s
1397
+
1398
+ ButtonBuilder.new(@builder_proxy, **kwargs, &block).tap do |b|
1399
+ children << b
1400
+ end
1401
+ end
1402
+ end
1403
+
1172
1404
  # Builds a `Sitemap`
1173
1405
  # @see https://www.openhab.org/docs/ui/sitemaps.html
1174
1406
  # @see org.openhab.core.model.sitemap.sitemap.Sitemap
@@ -4,6 +4,6 @@ module OpenHAB
4
4
  module DSL
5
5
  # Version of openHAB helper libraries
6
6
  # @return [String]
7
- VERSION = "5.22.1"
7
+ VERSION = "5.23.0"
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openhab-scripting
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.22.1
4
+ version: 5.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian O'Connell
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-07-06 00:00:00.000000000 Z
13
+ date: 2024-07-22 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -490,7 +490,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
490
490
  - !ruby/object:Gem::Version
491
491
  version: '0'
492
492
  requirements: []
493
- rubygems_version: 3.5.14
493
+ rubygems_version: 3.5.16
494
494
  signing_key:
495
495
  specification_version: 4
496
496
  summary: JRuby Helper Libraries for openHAB Scripting