Skip to content

Commit

Permalink
Support AND'd visibility and color rules in sitemap builder (#200)
Browse files Browse the repository at this point in the history
  • Loading branch information
ccutrer authored Dec 15, 2023
1 parent e27a7e5 commit 2089728
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 52 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
if: "!contains(github.event.head_commit.message, 'ci skip')"
outputs:
openhab_matrix: |
["3.4.5", "4.0.4", "4.1.0.M3", "4.1.0-SNAPSHOT"]
["3.4.5", "4.0.4", "4.1.0.M4", "4.1.0-SNAPSHOT"]
snapshot_date: |
${{ steps.snapshot-date.outputs.SNAPSHOT_DATE }}
steps:
Expand Down Expand Up @@ -130,7 +130,7 @@ jobs:
exclude:
- openhab_version: 4.0.4
jruby_version: jruby-9.3.10.0
- openhab_version: 4.1.0.M3
- openhab_version: 4.1.0.M4
jruby_version: jruby-9.3.10.0
- openhab_version: 4.1.0-SNAPSHOT
jruby_version: jruby-9.3.10.0
Expand Down
8 changes: 6 additions & 2 deletions lib/openhab/core/sitemaps/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,17 @@ def unregister
# text label: "Climate", icon: "if:mdi:home-thermometer-outline" do
# frame label: "Main Floor" do
# text item: MainFloor_AmbTemp
# switch item: MainFloorThermostat_TargetMode, label: "Mode", mappings: %w[off auto cool heat]
# # colors are set with a hash, with key being condition, and value being the color
# switch item: MainFloorThermostat_TargetMode, label: "Mode", mappings: %w[off auto cool heat], label_color: { "==heat" => "red", "" => "black" }
# # an array of conditions are OR'd together
# switch item: MainFloorThermostat_TargetMode, label: "Mode", mappings: %w[off auto cool heat], label_color: { ["==heat", "==cool"], => "green" }
# setpoint item: MainFloorThermostat_SetPoint, label: "Set Point", visibility: "MainFloorThermostat_TargetMode!=off"
# end
# frame label: "Basement" do
# text item: Basement_AmbTemp
# switch item: BasementThermostat_TargetMode, label: "Mode", mappings: { OFF: "off", COOL: "cool", HEAT: "heat" }
# setpoint item: BasementThermostat_SetPoint, label: "Set Point", visibility: "BasementThermostat_TargetMode!=off"
# # nested arrays are conditions that are AND'd together, instead of OR'd (requires openHAB 4.1)
# setpoint item: BasementThermostat_SetPoint, label: "Set Point", visibility: [["BasementThermostat_TargetMode!=off", "Vacation_Switch!=OFF"]]
# end
# end
# end
Expand Down
132 changes: 84 additions & 48 deletions lib/openhab/dsl/sitemaps/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,9 @@ def sitemap(name, label = nil, icon: nil, &block)
# Base class for all widgets
# @see org.openhab.core.model.sitemap.sitemap.Widget
class WidgetBuilder
# rubocop:disable Layout/LineLength, Lint/MixedRegexpCaptureTypes
# These are copied directly out of UIComponentSitemapProvider.java
VISIBILITY_PATTERN = /(?<item>[A-Za-z]\w*)\s*(?<condition>==|!=|<=|>=|<|>)\s*(?<sign>\+|-)?(?<state>\S+)/.freeze
COLOR_PATTERN = /((?<item>[A-Za-z]\w*)?\s*((?<condition>==|!=|<=|>=|<|>)\s*(?<sign>\+|-)?(?<state>\S+))?\s*=)?\s*(?<arg>\S+)/.freeze
# rubocop:enable Layout/LineLength, Lint/MixedRegexpCaptureTypes
private_constant :VISIBILITY_PATTERN, :COLOR_PATTERN
# This is copied directly out of UIComponentSitemapProvider.java
CONDITION_PATTERN = /(?<item>[A-Za-z]\w*)?\s*(?<condition>==|!=|<=|>=|<|>)?\s*(?<sign>\+|-)?(?<state>.+)/.freeze
private_constant :CONDITION_PATTERN

# @return [String, nil]
attr_accessor :label
Expand All @@ -52,15 +49,15 @@ class WidgetBuilder
# @see https://www.openhab.org/docs/ui/sitemaps.html#icons
attr_accessor :icon
# Label color rules
# @return [Array<String>]
# @return [Hash<String, String>]
# @see https://www.openhab.org/docs/ui/sitemaps.html#label-value-and-icon-colors
attr_reader :label_colors
# Value color rules
# @return [Array<String>]
# @return [Hash<String, String>]
# @see https://www.openhab.org/docs/ui/sitemaps.html#label-value-and-icon-colors
attr_reader :value_colors
# Icon color rules
# @return [Array<String>]
# @return [Hash<String, String>]
# @see https://www.openhab.org/docs/ui/sitemaps.html#label-value-and-icon-colors
attr_reader :icon_colors
# Visibility rules
Expand Down Expand Up @@ -94,32 +91,32 @@ def initialize(type,
@label = label
@icon = icon
@visibilities = []
@label_colors = []
@value_colors = []
@icon_colors = []
@label_colors = {}
@value_colors = {}
@icon_colors = {}

self.label_color(*label_color) if label_color
self.value_color(*value_color) if value_color
self.icon_color(*icon_color) if icon_color
self.label_color(label_color) if label_color
self.value_color(value_color) if value_color
self.icon_color(icon_color) if icon_color
self.visibility(*visibility) if visibility
end

# Adds one or more new rules for setting the label color
# @return [Array<String>] the current rules
def label_color(*rules)
@label_colors.concat(rules)
# @return [Hash<String, String>] the current rules
def label_color(rules)
@label_colors.merge!(rules)
end

# Adds one or more new rules for setting the value color
# @return [Array<String>] the current rules
def value_color(*rules)
@value_colors.concat(rules)
# @return [Hash<String, String>] the current rules
def value_color(rules)
@value_colors.merge!(rules)
end

# Adds one or more new rules for setting the icon color
# @return [Array<String>] the current rules
def icon_color(*rules)
@icon_colors.concat(rules)
# @return [Hash<String, String>] the current rules
def icon_color(rules)
@icon_colors.merge!(rules)
end

# Adds one or more new visibility rules
Expand All @@ -137,21 +134,28 @@ def build
widget.label = @label
widget.icon = @icon

add_color(widget.label_color, label_colors) unless label_colors.empty?
add_color(widget.value_color, value_colors) unless value_colors.empty?
add_color(widget.icon_color, icon_colors) unless icon_colors.empty?
add_colors(widget, :label_color, label_colors)
add_colors(widget, :value_color, value_colors)
add_colors(widget, :icon_color, icon_colors)

visibilities.each do |v|
unless (match = VISIBILITY_PATTERN.match(v))
raise ArgumentError, "Syntax error in visibility rule #{v.inspect}"
# @deprecated OH 4.1
if SitemapBuilder.factory.respond_to?(:create_condition)
add_conditions(widget, :visibility, visibilities, :create_visibility_rule)
else
visibilities.each do |v|
raise ArgumentError, "AND conditions not supported prior to openHAB 4.1" if v.is_a?(Array)

unless (match = CONDITION_PATTERN.match(v))
raise ArgumentError, "Syntax error in visibility rule #{v.inspect}"
end

rule = SitemapBuilder.factory.create_visibility_rule
rule.item = match["item"]
rule.condition = match["condition"]
rule.sign = match["sign"]
rule.state = match["state"]
widget.visibility.add(rule)
end

rule = SitemapBuilder.factory.create_visibility_rule
rule.item = match["item"]
rule.condition = match["condition"]
rule.sign = match["sign"]
rule.state = match["state"]
widget.visibility.add(rule)
end

widget
Expand All @@ -169,19 +173,51 @@ def inspect

private

def add_color(widget_color, colors)
colors.each do |c|
unless (match = COLOR_PATTERN.match(c))
raise ArgumentError, "Syntax error in color rule #{c.inspect}"
def add_colors(widget, method, conditions)
conditions.each do |condition, color|
condition = [condition] unless condition.is_a?(Array)
add_conditions(widget, method, condition, :create_color_array) do |color_array|
color_array.arg = color
end
end
end

def add_conditions(widget, method, conditions, container_method)
return if conditions.empty?

object = widget.send(method)
has_and_conditions = conditions.any?(Array)
# @deprecated OH 4.1
if !SitemapBuilder.factory.respond_to?(:create_condition) && has_and_conditions
raise ArgumentError, "AND conditions not supported prior to openHAB 4.1"
end

conditions = [conditions] unless has_and_conditions

conditions.each do |sub_conditions|
container = SitemapBuilder.factory.send(container_method)

add_conditions_to_container(container, sub_conditions)
yield container if block_given?
object.add(container)
end
end

def add_conditions_to_container(container, conditions)
# @deprecated OH 4.1
supports_and_conditions = SitemapBuilder.factory.respond_to?(:create_condition)

Array.wrap(conditions).each do |c|
unless (match = CONDITION_PATTERN.match(c))
raise ArgumentError, "Syntax error in condition #{c.inspect}"
end

rule = SitemapBuilder.factory.create_color_array
rule.item = match["item"]
rule.condition = match["condition"]
rule.sign = match["sign"]
rule.state = match["state"]
rule.arg = match["arg"]
widget_color.add(color)
condition = supports_and_conditions ? SitemapBuilder.factory.create_condition : container
condition.item = match["item"]
condition.condition = match["condition"]
condition.sign = match["sign"]
condition.state = match["state"]
container.conditions.add(condition) if supports_and_conditions
end
end
end
Expand Down
73 changes: 73 additions & 0 deletions spec/openhab/dsl/sitemaps/builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,79 @@
end
end

it "supports visibility" do
s = sitemaps.build do
sitemap "default", "My Residence" do
switch item: "Switch1", visibility: "Switch1 == ON"
end
end

switch = s.children.first
cond = switch.visibility.first
# @deprecated OH 4.0
cond = cond.conditions.first if cond.respond_to?(:conditions)
expect(cond.item).to eq "Switch1"
expect(cond.condition.to_s).to eq "=="
expect(cond.state).to eq "ON"
end

it "supports colors" do
s = sitemaps.build do
sitemap "default", "My Residence" do
switch item: "Switch1", label_color: { "Switch1 == ON" => "green" }
end
end

switch = s.children.first
cond = rule = switch.label_color.first
# @deprecated OH 4.0
cond = cond.conditions.first if cond.respond_to?(:conditions)
expect(cond.item).to eq "Switch1"
expect(cond.condition.to_s).to eq "=="
expect(cond.state).to eq "ON"
expect(rule.arg).to eq "green"
end

# @deprecated OH 4.0
if Gem::Version.new(OpenHAB::Core::VERSION) >= Gem::Version.new("4.1.0") ||
OpenHAB::Core::VERSION.start_with?("4.1.0.M")
it "supports AND conditions on visibility" do
sitemaps.build do
sitemap "default", "My Residence" do
switch item: "Switch1", visibility: [["Switch1 == ON"]]
end
end
end

it "supports AND conditions on colors" do
sitemaps.build do
sitemap "default", "My Residence" do
switch item: "Switch1", label_color: { [["Switch1 == ON", "Switch2 == OFF"]] => "green" }
end
end
end
else
it "does not support AND conditions on visibility" do
expect do
sitemaps.build do
sitemap "default", "My Residence" do
switch item: "Switch1", visibility: [["Switch1 == ON"]]
end
end
end.to raise_error(ArgumentError)
end

it "does not support AND conditions on colors" do
expect do
sitemaps.build do
sitemap "default", "My Residence" do
switch item: "Switch1", label_color: { [["Switch1 == ON", "Switch2 == OFF"]] => "green" }
end
end
end.to raise_error(ArgumentError)
end
end

it "can add a frame" do
sitemaps.build do
sitemap "default" do
Expand Down

0 comments on commit 2089728

Please sign in to comment.