mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2026-01-11 11:20:43 +08:00
Compare commits
8 Commits
8f02af3fe1
...
7142283370
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7142283370 | ||
|
|
001af5384a | ||
|
|
c8d48625e9 | ||
|
|
f5bc880c62 | ||
|
|
4c7b6d570d | ||
|
|
b95a2c18ee | ||
|
|
5dc6f400c2 | ||
|
|
006a445e3a |
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,4 +1,16 @@
|
||||
# CHANGELOG
|
||||
## v0.4.7
|
||||
### Added
|
||||
- Add turkish language in multi_lang.json. [#1593](https://github.com/XiaoMi/ha_xiaomi_home/pull/1593)
|
||||
### Changed
|
||||
- Remove unused info getting from central hub gateway. [#1574](https://github.com/XiaoMi/ha_xiaomi_home/pull/1574)
|
||||
- Remove xiaomi.router.rd03 from `UNSUPPORTED_MODELS` and add era.airp.cwb03, k0918.toothbrush.t700 into it. [#1567](https://github.com/XiaoMi/ha_xiaomi_home/pull/1567)
|
||||
### Fixed
|
||||
- Update the BLE mesh device online state from the central hub gateway. Partially fix the BLE mesh device online state. [#1579](https://github.com/XiaoMi/ha_xiaomi_home/pull/1579)
|
||||
- Add unit for xiaomi.toothbrush.p001 brush-head-left-level property. [#1588](https://github.com/XiaoMi/ha_xiaomi_home/pull/1588)
|
||||
- Fix the playing-state property's access field of xiaomi.wifispeaker.lx04, xiaomi.wifispeaker.lx06, xiaomi.wifispeaker.x08c and xiaomi.wifispeaker.l04m. [#1567](https://github.com/XiaoMi/ha_xiaomi_home/pull/1567)
|
||||
- Fix the MIoT-Spec-V2 of xiaomi.airc.h09h00 humidity-range unit. [#1567](https://github.com/XiaoMi/ha_xiaomi_home/pull/1567)
|
||||
|
||||
## v0.4.6
|
||||
### Added
|
||||
- Add tv-box device as the media player entity. [#1562](https://github.com/XiaoMi/ha_xiaomi_home/pull/1562)
|
||||
@ -12,7 +24,7 @@
|
||||
- Catch paho-mqtt subscribe error properly. [#1551](https://github.com/XiaoMi/ha_xiaomi_home/pull/1551)
|
||||
- After the network resumes, keep retrying to fetch the device list until it succeeds. [#1555](https://github.com/XiaoMi/ha_xiaomi_home/pull/1555)
|
||||
- Catch the http post error properly. [#1555](https://github.com/XiaoMi/ha_xiaomi_home/pull/1555)
|
||||
- Fixed the format and the access field of daikin.aircondition.k2 and fix: daikin.airfresh.k33 string value properties. [#1561](https://github.com/XiaoMi/ha_xiaomi_home/pull/1561)
|
||||
- Fix the format and the access field of daikin.aircondition.k2 and daikin.airfresh.k33 string value properties. [#1561](https://github.com/XiaoMi/ha_xiaomi_home/pull/1561)
|
||||
|
||||
## v0.4.5
|
||||
### Changed
|
||||
|
||||
@ -47,11 +47,12 @@ 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
|
||||
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,
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -252,42 +256,178 @@ 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
|
||||
# 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."""
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
"cryptography",
|
||||
"psutil"
|
||||
],
|
||||
"version": "v0.4.6",
|
||||
"version": "v0.4.7",
|
||||
"zeroconf": [
|
||||
"_miot-central._tcp.local."
|
||||
]
|
||||
|
||||
@ -90,8 +90,9 @@ SUPPORTED_PLATFORMS: list = [
|
||||
|
||||
UNSUPPORTED_MODELS: list = [
|
||||
'chuangmi.ir.v2',
|
||||
'era.airp.cwb03',
|
||||
'hmpace.motion.v6nfc',
|
||||
'xiaomi.router.rd03'
|
||||
'k0918.toothbrush.t700'
|
||||
]
|
||||
|
||||
DEFAULT_CLOUD_SERVER: str = 'cn'
|
||||
|
||||
@ -46,7 +46,7 @@ off Xiaomi or its affiliates' products.
|
||||
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
|
||||
@ -710,6 +710,84 @@ class MIoTClient:
|
||||
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:
|
||||
|
||||
@ -825,6 +825,22 @@ class MIoTHttpClient:
|
||||
|
||||
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:
|
||||
|
||||
@ -47,7 +47,7 @@ MIoT device instance.
|
||||
"""
|
||||
import asyncio
|
||||
from abc import abstractmethod
|
||||
from typing import Any, Callable, Optional
|
||||
from typing import Any, Callable, Optional, Dict, List
|
||||
import logging
|
||||
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@ -682,58 +682,66 @@ class MIoTDevice:
|
||||
|
||||
def unit_convert(self, spec_unit: str) -> Optional[str]:
|
||||
"""Convert MIoT unit to Home Assistant unit.
|
||||
25/01/20: All online prop unit statistical tables: unit, quantity.
|
||||
2026/01/06: property unit statistics of the latest released
|
||||
MIoT-Spec-V2 for all device models: unit, quantity.
|
||||
{
|
||||
"no_unit": 148499,
|
||||
"percentage": 10042,
|
||||
"kelvin": 1895,
|
||||
"rgb": 772, // color
|
||||
"celsius": 5762,
|
||||
"none": 16106,
|
||||
"hours": 1540,
|
||||
"minutes": 5061,
|
||||
"ms": 27,
|
||||
"watt": 216,
|
||||
"arcdegrees": 159,
|
||||
"ppm": 177,
|
||||
"μg/m3": 106,
|
||||
"days": 571,
|
||||
"seconds": 2749,
|
||||
"percentage": 12074,
|
||||
"none": 11857,
|
||||
"minutes": 5707,
|
||||
"celsius": 5767,
|
||||
"seconds": 3062,
|
||||
"kelvin": 2511,
|
||||
"hours": 1380,
|
||||
"days": 615,
|
||||
"rgb": 752, // color
|
||||
"L": 379,
|
||||
"mg/m3": 335,
|
||||
"ppm": 182,
|
||||
"watt": 246,
|
||||
"arcdegrees": 130,
|
||||
"μg/m3": 117,
|
||||
"kWh": 149,
|
||||
"ms": 108,
|
||||
"pascal": 108,
|
||||
"lux": 100,
|
||||
"V": 59,
|
||||
"m": 45,
|
||||
"A": 36,
|
||||
"mL": 30,
|
||||
"arcdegress": 25,
|
||||
"mA": 26,
|
||||
"bpm": 21, // realtime-heartrate
|
||||
"B/s": 21,
|
||||
"pascal": 110,
|
||||
"mg/m3": 339,
|
||||
"lux": 125,
|
||||
"kWh": 124,
|
||||
"mv": 2,
|
||||
"V": 38,
|
||||
"A": 29,
|
||||
"mV": 4,
|
||||
"L": 352,
|
||||
"m": 37,
|
||||
"毫摩尔每升": 2, // blood-sugar, cholesterol
|
||||
"mmol/L": 1, // urea
|
||||
"weeks": 26,
|
||||
"meter": 3,
|
||||
"dB": 26,
|
||||
"hour": 14,
|
||||
"calorie": 19, // 1 cal = 4.184 J
|
||||
"ppb": 3,
|
||||
"arcdegress": 30,
|
||||
"bpm": 4, // realtime-heartrate
|
||||
"gram": 7,
|
||||
"km/h": 9,
|
||||
"W": 1,
|
||||
"m3/h": 2,
|
||||
"kilopascal": 1,
|
||||
"mL": 4,
|
||||
"weeks": 18,
|
||||
"dB": 17,
|
||||
"calorie": 18, // 1 cal = 4.184 J
|
||||
"metre": 15,
|
||||
"hour": 11,
|
||||
"cm": 12,
|
||||
"gram": 8,
|
||||
"km/h": 8,
|
||||
"mV": 9,
|
||||
"times": 4, // exercise-count
|
||||
"kCal": 4,
|
||||
"mmHg": 4,
|
||||
"pcs": 3,
|
||||
"meter": 3,
|
||||
"kW": 2,
|
||||
"KByte/s": 2,
|
||||
"毫摩尔每升": 2, // blood-sugar, cholesterol
|
||||
"m3/h": 2,
|
||||
"ppb": 2,
|
||||
"mv": 2,
|
||||
"w": 1,
|
||||
"bar": 1,
|
||||
"megapascal": 1,
|
||||
"kB": 1,
|
||||
"mmol/L": 1, // urea
|
||||
"min/km": 1,
|
||||
"kilopascal": 1,
|
||||
"liter": 1,
|
||||
"cm": 3,
|
||||
"mA": 2,
|
||||
"kilogram": 2,
|
||||
"kcal/d": 2, // basal-metabolism
|
||||
"times": 1 // exercise-count
|
||||
"W": 1
|
||||
}
|
||||
"""
|
||||
unit_map = {
|
||||
@ -1082,6 +1090,49 @@ class MIoTServiceEntity(Entity):
|
||||
self.async_write_ha_state()
|
||||
return True
|
||||
|
||||
async def set_properties_async(
|
||||
self, set_properties_list: List[Dict[str, Any]],
|
||||
update_value: bool = True, write_ha_state: bool = True,
|
||||
) -> bool:
|
||||
# set_properties_list = [{'prop': Optional[MIoTSpecProperty],
|
||||
# 'value': Any}....]
|
||||
for set_property in set_properties_list:
|
||||
prop = set_property.get("prop")
|
||||
value = set_property.get("value")
|
||||
if not prop:
|
||||
raise RuntimeError(
|
||||
f'set property failed, property is None, '
|
||||
f'{self.entity_id}, {self.name}')
|
||||
value = prop.value_format(value)
|
||||
value = prop.value_precision(value)
|
||||
# 因为下面还有判断在这个循环里 所以这里要赋值回去
|
||||
set_property["value"] = value
|
||||
if prop not in self.entity_data.props:
|
||||
raise RuntimeError(
|
||||
f'set property failed, unknown property, '
|
||||
f'{self.entity_id}, {self.name}, {prop.name}')
|
||||
if not prop.writable:
|
||||
raise RuntimeError(
|
||||
f'set property failed, not writable, '
|
||||
f'{self.entity_id}, {self.name}, {prop.name}')
|
||||
try:
|
||||
await self.miot_device.miot_client.set_props_async([{
|
||||
"did": self.miot_device.did,
|
||||
"siid": set_property["prop"].service.iid,
|
||||
"piid": set_property["prop"].iid,
|
||||
"value": set_property["value"],
|
||||
} for set_property in set_properties_list])
|
||||
except MIoTClientError as e:
|
||||
raise RuntimeError(
|
||||
f"{e}, {self.entity_id}, {self.name}, {'&'.join([set_property['prop'].name for set_property in set_properties_list])}") from e
|
||||
if update_value:
|
||||
for set_property in set_properties_list:
|
||||
self._prop_value_map[
|
||||
set_property["prop"]] = set_property["value"]
|
||||
if write_ha_state:
|
||||
self.async_write_ha_state()
|
||||
return True
|
||||
|
||||
async def get_property_async(self, prop: MIoTSpecProperty) -> Any:
|
||||
if not prop:
|
||||
_LOGGER.error(
|
||||
|
||||
@ -58,7 +58,7 @@ import secrets
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
from typing import Any, Callable, Coroutine, Optional, final
|
||||
from typing import Any, Callable, Coroutine, Optional, final, Dict, List
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
@ -857,6 +857,29 @@ class MIoTLan:
|
||||
return result_obj
|
||||
raise MIoTError('Invalid result', MIoTErrorCode.CODE_INTERNAL_ERROR)
|
||||
|
||||
@final
|
||||
async def set_props_async(
|
||||
self,did: str,props_list: List[Dict[str, Any]],
|
||||
timeout_ms: int = 10000) -> dict:
|
||||
# props_list = [{'did': did, 'siid': siid, 'piid': piid, 'value': value}......]
|
||||
self.__assert_service_ready()
|
||||
result_obj = await self.__call_api_async(
|
||||
did=did, msg={
|
||||
'method': 'set_properties',
|
||||
'params': props_list,
|
||||
}, timeout_ms=timeout_ms)
|
||||
if result_obj:
|
||||
if (
|
||||
'result' in result_obj and
|
||||
len(result_obj['result']) == len(props_list)
|
||||
and result_obj['result'][0].get('did') == did
|
||||
and all('code' in item for item in result_obj['result'])
|
||||
):
|
||||
return result_obj['result']
|
||||
if 'code' in result_obj:
|
||||
return result_obj
|
||||
raise MIoTError('Invalid result', MIoTErrorCode.CODE_INTERNAL_ERROR)
|
||||
|
||||
@final
|
||||
async def action_async(
|
||||
self, did: str, siid: int, aiid: int, in_list: list,
|
||||
|
||||
@ -56,7 +56,7 @@ import threading
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from typing import Any, Callable, Optional, final, Coroutine
|
||||
from typing import Any, Callable, Optional, final, Coroutine, Dict, List
|
||||
|
||||
from paho.mqtt.client import (
|
||||
MQTT_ERR_SUCCESS,
|
||||
@ -1342,6 +1342,41 @@ class MipsLocalClient(_MipsClient):
|
||||
'code': MIoTErrorCode.CODE_INTERNAL_ERROR.value,
|
||||
'message': 'Invalid result'}
|
||||
|
||||
@final
|
||||
async def set_props_async(
|
||||
self, did: str, props_list: List[Dict[str, Any]],
|
||||
timeout_ms: int = 10000
|
||||
) -> dict:
|
||||
# props_list= [{
|
||||
# 'did': did,
|
||||
# 'siid': siid,
|
||||
# 'piid': piid,
|
||||
# 'value': value
|
||||
# }]
|
||||
payload_obj: dict = {
|
||||
"did": did,
|
||||
"rpc": {
|
||||
"id": self.__gen_mips_id,
|
||||
"method": "set_properties",
|
||||
"params": props_list,
|
||||
}
|
||||
}
|
||||
result_obj = await self.__request_async(
|
||||
topic="proxy/rpcReq",
|
||||
payload=json.dumps(payload_obj),
|
||||
timeout_ms=timeout_ms)
|
||||
if result_obj:
|
||||
if ("result" in result_obj and
|
||||
len(result_obj["result"]) == len(props_list) and
|
||||
result_obj["result"][0].get("did") == did and
|
||||
all("code" in item for item in result_obj["result"])):
|
||||
return result_obj["result"]
|
||||
if "error" in result_obj:
|
||||
return result_obj["error"]
|
||||
return {
|
||||
'code': MIoTErrorCode.CODE_INTERNAL_ERROR.value,
|
||||
'message': 'Invalid result'}
|
||||
|
||||
@final
|
||||
async def action_async(
|
||||
self, did: str, siid: int, aiid: int, in_list: list,
|
||||
|
||||
@ -54,6 +54,9 @@ urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c35:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c35:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c35:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-h09h00:4:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-h40h00:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
@ -499,6 +502,26 @@ urn:miot-spec-v2:device:safe-box:0000A042:loock-v1:1:
|
||||
prop.5.1:
|
||||
name: contact-state
|
||||
expr: (src_value!=1)
|
||||
urn:miot-spec-v2:device:speaker:0000A015:xiaomi-l04m:2:
|
||||
prop.3.1:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
urn:miot-spec-v2:device:speaker:0000A015:xiaomi-lx04:2:
|
||||
prop.3.1:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
urn:miot-spec-v2:device:speaker:0000A015:xiaomi-lx06:2:
|
||||
prop.3.1:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
urn:miot-spec-v2:device:speaker:0000A015:xiaomi-x08c:2:
|
||||
prop.2.1:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
urn:miot-spec-v2:device:speaker:0000A015:xiaomi-x08e:1:
|
||||
prop.3.1:
|
||||
name: playing-state-a
|
||||
|
||||
@ -52,6 +52,8 @@ 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
|
||||
@ -75,6 +77,17 @@ async def async_setup_entry(
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
|
||||
# create select for light
|
||||
new_light_select_entities = []
|
||||
for miot_device in device_list:
|
||||
# Add it to all devices with light entities, because some bathroom heaters and clothes drying racks also have lights.
|
||||
# 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]
|
||||
new_light_select_entities.append(
|
||||
LightCommandSendMode(hass=hass, 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."""
|
||||
@ -94,3 +107,38 @@ class Select(MIoTPropertyEntity, SelectEntity):
|
||||
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, device_id: str):
|
||||
super().__init__()
|
||||
self.hass = hass
|
||||
self._device_id = device_id
|
||||
self._attr_name = "Command Send Mode"
|
||||
self.entity_id = f"select.light_{device_id}_command_send_mode"
|
||||
self._attr_unique_id = self.entity_id
|
||||
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
|
||||
|
||||
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):
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user