Skip to content

Commit

Permalink
Add scaling utils for brightness and fanspeed (#104753)
Browse files Browse the repository at this point in the history
Co-authored-by: Robert Resch <robert@resch.dev>
  • Loading branch information
jbouwh and edenhaus authored Dec 4, 2023
1 parent 7222e2b commit e8475b9
Show file tree
Hide file tree
Showing 19 changed files with 1,034 additions and 36 deletions.
2 changes: 1 addition & 1 deletion homeassistant/components/bond/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from .const import DOMAIN, SERVICE_SET_FAN_SPEED_TRACKED_STATE
from .entity import BondEntity
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/comfoconnect/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from . import DOMAIN, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, ComfoConnectBridge

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/isy994/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from .const import _LOGGER, DOMAIN
from .entity import ISYNodeEntity, ISYProgramEntity
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/knx/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from .const import DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS
from .knx_entity import KnxEntity
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/modern_forms/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from . import (
ModernFormsDataUpdateCoordinator,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mqtt/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from . import subscription
from .config import MQTT_RW_SCHEMA
Expand Down
19 changes: 9 additions & 10 deletions homeassistant/components/mqtt/light/schema_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from contextlib import suppress
import logging
from typing import Any, cast
from typing import TYPE_CHECKING, Any, cast

import voluptuous as vol

Expand Down Expand Up @@ -367,10 +367,10 @@ def state_received(msg: ReceiveMessage) -> None:
if brightness_supported(self.supported_color_modes):
try:
if brightness := values["brightness"]:
scale = self._config[CONF_BRIGHTNESS_SCALE]
self._attr_brightness = min(
255,
round(brightness * 255 / scale), # type: ignore[operator]
if TYPE_CHECKING:
assert isinstance(brightness, float)
self._attr_brightness = color_util.value_to_brightness(
(1, self._config[CONF_BRIGHTNESS_SCALE]), brightness
)
else:
_LOGGER.debug(
Expand Down Expand Up @@ -591,13 +591,12 @@ async def async_turn_on(self, **kwargs: Any) -> None: # noqa: C901
self._set_flash_and_transition(message, **kwargs)

if ATTR_BRIGHTNESS in kwargs and self._config[CONF_BRIGHTNESS]:
brightness_normalized = kwargs[ATTR_BRIGHTNESS] / DEFAULT_BRIGHTNESS_SCALE
brightness_scale = self._config[CONF_BRIGHTNESS_SCALE]
device_brightness = min(
round(brightness_normalized * brightness_scale), brightness_scale
device_brightness = color_util.brightness_to_value(
(1, self._config[CONF_BRIGHTNESS_SCALE]),
kwargs[ATTR_BRIGHTNESS],
)
# Make sure the brightness is not rounded down to 0
device_brightness = max(device_brightness, 1)
device_brightness = max(round(device_brightness), 1)
message["brightness"] = device_brightness

if self._optimistic:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/renson/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from .const import DOMAIN
from .coordinator import RensonCoordinator
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/smartthings/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/smarty/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from . import DOMAIN, SIGNAL_UPDATE_SMARTY

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/vesync/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from .common import VeSyncDevice
from .const import DEV_TYPE_TO_HA, DOMAIN, SKU_TO_BASE_DEVICE, VS_DISCOVERY, VS_FANS
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/wemo/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from . import async_wemo_dispatcher_connect
from .const import SERVICE_RESET_FILTER_LIFE, SERVICE_SET_HUMIDITY
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/zha/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from .core import discovery
from .core.cluster_handlers import wrap_zigpy_exceptions
Expand Down
37 changes: 37 additions & 0 deletions homeassistant/util/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import attr

from .scaling import scale_to_ranged_value


class RGBColor(NamedTuple):
"""RGB hex values."""
Expand Down Expand Up @@ -744,3 +746,38 @@ def check_valid_gamut(Gamut: GamutType) -> bool:
)

return not_on_line and red_valid and green_valid and blue_valid


def brightness_to_value(low_high_range: tuple[float, float], brightness: int) -> float:
"""Given a brightness_scale convert a brightness to a single value.
Do not include 0 if the light is off for value 0.
Given a brightness low_high_range of (1,100) this function
will return:
255: 100.0
127: ~49.8039
10: ~3.9216
"""
return scale_to_ranged_value((1, 255), low_high_range, brightness)


def value_to_brightness(low_high_range: tuple[float, float], value: float) -> int:
"""Given a brightness_scale convert a single value to a brightness.
Do not include 0 if the light is off for value 0.
Given a brightness low_high_range of (1,100) this function
will return:
100: 255
50: 128
4: 10
The value will be clamped between 1..255 to ensure valid value.
"""
return min(
255,
max(1, round(scale_to_ranged_value(low_high_range, (1, 255), value))),
)
23 changes: 9 additions & 14 deletions homeassistant/util/percentage.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@

from typing import TypeVar

from .scaling import ( # noqa: F401
int_states_in_range,
scale_ranged_value_to_int_range,
scale_to_ranged_value,
states_in_range,
)

_T = TypeVar("_T")


Expand Down Expand Up @@ -69,8 +76,7 @@ def ranged_value_to_percentage(
(1,255), 127: 50
(1,255), 10: 4
"""
offset = low_high_range[0] - 1
return int(((value - offset) * 100) // states_in_range(low_high_range))
return scale_ranged_value_to_int_range(low_high_range, (1, 100), value)


def percentage_to_ranged_value(
Expand All @@ -87,15 +93,4 @@ def percentage_to_ranged_value(
(1,255), 50: 127.5
(1,255), 4: 10.2
"""
offset = low_high_range[0] - 1
return states_in_range(low_high_range) * percentage / 100 + offset


def states_in_range(low_high_range: tuple[float, float]) -> float:
"""Given a range of low and high values return how many states exist."""
return low_high_range[1] - low_high_range[0] + 1


def int_states_in_range(low_high_range: tuple[float, float]) -> int:
"""Given a range of low and high values return how many integer states exist."""
return int(states_in_range(low_high_range))
return scale_to_ranged_value((1, 100), low_high_range, percentage)
62 changes: 62 additions & 0 deletions homeassistant/util/scaling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Scaling util functions."""
from __future__ import annotations


def scale_ranged_value_to_int_range(
source_low_high_range: tuple[float, float],
target_low_high_range: tuple[float, float],
value: float,
) -> int:
"""Given a range of low and high values convert a single value to another range.
Given a source low value of 1 and a high value of 255 and
a target range from 1 to 100 this function
will return:
(1,255), (1,100), 255: 100
(1,255), (1,100), 127: 49
(1,255), (1,100), 10: 3
"""
source_offset = source_low_high_range[0] - 1
target_offset = target_low_high_range[0] - 1
return int(
(value - source_offset)
* states_in_range(target_low_high_range)
// states_in_range(source_low_high_range)
+ target_offset
)


def scale_to_ranged_value(
source_low_high_range: tuple[float, float],
target_low_high_range: tuple[float, float],
value: float,
) -> float:
"""Given a range of low and high values convert a single value to another range.
Do not include 0 in a range if 0 means off,
e.g. for brightness or fan speed.
Given a source low value of 1 and a high value of 255 and
a target range from 1 to 100 this function
will return:
(1,255), 255: 100
(1,255), 127: ~49.8039
(1,255), 10: ~3.9216
"""
source_offset = source_low_high_range[0] - 1
target_offset = target_low_high_range[0] - 1
return (value - source_offset) * (
states_in_range(target_low_high_range)
) / states_in_range(source_low_high_range) + target_offset


def states_in_range(low_high_range: tuple[float, float]) -> float:
"""Given a range of low and high values return how many states exist."""
return low_high_range[1] - low_high_range[0] + 1


def int_states_in_range(low_high_range: tuple[float, float]) -> int:
"""Given a range of low and high values return how many integer states exist."""
return int(states_in_range(low_high_range))
Loading

0 comments on commit e8475b9

Please sign in to comment.