openhab-scripting 4.30.3 → 4.30.4

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,90 @@ 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
+ logger.states 'Creating received command trigger', item: item, command: command, commands: commands,
33
+ combined_commands: combined_commands
34
+
35
+ command_trigger.trigger(item: item, commands: combined_commands, attach: attach)
32
36
  end.flatten
33
37
  end
34
38
 
35
- private
36
-
37
- #
38
- # Create a received trigger based on item type
39
39
  #
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)
40
+ # Creates command triggers
41
+ #
42
+ class Command < Trigger
43
+ # Combine command and commands into a single array
44
+ #
45
+ # @param [Array] command list of commands to trigger on
46
+ # @param [Array] commands list of commands to trigger on
47
+ #
48
+ # @return [Array] Combined flattened and compacted list of commands
49
+ #
50
+ def self.combine_commands(command:, commands:)
51
+ combined_commands = ([command] | [commands]).flatten
52
+
53
+ # If either command or commands has a value and one is nil, we need to remove nil from the array.
54
+ # If it is only now a single nil value, we leave the nil in place, so that we create a trigger
55
+ # That isn't looking for a specific command.
56
+ combined_commands = combined_commands.compact unless combined_commands.all?(&:nil?)
57
+ combined_commands
58
+ end
59
+
60
+ #
61
+ # Create a received trigger based on item type
62
+ #
63
+ # @param [Array] commands to create trigger for
64
+ # @param [Object] item to create trigger for
65
+ #
66
+ #
67
+ def trigger(item:, commands:, attach:)
68
+ commands.map do |command|
69
+ type, config = if item.is_a? OpenHAB::DSL::Items::GroupItem::GroupMembers
70
+ group(group: item)
71
+ else
72
+ item(item: item)
73
+ end
74
+ config['command'] = command.to_s unless command.nil?
75
+ append_trigger(type: type, config: config, attach: attach)
50
76
  end
51
- config['command'] = command.to_s unless command.nil?
52
- append_trigger(trigger, config, attach: attach)
53
77
  end
54
- end
55
78
 
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
79
+ private
69
80
 
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
81
+ # @return [String] item command trigger
82
+ ITEM_COMMAND = 'core.ItemCommandTrigger'
83
83
 
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
84
+ # @return [String] A group command trigger for items in the group
85
+ GROUP_COMMAND = 'core.GroupCommandTrigger'
94
86
 
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
87
+ #
88
+ # Create trigger for item commands
89
+ #
90
+ # @param [Item] item to create trigger for
91
+ #
92
+ # @return [Array<Hash,Trigger>] first element is hash of trigger config properties
93
+ # second element is trigger type
94
+ #
95
+ def item(item:)
96
+ [ITEM_COMMAND, { 'itemName' => item.name }]
97
+ end
98
+
99
+ #
100
+ # Create trigger for group items
101
+ #
102
+ # @param [Group] group to create trigger for
103
+ #
104
+ # @return [Array<Hash,Trigger>] first element is hash of trigger config properties
105
+ # second element is trigger type
106
+ #
107
+ def group(group:)
108
+ [GROUP_COMMAND, { 'groupName' => group.group.name }]
109
+ end
100
110
  end
101
111
  end
102
112
  end
@@ -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
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'java'
4
+ require_relative 'cron'
5
+
6
+ module OpenHAB
7
+ module DSL
8
+ module Rules
9
+ #
10
+ # Cron type rules
11
+ #
12
+ module Triggers
13
+ #
14
+ # Cron trigger handler that provides trigger ID
15
+ #
16
+ module CronHandler
17
+ include OpenHAB::Log
18
+
19
+ #
20
+ # Creates trigger types and trigger type factories for OpenHAB
21
+ #
22
+ def self.add_script_cron_handler
23
+ java_import org.openhab.core.automation.type.TriggerType
24
+ OpenHAB::Core.automation_manager.add_trigger_handler(
25
+ OpenHAB::DSL::Rules::Triggers::Cron::CRON_TRIGGER_MODULE_ID,
26
+ OpenHAB::DSL::Rules::Triggers::CronHandler::CronTriggerHandlerFactory.new
27
+ )
28
+
29
+ OpenHAB::Core.automation_manager.add_trigger_type(cron_trigger_type)
30
+ OpenHAB::Log.logger(self).trace('Added script cron trigger handler')
31
+ end
32
+
33
+ #
34
+ # Creates trigger types and trigger type factories for OpenHAB
35
+ #
36
+ private_class_method def self.cron_trigger_type
37
+ TriggerType.new(
38
+ OpenHAB::DSL::Rules::Triggers::Cron::CRON_TRIGGER_MODULE_ID,
39
+ nil,
40
+ 'A specific instant occurs',
41
+ 'Triggers when the specified instant occurs',
42
+ nil,
43
+ org.openhab.core.automation.Visibility::VISIBLE,
44
+ nil
45
+ )
46
+ end
47
+
48
+ # Cron Trigger Handler that provides trigger IDs
49
+ # Unfortunatly because the CronTriggerHandler in OpenHAB core is marked internal
50
+ # the entire thing must be recreated here
51
+ class CronTriggerHandler < org.openhab.core.automation.handler.BaseTriggerModuleHandler
52
+ include OpenHAB::Log
53
+ include org.openhab.core.scheduler.SchedulerRunnable
54
+ include org.openhab.core.automation.handler.TimeBasedTriggerHandler
55
+
56
+ # Provides access to protected fields
57
+ field_accessor :callback
58
+
59
+ # Creates a new CronTriggerHandler
60
+ # @param [Trigger] OpenHAB trigger associated with handler
61
+ #
62
+ def initialize(trigger)
63
+ @trigger = trigger
64
+ @scheduler = OpenHAB::Core::OSGI.service('org.openhab.core.scheduler.CronScheduler')
65
+ @expression = trigger.configuration.get('cronExpression')
66
+ super(trigger)
67
+ end
68
+
69
+ #
70
+ # Set the callback to execute when cron trigger fires
71
+ # @param [Object] callback to run
72
+ #
73
+ def setCallback(callback) # rubocop:disable Naming/MethodName
74
+ synchronized do
75
+ super(callback)
76
+ @scheduler.schedule(self, @expression)
77
+ logger.trace("Scheduled cron job '#{@expression}' for trigger '#{@trigger.id}'.")
78
+ end
79
+ end
80
+
81
+ #
82
+ # Get the temporal adjuster
83
+ # @return [CronAdjuster]
84
+ #
85
+ def getTemporalAdjuster # rubocop:disable Naming/MethodName
86
+ org.openhab.core.scheduler.CronAdjuster.new(expression)
87
+ end
88
+
89
+ #
90
+ # Execute the callback
91
+ #
92
+ def run
93
+ callback&.triggered(@trigger, { 'module' => @trigger.id })
94
+ end
95
+
96
+ #
97
+ # Displose of the handler
98
+ # cancel the cron scheduled task
99
+ #
100
+ def dispose
101
+ synchronized do
102
+ super
103
+ return unless @schedule
104
+
105
+ @schedule&.cancel(true)
106
+ end
107
+ logger.trace("cancelled job for trigger '#{@trigger.id}'.")
108
+ end
109
+ end
110
+
111
+ # Implements the ScriptedTriggerHandlerFactory interface to create a new Cron Trigger Handler
112
+ class CronTriggerHandlerFactory
113
+ include org.openhab.core.automation.module.script.rulesupport.shared.factories.ScriptedTriggerHandlerFactory
114
+
115
+ # Invoked by the OpenHAB core to get a trigger handler for the supllied trigger
116
+ # @param [Trigger] OpenHAB trigger
117
+ #
118
+ # @return [WatchTriggerHandler] trigger handler for supplied trigger
119
+ def get(trigger)
120
+ CronTriggerHandler.new(trigger)
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ # Add the cron handler to OpenHAB
130
+ OpenHAB::DSL::Rules::Triggers::CronHandler.add_script_cron_handler
@@ -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
@@ -21,7 +22,7 @@ module OpenHAB
21
22
  #
22
23
  def trigger(type, attach: nil, **configuration)
23
24
  logger.trace("Creating a generic trigger for type(#{type}) with configuration(#{configuration})")
24
- append_trigger(type, configuration, attach: attach)
25
+ Trigger.new(rule_triggers: @rule_triggers).append_trigger(type: type, config: configuration, attach: attach)
25
26
  end
26
27
  end
27
28
  end