feat: update entity value-range

This commit is contained in:
topsworld 2025-01-08 19:29:39 +08:00
parent 078adfbd4c
commit 3399e3bb20
10 changed files with 78 additions and 70 deletions

View File

@ -180,26 +180,26 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
self._attr_hvac_modes = list(self._hvac_mode_map.values()) self._attr_hvac_modes = list(self._hvac_mode_map.values())
self._prop_mode = prop self._prop_mode = prop
elif prop.name == 'target-temperature': elif prop.name == 'target-temperature':
if not isinstance(prop.value_range, dict): if not prop.value_range:
_LOGGER.error( _LOGGER.error(
'invalid target-temperature value_range format, %s', 'invalid target-temperature value_range format, %s',
self.entity_id) self.entity_id)
continue continue
self._attr_min_temp = prop.value_range['min'] self._attr_min_temp = prop.value_range.min_
self._attr_max_temp = prop.value_range['max'] self._attr_max_temp = prop.value_range.max_
self._attr_target_temperature_step = prop.value_range['step'] self._attr_target_temperature_step = prop.value_range.step
self._attr_temperature_unit = prop.external_unit self._attr_temperature_unit = prop.external_unit
self._attr_supported_features |= ( self._attr_supported_features |= (
ClimateEntityFeature.TARGET_TEMPERATURE) ClimateEntityFeature.TARGET_TEMPERATURE)
self._prop_target_temp = prop self._prop_target_temp = prop
elif prop.name == 'target-humidity': elif prop.name == 'target-humidity':
if not isinstance(prop.value_range, dict): if not prop.value_range:
_LOGGER.error( _LOGGER.error(
'invalid target-humidity value_range format, %s', 'invalid target-humidity value_range format, %s',
self.entity_id) self.entity_id)
continue continue
self._attr_min_humidity = prop.value_range['min'] self._attr_min_humidity = prop.value_range.min_
self._attr_max_humidity = prop.value_range['max'] self._attr_max_humidity = prop.value_range.max_
self._attr_supported_features |= ( self._attr_supported_features |= (
ClimateEntityFeature.TARGET_HUMIDITY) ClimateEntityFeature.TARGET_HUMIDITY)
self._prop_target_humi = prop self._prop_target_humi = prop
@ -517,14 +517,14 @@ class Heater(MIoTServiceEntity, ClimateEntity):
ClimateEntityFeature.TURN_OFF) ClimateEntityFeature.TURN_OFF)
self._prop_on = prop self._prop_on = prop
elif prop.name == 'target-temperature': elif prop.name == 'target-temperature':
if not isinstance(prop.value_range, dict): if not prop.value_range:
_LOGGER.error( _LOGGER.error(
'invalid target-temperature value_range format, %s', 'invalid target-temperature value_range format, %s',
self.entity_id) self.entity_id)
continue continue
self._attr_min_temp = prop.value_range['min'] self._attr_min_temp = prop.value_range.min_
self._attr_max_temp = prop.value_range['max'] self._attr_max_temp = prop.value_range.max_
self._attr_target_temperature_step = prop.value_range['step'] self._attr_target_temperature_step = prop.value_range.step
self._attr_temperature_unit = prop.external_unit self._attr_temperature_unit = prop.external_unit
self._attr_supported_features |= ( self._attr_supported_features |= (
ClimateEntityFeature.TARGET_TEMPERATURE) ClimateEntityFeature.TARGET_TEMPERATURE)

View File

@ -172,13 +172,13 @@ class Cover(MIoTServiceEntity, CoverEntity):
elif prop.name == 'current-position': elif prop.name == 'current-position':
self._prop_current_position = prop self._prop_current_position = prop
elif prop.name == 'target-position': elif prop.name == 'target-position':
if not isinstance(prop.value_range, dict): if not prop.value_range:
_LOGGER.error( _LOGGER.error(
'invalid target-position value_range format, %s', 'invalid target-position value_range format, %s',
self.entity_id) self.entity_id)
continue continue
self._prop_position_value_min = prop.value_range['min'] self._prop_position_value_min = prop.value_range.min_
self._prop_position_value_max = prop.value_range['max'] self._prop_position_value_max = prop.value_range.max_
self._prop_position_value_range = ( self._prop_position_value_range = (
self._prop_position_value_max - self._prop_position_value_max -
self._prop_position_value_min) self._prop_position_value_min)

View File

@ -119,11 +119,11 @@ class Fan(MIoTServiceEntity, FanEntity):
self._attr_supported_features |= FanEntityFeature.TURN_OFF self._attr_supported_features |= FanEntityFeature.TURN_OFF
self._prop_on = prop self._prop_on = prop
elif prop.name == 'fan-level': elif prop.name == 'fan-level':
if isinstance(prop.value_range, dict): if prop.value_range:
# Fan level with value-range # Fan level with value-range
self._speed_min = prop.value_range['min'] self._speed_min = prop.value_range.min_
self._speed_max = prop.value_range['max'] self._speed_max = prop.value_range.max_
self._speed_step = prop.value_range['step'] self._speed_step = prop.value_range.step
self._attr_speed_count = self._speed_max - self._speed_min+1 self._attr_speed_count = self._speed_max - self._speed_min+1
self._attr_supported_features |= FanEntityFeature.SET_SPEED self._attr_supported_features |= FanEntityFeature.SET_SPEED
self._prop_fan_level = prop self._prop_fan_level = prop

View File

@ -119,13 +119,13 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
self._prop_on = prop self._prop_on = prop
# target-humidity # target-humidity
elif prop.name == 'target-humidity': elif prop.name == 'target-humidity':
if not isinstance(prop.value_range, dict): if not prop.value_range:
_LOGGER.error( _LOGGER.error(
'invalid target-humidity value_range format, %s', 'invalid target-humidity value_range format, %s',
self.entity_id) self.entity_id)
continue continue
self._attr_min_humidity = prop.value_range['min'] self._attr_min_humidity = prop.value_range.min_
self._attr_max_humidity = prop.value_range['max'] self._attr_max_humidity = prop.value_range.max_
self._prop_target_humidity = prop self._prop_target_humidity = prop
# mode # mode
elif prop.name == 'mode': elif prop.name == 'mode':

View File

@ -131,9 +131,9 @@ class Light(MIoTServiceEntity, LightEntity):
self._prop_on = prop self._prop_on = prop
# brightness # brightness
if prop.name == 'brightness': if prop.name == 'brightness':
if isinstance(prop.value_range, dict): if prop.value_range:
self._brightness_scale = ( self._brightness_scale = (
prop.value_range['min'], prop.value_range['max']) prop.value_range.min_, prop.value_range.max_)
self._prop_brightness = prop self._prop_brightness = prop
elif ( elif (
self._mode_list is None self._mode_list is None
@ -153,13 +153,13 @@ class Light(MIoTServiceEntity, LightEntity):
continue continue
# color-temperature # color-temperature
if prop.name == 'color-temperature': if prop.name == 'color-temperature':
if not isinstance(prop.value_range, dict): if not prop.value_range:
_LOGGER.info( _LOGGER.info(
'invalid color-temperature value_range format, %s', 'invalid color-temperature value_range format, %s',
self.entity_id) self.entity_id)
continue continue
self._attr_min_color_temp_kelvin = prop.value_range['min'] self._attr_min_color_temp_kelvin = prop.value_range.min_
self._attr_max_color_temp_kelvin = prop.value_range['max'] self._attr_max_color_temp_kelvin = prop.value_range.max_
self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP) self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP)
self._attr_color_mode = ColorMode.COLOR_TEMP self._attr_color_mode = ColorMode.COLOR_TEMP
self._prop_color_temp = prop self._prop_color_temp = prop
@ -178,13 +178,13 @@ class Light(MIoTServiceEntity, LightEntity):
mode_list = { mode_list = {
item['value']: item['description'] item['value']: item['description']
for item in prop.value_list} for item in prop.value_list}
elif isinstance(prop.value_range, dict): elif prop.value_range:
mode_list = {} mode_list = {}
if ( if (
int(( int((
prop.value_range['max'] prop.value_range.max_
- prop.value_range['min'] - prop.value_range.min_
) / prop.value_range['step']) ) / prop.value_range.step)
> self._VALUE_RANGE_MODE_COUNT_MAX > self._VALUE_RANGE_MODE_COUNT_MAX
): ):
_LOGGER.info( _LOGGER.info(
@ -192,9 +192,9 @@ class Light(MIoTServiceEntity, LightEntity):
self.entity_id, prop.name, prop.value_range) self.entity_id, prop.name, prop.value_range)
else: else:
for value in range( for value in range(
prop.value_range['min'], prop.value_range.min_,
prop.value_range['max'], prop.value_range.max_,
prop.value_range['step']): prop.value_range.step):
mode_list[value] = f'mode {value}' mode_list[value] = f'mode {value}'
if mode_list: if mode_list:
self._mode_list = mode_list self._mode_list = mode_list

View File

@ -94,7 +94,8 @@ from .miot_spec import (
MIoTSpecEvent, MIoTSpecEvent,
MIoTSpecInstance, MIoTSpecInstance,
MIoTSpecProperty, MIoTSpecProperty,
MIoTSpecService MIoTSpecService,
MIoTSpecValueRange
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -1006,8 +1007,7 @@ class MIoTPropertyEntity(Entity):
service: MIoTSpecService service: MIoTSpecService
_main_loop: asyncio.AbstractEventLoop _main_loop: asyncio.AbstractEventLoop
# {'min':int, 'max':int, 'step': int} _value_range: Optional[MIoTSpecValueRange]
_value_range: dict[str, int]
# {Any: Any} # {Any: Any}
_value_list: Optional[dict[Any, Any]] _value_list: Optional[dict[Any, Any]]
_value: Any _value: Any

View File

@ -64,13 +64,13 @@ from .miot_storage import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class _MIoTSpecValueRange: class MIoTSpecValueRange:
"""MIoT SPEC value range class.""" """MIoT SPEC value range class."""
min_: int min_: int
max_: int max_: int
step: int step: int
def __init__(self, value_range: Union[dict, list, None]) -> None: def __init__(self, value_range: Union[dict, list]) -> None:
if isinstance(value_range, dict): if isinstance(value_range, dict):
self.load(value_range) self.load(value_range)
elif isinstance(value_range, list): elif isinstance(value_range, list):
@ -101,6 +101,9 @@ class _MIoTSpecValueRange:
'step': self.step 'step': self.step
} }
def __str__(self) -> str:
return f'[{self.min_}, {self.max_}, {self.step}'
class _MIoTSpecValueListItem: class _MIoTSpecValueListItem:
"""MIoT SPEC value list item class.""" """MIoT SPEC value list item class."""
@ -419,10 +422,11 @@ class _MIoTSpecBase:
class MIoTSpecProperty(_MIoTSpecBase): class MIoTSpecProperty(_MIoTSpecBase):
"""MIoT SPEC property class.""" """MIoT SPEC property class."""
format_: str format_: str
precision: int
unit: Optional[str] unit: Optional[str]
precision: int
_value_range: Optional[MIoTSpecValueRange]
_value_range: Optional[_MIoTSpecValueRange]
value_list: Optional[list[dict]] value_list: Optional[list[dict]]
_access: list _access: list
@ -441,7 +445,7 @@ class MIoTSpecProperty(_MIoTSpecBase):
unit: Optional[str] = None, unit: Optional[str] = None,
value_range: Optional[dict] = None, value_range: Optional[dict] = None,
value_list: Optional[list[dict]] = None, value_list: Optional[list[dict]] = None,
precision: int = 0 precision: Optional[int] = None
) -> None: ) -> None:
super().__init__(spec=spec) super().__init__(spec=spec)
self.service = service self.service = service
@ -450,7 +454,7 @@ class MIoTSpecProperty(_MIoTSpecBase):
self.unit = unit self.unit = unit
self.value_range = value_range self.value_range = value_range
self.value_list = value_list self.value_list = value_list
self.precision = precision self.precision = precision or 1
self.spec_id = hash( self.spec_id = hash(
f'p.{self.name}.{self.service.iid}.{self.iid}') f'p.{self.name}.{self.service.iid}.{self.iid}')
@ -480,12 +484,19 @@ class MIoTSpecProperty(_MIoTSpecBase):
return self._notifiable return self._notifiable
@property @property
def value_range(self) -> Optional[_MIoTSpecValueRange]: def value_range(self) -> Optional[MIoTSpecValueRange]:
return self._value_range return self._value_range
@value_range.setter @value_range.setter
def value_range(self, value: Union[dict, list, None]) -> None: def value_range(self, value: Union[dict, list, None]) -> None:
self._value_range = _MIoTSpecValueRange(value_range=value) """Set value-range, precision."""
if not value:
self._value_range = None
return
self._value_range = MIoTSpecValueRange(value_range=value)
if isinstance(value, list):
self.precision = len(str(value[2]).split(
'.')[1].rstrip('0')) if '.' in str(value[2]) else 0
def value_format(self, value: Any) -> Any: def value_format(self, value: Any) -> Any:
if value is None: if value is None:
@ -648,7 +659,7 @@ class MIoTSpecInstance:
unit=prop['unit'], unit=prop['unit'],
value_range=prop['value_range'], value_range=prop['value_range'],
value_list=prop['value_list'], value_list=prop['value_list'],
precision=prop.get('precision', 0)) precision=prop.get('precision', None))
spec_service.properties.append(spec_prop) spec_service.properties.append(spec_prop)
for event in service['events']: for event in service['events']:
spec_event = MIoTSpecEvent( spec_event = MIoTSpecEvent(
@ -753,9 +764,9 @@ class _MIoTSpecMultiLang:
_LOGGER.info('get multi lang from local failed, %s, %s', urn, err) _LOGGER.info('get multi lang from local failed, %s, %s', urn, err)
# Default language # Default language
if not trans_cache: if not trans_cache:
if DEFAULT_INTEGRATION_LANGUAGE in trans_cloud: if trans_cloud and DEFAULT_INTEGRATION_LANGUAGE in trans_cloud:
trans_cache = trans_cloud[DEFAULT_INTEGRATION_LANGUAGE] trans_cache = trans_cloud[DEFAULT_INTEGRATION_LANGUAGE]
if DEFAULT_INTEGRATION_LANGUAGE in trans_local: if trans_local and DEFAULT_INTEGRATION_LANGUAGE in trans_local:
trans_cache.update( trans_cache.update(
trans_local[DEFAULT_INTEGRATION_LANGUAGE]) trans_local[DEFAULT_INTEGRATION_LANGUAGE])
trans_data: dict[str, str] = {} trans_data: dict[str, str] = {}
@ -892,11 +903,11 @@ class MIoTSpecParser:
return MIoTSpecInstance.load(specs=cache_result) return MIoTSpecInstance.load(specs=cache_result)
# Retry three times # Retry three times
for index in range(3): for index in range(3):
try: # try:
return await self.__parse(urn=urn) return await self.__parse(urn=urn)
except Exception as err: # pylint: disable=broad-exception-caught # except Exception as err: # pylint: disable=broad-exception-caught
_LOGGER.error( # _LOGGER.error(
'parse error, retry, %d, %s, %s', index, urn, err) # 'parse error, retry, %d, %s, %s', index, urn, err)
return None return None
async def refresh_async(self, urn_list: list[str]) -> int: async def refresh_async(self, urn_list: list[str]) -> int:
@ -1032,15 +1043,7 @@ class MIoTSpecParser:
or property_['description'] or property_['description']
or spec_prop.name) or spec_prop.name)
if 'value-range' in property_: if 'value-range' in property_:
spec_prop.value_range = { spec_prop.value_range = property_['value-range']
'min': property_['value-range'][0],
'max': property_['value-range'][1],
'step': property_['value-range'][2]
}
spec_prop.precision = len(str(
property_['value-range'][2]).split(
'.')[1].rstrip('0')) if '.' in str(
property_['value-range'][2]) else 0
elif 'value-list' in property_: elif 'value-list' in property_:
v_list: list[dict] = property_['value-list'] v_list: list[dict] = property_['value-list']
for index, v in enumerate(v_list): for index, v in enumerate(v_list):

View File

@ -92,9 +92,9 @@ class Number(MIoTPropertyEntity, NumberEntity):
self._attr_icon = self.spec.icon self._attr_icon = self.spec.icon
# Set value range # Set value range
if self._value_range: if self._value_range:
self._attr_native_min_value = self._value_range['min'] self._attr_native_min_value = self._value_range.min_
self._attr_native_max_value = self._value_range['max'] self._attr_native_max_value = self._value_range.max_
self._attr_native_step = self._value_range['step'] self._attr_native_step = self._value_range.step
@property @property
def native_value(self) -> Optional[float]: def native_value(self) -> Optional[float]:

View File

@ -115,8 +115,8 @@ class Sensor(MIoTPropertyEntity, SensorEntity):
"""Return the current value of the sensor.""" """Return the current value of the sensor."""
if self._value_range and isinstance(self._value, (int, float)): if self._value_range and isinstance(self._value, (int, float)):
if ( if (
self._value < self._value_range['min'] self._value < self._value_range.min_
or self._value > self._value_range['max'] or self._value > self._value_range.max_
): ):
_LOGGER.info( _LOGGER.info(
'%s, data exception, out of range, %s, %s', '%s, data exception, out of range, %s, %s',

View File

@ -115,7 +115,7 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
self._prop_on = prop self._prop_on = prop
# temperature # temperature
if prop.name == 'temperature': if prop.name == 'temperature':
if isinstance(prop.value_range, dict): if prop.value_range:
if ( if (
self._attr_temperature_unit is None self._attr_temperature_unit is None
and prop.external_unit and prop.external_unit
@ -128,9 +128,14 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
self.entity_id) self.entity_id)
# target-temperature # target-temperature
if prop.name == 'target-temperature': if prop.name == 'target-temperature':
self._attr_min_temp = prop.value_range['min'] if not prop.value_range:
self._attr_max_temp = prop.value_range['max'] _LOGGER.error(
self._attr_precision = prop.value_range['step'] 'invalid target-temperature value_range format, %s',
self.entity_id)
continue
self._attr_min_temp = prop.value_range.min_
self._attr_max_temp = prop.value_range.max_
self._attr_precision = prop.value_range.step
if self._attr_temperature_unit is None and prop.external_unit: if self._attr_temperature_unit is None and prop.external_unit:
self._attr_temperature_unit = prop.external_unit self._attr_temperature_unit = prop.external_unit
self._attr_supported_features |= ( self._attr_supported_features |= (