Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix condition handling for colors and visibility in sitemap builder #210

Merged
merged 1 commit into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 35 additions & 11 deletions lib/openhab/core/sitemaps/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ def unregister
@registration.unregister
end

# rubocop:disable Layout/LineLength

#
# Enter the Sitemap Builder DSL.
#
Expand All @@ -69,18 +67,45 @@ def unregister
# frame label: "Control" do
# text label: "Climate", icon: "if:mdi:home-thermometer-outline" do
# frame label: "Main Floor" do
# text item: MainFloor_AmbTemp
# # 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"
# # The :default key is used when no other condition matches
# text item: MainFloor_AmbTemp,
# label_color: "purple", # A simple string can be used when no conditions are needed
# value_color: { ">=90" => "red", "<=70" => "blue", :default => "black" }
#
# # If item name is not provided in the condition, it will default to the widget's Item
# # The operator will default to == if not specified
# switch item: MainFloorThermostat_TargetMode, label: "Mode",
# mappings: %w[off auto cool heat],
# value_color: { "cool" => "blue", "heat" => "red", :default => "black" }
#
# # an array of conditions are AND'd together
# setpoint item: MainFloorThermostat_SetPoint, label: "Set Point",
# value_color: {
# ["MainFloorThermostat_TargetMode!=off", ">80"] => "red", # red if mode!=off AND setpoint > 80
# ["MainFloorThermostat_TargetMode!=off", ">74"] => "yellow",
# ["MainFloorThermostat_TargetMode!=off", ">70"] => "green",
# "MainFloorThermostat_TargetMode!=off" => "blue",
# :default => "gray"
# }
# end
# frame label: "Basement" do
# text item: Basement_AmbTemp
# switch item: BasementThermostat_TargetMode, label: "Mode", mappings: { OFF: "off", COOL: "cool", HEAT: "heat" }
# # 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"]]
# switch item: BasementThermostat_TargetMode, label: "Mode",
# mappings: { OFF: "off", COOL: "cool", HEAT: "heat" }
#
# # Conditions within a nested array are AND'd together (requires openHAB 4.1)
# setpoint item: BasementThermostat_SetPoint, label: "Set Point",
# visibility: [["BasementThermostat_TargetMode!=off", "Vacation_Switch==OFF"]]
#
# # Additional elements are OR'd
# # The following visibility conditions are evaluated as:
# # (BasementThermostat_TargetMode!=off AND Vacation_Switch==OFF) OR Verbose_Mode==ON
# setpoint item: BasementThermostat_SetPoint, label: "Set Point",
# visibility: [
# ["BasementThermostat_TargetMode!=off", "Vacation_Switch==OFF"],
# "Verbose_Mode==ON"
# ]
# end
# end
# end
Expand All @@ -90,7 +115,6 @@ def unregister
def build(update: true, &block)
DSL::Sitemaps::Builder.new(self, update: update).instance_eval(&block)
end
# rubocop:enable Layout/LineLength

# For use in specs
# @!visibility private
Expand Down
76 changes: 33 additions & 43 deletions lib/openhab/dsl/sitemaps/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@ def sitemap(name, label = nil, icon: nil, &block)
# Base class for all widgets
# @see org.openhab.core.model.sitemap.sitemap.Widget
class WidgetBuilder
# This is copied directly out of UIComponentSitemapProvider.java
CONDITION_PATTERN = /(?<item>[A-Za-z]\w*)?\s*(?<condition>==|!=|<=|>=|<|>)?\s*(?<sign>\+|-)?(?<state>.+)/.freeze
include Core::EntityLookup

# This is copied out of UIComponentSitemapProvider.java
# The original pattern will match plain state e.g. "ON" as item="O" and state="N"
# this pattern is modified so it matches as item=nil and state="ON" by using atomic grouping `(?>subexpression)`
# rubocop:disable Layout/LineLength
CONDITION_PATTERN = /(?>(?<item>[A-Za-z]\w*)?\s*(?<condition>==|!=|<=|>=|<|>))?\s*(?<sign>\+|-)?(?<state>.+)/.freeze
# rubocop:enable Layout/LineLength
private_constant :CONDITION_PATTERN

# @return [String, nil]
Expand All @@ -49,15 +55,15 @@ class WidgetBuilder
# @see https://www.openhab.org/docs/ui/sitemaps.html#icons
attr_accessor :icon
# Label color rules
# @return [Hash<String, String>]
# @return [Hash<String, String>, Hash<Array<String>, String>]
# @see https://www.openhab.org/docs/ui/sitemaps.html#label-value-and-icon-colors
attr_reader :label_colors
# Value color rules
# @return [Hash<String, String>]
# @return [Hash<String, String>, Hash<Array<String>, String>]
# @see https://www.openhab.org/docs/ui/sitemaps.html#label-value-and-icon-colors
attr_reader :value_colors
# Icon color rules
# @return [Hash<String, String>]
# @return [Hash<String, String>, Hash<Array<String>, String>]
# @see https://www.openhab.org/docs/ui/sitemaps.html#label-value-and-icon-colors
attr_reader :icon_colors
# Visibility rules
Expand All @@ -68,10 +74,14 @@ class WidgetBuilder
# @param item [String, Core::Items::Item, nil] The item whose state to show (see {#item})
# @param label [String, nil] (see {#label})
# @param icon [String, nil] (see {#icon})
# @param label_color [String, Array<String>, nil] One or more label color rules (see {#label_color})
# @param value_color [String, Array<String>, nil] One or more value color rules (see {#value_color})
# @param icon_color [String, Array<String>, nil] One or more icon color rules (see {#icon_color})
# @param visibility [String, Array<String>, nil] One or more visibility rules (see {#visibility})
# @param label_color [String, Hash<String, String>, Hash<Array<String>, String>, nil]
# One or more label color rules (see {#label_color})
# @param value_color [String, Hash<String, String>, Hash<Array<String>, String>, nil]
# One or more value color rules (see {#value_color})
# @param icon_color [String, Hash<String, String>, Hash<Array<String>, String>, nil]
# One or more icon color rules (see {#icon_color})
# @param visibility [String, Array<String>, Array<Array<String>>, nil]
# One or more visibility rules (see {#visibility})
# @!visibility private
def initialize(type,
item: nil,
Expand Down Expand Up @@ -104,18 +114,21 @@ def initialize(type,
# Adds one or more new rules for setting the label color
# @return [Hash<String, String>] the current rules
def label_color(rules)
rules = { default: rules } if rules.is_a?(String)
@label_colors.merge!(rules)
end

# Adds one or more new rules for setting the value color
# @return [Hash<String, String>] the current rules
def value_color(rules)
rules = { default: rules } if rules.is_a?(String)
@value_colors.merge!(rules)
end

# Adds one or more new rules for setting the icon color
# @return [Hash<String, String>] the current rules
def icon_color(rules)
rules = { default: rules } if rules.is_a?(String)
@icon_colors.merge!(rules)
end

Expand All @@ -138,25 +151,7 @@ def build
add_colors(widget, :value_color, value_colors)
add_colors(widget, :icon_color, icon_colors)

# @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
end
add_conditions(widget, :visibility, visibilities, :create_visibility_rule)

widget
end
Expand All @@ -173,38 +168,35 @@ def inspect

private

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
def add_colors(widget, method, colors)
# ensure that the default color is at the end, and make the conditions nil (no conditions)
colors.delete(:default)&.tap { |default_color| colors.merge!(nil => default_color) }

add_conditions(widget, method, colors.keys, :create_color_array) do |color_array, key|
color_array.arg = colors[key]
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
# @deprecated OH 4.0
if conditions.any?(Array) && !SitemapBuilder.factory.respond_to?(:create_condition)
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?
yield container, sub_conditions if block_given?
object.add(container)
end
end

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

Array.wrap(conditions).each do |c|
Expand Down Expand Up @@ -591,8 +583,6 @@ def build
# Parent class for builders of widgets that can contain other widgets.
# @see org.openhab.core.model.sitemap.sitemap.LinkableWidget
class LinkableWidgetBuilder < WidgetBuilder
include Core::EntityLookup

# allow referring to items that don't exist yet
self.create_dummy_items = true

Expand Down
Loading