Add scaling utils for brightness and fanspeed (#104753)

Co-authored-by: Robert Resch <robert@resch.dev>
This commit is contained in:
Jan Bouwhuis 2023-12-04 12:10:58 +01:00 committed by GitHub
parent 7222e2b2d6
commit e8475b9b33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1034 additions and 36 deletions

View File

@ -21,10 +21,10 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_platform from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value, percentage_to_ranged_value,
ranged_value_to_percentage, ranged_value_to_percentage,
) )
from homeassistant.util.scaling import int_states_in_range
from .const import DOMAIN, SERVICE_SET_FAN_SPEED_TRACKED_STATE from .const import DOMAIN, SERVICE_SET_FAN_SPEED_TRACKED_STATE
from .entity import BondEntity from .entity import BondEntity

View File

@ -22,10 +22,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value, percentage_to_ranged_value,
ranged_value_to_percentage, ranged_value_to_percentage,
) )
from homeassistant.util.scaling import int_states_in_range
from . import DOMAIN, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, ComfoConnectBridge from . import DOMAIN, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, ComfoConnectBridge

View File

@ -13,10 +13,10 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value, percentage_to_ranged_value,
ranged_value_to_percentage, ranged_value_to_percentage,
) )
from homeassistant.util.scaling import int_states_in_range
from .const import _LOGGER, DOMAIN from .const import _LOGGER, DOMAIN
from .entity import ISYNodeEntity, ISYProgramEntity from .entity import ISYNodeEntity, ISYProgramEntity

View File

@ -14,10 +14,10 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value, percentage_to_ranged_value,
ranged_value_to_percentage, ranged_value_to_percentage,
) )
from homeassistant.util.scaling import int_states_in_range
from .const import DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS from .const import DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS
from .knx_entity import KnxEntity from .knx_entity import KnxEntity

View File

@ -12,10 +12,10 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_platform from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value, percentage_to_ranged_value,
ranged_value_to_percentage, ranged_value_to_percentage,
) )
from homeassistant.util.scaling import int_states_in_range
from . import ( from . import (
ModernFormsDataUpdateCoordinator, ModernFormsDataUpdateCoordinator,

View File

@ -31,10 +31,10 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.template import Template from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value, percentage_to_ranged_value,
ranged_value_to_percentage, ranged_value_to_percentage,
) )
from homeassistant.util.scaling import int_states_in_range
from . import subscription from . import subscription
from .config import MQTT_RW_SCHEMA from .config import MQTT_RW_SCHEMA

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from contextlib import suppress from contextlib import suppress
import logging import logging
from typing import Any, cast from typing import TYPE_CHECKING, Any, cast
import voluptuous as vol import voluptuous as vol
@ -367,10 +367,10 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
if brightness_supported(self.supported_color_modes): if brightness_supported(self.supported_color_modes):
try: try:
if brightness := values["brightness"]: if brightness := values["brightness"]:
scale = self._config[CONF_BRIGHTNESS_SCALE] if TYPE_CHECKING:
self._attr_brightness = min( assert isinstance(brightness, float)
255, self._attr_brightness = color_util.value_to_brightness(
round(brightness * 255 / scale), # type: ignore[operator] (1, self._config[CONF_BRIGHTNESS_SCALE]), brightness
) )
else: else:
_LOGGER.debug( _LOGGER.debug(
@ -591,13 +591,12 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
self._set_flash_and_transition(message, **kwargs) self._set_flash_and_transition(message, **kwargs)
if ATTR_BRIGHTNESS in kwargs and self._config[CONF_BRIGHTNESS]: if ATTR_BRIGHTNESS in kwargs and self._config[CONF_BRIGHTNESS]:
brightness_normalized = kwargs[ATTR_BRIGHTNESS] / DEFAULT_BRIGHTNESS_SCALE device_brightness = color_util.brightness_to_value(
brightness_scale = self._config[CONF_BRIGHTNESS_SCALE] (1, self._config[CONF_BRIGHTNESS_SCALE]),
device_brightness = min( kwargs[ATTR_BRIGHTNESS],
round(brightness_normalized * brightness_scale), brightness_scale
) )
# Make sure the brightness is not rounded down to 0 # 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 message["brightness"] = device_brightness
if self._optimistic: if self._optimistic:

View File

@ -13,10 +13,10 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value, percentage_to_ranged_value,
ranged_value_to_percentage, ranged_value_to_percentage,
) )
from homeassistant.util.scaling import int_states_in_range
from .const import DOMAIN from .const import DOMAIN
from .coordinator import RensonCoordinator from .coordinator import RensonCoordinator

View File

@ -12,10 +12,10 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value, percentage_to_ranged_value,
ranged_value_to_percentage, ranged_value_to_percentage,
) )
from homeassistant.util.scaling import int_states_in_range
from . import SmartThingsEntity from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN from .const import DATA_BROKERS, DOMAIN

View File

@ -14,10 +14,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value, percentage_to_ranged_value,
ranged_value_to_percentage, ranged_value_to_percentage,
) )
from homeassistant.util.scaling import int_states_in_range
from . import DOMAIN, SIGNAL_UPDATE_SMARTY from . import DOMAIN, SIGNAL_UPDATE_SMARTY

View File

@ -11,10 +11,10 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value, percentage_to_ranged_value,
ranged_value_to_percentage, ranged_value_to_percentage,
) )
from homeassistant.util.scaling import int_states_in_range
from .common import VeSyncDevice from .common import VeSyncDevice
from .const import DEV_TYPE_TO_HA, DOMAIN, SKU_TO_BASE_DEVICE, VS_DISCOVERY, VS_FANS from .const import DEV_TYPE_TO_HA, DOMAIN, SKU_TO_BASE_DEVICE, VS_DISCOVERY, VS_FANS

View File

@ -14,10 +14,10 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_platform from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value, percentage_to_ranged_value,
ranged_value_to_percentage, ranged_value_to_percentage,
) )
from homeassistant.util.scaling import int_states_in_range
from . import async_wemo_dispatcher_connect from . import async_wemo_dispatcher_connect
from .const import SERVICE_RESET_FILTER_LIFE, SERVICE_SET_HUMIDITY from .const import SERVICE_RESET_FILTER_LIFE, SERVICE_SET_HUMIDITY

View File

@ -20,10 +20,10 @@ from homeassistant.core import HomeAssistant, State, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
int_states_in_range,
percentage_to_ranged_value, percentage_to_ranged_value,
ranged_value_to_percentage, ranged_value_to_percentage,
) )
from homeassistant.util.scaling import int_states_in_range
from .core import discovery from .core import discovery
from .core.cluster_handlers import wrap_zigpy_exceptions from .core.cluster_handlers import wrap_zigpy_exceptions

View File

@ -7,6 +7,8 @@ from typing import NamedTuple
import attr import attr
from .scaling import scale_to_ranged_value
class RGBColor(NamedTuple): class RGBColor(NamedTuple):
"""RGB hex values.""" """RGB hex values."""
@ -744,3 +746,38 @@ def check_valid_gamut(Gamut: GamutType) -> bool:
) )
return not_on_line and red_valid and green_valid and blue_valid 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))),
)

View File

@ -3,6 +3,13 @@ from __future__ import annotations
from typing import TypeVar 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") _T = TypeVar("_T")
@ -69,8 +76,7 @@ def ranged_value_to_percentage(
(1,255), 127: 50 (1,255), 127: 50
(1,255), 10: 4 (1,255), 10: 4
""" """
offset = low_high_range[0] - 1 return scale_ranged_value_to_int_range(low_high_range, (1, 100), value)
return int(((value - offset) * 100) // states_in_range(low_high_range))
def percentage_to_ranged_value( def percentage_to_ranged_value(
@ -87,15 +93,4 @@ def percentage_to_ranged_value(
(1,255), 50: 127.5 (1,255), 50: 127.5
(1,255), 4: 10.2 (1,255), 4: 10.2
""" """
offset = low_high_range[0] - 1 return scale_to_ranged_value((1, 100), low_high_range, percentage)
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))

View File

@ -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))

View File

@ -0,0 +1,519 @@
# serializer version: 1
# name: test_brightness_to_254_range
dict({
1: 0.996078431372549,
2: 1.992156862745098,
3: 2.988235294117647,
4: 3.984313725490196,
5: 4.980392156862745,
6: 5.976470588235294,
7: 6.972549019607843,
8: 7.968627450980392,
9: 8.964705882352941,
10: 9.96078431372549,
11: 10.95686274509804,
12: 11.952941176470588,
13: 12.949019607843137,
14: 13.945098039215686,
15: 14.941176470588236,
16: 15.937254901960785,
17: 16.933333333333334,
18: 17.929411764705883,
19: 18.92549019607843,
20: 19.92156862745098,
21: 20.91764705882353,
22: 21.91372549019608,
23: 22.909803921568628,
24: 23.905882352941177,
25: 24.901960784313726,
26: 25.898039215686275,
27: 26.894117647058824,
28: 27.890196078431373,
29: 28.886274509803922,
30: 29.88235294117647,
31: 30.87843137254902,
32: 31.87450980392157,
33: 32.870588235294115,
34: 33.86666666666667,
35: 34.86274509803921,
36: 35.858823529411765,
37: 36.85490196078431,
38: 37.85098039215686,
39: 38.84705882352941,
40: 39.84313725490196,
41: 40.83921568627451,
42: 41.83529411764706,
43: 42.831372549019605,
44: 43.82745098039216,
45: 44.8235294117647,
46: 45.819607843137256,
47: 46.8156862745098,
48: 47.811764705882354,
49: 48.8078431372549,
50: 49.80392156862745,
51: 50.8,
52: 51.79607843137255,
53: 52.792156862745095,
54: 53.78823529411765,
55: 54.78431372549019,
56: 55.780392156862746,
57: 56.77647058823529,
58: 57.772549019607844,
59: 58.76862745098039,
60: 59.76470588235294,
61: 60.76078431372549,
62: 61.75686274509804,
63: 62.752941176470586,
64: 63.74901960784314,
65: 64.74509803921569,
66: 65.74117647058823,
67: 66.73725490196078,
68: 67.73333333333333,
69: 68.72941176470589,
70: 69.72549019607843,
71: 70.72156862745098,
72: 71.71764705882353,
73: 72.71372549019608,
74: 73.70980392156862,
75: 74.70588235294117,
76: 75.70196078431373,
77: 76.69803921568628,
78: 77.69411764705882,
79: 78.69019607843137,
80: 79.68627450980392,
81: 80.68235294117648,
82: 81.67843137254901,
83: 82.67450980392157,
84: 83.67058823529412,
85: 84.66666666666667,
86: 85.66274509803921,
87: 86.65882352941176,
88: 87.65490196078431,
89: 88.65098039215687,
90: 89.6470588235294,
91: 90.64313725490196,
92: 91.63921568627451,
93: 92.63529411764706,
94: 93.6313725490196,
95: 94.62745098039215,
96: 95.62352941176471,
97: 96.61960784313726,
98: 97.6156862745098,
99: 98.61176470588235,
100: 99.6078431372549,
101: 100.60392156862746,
102: 101.6,
103: 102.59607843137255,
104: 103.5921568627451,
105: 104.58823529411765,
106: 105.58431372549019,
107: 106.58039215686274,
108: 107.5764705882353,
109: 108.57254901960785,
110: 109.56862745098039,
111: 110.56470588235294,
112: 111.56078431372549,
113: 112.55686274509804,
114: 113.55294117647058,
115: 114.54901960784314,
116: 115.54509803921569,
117: 116.54117647058824,
118: 117.53725490196078,
119: 118.53333333333333,
120: 119.52941176470588,
121: 120.52549019607844,
122: 121.52156862745097,
123: 122.51764705882353,
124: 123.51372549019608,
125: 124.50980392156863,
126: 125.50588235294117,
127: 126.50196078431372,
128: 127.49803921568628,
129: 128.49411764705883,
130: 129.49019607843138,
131: 130.48627450980393,
132: 131.48235294117646,
133: 132.478431372549,
134: 133.47450980392156,
135: 134.47058823529412,
136: 135.46666666666667,
137: 136.46274509803922,
138: 137.45882352941177,
139: 138.45490196078433,
140: 139.45098039215685,
141: 140.4470588235294,
142: 141.44313725490196,
143: 142.4392156862745,
144: 143.43529411764706,
145: 144.4313725490196,
146: 145.42745098039217,
147: 146.42352941176472,
148: 147.41960784313724,
149: 148.4156862745098,
150: 149.41176470588235,
151: 150.4078431372549,
152: 151.40392156862745,
153: 152.4,
154: 153.39607843137256,
155: 154.3921568627451,
156: 155.38823529411764,
157: 156.3843137254902,
158: 157.38039215686274,
159: 158.3764705882353,
160: 159.37254901960785,
161: 160.3686274509804,
162: 161.36470588235295,
163: 162.3607843137255,
164: 163.35686274509803,
165: 164.35294117647058,
166: 165.34901960784313,
167: 166.34509803921569,
168: 167.34117647058824,
169: 168.3372549019608,
170: 169.33333333333334,
171: 170.3294117647059,
172: 171.32549019607842,
173: 172.32156862745097,
174: 173.31764705882352,
175: 174.31372549019608,
176: 175.30980392156863,
177: 176.30588235294118,
178: 177.30196078431374,
179: 178.2980392156863,
180: 179.2941176470588,
181: 180.29019607843136,
182: 181.28627450980392,
183: 182.28235294117647,
184: 183.27843137254902,
185: 184.27450980392157,
186: 185.27058823529413,
187: 186.26666666666668,
188: 187.2627450980392,
189: 188.25882352941176,
190: 189.2549019607843,
191: 190.25098039215686,
192: 191.24705882352941,
193: 192.24313725490197,
194: 193.23921568627452,
195: 194.23529411764707,
196: 195.2313725490196,
197: 196.22745098039215,
198: 197.2235294117647,
199: 198.21960784313725,
200: 199.2156862745098,
201: 200.21176470588236,
202: 201.2078431372549,
203: 202.20392156862746,
204: 203.2,
205: 204.19607843137254,
206: 205.1921568627451,
207: 206.18823529411765,
208: 207.1843137254902,
209: 208.18039215686275,
210: 209.1764705882353,
211: 210.17254901960786,
212: 211.16862745098038,
213: 212.16470588235293,
214: 213.1607843137255,
215: 214.15686274509804,
216: 215.1529411764706,
217: 216.14901960784314,
218: 217.1450980392157,
219: 218.14117647058825,
220: 219.13725490196077,
221: 220.13333333333333,
222: 221.12941176470588,
223: 222.12549019607843,
224: 223.12156862745098,
225: 224.11764705882354,
226: 225.1137254901961,
227: 226.10980392156864,
228: 227.10588235294117,
229: 228.10196078431372,
230: 229.09803921568627,
231: 230.09411764705882,
232: 231.09019607843138,
233: 232.08627450980393,
234: 233.08235294117648,
235: 234.07843137254903,
236: 235.07450980392156,
237: 236.0705882352941,
238: 237.06666666666666,
239: 238.06274509803922,
240: 239.05882352941177,
241: 240.05490196078432,
242: 241.05098039215687,
243: 242.04705882352943,
244: 243.04313725490195,
245: 244.0392156862745,
246: 245.03529411764706,
247: 246.0313725490196,
248: 247.02745098039216,
249: 248.0235294117647,
250: 249.01960784313727,
251: 250.01568627450982,
252: 251.01176470588234,
253: 252.0078431372549,
254: 253.00392156862745,
255: 254.0,
})
# ---
# name: test_brightness_to_254_range.1
dict({
0.996078431372549: 1,
1.992156862745098: 2,
2.988235294117647: 3,
3.984313725490196: 4,
4.980392156862745: 5,
5.976470588235294: 6,
6.972549019607843: 7,
7.968627450980392: 8,
8.964705882352941: 9,
9.96078431372549: 10,
10.95686274509804: 11,
11.952941176470588: 12,
12.949019607843137: 13,
13.945098039215686: 14,
14.941176470588236: 15,
15.937254901960785: 16,
16.933333333333334: 17,
17.929411764705883: 18,
18.92549019607843: 19,
19.92156862745098: 20,
20.91764705882353: 21,
21.91372549019608: 22,
22.909803921568628: 23,
23.905882352941177: 24,
24.901960784313726: 25,
25.898039215686275: 26,
26.894117647058824: 27,
27.890196078431373: 28,
28.886274509803922: 29,
29.88235294117647: 30,
30.87843137254902: 31,
31.87450980392157: 32,
32.870588235294115: 33,
33.86666666666667: 34,
34.86274509803921: 35,
35.858823529411765: 36,
36.85490196078431: 37,
37.85098039215686: 38,
38.84705882352941: 39,
39.84313725490196: 40,
40.83921568627451: 41,
41.83529411764706: 42,
42.831372549019605: 43,
43.82745098039216: 44,
44.8235294117647: 45,
45.819607843137256: 46,
46.8156862745098: 47,
47.811764705882354: 48,
48.8078431372549: 49,
49.80392156862745: 50,
50.8: 51,
51.79607843137255: 52,
52.792156862745095: 53,
53.78823529411765: 54,
54.78431372549019: 55,
55.780392156862746: 56,
56.77647058823529: 57,
57.772549019607844: 58,
58.76862745098039: 59,
59.76470588235294: 60,
60.76078431372549: 61,
61.75686274509804: 62,
62.752941176470586: 63,
63.74901960784314: 64,
64.74509803921569: 65,
65.74117647058823: 66,
66.73725490196078: 67,
67.73333333333333: 68,
68.72941176470589: 69,
69.72549019607843: 70,
70.72156862745098: 71,
71.71764705882353: 72,
72.71372549019608: 73,
73.70980392156862: 74,
74.70588235294117: 75,
75.70196078431373: 76,
76.69803921568628: 77,
77.69411764705882: 78,
78.69019607843137: 79,
79.68627450980392: 80,
80.68235294117648: 81,
81.67843137254901: 82,
82.67450980392157: 83,
83.67058823529412: 84,
84.66666666666667: 85,
85.66274509803921: 86,
86.65882352941176: 87,
87.65490196078431: 88,
88.65098039215687: 89,
89.6470588235294: 90,
90.64313725490196: 91,
91.63921568627451: 92,
92.63529411764706: 93,
93.6313725490196: 94,
94.62745098039215: 95,
95.62352941176471: 96,
96.61960784313726: 97,
97.6156862745098: 98,
98.61176470588235: 99,
99.6078431372549: 100,
100.60392156862746: 101,
101.6: 102,
102.59607843137255: 103,
103.5921568627451: 104,
104.58823529411765: 105,
105.58431372549019: 106,
106.58039215686274: 107,
107.5764705882353: 108,
108.57254901960785: 109,
109.56862745098039: 110,
110.56470588235294: 111,
111.56078431372549: 112,
112.55686274509804: 113,
113.55294117647058: 114,
114.54901960784314: 115,
115.54509803921569: 116,
116.54117647058824: 117,
117.53725490196078: 118,
118.53333333333333: 119,
119.52941176470588: 120,
120.52549019607844: 121,
121.52156862745097: 122,
122.51764705882353: 123,
123.51372549019608: 124,
124.50980392156863: 125,
125.50588235294117: 126,
126.50196078431372: 127,
127.49803921568628: 128,
128.49411764705883: 129,
129.49019607843138: 130,
130.48627450980393: 131,
131.48235294117646: 132,
132.478431372549: 133,
133.47450980392156: 134,
134.47058823529412: 135,
135.46666666666667: 136,
136.46274509803922: 137,
137.45882352941177: 138,
138.45490196078433: 139,
139.45098039215685: 140,
140.4470588235294: 141,
141.44313725490196: 142,
142.4392156862745: 143,
143.43529411764706: 144,
144.4313725490196: 145,
145.42745098039217: 146,
146.42352941176472: 147,
147.41960784313724: 148,
148.4156862745098: 149,
149.41176470588235: 150,
150.4078431372549: 151,
151.40392156862745: 152,
152.4: 153,
153.39607843137256: 154,
154.3921568627451: 155,
155.38823529411764: 156,
156.3843137254902: 157,
157.38039215686274: 158,
158.3764705882353: 159,
159.37254901960785: 160,
160.3686274509804: 161,
161.36470588235295: 162,
162.3607843137255: 163,
163.35686274509803: 164,
164.35294117647058: 165,
165.34901960784313: 166,
166.34509803921569: 167,
167.34117647058824: 168,
168.3372549019608: 169,
169.33333333333334: 170,
170.3294117647059: 171,
171.32549019607842: 172,
172.32156862745097: 173,
173.31764705882352: 174,
174.31372549019608: 175,
175.30980392156863: 176,
176.30588235294118: 177,
177.30196078431374: 178,
178.2980392156863: 179,
179.2941176470588: 180,
180.29019607843136: 181,
181.28627450980392: 182,
182.28235294117647: 183,
183.27843137254902: 184,
184.27450980392157: 185,
185.27058823529413: 186,
186.26666666666668: 187,
187.2627450980392: 188,
188.25882352941176: 189,
189.2549019607843: 190,
190.25098039215686: 191,
191.24705882352941: 192,
192.24313725490197: 193,
193.23921568627452: 194,
194.23529411764707: 195,
195.2313725490196: 196,
196.22745098039215: 197,
197.2235294117647: 198,
198.21960784313725: 199,
199.2156862745098: 200,
200.21176470588236: 201,
201.2078431372549: 202,
202.20392156862746: 203,
203.2: 204,
204.19607843137254: 205,
205.1921568627451: 206,
206.18823529411765: 207,
207.1843137254902: 208,
208.18039215686275: 209,
209.1764705882353: 210,
210.17254901960786: 211,
211.16862745098038: 212,
212.16470588235293: 213,
213.1607843137255: 214,
214.15686274509804: 215,
215.1529411764706: 216,
216.14901960784314: 217,
217.1450980392157: 218,
218.14117647058825: 219,
219.13725490196077: 220,
220.13333333333333: 221,
221.12941176470588: 222,
222.12549019607843: 223,
223.12156862745098: 224,
224.11764705882354: 225,
225.1137254901961: 226,
226.10980392156864: 227,
227.10588235294117: 228,
228.10196078431372: 229,
229.09803921568627: 230,
230.09411764705882: 231,
231.09019607843138: 232,
232.08627450980393: 233,
233.08235294117648: 234,
234.07843137254903: 235,
235.07450980392156: 236,
236.0705882352941: 237,
237.06666666666666: 238,
238.06274509803922: 239,
239.05882352941177: 240,
240.05490196078432: 241,
241.05098039215687: 242,
242.04705882352943: 243,
243.04313725490195: 244,
244.0392156862745: 245,
245.03529411764706: 246,
246.0313725490196: 247,
247.02745098039216: 248,
248.0235294117647: 249,
249.01960784313727: 250,
250.01568627450982: 251,
251.01176470588234: 252,
252.0078431372549: 253,
253.00392156862745: 254,
254.0: 255,
})
# ---

View File

@ -1,5 +1,8 @@
"""Test Home Assistant color util methods.""" """Test Home Assistant color util methods."""
import math
import pytest import pytest
from syrupy.assertion import SnapshotAssertion
import voluptuous as vol import voluptuous as vol
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
@ -587,3 +590,137 @@ def test_white_levels_to_color_temperature() -> None:
2000, 2000,
0, 0,
) )
@pytest.mark.parametrize(
("value", "brightness"),
[
(530, 255), # test min==255 clamp
(511, 255),
(255, 127),
(49, 24),
(1, 1),
(0, 1), # test max==1 clamp
],
)
async def test_ranged_value_to_brightness_large(value: float, brightness: int) -> None:
"""Test a large scale and clamping and convert a single value to a brightness."""
scale = (1, 511)
assert color_util.value_to_brightness(scale, value) == brightness
@pytest.mark.parametrize(
("brightness", "value", "math_ceil"),
[
(255, 511.0, 511),
(127, 254.49803921568628, 255),
(24, 48.09411764705882, 49),
],
)
async def test_brightness_to_ranged_value_large(
brightness: int, value: float, math_ceil: int
) -> None:
"""Test a large scale and convert a brightness to a single value."""
scale = (1, 511)
assert color_util.brightness_to_value(scale, brightness) == value
assert math.ceil(color_util.brightness_to_value(scale, brightness)) == math_ceil
@pytest.mark.parametrize(
("scale", "value", "brightness"),
[
((1, 4), 1, 64),
((1, 4), 2, 128),
((1, 4), 3, 191),
((1, 4), 4, 255),
((1, 6), 1, 42),
((1, 6), 2, 85),
((1, 6), 3, 128),
((1, 6), 4, 170),
((1, 6), 5, 212),
((1, 6), 6, 255),
],
)
async def test_ranged_value_to_brightness_small(
scale: tuple[float, float], value: float, brightness: int
) -> None:
"""Test a small scale and convert a single value to a brightness."""
assert color_util.value_to_brightness(scale, value) == brightness
@pytest.mark.parametrize(
("scale", "brightness", "value"),
[
((1, 4), 63, 1),
((1, 4), 127, 2),
((1, 4), 191, 3),
((1, 4), 255, 4),
((1, 6), 42, 1),
((1, 6), 85, 2),
((1, 6), 127, 3),
((1, 6), 170, 4),
((1, 6), 212, 5),
((1, 6), 255, 6),
],
)
async def test_brightness_to_ranged_value_small(
scale: tuple[float, float], brightness: int, value: float
) -> None:
"""Test a small scale and convert a brightness to a single value."""
assert math.ceil(color_util.brightness_to_value(scale, brightness)) == value
@pytest.mark.parametrize(
("value", "brightness"),
[
(101, 2),
(139, 64),
(178, 128),
(217, 192),
(255, 255),
],
)
async def test_ranged_value_to_brightness_starting_high(
value: float, brightness: int
) -> None:
"""Test a range that does not start with 1."""
scale = (101, 255)
assert color_util.value_to_brightness(scale, value) == brightness
@pytest.mark.parametrize(
("value", "brightness"),
[
(0, 64),
(1, 128),
(2, 191),
(3, 255),
],
)
async def test_ranged_value_to_brightness_starting_zero(
value: float, brightness: int
) -> None:
"""Test a range that starts with 0."""
scale = (0, 3)
assert color_util.value_to_brightness(scale, value) == brightness
async def test_brightness_to_254_range(snapshot: SnapshotAssertion) -> None:
"""Test brightness scaling to a 254 range and back."""
brightness_range = range(1, 256) # (1..255)
scale = (1, 254)
scaled_values = {
brightness: color_util.brightness_to_value(scale, brightness)
for brightness in brightness_range
}
assert scaled_values == snapshot
restored_values = {}
for expected_brightness, value in scaled_values.items():
restored_values[value] = color_util.value_to_brightness(scale, value)
assert color_util.value_to_brightness(scale, value) == expected_brightness
assert restored_values == snapshot

249
tests/util/test_scaling.py Normal file
View File

@ -0,0 +1,249 @@
"""Test Home Assistant scaling utils."""
import math
import pytest
from homeassistant.util.percentage import (
scale_ranged_value_to_int_range,
scale_to_ranged_value,
)
@pytest.mark.parametrize(
("input_val", "output_val"),
[
(255, 100),
(127, 49),
(10, 3),
(1, 0),
],
)
async def test_ranged_value_to_int_range_large(
input_val: float, output_val: int
) -> None:
"""Test a large range of low and high values convert a single value to a percentage."""
source_range = (1, 255)
dest_range = (1, 100)
assert (
scale_ranged_value_to_int_range(source_range, dest_range, input_val)
== output_val
)
@pytest.mark.parametrize(
("input_val", "output_val", "math_ceil"),
[
(100, 255, 255),
(50, 127.5, 128),
(4, 10.2, 11),
],
)
async def test_scale_to_ranged_value_large(
input_val: float, output_val: float, math_ceil: int
) -> None:
"""Test a large range of low and high values convert an int to a single value."""
source_range = (1, 100)
dest_range = (1, 255)
assert scale_to_ranged_value(source_range, dest_range, input_val) == output_val
assert (
math.ceil(scale_to_ranged_value(source_range, dest_range, input_val))
== math_ceil
)
@pytest.mark.parametrize(
("input_val", "output_val"),
[
(1, 16),
(2, 33),
(3, 50),
(4, 66),
(5, 83),
(6, 100),
],
)
async def test_scale_ranged_value_to_int_range_small(
input_val: float, output_val: int
) -> None:
"""Test a small range of low and high values convert a single value to a percentage."""
source_range = (1, 6)
dest_range = (1, 100)
assert (
scale_ranged_value_to_int_range(source_range, dest_range, input_val)
== output_val
)
@pytest.mark.parametrize(
("input_val", "output_val"),
[
(16, 1),
(33, 2),
(50, 3),
(66, 4),
(83, 5),
(100, 6),
],
)
async def test_scale_to_ranged_value_small(input_val: float, output_val: int) -> None:
"""Test a small range of low and high values convert an int to a single value."""
source_range = (1, 100)
dest_range = (1, 6)
assert (
math.ceil(scale_to_ranged_value(source_range, dest_range, input_val))
== output_val
)
@pytest.mark.parametrize(
("input_val", "output_val"),
[
(1, 25),
(2, 50),
(3, 75),
(4, 100),
],
)
async def test_scale_ranged_value_to_int_range_starting_at_one(
input_val: float, output_val: int
) -> None:
"""Test a range that starts with 1."""
source_range = (1, 4)
dest_range = (1, 100)
assert (
scale_ranged_value_to_int_range(source_range, dest_range, input_val)
== output_val
)
@pytest.mark.parametrize(
("input_val", "output_val"),
[
(101, 0),
(139, 25),
(178, 50),
(217, 75),
(255, 100),
],
)
async def test_scale_ranged_value_to_int_range_starting_high(
input_val: float, output_val: int
) -> None:
"""Test a range that does not start with 1."""
source_range = (101, 255)
dest_range = (1, 100)
assert (
scale_ranged_value_to_int_range(source_range, dest_range, input_val)
== output_val
)
@pytest.mark.parametrize(
("input_val", "output_int", "output_float"),
[
(0.0, 25, 25.0),
(1.0, 50, 50.0),
(2.0, 75, 75.0),
(3.0, 100, 100.0),
],
)
async def test_scale_ranged_value_to_scaled_range_starting_zero(
input_val: float, output_int: int, output_float: float
) -> None:
"""Test a range that starts with 0."""
source_range = (0, 3)
dest_range = (1, 100)
assert (
scale_ranged_value_to_int_range(source_range, dest_range, input_val)
== output_int
)
assert scale_to_ranged_value(source_range, dest_range, input_val) == output_float
assert scale_ranged_value_to_int_range(
dest_range, source_range, output_float
) == int(input_val)
assert scale_to_ranged_value(dest_range, source_range, output_float) == input_val
@pytest.mark.parametrize(
("input_val", "output_val"),
[
(101, 100),
(139, 125),
(178, 150),
(217, 175),
(255, 200),
],
)
async def test_scale_ranged_value_to_int_range_starting_high_with_offset(
input_val: float, output_val: int
) -> None:
"""Test a ranges that do not start with 1."""
source_range = (101, 255)
dest_range = (101, 200)
assert (
scale_ranged_value_to_int_range(source_range, dest_range, input_val)
== output_val
)
@pytest.mark.parametrize(
("input_val", "output_val"),
[
(0, 125),
(1, 150),
(2, 175),
(3, 200),
],
)
async def test_scale_ranged_value_to_int_range_starting_zero_with_offset(
input_val: float, output_val: int
) -> None:
"""Test a range that starts with 0 and an other starting high."""
source_range = (0, 3)
dest_range = (101, 200)
assert (
scale_ranged_value_to_int_range(source_range, dest_range, input_val)
== output_val
)
@pytest.mark.parametrize(
("input_val", "output_int", "output_float"),
[
(0.0, 1, 1.0),
(1.0, 3, 3.0),
(2.0, 5, 5.0),
(3.0, 7, 7.0),
],
)
async def test_scale_ranged_value_to_int_range_starting_zero_with_zero_offset(
input_val: float, output_int: int, output_float: float
) -> None:
"""Test a ranges that start with 0.
In case a range starts with 0, this means value 0 is the first value,
and the values shift -1.
"""
source_range = (0, 3)
dest_range = (0, 7)
assert (
scale_ranged_value_to_int_range(source_range, dest_range, input_val)
== output_int
)
assert scale_to_ranged_value(source_range, dest_range, input_val) == output_float
assert scale_ranged_value_to_int_range(dest_range, source_range, output_int) == int(
input_val
)
assert scale_to_ranged_value(dest_range, source_range, output_float) == input_val