mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2025-04-02 07:45:31 +08:00
Merge branch 'XiaoMi:main' into main
This commit is contained in:
commit
ade18deb7e
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,5 +1,18 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.2.1
|
||||
### Added
|
||||
- Add the preset mode for the thermostat. [#833](https://github.com/XiaoMi/ha_xiaomi_home/pull/833)
|
||||
|
||||
### Changed
|
||||
- Change paho-mqtt version to adapt Home Assistant 2025.03. [#839](https://github.com/XiaoMi/ha_xiaomi_home/pull/839)
|
||||
- Revert to use multi_lang.json. [#834](https://github.com/XiaoMi/ha_xiaomi_home/pull/834)
|
||||
|
||||
### Fixed
|
||||
- Fix the opening and the closing status of linp.wopener.wd1lb. [#826](https://github.com/XiaoMi/ha_xiaomi_home/pull/826)
|
||||
- Fix the format type of the wind-reverse property. [#810](https://github.com/XiaoMi/ha_xiaomi_home/pull/810)
|
||||
- Fix the fan-level property without value-list but with value-range. [#808](https://github.com/XiaoMi/ha_xiaomi_home/pull/808)
|
||||
|
||||
## v0.2.0
|
||||
This version has modified some default units of sensors. After updating, it may cause Home Assistant to pop up some compatibility warnings. You can re-add the integration to resolve it.
|
||||
|
||||
|
@ -189,7 +189,7 @@ class FeaturePresetMode(MIoTServiceEntity, ClimateEntity):
|
||||
for prop in self.entity_data.props:
|
||||
if prop.name == prop_name and prop.service.name == service_name:
|
||||
if not prop.value_list:
|
||||
_LOGGER.error('invalid %s %s value_list, %s',service_name,
|
||||
_LOGGER.error('invalid %s %s value_list, %s', service_name,
|
||||
prop_name, self.entity_id)
|
||||
continue
|
||||
self._mode_map = prop.value_list.to_map()
|
||||
@ -229,7 +229,9 @@ class FeatureFanMode(MIoTServiceEntity, ClimateEntity):
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'fan-level' and prop.service.name == 'fan-control':
|
||||
if (prop.name == 'fan-level' and
|
||||
(prop.service.name == 'fan-control' or
|
||||
prop.service.name == 'thermostat')):
|
||||
if not prop.value_list:
|
||||
_LOGGER.error('invalid fan-level value_list, %s',
|
||||
self.entity_id)
|
||||
@ -665,78 +667,31 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
|
||||
|
||||
|
||||
class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
||||
FeatureHumidity, FeatureFanMode):
|
||||
FeatureHumidity, FeatureFanMode, FeaturePresetMode):
|
||||
"""Thermostat"""
|
||||
_prop_mode: Optional[MIoTSpecProperty]
|
||||
_hvac_mode_map: Optional[dict[int, HVACMode]]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the thermostat."""
|
||||
self._prop_mode = None
|
||||
self._hvac_mode_map = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
|
||||
self._attr_icon = 'mdi:thermostat'
|
||||
# hvac modes
|
||||
self._attr_hvac_modes = None
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'mode':
|
||||
if not prop.value_list:
|
||||
_LOGGER.error('invalid mode value_list, %s', self.entity_id)
|
||||
continue
|
||||
self._hvac_mode_map = {}
|
||||
for item in prop.value_list.items:
|
||||
if item.name in {'off', 'idle'}:
|
||||
self._hvac_mode_map[item.value] = HVACMode.OFF
|
||||
elif item.name in {'auto'}:
|
||||
self._hvac_mode_map[item.value] = HVACMode.AUTO
|
||||
elif item.name in {'cool'}:
|
||||
self._hvac_mode_map[item.value] = HVACMode.COOL
|
||||
elif item.name in {'heat'}:
|
||||
self._hvac_mode_map[item.value] = HVACMode.HEAT
|
||||
elif item.name in {'dry'}:
|
||||
self._hvac_mode_map[item.value] = HVACMode.DRY
|
||||
elif item.name in {'fan'}:
|
||||
self._hvac_mode_map[item.value] = HVACMode.FAN_ONLY
|
||||
self._attr_hvac_modes = list(self._hvac_mode_map.values())
|
||||
self._prop_mode = prop
|
||||
|
||||
if self._attr_hvac_modes is None:
|
||||
self._attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO]
|
||||
elif HVACMode.OFF not in self._attr_hvac_modes:
|
||||
self._attr_hvac_modes.insert(0, HVACMode.OFF)
|
||||
self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF]
|
||||
# preset modes
|
||||
self._init_preset_modes('thermostat', 'mode')
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set the target hvac mode."""
|
||||
# set the device off
|
||||
if hvac_mode == HVACMode.OFF:
|
||||
if not await self.set_property_async(prop=self._prop_on,
|
||||
value=False):
|
||||
raise RuntimeError(f'set climate prop.on failed, {hvac_mode}, '
|
||||
f'{self.entity_id}')
|
||||
return
|
||||
# set the device on
|
||||
elif self.get_prop_value(prop=self._prop_on) is False:
|
||||
await self.set_property_async(prop=self._prop_on, value=True)
|
||||
# set mode
|
||||
if self._prop_mode is None:
|
||||
return
|
||||
mode_value = self.get_map_key(map_=self._hvac_mode_map, value=hvac_mode)
|
||||
if mode_value is None or not await self.set_property_async(
|
||||
prop=self._prop_mode, value=mode_value):
|
||||
raise RuntimeError(
|
||||
f'set climate prop.mode failed, {hvac_mode}, {self.entity_id}')
|
||||
await self.set_property_async(
|
||||
prop=self._prop_on,
|
||||
value=False if hvac_mode == HVACMode.OFF else True)
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> Optional[HVACMode]:
|
||||
"""The current hvac mode."""
|
||||
if self.get_prop_value(prop=self._prop_on) is False:
|
||||
return HVACMode.OFF
|
||||
return (self.get_map_value(map_=self._hvac_mode_map,
|
||||
key=self.get_prop_value(
|
||||
prop=self._prop_mode))
|
||||
if self._prop_mode else None)
|
||||
return (HVACMode.AUTO if self.get_prop_value(
|
||||
prop=self._prop_on) else HVACMode.OFF)
|
||||
|
||||
|
||||
class ElectricBlanket(FeatureOnOff, FeatureTargetTemperature,
|
||||
|
@ -47,7 +47,7 @@ Cover entities for Xiaomi Home.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
@ -101,7 +101,11 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
_prop_status_closed: Optional[list[int]]
|
||||
_prop_current_position: Optional[MIoTSpecProperty]
|
||||
_prop_target_position: Optional[MIoTSpecProperty]
|
||||
_prop_position_value_min: Optional[int]
|
||||
_prop_position_value_max: Optional[int]
|
||||
_prop_position_value_range: Optional[int]
|
||||
_prop_pos_closing: bool
|
||||
_prop_pos_opening: bool
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
@ -122,7 +126,11 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
self._prop_status_closed = []
|
||||
self._prop_current_position = None
|
||||
self._prop_target_position = None
|
||||
self._prop_position_value_min = None
|
||||
self._prop_position_value_max = None
|
||||
self._prop_position_value_range = None
|
||||
self._prop_pos_closing = False
|
||||
self._prop_pos_opening = False
|
||||
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
@ -166,6 +174,8 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
'invalid current-position value_range format, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._prop_position_value_min = prop.value_range.min_
|
||||
self._prop_position_value_max = prop.value_range.max_
|
||||
self._prop_position_value_range = (prop.value_range.max_ -
|
||||
prop.value_range.min_)
|
||||
self._prop_current_position = prop
|
||||
@ -175,23 +185,52 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
'invalid target-position value_range format, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._prop_position_value_min = prop.value_range.min_
|
||||
self._prop_position_value_max = prop.value_range.max_
|
||||
self._prop_position_value_range = (prop.value_range.max_ -
|
||||
prop.value_range.min_)
|
||||
self._attr_supported_features |= CoverEntityFeature.SET_POSITION
|
||||
self._prop_target_position = prop
|
||||
# For the device that has the current position property but no status
|
||||
# property, the current position property will be used to determine the
|
||||
# opening and the closing status.
|
||||
if (self._prop_status is None) and (self._prop_current_position
|
||||
is not None):
|
||||
self.sub_prop_changed(self._prop_current_position,
|
||||
self._position_changed_handler)
|
||||
|
||||
def _position_changed_handler(self, prop: MIoTSpecProperty,
|
||||
ctx: Any) -> None:
|
||||
self._prop_pos_closing = False
|
||||
self._prop_pos_opening = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_open_cover(self, **kwargs) -> None:
|
||||
"""Open the cover."""
|
||||
current = None if (self._prop_current_position
|
||||
is None) else self.get_prop_value(
|
||||
prop=self._prop_current_position)
|
||||
if (current is not None) and (current < self._prop_position_value_max):
|
||||
self._prop_pos_opening = True
|
||||
self._prop_pos_closing = False
|
||||
await self.set_property_async(self._prop_motor_control,
|
||||
self._prop_motor_value_open)
|
||||
|
||||
async def async_close_cover(self, **kwargs) -> None:
|
||||
"""Close the cover."""
|
||||
current = None if (self._prop_current_position
|
||||
is None) else self.get_prop_value(
|
||||
prop=self._prop_current_position)
|
||||
if (current is not None) and (current > self._prop_position_value_min):
|
||||
self._prop_pos_opening = False
|
||||
self._prop_pos_closing = True
|
||||
await self.set_property_async(self._prop_motor_control,
|
||||
self._prop_motor_value_close)
|
||||
|
||||
async def async_stop_cover(self, **kwargs) -> None:
|
||||
"""Stop the cover."""
|
||||
self._prop_pos_opening = False
|
||||
self._prop_pos_closing = False
|
||||
await self.set_property_async(self._prop_motor_control,
|
||||
self._prop_motor_value_pause)
|
||||
|
||||
@ -200,6 +239,10 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
pos = kwargs.get(ATTR_POSITION, None)
|
||||
if pos is None:
|
||||
return None
|
||||
current = self.current_cover_position
|
||||
if current is not None:
|
||||
self._prop_pos_opening = pos > current
|
||||
self._prop_pos_closing = pos < current
|
||||
pos = round(pos * self._prop_position_value_range / 100)
|
||||
await self.set_property_async(prop=self._prop_target_position,
|
||||
value=pos)
|
||||
@ -214,9 +257,11 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
# Assume that the current position is the same as the target
|
||||
# position when the current position is not defined in the device's
|
||||
# MIoT-Spec-V2.
|
||||
return None if (self._prop_target_position
|
||||
is None) else self.get_prop_value(
|
||||
prop=self._prop_target_position)
|
||||
if self._prop_target_position is None:
|
||||
return None
|
||||
self._prop_pos_opening = False
|
||||
self._prop_pos_closing = False
|
||||
return self.get_prop_value(prop=self._prop_target_position)
|
||||
pos = self.get_prop_value(prop=self._prop_current_position)
|
||||
return None if pos is None else round(pos * 100 /
|
||||
self._prop_position_value_range)
|
||||
@ -227,14 +272,9 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
if self._prop_status and self._prop_status_opening:
|
||||
return (self.get_prop_value(prop=self._prop_status)
|
||||
in self._prop_status_opening)
|
||||
# The status is prior to the numerical relationship of the current
|
||||
# position and the target position when determining whether the cover
|
||||
# The status has higher priority when determining whether the cover
|
||||
# is opening.
|
||||
if (self._prop_target_position and
|
||||
self.current_cover_position is not None):
|
||||
return (self.current_cover_position
|
||||
< self.get_prop_value(prop=self._prop_target_position))
|
||||
return None
|
||||
return self._prop_pos_opening
|
||||
|
||||
@property
|
||||
def is_closing(self) -> Optional[bool]:
|
||||
@ -242,14 +282,9 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
if self._prop_status and self._prop_status_closing:
|
||||
return (self.get_prop_value(prop=self._prop_status)
|
||||
in self._prop_status_closing)
|
||||
# The status is prior to the numerical relationship of the current
|
||||
# position and the target position when determining whether the cover
|
||||
# The status has higher priority when determining whether the cover
|
||||
# is closing.
|
||||
if (self._prop_target_position and
|
||||
self.current_cover_position is not None):
|
||||
return (self.current_cover_position
|
||||
> self.get_prop_value(prop=self._prop_target_position))
|
||||
return None
|
||||
return self._prop_pos_closing
|
||||
|
||||
@property
|
||||
def is_closed(self) -> Optional[bool]:
|
||||
|
@ -46,6 +46,7 @@ off Xiaomi or its affiliates' products.
|
||||
Event entities for Xiaomi Home.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -57,6 +58,8 @@ from .miot.miot_spec import MIoTSpecEvent
|
||||
from .miot.miot_device import MIoTDevice, MIoTEventEntity
|
||||
from .miot.const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@ -89,4 +92,5 @@ class Event(MIoTEventEntity, EventEntity):
|
||||
self, name: str, arguments: dict[str, Any] | None = None
|
||||
) -> None:
|
||||
"""An event is occurred."""
|
||||
_LOGGER.debug('%s, attributes: %s', name, str(arguments))
|
||||
self._trigger_event(event_type=name, event_attributes=arguments)
|
||||
|
@ -172,7 +172,7 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
self._attr_supported_features |= FanEntityFeature.OSCILLATE
|
||||
self._prop_horizontal_swing = prop
|
||||
elif prop.name == 'wind-reverse':
|
||||
if prop.format_ == 'bool':
|
||||
if prop.format_ == bool:
|
||||
self._prop_wind_reverse_forward = False
|
||||
self._prop_wind_reverse_reverse = True
|
||||
elif prop.value_list:
|
||||
@ -186,7 +186,7 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
or self._prop_wind_reverse_reverse is None
|
||||
):
|
||||
# NOTICE: Value may be 0 or False
|
||||
_LOGGER.info(
|
||||
_LOGGER.error(
|
||||
'invalid wind-reverse, %s', self.entity_id)
|
||||
continue
|
||||
self._attr_supported_features |= FanEntityFeature.DIRECTION
|
||||
|
@ -179,7 +179,7 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
) / prop.value_range.step)
|
||||
> self._VALUE_RANGE_MODE_COUNT_MAX
|
||||
):
|
||||
_LOGGER.info(
|
||||
_LOGGER.error(
|
||||
'too many mode values, %s, %s, %s',
|
||||
self.entity_id, prop.name, prop.value_range)
|
||||
else:
|
||||
|
@ -20,13 +20,13 @@
|
||||
],
|
||||
"requirements": [
|
||||
"construct>=2.10.56",
|
||||
"paho-mqtt<2.0.0",
|
||||
"paho-mqtt",
|
||||
"numpy",
|
||||
"cryptography",
|
||||
"psutil"
|
||||
],
|
||||
"version": "v0.2.0",
|
||||
"version": "v0.2.1",
|
||||
"zeroconf": [
|
||||
"_miot-central._tcp.local."
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -549,6 +549,10 @@ class MIoTDevice:
|
||||
# Optional actions
|
||||
# Optional events
|
||||
miot_service.platform = platform
|
||||
# entity_category
|
||||
if entity_category := SPEC_SERVICE_TRANS_MAP[service_name].get(
|
||||
'entity_category', None):
|
||||
miot_service.entity_category = entity_category
|
||||
return entity_data
|
||||
|
||||
def parse_miot_property_entity(self, miot_prop: MIoTSpecProperty) -> bool:
|
||||
@ -587,13 +591,8 @@ class MIoTDevice:
|
||||
# Priority: spec_modify.unit > unit_convert > specv2entity.unit
|
||||
miot_prop.external_unit = SPEC_PROP_TRANS_MAP['properties'][
|
||||
prop_name]['unit_of_measurement']
|
||||
if (
|
||||
not miot_prop.icon
|
||||
and 'icon' in SPEC_PROP_TRANS_MAP['properties'][prop_name]
|
||||
):
|
||||
# Priority: spec_modify.icon > icon_convert > specv2entity.icon
|
||||
miot_prop.icon = SPEC_PROP_TRANS_MAP['properties'][prop_name][
|
||||
'icon']
|
||||
# Priority: default.icon when device_class is set > spec_modify.icon
|
||||
# > icon_convert
|
||||
miot_prop.platform = platform
|
||||
return True
|
||||
|
||||
@ -899,6 +898,7 @@ class MIoTServiceEntity(Entity):
|
||||
self._attr_name = (
|
||||
f'{"* "if self.entity_data.spec.proprietary else " "}'
|
||||
f'{self.entity_data.spec.description_trans}')
|
||||
self._attr_entity_category = entity_data.spec.entity_category
|
||||
# Set entity attr
|
||||
self._attr_unique_id = self.entity_id
|
||||
self._attr_should_poll = False
|
||||
|
@ -1215,9 +1215,10 @@ class MipsLocalClient(_MipsClient):
|
||||
or 'eiid' not in msg
|
||||
# or 'arguments' not in msg
|
||||
):
|
||||
# self.log_error(f'on_event_msg, recv unknown msg, {payload}')
|
||||
self.log_error('unknown event msg, %s', payload)
|
||||
return
|
||||
if 'arguments' not in msg:
|
||||
self.log_info('wrong event msg, %s', payload)
|
||||
msg['arguments'] = []
|
||||
if handler:
|
||||
self.log_debug('local, on event_occurred, %s', payload)
|
||||
|
@ -56,7 +56,7 @@ from slugify import slugify
|
||||
|
||||
# pylint: disable=relative-beyond-top-level
|
||||
from .const import DEFAULT_INTEGRATION_LANGUAGE, SPEC_STD_LIB_EFFECTIVE_TIME
|
||||
from .common import MIoTHttp, load_yaml_file
|
||||
from .common import MIoTHttp, load_yaml_file, load_json_file
|
||||
from .miot_error import MIoTSpecError
|
||||
from .miot_storage import MIoTStorage
|
||||
|
||||
@ -471,7 +471,7 @@ class _MIoTSpecBase:
|
||||
iid: int
|
||||
type_: str
|
||||
description: str
|
||||
description_trans: str
|
||||
description_trans: Optional[str]
|
||||
proprietary: bool
|
||||
need_filter: bool
|
||||
name: str
|
||||
@ -482,6 +482,7 @@ class _MIoTSpecBase:
|
||||
device_class: Any
|
||||
state_class: Any
|
||||
external_unit: Any
|
||||
entity_category: Optional[str]
|
||||
|
||||
spec_id: int
|
||||
|
||||
@ -500,6 +501,7 @@ class _MIoTSpecBase:
|
||||
self.device_class = None
|
||||
self.state_class = None
|
||||
self.external_unit = None
|
||||
self.entity_category = None
|
||||
|
||||
self.spec_id = hash(f'{self.type_}.{self.iid}')
|
||||
|
||||
@ -843,6 +845,7 @@ class _MIoTSpecMultiLang:
|
||||
"""MIoT SPEC multi lang class."""
|
||||
# pylint: disable=broad-exception-caught
|
||||
_DOMAIN: str = 'miot_specs_multi_lang'
|
||||
_MULTI_LANG_FILE = 'specs/multi_lang.json'
|
||||
_lang: str
|
||||
_storage: MIoTStorage
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
@ -898,6 +901,25 @@ class _MIoTSpecMultiLang:
|
||||
except Exception as err:
|
||||
trans_local = {}
|
||||
_LOGGER.info('get multi lang from local failed, %s, %s', urn, err)
|
||||
# Revert: load multi_lang.json
|
||||
try:
|
||||
trans_local_json = await self._main_loop.run_in_executor(
|
||||
None, load_json_file,
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
self._MULTI_LANG_FILE))
|
||||
urn_strs: list[str] = urn.split(':')
|
||||
urn_key: str = ':'.join(urn_strs[:6])
|
||||
if (
|
||||
isinstance(trans_local_json, dict)
|
||||
and urn_key in trans_local_json
|
||||
and self._lang in trans_local_json[urn_key]
|
||||
):
|
||||
trans_cache.update(trans_local_json[urn_key][self._lang])
|
||||
trans_local = trans_local_json[urn_key]
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
_LOGGER.error('multi lang, load json file error, %s', err)
|
||||
# Revert end
|
||||
# Default language
|
||||
if not trans_cache:
|
||||
if trans_cloud and DEFAULT_INTEGRATION_LANGUAGE in trans_cloud:
|
||||
@ -1189,6 +1211,9 @@ class _SpecModify:
|
||||
if isinstance(self._selected, str):
|
||||
return await self.set_spec_async(urn=self._selected)
|
||||
|
||||
def get_prop_name(self, siid: int, piid: int) -> Optional[str]:
|
||||
return self.__get_prop_item(siid=siid, piid=piid, key='name')
|
||||
|
||||
def get_prop_unit(self, siid: int, piid: int) -> Optional[str]:
|
||||
return self.__get_prop_item(siid=siid, piid=piid, key='unit')
|
||||
|
||||
@ -1211,6 +1236,13 @@ class _SpecModify:
|
||||
return None
|
||||
return value_range
|
||||
|
||||
def get_prop_value_list(self, siid: int, piid: int) -> Optional[list]:
|
||||
value_list = self.__get_prop_item(siid=siid, piid=piid,
|
||||
key='value-list')
|
||||
if not isinstance(value_list, list):
|
||||
return None
|
||||
return value_list
|
||||
|
||||
def __get_prop_item(self, siid: int, piid: int, key: str) -> Optional[str]:
|
||||
if not self._selected:
|
||||
return None
|
||||
@ -1450,10 +1482,12 @@ class MIoTSpecParser:
|
||||
key=':'.join(p_type_strs[:5]))
|
||||
or property_['description']
|
||||
or spec_prop.name)
|
||||
if 'value-range' in property_:
|
||||
spec_prop.value_range = property_['value-range']
|
||||
elif 'value-list' in property_:
|
||||
v_list: list[dict] = property_['value-list']
|
||||
# Modify value-list before translation
|
||||
v_list: list[dict] = self._spec_modify.get_prop_value_list(
|
||||
siid=service['iid'], piid=property_['iid'])
|
||||
if (v_list is None) and ('value-list' in property_):
|
||||
v_list = property_['value-list']
|
||||
if v_list is not None:
|
||||
for index, v in enumerate(v_list):
|
||||
if v['description'].strip() == '':
|
||||
v['description'] = f'v_{v["value"]}'
|
||||
@ -1467,6 +1501,8 @@ class MIoTSpecParser:
|
||||
f'{v["description"]}')
|
||||
or v['name'])
|
||||
spec_prop.value_list = MIoTSpecValueList.from_spec(v_list)
|
||||
if 'value-range' in property_:
|
||||
spec_prop.value_range = property_['value-range']
|
||||
elif property_['format'] == 'bool':
|
||||
v_tag = ':'.join(p_type_strs[:5])
|
||||
v_descriptions = (
|
||||
@ -1491,6 +1527,10 @@ class MIoTSpecParser:
|
||||
siid=service['iid'], piid=property_['iid'])
|
||||
if custom_range:
|
||||
spec_prop.value_range = custom_range
|
||||
custom_name = self._spec_modify.get_prop_name(
|
||||
siid=service['iid'], piid=property_['iid'])
|
||||
if custom_name:
|
||||
spec_prop.name = custom_name
|
||||
# Parse service event
|
||||
for event in service.get('events', []):
|
||||
if (
|
||||
|
172
custom_components/xiaomi_home/miot/specs/multi_lang.json
Normal file
172
custom_components/xiaomi_home/miot/specs/multi_lang.json
Normal file
@ -0,0 +1,172 @@
|
||||
{
|
||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
||||
"de": {
|
||||
"service:001": "Geräteinformationen",
|
||||
"service:001:property:003": "Geräte-ID",
|
||||
"service:001:property:005": "Seriennummer (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Netzwerk geändert",
|
||||
"service:002:event:002": "Netzwerk geändert",
|
||||
"service:002:property:001": "Zugriffsmethode",
|
||||
"service:002:property:001:valuelist:000": "Kabelgebunden",
|
||||
"service:002:property:001:valuelist:001": "5G Drahtlos",
|
||||
"service:002:property:001:valuelist:002": "2.4G Drahtlos",
|
||||
"service:002:property:002": "IP-Adresse",
|
||||
"service:002:property:003": "WiFi-Netzwerkname",
|
||||
"service:002:property:004": "Aktuelle Zeit",
|
||||
"service:002:property:005": "DHCP-Server-MAC-Adresse",
|
||||
"service:003": "Anzeigelampe",
|
||||
"service:003:property:001": "Schalter",
|
||||
"service:004": "Virtueller Dienst",
|
||||
"service:004:action:001": "Virtuelles Ereignis erzeugen",
|
||||
"service:004:event:001": "Virtuelles Ereignis aufgetreten",
|
||||
"service:004:property:001": "Ereignisname"
|
||||
},
|
||||
"en": {
|
||||
"service:001": "Device Information",
|
||||
"service:001:property:003": "Device ID",
|
||||
"service:001:property:005": "Serial Number (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Network Changed",
|
||||
"service:002:event:002": "Network Changed",
|
||||
"service:002:property:001": "Access Method",
|
||||
"service:002:property:001:valuelist:000": "Wired",
|
||||
"service:002:property:001:valuelist:001": "5G Wireless",
|
||||
"service:002:property:001:valuelist:002": "2.4G Wireless",
|
||||
"service:002:property:002": "IP Address",
|
||||
"service:002:property:003": "WiFi Network Name",
|
||||
"service:002:property:004": "Current Time",
|
||||
"service:002:property:005": "DHCP Server MAC Address",
|
||||
"service:003": "Indicator Light",
|
||||
"service:003:property:001": "Switch",
|
||||
"service:004": "Virtual Service",
|
||||
"service:004:action:001": "Generate Virtual Event",
|
||||
"service:004:event:001": "Virtual Event Occurred",
|
||||
"service:004:property:001": "Event Name"
|
||||
},
|
||||
"es": {
|
||||
"service:001": "Información del dispositivo",
|
||||
"service:001:property:003": "ID del dispositivo",
|
||||
"service:001:property:005": "Número de serie (SN)",
|
||||
"service:002": "Puerta de enlace",
|
||||
"service:002:event:001": "Cambio de red",
|
||||
"service:002:event:002": "Cambio de red",
|
||||
"service:002:property:001": "Método de acceso",
|
||||
"service:002:property:001:valuelist:000": "Cableado",
|
||||
"service:002:property:001:valuelist:001": "5G inalámbrico",
|
||||
"service:002:property:001:valuelist:002": "2.4G inalámbrico",
|
||||
"service:002:property:002": "Dirección IP",
|
||||
"service:002:property:003": "Nombre de red WiFi",
|
||||
"service:002:property:004": "Hora actual",
|
||||
"service:002:property:005": "Dirección MAC del servidor DHCP",
|
||||
"service:003": "Luz indicadora",
|
||||
"service:003:property:001": "Interruptor",
|
||||
"service:004": "Servicio virtual",
|
||||
"service:004:action:001": "Generar evento virtual",
|
||||
"service:004:event:001": "Ocurrió un evento virtual",
|
||||
"service:004:property:001": "Nombre del evento"
|
||||
},
|
||||
"fr": {
|
||||
"service:001": "Informations sur l'appareil",
|
||||
"service:001:property:003": "ID de l'appareil",
|
||||
"service:001:property:005": "Numéro de série (SN)",
|
||||
"service:002": "Passerelle",
|
||||
"service:002:event:001": "Changement de réseau",
|
||||
"service:002:event:002": "Changement de réseau",
|
||||
"service:002:property:001": "Méthode d'accès",
|
||||
"service:002:property:001:valuelist:000": "Câblé",
|
||||
"service:002:property:001:valuelist:001": "Sans fil 5G",
|
||||
"service:002:property:001:valuelist:002": "Sans fil 2.4G",
|
||||
"service:002:property:002": "Adresse IP",
|
||||
"service:002:property:003": "Nom du réseau WiFi",
|
||||
"service:002:property:004": "Heure actuelle",
|
||||
"service:002:property:005": "Adresse MAC du serveur DHCP",
|
||||
"service:003": "Voyant lumineux",
|
||||
"service:003:property:001": "Interrupteur",
|
||||
"service:004": "Service virtuel",
|
||||
"service:004:action:001": "Générer un événement virtuel",
|
||||
"service:004:event:001": "Événement virtuel survenu",
|
||||
"service:004:property:001": "Nom de l'événement"
|
||||
},
|
||||
"ja": {
|
||||
"service:001": "デバイス情報",
|
||||
"service:001:property:003": "デバイスID",
|
||||
"service:001:property:005": "シリアル番号 (SN)",
|
||||
"service:002": "ゲートウェイ",
|
||||
"service:002:event:001": "ネットワークが変更されました",
|
||||
"service:002:event:002": "ネットワークが変更されました",
|
||||
"service:002:property:001": "アクセス方法",
|
||||
"service:002:property:001:valuelist:000": "有線",
|
||||
"service:002:property:001:valuelist:001": "5G ワイヤレス",
|
||||
"service:002:property:001:valuelist:002": "2.4G ワイヤレス",
|
||||
"service:002:property:002": "IPアドレス",
|
||||
"service:002:property:003": "WiFiネットワーク名",
|
||||
"service:002:property:004": "現在の時間",
|
||||
"service:002:property:005": "DHCPサーバーMACアドレス",
|
||||
"service:003": "インジケータライト",
|
||||
"service:003:property:001": "スイッチ",
|
||||
"service:004": "バーチャルサービス",
|
||||
"service:004:action:001": "バーチャルイベントを生成",
|
||||
"service:004:event:001": "バーチャルイベントが発生しました",
|
||||
"service:004:property:001": "イベント名"
|
||||
},
|
||||
"ru": {
|
||||
"service:001": "Информация об устройстве",
|
||||
"service:001:property:003": "ID устройства",
|
||||
"service:001:property:005": "Серийный номер (SN)",
|
||||
"service:002": "Шлюз",
|
||||
"service:002:event:001": "Сеть изменена",
|
||||
"service:002:event:002": "Сеть изменена",
|
||||
"service:002:property:001": "Метод доступа",
|
||||
"service:002:property:001:valuelist:000": "Проводной",
|
||||
"service:002:property:001:valuelist:001": "5G Беспроводной",
|
||||
"service:002:property:001:valuelist:002": "2.4G Беспроводной",
|
||||
"service:002:property:002": "IP Адрес",
|
||||
"service:002:property:003": "Название WiFi сети",
|
||||
"service:002:property:004": "Текущее время",
|
||||
"service:002:property:005": "MAC адрес DHCP сервера",
|
||||
"service:003": "Световой индикатор",
|
||||
"service:003:property:001": "Переключатель",
|
||||
"service:004": "Виртуальная служба",
|
||||
"service:004:action:001": "Создать виртуальное событие",
|
||||
"service:004:event:001": "Произошло виртуальное событие",
|
||||
"service:004:property:001": "Название события"
|
||||
},
|
||||
"zh-Hant": {
|
||||
"service:001": "設備信息",
|
||||
"service:001:property:003": "設備ID",
|
||||
"service:001:property:005": "序號 (SN)",
|
||||
"service:002": "網關",
|
||||
"service:002:event:001": "網路發生變化",
|
||||
"service:002:event:002": "網路發生變化",
|
||||
"service:002:property:001": "接入方式",
|
||||
"service:002:property:001:valuelist:000": "有線",
|
||||
"service:002:property:001:valuelist:001": "5G 無線",
|
||||
"service:002:property:001:valuelist:002": "2.4G 無線",
|
||||
"service:002:property:002": "IP地址",
|
||||
"service:002:property:003": "WiFi網路名稱",
|
||||
"service:002:property:004": "當前時間",
|
||||
"service:002:property:005": "DHCP伺服器MAC地址",
|
||||
"service:003": "指示燈",
|
||||
"service:003:property:001": "開關",
|
||||
"service:004": "虛擬服務",
|
||||
"service:004:action:001": "產生虛擬事件",
|
||||
"service:004:event:001": "虛擬事件發生",
|
||||
"service:004:property:001": "事件名稱"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:switch:0000A003:lumi-acn040": {
|
||||
"en": {
|
||||
"service:011": "Right Button On and Off",
|
||||
"service:011:property:001": "Right Button On and Off",
|
||||
"service:015:action:001": "Left Button Identify",
|
||||
"service:016:action:001": "Middle Button Identify",
|
||||
"service:017:action:001": "Right Button Identify"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:015:action:001": "左键确认",
|
||||
"service:016:action:001": "中键确认",
|
||||
"service:017:action:001": "右键确认"
|
||||
}
|
||||
}
|
||||
}
|
@ -49,3 +49,22 @@ urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1:
|
||||
- 1
|
||||
- 1
|
||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:2: urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1
|
||||
urn:miot-spec-v2:device:bath-heater:0000A028:opple-acmoto:1:
|
||||
prop.5.2:
|
||||
value-list:
|
||||
- value: 1
|
||||
description: low
|
||||
- value: 128
|
||||
description: medium
|
||||
- value: 255
|
||||
description: high
|
||||
urn:miot-spec-v2:device:bath-heater:0000A028:mike-2:1:
|
||||
prop.3.1:
|
||||
name: mode-a
|
||||
prop.3.11:
|
||||
name: mode-b
|
||||
prop.3.12:
|
||||
name: mode-c
|
||||
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p51:1:
|
||||
prop.2.2:
|
||||
name: fan-level-a
|
||||
|
@ -51,6 +51,7 @@ from homeassistant.components.event import EventDeviceClass
|
||||
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
EntityCategory,
|
||||
LIGHT_LUX,
|
||||
UnitOfEnergy,
|
||||
UnitOfPower,
|
||||
@ -330,7 +331,8 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
'events': set<event instance name: str>,
|
||||
'actions': set<action instance name: str>
|
||||
},
|
||||
'entity': str
|
||||
'entity': str,
|
||||
'entity_category'?: str
|
||||
}
|
||||
}
|
||||
"""
|
||||
@ -348,10 +350,23 @@ SPEC_SERVICE_TRANS_MAP: dict = {
|
||||
},
|
||||
'entity': 'light'
|
||||
},
|
||||
'indicator-light': 'light',
|
||||
'ambient-light': 'light',
|
||||
'night-light': 'light',
|
||||
'white-light': 'light',
|
||||
'indicator-light': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {
|
||||
'mode', 'brightness',
|
||||
}
|
||||
},
|
||||
'entity': 'light',
|
||||
'entity_category': EntityCategory.CONFIG
|
||||
},
|
||||
'fan': {
|
||||
'required': {
|
||||
'properties': {
|
||||
|
@ -88,7 +88,7 @@ class Number(MIoTPropertyEntity, NumberEntity):
|
||||
if self.spec.external_unit:
|
||||
self._attr_native_unit_of_measurement = self.spec.external_unit
|
||||
# Set icon
|
||||
if self.spec.icon:
|
||||
if self.spec.icon and not self.device_class:
|
||||
self._attr_icon = self.spec.icon
|
||||
# Set value range
|
||||
if self._value_range:
|
||||
|
@ -116,7 +116,7 @@ class Sensor(MIoTPropertyEntity, SensorEntity):
|
||||
if spec.state_class:
|
||||
self._attr_state_class = spec.state_class
|
||||
# Set icon
|
||||
if spec.icon:
|
||||
if spec.icon and not self.device_class:
|
||||
self._attr_icon = spec.icon
|
||||
|
||||
@property
|
||||
|
Loading…
x
Reference in New Issue
Block a user