openhab-scripting 5.47.2 → 5.47.3
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.
- checksums.yaml +4 -4
- data/lib/openhab/core/items/metadata/hash.rb +4 -1
- data/lib/openhab/core/items/metadata/provider.rb +5 -2
- data/lib/openhab/core/items/provider.rb +2 -0
- data/lib/openhab/core/provider.rb +12 -2
- data/lib/openhab/core/sitemaps/compatibility.rb +76 -0
- data/lib/openhab/core/sitemaps/model.rb +131 -0
- data/lib/openhab/core/sitemaps/provider.rb +38 -166
- data/lib/openhab/core/sitemaps/registry.rb +151 -0
- data/lib/openhab/core/sitemaps/sitemap.rb +10 -0
- data/lib/openhab/core/things/provider.rb +2 -0
- data/lib/openhab/core.rb +7 -2
- data/lib/openhab/dsl/rules/builder.rb +1 -1
- data/lib/openhab/dsl/sitemaps/builder.rb +29 -23
- data/lib/openhab/dsl/version.rb +1 -1
- data/lib/openhab/dsl.rb +2 -3
- data/lib/openhab/rspec/hooks.rb +2 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2fd2aa14f2bc1f8ac43aa9cd55dd95c24395367e95f16732b3911d77d707e12d
|
|
4
|
+
data.tar.gz: 35b524b0b291fd2fdec0f25290274be648697860e17df54ad8c06e0f643fbc0f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 12f55e491a8c72447d539c45ddd74db196c80c92bbec37e0f0aa0277b53633f14c78d15634b5814b75fd715844096d53f324d443806e0d5f30895addc50c44b2
|
|
7
|
+
data.tar.gz: 3eec0b52f59468f948ee68269d47a3d537ff425795f018f6ed4dd493c6caf8912482125db293ef1f9aac571c93b3a02fd084417f0a6a9a1aafd2bbb7dd25bbd7
|
|
@@ -264,7 +264,10 @@ module OpenHAB
|
|
|
264
264
|
# in the meantime, force the serialization round-trip right now
|
|
265
265
|
#
|
|
266
266
|
def javaify
|
|
267
|
-
|
|
267
|
+
managed_provider = Provider.registry.managed_provider
|
|
268
|
+
# @deprecated remove the next line when dropping openHAB 5.1
|
|
269
|
+
managed_provider = managed_provider.get if managed_provider.is_a?(java.util.Optional)
|
|
270
|
+
mapper = managed_provider.storage.entityMapper
|
|
268
271
|
|
|
269
272
|
@metadata = mapper.from_json(mapper.to_json_tree(@metadata), Metadata.java_class)
|
|
270
273
|
end
|
|
@@ -22,8 +22,11 @@ module OpenHAB
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# see Hash#javaify
|
|
25
|
-
registry.managed_provider
|
|
26
|
-
|
|
25
|
+
managed_provider = registry.managed_provider
|
|
26
|
+
# @deprecated remove the next line when dropping openHAB 5.1
|
|
27
|
+
managed_provider = managed_provider.get if managed_provider.is_a?(java.util.Optional)
|
|
28
|
+
managed_provider.class.field_reader :storage
|
|
29
|
+
managed_provider.storage.class.field_reader :entityMapper
|
|
27
30
|
|
|
28
31
|
#
|
|
29
32
|
# Removes all metadata of a given item.
|
|
@@ -87,7 +87,10 @@ module OpenHAB
|
|
|
87
87
|
when nil, :transient
|
|
88
88
|
instance
|
|
89
89
|
when :persistent
|
|
90
|
-
registry.managed_provider
|
|
90
|
+
provider = registry.managed_provider
|
|
91
|
+
# @deprecated remove the next line when dropping openHAB 5.1
|
|
92
|
+
provider = provider.get if provider.is_a?(java.util.Optional)
|
|
93
|
+
provider
|
|
91
94
|
when org.openhab.core.common.registry.ManagedProvider
|
|
92
95
|
preferred_provider
|
|
93
96
|
else
|
|
@@ -122,6 +125,7 @@ module OpenHAB
|
|
|
122
125
|
end
|
|
123
126
|
|
|
124
127
|
r = provider = super()
|
|
128
|
+
|
|
125
129
|
if block_given?
|
|
126
130
|
if thread_provider
|
|
127
131
|
DSL.provider(provider) do
|
|
@@ -226,7 +230,7 @@ module OpenHAB
|
|
|
226
230
|
# @!visibility private
|
|
227
231
|
def unregister
|
|
228
232
|
clear
|
|
229
|
-
self.class.registry
|
|
233
|
+
self.class.registry&.remove_provider(self)
|
|
230
234
|
end
|
|
231
235
|
|
|
232
236
|
private
|
|
@@ -236,7 +240,13 @@ module OpenHAB
|
|
|
236
240
|
@elements = java.util.concurrent.ConcurrentHashMap.new
|
|
237
241
|
self.class.registry&.add_provider(self)
|
|
238
242
|
ScriptHandling.script_unloaded(priority: unload_priority) { unregister }
|
|
243
|
+
# @deprecated OH 5.2: Remove the rest of this method when dropping OH 5.1
|
|
244
|
+
extra_initialize
|
|
239
245
|
end
|
|
246
|
+
|
|
247
|
+
# @deprecated OH 5.2: Remove this method when dropping OH 5.1
|
|
248
|
+
# See https://github.com/jruby/jruby/issues/9321 for why this is needed instead of just putting the code in Model::Provider#initialize
|
|
249
|
+
def extra_initialize; end
|
|
240
250
|
end
|
|
241
251
|
end
|
|
242
252
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenHAB
|
|
4
|
+
module Core
|
|
5
|
+
module Sitemaps
|
|
6
|
+
#
|
|
7
|
+
# Compatibility helpers for old model-backed sitemaps and the new core sitemap registry.
|
|
8
|
+
#
|
|
9
|
+
# @deprecated OH 5.2: Remove entire module when dropping OH 5.1, collapsing calls to match the registry branch
|
|
10
|
+
# Don't forget to remove the reference to it in .yardopts
|
|
11
|
+
# @!visibility private
|
|
12
|
+
module Compatibility
|
|
13
|
+
class << self
|
|
14
|
+
def factory
|
|
15
|
+
Provider.factory
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if Provider.registry
|
|
19
|
+
def supported_widget_type?(type)
|
|
20
|
+
return true if type == :sitemap
|
|
21
|
+
|
|
22
|
+
factory.get_supported_widget_types.include?(camelize(type))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create_sitemap(name)
|
|
26
|
+
factory.create_sitemap(name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create_widget(type, parent = nil)
|
|
30
|
+
parent ? factory.create_widget(camelize(type), parent) : factory.create_widget(camelize(type))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def create_rule(_type)
|
|
34
|
+
factory.create_rule
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def set_condition(condition, sign:, state:)
|
|
38
|
+
condition.value = "#{sign}#{state}"
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
def supported_widget_type?(type)
|
|
42
|
+
factory.respond_to?(:"create_#{type}")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def create_sitemap(name)
|
|
46
|
+
factory.create_sitemap.tap { |sitemap| sitemap.name = name }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def create_widget(type, _parent = nil)
|
|
50
|
+
factory.public_send(:"create_#{type}")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def create_rule(type)
|
|
54
|
+
case type
|
|
55
|
+
when :visibility then factory.create_visibility_rule
|
|
56
|
+
when :icon then factory.create_icon_rule
|
|
57
|
+
when :color then factory.create_color_array
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def set_condition(condition, sign:, state:)
|
|
62
|
+
condition.sign = sign
|
|
63
|
+
condition.state = state
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def camelize(type)
|
|
70
|
+
type.to_s.split("_").map(&:capitalize).join
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# @deprecated OH 5.2: Remove entire file when dropping OH 5.1
|
|
4
|
+
# Don't forget to remove the reference to it in .yardopts
|
|
5
|
+
return if OpenHAB::Core::Sitemaps::Provider.registry
|
|
6
|
+
|
|
7
|
+
module OpenHAB
|
|
8
|
+
module Core
|
|
9
|
+
module Sitemaps
|
|
10
|
+
# Adds compatibility shims for the old Xtext based Sitemap model to the newer registry style
|
|
11
|
+
# @!visibility private
|
|
12
|
+
module Model
|
|
13
|
+
org.openhab.core.model.sitemap.sitemap.impl.SitemapImpl.alias_method :uid, :name
|
|
14
|
+
org.openhab.core.model.sitemap.sitemap.impl.SitemapImpl.alias_method :widgets, :children
|
|
15
|
+
|
|
16
|
+
org.openhab.core.model.sitemap.sitemap.impl.LinkableWidgetImpl.alias_method :widgets, :children
|
|
17
|
+
|
|
18
|
+
module Rule
|
|
19
|
+
org.openhab.core.model.sitemap.sitemap.impl.ColorArrayImpl.include(self)
|
|
20
|
+
org.openhab.core.model.sitemap.sitemap.impl.IconRuleImpl.include(self)
|
|
21
|
+
|
|
22
|
+
def argument = arg
|
|
23
|
+
|
|
24
|
+
def argument=(value)
|
|
25
|
+
self.arg = value
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
module Widget
|
|
30
|
+
org.openhab.core.model.sitemap.sitemap.impl.WidgetImpl.prepend(self)
|
|
31
|
+
|
|
32
|
+
def icon
|
|
33
|
+
static_icon || super
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def icon=(value)
|
|
37
|
+
if instance_variable_defined?(:@static_icon) && @static_icon
|
|
38
|
+
self.static_icon = value
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
super
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def static_icon=(value)
|
|
45
|
+
# Disable "instance vars on non-persistent Java type"
|
|
46
|
+
original_verbose = $VERBOSE
|
|
47
|
+
$VERBOSE = nil
|
|
48
|
+
if value == true
|
|
49
|
+
@static_icon = true
|
|
50
|
+
return
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
super
|
|
54
|
+
ensure
|
|
55
|
+
$VERBOSE = original_verbose
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
module Condition
|
|
60
|
+
org.openhab.core.model.sitemap.sitemap.impl.ConditionImpl.include(self)
|
|
61
|
+
|
|
62
|
+
def value = "#{sign}#{state}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
module Provider
|
|
66
|
+
def self.prepended(klass)
|
|
67
|
+
klass.include org.openhab.core.model.sitemap.SitemapProvider
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
SUFFIX = ".sitemap"
|
|
71
|
+
private_constant :SUFFIX
|
|
72
|
+
|
|
73
|
+
def getSitemap = get # rubocop:disable Naming/MethodName
|
|
74
|
+
|
|
75
|
+
# rubocop:disable Naming/MethodName
|
|
76
|
+
def getSitemapNames
|
|
77
|
+
@elements.key_set
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def addModelChangeListener(listener)
|
|
81
|
+
@listeners.add(listener)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def removeModelChangeListener(listener)
|
|
85
|
+
@listeners.remove(listener)
|
|
86
|
+
end
|
|
87
|
+
# rubocop:enable Naming/MethodName
|
|
88
|
+
|
|
89
|
+
def unregister
|
|
90
|
+
clear
|
|
91
|
+
@registration.unregister
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def update(sitemap)
|
|
95
|
+
if sitemap.respond_to?(:to_str)
|
|
96
|
+
sitemap = get(sitemap).tap do |obj|
|
|
97
|
+
raise ArgumentError, "Sitemap #{sitemap} not found" unless obj
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
super
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def remove(sitemap)
|
|
104
|
+
sitemap = sitemap.uid if sitemap.respond_to?(:uid)
|
|
105
|
+
super
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def notify_listeners_about_added_element(element)
|
|
111
|
+
model_name = "#{element.name}#{SUFFIX}"
|
|
112
|
+
@listeners.each do |listener|
|
|
113
|
+
listener.modelChanged(model_name, org.openhab.core.model.core.EventType::ADDED)
|
|
114
|
+
listener.modelChanged(model_name, org.openhab.core.model.core.EventType::MODIFIED)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def notify_listeners_about_removed_element(element)
|
|
119
|
+
model_name = "#{element.name}#{SUFFIX}"
|
|
120
|
+
@listeners.each { |listener| listener.modelChanged(model_name, org.openhab.core.model.core.EventType::REMOVED) }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def notify_listeners_about_updated_element(_old_element, element)
|
|
124
|
+
model_name = "#{element.name}#{SUFFIX}"
|
|
125
|
+
@listeners.each { |listener| listener.modelChanged(model_name, org.openhab.core.model.core.EventType::MODIFIED) }
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "singleton"
|
|
4
|
-
|
|
5
3
|
module OpenHAB
|
|
6
4
|
module Core
|
|
7
5
|
#
|
|
@@ -12,191 +10,65 @@ module OpenHAB
|
|
|
12
10
|
# Provides sitemaps created in Ruby to openHAB
|
|
13
11
|
#
|
|
14
12
|
class Provider < Core::Provider
|
|
15
|
-
SUFFIX = ".sitemap"
|
|
16
|
-
private_constant :SUFFIX
|
|
17
|
-
|
|
18
13
|
class << self
|
|
19
|
-
#
|
|
14
|
+
#
|
|
15
|
+
# The Sitemap registry
|
|
16
|
+
#
|
|
17
|
+
# @return [org.openhab.core.sitemap.registry.SitemapRegistry, nil]
|
|
18
|
+
#
|
|
19
|
+
# @since 5.2.0
|
|
20
|
+
#
|
|
20
21
|
def registry
|
|
21
|
-
|
|
22
|
+
return @registry if instance_variable_defined?(:@registry)
|
|
23
|
+
|
|
24
|
+
@registry = OSGi.service("org.openhab.core.sitemap.registry.SitemapRegistry")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @!visibility private
|
|
28
|
+
def factory
|
|
29
|
+
# @deprecated OH 5.2: remove the non-registry branch when dropping OH 5.1
|
|
30
|
+
@factory ||= if registry
|
|
31
|
+
OSGi.service("org.openhab.core.sitemap.registry.SitemapFactory")
|
|
32
|
+
else
|
|
33
|
+
org.openhab.core.model.sitemap.sitemap.SitemapFactory.eINSTANCE
|
|
34
|
+
end
|
|
22
35
|
end
|
|
23
36
|
end
|
|
24
37
|
|
|
25
|
-
|
|
38
|
+
# @deprecated OH 5.2: remove the non-registry branch when dropping OH 5.1
|
|
39
|
+
if registry
|
|
40
|
+
require_relative "sitemap"
|
|
41
|
+
|
|
42
|
+
include org.openhab.core.sitemap.registry.SitemapProvider
|
|
43
|
+
else
|
|
44
|
+
require_relative "model"
|
|
45
|
+
|
|
46
|
+
prepend Model::Provider
|
|
47
|
+
end
|
|
26
48
|
|
|
27
|
-
# @!visibility private
|
|
28
49
|
alias_method :getSitemap, :get # rubocop:disable Naming/MethodName
|
|
29
50
|
|
|
30
51
|
# rubocop:disable Naming/MethodName
|
|
31
|
-
|
|
32
52
|
# @!visibility private
|
|
33
53
|
def getSitemapNames
|
|
34
54
|
@elements.key_set
|
|
35
55
|
end
|
|
36
|
-
|
|
37
|
-
# @!visibility private
|
|
38
|
-
def addModelChangeListener(listener)
|
|
39
|
-
@listeners.add(listener)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# @!visibility private
|
|
43
|
-
def removeModelChangeListener(listener)
|
|
44
|
-
@listeners.remove(listener)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
56
|
# rubocop:enable Naming/MethodName
|
|
48
57
|
|
|
49
|
-
# @!visibility private
|
|
50
|
-
# Override this because we don't have a registry
|
|
51
|
-
def unregister
|
|
52
|
-
clear
|
|
53
|
-
@registration.unregister
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
# Enter the Sitemap Builder DSL.
|
|
58
|
-
#
|
|
59
|
-
# @param update [true, false] When true, existing sitemaps with the same name will be updated.
|
|
60
|
-
# @yield Block executed in the context of a {DSL::Sitemaps::Builder}
|
|
61
|
-
# @return [void]
|
|
62
|
-
#
|
|
63
|
-
# @see DSL::Sitemaps::Builder
|
|
64
|
-
#
|
|
65
|
-
# @example
|
|
66
|
-
# sitemaps.build do
|
|
67
|
-
# sitemap "default", label: "My Residence" do
|
|
68
|
-
# frame label: "Control" do
|
|
69
|
-
# text label: "Climate", icon: "if:mdi:home-thermometer-outline" do
|
|
70
|
-
# frame label: "Main Floor" do
|
|
71
|
-
# # colors are set with a hash, with key being condition, and value being the color
|
|
72
|
-
# # The :default key is used when no other condition matches
|
|
73
|
-
# text item: MainFloor_AmbTemp,
|
|
74
|
-
# label_color: "purple", # A simple string can be used when no conditions are needed
|
|
75
|
-
# value_color: { ">=90" => "red", "<=70" => "blue", :default => "black" }
|
|
76
|
-
#
|
|
77
|
-
# # If item name is not provided in the condition, it will default to the widget's Item
|
|
78
|
-
# # The operator will default to == if not specified
|
|
79
|
-
# switch item: MainFloorThermostat_TargetMode, label: "Mode",
|
|
80
|
-
# mappings: %w[off auto cool heat],
|
|
81
|
-
# value_color: { "cool" => "blue", "heat" => "red", :default => "black" }
|
|
82
|
-
#
|
|
83
|
-
# # an array of conditions are AND'd together
|
|
84
|
-
# setpoint item: MainFloorThermostat_SetPoint, label: "Set Point",
|
|
85
|
-
# value_color: {
|
|
86
|
-
# ["MainFloorThermostat_TargetMode!=off", ">80"] => "red", # red if mode!=off AND setpoint > 80
|
|
87
|
-
# ["MainFloorThermostat_TargetMode!=off", ">74"] => "yellow",
|
|
88
|
-
# ["MainFloorThermostat_TargetMode!=off", ">70"] => "green",
|
|
89
|
-
# "MainFloorThermostat_TargetMode!=off" => "blue",
|
|
90
|
-
# :default => "gray"
|
|
91
|
-
# }
|
|
92
|
-
# end
|
|
93
|
-
# frame label: "Basement" do
|
|
94
|
-
# text item: Basement_AmbTemp
|
|
95
|
-
# switch item: BasementThermostat_TargetMode, label: "Mode",
|
|
96
|
-
# mappings: { OFF: "off", COOL: "cool", HEAT: "heat" }
|
|
97
|
-
#
|
|
98
|
-
# # Conditions within a nested array are AND'd together (requires openHAB 4.1)
|
|
99
|
-
# setpoint item: BasementThermostat_SetPoint, label: "Set Point",
|
|
100
|
-
# visibility: [["BasementThermostat_TargetMode!=off", "Vacation_Switch==OFF"]]
|
|
101
|
-
#
|
|
102
|
-
# # Additional elements are OR'd
|
|
103
|
-
# # The following visibility conditions are evaluated as:
|
|
104
|
-
# # (BasementThermostat_TargetMode!=off AND Vacation_Switch==OFF) OR Verbose_Mode==ON
|
|
105
|
-
# setpoint item: BasementThermostat_SetPoint, label: "Set Point",
|
|
106
|
-
# visibility: [
|
|
107
|
-
# ["BasementThermostat_TargetMode!=off", "Vacation_Switch==OFF"],
|
|
108
|
-
# "Verbose_Mode==ON"
|
|
109
|
-
# ]
|
|
110
|
-
# end
|
|
111
|
-
# end
|
|
112
|
-
# end
|
|
113
|
-
# end
|
|
114
|
-
# end
|
|
115
|
-
#
|
|
116
|
-
# @example
|
|
117
|
-
# def add_tv(builder, tv)
|
|
118
|
-
# builder.frame label: tv.location.label do
|
|
119
|
-
# builder.switch item: tv.points(Semantics::Switch), label: "Power"
|
|
120
|
-
# end
|
|
121
|
-
# end
|
|
122
|
-
#
|
|
123
|
-
# sitemaps.build do |builder|
|
|
124
|
-
# builder.sitemap "tvs", label: "TVs" do
|
|
125
|
-
# items.equipments(Semantics::TV).each do |tv|
|
|
126
|
-
# add_tv(builder, tv)
|
|
127
|
-
# end
|
|
128
|
-
# end
|
|
129
|
-
# end
|
|
130
|
-
#
|
|
131
|
-
def build(update: true, &block)
|
|
132
|
-
builder_proxy = SimpleDelegator.new(nil) if block.arity == 1
|
|
133
|
-
builder = DSL::Sitemaps::Builder.new(self, builder_proxy, update:)
|
|
134
|
-
if block.arity == 1
|
|
135
|
-
builder_proxy.__setobj__(builder)
|
|
136
|
-
DSL::ThreadLocal.thread_local(openhab_create_dummy_items: true) do
|
|
137
|
-
yield builder_proxy
|
|
138
|
-
end
|
|
139
|
-
else
|
|
140
|
-
builder.instance_eval(&block)
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
#
|
|
145
|
-
# Notify listeners about updated sitemap
|
|
146
|
-
#
|
|
147
|
-
# @param [String, org.openhab.core.model.sitemap.sitemap.Sitemap] sitemap The sitemap to update.
|
|
148
|
-
# @return [void]
|
|
149
|
-
#
|
|
150
|
-
def update(sitemap)
|
|
151
|
-
if sitemap.respond_to?(:to_str)
|
|
152
|
-
sitemap = get(sitemap).tap do |obj|
|
|
153
|
-
raise ArgumentError, "Sitemap #{sitemap} not found" unless obj
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
super
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
#
|
|
160
|
-
# Remove a sitemap.
|
|
161
|
-
#
|
|
162
|
-
# @param [String, org.openhab.core.model.sitemap.sitemap.Sitemap] sitemap
|
|
163
|
-
# @return [Boolean] If a sitemap was removed
|
|
164
|
-
#
|
|
165
|
-
def remove(sitemap)
|
|
166
|
-
sitemap = sitemap.uid if sitemap.respond_to?(:uid)
|
|
167
|
-
super
|
|
168
|
-
end
|
|
169
|
-
|
|
170
58
|
private
|
|
171
59
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
60
|
+
# @deprecated OH 5.2: Remove this entire method when dropping OH 5.1
|
|
61
|
+
# I can't put this in Model::Provider#initialize, due to https://github.com/jruby/jruby/issues/9321
|
|
62
|
+
def extra_initialize
|
|
63
|
+
return if self.class.registry
|
|
175
64
|
|
|
65
|
+
@listeners = java.util.concurrent.CopyOnWriteArraySet.new
|
|
176
66
|
@registration = OSGi.register_service(self, org.openhab.core.model.sitemap.SitemapProvider)
|
|
177
67
|
end
|
|
178
|
-
|
|
179
|
-
def notify_listeners_about_added_element(element)
|
|
180
|
-
model_name = "#{element.name}#{SUFFIX}"
|
|
181
|
-
@listeners.each do |l|
|
|
182
|
-
l.modelChanged(model_name, org.openhab.core.model.core.EventType::ADDED)
|
|
183
|
-
# Ensure that when a script is reloaded, the sitemap is updated.
|
|
184
|
-
# This is because our listener, org.openhab.core.io.rest.sitemap.SitemapSubscriptionService
|
|
185
|
-
# only handles MODIFIED events in its modelChanged() method.
|
|
186
|
-
l.modelChanged(model_name, org.openhab.core.model.core.EventType::MODIFIED)
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def notify_listeners_about_removed_element(element)
|
|
191
|
-
model_name = "#{element.name}#{SUFFIX}"
|
|
192
|
-
@listeners.each { |l| l.modelChanged(model_name, org.openhab.core.model.core.EventType::REMOVED) }
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def notify_listeners_about_updated_element(_old_element, element)
|
|
196
|
-
model_name = "#{element.name}#{SUFFIX}"
|
|
197
|
-
@listeners.each { |l| l.modelChanged(model_name, org.openhab.core.model.core.EventType::MODIFIED) }
|
|
198
|
-
end
|
|
199
68
|
end
|
|
200
69
|
end
|
|
201
70
|
end
|
|
202
71
|
end
|
|
72
|
+
|
|
73
|
+
# @deprecated OH 5.2: Remove require when dropping OH 5.1
|
|
74
|
+
require_relative "compatibility"
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "singleton"
|
|
4
|
+
|
|
5
|
+
require_relative "provider"
|
|
6
|
+
|
|
7
|
+
module OpenHAB
|
|
8
|
+
module Core
|
|
9
|
+
module Sitemaps
|
|
10
|
+
#
|
|
11
|
+
# Provides access to all openHAB sitemap definitions, and acts like an array.
|
|
12
|
+
#
|
|
13
|
+
class Registry
|
|
14
|
+
include LazyArray
|
|
15
|
+
include Singleton
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# Gets a specific sitemap definition.
|
|
19
|
+
#
|
|
20
|
+
# @param [String] name Sitemap name
|
|
21
|
+
# @return [Sitemap, nil]
|
|
22
|
+
#
|
|
23
|
+
def [](name)
|
|
24
|
+
# @deprecated OH 5.2: Remove the registry check when dropping OH 5.1
|
|
25
|
+
Provider.registry ? Provider.registry.get(name) : Provider.current.get(name)
|
|
26
|
+
end
|
|
27
|
+
alias_method :include?, :[]
|
|
28
|
+
alias_method :key?, :[]
|
|
29
|
+
# @deprecated
|
|
30
|
+
alias_method :has_key?, :[]
|
|
31
|
+
|
|
32
|
+
#
|
|
33
|
+
# Explicit conversion to array.
|
|
34
|
+
#
|
|
35
|
+
# @return [Array<Sitemap>]
|
|
36
|
+
#
|
|
37
|
+
def to_a
|
|
38
|
+
# @deprecated OH 5.2: Remove the registry check when dropping OH 5.1
|
|
39
|
+
Provider.registry ? Provider.registry.all.to_a : Provider.current.all.to_a
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
# Enter the Sitemap Builder DSL.
|
|
44
|
+
#
|
|
45
|
+
# @param update [true, false] When true, existing sitemaps with the same name will be updated.
|
|
46
|
+
# @yield Block executed in the context of a {DSL::Sitemaps::Builder}
|
|
47
|
+
# @return [void]
|
|
48
|
+
#
|
|
49
|
+
# @see DSL::Sitemaps::Builder
|
|
50
|
+
#
|
|
51
|
+
# @example
|
|
52
|
+
# sitemaps.build do
|
|
53
|
+
# sitemap "default", label: "My Residence" do
|
|
54
|
+
# frame label: "Control" do
|
|
55
|
+
# text label: "Climate", icon: "if:mdi:home-thermometer-outline" do
|
|
56
|
+
# frame label: "Main Floor" do
|
|
57
|
+
# # colors are set with a hash, with key being condition, and value being the color
|
|
58
|
+
# # The :default key is used when no other condition matches
|
|
59
|
+
# text item: MainFloor_AmbTemp,
|
|
60
|
+
# label_color: "purple", # A simple string can be used when no conditions are needed
|
|
61
|
+
# value_color: { ">=90" => "red", "<=70" => "blue", :default => "black" }
|
|
62
|
+
#
|
|
63
|
+
# # If item name is not provided in the condition, it will default to the widget's Item
|
|
64
|
+
# # The operator will default to == if not specified
|
|
65
|
+
# switch item: MainFloorThermostat_TargetMode, label: "Mode",
|
|
66
|
+
# mappings: %w[off auto cool heat],
|
|
67
|
+
# value_color: { "cool" => "blue", "heat" => "red", :default => "black" }
|
|
68
|
+
#
|
|
69
|
+
# # an array of conditions are AND'd together
|
|
70
|
+
# setpoint item: MainFloorThermostat_SetPoint, label: "Set Point",
|
|
71
|
+
# value_color: {
|
|
72
|
+
# ["MainFloorThermostat_TargetMode!=off", ">80"] => "red", # red if mode!=off AND setpoint > 80
|
|
73
|
+
# ["MainFloorThermostat_TargetMode!=off", ">74"] => "yellow",
|
|
74
|
+
# ["MainFloorThermostat_TargetMode!=off", ">70"] => "green",
|
|
75
|
+
# "MainFloorThermostat_TargetMode!=off" => "blue",
|
|
76
|
+
# :default => "gray"
|
|
77
|
+
# }
|
|
78
|
+
# end
|
|
79
|
+
# frame label: "Basement" do
|
|
80
|
+
# text item: Basement_AmbTemp
|
|
81
|
+
# switch item: BasementThermostat_TargetMode, label: "Mode",
|
|
82
|
+
# mappings: { OFF: "off", COOL: "cool", HEAT: "heat" }
|
|
83
|
+
#
|
|
84
|
+
# # Conditions within a nested array are AND'd together (requires openHAB 4.1)
|
|
85
|
+
# setpoint item: BasementThermostat_SetPoint, label: "Set Point",
|
|
86
|
+
# visibility: [["BasementThermostat_TargetMode!=off", "Vacation_Switch==OFF"]]
|
|
87
|
+
#
|
|
88
|
+
# # Additional elements are OR'd
|
|
89
|
+
# # The following visibility conditions are evaluated as:
|
|
90
|
+
# # (BasementThermostat_TargetMode!=off AND Vacation_Switch==OFF) OR Verbose_Mode==ON
|
|
91
|
+
# setpoint item: BasementThermostat_SetPoint, label: "Set Point",
|
|
92
|
+
# visibility: [
|
|
93
|
+
# ["BasementThermostat_TargetMode!=off", "Vacation_Switch==OFF"],
|
|
94
|
+
# "Verbose_Mode==ON"
|
|
95
|
+
# ]
|
|
96
|
+
# end
|
|
97
|
+
# end
|
|
98
|
+
# end
|
|
99
|
+
# end
|
|
100
|
+
# end
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# def add_tv(builder, tv)
|
|
104
|
+
# builder.frame label: tv.location.label do
|
|
105
|
+
# builder.switch item: tv.points(Semantics::Switch), label: "Power"
|
|
106
|
+
# end
|
|
107
|
+
# end
|
|
108
|
+
#
|
|
109
|
+
# sitemaps.build do |builder|
|
|
110
|
+
# builder.sitemap "tvs", label: "TVs" do
|
|
111
|
+
# items.equipments(Semantics::TV).each do |tv|
|
|
112
|
+
# add_tv(builder, tv)
|
|
113
|
+
# end
|
|
114
|
+
# end
|
|
115
|
+
# end
|
|
116
|
+
#
|
|
117
|
+
def build(update: true, &block)
|
|
118
|
+
builder_proxy = SimpleDelegator.new(nil) if block.arity == 1
|
|
119
|
+
builder = DSL::Sitemaps::Builder.new(builder_proxy, update:)
|
|
120
|
+
if block.arity == 1
|
|
121
|
+
builder_proxy.__setobj__(builder)
|
|
122
|
+
DSL::ThreadLocal.thread_local(openhab_create_dummy_items: true) do
|
|
123
|
+
yield builder_proxy
|
|
124
|
+
end
|
|
125
|
+
else
|
|
126
|
+
builder.instance_eval(&block)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
#
|
|
131
|
+
# Remove a sitemap.
|
|
132
|
+
#
|
|
133
|
+
# The sitemap must be managed by Ruby.
|
|
134
|
+
#
|
|
135
|
+
# @param [String, Sitemap] sitemap Sitemap or sitemap name
|
|
136
|
+
# @return [Sitemap] The removed sitemap.
|
|
137
|
+
# @raise [RuntimeError] if the sitemap cannot be removed.
|
|
138
|
+
#
|
|
139
|
+
def remove(sitemap)
|
|
140
|
+
sitemap = sitemap.uid if sitemap.respond_to?(:uid)
|
|
141
|
+
old_instance = Provider.current.remove(sitemap)
|
|
142
|
+
old_instance ||= Provider.registry&.remove(sitemap)
|
|
143
|
+
|
|
144
|
+
raise "Cannot remove sitemap #{sitemap}" unless old_instance
|
|
145
|
+
|
|
146
|
+
old_instance
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
data/lib/openhab/core.rb
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
module OpenHAB
|
|
4
4
|
# Contains classes and modules that wrap actual openHAB objects
|
|
5
5
|
module Core
|
|
6
|
+
module Sitemaps
|
|
7
|
+
autoload :Provider, "openhab/core/sitemaps/registry"
|
|
8
|
+
autoload :Registry, "openhab/core/sitemaps/registry"
|
|
9
|
+
end
|
|
10
|
+
|
|
6
11
|
# The openHAB Version. >= 4.1 is required.
|
|
7
12
|
# @return [String]
|
|
8
13
|
VERSION = org.openhab.core.OpenHAB.version.freeze
|
|
@@ -163,8 +168,8 @@ require_relative "core/provider"
|
|
|
163
168
|
Dir[File.expand_path("core/**/*.rb", __dir__)].each do |f|
|
|
164
169
|
# already been run by either a binstub, or the addon
|
|
165
170
|
next if f.end_with?("gem.rb")
|
|
166
|
-
#
|
|
167
|
-
next if f.include?("/metadata/")
|
|
171
|
+
# autoloaded things
|
|
172
|
+
next if f.include?("/metadata/") || f.include?("/sitemaps/")
|
|
168
173
|
|
|
169
174
|
require f
|
|
170
175
|
end
|
|
@@ -6,9 +6,6 @@ module OpenHAB
|
|
|
6
6
|
# Contains the various builders for sitemap elements.
|
|
7
7
|
#
|
|
8
8
|
module Sitemaps
|
|
9
|
-
# @!visibility private
|
|
10
|
-
org.openhab.core.model.sitemap.sitemap.impl.SitemapImpl.alias_method :uid, :name
|
|
11
|
-
|
|
12
9
|
#
|
|
13
10
|
# A sitemap builder allows you to dynamically create openHAB sitemaps at runtime.
|
|
14
11
|
#
|
|
@@ -33,12 +30,12 @@ module OpenHAB
|
|
|
33
30
|
#
|
|
34
31
|
# @see https://www.openhab.org/docs/ui/sitemaps.html
|
|
35
32
|
# @see OpenHAB::DSL.sitemaps
|
|
36
|
-
# @see OpenHAB::Core::Sitemaps::
|
|
33
|
+
# @see OpenHAB::Core::Sitemaps::Registry#build sitemaps.build
|
|
37
34
|
#
|
|
38
35
|
class Builder
|
|
39
36
|
# @!visibility private
|
|
40
|
-
def initialize(
|
|
41
|
-
@provider =
|
|
37
|
+
def initialize(builder_proxy, update:)
|
|
38
|
+
@provider = Core::Sitemaps::Provider.current
|
|
42
39
|
@builder_proxy = builder_proxy
|
|
43
40
|
@update = update
|
|
44
41
|
end
|
|
@@ -144,7 +141,7 @@ module OpenHAB
|
|
|
144
141
|
icon_color: nil,
|
|
145
142
|
visibility: nil,
|
|
146
143
|
&block)
|
|
147
|
-
unless
|
|
144
|
+
unless Core::Sitemaps::Compatibility.supported_widget_type?(type)
|
|
148
145
|
raise ArgumentError,
|
|
149
146
|
"#{type} is not a valid widget type"
|
|
150
147
|
end
|
|
@@ -211,7 +208,7 @@ module OpenHAB
|
|
|
211
208
|
|
|
212
209
|
# @!visibility private
|
|
213
210
|
def build
|
|
214
|
-
widget =
|
|
211
|
+
widget = create_widget
|
|
215
212
|
item = @item
|
|
216
213
|
item = item.name if item.respond_to?(:name)
|
|
217
214
|
widget.item = item if item
|
|
@@ -220,7 +217,8 @@ module OpenHAB
|
|
|
220
217
|
raise ArgumentError, "icon and static_icon are mutually exclusive" if icon && static_icon
|
|
221
218
|
|
|
222
219
|
if static_icon
|
|
223
|
-
widget.static_icon =
|
|
220
|
+
widget.static_icon = true
|
|
221
|
+
widget.icon = static_icon
|
|
224
222
|
elsif icon.is_a?(String)
|
|
225
223
|
widget.icon = @icon
|
|
226
224
|
elsif icon.is_a?(Hash)
|
|
@@ -233,7 +231,7 @@ module OpenHAB
|
|
|
233
231
|
add_colors(widget, :value_color, value_colors)
|
|
234
232
|
add_colors(widget, :icon_color, icon_colors)
|
|
235
233
|
|
|
236
|
-
add_conditions(widget, :visibility, visibilities, :
|
|
234
|
+
add_conditions(widget, :visibility, visibilities, :visibility)
|
|
237
235
|
|
|
238
236
|
widget
|
|
239
237
|
end
|
|
@@ -250,29 +248,33 @@ module OpenHAB
|
|
|
250
248
|
|
|
251
249
|
private
|
|
252
250
|
|
|
251
|
+
def create_widget
|
|
252
|
+
Core::Sitemaps::Compatibility.create_widget(@type)
|
|
253
|
+
end
|
|
254
|
+
|
|
253
255
|
def add_colors(widget, method, colors)
|
|
254
256
|
# ensure that the default color is at the end, and make the conditions nil (no conditions)
|
|
255
257
|
colors.delete(:default)&.tap { |default_color| colors.merge!(nil => default_color) }
|
|
256
258
|
|
|
257
|
-
add_conditions(widget, method, colors.keys, :
|
|
258
|
-
|
|
259
|
+
add_conditions(widget, method, colors.keys, :color) do |rule, key|
|
|
260
|
+
rule.argument = colors[key]
|
|
259
261
|
end
|
|
260
262
|
end
|
|
261
263
|
|
|
262
264
|
def add_icons(widget)
|
|
263
265
|
icon.delete(:default)&.tap { |default_icon| icon.merge!(nil => default_icon) }
|
|
264
|
-
add_conditions(widget, :icon_rules, icon.keys, :
|
|
265
|
-
|
|
266
|
+
add_conditions(widget, :icon_rules, icon.keys, :icon) do |rule, key|
|
|
267
|
+
rule.argument = icon[key]
|
|
266
268
|
end
|
|
267
269
|
end
|
|
268
270
|
|
|
269
|
-
def add_conditions(widget, method, conditions,
|
|
271
|
+
def add_conditions(widget, method, conditions, rule_type)
|
|
270
272
|
return if conditions.empty?
|
|
271
273
|
|
|
272
274
|
object = widget.send(method)
|
|
273
275
|
|
|
274
276
|
conditions.each do |sub_conditions|
|
|
275
|
-
container =
|
|
277
|
+
container = Core::Sitemaps::Compatibility.create_rule(rule_type)
|
|
276
278
|
|
|
277
279
|
add_conditions_to_container(container, sub_conditions)
|
|
278
280
|
yield container, sub_conditions if block_given?
|
|
@@ -293,8 +295,11 @@ module OpenHAB
|
|
|
293
295
|
condition = SitemapBuilder.factory.create_condition
|
|
294
296
|
condition.item = match["item"]
|
|
295
297
|
condition.condition = match["condition"]
|
|
296
|
-
|
|
297
|
-
|
|
298
|
+
Core::Sitemaps::Compatibility.set_condition(
|
|
299
|
+
condition,
|
|
300
|
+
sign: match["sign"],
|
|
301
|
+
state: match["state"]
|
|
302
|
+
)
|
|
298
303
|
container.conditions.add(condition)
|
|
299
304
|
end
|
|
300
305
|
end
|
|
@@ -1165,7 +1170,7 @@ module OpenHAB
|
|
|
1165
1170
|
widget = super
|
|
1166
1171
|
|
|
1167
1172
|
children.each do |child|
|
|
1168
|
-
widget.
|
|
1173
|
+
widget.widgets.add(child.build)
|
|
1169
1174
|
end
|
|
1170
1175
|
|
|
1171
1176
|
widget
|
|
@@ -1427,7 +1432,7 @@ module OpenHAB
|
|
|
1427
1432
|
class << self
|
|
1428
1433
|
# @!visibility private
|
|
1429
1434
|
def factory
|
|
1430
|
-
|
|
1435
|
+
Core::Sitemaps::Provider.factory
|
|
1431
1436
|
end
|
|
1432
1437
|
end
|
|
1433
1438
|
|
|
@@ -1453,9 +1458,10 @@ module OpenHAB
|
|
|
1453
1458
|
@name = name
|
|
1454
1459
|
end
|
|
1455
1460
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1461
|
+
private
|
|
1462
|
+
|
|
1463
|
+
def create_widget
|
|
1464
|
+
Core::Sitemaps::Compatibility.create_sitemap(name)
|
|
1459
1465
|
end
|
|
1460
1466
|
end
|
|
1461
1467
|
end
|
data/lib/openhab/dsl/version.rb
CHANGED
data/lib/openhab/dsl.rb
CHANGED
|
@@ -40,7 +40,6 @@ module OpenHAB
|
|
|
40
40
|
|
|
41
41
|
module Sitemaps
|
|
42
42
|
autoload :Builder, "openhab/dsl/sitemaps/builder"
|
|
43
|
-
autoload :LinkableWidgetBuilder, "openhab/dsl/sitemaps/builder"
|
|
44
43
|
end
|
|
45
44
|
|
|
46
45
|
module Rules
|
|
@@ -289,9 +288,9 @@ module OpenHAB
|
|
|
289
288
|
Core::Rules::Registry.instance
|
|
290
289
|
end
|
|
291
290
|
|
|
292
|
-
# @return [Core::Sitemaps::
|
|
291
|
+
# @return [Core::Sitemaps::Registry]
|
|
293
292
|
def sitemaps
|
|
294
|
-
Core::Sitemaps::
|
|
293
|
+
Core::Sitemaps::Registry.instance
|
|
295
294
|
end
|
|
296
295
|
|
|
297
296
|
#
|
data/lib/openhab/rspec/hooks.rb
CHANGED
|
@@ -70,10 +70,11 @@ module OpenHAB
|
|
|
70
70
|
Core::Items::Metadata::Provider,
|
|
71
71
|
Core::Items::Semantics::Provider,
|
|
72
72
|
Core::Rules::Provider,
|
|
73
|
+
Core::Sitemaps::Provider,
|
|
73
74
|
Core::Things::Provider,
|
|
74
75
|
Core::Things::Links::Provider].each do |klass|
|
|
75
76
|
config.around do |example|
|
|
76
|
-
klass.new
|
|
77
|
+
klass.send(:new, &example)
|
|
77
78
|
end
|
|
78
79
|
end
|
|
79
80
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openhab-scripting
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.47.
|
|
4
|
+
version: 5.47.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian O'Connell
|
|
@@ -172,7 +172,11 @@ files:
|
|
|
172
172
|
- lib/openhab/core/rules/rule.rb
|
|
173
173
|
- lib/openhab/core/rules/tagged_array.rb
|
|
174
174
|
- lib/openhab/core/script_handling.rb
|
|
175
|
+
- lib/openhab/core/sitemaps/compatibility.rb
|
|
176
|
+
- lib/openhab/core/sitemaps/model.rb
|
|
175
177
|
- lib/openhab/core/sitemaps/provider.rb
|
|
178
|
+
- lib/openhab/core/sitemaps/registry.rb
|
|
179
|
+
- lib/openhab/core/sitemaps/sitemap.rb
|
|
176
180
|
- lib/openhab/core/things.rb
|
|
177
181
|
- lib/openhab/core/things/abstract_description_type.rb
|
|
178
182
|
- lib/openhab/core/things/channel.rb
|
|
@@ -354,7 +358,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
354
358
|
- !ruby/object:Gem::Version
|
|
355
359
|
version: '0'
|
|
356
360
|
requirements: []
|
|
357
|
-
rubygems_version: 4.0.
|
|
361
|
+
rubygems_version: 4.0.9
|
|
358
362
|
specification_version: 4
|
|
359
363
|
summary: JRuby Helper Libraries for openHAB Scripting
|
|
360
364
|
test_files: []
|