From 4275cc4f1030525bfb2a3e66b25c4caa27d1de21 Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Thu, 10 Oct 2024 13:51:23 -0600 Subject: [PATCH] Add planckian helpers for HSBType Signed-off-by: Cody Cutrer --- lib/openhab/core/types/hsb_type.rb | 71 +++++++++++++++++++++++- spec/openhab/core/types/hsb_type_spec.rb | 41 ++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/lib/openhab/core/types/hsb_type.rb b/lib/openhab/core/types/hsb_type.rb index b9812e06f..04e9ab2e0 100644 --- a/lib/openhab/core/types/hsb_type.rb +++ b/lib/openhab/core/types/hsb_type.rb @@ -190,11 +190,80 @@ def to_s # @return [[PercentType, PercentType, PercentType]] # @!attribute [r] cct - # @return [QuantityType] The color temperature in Kelvin + # @return [QuantityType] The correlated color temperature in Kelvin # @since openHAB 4.3 + # @see https://en.wikipedia.org/wiki/Planckian_locus Planckian Locus def cct ColorUtil.xy_to_kelvin(to_xy[0..1].map { |x| x.double_value / 100 }) | "K" end + + # @!attribute [r] duv + # The distance that this color is from the planckian locus + # + # @return [Float] The delta u, v + # + # @see planckian? + # @see planckian_cct + # @see https://en.wikipedia.org/wiki/Planckian_locus Planckian Locus + # @since openHAB 4.3 + def duv + ColorUtil.xy_to_duv(to_xy[0..1].map { |x| x.double_value / 100 }) + end + + # Checks if this color is within a certain tolerance of the planckian locus ("white") + # + # @param [Float] duv_tolerance The maximum allowed distance from the planckian locus + # @param [Numeric, PercentType] maximum_saturation The maximum allowed saturation. + # Some colors (bright green for example) may be close to the planckian locus, + # but you don't want to treat them as "white" because they are very saturated. + # @return [true, false] + # + # @note The parameters and defaults for this method are subject to change in future + # releases of this library, and should be considered beta. For now, the default + # parameters should be sufficient to detect most colors that Apple's HomeKit color + # temperature color chooser uses as planckian, without detecting most other "real" + # colors as planckian. + # @see duv + # @see planckian_cct + # @see https://en.wikipedia.org/wiki/Planckian_locus Planckian Locus + # @since openHAB 4.3 + def planckian?(duv_tolerance: 0.015, maximum_saturation: 75) + duv.abs < duv_tolerance && saturation < maximum_saturation + end + alias_method :white_cct?, :planckian? + + # Returns the color temperature of this color _if_ it is within a certain tolerance + # of the planckian locus ("white"), otherwise returns `nil`. + # + # @param [Range, NumberItem] range An allowed range to additionally restrict + # if the CCT should be returned. A NumberItem that represents a CCT channel + # may be provided, and {NumberItem#range NumberItem#range} will be used instead. If the range + # does not have units (is {QuantityType}), it is interpreted as being in Kelvin. + # @return [QuantityType, nil] The color temperature in Kelvin + # (unless the range is in mireds; then it will be in mireds) + # + # @note Additional parameters are forwarded to {#planckian?} + # @see planckian? + # @see https://en.wikipedia.org/wiki/Planckian_locus Planckian Locus + # @since openHAB 4.3 + def planckian_cct(range: nil, **kwargs) + return unless planckian?(**kwargs) + + range = range.range if range.is_a?(NumberItem) + cct = self.cct + if range + range_type = range.begin || range.end + if !range_type.is_a?(QuantityType) + range = Range.new(range.begin | "K", range.end | "K") + elsif range_type.unit.to_s == "mired" + cct |= "mired" + end + end + return nil if range && !range.cover?(cct) + + cct + end + alias_method :white_cct, :planckian_cct end end end diff --git a/spec/openhab/core/types/hsb_type_spec.rb b/spec/openhab/core/types/hsb_type_spec.rb index 70fb0c6bf..2b869ae14 100644 --- a/spec/openhab/core/types/hsb_type_spec.rb +++ b/spec/openhab/core/types/hsb_type_spec.rb @@ -28,6 +28,47 @@ end end + describe "#planckian_cct", if: OpenHAB::Core.version >= OpenHAB::Core::V4_3 do + it "returns a value for a pure 'white'" do + warm_white = HSBType.from_cct(2700) + expect(warm_white.planckian_cct.to_i).to be 2699 + end + + it "returns a value if in range (bare range)" do + warm_white = HSBType.from_cct(2700) + expect(warm_white.planckian_cct(range: 2000..6000).to_i).to be 2699 + end + + it "returns a value if in range (K range)" do + warm_white = HSBType.from_cct(2700) + expect(warm_white.planckian_cct(range: (2000 | "K")..(6000 | "K")).to_i).to be 2699 + end + + it "returns a value if in range (mired range)" do + warm_white = HSBType.from_cct(2700) + expect(warm_white.planckian_cct(range: (167 | "mired")..(500 | "mired")).to_i).to be 370 + end + + it "returns nil for red" do + expect(HSBType::RED.planckian_cct).to be_nil + end + + it "returns nil if the CCT is out of range (bare range)" do + color = HSBType.from_cct(2000) + expect(color.planckian_cct(range: 2700..6000)).to be_nil + end + + it "returns nil if the CCT is out of range (K range)" do + color = HSBType.from_cct(2000) + expect(color.planckian_cct(range: (2700 | "K")..(6000 | "K"))).to be_nil + end + + it "returns nil if the CCT is out of range (mired range)" do + color = HSBType.from_cct(2000) + expect(color.planckian_cct(range: (167 | "mired")..(370 | "mired"))).to be_nil + end + end + it "is inspectable" do expect(HSBType.new.inspect).to eql "0 °,0%,0%" end