33from __future__ import annotations
44
55import base64
6- from dataclasses import dataclass
7- from typing import Any , Literal , Self , cast , overload
6+ from typing import Any , Self
87
98from 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
1312from .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-
16839class 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
0 commit comments