openhab-scripting 5.5.0 → 5.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openhab/core/actions/audio.rb +5 -2
  3. data/lib/openhab/core/configuration.rb +70 -0
  4. data/lib/openhab/core/emulate_hash.rb +241 -0
  5. data/lib/openhab/core/events/abstract_event.rb +11 -0
  6. data/lib/openhab/core/events/timer_event.rb +48 -0
  7. data/lib/openhab/core/items/group_function.rb +37 -0
  8. data/lib/openhab/core/items/group_item.rb +10 -4
  9. data/lib/openhab/core/items/item.rb +31 -0
  10. data/lib/openhab/core/items/metadata/hash.rb +9 -179
  11. data/lib/openhab/core/items/metadata/namespace_hash.rb +38 -141
  12. data/lib/openhab/core/items/semantics/semantic_tag.rb +5 -0
  13. data/lib/openhab/core/items/semantics/tag_class_methods.rb +9 -0
  14. data/lib/openhab/core/items/semantics.rb +7 -3
  15. data/lib/openhab/core/rules/rule.rb +17 -4
  16. data/lib/openhab/core/things/links/provider.rb +1 -1
  17. data/lib/openhab/core/things/proxy.rb +2 -1
  18. data/lib/openhab/core/things/thing.rb +23 -0
  19. data/lib/openhab/core/types/date_time_type.rb +2 -1
  20. data/lib/openhab/core/types/open_closed_type.rb +2 -1
  21. data/lib/openhab/core/types/string_type.rb +1 -1
  22. data/lib/openhab/core/types/up_down_type.rb +2 -1
  23. data/lib/openhab/core/value_cache.rb +6 -5
  24. data/lib/openhab/core_ext/java/duration.rb +2 -1
  25. data/lib/openhab/core_ext/java/local_time.rb +8 -6
  26. data/lib/openhab/core_ext/java/month_day.rb +2 -1
  27. data/lib/openhab/core_ext/java/period.rb +1 -1
  28. data/lib/openhab/core_ext/ruby/numeric.rb +1 -0
  29. data/lib/openhab/dsl/items/builder.rb +13 -6
  30. data/lib/openhab/dsl/rules/automation_rule.rb +29 -5
  31. data/lib/openhab/dsl/rules/builder.rb +47 -22
  32. data/lib/openhab/dsl/rules/rule_triggers.rb +1 -1
  33. data/lib/openhab/dsl/rules/triggers/conditions/generic.rb +1 -1
  34. data/lib/openhab/dsl/rules/triggers/cron/cron.rb +43 -16
  35. data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +101 -96
  36. data/lib/openhab/dsl/things/builder.rb +8 -6
  37. data/lib/openhab/dsl/thread_local.rb +1 -0
  38. data/lib/openhab/dsl/timer_manager.rb +4 -4
  39. data/lib/openhab/dsl/version.rb +1 -1
  40. data/lib/openhab/dsl.rb +23 -4
  41. data/lib/openhab/osgi.rb +1 -1
  42. data/lib/openhab/rspec/karaf.rb +7 -4
  43. data/lib/openhab/rspec/openhab/core/actions.rb +0 -3
  44. metadata +13 -23
@@ -37,7 +37,8 @@ module OpenHAB
37
37
  #
38
38
  def !
39
39
  return UP if down?
40
- return DOWN if up?
40
+
41
+ DOWN if up?
41
42
  end
42
43
  end
43
44
  end
@@ -28,7 +28,7 @@ module OpenHAB
28
28
  # For a private cache, simply use an instance variable. See
29
29
  # {file:docs/ruby-basics.md#variables Instance Variables}.
30
30
  #
31
- # @note Because every script or UI rule gets it own JRuby engine instance,
31
+ # @note Because every script or UI rule gets its own JRuby engine instance,
32
32
  # you cannot rely on being able to access Ruby objects between them. Only
33
33
  # objects that implement a Java interface that's part of Java or openHAB
34
34
  # Core (such as Hash implements {java.util.Map}, or other basic
@@ -107,10 +107,11 @@ module OpenHAB
107
107
 
108
108
  # @see https://docs.ruby-lang.org/en/master/Hash.html#method-i-assoc Hash#assoc
109
109
  def assoc(key)
110
- [key, fetch(key) do
111
- # return nil directly, without storing a value to the cache
112
- return nil
113
- end]
110
+ [key,
111
+ fetch(key) do
112
+ # return nil directly, without storing a value to the cache
113
+ return nil
114
+ end]
114
115
  end
115
116
 
116
117
  # @see https://docs.ruby-lang.org/en/master/Hash.html#method-i-dig Hash#dig
@@ -64,7 +64,8 @@ module OpenHAB
64
64
  #
65
65
  def coerce(other)
66
66
  return [other.seconds, self] if other.is_a?(Numeric)
67
- return [other.to_i.seconds, self] if other.is_a?(Period)
67
+
68
+ [other.to_i.seconds, self] if other.is_a?(Period)
68
69
  end
69
70
 
70
71
  {
@@ -56,12 +56,14 @@ module OpenHAB
56
56
  return raw_parse(string, formatter) if formatter
57
57
 
58
58
  format = /(am|pm)$/i.match?(string) ? "h[:mm[:ss][.S]][ ]a" : "H[:mm[:ss][.S]]"
59
- java_send(:parse, [java.lang.CharSequence, java.time.format.DateTimeFormatter],
60
- string, java.time.format.DateTimeFormatterBuilder.new
61
- .parse_case_insensitive
62
- .parse_lenient
63
- .append_pattern(format)
64
- .to_formatter(java.util.Locale::ENGLISH))
59
+ java_send(:parse,
60
+ [java.lang.CharSequence, java.time.format.DateTimeFormatter],
61
+ string,
62
+ java.time.format.DateTimeFormatterBuilder.new
63
+ .parse_case_insensitive
64
+ .parse_lenient
65
+ .append_pattern(format)
66
+ .to_formatter(java.util.Locale::ENGLISH))
65
67
  end
66
68
  end
67
69
 
@@ -21,7 +21,8 @@ module OpenHAB
21
21
  #
22
22
  def parse(string)
23
23
  logger.trace("#{self.class}.parse #{string} (#{string.class})")
24
- java_send(:parse, [java.lang.CharSequence, java.time.format.DateTimeFormatter],
24
+ java_send(:parse,
25
+ [java.lang.CharSequence, java.time.format.DateTimeFormatter],
25
26
  string.to_s,
26
27
  java.time.format.DateTimeFormatter.ofPattern("[--]M-d"))
27
28
  end
@@ -47,7 +47,7 @@ module OpenHAB
47
47
  # @return [Array, nil]
48
48
  #
49
49
  def coerce(other)
50
- return [other.seconds, to_i.seconds] if other.is_a?(Numeric)
50
+ [other.seconds, to_i.seconds] if other.is_a?(Numeric)
51
51
  end
52
52
 
53
53
  {
@@ -190,6 +190,7 @@ module OpenHAB
190
190
  # Integer already has #|, so we have to prepend it here
191
191
  ::Integer.prepend(QuantityTypeConversion)
192
192
  ::Numeric.include(Numeric)
193
+ java.math.BigDecimal.include(QuantityTypeConversion)
193
194
  end
194
195
  end
195
196
  end
@@ -230,9 +230,7 @@ module OpenHAB
230
230
  tags.compact.map do |tag|
231
231
  case tag
232
232
  when String then tag
233
- when Symbol then tag.to_s
234
- when old_semantics then tag.java_class.simple_name
235
- when semantics then tag.name
233
+ when Symbol, semantics, old_semantics then tag.to_s
236
234
  else raise ArgumentError, "`#{tag}` must be a subclass of Semantics::Tag, a `Symbol`, or a `String`."
237
235
  end
238
236
  end
@@ -260,6 +258,8 @@ module OpenHAB
260
258
  # @param tags [String, Symbol, Semantics::Tag, Array<String, Symbol, Semantics::Tag>, nil]
261
259
  # Fluent alias for `tag`.
262
260
  # @param autoupdate [true, false, nil] Autoupdate setting (see {ItemBuilder#autoupdate})
261
+ # @param thing [String, Core::Things::Thing, Core::Things::ThingUID, nil]
262
+ # A Thing to be used as the base for the channel
263
263
  # @param channel [String, Core::Things::ChannelUID, nil] Channel to link the item to
264
264
  # @param expire [String] An expiration specification.
265
265
  # @param alexa [String, Symbol, Array<(String, Hash<String, Object>)>, nil]
@@ -270,7 +270,9 @@ module OpenHAB
270
270
  # Homekit metadata (see {ItemBuilder#homekit})
271
271
  # @param metadata [Hash<String, Hash>] Generic metadata (see {ItemBuilder#metadata})
272
272
  # @param state [State] Initial state
273
- def initialize(type, name = nil, label = nil,
273
+ def initialize(type,
274
+ name = nil,
275
+ label = nil,
274
276
  provider:,
275
277
  dimension: nil,
276
278
  unit: nil,
@@ -281,6 +283,7 @@ module OpenHAB
281
283
  tag: nil,
282
284
  tags: nil,
283
285
  autoupdate: nil,
286
+ thing: nil,
284
287
  channel: nil,
285
288
  expire: nil,
286
289
  alexa: nil,
@@ -310,6 +313,7 @@ module OpenHAB
310
313
  @metadata.merge!(metadata) if metadata
311
314
  @autoupdate = autoupdate
312
315
  @channels = []
316
+ @thing = thing
313
317
  @expire = nil
314
318
  if expire
315
319
  expire = Array(expire)
@@ -420,6 +424,7 @@ module OpenHAB
420
424
  # end
421
425
  #
422
426
  def channel(channel, config = {})
427
+ channel = "#{@thing}:#{channel}" if @thing && !channel.include?(":")
423
428
  @channels << [channel, config]
424
429
  end
425
430
 
@@ -569,7 +574,7 @@ module OpenHAB
569
574
  RUBY
570
575
  end
571
576
 
572
- FUNCTION_REGEX = /^([a-z]+)(?:\(([a-z]+)(?:,([a-z]+))*\))?/i.freeze
577
+ FUNCTION_REGEX = /^([a-z]+)(?:\((.*)\))?/i.freeze
573
578
  private_constant :FUNCTION_REGEX
574
579
 
575
580
  # The combiner function for this group
@@ -607,11 +612,13 @@ module OpenHAB
607
612
  def create_item
608
613
  base_item = super if type
609
614
  if function
615
+ require "csv"
616
+
610
617
  match = function.match(FUNCTION_REGEX)
611
618
 
612
619
  dto = org.openhab.core.items.dto.GroupFunctionDTO.new
613
620
  dto.name = match[1]
614
- dto.params = match[2..]
621
+ dto.params = CSV.parse_line(match[2]) if match[2]
615
622
  function = org.openhab.core.items.dto.ItemDTOMapper.map_function(base_item, dto)
616
623
  Core::Items::GroupItem.new(name, base_item, function)
617
624
  else
@@ -74,6 +74,10 @@ module OpenHAB
74
74
 
75
75
  # This method gets called in rspec's SuspendRules as well
76
76
  def execute!(mod, inputs)
77
+ # Store the context in a thread variable. It is accessed through DSL#method_missing
78
+ # which is triggered when the context variable is referenced inside the run block.
79
+ # It is added to @thread_locals so it is also available in #process_task below.
80
+ @thread_locals[:openhab_context] = extract_context(inputs)
77
81
  ThreadLocal.thread_local(**@thread_locals) do
78
82
  if logger.trace?
79
83
  logger.trace("Execute called with mod (#{mod&.to_string}) and inputs (#{inputs.inspect})")
@@ -84,10 +88,12 @@ module OpenHAB
84
88
  @debouncer.call { process_queue(create_queue(event), mod, event) }
85
89
  end
86
90
  rescue Exception => e
87
- raise if defined?(::RSpec) && ::RSpec.current_example.example_group.propagate_exceptions?
91
+ raise if defined?(::RSpec) && ::RSpec.current_example&.example_group&.propagate_exceptions?
88
92
 
89
93
  @run_context.send(:logger).log_exception(e)
90
94
  end
95
+ ensure
96
+ @thread_locals.delete(:openhab_context)
91
97
  end
92
98
 
93
99
  def cleanup
@@ -157,6 +163,24 @@ module OpenHAB
157
163
  struct_class.new(**inputs)
158
164
  end
159
165
 
166
+ #
167
+ # Converts inputs into context hash
168
+ # @return [Hash] Context hash.
169
+ #
170
+ def extract_context(inputs)
171
+ return unless inputs
172
+
173
+ inputs.reject { |key, _| key.include?(".") }
174
+ .to_h do |key, value|
175
+ [key.to_sym,
176
+ if value.is_a?(Item) && !value.is_a?(Core::Items::Proxy)
177
+ Core::Items::Proxy.new(value)
178
+ else
179
+ value
180
+ end]
181
+ end
182
+ end
183
+
160
184
  #
161
185
  # Get the trigger_id for the trigger that caused the rule creation
162
186
  #
@@ -230,7 +254,7 @@ module OpenHAB
230
254
  ThreadLocal.thread_local(**@thread_locals) do
231
255
  case task
232
256
  when BuilderDSL::Run then process_run_task(event, task)
233
- when BuilderDSL::Script then process_script_task(task)
257
+ when BuilderDSL::Script then process_script_task(event, task)
234
258
  when BuilderDSL::Trigger then process_trigger_task(event, task)
235
259
  when BuilderDSL::Otherwise then process_otherwise_task(event, task)
236
260
  end
@@ -294,9 +318,9 @@ module OpenHAB
294
318
  #
295
319
  # @param [Script] task to execute
296
320
  #
297
- def process_script_task(task)
298
- logger.trace { "Executing script '#{name}' run block" }
299
- @run_context.instance_exec(&task.block)
321
+ def process_script_task(event, task)
322
+ logger.trace { "Executing script '#{name}' run block with event(#{event})" }
323
+ @run_context.instance_exec(event, &task.block)
300
324
  end
301
325
 
302
326
  #
@@ -70,7 +70,8 @@ module OpenHAB
70
70
  builder = BuilderDSL.new(binding || block.binding)
71
71
  builder.uid(id)
72
72
  builder.instance_exec(builder, &block)
73
- builder.guard = Guard.new(run_context: builder.caller, only_if: builder.only_if,
73
+ builder.guard = Guard.new(run_context: builder.caller,
74
+ only_if: builder.only_if,
74
75
  not_if: builder.not_if)
75
76
 
76
77
  name ||= NameInference.infer_rule_name(builder)
@@ -1075,28 +1076,50 @@ module OpenHAB
1075
1076
  #
1076
1077
  # @overload cron(second: nil, minute: nil, hour: nil, dom: nil, month: nil, dow: nil, year: nil, attach: nil)
1077
1078
  # The trigger can be created by specifying each field as keyword arguments.
1078
- # Omitted fields will default to `*` or `?` as appropriate.
1079
+ #
1080
+ # When certain fields were omitted:
1081
+ # - The more specific fields will default to `0` for `hour`, `minute`, and `second`,
1082
+ # to `MON` for `dow`, and to `1` for `dom` and `month`.
1083
+ # - The less specific fields will default to `*` or `?` as appropriate.
1079
1084
  #
1080
1085
  # Each field is optional, but at least one must be specified.
1081
1086
  #
1082
1087
  # The same rules for the standard
1083
1088
  # [cron expression](https://www.quartz-scheduler.org/documentation/quartz-2.2.2/tutorials/tutorial-lesson-06.html)
1084
1089
  # apply for each field. For example, multiple values can be separated
1085
- # with a comma within a string.
1086
- #
1087
- # @param [Integer, String, nil] second
1088
- # @param [Integer, String, nil] minute
1089
- # @param [Integer, String, nil] hour
1090
- # @param [Integer, String, nil] dom
1091
- # @param [Integer, String, nil] month
1092
- # @param [Integer, String, nil] dow
1093
- # @param [Integer, String, nil] year
1090
+ # with a comma within a string, and ranges can be specified with a dash or with
1091
+ # a Ruby Range.
1092
+ #
1093
+ # @param [Integer, String, Range, nil] second
1094
+ # @param [Integer, String, Range, nil] minute
1095
+ # @param [Integer, String, Range, nil] hour
1096
+ # @param [Integer, String, Symbol, Range, nil] dom
1097
+ # @param [Integer, String, Symbol, Range, nil] month
1098
+ # @param [Integer, String, Symbol, Range, nil] dow
1099
+ # @param [Integer, String, Range, nil] year
1094
1100
  # @param [Object] attach object to be attached to the trigger
1095
- # @example
1101
+ #
1102
+ # @example Using String values
1096
1103
  # # Run every 3 minutes on Monday to Friday
1097
1104
  # # equivalent to the cron expression "0 */3 * ? * MON-FRI *"
1098
1105
  # rule "Using cron fields" do
1099
- # cron second: 0, minute: "*/3", dow: "MON-FRI"
1106
+ # cron minute: "*/3", dow: "MON-FRI"
1107
+ # run { logger.info "Cron rule executed" }
1108
+ # end
1109
+ #
1110
+ # @example Defaults for unspecified fields
1111
+ # # Run at midnight on the first day of January, February, and March
1112
+ # # equivalent to the cron expression "0 0 0 1 JAN-MAR ? *"
1113
+ # rule "Using cron fields" do
1114
+ # cron month: "JAN-MAR"
1115
+ # run { logger.info "Cron rule executed" }
1116
+ # end
1117
+ #
1118
+ # @example Using Ruby Range values
1119
+ # # Run on the hour, every hour between 1pm and 5pm
1120
+ # # equivalent to the cron expression "0 0 13-17 ? * ? *"
1121
+ # rule "Using cron fields" do
1122
+ # cron hour: 13..17
1100
1123
  # run { logger.info "Cron rule executed" }
1101
1124
  # end
1102
1125
  #
@@ -1112,7 +1135,6 @@ module OpenHAB
1112
1135
 
1113
1136
  raise ArgumentError, "Missing cron expression or elements" unless expression
1114
1137
 
1115
- add_tag("Schedule")
1116
1138
  cron = Cron.new(rule_triggers: @rule_triggers)
1117
1139
  cron.trigger(config: { "cronExpression" => expression }, attach: attach)
1118
1140
  end
@@ -1211,7 +1233,6 @@ module OpenHAB
1211
1233
  if value == :day && at.is_a?(Item)
1212
1234
  raise ArgumentError, "Attachments are not supported with dynamic datetime triggers" unless attach.nil?
1213
1235
 
1214
- add_tag("Schedule")
1215
1236
  return trigger("timer.DateTimeTrigger", itemName: at.name, timeOnly: true)
1216
1237
  end
1217
1238
 
@@ -1561,8 +1582,12 @@ module OpenHAB
1561
1582
  # @deprecated OH3.4 - OH3 config uses eventXXX vs OH4 uses `topic`, `source`, and `types`
1562
1583
  # See https://github.com/openhab/openhab-core/pull/3299
1563
1584
  trigger("core.GenericEventTrigger",
1564
- eventTopic: topic, eventSource: source, eventTypes: types, # @deprecated OH3.4
1565
- topic: topic, source: source, types: types,
1585
+ eventTopic: topic,
1586
+ eventSource: source,
1587
+ eventTypes: types, # @deprecated OH3.4
1588
+ topic: topic,
1589
+ source: source,
1590
+ types: types,
1566
1591
  attach: attach)
1567
1592
  end
1568
1593
 
@@ -1593,7 +1618,6 @@ module OpenHAB
1593
1618
  #
1594
1619
  def at(item)
1595
1620
  item = item.name if item.is_a?(Item)
1596
- add_tag("Schedule")
1597
1621
  trigger("timer.DateTimeTrigger", itemName: item.to_s)
1598
1622
  end
1599
1623
 
@@ -1834,10 +1858,11 @@ module OpenHAB
1834
1858
  types = [binding.local_variable_get(:for)].flatten
1835
1859
 
1836
1860
  WatchHandler::WatchTriggerHandlerFactory.instance # ensure it's registered
1837
- trigger(WatchHandler::WATCH_TRIGGER_MODULE_ID, path: path.to_s,
1838
- types: types.map(&:to_s),
1839
- glob: glob.to_s,
1840
- attach: attach)
1861
+ trigger(WatchHandler::WATCH_TRIGGER_MODULE_ID,
1862
+ path: path.to_s,
1863
+ types: types.map(&:to_s),
1864
+ glob: glob.to_s,
1865
+ attach: attach)
1841
1866
  end
1842
1867
 
1843
1868
  # @!endgroup
@@ -64,7 +64,7 @@ module OpenHAB
64
64
  org.openhab.core.automation.util.TriggerBuilder.create
65
65
  .with_id(uuid)
66
66
  .with_type_uid(type)
67
- .with_configuration(org.openhab.core.config.core.Configuration.new(config))
67
+ .with_configuration(Core::Configuration.new(config))
68
68
  .build
69
69
  end
70
70
 
@@ -29,7 +29,7 @@ module OpenHAB
29
29
  # @param [Hash] inputs inputs from trigger
30
30
  # @return [true, false] if the conditions passed (and therefore the block was run)
31
31
  #
32
- def process(mod:, inputs:) # rubocop:disable Lint/UnusedMethodArgument - mod is unused here but required
32
+ def process(mod:, inputs:)
33
33
  logger.trace("Checking #{inputs} against condition trigger #{self}")
34
34
  unless check_value(Conditions.old_state_from(inputs), @from) &&
35
35
  check_value(Conditions.new_state_from(inputs), @to) &&
@@ -12,7 +12,12 @@ module OpenHAB
12
12
  #
13
13
  class Cron < Trigger
14
14
  # Trigger ID for Cron Triggers
15
- CRON_TRIGGER_MODULE_ID = "jsr223.jruby.CronTrigger"
15
+ CRON_TRIGGER_MODULE_ID = if Gem::Version.new(OpenHAB::Core::VERSION) >= Gem::Version.new("4.0.0")
16
+ "timer.GenericCronTrigger"
17
+ else
18
+ # @deprecated OH3.4 We need to use a custom CronTrigger handler
19
+ "jsr223.jruby.CronTrigger"
20
+ end
16
21
 
17
22
  #
18
23
  # Returns a default map for cron expressions that fires every second
@@ -21,16 +26,15 @@ module OpenHAB
21
26
  # @return [Hash] Map with symbols for :seconds, :minute, :hour, :dom, :month, :dow
22
27
  # configured to fire every second
23
28
  #
24
- CRON_EXPRESSION_MAP =
25
- {
26
- second: "*",
27
- minute: "*",
28
- hour: "*",
29
- dom: "?",
30
- month: "*",
31
- dow: "?",
32
- year: "*"
33
- }.freeze
29
+ CRON_EXPRESSION_MAP = {
30
+ second: "*",
31
+ minute: "*",
32
+ hour: "*",
33
+ dom: "?",
34
+ month: "*",
35
+ dow: "?",
36
+ year: "*"
37
+ }.freeze
34
38
  private_constant :CRON_EXPRESSION_MAP
35
39
 
36
40
  # @return [Hash] Map of days of the week from symbols to to openHAB cron strings
@@ -47,7 +51,6 @@ module OpenHAB
47
51
 
48
52
  # @return [Hash] Converts the DAY_OF_WEEK_MAP to map used by Cron Expression
49
53
  DAY_OF_WEEK_EXPRESSION_MAP = DAY_OF_WEEK_MAP.transform_values { |v| CRON_EXPRESSION_MAP.merge(dow: v) }
50
-
51
54
  private_constant :DAY_OF_WEEK_EXPRESSION_MAP
52
55
 
53
56
  # @return [Hash] Create a set of cron expressions based on different time intervals
@@ -60,9 +63,12 @@ module OpenHAB
60
63
  month: CRON_EXPRESSION_MAP.merge(second: "0", minute: "0", hour: "0", dom: "1"),
61
64
  year: CRON_EXPRESSION_MAP.merge(second: "0", minute: "0", hour: "0", dom: "1", month: "1")
62
65
  }.merge(DAY_OF_WEEK_EXPRESSION_MAP).freeze
63
-
64
66
  private_constant :EXPRESSION_MAP
65
67
 
68
+ # @return [Hash] Translate cron field names to expression keys
69
+ FIELD_TO_EXPRESSION_KEY = Hash.new { |_, key| key }.merge({ dow: :week, dom: :day })
70
+ private_constant :FIELD_TO_EXPRESSION_KEY
71
+
66
72
  #
67
73
  # Create a cron map from a duration
68
74
  #
@@ -119,10 +125,28 @@ module OpenHAB
119
125
  "unknown keyword#{"s" if extra_fields.size > 1}: #{extra_fields.map(&:inspect).join(", ")}"
120
126
  end
121
127
 
122
- fields = fields.transform_values { |value| value.to_s.delete(" ") }
128
+ fields = fields.to_h do |key, value|
129
+ if value.is_a?(Range)
130
+ if value.exclude_end?
131
+ raise ArgumentError,
132
+ "Range must be inclusive for '#{key}'. Try '#{value.begin}..#{value.end}' instead"
133
+ end
134
+
135
+ unless value.begin && value.end
136
+ raise ArgumentError,
137
+ "Range must have a beginning and ending for '#{key}'"
138
+ end
139
+
140
+ [key, "#{value.begin.to_s.upcase}-#{value.end.to_s.upcase}".delete(" ")]
141
+ else
142
+ [key, value.to_s.delete(" ").upcase]
143
+ end
144
+ end
145
+ # convert fields' key dow->week, dom->day to look into EXPRESSION_MAP
146
+ fields_expression = fields.transform_keys { |key| FIELD_TO_EXPRESSION_KEY[key] }
123
147
  # find the first expression map that has a field from fields.
124
148
  # this ensure more-specific fields get set to 0, not *
125
- base_key = EXPRESSION_MAP.keys.find { |field, _| fields.key?(field) }
149
+ base_key = EXPRESSION_MAP.keys.find { |field, _| fields_expression.key?(field) }
126
150
  base_expression = EXPRESSION_MAP[base_key]
127
151
  expression_map = base_expression.merge(fields)
128
152
 
@@ -186,7 +210,10 @@ module OpenHAB
186
210
  #
187
211
  #
188
212
  def trigger(config:, attach:)
189
- CronHandler::CronTriggerHandlerFactory.instance # ensure it's registered
213
+ # @deprecated OH3.4 needs a custom CronTriggerHandlerFactory
214
+ if Gem::Version.new(OpenHAB::Core::VERSION) < Gem::Version.new("4.0.0")
215
+ CronHandler::CronTriggerHandlerFactory.instance # ensure it's registered
216
+ end
190
217
  append_trigger(type: CRON_TRIGGER_MODULE_ID, config: config, attach: attach)
191
218
  end
192
219
  end