openhab-scripting 4.30.3 → 4.32.0

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.
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'openhab/log/logger'
4
- require 'openhab/dsl/rules/triggers/trigger'
4
+ require 'openhab/dsl/things'
5
+ require_relative 'trigger'
5
6
 
6
7
  module OpenHAB
7
8
  module DSL
@@ -22,32 +23,59 @@ module OpenHAB
22
23
  # thing(s) to create trigger for if not specified with the channel
23
24
  # @param [String, Array<String>] triggered specific triggering condition(s) to match for trigger
24
25
  #
25
- def channel(*channels, thing: nil, triggered: nil, attach: nil) # rubocop:disable Metrics/AbcSize
26
- channels.flatten.product([thing].flatten).each do |(channel, t)|
27
- channel = channel.uid if channel.is_a?(org.openhab.core.thing.Channel)
28
- t = t.uid if t.is_a?(Thing)
29
- channel = [t, channel].compact.join(':')
30
- logger.trace("Creating channel trigger for channel(#{channel}), thing(#{t}), trigger(#{triggered})")
26
+ def channel(*channels, thing: nil, triggered: nil, attach: nil)
27
+ channel_trigger = Channel.new(rule_triggers: @rule_triggers)
28
+ Channel.channels(channels: channels, thing: thing).each do |channel|
31
29
  [triggered].flatten.each do |trigger|
32
- create_channel_trigger(channel, trigger, attach)
30
+ channel_trigger.trigger(channel: channel, trigger: trigger, attach: attach)
33
31
  end
34
32
  end
35
33
  end
36
34
 
37
- private
38
-
39
- #
40
- # Create a trigger for a channel
41
35
  #
42
- # @param [String] channel to look for triggers
43
- # @param [String] trigger specific channel trigger to match
36
+ # Creates channel triggers
44
37
  #
45
- #
46
- def create_channel_trigger(channel, trigger, attach)
47
- config = { 'channelUID' => channel }
48
- config['event'] = trigger.to_s unless trigger.nil?
49
- logger.trace("Creating Change Trigger for #{config}")
50
- append_trigger(Trigger::CHANNEL_EVENT, config, attach: attach)
38
+ class Channel < Trigger
39
+ include OpenHAB::Log
40
+
41
+ # @return [String] A channel event trigger
42
+ CHANNEL_EVENT = 'core.ChannelEventTrigger'
43
+
44
+ #
45
+ # Get an enumerator over the product of the channels and things and map them to a channel id
46
+ # @param [Object] channels to iterate over
47
+ # @param [Object] thing to combine with channels and iterate over
48
+ # @return [Enumerable] enumerable channel ids to trigger on
49
+ def self.channels(channels:, thing:)
50
+ logger.state 'Creating Channel/Thing Pairs', channels: channels, thing: thing
51
+ channels.flatten.product([thing].flatten)
52
+ .map { |channel_thing| channel_id(*channel_thing) }
53
+ end
54
+
55
+ #
56
+ # Get a channel id from a channel and thing
57
+ # @param [Object] channel part of channel id, get UID if object is a Channel
58
+ # @param [Object] thing part of channel id, get UID if object is a Thing
59
+ #
60
+ def self.channel_id(channel, thing)
61
+ channel = channel.uid if channel.is_a?(org.openhab.core.thing.Channel)
62
+ thing = thing.uid if thing.is_a?(Thing)
63
+ [thing, channel].compact.join(':')
64
+ end
65
+
66
+ #
67
+ # Create a trigger for a channel
68
+ #
69
+ # @param [String] channel to look for triggers
70
+ # @param [String] trigger specific channel trigger to match
71
+ #
72
+ #
73
+ def trigger(channel:, trigger:, attach:)
74
+ config = { 'channelUID' => channel }
75
+ config['event'] = trigger.to_s unless trigger.nil?
76
+ logger.state 'Creating Channel Trigger', channel: channel, config: config
77
+ append_trigger(type: CHANNEL_EVENT, config: config, attach: attach)
78
+ end
51
79
  end
52
80
  end
53
81
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'openhab/log/logger'
4
+ require_relative 'trigger'
4
5
 
5
6
  module OpenHAB
6
7
  module DSL
@@ -22,81 +23,130 @@ module OpenHAB
22
23
  #
23
24
  #
24
25
  def received_command(*items, command: nil, commands: nil, attach: nil)
25
- separate_groups(items).map do |item|
26
- logger.trace("Creating received command trigger for item(#{item})"\
27
- "command(#{command}) commands(#{commands})")
26
+ command_trigger = Command.new(rule_triggers: @rule_triggers)
28
27
 
29
- # Combine command and commands, doing union so only a single nil will be in the combined array.
30
- combined_commands = combine_commands(command, commands)
31
- create_received_trigger(combined_commands, item, attach)
28
+ # Combine command and commands, doing union so only a single nil will be in the combined array.
29
+ combined_commands = Command.combine_commands(command: command, commands: commands)
30
+
31
+ Command.flatten_items(items).map do |item|
32
+ combined_commands.map do |cmd|
33
+ logger.states 'Creating received command trigger', item: item, command: cmd
34
+
35
+ command_trigger.trigger(item: item, command: cmd, attach: attach)
36
+ end
32
37
  end.flatten
33
38
  end
34
39
 
35
- private
36
-
37
- #
38
- # Create a received trigger based on item type
39
40
  #
40
- # @param [Array] commands to create trigger for
41
- # @param [Object] item to create trigger for
42
- #
43
- #
44
- def create_received_trigger(commands, item, attach)
45
- commands.map do |command|
46
- if item.is_a? OpenHAB::DSL::Items::GroupItem::GroupMembers
47
- config, trigger = create_group_command_trigger(item)
48
- else
49
- config, trigger = create_item_command_trigger(item)
41
+ # Creates command triggers
42
+ #
43
+ class Command < Trigger
44
+ # Combine command and commands into a single array
45
+ #
46
+ # @param [Array] command list of commands to trigger on
47
+ # @param [Array] commands list of commands to trigger on
48
+ #
49
+ # @return [Array] Combined flattened and compacted list of commands
50
+ #
51
+ def self.combine_commands(command:, commands:)
52
+ combined_commands = ([command] | [commands]).flatten
53
+
54
+ # If either command or commands has a value and one is nil, we need to remove nil from the array.
55
+ # If it is only now a single nil value, we leave the nil in place, so that we create a trigger
56
+ # That isn't looking for a specific command.
57
+ combined_commands = combined_commands.compact unless combined_commands.all?(&:nil?)
58
+ combined_commands
59
+ end
60
+
61
+ #
62
+ # Create a received command trigger
63
+ #
64
+ # @param [Object] item item to create trigger for
65
+ # @param [Object] command to check against
66
+ # @param [Object] attach attachment
67
+ #
68
+ # @return [Trigger] OpenHAB triggers
69
+ #
70
+ def trigger(item:, command:, attach:)
71
+ case command
72
+ when Range then range_trigger(item: item, command: command, attach: attach)
73
+ when Proc then proc_trigger(item: item, command: command, attach: attach)
74
+ else command_trigger(item: item, command: command, attach: attach)
50
75
  end
76
+ end
77
+
78
+ #
79
+ # Creates a trigger with a range condition on the 'command' field
80
+ # @param [Object] item to create changed trigger on
81
+ # @param [Object] command to restrict trigger to
82
+ # @param [Object] attach to trigger
83
+ # @return [Trigger] OpenHAB trigger
84
+ #
85
+ def range_trigger(item:, command:, attach:)
86
+ command_range, * = Conditions::Proc.range_procs(command)
87
+ proc_trigger(item: item, command: command_range, attach: attach)
88
+ end
89
+
90
+ #
91
+ # Creates a trigger with a proc condition on the 'command' field
92
+ # @param [Object] item to create changed trigger on
93
+ # @param [Object] command to restrict trigger to
94
+ # @param [Object] attach to trigger
95
+ # @return [Trigger] OpenHAB trigger
96
+ #
97
+ def proc_trigger(item:, command:, attach:)
98
+ conditions = Conditions::Proc.new(command: command)
99
+ command_trigger(item: item, command: nil, attach: attach, conditions: conditions)
100
+ end
101
+
102
+ #
103
+ # Create a received trigger based on item type
104
+ #
105
+ # @param [Array] commands to create trigger for
106
+ # @param [Object] item to create trigger for
107
+ #
108
+ #
109
+ def command_trigger(item:, command:, attach: nil, conditions: nil)
110
+ type, config = if item.is_a? OpenHAB::DSL::Items::GroupItem::GroupMembers
111
+ group(group: item)
112
+ else
113
+ item(item: item)
114
+ end
51
115
  config['command'] = command.to_s unless command.nil?
52
- append_trigger(trigger, config, attach: attach)
116
+ append_trigger(type: type, config: config, attach: attach, conditions: conditions)
53
117
  end
54
- end
55
118
 
56
- #
57
- # Create trigger for item commands
58
- #
59
- # @param [Item] item to create trigger for
60
- #
61
- # @return [Array<Hash,Trigger>] first element is hash of trigger config properties
62
- # second element is trigger type
63
- #
64
- def create_item_command_trigger(item)
65
- config = { 'itemName' => item.name }
66
- trigger = Trigger::ITEM_COMMAND
67
- [config, trigger]
68
- end
119
+ private
69
120
 
70
- #
71
- # Create trigger for group items
72
- #
73
- # @param [Group] group to create trigger for
74
- #
75
- # @return [Array<Hash,Trigger>] first element is hash of trigger config properties
76
- # second element is trigger type
77
- #
78
- def create_group_command_trigger(group)
79
- config = { 'groupName' => group.group.name }
80
- trigger = Trigger::GROUP_COMMAND
81
- [config, trigger]
82
- end
121
+ # @return [String] item command trigger
122
+ ITEM_COMMAND = 'core.ItemCommandTrigger'
83
123
 
84
- #
85
- # Combine command and commands into a single array
86
- #
87
- # @param [Array] command list of commands to trigger on
88
- # @param [Array] commands list of commands to trigger on
89
- #
90
- # @return [Array] Combined flattened and compacted list of commands
91
- #
92
- def combine_commands(command, commands)
93
- combined_commands = ([command] | [commands]).flatten
94
-
95
- # If either command or commands has a value and one is nil, we need to remove nil from the array.
96
- # If it is only now a single nil value, we leave the nil in place, so that we create a trigger
97
- # That isn't looking for a specific command.
98
- combined_commands = combined_commands.compact unless combined_commands.all?(&:nil?)
99
- combined_commands
124
+ # @return [String] A group command trigger for items in the group
125
+ GROUP_COMMAND = 'core.GroupCommandTrigger'
126
+
127
+ #
128
+ # Create trigger for item commands
129
+ #
130
+ # @param [Item] item to create trigger for
131
+ #
132
+ # @return [Array<Hash,Trigger>] first element is hash of trigger config properties
133
+ # second element is trigger type
134
+ #
135
+ def item(item:)
136
+ [ITEM_COMMAND, { 'itemName' => item.name }]
137
+ end
138
+
139
+ #
140
+ # Create trigger for group items
141
+ #
142
+ # @param [Group] group to create trigger for
143
+ #
144
+ # @return [Array<Hash,Trigger>] first element is hash of trigger config properties
145
+ # second element is trigger type
146
+ #
147
+ def group(group:)
148
+ [GROUP_COMMAND, { 'groupName' => group.group.name }]
149
+ end
100
150
  end
101
151
  end
102
152
  end
@@ -38,9 +38,9 @@ module OpenHAB
38
38
  #
39
39
  def self.range_proc(range)
40
40
  logger.trace("Creating range proc for #{range}")
41
- lambda do |state|
42
- logger.trace("Range proc checking if #{state} is in #{range}")
43
- range.include? state
41
+ lambda do |val|
42
+ logger.trace("Range proc checking if #{val} is in #{range}")
43
+ range.cover? val
44
44
  end
45
45
  end
46
46
 
@@ -77,9 +77,10 @@ module OpenHAB
77
77
  # @param [Proc] from Proc to check against from value
78
78
  # @param [Proc] to Proc to check against to value
79
79
  #
80
- def initialize(from: nil, to: nil)
80
+ def initialize(from: nil, to: nil, command: nil)
81
81
  @from = from
82
82
  @to = to
83
+ @command = command
83
84
  end
84
85
 
85
86
  #
@@ -88,7 +89,18 @@ module OpenHAB
88
89
  #
89
90
  def process(mod:, inputs:) # rubocop:disable Lint/UnusedMethodArgument - mod is unused here but required
90
91
  logger.trace("Checking #{inputs} against condition trigger #{self}")
91
- yield if check_from(inputs: inputs) && check_to(inputs: inputs)
92
+ yield if check_procs(inputs: inputs)
93
+ end
94
+
95
+ #
96
+ # Check if command condition match the proc
97
+ # @param [Hash] inputs from trigger must be supplied if state is not supplied
98
+ # @return [true/false] depending on if from is set and matches supplied conditions
99
+ #
100
+ def check_command(inputs: nil)
101
+ command = input_state(inputs, 'command')
102
+ logger.trace "Checking command(#{@command}) against command(#{command})"
103
+ check_proc(proc: @command, value: command)
92
104
  end
93
105
 
94
106
  #
@@ -100,7 +112,7 @@ module OpenHAB
100
112
  def check_from(inputs: nil, state: nil)
101
113
  state ||= input_state(inputs, 'oldState')
102
114
  logger.trace "Checking from(#{@from}) against state(#{state})"
103
- check_proc(proc: @from, state: state)
115
+ check_proc(proc: @from, value: state)
104
116
  end
105
117
 
106
118
  #
@@ -112,27 +124,34 @@ module OpenHAB
112
124
  def check_to(inputs: nil, state: nil)
113
125
  state ||= input_state(inputs, 'newState', 'state')
114
126
  logger.trace "Checking to(#{@to}) against state(#{state})"
115
- check_proc(proc: @to, state: state)
127
+ check_proc(proc: @to, value: state)
116
128
  end
117
129
 
118
130
  # Describe the Proc Condition as a string
119
131
  # @return [String] string representation of proc condition
120
132
  #
121
133
  def to_s
122
- "From:(#{@from}) To:(#{@to})"
134
+ "From:(#{@from}) To:(#{@to}) Command:(#{@command})"
123
135
  end
124
136
 
125
137
  private
126
138
 
139
+ #
140
+ # Check all procs
141
+ # @param [Hash] inputs from event
142
+ # @return [true/false] true if all procs return true, false otherwise
143
+ def check_procs(inputs:)
144
+ check_from(inputs: inputs) && check_to(inputs: inputs) && check_command(inputs: inputs)
145
+ end
146
+
127
147
  # Check if a field matches the proc condition
128
148
  # @param [Proc] proc to call
129
- # @param [Hash] inputs containing fields
130
- # @param [Array] fields array of fields to extract from inputs, first one found is passed to proc
149
+ # @param [Hash] value to check
131
150
  # @return [true,false] true if proc is nil or proc.call returns true, false otherwise
132
- def check_proc(proc:, state:)
133
- return true if proc.nil? || proc.call(state)
151
+ def check_proc(proc:, value:)
152
+ return true if proc.nil? || proc.call(value)
134
153
 
135
- logger.trace("Skipped execution of rule because state #{state} evalulated false for (#{proc})")
154
+ logger.trace("Skipped execution of rule because value #{value} evaluated false for (#{proc})")
136
155
  false
137
156
  end
138
157
 
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'java'
4
+ require 'openhab/dsl/rules/triggers/trigger'
5
+
6
+ module OpenHAB
7
+ module DSL
8
+ module Rules
9
+ #
10
+ # Cron type rules
11
+ #
12
+ module Triggers
13
+ #
14
+ # Create a rule that executes at the specified interval
15
+ #
16
+ # @param [Object] value Symbol or Duration to execute this rule
17
+ # @param [Object] at TimeOfDay or String representing TimeOfDay in which to execute rule
18
+ #
19
+ #
20
+ def every(value, at: nil, attach: nil)
21
+ cron_expression = case value
22
+ when Symbol then Cron.from_symbol(value, at)
23
+ when Java::JavaTime::Duration then Cron.from_duration(value, at)
24
+ else raise ArgumentExpression, 'Unknown interval'
25
+ end
26
+ cron(cron_expression, attach: attach)
27
+ end
28
+
29
+ #
30
+ # Create a OpenHAB Cron trigger
31
+ #
32
+ # @param [String] expression OpenHAB style cron expression
33
+ #
34
+ def cron(expression, attach: nil)
35
+ cron = Cron.new(rule_triggers: @rule_triggers)
36
+ cron.trigger(config: { 'cronExpression' => expression }, attach: attach)
37
+ end
38
+
39
+ #
40
+ # Creates cron triggers
41
+ #
42
+ class Cron < Trigger
43
+ # Trigger ID for Watch Triggers
44
+ CRON_TRIGGER_MODULE_ID = 'jsr223.jruby.CronTrigger'
45
+
46
+ #
47
+ # Returns a default map for cron expressions that fires every second
48
+ # This map is usually updated via merge by other methods to refine cron type triggers.
49
+ #
50
+ # @return [Hash] Map with symbols for :seconds, :minute, :hour, :dom, :month, :dow
51
+ # configured to fire every second
52
+ #
53
+ CRON_EXPRESSION_MAP =
54
+ {
55
+ second: '*',
56
+ minute: '*',
57
+ hour: '*',
58
+ dom: '?',
59
+ month: '*',
60
+ dow: '?'
61
+ }.freeze
62
+ private_constant :CRON_EXPRESSION_MAP
63
+
64
+ # @return [Hash] Map of days of the week from symbols to to OpenHAB cron strings
65
+ DAY_OF_WEEK_MAP = {
66
+ monday: 'MON',
67
+ tuesday: 'TUE',
68
+ wednesday: 'WED',
69
+ thursday: 'THU',
70
+ friday: 'FRI',
71
+ saturday: 'SAT',
72
+ sunday: 'SUN'
73
+ }.freeze
74
+ private_constant :DAY_OF_WEEK_MAP
75
+
76
+ # @return [Hash] Converts the DAY_OF_WEEK_MAP to map used by Cron Expression
77
+ DAY_OF_WEEK_EXPRESSION_MAP = DAY_OF_WEEK_MAP.transform_values { |v| CRON_EXPRESSION_MAP.merge(dow: v) }
78
+
79
+ private_constant :DAY_OF_WEEK_EXPRESSION_MAP
80
+
81
+ # @return [Hash] Create a set of cron expressions based on different time intervals
82
+ EXPRESSION_MAP = {
83
+ second: CRON_EXPRESSION_MAP,
84
+ minute: CRON_EXPRESSION_MAP.merge(second: '0'),
85
+ hour: CRON_EXPRESSION_MAP.merge(second: '0', minute: '0'),
86
+ day: CRON_EXPRESSION_MAP.merge(second: '0', minute: '0', hour: '0'),
87
+ week: CRON_EXPRESSION_MAP.merge(second: '0', minute: '0', hour: '0', dow: 'MON'),
88
+ month: CRON_EXPRESSION_MAP.merge(second: '0', minute: '0', hour: '0', dom: '1'),
89
+ year: CRON_EXPRESSION_MAP.merge(second: '0', minute: '0', hour: '0', dom: '1', month: '1')
90
+ }.merge(DAY_OF_WEEK_EXPRESSION_MAP).freeze
91
+
92
+ private_constant :EXPRESSION_MAP
93
+
94
+ #
95
+ # Create a cron map from a duration
96
+ #
97
+ # @param [java::time::Duration] duration
98
+ # @param [Object] at TimeOfDay or String representing time of day
99
+ #
100
+ # @return [Hash] map describing cron expression
101
+ #
102
+ def self.from_duration(duration, at)
103
+ raise ArgumentError, '"at" cannot be used with duration' if at
104
+
105
+ map_to_cron(duration_to_map(duration))
106
+ end
107
+
108
+ #
109
+ # Create a cron map from a symbol
110
+ #
111
+ # @param [Symbol] symbol
112
+ # @param [Object] at TimeOfDay or String representing time of day
113
+ #
114
+ # @return [Hash] map describing cron expression created from symbol
115
+ #
116
+ def self.from_symbol(symbol, at)
117
+ expression_map = EXPRESSION_MAP[symbol]
118
+ expression_map = at_condition(expression_map, at) if at
119
+ map_to_cron(expression_map)
120
+ end
121
+
122
+ #
123
+ # Map cron expression to to cron string
124
+ #
125
+ # @param [Map] map of cron expression
126
+ #
127
+ # @return [String] OpenHAB cron string
128
+ #
129
+ def self.map_to_cron(map)
130
+ %i[second minute hour dom month dow].map { |field| map.fetch(field) }.join(' ')
131
+ end
132
+
133
+ #
134
+ # Convert a Java Duration to a map for the map_to_cron method
135
+ #
136
+ # @param duration [Java::JavaTime::Duration] The duration object
137
+ #
138
+ # @return [Hash] a map suitable for map_to_cron
139
+ #
140
+ def self.duration_to_map(duration)
141
+ if duration.to_millis_part.zero? && duration.to_nanos_part.zero? && duration.to_days.zero?
142
+ %i[second minute hour].each do |unit|
143
+ to_unit_part = duration.public_send("to_#{unit}s_part")
144
+ next unless to_unit_part.positive?
145
+
146
+ to_unit = duration.public_send("to_#{unit}s")
147
+ break unless to_unit_part == to_unit
148
+
149
+ return EXPRESSION_MAP[unit].merge(unit => "*/#{to_unit}")
150
+ end
151
+ end
152
+ raise ArgumentError, "Cron Expression not supported for duration: #{duration}"
153
+ end
154
+
155
+ #
156
+ # If an at time is provided, parse that and merge the new fields into the expression.
157
+ #
158
+ # @param [<Type>] expression_map <description>
159
+ # @param [<Type>] at_time <description>
160
+ #
161
+ # @return [<Type>] <description>
162
+ #
163
+ def self.at_condition(expression_map, at_time)
164
+ if at_time
165
+ tod = at_time.is_a?(TimeOfDay::TimeOfDay) ? at_time : TimeOfDay::TimeOfDay.parse(at_time)
166
+ expression_map = expression_map.merge(hour: tod.hour, minute: tod.minute, second: tod.second)
167
+ end
168
+ expression_map
169
+ end
170
+
171
+ #
172
+ # Create a cron trigger based on item type
173
+ #
174
+ # @param [Array] commands to create trigger for
175
+ # @param [Object] item to create trigger for
176
+ #
177
+ #
178
+ def trigger(config:, attach:)
179
+ append_trigger(type: CRON_TRIGGER_MODULE_ID, config: config, attach: attach)
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end