mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2026-01-19 16:59:36 +08:00
Compare commits
5 Commits
9001606584
...
ace8adbf6b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ace8adbf6b | ||
|
|
4809c16aba | ||
|
|
e69448f2eb | ||
|
|
7901607648 | ||
|
|
5adcb7ce00 |
@ -101,7 +101,11 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
|||||||
_prop_status_closed: Optional[list[int]]
|
_prop_status_closed: Optional[list[int]]
|
||||||
_prop_current_position: Optional[MIoTSpecProperty]
|
_prop_current_position: Optional[MIoTSpecProperty]
|
||||||
_prop_target_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_position_value_range: Optional[int]
|
||||||
|
_prop_pos_closing: bool
|
||||||
|
_prop_pos_opening: bool
|
||||||
|
|
||||||
def __init__(self, miot_device: MIoTDevice,
|
def __init__(self, miot_device: MIoTDevice,
|
||||||
entity_data: MIoTEntityData) -> None:
|
entity_data: MIoTEntityData) -> None:
|
||||||
@ -122,7 +126,11 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
|||||||
self._prop_status_closed = []
|
self._prop_status_closed = []
|
||||||
self._prop_current_position = None
|
self._prop_current_position = None
|
||||||
self._prop_target_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_position_value_range = None
|
||||||
|
self._prop_pos_closing = False
|
||||||
|
self._prop_pos_opening = False
|
||||||
|
|
||||||
# properties
|
# properties
|
||||||
for prop in entity_data.props:
|
for prop in entity_data.props:
|
||||||
@ -166,6 +174,8 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
|||||||
'invalid current-position value_range format, %s',
|
'invalid current-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_max = prop.value_range.max_
|
||||||
self._prop_position_value_range = (prop.value_range.max_ -
|
self._prop_position_value_range = (prop.value_range.max_ -
|
||||||
prop.value_range.min_)
|
prop.value_range.min_)
|
||||||
self._prop_current_position = prop
|
self._prop_current_position = prop
|
||||||
@ -175,48 +185,52 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
|||||||
'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_max = prop.value_range.max_
|
||||||
self._prop_position_value_range = (prop.value_range.max_ -
|
self._prop_position_value_range = (prop.value_range.max_ -
|
||||||
prop.value_range.min_)
|
prop.value_range.min_)
|
||||||
self._attr_supported_features |= CoverEntityFeature.SET_POSITION
|
self._attr_supported_features |= CoverEntityFeature.SET_POSITION
|
||||||
self._prop_target_position = prop
|
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)
|
||||||
|
|
||||||
if (
|
def _position_changed_handler(self, prop: MIoTSpecProperty,
|
||||||
self._prop_status is None
|
ctx: Any) -> None:
|
||||||
and self._prop_current_position is not None
|
self._prop_pos_closing = False
|
||||||
):
|
self._prop_pos_opening = False
|
||||||
self.sub_prop_changed(prop=self._prop_current_position,
|
self.async_write_ha_state()
|
||||||
handler=self.__current_position_changed)
|
|
||||||
|
|
||||||
def __current_position_changed(
|
|
||||||
self, prop: MIoTSpecProperty, ctx: Any
|
|
||||||
) -> None:
|
|
||||||
if self._attr_is_opening or self._attr_is_closing:
|
|
||||||
self._attr_is_opening = False
|
|
||||||
self._attr_is_closing = False
|
|
||||||
self.async_write_ha_state()
|
|
||||||
|
|
||||||
async def async_open_cover(self, **kwargs) -> None:
|
async def async_open_cover(self, **kwargs) -> None:
|
||||||
"""Open the cover."""
|
"""Open the cover."""
|
||||||
current = self.get_prop_value(prop=self._prop_current_position)
|
current = None if (self._prop_current_position
|
||||||
if current is not None and current < 100:
|
is None) else self.get_prop_value(
|
||||||
self._attr_is_opening = True
|
prop=self._prop_current_position)
|
||||||
self._attr_is_closing = False
|
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,
|
await self.set_property_async(self._prop_motor_control,
|
||||||
self._prop_motor_value_open)
|
self._prop_motor_value_open)
|
||||||
|
|
||||||
async def async_close_cover(self, **kwargs) -> None:
|
async def async_close_cover(self, **kwargs) -> None:
|
||||||
"""Close the cover."""
|
"""Close the cover."""
|
||||||
current = self.get_prop_value(prop=self._prop_current_position)
|
current = None if (self._prop_current_position
|
||||||
if current is not None and current > 0:
|
is None) else self.get_prop_value(
|
||||||
self._attr_is_opening = False
|
prop=self._prop_current_position)
|
||||||
self._attr_is_closing = True
|
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,
|
await self.set_property_async(self._prop_motor_control,
|
||||||
self._prop_motor_value_close)
|
self._prop_motor_value_close)
|
||||||
|
|
||||||
async def async_stop_cover(self, **kwargs) -> None:
|
async def async_stop_cover(self, **kwargs) -> None:
|
||||||
"""Stop the cover."""
|
"""Stop the cover."""
|
||||||
self._attr_is_opening = False
|
self._prop_pos_opening = False
|
||||||
self._attr_is_closing = False
|
self._prop_pos_closing = False
|
||||||
await self.set_property_async(self._prop_motor_control,
|
await self.set_property_async(self._prop_motor_control,
|
||||||
self._prop_motor_value_pause)
|
self._prop_motor_value_pause)
|
||||||
|
|
||||||
@ -225,11 +239,11 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
|||||||
pos = kwargs.get(ATTR_POSITION, None)
|
pos = kwargs.get(ATTR_POSITION, None)
|
||||||
if pos is None:
|
if pos is None:
|
||||||
return None
|
return None
|
||||||
pos = round(pos * self._prop_position_value_range / 100)
|
current = self.current_cover_position
|
||||||
current = self.get_prop_value(prop=self._prop_current_position)
|
|
||||||
if current is not None:
|
if current is not None:
|
||||||
self._attr_is_opening = pos > current
|
self._prop_pos_opening = pos > current
|
||||||
self._attr_is_closing = 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,
|
await self.set_property_async(prop=self._prop_target_position,
|
||||||
value=pos)
|
value=pos)
|
||||||
|
|
||||||
@ -243,9 +257,11 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
|||||||
# Assume that the current position is the same as the target
|
# Assume that the current position is the same as the target
|
||||||
# position when the current position is not defined in the device's
|
# position when the current position is not defined in the device's
|
||||||
# MIoT-Spec-V2.
|
# MIoT-Spec-V2.
|
||||||
return None if (self._prop_target_position
|
if self._prop_target_position is None:
|
||||||
is None) else self.get_prop_value(
|
return None
|
||||||
prop=self._prop_target_position)
|
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)
|
pos = self.get_prop_value(prop=self._prop_current_position)
|
||||||
return None if pos is None else round(pos * 100 /
|
return None if pos is None else round(pos * 100 /
|
||||||
self._prop_position_value_range)
|
self._prop_position_value_range)
|
||||||
@ -256,7 +272,9 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
|||||||
if self._prop_status and self._prop_status_opening:
|
if self._prop_status and self._prop_status_opening:
|
||||||
return (self.get_prop_value(prop=self._prop_status)
|
return (self.get_prop_value(prop=self._prop_status)
|
||||||
in self._prop_status_opening)
|
in self._prop_status_opening)
|
||||||
return self._attr_is_opening
|
# The status has higher priority when determining whether the cover
|
||||||
|
# is opening.
|
||||||
|
return self._prop_pos_opening
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closing(self) -> Optional[bool]:
|
def is_closing(self) -> Optional[bool]:
|
||||||
@ -264,7 +282,9 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
|||||||
if self._prop_status and self._prop_status_closing:
|
if self._prop_status and self._prop_status_closing:
|
||||||
return (self.get_prop_value(prop=self._prop_status)
|
return (self.get_prop_value(prop=self._prop_status)
|
||||||
in self._prop_status_closing)
|
in self._prop_status_closing)
|
||||||
return self._attr_is_closing
|
# The status has higher priority when determining whether the cover
|
||||||
|
# is closing.
|
||||||
|
return self._prop_pos_closing
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closed(self) -> Optional[bool]:
|
def is_closed(self) -> Optional[bool]:
|
||||||
|
|||||||
@ -172,7 +172,7 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
self._attr_supported_features |= FanEntityFeature.OSCILLATE
|
self._attr_supported_features |= FanEntityFeature.OSCILLATE
|
||||||
self._prop_horizontal_swing = prop
|
self._prop_horizontal_swing = prop
|
||||||
elif prop.name == 'wind-reverse':
|
elif prop.name == 'wind-reverse':
|
||||||
if prop.format_ == 'bool':
|
if prop.format_ == bool:
|
||||||
self._prop_wind_reverse_forward = False
|
self._prop_wind_reverse_forward = False
|
||||||
self._prop_wind_reverse_reverse = True
|
self._prop_wind_reverse_reverse = True
|
||||||
elif prop.value_list:
|
elif prop.value_list:
|
||||||
@ -186,7 +186,7 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
or self._prop_wind_reverse_reverse is None
|
or self._prop_wind_reverse_reverse is None
|
||||||
):
|
):
|
||||||
# NOTICE: Value may be 0 or False
|
# NOTICE: Value may be 0 or False
|
||||||
_LOGGER.info(
|
_LOGGER.error(
|
||||||
'invalid wind-reverse, %s', self.entity_id)
|
'invalid wind-reverse, %s', self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._attr_supported_features |= FanEntityFeature.DIRECTION
|
self._attr_supported_features |= FanEntityFeature.DIRECTION
|
||||||
|
|||||||
@ -549,6 +549,10 @@ class MIoTDevice:
|
|||||||
# Optional actions
|
# Optional actions
|
||||||
# Optional events
|
# Optional events
|
||||||
miot_service.platform = platform
|
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
|
return entity_data
|
||||||
|
|
||||||
def parse_miot_property_entity(self, miot_prop: MIoTSpecProperty) -> bool:
|
def parse_miot_property_entity(self, miot_prop: MIoTSpecProperty) -> bool:
|
||||||
@ -899,6 +903,7 @@ class MIoTServiceEntity(Entity):
|
|||||||
self._attr_name = (
|
self._attr_name = (
|
||||||
f'{"* "if self.entity_data.spec.proprietary else " "}'
|
f'{"* "if self.entity_data.spec.proprietary else " "}'
|
||||||
f'{self.entity_data.spec.description_trans}')
|
f'{self.entity_data.spec.description_trans}')
|
||||||
|
self._attr_entity_category = entity_data.spec.entity_category
|
||||||
# Set entity attr
|
# Set entity attr
|
||||||
self._attr_unique_id = self.entity_id
|
self._attr_unique_id = self.entity_id
|
||||||
self._attr_should_poll = False
|
self._attr_should_poll = False
|
||||||
|
|||||||
@ -465,7 +465,7 @@ class _MIoTSpecBase:
|
|||||||
iid: int
|
iid: int
|
||||||
type_: str
|
type_: str
|
||||||
description: str
|
description: str
|
||||||
description_trans: str
|
description_trans: Optional[str]
|
||||||
proprietary: bool
|
proprietary: bool
|
||||||
need_filter: bool
|
need_filter: bool
|
||||||
name: str
|
name: str
|
||||||
@ -476,6 +476,7 @@ class _MIoTSpecBase:
|
|||||||
device_class: Any
|
device_class: Any
|
||||||
state_class: Any
|
state_class: Any
|
||||||
external_unit: Any
|
external_unit: Any
|
||||||
|
entity_category: Optional[str]
|
||||||
|
|
||||||
spec_id: int
|
spec_id: int
|
||||||
|
|
||||||
@ -494,6 +495,7 @@ class _MIoTSpecBase:
|
|||||||
self.device_class = None
|
self.device_class = None
|
||||||
self.state_class = None
|
self.state_class = None
|
||||||
self.external_unit = None
|
self.external_unit = None
|
||||||
|
self.entity_category = None
|
||||||
|
|
||||||
self.spec_id = hash(f'{self.type_}.{self.iid}')
|
self.spec_id = hash(f'{self.type_}.{self.iid}')
|
||||||
|
|
||||||
@ -1205,6 +1207,13 @@ class _SpecModify:
|
|||||||
return None
|
return None
|
||||||
return value_range
|
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]:
|
def __get_prop_item(self, siid: int, piid: int, key: str) -> Optional[str]:
|
||||||
if not self._selected:
|
if not self._selected:
|
||||||
return None
|
return None
|
||||||
@ -1485,6 +1494,10 @@ class MIoTSpecParser:
|
|||||||
siid=service['iid'], piid=property_['iid'])
|
siid=service['iid'], piid=property_['iid'])
|
||||||
if custom_range:
|
if custom_range:
|
||||||
spec_prop.value_range = custom_range
|
spec_prop.value_range = custom_range
|
||||||
|
custom_list = self._spec_modify.get_prop_value_list(
|
||||||
|
siid=service['iid'], piid=property_['iid'])
|
||||||
|
if custom_list:
|
||||||
|
spec_prop.value_list = custom_list
|
||||||
# Parse service event
|
# Parse service event
|
||||||
for event in service.get('events', []):
|
for event in service.get('events', []):
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -49,3 +49,12 @@ urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1:
|
|||||||
- 1
|
- 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: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
|
||||||
|
|||||||
@ -51,6 +51,7 @@ from homeassistant.components.event import EventDeviceClass
|
|||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
EntityCategory,
|
||||||
LIGHT_LUX,
|
LIGHT_LUX,
|
||||||
UnitOfEnergy,
|
UnitOfEnergy,
|
||||||
UnitOfPower,
|
UnitOfPower,
|
||||||
@ -330,7 +331,8 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
|||||||
'events': set<event instance name: str>,
|
'events': set<event instance name: str>,
|
||||||
'actions': set<action 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'
|
'entity': 'light'
|
||||||
},
|
},
|
||||||
'indicator-light': 'light',
|
|
||||||
'ambient-light': 'light',
|
'ambient-light': 'light',
|
||||||
'night-light': 'light',
|
'night-light': 'light',
|
||||||
'white-light': 'light',
|
'white-light': 'light',
|
||||||
|
'indicator-light': {
|
||||||
|
'required': {
|
||||||
|
'properties': {
|
||||||
|
'on': {'read', 'write'}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'optional': {
|
||||||
|
'properties': {
|
||||||
|
'mode', 'brightness',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'entity': 'light',
|
||||||
|
'entity_category': EntityCategory.CONFIG
|
||||||
|
},
|
||||||
'fan': {
|
'fan': {
|
||||||
'required': {
|
'required': {
|
||||||
'properties': {
|
'properties': {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user