Skip to content

Commit 81ed259

Browse files
epenetCopilot
andauthored
Move Tuya type information classes to separate module (home-assistant#157958)
Co-authored-by: Copilot <[email protected]>
1 parent 5f00452 commit 81ed259

File tree

5 files changed

+251
-236
lines changed

5 files changed

+251
-236
lines changed

homeassistant/components/tuya/light.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
DPCodeEnumWrapper,
3636
DPCodeIntegerWrapper,
3737
DPCodeJsonWrapper,
38-
IntegerTypeData,
3938
)
39+
from .type_information import IntegerTypeInformation
4040
from .util import remap_value
4141

4242

@@ -138,24 +138,24 @@ def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any
138138
)
139139

140140

141-
DEFAULT_H_TYPE = IntegerTypeData(
141+
DEFAULT_H_TYPE = IntegerTypeInformation(
142142
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1
143143
)
144-
DEFAULT_S_TYPE = IntegerTypeData(
144+
DEFAULT_S_TYPE = IntegerTypeInformation(
145145
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=255, step=1
146146
)
147-
DEFAULT_V_TYPE = IntegerTypeData(
147+
DEFAULT_V_TYPE = IntegerTypeInformation(
148148
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=255, step=1
149149
)
150150

151151

152-
DEFAULT_H_TYPE_V2 = IntegerTypeData(
152+
DEFAULT_H_TYPE_V2 = IntegerTypeInformation(
153153
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1
154154
)
155-
DEFAULT_S_TYPE_V2 = IntegerTypeData(
155+
DEFAULT_S_TYPE_V2 = IntegerTypeInformation(
156156
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1
157157
)
158-
DEFAULT_V_TYPE_V2 = IntegerTypeData(
158+
DEFAULT_V_TYPE_V2 = IntegerTypeInformation(
159159
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1
160160
)
161161

@@ -578,15 +578,15 @@ def _get_color_data_wrapper(
578578
if function_data := json_loads_object(
579579
cast(str, color_data_wrapper.type_information.type_data)
580580
):
581-
color_data_wrapper.h_type = IntegerTypeData(
581+
color_data_wrapper.h_type = IntegerTypeInformation(
582582
dpcode=color_data_wrapper.dpcode,
583583
**cast(dict, function_data["h"]),
584584
)
585-
color_data_wrapper.s_type = IntegerTypeData(
585+
color_data_wrapper.s_type = IntegerTypeInformation(
586586
dpcode=color_data_wrapper.dpcode,
587587
**cast(dict, function_data["s"]),
588588
)
589-
color_data_wrapper.v_type = IntegerTypeData(
589+
color_data_wrapper.v_type = IntegerTypeInformation(
590590
dpcode=color_data_wrapper.dpcode,
591591
**cast(dict, function_data["v"]),
592592
)

homeassistant/components/tuya/models.py

Lines changed: 13 additions & 221 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@
33
from __future__ import annotations
44

55
import base64
6-
from dataclasses import dataclass
7-
from typing import Any, Literal, Self, cast, overload
6+
from typing import Any, Self
87

98
from tuya_sharing import CustomerDevice
109

11-
from homeassistant.util.json import json_loads, json_loads_object
10+
from homeassistant.util.json import json_loads
1211

1312
from .const import LOGGER, DPType
14-
from .util import parse_dptype, remap_value
13+
from .type_information import (
14+
EnumTypeInformation,
15+
IntegerTypeInformation,
16+
TypeInformation,
17+
find_dpcode,
18+
)
1519

1620
# Dictionary to track logged warnings to avoid spamming logs
1721
# Keyed by device ID
@@ -32,139 +36,6 @@ def _should_log_warning(device_id: str, warning_key: str) -> bool:
3236
return True
3337

3438

35-
@dataclass(kw_only=True)
36-
class TypeInformation:
37-
"""Type information.
38-
39-
As provided by the SDK, from `device.function` / `device.status_range`.
40-
"""
41-
42-
dpcode: str
43-
type_data: str | None = None
44-
45-
@classmethod
46-
def from_json(cls, dpcode: str, type_data: str) -> Self | None:
47-
"""Load JSON string and return a TypeInformation object."""
48-
return cls(dpcode=dpcode, type_data=type_data)
49-
50-
51-
@dataclass(kw_only=True)
52-
class IntegerTypeData(TypeInformation):
53-
"""Integer Type Data."""
54-
55-
min: int
56-
max: int
57-
scale: int
58-
step: int
59-
unit: str | None = None
60-
61-
@property
62-
def max_scaled(self) -> float:
63-
"""Return the max scaled."""
64-
return self.scale_value(self.max)
65-
66-
@property
67-
def min_scaled(self) -> float:
68-
"""Return the min scaled."""
69-
return self.scale_value(self.min)
70-
71-
@property
72-
def step_scaled(self) -> float:
73-
"""Return the step scaled."""
74-
return self.step / (10**self.scale)
75-
76-
def scale_value(self, value: int) -> float:
77-
"""Scale a value."""
78-
return value / (10**self.scale)
79-
80-
def scale_value_back(self, value: float) -> int:
81-
"""Return raw value for scaled."""
82-
return round(value * (10**self.scale))
83-
84-
def remap_value_to(
85-
self,
86-
value: float,
87-
to_min: float = 0,
88-
to_max: float = 255,
89-
reverse: bool = False,
90-
) -> float:
91-
"""Remap a value from this range to a new range."""
92-
return remap_value(value, self.min, self.max, to_min, to_max, reverse)
93-
94-
def remap_value_from(
95-
self,
96-
value: float,
97-
from_min: float = 0,
98-
from_max: float = 255,
99-
reverse: bool = False,
100-
) -> float:
101-
"""Remap a value from its current range to this range."""
102-
return remap_value(value, from_min, from_max, self.min, self.max, reverse)
103-
104-
@classmethod
105-
def from_json(cls, dpcode: str, type_data: str) -> Self | None:
106-
"""Load JSON string and return a IntegerTypeData object."""
107-
if not (parsed := cast(dict[str, Any] | None, json_loads_object(type_data))):
108-
return None
109-
110-
return cls(
111-
dpcode=dpcode,
112-
type_data=type_data,
113-
min=int(parsed["min"]),
114-
max=int(parsed["max"]),
115-
scale=int(parsed["scale"]),
116-
step=int(parsed["step"]),
117-
unit=parsed.get("unit"),
118-
)
119-
120-
121-
@dataclass(kw_only=True)
122-
class BitmapTypeInformation(TypeInformation):
123-
"""Bitmap type information."""
124-
125-
label: list[str]
126-
127-
@classmethod
128-
def from_json(cls, dpcode: str, type_data: str) -> Self | None:
129-
"""Load JSON string and return a BitmapTypeInformation object."""
130-
if not (parsed := json_loads_object(type_data)):
131-
return None
132-
return cls(
133-
dpcode=dpcode,
134-
type_data=type_data,
135-
**cast(dict[str, list[str]], parsed),
136-
)
137-
138-
139-
@dataclass(kw_only=True)
140-
class EnumTypeData(TypeInformation):
141-
"""Enum Type Data."""
142-
143-
range: list[str]
144-
145-
@classmethod
146-
def from_json(cls, dpcode: str, type_data: str) -> Self | None:
147-
"""Load JSON string and return a EnumTypeData object."""
148-
if not (parsed := json_loads_object(type_data)):
149-
return None
150-
return cls(
151-
dpcode=dpcode,
152-
type_data=type_data,
153-
**cast(dict[str, list[str]], parsed),
154-
)
155-
156-
157-
_TYPE_INFORMATION_MAPPINGS: dict[DPType, type[TypeInformation]] = {
158-
DPType.BITMAP: BitmapTypeInformation,
159-
DPType.BOOLEAN: TypeInformation,
160-
DPType.ENUM: EnumTypeData,
161-
DPType.INTEGER: IntegerTypeData,
162-
DPType.JSON: TypeInformation,
163-
DPType.RAW: TypeInformation,
164-
DPType.STRING: TypeInformation,
165-
}
166-
167-
16839
class DeviceWrapper:
16940
"""Base device wrapper."""
17041

@@ -303,8 +174,8 @@ def read_json(self, device: CustomerDevice) -> Any | None:
303174
return json_loads(raw_value)
304175

305176

306-
class DPCodeEnumWrapper(DPCodeTypeInformationWrapper[EnumTypeData]):
307-
"""Simple wrapper for EnumTypeData values."""
177+
class DPCodeEnumWrapper(DPCodeTypeInformationWrapper[EnumTypeInformation]):
178+
"""Simple wrapper for EnumTypeInformation values."""
308179

309180
DPTYPE = DPType.ENUM
310181

@@ -342,12 +213,12 @@ def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any
342213
)
343214

344215

345-
class DPCodeIntegerWrapper(DPCodeTypeInformationWrapper[IntegerTypeData]):
346-
"""Simple wrapper for IntegerTypeData values."""
216+
class DPCodeIntegerWrapper(DPCodeTypeInformationWrapper[IntegerTypeInformation]):
217+
"""Simple wrapper for IntegerTypeInformation values."""
347218

348219
DPTYPE = DPType.INTEGER
349220

350-
def __init__(self, dpcode: str, type_information: IntegerTypeData) -> None:
221+
def __init__(self, dpcode: str, type_information: IntegerTypeInformation) -> None:
351222
"""Init DPCodeIntegerWrapper."""
352223
super().__init__(dpcode, type_information)
353224
self.native_unit = type_information.unit
@@ -414,82 +285,3 @@ def find_dpcode(
414285
type_information.dpcode, type_information.label.index(bitmap_key)
415286
)
416287
return None
417-
418-
419-
@overload
420-
def find_dpcode(
421-
device: CustomerDevice,
422-
dpcodes: str | tuple[str, ...] | None,
423-
*,
424-
prefer_function: bool = False,
425-
dptype: Literal[DPType.BITMAP],
426-
) -> BitmapTypeInformation | None: ...
427-
428-
429-
@overload
430-
def find_dpcode(
431-
device: CustomerDevice,
432-
dpcodes: str | tuple[str, ...] | None,
433-
*,
434-
prefer_function: bool = False,
435-
dptype: Literal[DPType.ENUM],
436-
) -> EnumTypeData | None: ...
437-
438-
439-
@overload
440-
def find_dpcode(
441-
device: CustomerDevice,
442-
dpcodes: str | tuple[str, ...] | None,
443-
*,
444-
prefer_function: bool = False,
445-
dptype: Literal[DPType.INTEGER],
446-
) -> IntegerTypeData | None: ...
447-
448-
449-
@overload
450-
def find_dpcode(
451-
device: CustomerDevice,
452-
dpcodes: str | tuple[str, ...] | None,
453-
*,
454-
prefer_function: bool = False,
455-
dptype: Literal[DPType.BOOLEAN, DPType.JSON, DPType.RAW],
456-
) -> TypeInformation | None: ...
457-
458-
459-
def find_dpcode(
460-
device: CustomerDevice,
461-
dpcodes: str | tuple[str, ...] | None,
462-
*,
463-
prefer_function: bool = False,
464-
dptype: DPType,
465-
) -> TypeInformation | None:
466-
"""Find type information for a matching DP code available for this device."""
467-
if not (type_information_cls := _TYPE_INFORMATION_MAPPINGS.get(dptype)):
468-
raise NotImplementedError(f"find_dpcode not supported for {dptype}")
469-
470-
if dpcodes is None:
471-
return None
472-
473-
if not isinstance(dpcodes, tuple):
474-
dpcodes = (dpcodes,)
475-
476-
lookup_tuple = (
477-
(device.function, device.status_range)
478-
if prefer_function
479-
else (device.status_range, device.function)
480-
)
481-
482-
for dpcode in dpcodes:
483-
for device_specs in lookup_tuple:
484-
if (
485-
(current_definition := device_specs.get(dpcode))
486-
and parse_dptype(current_definition.type) is dptype
487-
and (
488-
type_information := type_information_cls.from_json(
489-
dpcode=dpcode, type_data=current_definition.values
490-
)
491-
)
492-
):
493-
return type_information
494-
495-
return None

homeassistant/components/tuya/number.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
DPCode,
2626
)
2727
from .entity import TuyaEntity
28-
from .models import DPCodeIntegerWrapper, IntegerTypeData
28+
from .models import DPCodeIntegerWrapper
2929

3030
NUMBERS: dict[DeviceCategory, tuple[NumberEntityDescription, ...]] = {
3131
DeviceCategory.BH: (
@@ -483,8 +483,6 @@ def async_discover_device(device_ids: list[str]) -> None:
483483
class TuyaNumberEntity(TuyaEntity, NumberEntity):
484484
"""Tuya Number Entity."""
485485

486-
_number: IntegerTypeData | None = None
487-
488486
def __init__(
489487
self,
490488
device: CustomerDevice,

homeassistant/components/tuya/sensor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@
4646
DPCodeJsonWrapper,
4747
DPCodeTypeInformationWrapper,
4848
DPCodeWrapper,
49-
EnumTypeData,
5049
)
5150
from .raw_data_models import ElectricityData
51+
from .type_information import EnumTypeInformation
5252

5353

54-
class _WindDirectionWrapper(DPCodeTypeInformationWrapper[EnumTypeData]):
54+
class _WindDirectionWrapper(DPCodeTypeInformationWrapper[EnumTypeInformation]):
5555
"""Custom DPCode Wrapper for converting enum to wind direction."""
5656

5757
DPTYPE = DPType.ENUM

0 commit comments

Comments
 (0)