openhab-scripting 5.22.0 → 5.23.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 195fdc136c01213d162a6e82a75003c2c52102ffe68e8eaf48fe96d39517d6b0
4
- data.tar.gz: bae1950261063adc7b741158a6a4c16cd4a390de63717f3a391d140734a659f9
3
+ metadata.gz: 55ff8734004f7befde1f1d3bb27560cfc8c5dbb7287f8dbb5e19a50fadec0d23
4
+ data.tar.gz: dba9276e8b4845e1b265973440aa0e036da3137176ba44c5c256ddfcd5dad227
5
5
  SHA512:
6
- metadata.gz: e9d2406ccc8a7140e5de11207aac0e6341dfe4c37cf397fa3e43e026a6b84a63b1b1030c0ea7982ac84de3be8a087aca8c4b990c759058c9cbbf5bb579071226
7
- data.tar.gz: 0f317d5d7d1594466752ae47b2f6f0285974c7d159c423ad3d95f8a5519809cdbb08059da89273307b6bfc449dce4ec840dcdfb848f315ce524e4a35f412d9dc
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
data/lib/openhab/core.rb CHANGED
@@ -15,9 +15,10 @@ module OpenHAB
15
15
  V4_2 = Gem::Version.new("4.2.0").freeze
16
16
 
17
17
  # @return [Gem::Version] Returns the current openHAB version as a Gem::Version object
18
+ # Note, this strips off snapshots, milestones and RC versions and returns the release version.
18
19
  # @!visibility private
19
20
  def self.version
20
- @version ||= Gem::Version.new(VERSION).freeze
21
+ @version ||= Gem::Version.new(VERSION).release.freeze
21
22
  end
22
23
 
23
24
  raise "`openhab-scripting` requires openHAB >= 3.4.0" unless version >= Gem::Version.new("3.4.0")
@@ -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.0"
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.0
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-05 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