openhab-scripting 4.30.3 → 4.30.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
4
- require 'java'
5
- require_relative 'cron'
3
+ require 'forwardable'
6
4
 
7
5
  module OpenHAB
8
6
  module DSL
@@ -11,132 +9,66 @@ module OpenHAB
11
9
  # Module holds rule triggers
12
10
  #
13
11
  module Triggers
14
- #
15
- # Create a trigger for a thing
16
- #
17
- # @param [Thing] thing to create trigger for
18
- # @param [Trigger] trigger to map with thing
19
- # @param [State] to for thing
20
- # @param [State] from state of thing
21
- #
22
- # @return [Array] Trigger and config for thing
23
- #
24
- def trigger_for_thing(thing, trigger, to = nil, from = nil)
25
- config = { 'thingUID' => thing.uid.to_s }
26
- config['status'] = trigger_state_from_symbol(to).to_s if to
27
- config['previousStatus'] = trigger_state_from_symbol(from).to_s if from
28
- [trigger, config]
29
- end
30
-
31
- #
32
- # converts object to upcase string if its a symbol
33
- #
34
- # @param [sym] sym potential symbol to convert
35
- #
36
- # @return [String] Upcased symbol as string
37
- #
38
- def trigger_state_from_symbol(sym)
39
- sym.to_s.upcase if (sym.is_a? Symbol) || sym
40
- end
41
-
42
- #
43
- # Append a trigger to the list of triggeres
44
- #
45
- # @param [String] type of trigger to create
46
- # @param [Map] config map describing trigger configuration
47
- #
48
- # @return [Trigger] OpenHAB trigger
49
- #
50
- def append_trigger(type, config, attach: nil)
51
- logger.trace("Creating trigger of type #{type} for #{config}")
52
- config.transform_keys!(&:to_s)
53
- trigger = Trigger.trigger(type: type, config: config)
54
- @attachments[trigger.id] = attach if attach
55
- @triggers << trigger
56
- trigger
57
- end
58
-
59
- #
60
- # Separates groups from items, and flattens any nested arrays of items
61
- #
62
- # @param [Array] item_array Array of items passed to a trigger
63
- #
64
- # @return [Array] A new flat array with any GroupMembers object left intact
65
- #
66
- def separate_groups(item_array)
67
- # we want to support anything that can be flattened... i.e. responds to to_ary
68
- # we want to be more lenient than only things that are currently Array,
69
- # but Enumerable is too lenient because Array#flatten won't traverse interior
70
- # Enumerables
71
- return item_array unless item_array.find { |item| item.respond_to?(:to_ary) }
72
-
73
- groups, items = item_array.partition { |item| item.is_a?(OpenHAB::DSL::Items::GroupItem::GroupMembers) }
74
- groups + separate_groups(items.flatten(1))
75
- end
76
-
77
12
  #
78
13
  # Class for creating and managing triggers
79
14
  #
80
15
  class Trigger
81
- java_import org.openhab.core.automation.util.TriggerBuilder
82
- java_import org.openhab.core.config.core.Configuration
83
-
84
- # @return [String] A channel event trigger
85
- CHANNEL_EVENT = 'core.ChannelEventTrigger'
86
-
87
- # @return [String] A thing status Change trigger
88
- THING_CHANGE = 'core.ThingStatusChangeTrigger'
89
-
90
- # @return [String] A thing status update trigger
91
- THING_UPDATE = 'core.ThingStatusUpdateTrigger'
92
-
93
- # @return [String] An item command trigger
94
- ITEM_COMMAND = 'core.ItemCommandTrigger'
95
-
96
- # @return [String] An item state update trigger
97
- ITEM_STATE_UPDATE = 'core.ItemStateUpdateTrigger'
98
-
99
- # @return [String] An item state change trigger
100
- ITEM_STATE_CHANGE = 'core.ItemStateChangeTrigger'
16
+ extend Forwardable
101
17
 
102
- # @return [String] A group state change trigger for items in the group
103
- GROUP_STATE_CHANGE = 'core.GroupStateChangeTrigger'
18
+ # Provide backwards compatibility for these fields
19
+ delegate :append_trigger => :@rule_triggers
104
20
 
105
- # @return [String] A group state update trigger for items in the group
106
- GROUP_STATE_UPDATE = 'core.GroupStateUpdateTrigger'
107
-
108
- # @return [String] A group command trigger for items in the group
109
- GROUP_COMMAND = 'core.GroupCommandTrigger'
110
-
111
- # @return [String] A time of day trigger
112
- TIME_OF_DAY = 'timer.TimeOfDayTrigger'
21
+ #
22
+ # Separates groups from items, and flattens any nested arrays of items
23
+ #
24
+ # @param [Array] item_array Array of items passed to a trigger
25
+ #
26
+ # @return [Array] A new flat array with any GroupMembers object left intact
27
+ #
28
+ def self.flatten_items(item_array)
29
+ # we want to support anything that can be flattened... i.e. responds to to_ary
30
+ # we want to be more lenient than only things that are currently Array,
31
+ # but Enumerable is too lenient because Array#flatten won't traverse interior
32
+ # Enumerables
33
+ return item_array unless item_array.find { |item| item.respond_to?(:to_ary) }
34
+
35
+ groups, items = item_array.partition { |item| item.is_a?(OpenHAB::DSL::Items::GroupItem::GroupMembers) }
36
+ groups + flatten_items(items.flatten(1))
37
+ end
113
38
 
114
- # @return [String] A cron trigger
115
- CRON = OpenHAB::DSL::Rules::Triggers::Cron::CRON_TRIGGER_MODULE_ID
39
+ #
40
+ # Creates a new Trigger
41
+ # @param [RuleTrigger] rule trigger information
42
+ def initialize(rule_triggers:)
43
+ @rule_triggers = rule_triggers
44
+ end
116
45
 
117
46
  #
118
- # Create a trigger
47
+ # Create a trigger for a thing
119
48
  #
120
- # @param [String] type of trigger
121
- # @param [Map] config map
49
+ # @param [Thing] thing to create trigger for
50
+ # @param [Trigger] trigger to map with thing
51
+ # @param [State] to for thing
52
+ # @param [State] from state of thing
122
53
  #
123
- # @return [OpenHAB Trigger] configured by type and supplied config
54
+ # @return [Array] Trigger and config for thing
124
55
  #
125
- def self.trigger(type:, config:)
126
- TriggerBuilder.create
127
- .with_id(uuid)
128
- .with_type_uid(type)
129
- .with_configuration(Configuration.new(config))
130
- .build
56
+ def trigger_for_thing(thing:, type:, to: nil, from: nil)
57
+ config = { 'thingUID' => thing.uid.to_s }
58
+ config['status'] = trigger_state_from_symbol(to).to_s if to
59
+ config['previousStatus'] = trigger_state_from_symbol(from).to_s if from
60
+ [type, config]
131
61
  end
132
62
 
133
63
  #
134
- # Generate a UUID for triggers
64
+ # converts object to upcase string if its a symbol
65
+ #
66
+ # @param [sym] sym potential symbol to convert
135
67
  #
136
- # @return [String] UUID
68
+ # @return [String] Upcased symbol as string
137
69
  #
138
- def self.uuid
139
- SecureRandom.uuid
70
+ def trigger_state_from_symbol(sym)
71
+ sym.to_s.upcase if (sym.is_a? Symbol) || sym
140
72
  end
141
73
  end
142
74
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'cron/cron'
4
+ require_relative 'cron/cron_handler'
5
+ require_relative 'changed'
6
+ require_relative 'channel'
7
+ require_relative 'command'
8
+ require_relative 'updated'
9
+ require_relative 'generic'
10
+ require_relative 'watch/watch'
11
+ require_relative 'watch/watch_handler'
@@ -21,119 +21,133 @@ module OpenHAB
21
21
  # @return [Trigger] Trigger for updated entity
22
22
  #
23
23
  def updated(*items, to: nil, attach: nil)
24
- separate_groups(items).map do |item|
24
+ updated = Updated.new(rule_triggers: @rule_triggers)
25
+ Updated.flatten_items(items).map do |item|
25
26
  logger.trace("Creating updated trigger for item(#{item}) to(#{to})")
26
27
  [to].flatten.map do |to_state|
27
- update_trigger(item: item, to: to_state, attach: attach)
28
+ updated.trigger(item: item, to: to_state, attach: attach)
28
29
  end
29
30
  end.flatten
30
31
  end
31
32
 
32
- private
33
-
34
- #
35
- # Create the trigger
36
- #
37
- # @param [Object] item item to create trigger for
38
- # @param [Item State] from state to restrict trigger to
39
- # @param [Item State] to state to restrict trigger to
40
- # @param attach attachment
41
33
  #
42
- # @return [Trigger] OpenHAB triggers
34
+ # Creates updated triggers
43
35
  #
44
- def update_trigger(item:, to:, attach:)
45
- case to
46
- when Range then create_update_range_trigger(item: item, to: to, attach: attach)
47
- when Proc then create_update_proc_trigger(item: item, to: to, attach: attach)
48
- else create_update_trigger(item: item, to: to, attach: attach)
36
+ class Updated < Trigger
37
+ include OpenHAB::Log
38
+
39
+ #
40
+ # Create the trigger
41
+ #
42
+ # @param [Object] item item to create trigger for
43
+ # @param [Item State] from state to restrict trigger to
44
+ # @param [Item State] to state to restrict trigger to
45
+ # @param attach attachment
46
+ #
47
+ # @return [Trigger] OpenHAB triggers
48
+ #
49
+ def trigger(item:, to:, attach:)
50
+ case to
51
+ when Range then range_trigger(item: item, to: to, attach: attach)
52
+ when Proc then proc_trigger(item: item, to: to, attach: attach)
53
+ else update_trigger(item: item, to: to, attach: attach)
54
+ end
49
55
  end
50
- end
51
56
 
52
- #
53
- # Creates a trigger with a range condition on the 'to' field
54
- # @param [Object] item to create changed trigger on
55
- # @param [Object] to state restrict trigger to
56
- # @param [Object] attach to trigger
57
- # @return [Trigger] OpenHAB trigger
58
- #
59
- def create_update_range_trigger(item:, to:, attach:)
60
- to, * = Conditions::Proc.range_procs(to)
61
- create_update_proc_trigger(item: item, to: to, attach: attach)
62
- end
57
+ private
63
58
 
64
- #
65
- # Creates a trigger with a proc condition on the 'to' field
66
- # @param [Object] item to create changed trigger on
67
- # @param [Object] to state restrict trigger to
68
- # @param [Object] attach to trigger
69
- # @return [Trigger] OpenHAB trigger
70
- #
71
- def create_update_proc_trigger(item:, to:, attach:)
72
- create_update_trigger(item: item, to: nil, attach: attach).tap do |trigger|
73
- @trigger_conditions[trigger.id] = Conditions::Proc.new(to: to)
59
+ # @return [String] A thing status update trigger
60
+ THING_UPDATE = 'core.ThingStatusUpdateTrigger'
61
+
62
+ # @return [String] An item state update trigger
63
+ ITEM_STATE_UPDATE = 'core.ItemStateUpdateTrigger'
64
+
65
+ # @return [String] A group state update trigger for items in the group
66
+ GROUP_STATE_UPDATE = 'core.GroupStateUpdateTrigger'
67
+
68
+ #
69
+ # Creates a trigger with a range condition on the 'to' field
70
+ # @param [Object] item to create changed trigger on
71
+ # @param [Object] to state restrict trigger to
72
+ # @param [Object] attach to trigger
73
+ # @return [Trigger] OpenHAB trigger
74
+ #
75
+ def range_trigger(item:, to:, attach:)
76
+ to, * = Conditions::Proc.range_procs(to)
77
+ proc_trigger(item: item, to: to, attach: attach)
74
78
  end
75
- end
76
79
 
77
- #
78
- # Create a trigger for updates
79
- #
80
- # @param [Object] item Type of item [Group,Thing,Item] to create update trigger for
81
- # @param [State] to_state state restriction on trigger
82
- #
83
- # @return [Trigger] OpenHAB triggers
84
- #
85
- def create_update_trigger(item:, to:, attach:)
86
- trigger, config = case item
87
- when OpenHAB::DSL::Items::GroupItem::GroupMembers then group_update(item: item, to: to)
88
- when Thing then thing_update(thing: item, to: to)
89
- else item_update(item: item, to: to)
90
- end
91
- append_trigger(trigger, config, attach: attach)
92
- end
80
+ #
81
+ # Creates a trigger with a proc condition on the 'to' field
82
+ # @param [Object] item to create changed trigger on
83
+ # @param [Object] to state restrict trigger to
84
+ # @param [Object] attach to trigger
85
+ # @return [Trigger] OpenHAB trigger
86
+ #
87
+ def proc_trigger(item:, to:, attach:)
88
+ conditions = Conditions::Proc.new(to: to)
89
+ update_trigger(item: item, to: nil, attach: attach, conditions: conditions)
90
+ end
93
91
 
94
- #
95
- # Create an update trigger for an item
96
- #
97
- # @param [Item] item to create trigger for
98
- # @param [State] to_state optional state restriction for target
99
- #
100
- # @return [Array<Hash,String>] first element is a String specifying trigger type
101
- # second element is a Hash configuring trigger
102
- #
103
- def item_update(item:, to:)
104
- config = { 'itemName' => item.name }
105
- config['state'] = to.to_s unless to.nil?
106
- trigger = Trigger::ITEM_STATE_UPDATE
107
- [trigger, config]
108
- end
92
+ #
93
+ # Create a trigger for updates
94
+ #
95
+ # @param [Object] item Type of item [Group,Thing,Item] to create update trigger for
96
+ # @param [State] to_state state restriction on trigger
97
+ #
98
+ # @return [Trigger] OpenHAB triggers
99
+ #
100
+ def update_trigger(item:, to:, attach: nil, conditions: nil)
101
+ type, config = case item
102
+ when OpenHAB::DSL::Items::GroupItem::GroupMembers then group_update(item: item, to: to)
103
+ when Thing then thing_update(thing: item, to: to)
104
+ else item_update(item: item, to: to)
105
+ end
106
+ append_trigger(type: type, config: config, attach: attach, conditions: conditions)
107
+ end
109
108
 
110
- #
111
- # Create an update trigger for a group
112
- #
113
- # @param [Item] item to create trigger for
114
- # @param [State] to_state optional state restriction for target
115
- #
116
- # @return [Array<Hash,String>] first element is a String specifying trigger type
117
- # second element is a Hash configuring trigger
118
- #
119
- def group_update(item:, to:)
120
- config = { 'groupName' => item.group.name }
121
- config['state'] = to.to_s unless to.nil?
122
- trigger = Trigger::GROUP_STATE_UPDATE
123
- [trigger, config]
124
- end
109
+ #
110
+ # Create an update trigger for an item
111
+ #
112
+ # @param [Item] item to create trigger for
113
+ # @param [State] to_state optional state restriction for target
114
+ #
115
+ # @return [Array<Hash,String>] first element is a String specifying trigger type
116
+ # second element is a Hash configuring trigger
117
+ #
118
+ def item_update(item:, to:)
119
+ config = { 'itemName' => item.name }
120
+ config['state'] = to.to_s unless to.nil?
121
+ [ITEM_STATE_UPDATE, config]
122
+ end
125
123
 
126
- #
127
- # Create an update trigger for a thing
128
- #
129
- # @param [Thing] thing to create trigger for
130
- # @param [State] to_state optional state restriction for target
131
- #
132
- # @return [Array<Hash,String>] first element is a String specifying trigger type
133
- # second element is a Hash configuring trigger
134
- #
135
- def thing_update(thing:, to:)
136
- trigger_for_thing(thing, Trigger::THING_UPDATE, to)
124
+ #
125
+ # Create an update trigger for a group
126
+ #
127
+ # @param [Item] item to create trigger for
128
+ # @param [State] to_state optional state restriction for target
129
+ #
130
+ # @return [Array<Hash,String>] first element is a String specifying trigger type
131
+ # second element is a Hash configuring trigger
132
+ #
133
+ def group_update(item:, to:)
134
+ config = { 'groupName' => item.group.name }
135
+ config['state'] = to.to_s unless to.nil?
136
+ [GROUP_STATE_UPDATE, config]
137
+ end
138
+
139
+ #
140
+ # Create an update trigger for a thing
141
+ #
142
+ # @param [Thing] thing to create trigger for
143
+ # @param [State] to_state optional state restriction for target
144
+ #
145
+ # @return [Array<Hash,String>] first element is a String specifying trigger type
146
+ # second element is a Hash configuring trigger
147
+ #
148
+ def thing_update(thing:, to:)
149
+ trigger_for_thing(thing: thing, type: THING_UPDATE, to: to)
150
+ end
137
151
  end
138
152
  end
139
153
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openhab/log/logger'
4
+ require 'openhab/dsl/rules/triggers/trigger'
5
+
6
+ module OpenHAB
7
+ module DSL
8
+ module Rules
9
+ #
10
+ # Module holds rule triggers
11
+ #
12
+ module Triggers
13
+ #
14
+ # Module for watching directories/files
15
+ #
16
+
17
+ #
18
+ # Create a trigger to watch a path
19
+ #
20
+ # @param [String] path to watch
21
+ #
22
+ # @return [Trigger] Trigger object
23
+ #
24
+ def watch(path, glob: '*', for: %i[created deleted modified], attach: nil)
25
+ glob, path = Watch.glob_for_path(Pathname.new(path), glob)
26
+ types = [binding.local_variable_get(:for)].flatten
27
+ config = { path: path.to_s, types: types.map(&:to_s), glob: glob.to_s }
28
+
29
+ logger.state 'Creating a watch trigger', path: path, glob: glob, types: types
30
+ Watch.new(rule_triggers: @rule_triggers).trigger(config: config, attach: attach)
31
+ end
32
+
33
+ #
34
+ # Creates watch triggers
35
+ #
36
+ class Watch < Trigger
37
+ # Characters in an fnmatch compatible glob
38
+ GLOB_CHARS = ['**', '*', '?', '[', ']', '{', '}'].freeze
39
+ private_constant :GLOB_CHARS
40
+
41
+ #
42
+ # Automatically creates globs for supplied paths if necessary
43
+ # @param [Pathname] path to check
44
+ # @param [String] specified glob
45
+ #
46
+ # @return [Pathname,String] Pathname to watch and glob to match
47
+ def self.glob_for_path(path, glob)
48
+ # Checks if the supplied pathname last element contains a glob char
49
+ if GLOB_CHARS.any? { |char| path.basename.to_s.include? char }
50
+ # Splits the supplied pathname into a glob string and parent path
51
+ [path.basename.to_s, path.parent]
52
+ elsif path.file? || !path.exist?
53
+ # glob string matching end of Pathname and parent path
54
+ ["*/#{path.basename}", path.parent]
55
+ else
56
+ [glob, path]
57
+ end
58
+ end
59
+
60
+ #
61
+ # Create a watch 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(config:, attach:)
68
+ append_trigger(type: WatchHandler::WATCH_TRIGGER_MODULE_ID,
69
+ config: config,
70
+ attach: attach)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -14,32 +14,15 @@ module OpenHAB
14
14
  #
15
15
  # Module for watching directories/files
16
16
  #
17
- module Watch
17
+ module WatchHandler
18
18
  include OpenHAB::Log
19
19
 
20
- # Characters in an fnmatch compatible glob
21
- GLOB_CHARS = ['**', '*', '?', '[', ']', '{', '}'].freeze
22
-
23
- #
24
- # Creates trigger types and trigger type factories for OpenHAB
25
- #
26
- def self.add_watch_handler
27
- java_import org.openhab.core.automation.type.TriggerType
28
- OpenHAB::Core.automation_manager.add_trigger_handler(
29
- OpenHAB::DSL::Rules::Triggers::Watch::WATCH_TRIGGER_MODULE_ID,
30
- OpenHAB::DSL::Rules::Triggers::Watch::WatchTriggerHandlerFactory.new
31
- )
32
-
33
- OpenHAB::Core.automation_manager.add_trigger_type(watch_trigger_type)
34
- OpenHAB::Log.logger(self).trace('Added watch trigger handler')
35
- end
36
-
37
20
  #
38
21
  # Creates trigger types and trigger type factories for OpenHAB
39
22
  #
40
23
  private_class_method def self.watch_trigger_type
41
24
  TriggerType.new(
42
- OpenHAB::DSL::Rules::Triggers::Watch::WATCH_TRIGGER_MODULE_ID,
25
+ WATCH_TRIGGER_MODULE_ID,
43
26
  nil,
44
27
  'A path change event is detected',
45
28
  'Triggers when a path change event is detected',
@@ -163,44 +146,19 @@ module OpenHAB
163
146
  WatchTriggerHandler.new(trigger)
164
147
  end
165
148
  end
166
- end
167
149
 
168
- #
169
- # Create a trigger to watch a path
170
- #
171
- # @param [String] path to watch
172
- #
173
- # @return [Trigger] Trigger object
174
- #
175
- def watch(path, glob: '*', for: %i[created deleted modified], attach: nil)
176
- glob, path = glob_for_path(Pathname.new(path), glob)
177
- types = [binding.local_variable_get(:for)].flatten
178
- conf = { path: path.to_s, types: types.map(&:to_s), glob: glob.to_s }
179
-
180
- logger.trace("Creating a watch trigger for path(#{path}) with glob(#{glob}) for types(#{types})")
181
- append_trigger(OpenHAB::DSL::Rules::Triggers::Watch::WATCH_TRIGGER_MODULE_ID,
182
- conf,
183
- attach: attach)
184
- end
185
-
186
- private
150
+ #
151
+ # Creates trigger types and trigger type factories for OpenHAB
152
+ #
153
+ def self.add_watch_handler
154
+ java_import org.openhab.core.automation.type.TriggerType
155
+ OpenHAB::Core.automation_manager.add_trigger_handler(
156
+ WATCH_TRIGGER_MODULE_ID,
157
+ WatchTriggerHandlerFactory.new
158
+ )
187
159
 
188
- #
189
- # Automatically creates globs for supplied paths if necessary
190
- # @param [Pathname] path to check
191
- # @param [String] specified glob
192
- #
193
- # @return [Pathname,String] Pathname to watch and glob to match
194
- def glob_for_path(path, glob)
195
- # Checks if the supplied pathname last element contains a glob char
196
- if OpenHAB::DSL::Rules::Triggers::Watch::GLOB_CHARS.any? { |char| path.basename.to_s.include? char }
197
- # Splits the supplied pathname into a glob string and parent path
198
- [path.basename.to_s, path.parent]
199
- elsif path.file? || !path.exist?
200
- # glob string matching end of Pathname and parent path
201
- ["*/#{path.basename}", path.parent]
202
- else
203
- [glob, path]
160
+ OpenHAB::Core.automation_manager.add_trigger_type(watch_trigger_type)
161
+ OpenHAB::Log.logger(self).trace('Added watch trigger handler')
204
162
  end
205
163
  end
206
164
  end
@@ -208,4 +166,4 @@ module OpenHAB
208
166
  end
209
167
  end
210
168
  # Add the watch handler to OpenHAB
211
- OpenHAB::DSL::Rules::Triggers::Watch.add_watch_handler
169
+ OpenHAB::DSL::Rules::Triggers::WatchHandler.add_watch_handler
@@ -61,6 +61,22 @@ module OpenHAB
61
61
  define_method("#{level}_enabled?") { @sl4fj_logger.send("is_#{level}_enabled") }
62
62
  end
63
63
 
64
+ #
65
+ # Logs a map of key(value) with an optional preamble at trace level
66
+ # @param [String] preamble to put at start of log message
67
+ # @param [Hash] key and values to log
68
+ def state(preamble = 'State:', **kwargs)
69
+ return unless trace_enabled?
70
+
71
+ states = kwargs.transform_keys(&:to_s)
72
+ .transform_keys(&:capitalize)
73
+ .transform_values { |v| v.nil? ? 'nil' : v }
74
+ .map { |k, v| "#{k}(#{v})" }
75
+ .join(' ')
76
+ trace "#{preamble} #{states}"
77
+ end
78
+ alias states state
79
+
64
80
  #
65
81
  # Cleans the backtrace of an error to remove internal calls. If logging is set
66
82
  # to debug or lower, the full backtrace is kept
@@ -5,5 +5,5 @@
5
5
  #
6
6
  module OpenHAB
7
7
  # @return [String] Version of OpenHAB helper libraries
8
- VERSION = '4.30.3'
8
+ VERSION = '4.30.4'
9
9
  end