openhab-scripting 2.9.1

Sign up to get free protection for your applications and to get access to all the features.
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