openhab-jrubyscripting 5.0.0.rc9 → 5.0.0.rc11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openhab/core/actions/audio.rb +47 -0
  3. data/lib/openhab/core/actions/ephemeris.rb +39 -0
  4. data/lib/openhab/core/actions/exec.rb +51 -0
  5. data/lib/openhab/core/actions/http.rb +80 -0
  6. data/lib/openhab/core/actions/ping.rb +30 -0
  7. data/lib/openhab/core/actions/transformation.rb +32 -0
  8. data/lib/openhab/core/actions/voice.rb +36 -0
  9. data/lib/openhab/core/actions.rb +23 -120
  10. data/lib/openhab/core/{events → dto}/item_channel_link.rb +1 -4
  11. data/lib/openhab/core/{events → dto}/thing.rb +10 -12
  12. data/lib/openhab/core/dto.rb +11 -0
  13. data/lib/openhab/core/entity_lookup.rb +1 -1
  14. data/lib/openhab/core/events/abstract_event.rb +1 -0
  15. data/lib/openhab/core/events/abstract_item_registry_event.rb +36 -0
  16. data/lib/openhab/core/events/abstract_thing_registry_event.rb +40 -0
  17. data/lib/openhab/core/events/item_command_event.rb +1 -1
  18. data/lib/openhab/core/events/item_state_changed_event.rb +6 -6
  19. data/lib/openhab/core/events/item_state_event.rb +6 -6
  20. data/lib/openhab/core/events/thing_status_info_event.rb +8 -6
  21. data/lib/openhab/core/items/generic_item.rb +2 -1
  22. data/lib/openhab/core/items/persistence.rb +52 -18
  23. data/lib/openhab/core/items/player_item.rb +1 -1
  24. data/lib/openhab/core/items/proxy.rb +20 -14
  25. data/lib/openhab/core/items/registry.rb +2 -0
  26. data/lib/openhab/core/items.rb +3 -3
  27. data/lib/openhab/core/profile_factory.rb +3 -1
  28. data/lib/openhab/core/proxy.rb +125 -0
  29. data/lib/openhab/core/things/links/provider.rb +1 -1
  30. data/lib/openhab/core/things/proxy.rb +8 -0
  31. data/lib/openhab/core/types/date_time_type.rb +2 -1
  32. data/lib/openhab/core/types/decimal_type.rb +1 -1
  33. data/lib/openhab/core/types/un_def_type.rb +2 -2
  34. data/lib/openhab/core/value_cache.rb +1 -1
  35. data/lib/openhab/core_ext/ephemeris.rb +53 -0
  36. data/lib/openhab/core_ext/java/class.rb +1 -1
  37. data/lib/openhab/core_ext/java/duration.rb +25 -1
  38. data/lib/openhab/core_ext/java/local_date.rb +2 -0
  39. data/lib/openhab/core_ext/java/month_day.rb +2 -0
  40. data/lib/openhab/core_ext/java/zoned_date_time.rb +85 -0
  41. data/lib/openhab/core_ext/ruby/date.rb +2 -0
  42. data/lib/openhab/core_ext/ruby/date_time.rb +1 -0
  43. data/lib/openhab/core_ext/ruby/time.rb +1 -0
  44. data/lib/openhab/dsl/debouncer.rb +259 -0
  45. data/lib/openhab/dsl/items/builder.rb +4 -2
  46. data/lib/openhab/dsl/items/timed_command.rb +31 -13
  47. data/lib/openhab/dsl/rules/automation_rule.rb +28 -21
  48. data/lib/openhab/dsl/rules/builder.rb +357 -37
  49. data/lib/openhab/dsl/rules/guard.rb +12 -54
  50. data/lib/openhab/dsl/rules/name_inference.rb +11 -0
  51. data/lib/openhab/dsl/rules/property.rb +3 -4
  52. data/lib/openhab/dsl/rules/terse.rb +4 -1
  53. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +5 -6
  54. data/lib/openhab/dsl/rules/triggers/cron/cron.rb +1 -0
  55. data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +19 -31
  56. data/lib/openhab/dsl/rules/triggers/watch/watch.rb +1 -0
  57. data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +22 -30
  58. data/lib/openhab/dsl/things/builder.rb +1 -1
  59. data/lib/openhab/dsl/thread_local.rb +1 -0
  60. data/lib/openhab/dsl/version.rb +1 -1
  61. data/lib/openhab/dsl.rb +224 -3
  62. data/lib/openhab/rspec/hooks.rb +5 -2
  63. data/lib/openhab/rspec/karaf.rb +7 -0
  64. data/lib/openhab/rspec/mocks/instance_method_stasher.rb +22 -0
  65. data/lib/openhab/rspec/mocks/space.rb +23 -0
  66. data/lib/openhab/rspec/openhab/core/actions.rb +16 -4
  67. data/lib/openhab/rspec/openhab/core/items/proxy.rb +1 -13
  68. data/lib/openhab/rspec/suspend_rules.rb +1 -14
  69. data/lib/openhab/yard/base_helper.rb +19 -0
  70. data/lib/openhab/yard/code_objects/group_object.rb +9 -3
  71. data/lib/openhab/yard/coderay.rb +17 -0
  72. data/lib/openhab/yard/handlers/jruby/base.rb +10 -1
  73. data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +3 -0
  74. data/lib/openhab/yard/html_helper.rb +49 -15
  75. data/lib/openhab/yard/markdown_helper.rb +135 -0
  76. data/lib/openhab/yard.rb +6 -0
  77. metadata +36 -4
@@ -30,8 +30,8 @@ module OpenHAB
30
30
  # @param [Config] config Rule configuration
31
31
  #
32
32
  # Constructor sets a number of variables, no further decomposition necessary
33
+ #
33
34
  def initialize(config)
34
- # Metrics disabled because only setters are called or defaults set.
35
35
  super()
36
36
  set_name(config.name)
37
37
  set_description(config.description)
@@ -50,6 +50,8 @@ module OpenHAB
50
50
  @thread_locals = ThreadLocal.persist
51
51
  @cleanup_hooks = Set.new
52
52
  @listener = nil
53
+ debounce_settings = config.debounce_settings || { for: nil }
54
+ @debouncer = Debouncer.new(**debounce_settings)
53
55
  end
54
56
 
55
57
  #
@@ -58,30 +60,34 @@ module OpenHAB
58
60
  # @param [java.util.Map] mod map provided by openHAB rules engine
59
61
  # @param [java.util.Map] inputs map provided by openHAB rules engine containing event and other information
60
62
  #
61
- #
62
63
  def execute(mod = nil, inputs = nil)
64
+ execute!(mod, inputs)
65
+ end
66
+
67
+ # @!visibility private
68
+ def on_removal(listener)
69
+ @cleanup_hooks << listener
70
+ listen_for_removal unless @listener
71
+ end
72
+
73
+ private
74
+
75
+ # This method gets called in rspec's SuspendRules as well
76
+ def execute!(mod, inputs)
63
77
  ThreadLocal.thread_local(**@thread_locals) do
64
78
  logger.trace { "Execute called with mod (#{mod&.to_string}) and inputs (#{inputs.inspect})" }
65
79
  logger.trace { "Event details #{inputs["event"].inspect}" } if inputs&.key?("event")
66
80
  trigger_conditions(inputs).process(mod: mod, inputs: inputs) do
67
81
  event = extract_event(inputs)
68
- process_queue(create_queue(event), mod, event)
82
+ @debouncer.call { process_queue(create_queue(event), mod, event) }
69
83
  end
70
84
  rescue Exception => e
71
- raise if defined?(::RSpec)
85
+ raise if defined?(::RSpec) && ::RSpec.current_example.example_group.propagate_exceptions?
72
86
 
73
87
  @run_context.send(:logger).log_exception(e)
74
88
  end
75
89
  end
76
90
 
77
- # @!visibility private
78
- def on_removal(listener)
79
- @cleanup_hooks << listener
80
- listen_for_removal unless @listener
81
- end
82
-
83
- private
84
-
85
91
  def cleanup
86
92
  @cleanup_hooks.each(&:cleanup)
87
93
  end
@@ -180,17 +186,18 @@ module OpenHAB
180
186
  def check_guards(event:)
181
187
  return true if @guard.nil?
182
188
 
183
- if @guard.should_run? event
184
- return true if @between.nil?
185
-
189
+ unless @between.nil?
186
190
  now = Time.now
187
- return true if @between.cover? now
188
-
189
- logger.trace("Skipped execution of rule '#{name}' because the current time #{now} " \
190
- "is not between #{@between.begin} and #{@between.end}")
191
- else
192
- logger.trace("Skipped execution of rule '#{name}' because of guard #{@guard}")
191
+ unless @between.cover?(now)
192
+ logger.trace("Skipped execution of rule '#{name}' because the current time #{now} " \
193
+ "is not between #{@between.begin} and #{@between.end}")
194
+ return false
195
+ end
193
196
  end
197
+
198
+ return true if @guard.should_run?(event)
199
+
200
+ logger.trace("Skipped execution of rule '#{name}' because of guard #{@guard}")
194
201
  false
195
202
  end
196
203
 
@@ -139,6 +139,9 @@ module OpenHAB
139
139
  # @return [Array] Of trigger definitions as passed in Ruby
140
140
  attr_reader :ruby_triggers
141
141
 
142
+ # @!visibility private
143
+ attr_reader :debounce_settings
144
+
142
145
  # @!visibility private
143
146
  Run = Struct.new(:block)
144
147
 
@@ -380,6 +383,30 @@ module OpenHAB
380
383
  #
381
384
  prop :enabled
382
385
 
386
+ #
387
+ # Returns all {Item Items} (or {GroupItem::Members GroupItem::Members}) referenced
388
+ # by the specified trigger types in this rule.
389
+ #
390
+ # @param [Symbol, Array<Symbol>] trigger_types Trigger types to search for dependencies
391
+ # @return [Array<Item, GroupItem::Members>]
392
+ #
393
+ # @example Ensure all dependencies have a state when executing a rule
394
+ # rule do |rule|
395
+ # changed Item1, Item2, Item3
396
+ # only_if { rule.dependencies.all?(&:state?) }
397
+ # run { FormulaItem.update(Item3.state - (Item1.state + Item2.state)) }
398
+ # end
399
+ #
400
+ def dependencies(trigger_types = %i[changed updated])
401
+ trigger_types = Array.wrap(trigger_types)
402
+
403
+ ruby_triggers.flat_map do |t|
404
+ next [] unless trigger_types.include?(t.first)
405
+
406
+ t[1].select { |i| i.is_a?(Item) || i.is_a?(GroupItem::Members) }
407
+ end.uniq
408
+ end
409
+
383
410
  # @!group Guards
384
411
  # Guards exist to only permit rules to run if certain conditions are
385
412
  # satisfied. Think of these as declarative `if` statements that keep
@@ -388,8 +415,8 @@ module OpenHAB
388
415
  #
389
416
  # ### Guard Combination
390
417
  #
391
- # {#only_if} and {#not_if} can be used on the same rule. Both must be
392
- # satisfied for a rule to execute.
418
+ # Multiple guards can be used on the same rule. All must be satisfied
419
+ # for a rule to execute.
393
420
  #
394
421
  # @example
395
422
  # rule "Set OutsideDimmer to 50% if LightSwitch turned on and OtherSwitch is OFF and Door is CLOSED" do
@@ -403,7 +430,7 @@ module OpenHAB
403
430
  #
404
431
  # @!method between(range)
405
432
  #
406
- # Only execute rule if current time is between supplied time ranges.
433
+ # Only execute rule if the current time is between the supplied time ranges.
407
434
  #
408
435
  # If the range is of strings, it will be parsed to an appropriate time class.
409
436
  #
@@ -464,7 +491,7 @@ module OpenHAB
464
491
  #
465
492
  # @!method only_if
466
493
  #
467
- # {only_if} allows rule execution when the block's is true and prevents it when it's false.
494
+ # Allows rule execution when the block's result is true and prevents it when it's false.
468
495
  #
469
496
  # @yieldparam [Core::Events::AbstractEvent] event The event data that is about to trigger the rule.
470
497
  # @yieldreturn [Boolean] A value indicating if the rule should run.
@@ -493,15 +520,13 @@ module OpenHAB
493
520
  # end
494
521
  #
495
522
  prop_array(:only_if) do |item|
496
- unless item.is_a?(Proc) || [item].flatten.all? { |it| it.respond_to?(:truthy?) }
497
- raise ArgumentError, "Object passed to only_if must be a proc"
498
- end
523
+ raise ArgumentError, "Object passed to only_if must be a proc" unless item.is_a?(Proc)
499
524
  end
500
525
 
501
526
  #
502
527
  # @!method not_if
503
528
  #
504
- # {not_if} prevents execution of rules when the block's result is true and allows it when it's true.
529
+ # Prevents execution of rules when the block's result is true and allows it when it's true.
505
530
  #
506
531
  # @yieldparam [Core::Events::AbstractEvent] event The event data that is about to trigger the rule.
507
532
  # @yieldreturn [Boolean] A value indicating if the rule should _not_ run.
@@ -523,9 +548,187 @@ module OpenHAB
523
548
  # end
524
549
  #
525
550
  prop_array(:not_if) do |item|
526
- unless item.is_a?(Proc) || [item].flatten.all? { |it| it.respond_to?(:truthy?) }
527
- raise ArgumentError, "Object passed to not_if must be a proc"
528
- end
551
+ raise ArgumentError, "Object passed to not_if must be a proc" unless item.is_a?(Proc)
552
+ end
553
+
554
+ # rubocop:disable Layout/LineLength
555
+
556
+ #
557
+ # Waits until triggers have stopped firing for a period of time before executing the rule.
558
+ #
559
+ # It ignores triggers that are "bouncing around" (rapidly firing) by ignoring
560
+ # them until they have quiesced (stopped triggering for a while).
561
+ #
562
+ # ## Comparison Table
563
+ # | Guard | Triggers Immediately | Description |
564
+ # | -------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
565
+ # | {debounce_for} | No | Waits until there is a minimum interval between triggers. |
566
+ # | {throttle_for} | No | Rate-limits the executions to a minimum interval, regardless of the interval between triggers. Waits until the end of the period before executing, ignores any leading triggers. |
567
+ # | {only_every} | Yes | Rate-limits the executions to a minimum interval. Immediately executes the first trigger, then ignores subsequent triggers for the period. |
568
+ #
569
+ # ## Timing Diagram
570
+ # The following timing diagram illustrates the difference between {debounce_for},
571
+ # {throttle_for}, and {only_every} guards:
572
+ #
573
+ # ```
574
+ # TIME INDEX ===> 1 1 2 2 3 3 4 4
575
+ # 0 5 0 5 0 5 0 5 0 5
576
+ # Triggers : "X.X...X...X..XX.X.X....X.XXXXXXXXXXX....X....."
577
+ # debounce_for 5 : "|......................X.|..............X....."
578
+ # debounce_for 5..5 : "|....X|....X.|....X....|....X|....X|....X....X"
579
+ # debounce_for 5..6 : "|.....X...|.....X.|....X.|.....X|.....X.|....X"
580
+ # debounce_for 5..7 : "|......X..|......X|....X.|......X|......X....."
581
+ # debounce_for 5..8 : "|.......X.|.......X....|.......X|.......X....."
582
+ # debounce_for 5..20: "|...................X..|................X....."
583
+ # # throttle_for will fire every 5 intervals after the "first" trigger
584
+ # throttle_for 5 : "|....X|....X.|....X....|....X|....X|....X....."
585
+ # only_every 5 : "X.....X......X....X....X....X....X......X....."
586
+ #
587
+ # Triggers : "X.X...X...X..XX.X.X..X...XXXXXXXXXXX.X..X.X..."
588
+ # debounce_for 5..44: "|...........................................X."
589
+ #
590
+ # # Notice above, triggers keep firing with intervals less than 5, so
591
+ # # debouncer keeps waiting, but puts a stop at 44 (the end of range).
592
+ # ```
593
+ #
594
+ # @param [Duration,Range] debounce_time The minimum interval between two consecutive
595
+ # triggers before the rules are allowed to run.
596
+ #
597
+ # When specified just as a Duration or an endless range, it sets the minimum interval
598
+ # between two consecutive triggers before rules are executed. It will
599
+ # wait endlessly unless this condition is met or an end of range was specified.
600
+ #
601
+ # When the end of the range is specified, it sets the maximum amount of time to wait
602
+ # from the first trigger before the rule will execute, even when triggers continue
603
+ # to occur more frequently than the minimum interval.
604
+ #
605
+ # When an equal beginning and ending values are given, it will behave just like
606
+ # {throttle_for}.
607
+ #
608
+ # @return [void]
609
+ #
610
+ # @see throttle_for
611
+ # @see only_every
612
+ #
613
+ # @example Wait until item stopped changing for at least 1 minute before running the rule
614
+ # rule do
615
+ # changed Item1
616
+ # debounce_for 1.minute
617
+ # run { ... }
618
+ # end
619
+ #
620
+ # @example Alert when door is open for a while
621
+ # # Note: When combined with a state check (only_if), this becomes functionally
622
+ # # equivalent to the changed duration feature.
623
+ # rule "Door alert" do
624
+ # changed Door_State
625
+ # debounce_for 10.minutes
626
+ # only_if { Door_State.open? }
627
+ # run { notify("The Door has been open for 10 minutes!") }
628
+ # end
629
+ #
630
+ def debounce_for(debounce_time)
631
+ idle_time = debounce_time.is_a?(Range) ? debounce_time.begin : debounce_time
632
+ debounce(for: debounce_time, idle_time: idle_time)
633
+ end
634
+ # rubocop:enable Layout/LineLength
635
+
636
+ #
637
+ # Rate-limits rule executions by delaying triggers and executing the last
638
+ # trigger within the given duration.
639
+ #
640
+ # When a new trigger occurs, it will hold the execution and start a fixed timer for
641
+ # the given duration. Should more triggers occur during this time, keep holding
642
+ # and once the wait time is over, execute the latest trigger.
643
+ #
644
+ # {throttle_for} will execute rules after it had waited
645
+ # for the given duration, regardless of how frequently the triggers were occuring.
646
+ # In contrast, {debounce_for} will wait until there is a minimum interval
647
+ # between two triggers.
648
+ #
649
+ # {throttle_for} is ideal in situations where regular status updates need to be made
650
+ # for frequently changing values. It is also useful when a rule responds to triggers
651
+ # from multiple related items that are updated at around the same time. Instead of
652
+ # executing the rule multiple times, {throttle_for} will wait for a pre-set amount
653
+ # of time since the first group of triggers occurred before executing the rule.
654
+ #
655
+ # @param [Duration] duration The minimum amount of time to wait inbetween rule
656
+ # executions.
657
+ #
658
+ # @return [void]
659
+ #
660
+ # @see debounce_for
661
+ # @see only_every
662
+ #
663
+ # @example Perform calculations from multiple items
664
+ # rule "Update Power Summary " do |rule|
665
+ # changed Power_From_Solar, Power_Load, Power_From_Grid
666
+ # throttle_for 1.second
667
+ # only_if { rule.dependencies.all?(&:state?) } # make sure all items have a state
668
+ # run do
669
+ # msg = []
670
+ # msg << Power_Load.state.negate.to_unit("kW").format("Load: %.2f %unit%")
671
+ # msg << Power_From_Solar.state.to_unit("kW").format("PV: %.2f %unit%")
672
+ # if Power_From_Grid.positive?
673
+ # msg << Power_From_Grid.state.to_unit("kW").format("From Grid: %.1f %unit%")
674
+ # else
675
+ # msg << Power_From_Grid.state.negate.to_unit("kW").format("To Grid: %.1f %unit%")
676
+ # end
677
+ # Power_Summary.update(msg.join(", "))
678
+ # end
679
+ # end
680
+ #
681
+ def throttle_for(duration)
682
+ debounce(for: duration)
683
+ end
684
+
685
+ #
686
+ # Executes the rule then ignores subsequent triggers for a given duration.
687
+ #
688
+ # Additional triggers that occur within the given duration after the rule execution
689
+ # will be ignored. This results in executions happening only at the specified interval or
690
+ # more.
691
+ #
692
+ # Unlike {throttle_for}, this guard will execute the rule as soon as a new trigger
693
+ # occurs instead of waiting for the specified duration. This is ideal for triggers
694
+ # such as a door bell where the rule should run as soon as a new trigger is detected
695
+ # but ignore subsequent triggers if they occur too soon after.
696
+ #
697
+ # @param [Duration,:second,:minute,:hour,:day] interval The period during which
698
+ # subsequent triggers are ignored.
699
+ # @return [void]
700
+ #
701
+ # @example Only allow executions every 10 minutes or more
702
+ # rule "Aircon Vent Control" do
703
+ # changed BedRoom_Temperature
704
+ # only_every 10.minutes
705
+ # run do
706
+ # # Adjust BedRoom_Aircon_Vent
707
+ # end
708
+ # end
709
+ #
710
+ # @example Run only on the first update and ignore subsequent triggers for the next minute
711
+ # # They can keep pressing the door bell as often as they like,
712
+ # # but the bell will only ring at most once every minute
713
+ # rule do
714
+ # updated DoorBell_Button, to: "single"
715
+ # only_every 1.minute
716
+ # run { Audio.play_stream "doorbell.mp3" }
717
+ # end
718
+ #
719
+ # @example Using symbolic duration
720
+ # rule "Display update" do
721
+ # updated Power_Usage
722
+ # only_every :minute
723
+ # run { Power_Usage_Display.update "Current power usage: #{Power_Usage.average_since(1.minute.ago)}" }
724
+ # end
725
+ #
726
+ # @see debounce_for
727
+ # @see throttle_for
728
+ #
729
+ def only_every(interval)
730
+ interval = 1.send(interval) if %i[second minute hour day].include?(interval)
731
+ debounce(for: interval, leading: true)
529
732
  end
530
733
 
531
734
  # @!endgroup
@@ -541,7 +744,8 @@ module OpenHAB
541
744
  @rule_triggers = RuleTriggers.new
542
745
  @caller = caller_binding.eval "self"
543
746
  @ruby_triggers = []
544
- @on_load_id = nil
747
+ @on_load = nil
748
+ @debounce_settings = nil
545
749
  enabled(true)
546
750
  tags([])
547
751
  end
@@ -680,8 +884,7 @@ module OpenHAB
680
884
  # end
681
885
  def channel_linked(attach: nil)
682
886
  @ruby_triggers << [:channel_linked]
683
- trigger("core.GenericEventTrigger", eventTopic: "openhab/links/*/added",
684
- eventTypes: "ItemChannelLinkAddedEvent", attach: attach)
887
+ event("openhab/links/*/added", types: "ItemChannelLinkAddedEvent", attach: attach)
685
888
  end
686
889
 
687
890
  #
@@ -702,8 +905,7 @@ module OpenHAB
702
905
  # end
703
906
  def channel_unlinked(attach: nil)
704
907
  @ruby_triggers << [:channel_linked]
705
- trigger("core.GenericEventTrigger", eventTopic: "openhab/links/*/removed",
706
- eventTypes: "ItemChannelLinkRemovedEvent", attach: attach)
908
+ event(topic: "openhab/links/*/removed", types: "ItemChannelLinkRemovedEvent", attach: attach)
707
909
  end
708
910
 
709
911
  #
@@ -976,6 +1178,8 @@ module OpenHAB
976
1178
  # and on subsequent reloads on file modifications.
977
1179
  # This is useful to perform initialization routines, especially when combined with other triggers.
978
1180
  #
1181
+ # @param [Duration] delay The amount of time to wait before executing the rule.
1182
+ # When nil, execute immediately.
979
1183
  # @param [Object] attach Object to be attached to the trigger
980
1184
  # @return [void]
981
1185
  #
@@ -993,12 +1197,12 @@ module OpenHAB
993
1197
  # run { Security_Lights.on }
994
1198
  # end
995
1199
  #
996
- def on_load(attach: nil)
997
- # prevent overwriting @on_load_id
998
- raise ArgumentError, "on_load can only be used once within a rule" if @on_load_id
1200
+ def on_load(delay: nil, attach: nil)
1201
+ # prevent overwriting @on_load
1202
+ raise ArgumentError, "on_load can only be used once within a rule" if @on_load
999
1203
 
1000
- @on_load_id = SecureRandom.uuid
1001
- attachments[@on_load_id] = attach
1204
+ @on_load = { module: SecureRandom.uuid, delay: delay }
1205
+ attachments[@on_load[:module]] = attach
1002
1206
  end
1003
1207
 
1004
1208
  #
@@ -1165,6 +1369,61 @@ module OpenHAB
1165
1369
  end
1166
1370
  end
1167
1371
 
1372
+ #
1373
+ # Creates an item added trigger
1374
+ #
1375
+ # @param [Object] attach object to be attached to the trigger
1376
+ # @return [void]
1377
+ #
1378
+ # @example
1379
+ # rule "item added" do
1380
+ # item_added
1381
+ # run do |event|
1382
+ # logger.info("#{event.item.name} added.")
1383
+ # end
1384
+ # end
1385
+ def item_added(attach: nil)
1386
+ @ruby_triggers << [:item_added]
1387
+ event("openhab/items/*/added", types: "ItemAddedEvent", attach: attach)
1388
+ end
1389
+
1390
+ #
1391
+ # Creates an item removed trigger
1392
+ #
1393
+ # @param [Object] attach object to be attached to the trigger
1394
+ # @return [void]
1395
+ #
1396
+ # @example
1397
+ # rule "item removed" do
1398
+ # item_removed
1399
+ # run do |event|
1400
+ # logger.info("#{event.item.name} removed.")
1401
+ # end
1402
+ # end
1403
+ def item_removed(attach: nil)
1404
+ @ruby_triggers << [:item_removed]
1405
+ event("openhab/items/*/removed", types: "ItemRemovedEvent", attach: attach)
1406
+ end
1407
+
1408
+ #
1409
+ # Creates an item updated trigger
1410
+ #
1411
+ # @param [Object] attach object to be attached to the trigger
1412
+ # @return [void]
1413
+ #
1414
+ # @example
1415
+ # rule "item updated" do
1416
+ # item_updated
1417
+ # run do |event|
1418
+ # logger.info("#{event.item.name} updated.")
1419
+ # end
1420
+ # end
1421
+ #
1422
+ def item_updated(attach: nil)
1423
+ @ruby_triggers << [:item_updated]
1424
+ event("openhab/items/*/updated", types: "ItemUpdatedEvent", attach: attach)
1425
+ end
1426
+
1168
1427
  #
1169
1428
  # Creates a thing added trigger
1170
1429
  #
@@ -1180,8 +1439,7 @@ module OpenHAB
1180
1439
  # end
1181
1440
  def thing_added(attach: nil)
1182
1441
  @ruby_triggers << [:thing_added]
1183
- trigger("core.GenericEventTrigger", eventTopic: "openhab/things/*/added",
1184
- eventTypes: "ThingAddedEvent", attach: attach)
1442
+ event("openhab/things/*/added", types: "ThingAddedEvent", attach: attach)
1185
1443
  end
1186
1444
 
1187
1445
  #
@@ -1199,8 +1457,7 @@ module OpenHAB
1199
1457
  # end
1200
1458
  def thing_removed(attach: nil)
1201
1459
  @ruby_triggers << [:thing_removed]
1202
- trigger("core.GenericEventTrigger", eventTopic: "openhab/things/*/removed",
1203
- eventTypes: "ThingRemovedEvent", attach: attach)
1460
+ event("openhab/things/*/removed", types: "ThingRemovedEvent", attach: attach)
1204
1461
  end
1205
1462
 
1206
1463
  #
@@ -1210,17 +1467,39 @@ module OpenHAB
1210
1467
  # @return [void]
1211
1468
  #
1212
1469
  # @example
1213
- # rule "thing updated" do
1214
- # thing_updated
1215
- # run do |event|
1216
- # logger.info("#{event.thing.uid} updated.")
1217
- # end
1218
- # end
1470
+ # rule "thing updated" do
1471
+ # thing_updated
1472
+ # run do |event|
1473
+ # logger.info("#{event.thing.uid} updated.")
1474
+ # end
1475
+ # end
1219
1476
  #
1220
1477
  def thing_updated(attach: nil)
1221
- @ruby_triggers << [:thing_removed]
1222
- trigger("core.GenericEventTrigger", eventTopic: "openhab/things/*/updated",
1223
- eventTypes: "ThingUpdatedEvent", attach: attach)
1478
+ @ruby_triggers << [:thing_updated]
1479
+ event("openhab/things/*/updated", types: "ThingUpdatedEvent", attach: attach)
1480
+ end
1481
+
1482
+ #
1483
+ # Creates a trigger on events coming through the event bus
1484
+ #
1485
+ # @param [String] topic The topic to trigger on; can contain the wildcard `*`.
1486
+ # @param [String, nil] source The sender of the event to trigger on.
1487
+ # Default does not filter on source.
1488
+ # @param [String, Array<String>, nil] types Only subscribe to certain event types.
1489
+ # Default does not filter on event types.
1490
+ # @return [void]
1491
+ #
1492
+ # @example
1493
+ # rule "thing updated" do
1494
+ # event("openhab/things/*/updated", types: "ThingUpdatedEvent")
1495
+ # run do |event|
1496
+ # logger.info("#{event.thing.uid} updated")
1497
+ # end
1498
+ # end
1499
+ #
1500
+ def event(topic, source: nil, types: nil, attach: nil)
1501
+ types = types.join(",") if types.is_a?(Enumerable)
1502
+ trigger("core.GenericEventTrigger", eventTopic: topic, eventSource: source, eventTypes: types, attach: attach)
1224
1503
  end
1225
1504
 
1226
1505
  #
@@ -1445,7 +1724,7 @@ module OpenHAB
1445
1724
  #<OpenHAB::DSL::Rules::Builder: #{uid}
1446
1725
  triggers=#{triggers.inspect},
1447
1726
  run blocks=#{run.inspect},
1448
- on_load=#{!@on_load_id.nil?},
1727
+ on_load=#{!@on_load.nil?},
1449
1728
  Trigger Conditions=#{trigger_conditions.inspect},
1450
1729
  Trigger UIDs=#{triggers.map(&:id).inspect},
1451
1730
  Attachments=#{attachments.inspect}
@@ -1468,12 +1747,26 @@ module OpenHAB
1468
1747
  added_rule.actions.first.configuration.put("type", "application/x-ruby")
1469
1748
  added_rule.actions.first.configuration.put("script", script) if script
1470
1749
 
1471
- rule.execute(nil, { "module" => @on_load_id }) if @on_load_id
1750
+ process_on_load { |module_id| rule.execute(nil, { "module" => module_id }) }
1751
+
1472
1752
  added_rule
1473
1753
  end
1474
1754
 
1475
1755
  private
1476
1756
 
1757
+ # Calls the on_load block, with a delay if specified
1758
+ # @yield block to execute on load time
1759
+ # @yieldparam [String] module The module ID that identifies this on_load event
1760
+ def process_on_load
1761
+ return unless @on_load
1762
+
1763
+ if @on_load[:delay]
1764
+ after(@on_load[:delay]) { yield @on_load[:module] }
1765
+ else
1766
+ yield @on_load[:module]
1767
+ end
1768
+ end
1769
+
1477
1770
  # delegate to the caller's logger
1478
1771
  def logger
1479
1772
  @caller.send(:logger)
@@ -1505,7 +1798,7 @@ module OpenHAB
1505
1798
  # @return [true,false] True if rule has triggers, false otherwise
1506
1799
  #
1507
1800
  def triggers?
1508
- @on_load_id || !triggers.empty?
1801
+ !(@on_load.nil? && triggers.empty?)
1509
1802
  end
1510
1803
 
1511
1804
  #
@@ -1534,6 +1827,33 @@ module OpenHAB
1534
1827
  provider.add(unmanaged_rule)
1535
1828
  unmanaged_rule
1536
1829
  end
1830
+
1831
+ #
1832
+ # Prevents or delays executions of rules to within a specified interval.
1833
+ # Debounce handling is done after the from/to/command types are filtered, but before only_if/not_if
1834
+ # guards.
1835
+ #
1836
+ # For a more detailed timing diagram, see {Debouncer}.
1837
+ #
1838
+ # Note the trailing edge debouncer delays the triggers so that they were postponed for the given interval after
1839
+ # the first detection of the trigger.
1840
+ #
1841
+ # @param (see Debouncer#initialize)
1842
+ #
1843
+ # @return [void]
1844
+ #
1845
+ # @see Debouncer Debouncer class
1846
+ # @see OpenHAB::DSL.debounce DSL.debounce method
1847
+ # @see only_every
1848
+ #
1849
+ # @!visibility private
1850
+ def debounce(for: 1.second, leading: false, idle_time: nil)
1851
+ raise ArgumentError, "Debounce guard can only be specified once" if @debounce_settings
1852
+
1853
+ interval = binding.local_variable_get(:for)
1854
+ # This hash structure must match the parameter signature for Debouncer.new
1855
+ @debounce_settings = { for: interval, leading: leading, idle_time: idle_time }
1856
+ end
1537
1857
  end
1538
1858
  end
1539
1859
  end