Skip to content

Commit

Permalink
Fallback to hs.csv when mode is color_temp and color_temp.csv is not …
Browse files Browse the repository at this point in the history
…available (#2249)

* fix: fallback to hs.csv when mode is color_temp and color_temp.csv is missing

* fix: fallback to hs.csv when mode is color_temp and color_temp.csv is missing
  • Loading branch information
bramstroker committed May 19, 2024
1 parent 17f3ede commit 981121a
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 6 deletions.
37 changes: 33 additions & 4 deletions custom_components/powercalc/strategy/lut.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import logging
import os
from collections import defaultdict
from collections.abc import Mapping
from csv import reader
from dataclasses import dataclass
from decimal import Decimal
from functools import partial
from gzip import GzipFile
from typing import Any

import numpy as np
from homeassistant.components import light
Expand All @@ -21,6 +23,7 @@
ColorMode,
)
from homeassistant.core import State
from homeassistant.util.color import color_temperature_to_hs

from custom_components.powercalc.common import SourceEntity
from custom_components.powercalc.errors import (
Expand All @@ -44,6 +47,7 @@
class LutRegistry:
def __init__(self) -> None:
self._lookup_dictionaries: dict[str, dict] = {}
self._supported_color_modes: dict[str, set[ColorMode]] = {}

async def get_lookup_dictionary(
self,
Expand Down Expand Up @@ -90,6 +94,20 @@ def get_lut_file(power_profile: PowerProfile, color_mode: ColorMode) -> GzipFile

raise LutFileNotFoundError("Data file not found: %s")

def get_supported_color_modes(self, power_profile: PowerProfile) -> set[ColorMode]:
"""Return the color modes supported by the Profile."""
cache_key = f"{power_profile.manufacturer}_{power_profile.model}_supported_color_modes"
supported_color_modes = self._supported_color_modes.get(cache_key)
if supported_color_modes is None:
supported_color_modes = set()
for file in os.listdir(power_profile.get_model_directory()):
if file.endswith(".csv.gz"):
color_mode = ColorMode(file.removesuffix(".csv.gz"))
if color_mode in LUT_COLOR_MODES:
supported_color_modes.add(color_mode)
self._supported_color_modes[cache_key] = supported_color_modes
return supported_color_modes


class LutStrategy(PowerCalculationStrategyInterface):
def __init__(
Expand All @@ -101,13 +119,13 @@ def __init__(
self._source_entity = source_entity
self._lut_registry = lut_registry
self._profile = profile
self._strategy_color_modes: set[ColorMode] | None = None

async def calculate(self, entity_state: State) -> Decimal | None:
"""Calculate the power consumption based on brightness, mired, hsl values."""
attrs = entity_state.attributes
color_mode = attrs.get(ATTR_COLOR_MODE)
if color_mode in COLOR_MODES_COLOR:
color_mode = ColorMode.HS
original_color_mode = attrs.get(ATTR_COLOR_MODE)
color_mode = await self.get_selected_color_mode(attrs)

brightness = attrs.get(ATTR_BRIGHTNESS)
if brightness is None:
Expand Down Expand Up @@ -142,7 +160,7 @@ async def calculate(self, entity_state: State) -> Decimal | None:

light_setting = LightSetting(color_mode=color_mode, brightness=brightness)
if color_mode == ColorMode.HS:
hs = attrs[ATTR_HS_COLOR]
hs = color_temperature_to_hs(attrs[ATTR_COLOR_TEMP]) if original_color_mode == ColorMode.COLOR_TEMP else attrs[ATTR_HS_COLOR]
light_setting.hue = int(hs[0] / 360 * 65535)
light_setting.saturation = int(hs[1] / 100 * 255)
_LOGGER.debug(
Expand Down Expand Up @@ -171,6 +189,17 @@ async def calculate(self, entity_state: State) -> Decimal | None:
_LOGGER.debug("%s: Calculated power:%s", entity_state.entity_id, power)
return power

async def get_selected_color_mode(self, attrs: Mapping[str, Any]) -> ColorMode:
"""Get the selected color mode for the entity."""
color_mode = ColorMode(str(attrs.get(ATTR_COLOR_MODE)))
if color_mode in COLOR_MODES_COLOR:
color_mode = ColorMode.HS
profile_color_modes = self._lut_registry.get_supported_color_modes(self._profile)
if color_mode not in profile_color_modes and color_mode == ColorMode.COLOR_TEMP:
_LOGGER.debug("Color mode not natively supported, falling back to HS")
color_mode = ColorMode.HS
return color_mode

def lookup_power(
self,
lookup_table: LookupDictType,
Expand Down
29 changes: 27 additions & 2 deletions tests/strategy/test_lut.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
PowerCalculationStrategyInterface,
)
from tests.common import run_powercalc_setup

from .common import create_source_entity
from tests.strategy.common import create_source_entity


async def test_colortemp_lut(hass: HomeAssistant) -> None:
Expand Down Expand Up @@ -203,6 +202,32 @@ async def test_sensor_unavailable_for_unsupported_color_mode(hass: HomeAssistant
assert "Lookup table not found for color mode" in caplog.text


async def test_fallback_color_temp_to_hs(hass: HomeAssistant) -> None:
"""
Test fallback is done when no color_temp.csv is available, but a hs.csv is.
Fixes issue where HUE bridge is falsly reporting color_temp as color_mode.
See: https://github.com/bramstroker/homeassistant-powercalc/issues/2247
"""

await run_powercalc_setup(
hass,
{
CONF_ENTITY_ID: "light.test",
CONF_MANUFACTURER: "signify",
CONF_MODEL: "LLC011",
},
)

hass.states.async_set(
"light.test",
STATE_ON,
{ATTR_COLOR_MODE: ColorMode.COLOR_TEMP, ATTR_BRIGHTNESS: 100, ATTR_COLOR_TEMP: 500},
)
await hass.async_block_till_done()

assert hass.states.get("sensor.test_power").state == "1.42"


async def _create_lut_strategy(
hass: HomeAssistant,
manufacturer: str,
Expand Down

0 comments on commit 981121a

Please sign in to comment.