Skip to content

Commit

Permalink
Fix condition handling for colors and visibility
Browse files Browse the repository at this point in the history
Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
  • Loading branch information
jimtng committed Dec 31, 2023
1 parent 19264d0 commit 029f1fc
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 80 deletions.
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
78 changes: 35 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,37 @@ 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?

puts conditions.inspect

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 +585,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

0 comments on commit 029f1fc

Please sign in to comment.