Compare commits

..

6 Commits

Author SHA1 Message Date
Gavin
04775f0dd0
Merge f5bc880c62 into f4d591b4d3 2025-12-28 23:24:40 +08:00
GavinIves
f5bc880c62 fixbug 2025-12-28 15:24:34 +00:00
GavinIves
4c7b6d570d sync main 2025-12-28 15:10:02 +00:00
GavinIves
b95a2c18ee review 2025-12-28 14:29:55 +00:00
GavinIves
5dc6f400c2 fomatted code 2025-12-28 13:46:30 +00:00
GavinIves
006a445e3a Added command sending mode for lights to optimize the lighting effect 2025-12-28 13:35:02 +00:00
7 changed files with 2776 additions and 2839 deletions

View File

@ -45,7 +45,6 @@ off Xiaomi or its affiliates' products.
Light entities for Xiaomi Home. Light entities for Xiaomi Home.
""" """
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import Any, Optional, List, Dict from typing import Any, Optional, List, Dict
@ -61,29 +60,32 @@ from homeassistant.components.light import (
ATTR_EFFECT, ATTR_EFFECT,
LightEntity, LightEntity,
LightEntityFeature, LightEntityFeature,
ColorMode, ColorMode
)
from homeassistant.util.color import (
value_to_brightness,
brightness_to_value
) )
from homeassistant.util.color import value_to_brightness, brightness_to_value
from .miot.miot_spec import MIoTSpecProperty from .miot.miot_spec import MIoTSpecProperty
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
from .miot.const import DOMAIN from .miot.const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up a config entry.""" """Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]["devices"][ device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id] config_entry.entry_id]
new_entities = [] new_entities = []
for miot_device in device_list: for miot_device in device_list:
for data in miot_device.entity_list.get("light", []): for data in miot_device.entity_list.get('light', []):
new_entities.append( new_entities.append(
Light(miot_device=miot_device, entity_data=data, hass=hass)) Light(miot_device=miot_device, entity_data=data, hass=hass))
@ -93,7 +95,6 @@ async def async_setup_entry(
class Light(MIoTServiceEntity, LightEntity): class Light(MIoTServiceEntity, LightEntity):
"""Light entities for Xiaomi Home.""" """Light entities for Xiaomi Home."""
# pylint: disable=unused-argument # pylint: disable=unused-argument
_VALUE_RANGE_MODE_COUNT_MAX = 30 _VALUE_RANGE_MODE_COUNT_MAX = 30
_prop_on: Optional[MIoTSpecProperty] _prop_on: Optional[MIoTSpecProperty]
@ -105,17 +106,19 @@ class Light(MIoTServiceEntity, LightEntity):
_brightness_scale: Optional[tuple[int, int]] _brightness_scale: Optional[tuple[int, int]]
_mode_map: Optional[dict[Any, Any]] _mode_map: Optional[dict[Any, Any]]
def __init__(self, miot_device: MIoTDevice, entity_data: MIoTEntityData, def __init__(
hass: HomeAssistant) -> None: self, miot_device: MIoTDevice, entity_data: MIoTEntityData,hass: HomeAssistant
) -> None:
"""Initialize the Light.""" """Initialize the Light."""
super().__init__(miot_device=miot_device, entity_data=entity_data) super().__init__(miot_device=miot_device, entity_data=entity_data)
self.hass = hass self.hass = hass
self.miot_device = miot_device
self._command_send_mode_entity_id = None
self._attr_color_mode = None self._attr_color_mode = None
self._attr_supported_color_modes = set() self._attr_supported_color_modes = set()
self._attr_supported_features = LightEntityFeature(0) self._attr_supported_features = LightEntityFeature(0)
self.miot_device = miot_device if miot_device.did.startswith('group.'):
if miot_device.did.startswith("group."): self._attr_icon = 'mdi:lightbulb-group'
self._attr_icon = "mdi:lightbulb-group"
self._prop_on = None self._prop_on = None
self._prop_brightness = None self._prop_brightness = None
@ -124,38 +127,37 @@ class Light(MIoTServiceEntity, LightEntity):
self._prop_mode = None self._prop_mode = None
self._brightness_scale = None self._brightness_scale = None
self._mode_map = None self._mode_map = None
self._command_send_mode_entity_id = None
# properties # properties
for prop in entity_data.props: for prop in entity_data.props:
# on # on
if prop.name == "on": if prop.name == 'on':
self._prop_on = prop self._prop_on = prop
# brightness # brightness
if prop.name == "brightness": if prop.name == 'brightness':
if prop.value_range: if prop.value_range:
self._brightness_scale = ( self._brightness_scale = (
prop.value_range.min_, prop.value_range.min_, prop.value_range.max_)
prop.value_range.max_,
)
self._prop_brightness = prop self._prop_brightness = prop
elif self._mode_map is None and prop.value_list: elif (
self._mode_map is None
and prop.value_list
):
# For value-list brightness # For value-list brightness
self._mode_map = prop.value_list.to_map() self._mode_map = prop.value_list.to_map()
self._attr_effect_list = list(self._mode_map.values()) self._attr_effect_list = list(self._mode_map.values())
self._attr_supported_features |= LightEntityFeature.EFFECT self._attr_supported_features |= LightEntityFeature.EFFECT
self._prop_mode = prop self._prop_mode = prop
else: else:
_LOGGER.info("invalid brightness format, %s", _LOGGER.info(
self.entity_id) 'invalid brightness format, %s', self.entity_id)
continue continue
# color-temperature # color-temperature
if prop.name == "color-temperature": if prop.name == 'color-temperature':
if not prop.value_range: 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_
@ -163,40 +165,40 @@ class Light(MIoTServiceEntity, LightEntity):
self._attr_color_mode = ColorMode.COLOR_TEMP self._attr_color_mode = ColorMode.COLOR_TEMP
self._prop_color_temp = prop self._prop_color_temp = prop
# color # color
if prop.name == "color": if prop.name == 'color':
self._attr_supported_color_modes.add(ColorMode.RGB) self._attr_supported_color_modes.add(ColorMode.RGB)
self._attr_color_mode = ColorMode.RGB self._attr_color_mode = ColorMode.RGB
self._prop_color = prop self._prop_color = prop
# mode # mode
if prop.name == "mode": if prop.name == 'mode':
mode_list = None mode_list = None
if prop.value_list: if prop.value_list:
mode_list = prop.value_list.to_map() mode_list = prop.value_list.to_map()
elif prop.value_range: elif prop.value_range:
mode_list = {} mode_list = {}
if (int((prop.value_range.max_ - prop.value_range.min_) / if (
prop.value_range.step) int((
> self._VALUE_RANGE_MODE_COUNT_MAX): prop.value_range.max_
- prop.value_range.min_
) / prop.value_range.step)
> self._VALUE_RANGE_MODE_COUNT_MAX
):
_LOGGER.error( _LOGGER.error(
"too many mode values, %s, %s, %s", 'too many mode values, %s, %s, %s',
self.entity_id, self.entity_id, prop.name, prop.value_range)
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_map = mode_list self._mode_map = mode_list
self._attr_effect_list = list(self._mode_map.values()) self._attr_effect_list = list(self._mode_map.values())
self._attr_supported_features |= LightEntityFeature.EFFECT self._attr_supported_features |= LightEntityFeature.EFFECT
self._prop_mode = prop self._prop_mode = prop
else: else:
_LOGGER.info("invalid mode format, %s", self.entity_id) _LOGGER.info('invalid mode format, %s', self.entity_id)
continue continue
if not self._attr_supported_color_modes: if not self._attr_supported_color_modes:
@ -243,8 +245,9 @@ class Light(MIoTServiceEntity, LightEntity):
@property @property
def effect(self) -> Optional[str]: def effect(self) -> Optional[str]:
"""Return the current mode.""" """Return the current mode."""
return self.get_map_value(map_=self._mode_map, return self.get_map_value(
key=self.get_prop_value(prop=self._prop_mode)) map_=self._mode_map,
key=self.get_prop_value(prop=self._prop_mode))
async def async_turn_on(self, **kwargs) -> None: async def async_turn_on(self, **kwargs) -> None:
"""Turn the light on. """Turn the light on.
@ -255,6 +258,7 @@ class Light(MIoTServiceEntity, LightEntity):
# Dirty logic for lumi.gateway.mgl03 indicator light # Dirty logic for lumi.gateway.mgl03 indicator light
# Determine whether the device sends the light-on properties in batches or one by one # Determine whether the device sends the light-on properties in batches or one by one
# Search entityid through unique_id to avoid the user modifying entityid and causing command_send_mode to not match # Search entityid through unique_id to avoid the user modifying entityid and causing command_send_mode to not match
# 获取开灯模式
if self._command_send_mode_entity_id is None: if self._command_send_mode_entity_id is None:
entity_registry = async_get_entity_registry(self.hass) entity_registry = async_get_entity_registry(self.hass)
device_id = list( device_id = list(
@ -269,27 +273,29 @@ class Light(MIoTServiceEntity, LightEntity):
return return
command_send_mode = self.hass.states.get( command_send_mode = self.hass.states.get(
self._command_send_mode_entity_id) self._command_send_mode_entity_id)
# 判断是先发送亮度还是先发送色温
send_brightness_first = False send_brightness_first = False
if ATTR_BRIGHTNESS in kwargs: if ATTR_BRIGHTNESS in kwargs:
brightness_new = kwargs[ATTR_BRIGHTNESS] brightness_new = kwargs[ATTR_BRIGHTNESS]
brightness_old = self.brightness brightness_old = self.brightness
if brightness_old and brightness_new <= brightness_old: if brightness_old and brightness_new <= brightness_old:
send_brightness_first = True send_brightness_first = True
# 开始发送开灯命令
if command_send_mode and command_send_mode.state == "Send Together": if command_send_mode and command_send_mode.state == "Send Together":
set_properties_list: List[Dict[str, Any]] = [] set_properties_list: List[Dict[str, Any]] = []
# mode # mode
if ATTR_EFFECT in kwargs: if ATTR_EFFECT in kwargs:
set_properties_list.append({ set_properties_list.append({
"prop": "prop":self._prop_mode,
self._prop_mode, "value":self.get_map_key(
"value": map_=self._mode_map,value=kwargs[ATTR_EFFECT]),
self.get_map_key(map_=self._mode_map,
value=kwargs[ATTR_EFFECT]),
}) })
# brightness # brightness
if send_brightness_first and ATTR_BRIGHTNESS in kwargs: if send_brightness_first and ATTR_BRIGHTNESS in kwargs:
brightness = brightness_to_value(self._brightness_scale, brightness = brightness_to_value(
kwargs[ATTR_BRIGHTNESS]) self._brightness_scale,kwargs[ATTR_BRIGHTNESS])
set_properties_list.append({ set_properties_list.append({
"prop": self._prop_brightness, "prop": self._prop_brightness,
"value": brightness "value": brightness
@ -314,25 +320,26 @@ class Light(MIoTServiceEntity, LightEntity):
self._attr_color_mode = ColorMode.RGB self._attr_color_mode = ColorMode.RGB
# brightness # brightness
if not send_brightness_first and ATTR_BRIGHTNESS in kwargs: if not send_brightness_first and ATTR_BRIGHTNESS in kwargs:
brightness = brightness_to_value(self._brightness_scale, brightness = brightness_to_value(
kwargs[ATTR_BRIGHTNESS]) self._brightness_scale,kwargs[ATTR_BRIGHTNESS])
set_properties_list.append({ set_properties_list.append({
"prop": self._prop_brightness, "prop": self._prop_brightness,
"value": brightness "value": brightness
}) })
if self._prop_on: if self._prop_on:
value_on = True if self._prop_on.format_ == bool else 1 # noqa: E721 value_on = True if self._prop_on.format_ == bool else 1
set_properties_list.append({ set_properties_list.append({
"prop": self._prop_on, "prop": self._prop_on,
"value": value_on "value": value_on
}) })
await self.set_properties_async(set_properties_list) await self.set_properties_async(set_properties_list,write_ha_state=False)
self.async_write_ha_state() self.async_write_ha_state()
elif command_send_mode and command_send_mode.state == "Send Turn On First": elif command_send_mode and command_send_mode.state == "Send Turn On First":
set_properties_list: List[Dict[str, Any]] = [] set_properties_list: List[Dict[str, Any]] = []
if self._prop_on: if self._prop_on:
value_on = True if self._prop_on.format_ == bool else 1 # noqa: E721 value_on = True if self._prop_on.format_ == bool else 1
set_properties_list.append({ set_properties_list.append({
"prop": self._prop_on, "prop": self._prop_on,
"value": value_on "value": value_on
@ -343,13 +350,13 @@ class Light(MIoTServiceEntity, LightEntity):
"prop": "prop":
self._prop_mode, self._prop_mode,
"value": "value":
self.get_map_key(map_=self._mode_map, self.get_map_key(
value=kwargs[ATTR_EFFECT]), map_=self._mode_map,value=kwargs[ATTR_EFFECT]),
}) })
# brightness # brightness
if send_brightness_first and ATTR_BRIGHTNESS in kwargs: if send_brightness_first and ATTR_BRIGHTNESS in kwargs:
brightness = brightness_to_value(self._brightness_scale, brightness = brightness_to_value(
kwargs[ATTR_BRIGHTNESS]) self._brightness_scale,kwargs[ATTR_BRIGHTNESS])
set_properties_list.append({ set_properties_list.append({
"prop": self._prop_brightness, "prop": self._prop_brightness,
"value": brightness "value": brightness
@ -374,28 +381,34 @@ class Light(MIoTServiceEntity, LightEntity):
self._attr_color_mode = ColorMode.RGB self._attr_color_mode = ColorMode.RGB
# brightness # brightness
if not send_brightness_first and ATTR_BRIGHTNESS in kwargs: if not send_brightness_first and ATTR_BRIGHTNESS in kwargs:
brightness = brightness_to_value(self._brightness_scale, brightness = brightness_to_value(
kwargs[ATTR_BRIGHTNESS]) self._brightness_scale,kwargs[ATTR_BRIGHTNESS])
set_properties_list.append({ set_properties_list.append({
"prop": self._prop_brightness, "prop": self._prop_brightness,
"value": brightness "value": brightness
}) })
await self.set_properties_async(set_properties_list) await self.set_properties_async(set_properties_list,write_ha_state=False)
self.async_write_ha_state() self.async_write_ha_state()
else: else:
if self._prop_on: if self._prop_on:
value_on = True if self._prop_on.format_ == bool else 1 # noqa: E721 value_on = True if self._prop_on.format_ == bool else 1
await self.set_property_async(prop=self._prop_on, await self.set_property_async(
value=value_on) prop=self._prop_on, value=value_on)
# brightness
if ATTR_BRIGHTNESS in kwargs:
brightness = brightness_to_value(
self._brightness_scale, kwargs[ATTR_BRIGHTNESS])
await self.set_property_async(
prop=self._prop_brightness, value=brightness,
write_ha_state=False)
# color-temperature # color-temperature
if ATTR_COLOR_TEMP_KELVIN in kwargs: if ATTR_COLOR_TEMP_KELVIN in kwargs:
await self.set_property_async( await self.set_property_async(
prop=self._prop_color_temp, prop=self._prop_color_temp,
value=kwargs[ATTR_COLOR_TEMP_KELVIN], value=kwargs[ATTR_COLOR_TEMP_KELVIN],
write_ha_state=False, write_ha_state=False)
)
self._attr_color_mode = ColorMode.COLOR_TEMP self._attr_color_mode = ColorMode.COLOR_TEMP
# rgb color # rgb color
if ATTR_RGB_COLOR in kwargs: if ATTR_RGB_COLOR in kwargs:
@ -403,25 +416,17 @@ class Light(MIoTServiceEntity, LightEntity):
g = kwargs[ATTR_RGB_COLOR][1] g = kwargs[ATTR_RGB_COLOR][1]
b = kwargs[ATTR_RGB_COLOR][2] b = kwargs[ATTR_RGB_COLOR][2]
rgb = (r << 16) | (g << 8) | b rgb = (r << 16) | (g << 8) | b
await self.set_property_async(prop=self._prop_color, await self.set_property_async(
value=rgb, prop=self._prop_color, value=rgb,
write_ha_state=False) write_ha_state=False)
self._attr_color_mode = ColorMode.RGB self._attr_color_mode = ColorMode.RGB
# brightness
if ATTR_BRIGHTNESS in kwargs:
brightness = brightness_to_value(self._brightness_scale,
kwargs[ATTR_BRIGHTNESS])
await self.set_property_async(prop=self._prop_brightness,
value=brightness,
write_ha_state=False)
# mode # mode
if ATTR_EFFECT in kwargs: if ATTR_EFFECT in kwargs:
await self.set_property_async( await self.set_property_async(
prop=self._prop_mode, prop=self._prop_mode,
value=self.get_map_key(map_=self._mode_map, value=self.get_map_key(
value=kwargs[ATTR_EFFECT]), map_=self._mode_map, value=kwargs[ATTR_EFFECT]),
write_ha_state=False, write_ha_state=False)
)
self.async_write_ha_state() self.async_write_ha_state()
async def async_turn_off(self, **kwargs) -> None: async def async_turn_off(self, **kwargs) -> None:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -45,9 +45,7 @@ off Xiaomi or its affiliates' products.
Select entities for Xiaomi Home. Select entities for Xiaomi Home.
""" """
from __future__ import annotations from __future__ import annotations
import logging
from typing import Optional from typing import Optional
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -61,8 +59,6 @@ from .miot.const import DOMAIN
from .miot.miot_device import MIoTDevice, MIoTPropertyEntity from .miot.miot_device import MIoTDevice, MIoTPropertyEntity
from .miot.miot_spec import MIoTSpecProperty from .miot.miot_spec import MIoTSpecProperty
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
@ -70,12 +66,12 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up a config entry.""" """Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]["devices"][ device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id] config_entry.entry_id]
new_entities = [] new_entities = []
for miot_device in device_list: for miot_device in device_list:
for prop in miot_device.prop_list.get("select", []): for prop in miot_device.prop_list.get('select', []):
new_entities.append(Select(miot_device=miot_device, spec=prop)) new_entities.append(Select(miot_device=miot_device, spec=prop))
if new_entities: if new_entities:
@ -90,11 +86,9 @@ async def async_setup_entry(
device_id = list(miot_device.device_info.get("identifiers"))[0][1] device_id = list(miot_device.device_info.get("identifiers"))[0][1]
new_light_select_entities.append( new_light_select_entities.append(
LightCommandSendMode(hass=hass, device_id=device_id)) LightCommandSendMode(hass=hass, device_id=device_id))
if new_light_select_entities: if new_light_select_entities:
async_add_entities(new_light_select_entities) async_add_entities(new_light_select_entities)
class Select(MIoTPropertyEntity, SelectEntity): class Select(MIoTPropertyEntity, SelectEntity):
"""Select entities for Xiaomi Home.""" """Select entities for Xiaomi Home."""
@ -106,8 +100,8 @@ class Select(MIoTPropertyEntity, SelectEntity):
async def async_select_option(self, option: str) -> None: async def async_select_option(self, option: str) -> None:
"""Change the selected option.""" """Change the selected option."""
await self.set_property_async(value=self.get_vlist_value( await self.set_property_async(
description=option)) value=self.get_vlist_value(description=option))
@property @property
def current_option(self) -> Optional[str]: def current_option(self) -> Optional[str]:
@ -127,11 +121,9 @@ class LightCommandSendMode(SelectEntity, RestoreEntity):
self._attr_name = "Command Send Mode" self._attr_name = "Command Send Mode"
self.entity_id = f"select.light_{device_id}_command_send_mode" self.entity_id = f"select.light_{device_id}_command_send_mode"
self._attr_unique_id = self.entity_id self._attr_unique_id = self.entity_id
self._attr_options = [ self._attr_options = [
"Send One by One", "Send Turn On First", "Send Together" "Send One by One", "Send Turn On First", "Send Together"
] ]
self._attr_device_info = {"identifiers": {(DOMAIN, device_id)}} self._attr_device_info = {"identifiers": {(DOMAIN, device_id)}}
self._attr_current_option = self._attr_options[0] self._attr_current_option = self._attr_options[0]
self._attr_entity_category = EntityCategory.CONFIG self._attr_entity_category = EntityCategory.CONFIG