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.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

View file

@ -22,10 +22,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
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

View file

@ -13,10 +13,10 @@ from homeassistant.core import HomeAssistant
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

View file

@ -14,10 +14,10 @@ from homeassistant.core import HomeAssistant
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

View file

@ -12,10 +12,10 @@ from homeassistant.core import HomeAssistant
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,

View file

@ -31,10 +31,10 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
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

View file

@ -3,7 +3,7 @@ from __future__ import annotations
from contextlib import suppress
import logging
from typing import Any, cast
from typing import TYPE_CHECKING, Any, cast
import voluptuous as vol
@ -367,10 +367,10 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
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(
@ -591,13 +591,12 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
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:

View file

@ -13,10 +13,10 @@ from homeassistant.config_entries import ConfigEntry
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

View file

@ -12,10 +12,10 @@ from homeassistant.config_entries import ConfigEntry
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

View file

@ -14,10 +14,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
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

View file

@ -11,10 +11,10 @@ from homeassistant.core import HomeAssistant, callback
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

View file

@ -14,10 +14,10 @@ from homeassistant.core import HomeAssistant, callback
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

View file

@ -20,10 +20,10 @@ from homeassistant.core import HomeAssistant, State, callback
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

View file

@ -7,6 +7,8 @@ from typing import NamedTuple
import attr
from .scaling import scale_to_ranged_value
class RGBColor(NamedTuple):
"""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
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 .scaling import ( # noqa: F401
int_states_in_range,
scale_ranged_value_to_int_range,
scale_to_ranged_value,
states_in_range,
)
_T = TypeVar("_T")
@ -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(
@ -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)

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."""
import math
import pytest
from syrupy.assertion import SnapshotAssertion
import voluptuous as vol
import homeassistant.util.color as color_util
@ -587,3 +590,137 @@ def test_white_levels_to_color_temperature() -> None:
2000,
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