openhab-scripting 2.9.1

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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/workflow.yml +327 -0
  3. data/.gitignore +17 -0
  4. data/.java-version +1 -0
  5. data/.rspec +1 -0
  6. data/.yardopts +1 -0
  7. data/CHANGELOG.md +113 -0
  8. data/Gemfile +28 -0
  9. data/Gemfile.lock +245 -0
  10. data/Guardfile +35 -0
  11. data/LICENSE +277 -0
  12. data/README.md +23 -0
  13. data/Rakefile +406 -0
  14. data/bin/console +15 -0
  15. data/bin/setup +8 -0
  16. data/config/userdata/config/org/openhab/restauth.config +3 -0
  17. data/cucumber.yml +1 -0
  18. data/docs/_config.yml +135 -0
  19. data/docs/contributing/index.md +47 -0
  20. data/docs/examples/conversions.md +123 -0
  21. data/docs/examples/index.md +61 -0
  22. data/docs/index.md +19 -0
  23. data/docs/installation/index.md +26 -0
  24. data/docs/motivation/index.md +27 -0
  25. data/docs/usage/execution.md +9 -0
  26. data/docs/usage/execution/delay.md +48 -0
  27. data/docs/usage/execution/otherwise.md +30 -0
  28. data/docs/usage/execution/run.md +70 -0
  29. data/docs/usage/execution/triggered.md +48 -0
  30. data/docs/usage/guards.md +51 -0
  31. data/docs/usage/guards/between.md +30 -0
  32. data/docs/usage/guards/not_if.md +41 -0
  33. data/docs/usage/guards/only_if.md +40 -0
  34. data/docs/usage/index.md +11 -0
  35. data/docs/usage/items.md +66 -0
  36. data/docs/usage/items/contact.md +84 -0
  37. data/docs/usage/items/dimmer.md +147 -0
  38. data/docs/usage/items/groups.md +76 -0
  39. data/docs/usage/items/number.md +225 -0
  40. data/docs/usage/items/string.md +49 -0
  41. data/docs/usage/items/switch.md +85 -0
  42. data/docs/usage/misc.md +7 -0
  43. data/docs/usage/misc/actions.md +108 -0
  44. data/docs/usage/misc/duration.md +21 -0
  45. data/docs/usage/misc/gems.md +25 -0
  46. data/docs/usage/misc/logging.md +21 -0
  47. data/docs/usage/misc/metadata.md +128 -0
  48. data/docs/usage/misc/store_states.md +42 -0
  49. data/docs/usage/misc/time_of_day.md +69 -0
  50. data/docs/usage/misc/timers.md +67 -0
  51. data/docs/usage/rule.md +43 -0
  52. data/docs/usage/things.md +29 -0
  53. data/docs/usage/triggers.md +8 -0
  54. data/docs/usage/triggers/changed.md +57 -0
  55. data/docs/usage/triggers/channel.md +54 -0
  56. data/docs/usage/triggers/command.md +69 -0
  57. data/docs/usage/triggers/cron.md +19 -0
  58. data/docs/usage/triggers/every.md +76 -0
  59. data/docs/usage/triggers/updated.md +78 -0
  60. data/lib/openhab.rb +39 -0
  61. data/lib/openhab/configuration.rb +16 -0
  62. data/lib/openhab/core/cron.rb +27 -0
  63. data/lib/openhab/core/debug.rb +34 -0
  64. data/lib/openhab/core/dsl.rb +47 -0
  65. data/lib/openhab/core/dsl/actions.rb +107 -0
  66. data/lib/openhab/core/dsl/entities.rb +103 -0
  67. data/lib/openhab/core/dsl/gems.rb +29 -0
  68. data/lib/openhab/core/dsl/group.rb +91 -0
  69. data/lib/openhab/core/dsl/items/items.rb +39 -0
  70. data/lib/openhab/core/dsl/items/number_item.rb +217 -0
  71. data/lib/openhab/core/dsl/items/string_item.rb +102 -0
  72. data/lib/openhab/core/dsl/monkey_patch/actions/actions.rb +4 -0
  73. data/lib/openhab/core/dsl/monkey_patch/actions/script_thing_actions.rb +22 -0
  74. data/lib/openhab/core/dsl/monkey_patch/events.rb +5 -0
  75. data/lib/openhab/core/dsl/monkey_patch/events/item_command.rb +13 -0
  76. data/lib/openhab/core/dsl/monkey_patch/events/item_state_changed.rb +25 -0
  77. data/lib/openhab/core/dsl/monkey_patch/events/thing_status_info.rb +26 -0
  78. data/lib/openhab/core/dsl/monkey_patch/items/contact_item.rb +54 -0
  79. data/lib/openhab/core/dsl/monkey_patch/items/dimmer_item.rb +125 -0
  80. data/lib/openhab/core/dsl/monkey_patch/items/group_item.rb +27 -0
  81. data/lib/openhab/core/dsl/monkey_patch/items/items.rb +130 -0
  82. data/lib/openhab/core/dsl/monkey_patch/items/metadata.rb +259 -0
  83. data/lib/openhab/core/dsl/monkey_patch/items/switch_item.rb +86 -0
  84. data/lib/openhab/core/dsl/monkey_patch/ruby/number.rb +69 -0
  85. data/lib/openhab/core/dsl/monkey_patch/ruby/range.rb +46 -0
  86. data/lib/openhab/core/dsl/monkey_patch/ruby/ruby.rb +5 -0
  87. data/lib/openhab/core/dsl/monkey_patch/types/decimal_type.rb +24 -0
  88. data/lib/openhab/core/dsl/monkey_patch/types/on_off_type.rb +41 -0
  89. data/lib/openhab/core/dsl/monkey_patch/types/open_closed_type.rb +25 -0
  90. data/lib/openhab/core/dsl/monkey_patch/types/percent_type.rb +23 -0
  91. data/lib/openhab/core/dsl/monkey_patch/types/types.rb +7 -0
  92. data/lib/openhab/core/dsl/property.rb +85 -0
  93. data/lib/openhab/core/dsl/rule/channel.rb +41 -0
  94. data/lib/openhab/core/dsl/rule/cron.rb +115 -0
  95. data/lib/openhab/core/dsl/rule/guard.rb +99 -0
  96. data/lib/openhab/core/dsl/rule/item.rb +207 -0
  97. data/lib/openhab/core/dsl/rule/rule.rb +374 -0
  98. data/lib/openhab/core/dsl/rule/triggers.rb +77 -0
  99. data/lib/openhab/core/dsl/states.rb +63 -0
  100. data/lib/openhab/core/dsl/things.rb +93 -0
  101. data/lib/openhab/core/dsl/time_of_day.rb +203 -0
  102. data/lib/openhab/core/dsl/timers.rb +85 -0
  103. data/lib/openhab/core/dsl/types/quantity.rb +255 -0
  104. data/lib/openhab/core/dsl/units.rb +41 -0
  105. data/lib/openhab/core/duration.rb +69 -0
  106. data/lib/openhab/core/log.rb +175 -0
  107. data/lib/openhab/core/patch_load_path.rb +7 -0
  108. data/lib/openhab/core/startup_delay.rb +22 -0
  109. data/lib/openhab/osgi.rb +52 -0
  110. data/lib/openhab/version.rb +9 -0
  111. data/openhab-scripting.gemspec +30 -0
  112. data/openhab_rules/warmup.rb +5 -0
  113. metadata +157 -0
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Monkey patch ruby
4
+ require 'openhab/core/dsl/monkey_patch/ruby/range'
5
+ require 'openhab/core/dsl/monkey_patch/ruby/number'
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'java'
4
+
5
+ #
6
+ # MonkeyPatching Decimal Type
7
+ #
8
+ # rubocop:disable Style/ClassAndModuleChildren
9
+ class Java::OrgOpenhabCoreLibraryTypes::DecimalType
10
+ # rubocop:enable Style/ClassAndModuleChildren
11
+
12
+ #
13
+ # Compare self to other using Java BigDecimal compare method
14
+ #
15
+ # @param [Object] other object to compare to
16
+ #
17
+ # @return [Boolean] True if have the same BigDecimal representation, false otherwise
18
+ #
19
+ def ==(other)
20
+ return equals(other) unless other.is_a? Integer
21
+
22
+ to_big_decimal.compare_to(Java::JavaMath::BigDecimal.new(other)).zero?
23
+ end
24
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'java'
4
+
5
+ #
6
+ # Monkey patching OnOffType
7
+ #
8
+ # rubocop:disable Style/ClassAndModuleChildren
9
+ class Java::OrgOpenhabCoreLibraryTypes::OnOffType
10
+ # rubocop:enable Style/ClassAndModuleChildren
11
+
12
+ #
13
+ # Invert the type
14
+ #
15
+ # @return [Java::OrgOpenhabCoreLibraryTypes::OnOffType] OFF if ON, ON if OFF
16
+ #
17
+ def !
18
+ return OFF if self == ON
19
+ return ON if self == OFF
20
+ end
21
+
22
+ # Check if the supplied object is case equals to self
23
+ #
24
+ # @param [Object] other object to compare
25
+ #
26
+ # @return [Boolean] True if the other object responds to on?/off? and is in the same state as this object,
27
+ # nil if object cannot be compared
28
+ #
29
+ def ===(other)
30
+ # A case statement here causes and infinite loop
31
+ # rubocop:disable Style/CaseLikeIf
32
+ if self == ON
33
+ other.on? if other.respond_to? :on?
34
+ elsif self == OFF
35
+ other.off? if other.respond_to?('off?')
36
+ else
37
+ false
38
+ end
39
+ # rubocop:enable Style/CaseLikeIf
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'java'
4
+
5
+ #
6
+ # Monkey patch for DSL use
7
+ #
8
+ # rubocop:disable Style/ClassAndModuleChildren
9
+ class Java::OrgOpenhabCoreLibraryTypes::OpenClosedType
10
+ # rubocop:enable Style/ClassAndModuleChildren
11
+ java_import org.openhab.core.library.items.ContactItem
12
+
13
+ #
14
+ # Check if the supplied object is case equals to self
15
+ #
16
+ # @param [Object] other object to compare
17
+ #
18
+ # @return [Boolean] True if the other object is a ContactItem and has the same state
19
+ #
20
+ def ===(other)
21
+ super unless other.is_a? ContactItem
22
+
23
+ self == other.state
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'java'
4
+
5
+ #
6
+ # MonkeyPatching PercentType
7
+ #
8
+ # rubocop:disable Style/ClassAndModuleChildren
9
+ class Java::OrgOpenhabCoreLibraryTypes::PercentType
10
+ # rubocop:enable Style/ClassAndModuleChildren
11
+
12
+ #
13
+ # Need to override and point to super because default JRuby implementation doesn't point to == of parent class
14
+ #
15
+ # @param [Object] other object to check equality for
16
+ # @return [Boolean] True if other equals self, false otherwise
17
+ #
18
+ # rubocop:disable Lint/UselessMethodDefinition
19
+ def ==(other)
20
+ super
21
+ end
22
+ # rubocop:enable Lint/UselessMethodDefinition
23
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Monkey patch types
4
+ require 'core/dsl/monkey_patch/types/open_closed_type'
5
+ require 'core/dsl/monkey_patch/types/on_off_type'
6
+ require 'core/dsl/monkey_patch/types/decimal_type'
7
+ require 'core/dsl/monkey_patch/types/percent_type'
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'core/log'
4
+
5
+ #
6
+ # Provides methods to support DSL properties
7
+ #
8
+ module DSLProperty
9
+ include Logging
10
+
11
+ #
12
+ # Extend the calling object with the property methods
13
+ #
14
+ # @param [Object] base object to extend
15
+ #
16
+ #
17
+ def self.included(base)
18
+ base.extend PropertyMethods
19
+ end
20
+
21
+ #
22
+ # Methods that support creating properties in the DSL
23
+ #
24
+ module PropertyMethods
25
+ #
26
+ # Dynamically creates a property that acts and an accessor with no arguments
27
+ # and a setter with any number of arguments or a block.
28
+ #
29
+ # @param [String] name of the property
30
+ #
31
+ #
32
+ def prop(name)
33
+ define_method(name) do |*args, &block|
34
+ if args.length.zero? && block.nil? == true
35
+ instance_variable_get("@#{name}")
36
+ else
37
+ logger.trace("Property '#{name}' called with args(#{args}) and block(#{block})")
38
+ if args.length == 1
39
+ instance_variable_set("@#{name}", args.first)
40
+ elsif args.length > 1
41
+ instance_variable_set("@#{name}", args)
42
+ elsif block
43
+ instance_variable_set("@#{name}", block)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ #
50
+ # Dynamically creates a property array acts and an accessor with no arguments
51
+ # and a pushes any number of arguments or a block onto they property array
52
+ # You can provide a block to this method which can be used to check if the provided value is acceptable.
53
+ #
54
+ # @param [String] name of the property
55
+ # @param [String] array_name name of the array to use, defaults to name of property
56
+ # @param [Class] wrapper object to put around elements added to the array
57
+ #
58
+ def prop_array(name, array_name: nil, wrapper: nil)
59
+ define_method(name) do |*args, &block|
60
+ array_name ||= name
61
+ if args.length.zero? && block.nil? == true
62
+ instance_variable_get("@#{array_name}")
63
+ else
64
+ logger.trace("Property '#{name}' called with args(#{args}) and block(#{block})")
65
+ if args.length == 1
66
+ insert = args.first
67
+ elsif args.length > 1
68
+ insert = args
69
+ elsif block
70
+ insert = block
71
+ end
72
+ yield insert if block_given?
73
+ insert = wrapper.new(insert) if wrapper
74
+ instance_variable_set("@#{array_name}", (instance_variable_get("@#{array_name}") || []) << insert)
75
+ end
76
+ end
77
+
78
+ if array_name
79
+ define_method(array_name) do
80
+ instance_variable_get("@#{array_name}")
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'core/log'
4
+ require 'openhab/core/dsl/rule/triggers'
5
+
6
+ module OpenHAB
7
+ module Core
8
+ module DSL
9
+ module Rule
10
+ #
11
+ # Channel triggers
12
+ #
13
+ module Channel
14
+ include Logging
15
+
16
+ #
17
+ # Creates a channel trigger
18
+ #
19
+ # @param [Array] channels array to create triggers for on form of 'binding_id:type_id:thing_id#channel_id' or 'channel_id' if thing is provided
20
+ # @param [thing] thing to create trigger for if not specified with the channel
21
+ # @param [String] triggered specific triggering condition to match for trigger
22
+ #
23
+ #
24
+ def channel(*channels, thing: nil, triggered: nil)
25
+ channels.flatten.each do |channel|
26
+ channel = [thing, channel].join(':') if thing
27
+ logger.trace("Creating channel trigger for channel(#{channel}), thing(#{thing}), trigger(#{triggered})")
28
+ [triggered].flatten.each do |trigger|
29
+ config = { 'channelUID' => channel }
30
+ config['event'] = trigger.to_s unless trigger.nil?
31
+ config['channelUID'] = channel
32
+ logger.trace("Creating Change Trigger for #{config}")
33
+ @triggers << Trigger.trigger(type: Trigger::CHANNEL_EVENT, config: config)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'java'
4
+ require 'core/duration'
5
+ require 'core/dsl/time_of_day'
6
+ require 'core/cron'
7
+
8
+ module OpenHAB
9
+ module Core
10
+ module DSL
11
+ module Rule
12
+ #
13
+ # Cron type rules
14
+ #
15
+ module Cron
16
+ java_import org.openhab.core.automation.util.TriggerBuilder
17
+ java_import org.openhab.core.config.core.Configuration
18
+
19
+ include OpenHAB::Core::DSL::Rule
20
+ extend OpenHAB::Core::Cron
21
+
22
+ # @return [Map] Map of days of the week from symbols to to OpenHAB cron strings
23
+ DAY_OF_WEEK_MAP = {
24
+ monday: 'MON',
25
+ tuesday: 'TUE',
26
+ wednesday: 'WED',
27
+ thursday: 'THU',
28
+ friday: 'FRI',
29
+ saturday: 'SAT',
30
+ sunday: 'SUN'
31
+ }.freeze
32
+
33
+ private_constant :DAY_OF_WEEK_MAP
34
+
35
+ # @return [MAP] Converts the DAY_OF_WEEK_MAP to map used by Cron Expression
36
+ DAY_OF_WEEK_EXPRESSION_MAP = DAY_OF_WEEK_MAP.transform_values { |v| cron_expression_map.merge(dow: v) }
37
+
38
+ private_constant :DAY_OF_WEEK_EXPRESSION_MAP
39
+
40
+ # @return [Map] Create a set of cron expressions based on different time intervals
41
+ EXPRESSION_MAP = {
42
+ second: cron_expression_map,
43
+ minute: cron_expression_map.merge(second: '0'),
44
+ hour: cron_expression_map.merge(second: '0', minute: '0'),
45
+ day: cron_expression_map.merge(second: '0', minute: '0', hour: '0'),
46
+ week: cron_expression_map.merge(second: '0', minute: '0', hour: '0', dow: 'MON'),
47
+ month: cron_expression_map.merge(second: '0', minute: '0', hour: '0', dom: '1'),
48
+ year: cron_expression_map.merge(second: '0', minute: '0', hour: '0', dom: '1', month: '1')
49
+ }.merge(DAY_OF_WEEK_EXPRESSION_MAP)
50
+ .freeze
51
+
52
+ private_constant :EXPRESSION_MAP
53
+
54
+ #
55
+ # Create a rule that executes at the specified interval
56
+ #
57
+ # @param [Object] value Symbol or Duration to execute this rule
58
+ # @param [Object] at TimeOfDay or String representing TimeOfDay in which to execute rule
59
+ #
60
+ #
61
+ def every(value, at: nil)
62
+ case value
63
+ when Symbol
64
+ expression_map = EXPRESSION_MAP[value]
65
+ expression_map = at_condition(expression_map, at) if at
66
+ cron(map_to_cron(expression_map))
67
+ when Duration
68
+ cron(map_to_cron(value.cron_map))
69
+ else
70
+ raise ArgumentExpression, 'Unknown interval' unless expression_map
71
+ end
72
+ end
73
+
74
+ #
75
+ # Create a OpenHAB Cron trigger
76
+ #
77
+ # @param [String] expression OpenHAB style cron expression
78
+ #
79
+ def cron(expression)
80
+ @triggers << Trigger.trigger(type: Trigger::CRON, config: { 'cronExpression' => expression })
81
+ end
82
+
83
+ private
84
+
85
+ #
86
+ # Map cron expression to to cron string
87
+ #
88
+ # @param [Map] map of cron expression
89
+ #
90
+ # @return [String] OpenHAB cron string
91
+ #
92
+ def map_to_cron(map)
93
+ %i[second minute hour dom month dow].map { |field| map.fetch(field) }.join(' ')
94
+ end
95
+
96
+ #
97
+ # If an at time is provided, parse that and merge the new fields into the expression.
98
+ #
99
+ # @param [<Type>] expression_map <description>
100
+ # @param [<Type>] at_time <description>
101
+ #
102
+ # @return [<Type>] <description>
103
+ #
104
+ def at_condition(expression_map, at_time)
105
+ if at_time
106
+ tod = (at_time.is_a? TimeOfDay) ? at_time : TimeOfDay.parse(at_time)
107
+ expression_map = expression_map.merge(hour: tod.hour, minute: tod.minute, second: tod.second)
108
+ end
109
+ expression_map
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'core/dsl/property'
4
+ require 'core/log'
5
+
6
+ module OpenHAB
7
+ module Core
8
+ module DSL
9
+ module Rule
10
+ #
11
+ # Guards for rules
12
+ #
13
+ module Guard
14
+ include DSLProperty
15
+
16
+ prop_array(:only_if) do |item|
17
+ unless item.is_a?(Proc) || item.respond_to?(:truthy?)
18
+ raise ArgumentError, "Object passed to only_if must respond_to 'truthy?'"
19
+ end
20
+ end
21
+
22
+ prop_array(:not_if) do |item|
23
+ unless item.is_a?(Proc) || item.respond_to?(:truthy?)
24
+ raise ArgumentError, "Object passed to not_if must respond_to 'truthy?'"
25
+ end
26
+ end
27
+
28
+ #
29
+ # Guard that can prevent execute of a rule if not satisfied
30
+ #
31
+ class Guard
32
+ include Logging
33
+
34
+ #
35
+ # Create a new Guard
36
+ #
37
+ # @param [Object] only_if Item or Proc to use as guard
38
+ # @param [Object] not_if Item or Proc to use as guard
39
+ #
40
+ def initialize(only_if: nil, not_if: nil)
41
+ @only_if = only_if
42
+ @not_if = not_if
43
+ end
44
+
45
+ #
46
+ # Convert the guard into a string
47
+ #
48
+ # @return [String] describing the only_of and not_if guards
49
+ #
50
+ def to_s
51
+ "only_if: #{@only_if}, not_if: #{@not_if}"
52
+ end
53
+
54
+ #
55
+ # Checks if a guard should run
56
+ #
57
+ # @param [OpenHAB Trigger Event] event OpenHAB Trigger Event
58
+ #
59
+ # @return [Boolean] True if guard is satisfied, false otherwise
60
+ #
61
+ def should_run?(event)
62
+ logger.trace("Checking guards #{self}")
63
+ check(@only_if, check_type: :only_if, event: event) && check(@not_if, check_type: :not_if, event: event)
64
+ end
65
+
66
+ private
67
+
68
+ #
69
+ # Check if guard is satisfied
70
+ #
71
+ # @param [Array] conditions to check
72
+ # @param [Symbol] check_type type of check to perform (:only_if or :not_if)
73
+ # @param [Event] event OpenHAB event to see if it satisfies the guard
74
+ #
75
+ # @return [Boolean] True if guard is satisfied, false otherwise
76
+ #
77
+ def check(conditions, check_type:, event:)
78
+ return true if conditions.nil? || conditions.empty?
79
+
80
+ procs, items = conditions.flatten.partition { |condition| condition.is_a? Proc }
81
+ logger.trace("Procs: #{procs} Items: #{items}")
82
+
83
+ items.each { |item| logger.trace("#{item} truthy? #{item.truthy?}") }
84
+
85
+ case check_type
86
+ when :only_if
87
+ items.all?(&:truthy?) && procs.all? { |proc| proc.call(event) }
88
+ when :not_if
89
+ items.none?(&:truthy?) && procs.none? { |proc| proc.call(event) }
90
+ else
91
+ raise ArgumentError, "Unexpected check type: #{check_type}"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end