openhab-jrubyscripting 5.0.0.rc10 → 5.0.0.rc12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) 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/date_time_item.rb +3 -2
  22. data/lib/openhab/core/items/generic_item.rb +92 -1
  23. data/lib/openhab/core/items/item.rb +9 -8
  24. data/lib/openhab/core/items/metadata/hash.rb +1 -1
  25. data/lib/openhab/core/items/metadata/namespace_hash.rb +10 -2
  26. data/lib/openhab/core/items/metadata/provider.rb +2 -2
  27. data/lib/openhab/core/items/persistence.rb +99 -21
  28. data/lib/openhab/core/items/player_item.rb +1 -1
  29. data/lib/openhab/core/items/proxy.rb +20 -14
  30. data/lib/openhab/core/items/registry.rb +12 -1
  31. data/lib/openhab/core/items/state_storage.rb +2 -2
  32. data/lib/openhab/core/items.rb +3 -3
  33. data/lib/openhab/core/profile_factory.rb +3 -1
  34. data/lib/openhab/core/proxy.rb +130 -0
  35. data/lib/openhab/core/registry.rb +12 -2
  36. data/lib/openhab/core/rules.rb +1 -1
  37. data/lib/openhab/core/things/links/provider.rb +39 -1
  38. data/lib/openhab/core/things/proxy.rb +8 -0
  39. data/lib/openhab/core/things/registry.rb +4 -0
  40. data/lib/openhab/core/timer.rb +3 -19
  41. data/lib/openhab/core/types/date_time_type.rb +3 -2
  42. data/lib/openhab/core/types/decimal_type.rb +1 -1
  43. data/lib/openhab/core/types/un_def_type.rb +2 -2
  44. data/lib/openhab/core/value_cache.rb +1 -1
  45. data/lib/openhab/core.rb +3 -3
  46. data/lib/openhab/core_ext/ephemeris.rb +53 -0
  47. data/lib/openhab/core_ext/java/class.rb +1 -1
  48. data/lib/openhab/core_ext/java/duration.rb +27 -1
  49. data/lib/openhab/core_ext/java/local_date.rb +17 -7
  50. data/lib/openhab/core_ext/java/local_time.rb +13 -3
  51. data/lib/openhab/core_ext/java/month.rb +1 -1
  52. data/lib/openhab/core_ext/java/month_day.rb +15 -3
  53. data/lib/openhab/core_ext/java/period.rb +1 -1
  54. data/lib/openhab/core_ext/java/temporal_amount.rb +1 -1
  55. data/lib/openhab/core_ext/java/time.rb +5 -1
  56. data/lib/openhab/core_ext/java/zoned_date_time.rb +100 -2
  57. data/lib/openhab/core_ext/ruby/date.rb +4 -2
  58. data/lib/openhab/core_ext/ruby/date_time.rb +1 -0
  59. data/lib/openhab/core_ext/ruby/numeric.rb +6 -1
  60. data/lib/openhab/core_ext/ruby/time.rb +1 -0
  61. data/lib/openhab/dsl/debouncer.rb +259 -0
  62. data/lib/openhab/dsl/items/builder.rb +29 -14
  63. data/lib/openhab/dsl/items/timed_command.rb +31 -13
  64. data/lib/openhab/dsl/rules/automation_rule.rb +30 -44
  65. data/lib/openhab/dsl/rules/builder.rb +404 -39
  66. data/lib/openhab/dsl/rules/guard.rb +12 -54
  67. data/lib/openhab/dsl/rules/name_inference.rb +11 -0
  68. data/lib/openhab/dsl/rules/property.rb +3 -4
  69. data/lib/openhab/dsl/rules/terse.rb +4 -1
  70. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +5 -6
  71. data/lib/openhab/dsl/rules/triggers/cron/cron.rb +1 -0
  72. data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +19 -31
  73. data/lib/openhab/dsl/rules/triggers/watch/watch.rb +1 -0
  74. data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +22 -30
  75. data/lib/openhab/dsl/things/builder.rb +1 -1
  76. data/lib/openhab/dsl/thread_local.rb +1 -0
  77. data/lib/openhab/dsl/version.rb +1 -1
  78. data/lib/openhab/dsl.rb +251 -14
  79. data/lib/openhab/rspec/helpers.rb +3 -2
  80. data/lib/openhab/rspec/hooks.rb +6 -2
  81. data/lib/openhab/rspec/karaf.rb +7 -0
  82. data/lib/openhab/rspec/mocks/instance_method_stasher.rb +22 -0
  83. data/lib/openhab/rspec/mocks/space.rb +23 -0
  84. data/lib/openhab/rspec/mocks/timer.rb +33 -0
  85. data/lib/openhab/rspec/openhab/core/actions.rb +16 -4
  86. data/lib/openhab/rspec/openhab/core/items/proxy.rb +1 -13
  87. data/lib/openhab/rspec/suspend_rules.rb +1 -14
  88. data/lib/openhab/rspec.rb +9 -0
  89. data/lib/openhab/yard/base_helper.rb +19 -0
  90. data/lib/openhab/yard/code_objects/group_object.rb +9 -3
  91. data/lib/openhab/yard/coderay.rb +17 -0
  92. data/lib/openhab/yard/handlers/jruby/base.rb +10 -1
  93. data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +3 -0
  94. data/lib/openhab/yard/html_helper.rb +49 -15
  95. data/lib/openhab/yard/markdown_helper.rb +135 -0
  96. data/lib/openhab/yard.rb +6 -0
  97. metadata +36 -4
@@ -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
 
@@ -355,7 +358,7 @@ module OpenHAB
355
358
  #
356
359
  # Set the rule's tags.
357
360
  #
358
- # @param [String, Class, Array<String, Class>] tags
361
+ # @param [String, Symbol, Semantics::Tag] tags A list of tags to assign to the rule.
359
362
  # @return [void]
360
363
  #
361
364
  # @example
@@ -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
  #
@@ -899,7 +1101,10 @@ module OpenHAB
899
1101
  # :saturday,
900
1102
  # :sunday] value
901
1103
  # When to execute rule.
902
- # @param [LocalTime, String, nil] at What time of day to execute rule
1104
+ # @param [LocalTime, String, Core::Items::DateTimeItem, nil] at What time of day to execute rule
1105
+ # If `value` is `:day`, `at` can be a {Core::Items::DateTimeItem DateTimeItem}, and
1106
+ # the trigger will run every day at the (time only portion of) current state of the
1107
+ # item. If the item is {NULL} or {UNDEF}, the trigger will not run.
903
1108
  # @param [Object] attach Object to be attached to the trigger
904
1109
  # @return [void]
905
1110
  #
@@ -955,11 +1160,23 @@ module OpenHAB
955
1160
  # run { logger.info "Happy Valentine's Day!" }
956
1161
  # end
957
1162
  #
1163
+ # @example
1164
+ # rule "Every day at sunset" do
1165
+ # every :day, at: Sunset_Time
1166
+ # run { logger.info "It's getting dark" }
1167
+ # end
1168
+ #
958
1169
  def every(value, at: nil, attach: nil)
959
1170
  return every(java.time.MonthDay.parse(value), at: at, attach: attach) if value.is_a?(String)
960
1171
 
961
1172
  @ruby_triggers << [:every, value, { at: at }]
962
1173
 
1174
+ if value == :day && at.is_a?(Item)
1175
+ raise ArgumentError, "Attachments are not supported with dynamic datetime triggers" unless attach.nil?
1176
+
1177
+ return trigger("timer.DateTimeTrigger", itemName: at.name, timeOnly: true)
1178
+ end
1179
+
963
1180
  cron_expression = case value
964
1181
  when Symbol then Cron.from_symbol(value, at)
965
1182
  when Duration then Cron.from_duration(value, at)
@@ -976,6 +1193,8 @@ module OpenHAB
976
1193
  # and on subsequent reloads on file modifications.
977
1194
  # This is useful to perform initialization routines, especially when combined with other triggers.
978
1195
  #
1196
+ # @param [Duration] delay The amount of time to wait before executing the rule.
1197
+ # When nil, execute immediately.
979
1198
  # @param [Object] attach Object to be attached to the trigger
980
1199
  # @return [void]
981
1200
  #
@@ -993,12 +1212,12 @@ module OpenHAB
993
1212
  # run { Security_Lights.on }
994
1213
  # end
995
1214
  #
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
1215
+ def on_load(delay: nil, attach: nil)
1216
+ # prevent overwriting @on_load
1217
+ raise ArgumentError, "on_load can only be used once within a rule" if @on_load
999
1218
 
1000
- @on_load_id = SecureRandom.uuid
1001
- attachments[@on_load_id] = attach
1219
+ @on_load = { module: SecureRandom.uuid, delay: delay }
1220
+ attachments[@on_load[:module]] = attach
1002
1221
  end
1003
1222
 
1004
1223
  #
@@ -1165,6 +1384,61 @@ module OpenHAB
1165
1384
  end
1166
1385
  end
1167
1386
 
1387
+ #
1388
+ # Creates an item added trigger
1389
+ #
1390
+ # @param [Object] attach object to be attached to the trigger
1391
+ # @return [void]
1392
+ #
1393
+ # @example
1394
+ # rule "item added" do
1395
+ # item_added
1396
+ # run do |event|
1397
+ # logger.info("#{event.item.name} added.")
1398
+ # end
1399
+ # end
1400
+ def item_added(attach: nil)
1401
+ @ruby_triggers << [:item_added]
1402
+ event("openhab/items/*/added", types: "ItemAddedEvent", attach: attach)
1403
+ end
1404
+
1405
+ #
1406
+ # Creates an item removed trigger
1407
+ #
1408
+ # @param [Object] attach object to be attached to the trigger
1409
+ # @return [void]
1410
+ #
1411
+ # @example
1412
+ # rule "item removed" do
1413
+ # item_removed
1414
+ # run do |event|
1415
+ # logger.info("#{event.item.name} removed.")
1416
+ # end
1417
+ # end
1418
+ def item_removed(attach: nil)
1419
+ @ruby_triggers << [:item_removed]
1420
+ event("openhab/items/*/removed", types: "ItemRemovedEvent", attach: attach)
1421
+ end
1422
+
1423
+ #
1424
+ # Creates an item updated trigger
1425
+ #
1426
+ # @param [Object] attach object to be attached to the trigger
1427
+ # @return [void]
1428
+ #
1429
+ # @example
1430
+ # rule "item updated" do
1431
+ # item_updated
1432
+ # run do |event|
1433
+ # logger.info("#{event.item.name} updated.")
1434
+ # end
1435
+ # end
1436
+ #
1437
+ def item_updated(attach: nil)
1438
+ @ruby_triggers << [:item_updated]
1439
+ event("openhab/items/*/updated", types: "ItemUpdatedEvent", attach: attach)
1440
+ end
1441
+
1168
1442
  #
1169
1443
  # Creates a thing added trigger
1170
1444
  #
@@ -1180,8 +1454,7 @@ module OpenHAB
1180
1454
  # end
1181
1455
  def thing_added(attach: nil)
1182
1456
  @ruby_triggers << [:thing_added]
1183
- trigger("core.GenericEventTrigger", eventTopic: "openhab/things/*/added",
1184
- eventTypes: "ThingAddedEvent", attach: attach)
1457
+ event("openhab/things/*/added", types: "ThingAddedEvent", attach: attach)
1185
1458
  end
1186
1459
 
1187
1460
  #
@@ -1199,8 +1472,7 @@ module OpenHAB
1199
1472
  # end
1200
1473
  def thing_removed(attach: nil)
1201
1474
  @ruby_triggers << [:thing_removed]
1202
- trigger("core.GenericEventTrigger", eventTopic: "openhab/things/*/removed",
1203
- eventTypes: "ThingRemovedEvent", attach: attach)
1475
+ event("openhab/things/*/removed", types: "ThingRemovedEvent", attach: attach)
1204
1476
  end
1205
1477
 
1206
1478
  #
@@ -1210,17 +1482,69 @@ module OpenHAB
1210
1482
  # @return [void]
1211
1483
  #
1212
1484
  # @example
1213
- # rule "thing updated" do
1214
- # thing_updated
1215
- # run do |event|
1216
- # logger.info("#{event.thing.uid} updated.")
1217
- # end
1218
- # end
1485
+ # rule "thing updated" do
1486
+ # thing_updated
1487
+ # run do |event|
1488
+ # logger.info("#{event.thing.uid} updated.")
1489
+ # end
1490
+ # end
1219
1491
  #
1220
1492
  def thing_updated(attach: nil)
1221
- @ruby_triggers << [:thing_removed]
1222
- trigger("core.GenericEventTrigger", eventTopic: "openhab/things/*/updated",
1223
- eventTypes: "ThingUpdatedEvent", attach: attach)
1493
+ @ruby_triggers << [:thing_updated]
1494
+ event("openhab/things/*/updated", types: "ThingUpdatedEvent", attach: attach)
1495
+ end
1496
+
1497
+ #
1498
+ # Creates a trigger on events coming through the event bus
1499
+ #
1500
+ # @param [String] topic The topic to trigger on; can contain the wildcard `*`.
1501
+ # @param [String, nil] source The sender of the event to trigger on.
1502
+ # Default does not filter on source.
1503
+ # @param [String, Array<String>, nil] types Only subscribe to certain event types.
1504
+ # Default does not filter on event types.
1505
+ # @return [void]
1506
+ #
1507
+ # @example
1508
+ # rule "thing updated" do
1509
+ # event("openhab/things/*/updated", types: "ThingUpdatedEvent")
1510
+ # run do |event|
1511
+ # logger.info("#{event.thing.uid} updated")
1512
+ # end
1513
+ # end
1514
+ #
1515
+ def event(topic, source: nil, types: nil, attach: nil)
1516
+ types = types.join(",") if types.is_a?(Enumerable)
1517
+ trigger("core.GenericEventTrigger", eventTopic: topic, eventSource: source, eventTypes: types, attach: attach)
1518
+ end
1519
+
1520
+ #
1521
+ # Creates a trigger based on the time stored in a {DateTimeItem}
1522
+ #
1523
+ # The trigger will dynamically update any time the state of the item
1524
+ # changes. If the item is {NULL} or {UNDEF}, the trigger will not run.
1525
+ #
1526
+ # @param [Item, String, Symbol] item The item (or it's name)
1527
+ # @return [void]
1528
+ #
1529
+ # @example
1530
+ # rule "say hello when the kids get home from school" do
1531
+ # at HomeFromSchool_Time
1532
+ # run do
1533
+ # KitchenEcho_TTS << "hi kids! how was school?"
1534
+ # end
1535
+ # end
1536
+ #
1537
+ # rule "set home from school time" do
1538
+ # on_load
1539
+ # every :day, at: "5:00am" do
1540
+ # run do
1541
+ # HomeFromSchool_Time.ensure.update(school_day? ? LocalTime.parse("3:30pm") : NULL)
1542
+ # end
1543
+ # end
1544
+ #
1545
+ def at(item)
1546
+ item = item.name if item.is_a?(Item)
1547
+ trigger("timer.DateTimeTrigger", itemName: item.to_s)
1224
1548
  end
1225
1549
 
1226
1550
  #
@@ -1445,7 +1769,7 @@ module OpenHAB
1445
1769
  #<OpenHAB::DSL::Rules::Builder: #{uid}
1446
1770
  triggers=#{triggers.inspect},
1447
1771
  run blocks=#{run.inspect},
1448
- on_load=#{!@on_load_id.nil?},
1772
+ on_load=#{!@on_load.nil?},
1449
1773
  Trigger Conditions=#{trigger_conditions.inspect},
1450
1774
  Trigger UIDs=#{triggers.map(&:id).inspect},
1451
1775
  Attachments=#{attachments.inspect}
@@ -1468,12 +1792,26 @@ module OpenHAB
1468
1792
  added_rule.actions.first.configuration.put("type", "application/x-ruby")
1469
1793
  added_rule.actions.first.configuration.put("script", script) if script
1470
1794
 
1471
- rule.execute(nil, { "module" => @on_load_id }) if @on_load_id
1795
+ process_on_load { |module_id| rule.execute(nil, { "module" => module_id }) }
1796
+
1472
1797
  added_rule
1473
1798
  end
1474
1799
 
1475
1800
  private
1476
1801
 
1802
+ # Calls the on_load block, with a delay if specified
1803
+ # @yield block to execute on load time
1804
+ # @yieldparam [String] module The module ID that identifies this on_load event
1805
+ def process_on_load
1806
+ return unless @on_load
1807
+
1808
+ if @on_load[:delay]
1809
+ after(@on_load[:delay]) { yield @on_load[:module] }
1810
+ else
1811
+ yield @on_load[:module]
1812
+ end
1813
+ end
1814
+
1477
1815
  # delegate to the caller's logger
1478
1816
  def logger
1479
1817
  @caller.send(:logger)
@@ -1505,7 +1843,7 @@ module OpenHAB
1505
1843
  # @return [true,false] True if rule has triggers, false otherwise
1506
1844
  #
1507
1845
  def triggers?
1508
- @on_load_id || !triggers.empty?
1846
+ !(@on_load.nil? && triggers.empty?)
1509
1847
  end
1510
1848
 
1511
1849
  #
@@ -1534,6 +1872,33 @@ module OpenHAB
1534
1872
  provider.add(unmanaged_rule)
1535
1873
  unmanaged_rule
1536
1874
  end
1875
+
1876
+ #
1877
+ # Prevents or delays executions of rules to within a specified interval.
1878
+ # Debounce handling is done after the from/to/command types are filtered, but before only_if/not_if
1879
+ # guards.
1880
+ #
1881
+ # For a more detailed timing diagram, see {Debouncer}.
1882
+ #
1883
+ # Note the trailing edge debouncer delays the triggers so that they were postponed for the given interval after
1884
+ # the first detection of the trigger.
1885
+ #
1886
+ # @param (see Debouncer#initialize)
1887
+ #
1888
+ # @return [void]
1889
+ #
1890
+ # @see Debouncer Debouncer class
1891
+ # @see OpenHAB::DSL.debounce DSL.debounce method
1892
+ # @see only_every
1893
+ #
1894
+ # @!visibility private
1895
+ def debounce(for: 1.second, leading: false, idle_time: nil)
1896
+ raise ArgumentError, "Debounce guard can only be specified once" if @debounce_settings
1897
+
1898
+ interval = binding.local_variable_get(:for)
1899
+ # This hash structure must match the parameter signature for Debouncer.new
1900
+ @debounce_settings = { for: interval, leading: leading, idle_time: idle_time }
1901
+ end
1537
1902
  end
1538
1903
  end
1539
1904
  end