Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 178 additions & 38 deletions custom_components/xiaomi_home/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@
"""
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
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
Expand Down Expand Up @@ -86,7 +87,7 @@ async def async_setup_entry(
for miot_device in device_list:
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)
Expand All @@ -106,10 +107,13 @@ 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)
self.hass = hass
self.miot_device = miot_device
self._command_send_mode_entity_id = None
self._attr_color_mode = None
self._attr_supported_color_modes = set()
self._attr_supported_features = LightEntityFeature(0)
Expand Down Expand Up @@ -252,42 +256,178 @@ async def async_turn_on(self, **kwargs) -> None:
"""
# 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
# 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
# 获取开灯模式
if self._command_send_mode_entity_id is None:
entity_registry = async_get_entity_registry(self.hass)
device_id = list(
self.miot_device.device_info.get("identifiers"))[0][1]
self._command_send_mode_entity_id = entity_registry.async_get_entity_id(
"select", DOMAIN, f"select.light_{device_id}_command_send_mode")
if self._command_send_mode_entity_id is None:
_LOGGER.error(
"light command_send_mode not found, %s",
self.entity_id,
)
return
command_send_mode = self.hass.states.get(
self._command_send_mode_entity_id)

# 判断是先发送亮度还是先发送色温
send_brightness_first = False
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()
brightness_new = kwargs[ATTR_BRIGHTNESS]
brightness_old = self.brightness
if brightness_old and brightness_new <= brightness_old:
send_brightness_first = True

# 开始发送开灯命令
if command_send_mode and command_send_mode.state == "Send Together":
set_properties_list: List[Dict[str, Any]] = []
# 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]),
})
# brightness
if send_brightness_first and 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
# brightness
if not send_brightness_first and ATTR_BRIGHTNESS in kwargs:
brightness = brightness_to_value(
self._brightness_scale,kwargs[ATTR_BRIGHTNESS])
set_properties_list.append({
"prop": self._prop_brightness,
"value": brightness
})

if self._prop_on:
value_on = True if self._prop_on.format_ == bool else 1
set_properties_list.append({
"prop": self._prop_on,
"value": value_on
})
await self.set_properties_async(set_properties_list,write_ha_state=False)
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
set_properties_list.append({
"prop": self._prop_on,
"value": value_on
})
# 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]),
})
# brightness
if send_brightness_first and 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
# brightness
if not send_brightness_first and ATTR_BRIGHTNESS in kwargs:
brightness = brightness_to_value(
self._brightness_scale,kwargs[ATTR_BRIGHTNESS])
set_properties_list.append({
"prop": self._prop_brightness,
"value": brightness
})

await self.set_properties_async(set_properties_list,write_ha_state=False)
self.async_write_ha_state()

else:
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()

async def async_turn_off(self, **kwargs) -> None:
"""Turn the light off."""
Expand Down
80 changes: 79 additions & 1 deletion custom_components/xiaomi_home/miot/miot_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
MIoT client instance.
"""
from copy import deepcopy
from typing import Any, Callable, Optional, final
from typing import Any, Callable, Optional, final, Dict, List
import asyncio
import json
import logging
Expand Down Expand Up @@ -710,6 +710,84 @@ async def set_prop_async(
f'{self._i18n.translate("miot.client.device_exec_error")}, '
f'{self._i18n.translate("error.common.-10007")}')

async def set_props_async(
self, props_list: List[Dict[str, Any]],
) -> bool:
# props_list = [{'did': str, 'siid': int, 'piid': int, 'value': Any}......]
# 判断是不是只有一个did
did_set = {prop["did"] for prop in props_list}
if len(did_set) != 1:
raise MIoTClientError(f"more than one or no did once, {did_set}")
did = did_set.pop()

if did not in self._device_list_cache:
raise MIoTClientError(f"did not exist, {did}")
# Priority local control
if self._ctrl_mode == CtrlMode.AUTO:
# Gateway control
device_gw = self._device_list_gateway.get(did, None)
if (
device_gw and device_gw.get("online", False)
and device_gw.get("specv2_access", False) and "group_id" in device_gw
):
mips = self._mips_local.get(device_gw["group_id"], None)
if mips is None:
_LOGGER.error(
"no gateway route, %s, try control through cloud",
device_gw)
else:
result = await mips.set_props_async(
did=did,props_list=props_list)
_LOGGER.debug("gateway set props, %s -> %s", props_list, result)
rc = [(r or {}).get("code",
MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
for r in result]
if all(t in [0, 1] for t in rc):
return True
else:
raise MIoTClientError(
self.__get_exec_error_with_rc(rc=next(x for x in rc if x not in (0, 1))))
# Lan control
device_lan = self._device_list_lan.get(did, None)
if device_lan and device_lan.get("online", False):
result = await self._miot_lan.set_props_async(
did=did, props_list=props_list)
_LOGGER.debug("lan set props, %s -> %s", props_list, result)
rc = [(r or {}).get("code",
MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
for r in result]
if all(t in [0, 1] for t in rc):
return True
else:
raise MIoTClientError(
self.__get_exec_error_with_rc(rc=next(x for x in rc if x not in (0, 1))))
# Cloud control
device_cloud = self._device_list_cloud.get(did, None)
if device_cloud and device_cloud.get("online", False):
result = await self._http.set_props_async(params=props_list)
_LOGGER.debug(
"cloud set props, %s, result, %s",
props_list, result)
if result and len(result) == len(props_list):
rc = [(r or {}).get("code",
MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
for r in result]
if all(t in [0, 1] for t in rc):
return True
if any(t in [-704010000, -704042011] for t in rc):
# Device remove or offline
_LOGGER.error("device may be removed or offline, %s", did)
self._main_loop.create_task(
await
self.__refresh_cloud_device_with_dids_async(dids=[did]))
raise MIoTClientError(
self.__get_exec_error_with_rc(rc=next(x for x in rc if x not in (0, 1))))

# Show error message
raise MIoTClientError(
f'{self._i18n.translate("miot.client.device_exec_error")}, '
f'{self._i18n.translate("error.common.-10007")}')

def request_refresh_prop(
self, did: str, siid: int, piid: int
) -> None:
Expand Down
16 changes: 16 additions & 0 deletions custom_components/xiaomi_home/miot/miot_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,22 @@ async def set_prop_async(self, params: list) -> list:

return res_obj['result']

async def set_props_async(self, params: list) -> list:
"""
params = [{"did": "xxxx", "siid": 2, "piid": 1, "value": False}]
"""
res_obj = await self.__mihome_api_post_async(
url_path='/app/v2/miotspec/prop/set',
data={
'params': params
},
timeout=15
)
if 'result' not in res_obj:
raise MIoTHttpError('invalid response result')

return res_obj['result']

async def action_async(
self, did: str, siid: int, aiid: int, in_list: list[dict]
) -> dict:
Expand Down
Loading