Added command sending mode for lights to optimize the lighting effect

This commit is contained in:
GavinIves 2025-06-04 08:19:00 +00:00
parent d05bdcbba9
commit b72ec85ae9
7 changed files with 2978 additions and 2470 deletions

View File

@ -45,9 +45,10 @@ off Xiaomi or its affiliates' products.
Light entities for Xiaomi Home.
"""
from __future__ import annotations
import logging
from typing import Any, Optional
from typing import Any, Optional, List, Dict
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
@ -59,34 +60,31 @@ from homeassistant.components.light import (
ATTR_EFFECT,
LightEntity,
LightEntityFeature,
ColorMode
)
from homeassistant.util.color import (
value_to_brightness,
brightness_to_value
ColorMode,
)
from homeassistant.util.color import value_to_brightness, brightness_to_value
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
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
device_list: list[MIoTDevice] = hass.data[DOMAIN]["devices"][config_entry.entry_id]
new_entities = []
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(
Light(miot_device=miot_device, entity_data=data))
Light(miot_device=miot_device, entity_data=data, hass=hass)
)
if new_entities:
async_add_entities(new_entities)
@ -94,6 +92,7 @@ async def async_setup_entry(
class Light(MIoTServiceEntity, LightEntity):
"""Light entities for Xiaomi Home."""
# pylint: disable=unused-argument
_VALUE_RANGE_MODE_COUNT_MAX = 30
_prop_on: Optional[MIoTSpecProperty]
@ -106,15 +105,17 @@ class Light(MIoTServiceEntity, LightEntity):
_mode_map: Optional[dict[Any, Any]]
def __init__(
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
self, miot_device: MIoTDevice, entity_data: MIoTEntityData, hass: HomeAssistant
) -> None:
"""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._attr_color_mode = None
self._attr_supported_color_modes = set()
self._attr_supported_features = LightEntityFeature(0)
if miot_device.did.startswith('group.'):
self._attr_icon = 'mdi:lightbulb-group'
self.miot_device = miot_device
if miot_device.did.startswith("group."):
self._attr_icon = "mdi:lightbulb-group"
self._prop_on = None
self._prop_brightness = None
@ -127,33 +128,32 @@ class Light(MIoTServiceEntity, LightEntity):
# properties
for prop in entity_data.props:
# on
if prop.name == 'on':
if prop.name == "on":
self._prop_on = prop
# brightness
if prop.name == 'brightness':
if prop.name == "brightness":
if prop.value_range:
self._brightness_scale = (
prop.value_range.min_, prop.value_range.max_)
prop.value_range.min_,
prop.value_range.max_,
)
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
self._mode_map = prop.value_list.to_map()
self._attr_effect_list = list(self._mode_map.values())
self._attr_supported_features |= LightEntityFeature.EFFECT
self._prop_mode = prop
else:
_LOGGER.info(
'invalid brightness format, %s', self.entity_id)
_LOGGER.info("invalid brightness format, %s", self.entity_id)
continue
# color-temperature
if prop.name == 'color-temperature':
if prop.name == "color-temperature":
if not prop.value_range:
_LOGGER.info(
'invalid color-temperature value_range format, %s',
self.entity_id)
"invalid color-temperature value_range format, %s",
self.entity_id,
)
continue
self._attr_min_color_temp_kelvin = prop.value_range.min_
self._attr_max_color_temp_kelvin = prop.value_range.max_
@ -161,40 +161,44 @@ class Light(MIoTServiceEntity, LightEntity):
self._attr_color_mode = ColorMode.COLOR_TEMP
self._prop_color_temp = prop
# color
if prop.name == 'color':
if prop.name == "color":
self._attr_supported_color_modes.add(ColorMode.RGB)
self._attr_color_mode = ColorMode.RGB
self._prop_color = prop
# mode
if prop.name == 'mode':
if prop.name == "mode":
mode_list = None
if prop.value_list:
mode_list = prop.value_list.to_map()
elif prop.value_range:
mode_list = {}
if (
int((
prop.value_range.max_
- prop.value_range.min_
) / prop.value_range.step)
int(
(prop.value_range.max_ - prop.value_range.min_)
/ prop.value_range.step
)
> self._VALUE_RANGE_MODE_COUNT_MAX
):
_LOGGER.error(
'too many mode values, %s, %s, %s',
self.entity_id, prop.name, prop.value_range)
"too many mode values, %s, %s, %s",
self.entity_id,
prop.name,
prop.value_range,
)
else:
for value in range(
prop.value_range.min_,
prop.value_range.max_,
prop.value_range.step):
mode_list[value] = f'mode {value}'
prop.value_range.min_,
prop.value_range.max_,
prop.value_range.step,
):
mode_list[value] = f"mode {value}"
if mode_list:
self._mode_map = mode_list
self._attr_effect_list = list(self._mode_map.values())
self._attr_supported_features |= LightEntityFeature.EFFECT
self._prop_mode = prop
else:
_LOGGER.info('invalid mode format, %s', self.entity_id)
_LOGGER.info("invalid mode format, %s", self.entity_id)
continue
if not self._attr_supported_color_modes:
@ -242,8 +246,8 @@ class Light(MIoTServiceEntity, LightEntity):
def effect(self) -> Optional[str]:
"""Return the current mode."""
return self.get_map_value(
map_=self._mode_map,
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:
"""Turn the light on.
@ -252,42 +256,135 @@ class Light(MIoTServiceEntity, LightEntity):
"""
# on
# Dirty logic for lumi.gateway.mgl03 indicator light
if self._prop_on:
value_on = True if self._prop_on.format_ == bool else 1
await self.set_property_async(
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
if ATTR_COLOR_TEMP_KELVIN in kwargs:
await self.set_property_async(
prop=self._prop_color_temp,
value=kwargs[ATTR_COLOR_TEMP_KELVIN],
write_ha_state=False)
self._attr_color_mode = ColorMode.COLOR_TEMP
# rgb color
if ATTR_RGB_COLOR in kwargs:
r = kwargs[ATTR_RGB_COLOR][0]
g = kwargs[ATTR_RGB_COLOR][1]
b = kwargs[ATTR_RGB_COLOR][2]
rgb = (r << 16) | (g << 8) | b
await self.set_property_async(
prop=self._prop_color, value=rgb,
write_ha_state=False)
self._attr_color_mode = ColorMode.RGB
# mode
if ATTR_EFFECT in kwargs:
await self.set_property_async(
prop=self._prop_mode,
value=self.get_map_key(
map_=self._mode_map, value=kwargs[ATTR_EFFECT]),
write_ha_state=False)
self.async_write_ha_state()
# Determine whether the device sends the light-on properties in batches or one by one
select_entity_id = f"select.{self.miot_device.gen_device_entity_id(DOMAIN).split('.')[-1]}_command_send_mode"
command_send_mode = self.hass.states.get(select_entity_id)
if command_send_mode and command_send_mode.state == "Send Together":
set_properties_list: List[Dict[str, Any]] = []
if self._prop_on:
value_on = True if self._prop_on.format_ == bool else 1 # noqa: E721
set_properties_list.append({"prop": self._prop_on, "value": value_on})
# brightness
if ATTR_BRIGHTNESS in kwargs:
brightness = brightness_to_value(
self._brightness_scale, kwargs[ATTR_BRIGHTNESS]
)
set_properties_list.append(
{"prop": self._prop_brightness, "value": brightness}
)
# color-temperature
if ATTR_COLOR_TEMP_KELVIN in kwargs:
set_properties_list.append(
{
"prop": self._prop_color_temp,
"value": kwargs[ATTR_COLOR_TEMP_KELVIN],
}
)
self._attr_color_mode = ColorMode.COLOR_TEMP
# rgb color
if ATTR_RGB_COLOR in kwargs:
r = kwargs[ATTR_RGB_COLOR][0]
g = kwargs[ATTR_RGB_COLOR][1]
b = kwargs[ATTR_RGB_COLOR][2]
rgb = (r << 16) | (g << 8) | b
set_properties_list.append({"prop": self._prop_color, "value": rgb})
self._attr_color_mode = ColorMode.RGB
# mode
if ATTR_EFFECT in kwargs:
set_properties_list.append(
{
"prop": self._prop_mode,
"value": self.get_map_key(
map_=self._mode_map, value=kwargs[ATTR_EFFECT]
),
}
)
await self.set_properties_async(set_properties_list)
self.async_write_ha_state()
elif command_send_mode and command_send_mode.state == "Send Turn On First":
set_properties_list: List[Dict[str, Any]] = []
if self._prop_on:
value_on = True if self._prop_on.format_ == bool else 1 # noqa: E721
set_properties_list.append({"prop": self._prop_on, "value": value_on})
await self.set_property_async(prop=self._prop_on, value=value_on)
# brightness
if ATTR_BRIGHTNESS in kwargs:
brightness = brightness_to_value(
self._brightness_scale, kwargs[ATTR_BRIGHTNESS]
)
set_properties_list.append(
{"prop": self._prop_brightness, "value": brightness}
)
# color-temperature
if ATTR_COLOR_TEMP_KELVIN in kwargs:
set_properties_list.append(
{
"prop": self._prop_color_temp,
"value": kwargs[ATTR_COLOR_TEMP_KELVIN],
}
)
self._attr_color_mode = ColorMode.COLOR_TEMP
# rgb color
if ATTR_RGB_COLOR in kwargs:
r = kwargs[ATTR_RGB_COLOR][0]
g = kwargs[ATTR_RGB_COLOR][1]
b = kwargs[ATTR_RGB_COLOR][2]
rgb = (r << 16) | (g << 8) | b
set_properties_list.append({"prop": self._prop_color, "value": rgb})
self._attr_color_mode = ColorMode.RGB
# mode
if ATTR_EFFECT in kwargs:
set_properties_list.append(
{
"prop": self._prop_mode,
"value": self.get_map_key(
map_=self._mode_map, value=kwargs[ATTR_EFFECT]
),
}
)
await self.set_properties_async(set_properties_list)
self.async_write_ha_state()
else:
if self._prop_on:
value_on = True if self._prop_on.format_ == bool else 1 # noqa: E721
await self.set_property_async(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
if ATTR_COLOR_TEMP_KELVIN in kwargs:
await self.set_property_async(
prop=self._prop_color_temp,
value=kwargs[ATTR_COLOR_TEMP_KELVIN],
write_ha_state=False,
)
self._attr_color_mode = ColorMode.COLOR_TEMP
# rgb color
if ATTR_RGB_COLOR in kwargs:
r = kwargs[ATTR_RGB_COLOR][0]
g = kwargs[ATTR_RGB_COLOR][1]
b = kwargs[ATTR_RGB_COLOR][2]
rgb = (r << 16) | (g << 8) | b
await self.set_property_async(
prop=self._prop_color, value=rgb, write_ha_state=False
)
self._attr_color_mode = ColorMode.RGB
# mode
if ATTR_EFFECT in kwargs:
await self.set_property_async(
prop=self._prop_mode,
value=self.get_map_key(
map_=self._mode_map, value=kwargs[ATTR_EFFECT]
),
write_ha_state=False,
)
self.async_write_ha_state()
async def async_turn_off(self, **kwargs) -> None:
"""Turn the light off."""

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,18 +45,24 @@ off Xiaomi or its affiliates' products.
Select entities for Xiaomi Home.
"""
from __future__ import annotations
import logging
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.select import SelectEntity
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.restore_state import RestoreEntity
from .miot.const import DOMAIN
from .miot.miot_device import MIoTDevice, MIoTPropertyEntity
from .miot.miot_spec import MIoTSpecProperty
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
@ -64,17 +70,32 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
device_list: list[MIoTDevice] = hass.data[DOMAIN]["devices"][config_entry.entry_id]
new_entities = []
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))
if new_entities:
async_add_entities(new_entities)
# create select for light
new_light_select_entities = []
for miot_device in device_list:
if "device:light" in miot_device.spec_instance.urn:
if miot_device.entity_list.get("light", []):
device_id = list(miot_device.device_info.get("identifiers"))[0][1]
light_entity_id = miot_device.gen_device_entity_id(DOMAIN)
new_light_select_entities.append(
LightCommandSendMode(
hass=hass, light_entity_id=light_entity_id, device_id=device_id
)
)
if new_light_select_entities:
async_add_entities(new_light_select_entities)
class Select(MIoTPropertyEntity, SelectEntity):
"""Select entities for Xiaomi Home."""
@ -87,10 +108,46 @@ class Select(MIoTPropertyEntity, SelectEntity):
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
await self.set_property_async(
value=self.get_vlist_value(description=option))
await self.set_property_async(value=self.get_vlist_value(description=option))
@property
def current_option(self) -> Optional[str]:
"""Return the current selected option."""
return self.get_vlist_description(value=self._value)
class LightCommandSendMode(SelectEntity, RestoreEntity):
"""To control whether to turn on the light, you need to send the light-on command first and
then send other color temperatures and brightness or send them all at the same time.
The default is to send one by one."""
def __init__(self, hass: HomeAssistant, light_entity_id: str, device_id: str):
super().__init__()
self.hass = hass
self._device_id = device_id
self._attr_name = "Command Send Mode"
self._attr_unique_id = f"{light_entity_id}_command_send_mode"
self._attr_options = ["Send One by One", "Send Turn On First", "Send Together"]
self._attr_device_info = {"identifiers": {(DOMAIN, device_id)}}
self._attr_current_option = self._attr_options[0] # 默认选项
self._attr_entity_category = (
EntityCategory.CONFIG
) # **重点:告诉 HA 这是配置类实体**
async def async_select_option(self, option: str):
"""处理用户选择的选项。"""
if option in self._attr_options:
self._attr_current_option = option
self.async_write_ha_state()
async def async_added_to_hass(self):
"""在实体添加到 Home Assistant 时恢复上次的状态。"""
await super().async_added_to_hass()
if (
last_state := await self.async_get_last_state()
) and last_state.state in self._attr_options:
self._attr_current_option = last_state.state
@property
def current_option(self):
return self._attr_current_option