mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2026-01-12 03:40:43 +08:00
Compare commits
7 Commits
c8be8552ae
...
10dd8ffae1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10dd8ffae1 | ||
|
|
593d7d3c0a | ||
|
|
77cebb2e15 | ||
|
|
3925e12863 | ||
|
|
500ed76971 | ||
|
|
506bd9f52e | ||
|
|
7c0caa9df7 |
@ -91,13 +91,13 @@ Xiaomi Home Integration and the affiliated cloud interface is provided by Xiaomi
|
||||
|
||||
Yes, it supports multiple Xiaomi accounts. Furthermore, Xiaomi Home Integration allows that devices belonging to different accounts can be added to a same area.
|
||||
|
||||
- Does Xiaomi Home Integration support local control?
|
||||
- Does Xiaomi Home Integration support local mode?
|
||||
|
||||
Local control is implemented by [Xiaomi Central Hub Gateway](https://www.mi.com/shop/buy/detail?product_id=15755&cfrom=search) (firmware version 3.3.0_0023 and above) or Xiaomi smart devices with built-in central hub gateway (software version 0.8.9 and above) inside. If you do not have a Xiaomi central hub gateway or other devices having central hub gateway function, all control commands are sent through Xiaomi Cloud. The firmware for Xiaomi central hub gateway including the built-in central hub gateway supporting Home Assistant local control feature has not been released yet. Please refer to MIoT team's notification for upgrade plans.
|
||||
Local mode is implemented by [Xiaomi Central Hub Gateway](https://www.mi.com/shop/buy/detail?product_id=15755&cfrom=search) (firmware version 3.3.0_0023 and above) or Xiaomi smart devices with [built-in central hub gateway](https://github.com/XiaoMi/ha_xiaomi_home/wiki/Central-hub-gateway-device-models) (software version 0.8.9 and above) inside. If you do not have a Xiaomi central hub gateway or other devices having central hub gateway function, all control commands are sent through Xiaomi Cloud. The firmware for Xiaomi central hub gateway including the built-in central hub gateway supporting Home Assistant local mode feature has been released.
|
||||
|
||||
Xiaomi central hub gateway is only available in mainland China. In other regions, it is not available.
|
||||
|
||||
Xiaomi Home Integration can also implement partial local control by enabling Xiaomi LAN control function. Xiaomi LAN control function can only control IP devices (devices connected to the router via WiFi or ethernet cable) in the same local area network as Home Assistant. It cannot control BLE Mesh, ZigBee, etc. devices. This function may cause some abnormalities. We recommend not to use this function. Xiaomi LAN control function is enabled by [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > CONFIGURE > Update LAN control configuration
|
||||
Xiaomi Home Integration can also implement partial local mode by enabling Xiaomi LAN control function. Xiaomi LAN control function can only control IP devices (devices connected to the router via WiFi or ethernet cable) in the same local area network as Home Assistant. It cannot control BLE Mesh, ZigBee, etc. devices. This function may cause some abnormalities. We recommend NOT using this function. Xiaomi LAN control function is enabled by [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > CONFIGURE > Update LAN control configuration.
|
||||
|
||||
Xiaomi LAN control function is not restricted by region. It is available in all regions. However, if there is a central gateway in the local area network where Home Assistant is located, even Xiaomi LAN control function is enabled in the integration, it will not take effect.
|
||||
|
||||
|
||||
@ -349,3 +349,101 @@ async def async_remove_config_entry_device(
|
||||
_LOGGER.info(
|
||||
'remove device, %s, %s', identifiers[1], device_entry.id)
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
"""Migrate old entry."""
|
||||
_LOGGER.debug(
|
||||
'Migrating configuration from version %s.%s',
|
||||
config_entry.version,
|
||||
config_entry.minor_version,
|
||||
)
|
||||
|
||||
if config_entry.version > 1:
|
||||
# This means the user has downgraded from a future version
|
||||
return False
|
||||
|
||||
if config_entry.version == 1:
|
||||
await _migrate_v1_to_v2(hass, config_entry)
|
||||
|
||||
_LOGGER.debug(
|
||||
'Migration to configuration version %s.%s successful',
|
||||
config_entry.version,
|
||||
config_entry.minor_version,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def _migrate_v1_to_v2(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
def ha_persistent_notify(
|
||||
notify_id: str, title: Optional[str] = None,
|
||||
message: Optional[str] = None
|
||||
) -> None:
|
||||
"""Send messages in Notifications dialog box."""
|
||||
if title:
|
||||
persistent_notification.async_create(
|
||||
hass=hass, message=message or '',
|
||||
title=title, notification_id=notify_id)
|
||||
else:
|
||||
persistent_notification.async_dismiss(
|
||||
hass=hass, notification_id=notify_id)
|
||||
|
||||
entry_id = config_entry.entry_id
|
||||
entry_data = dict(config_entry.data)
|
||||
|
||||
ha_persistent_notify(
|
||||
notify_id=f'{entry_id}.oauth_error', title=None, message=None)
|
||||
|
||||
miot_client: MIoTClient = await get_miot_instance_async(
|
||||
hass=hass, entry_id=entry_id,
|
||||
entry_data=entry_data,
|
||||
persistent_notify=ha_persistent_notify)
|
||||
# Spec parser
|
||||
spec_parser = MIoTSpecParser(
|
||||
lang=entry_data.get(
|
||||
'integration_language', DEFAULT_INTEGRATION_LANGUAGE),
|
||||
storage=miot_client.miot_storage,
|
||||
loop=miot_client.main_loop
|
||||
)
|
||||
await spec_parser.init_async()
|
||||
# Manufacturer
|
||||
manufacturer: DeviceManufacturer = DeviceManufacturer(
|
||||
storage=miot_client.miot_storage,
|
||||
loop=miot_client.main_loop)
|
||||
await manufacturer.init_async()
|
||||
er = entity_registry.async_get(hass)
|
||||
for _, info in miot_client.device_list.items():
|
||||
spec_instance = await spec_parser.parse(urn=info['urn'])
|
||||
if not isinstance(spec_instance, MIoTSpecInstance):
|
||||
continue
|
||||
device: MIoTDevice = MIoTDevice(
|
||||
miot_client=miot_client,
|
||||
device_info={
|
||||
**info, 'manufacturer': manufacturer.get_name(
|
||||
info.get('manufacturer', ''))},
|
||||
spec_instance=spec_instance)
|
||||
device.spec_transform()
|
||||
|
||||
# Update unique_id
|
||||
for platform, entities in device.entity_list.items():
|
||||
for entity in entities:
|
||||
if not isinstance(entity.spec, MIoTSpecService):
|
||||
continue
|
||||
old_unique_id = device.gen_service_entity_id_v1(
|
||||
ha_domain=DOMAIN,
|
||||
siid=entity.spec.iid,
|
||||
)
|
||||
entity_id = er.async_get_entity_id(
|
||||
platform, DOMAIN, old_unique_id
|
||||
)
|
||||
if entity_id is None:
|
||||
continue
|
||||
new_unique_id = device.gen_service_entity_id(
|
||||
ha_domain=DOMAIN,
|
||||
siid=entity.spec.iid,
|
||||
description=entity.spec.description,
|
||||
)
|
||||
er.async_update_entity(entity_id, new_unique_id=new_unique_id)
|
||||
|
||||
hass.config_entries.async_update_entry(config_entry, version=2)
|
||||
|
||||
@ -109,7 +109,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Xiaomi Home config flow."""
|
||||
# pylint: disable=unused-argument, inconsistent-quotes
|
||||
VERSION = 1
|
||||
VERSION = 2
|
||||
MINOR_VERSION = 1
|
||||
DEFAULT_AREA_NAME_RULE = 'room'
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
|
||||
@ -430,15 +430,16 @@ class FeatureState(MIoTServiceEntity, MediaPlayerEntity):
|
||||
elif item.name in {'pause', 'paused'}:
|
||||
self._playing_state_map[
|
||||
item.value] = MediaPlayerState.PAUSED
|
||||
self._prop_playing_state = prop
|
||||
self._prop_playing_state = prop
|
||||
|
||||
@property
|
||||
def state(self) -> Optional[MediaPlayerState]:
|
||||
"""The current state."""
|
||||
return (self.get_map_value(map_=self._playing_state_map,
|
||||
key=self.get_prop_value(
|
||||
prop=self._prop_playing_state))
|
||||
if self._prop_playing_state else MediaPlayerState.ON)
|
||||
current_state = self.get_prop_value(
|
||||
prop=self._prop_playing_state) if self._prop_playing_state else None
|
||||
return (MediaPlayerState.ON if
|
||||
(current_state is None) else self.get_map_value(
|
||||
map_=self._playing_state_map, key=current_state))
|
||||
|
||||
|
||||
class WifiSpeaker(FeatureVolumeSet, FeatureVolumeMute, FeaturePlay,
|
||||
|
||||
@ -79,6 +79,12 @@ from .miot_i18n import MIoTI18n
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
REFRESH_PROPS_DELAY = 0.2
|
||||
REFRESH_PROPS_RETRY_DELAY = 3
|
||||
REFRESH_CLOUD_DEVICES_DELAY = 6
|
||||
REFRESH_CLOUD_DEVICES_RETRY_DELAY = 60
|
||||
REFRESH_GATEWAY_DEVICES_DELAY = 3
|
||||
|
||||
@dataclass
|
||||
class MIoTClientSub:
|
||||
"""MIoT client subscription."""
|
||||
@ -717,7 +723,7 @@ class MIoTClient:
|
||||
if self._refresh_props_timer:
|
||||
return
|
||||
self._refresh_props_timer = self._main_loop.call_later(
|
||||
0.2, lambda: self._main_loop.create_task(
|
||||
REFRESH_PROPS_DELAY, lambda: self._main_loop.create_task(
|
||||
self.__refresh_props_handler()))
|
||||
|
||||
async def get_prop_async(self, did: str, siid: int, piid: int) -> Any:
|
||||
@ -1433,9 +1439,19 @@ class MIoTClient:
|
||||
async def __refresh_cloud_devices_async(self) -> None:
|
||||
_LOGGER.debug(
|
||||
'refresh cloud devices, %s, %s', self._uid, self._cloud_server)
|
||||
self._refresh_cloud_devices_timer = None
|
||||
result = await self._http.get_devices_async(
|
||||
home_ids=list(self._entry_data.get('home_selected', {}).keys()))
|
||||
if self._refresh_cloud_devices_timer:
|
||||
self._refresh_cloud_devices_timer.cancel()
|
||||
self._refresh_cloud_devices_timer = None
|
||||
try:
|
||||
result = await self._http.get_devices_async(
|
||||
home_ids=list(self._entry_data.get('home_selected', {}).keys()))
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
_LOGGER.error('refresh cloud devices failed, %s', err)
|
||||
self._refresh_cloud_devices_timer = self._main_loop.call_later(
|
||||
REFRESH_CLOUD_DEVICES_RETRY_DELAY,
|
||||
lambda: self._main_loop.create_task(
|
||||
self.__refresh_cloud_devices_async()))
|
||||
return
|
||||
if not result and 'devices' not in result:
|
||||
self.__show_client_error_notify(
|
||||
message=self._i18n.translate(
|
||||
@ -1481,17 +1497,11 @@ class MIoTClient:
|
||||
_LOGGER.debug(
|
||||
'request refresh cloud devices, %s, %s',
|
||||
self._uid, self._cloud_server)
|
||||
if immediately:
|
||||
if self._refresh_cloud_devices_timer:
|
||||
self._refresh_cloud_devices_timer.cancel()
|
||||
self._refresh_cloud_devices_timer = self._main_loop.call_later(
|
||||
0, lambda: self._main_loop.create_task(
|
||||
self.__refresh_cloud_devices_async()))
|
||||
return
|
||||
delay_sec : int = 0 if immediately else REFRESH_CLOUD_DEVICES_DELAY
|
||||
if self._refresh_cloud_devices_timer:
|
||||
return
|
||||
self._refresh_cloud_devices_timer.cancel()
|
||||
self._refresh_cloud_devices_timer = self._main_loop.call_later(
|
||||
6, lambda: self._main_loop.create_task(
|
||||
delay_sec, lambda: self._main_loop.create_task(
|
||||
self.__refresh_cloud_devices_async()))
|
||||
|
||||
@final
|
||||
@ -1615,7 +1625,8 @@ class MIoTClient:
|
||||
return
|
||||
self._mips_local_state_changed_timers[group_id] = (
|
||||
self._main_loop.call_later(
|
||||
3, lambda: self._main_loop.create_task(
|
||||
REFRESH_GATEWAY_DEVICES_DELAY,
|
||||
lambda: self._main_loop.create_task(
|
||||
self.__refresh_gw_devices_with_group_id_async(
|
||||
group_id=group_id))))
|
||||
|
||||
@ -1769,7 +1780,7 @@ class MIoTClient:
|
||||
self._refresh_props_retry_count = 0
|
||||
if self._refresh_props_list:
|
||||
self._refresh_props_timer = self._main_loop.call_later(
|
||||
0.2, lambda: self._main_loop.create_task(
|
||||
REFRESH_PROPS_DELAY, lambda: self._main_loop.create_task(
|
||||
self.__refresh_props_handler()))
|
||||
else:
|
||||
self._refresh_props_timer = None
|
||||
@ -1788,7 +1799,7 @@ class MIoTClient:
|
||||
_LOGGER.info(
|
||||
'refresh props failed, retry, %s', self._refresh_props_retry_count)
|
||||
self._refresh_props_timer = self._main_loop.call_later(
|
||||
3, lambda: self._main_loop.create_task(
|
||||
REFRESH_PROPS_RETRY_DELAY, lambda: self._main_loop.create_task(
|
||||
self.__refresh_props_handler()))
|
||||
|
||||
@final
|
||||
|
||||
@ -345,6 +345,11 @@ class MIoTDevice:
|
||||
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
|
||||
f'{self._model_strs[-1][:20]}')
|
||||
|
||||
def gen_service_entity_id_v1(self, ha_domain: str, siid: int) -> str:
|
||||
return (
|
||||
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
|
||||
f'{self._model_strs[-1][:20]}_s_{siid}')
|
||||
|
||||
def gen_service_entity_id(self, ha_domain: str, siid: int,
|
||||
description: str) -> str:
|
||||
return (
|
||||
|
||||
@ -519,15 +519,12 @@ class _MipsClient(ABC):
|
||||
if not self._mqtt or not self._mqtt.is_connected():
|
||||
self.log_error(f'mips sub when not connected, {topic}')
|
||||
return
|
||||
try:
|
||||
if topic not in self._mips_sub_pending_map:
|
||||
self._mips_sub_pending_map[topic] = 0
|
||||
if not self._mips_sub_pending_timer:
|
||||
self._mips_sub_pending_timer = self._internal_loop.call_later(
|
||||
0.01, self.__mips_sub_internal_pending_handler, topic)
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
# Catch all exception
|
||||
self.log_error(f'mips sub internal error, {topic}. {err}')
|
||||
|
||||
if topic not in self._mips_sub_pending_map:
|
||||
self._mips_sub_pending_map[topic] = 0
|
||||
if not self._mips_sub_pending_timer:
|
||||
self._mips_sub_pending_timer = self._internal_loop.call_later(
|
||||
0.01, self.__mips_sub_internal_pending_handler, topic)
|
||||
|
||||
@final
|
||||
def _mips_unsub_internal(self, topic: str) -> None:
|
||||
@ -736,11 +733,16 @@ class _MipsClient(ABC):
|
||||
self.log_error(f'retry mips sub internal error, {topic}')
|
||||
continue
|
||||
subbed_count += 1
|
||||
result, mid = self._mqtt.subscribe(topic, qos=self.MIPS_QOS)
|
||||
if result == MQTT_ERR_SUCCESS:
|
||||
self._mips_sub_pending_map.pop(topic)
|
||||
self.log_debug(f'mips sub internal success, {topic}')
|
||||
continue
|
||||
result = mid = None
|
||||
try:
|
||||
result, mid = self._mqtt.subscribe(topic, qos=self.MIPS_QOS)
|
||||
if result == MQTT_ERR_SUCCESS:
|
||||
self._mips_sub_pending_map.pop(topic)
|
||||
self.log_debug(f'mips sub internal success, {topic}')
|
||||
continue
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
# Catch all exception
|
||||
self.log_error(f'mips sub internal error, {topic}. {err}')
|
||||
self._mips_sub_pending_map[topic] = count+1
|
||||
self.log_error(
|
||||
f'retry mips sub internal, {count}, {topic}, {result}, {mid}')
|
||||
|
||||
@ -291,7 +291,7 @@ class MIoTNetwork:
|
||||
return self._main_loop.time() - start_ts
|
||||
return self._DETECT_TIMEOUT
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
print(err)
|
||||
_LOGGER.debug('ping error, %s',err)
|
||||
return self._DETECT_TIMEOUT
|
||||
|
||||
async def __http_async(self, url: str) -> float:
|
||||
|
||||
@ -1,6 +1,16 @@
|
||||
urn:miot-spec-v2:device:air-condition-outlet:0000A045:lumi-mcn04:1:
|
||||
prop.3.4:
|
||||
format: uint8
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:daikin-k2:1:
|
||||
prop.2.1:
|
||||
name: ac-on
|
||||
format: string
|
||||
prop.2.2:
|
||||
name: ac-mode
|
||||
format: string
|
||||
prop.3.1:
|
||||
name: ac-fan-level
|
||||
format: string
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:qdhkl-a42:1:
|
||||
prop.2.2:
|
||||
value-list:
|
||||
@ -79,6 +89,16 @@ urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:1:
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:3: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:4: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:1
|
||||
urn:miot-spec-v2:device:air-fresh:0000A012:daikin-k33:1:
|
||||
prop.2.1:
|
||||
name: ac-on
|
||||
format: string
|
||||
prop.2.3:
|
||||
name: ac-mode
|
||||
format: string
|
||||
prop.2.5:
|
||||
name: ac-fan-level
|
||||
format: string
|
||||
urn:miot-spec-v2:device:air-monitor:0000A008:cgllc-cgd1st:1:
|
||||
prop.3.7:
|
||||
value-range:
|
||||
@ -461,6 +481,9 @@ 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-x08e:1:
|
||||
prop.3.1:
|
||||
name: playing-state-a
|
||||
urn:miot-spec-v2:device:switch:0000A003:090615-x1tpm:1:0000D042:
|
||||
prop.27.3:
|
||||
name: light-on
|
||||
|
||||
@ -325,10 +325,12 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
},
|
||||
'play-control': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'playing-state': {'read'}
|
||||
},
|
||||
'actions': {'play'}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'playing-state'},
|
||||
'actions': {'pause', 'stop', 'next', 'previous'}
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,11 +93,11 @@ git checkout v1.0.0
|
||||
|
||||
- 米家集成是否支持本地化控制?
|
||||
|
||||
米家集成支持通过[小米中枢网关](https://www.mi.com/shop/buy/detail?product_id=15755&cfrom=search)(固件版本 3.3.0_0023 及以上)或内置中枢网关(软件版本 0.8.9 及以上)的米家设备实现本地化控制。如果没有小米中枢网关或其他带中枢网关功能的设备,那么所有控制指令都会通过小米云发送。支持 Home Assistant 本地化控制的小米中枢网关(含内置中枢网关)的固件尚未发布,固件升级计划请参阅 MIoT 团队的通知。
|
||||
米家集成支持通过[小米中枢网关](https://www.mi.com/shop/buy/detail?product_id=15755&cfrom=search)(固件版本 3.3.0_0023 及以上)或[内置中枢网关](https://github.com/XiaoMi/ha_xiaomi_home/wiki/Central-hub-gateway-device-models)(软件版本 0.8.9 及以上)的米家设备实现本地化控制。如果没有小米中枢网关或其他带中枢网关功能的设备,那么所有控制指令都会通过小米云发送。支持 Home Assistant 本地化控制的小米中枢网关(含内置中枢网关)的固件已发布。
|
||||
|
||||
小米中枢网关仅在中国大陆可用,在其他地区不可用。
|
||||
|
||||
米家集成也能通过开启小米局域网控制功能实现部分本地化控制效果。小米局域网控制功能只能控制与 Home Assistant 处于同一局域网内的 IP 设备(使用 WiFi、网线连接路由器的设备),无法控制蓝牙 Mesh、ZigBee 等协议接入的设备。该功能可能会引起一些异常,我们建议不要使用该功能。小米局域网控制功能开启方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 配置 > 更新局域网控制配置
|
||||
米家集成也能通过开启小米局域网控制功能实现部分本地化控制效果。小米局域网控制功能只能控制与 Home Assistant 处于同一局域网内的 IP 设备(使用 WiFi、网线连接路由器的设备),无法控制蓝牙 Mesh、ZigBee 等协议接入的设备。该功能可能会引起一些异常,我们建议不要使用该功能。小米局域网控制功能开启方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 配置 > 更新局域网控制配置。
|
||||
|
||||
小米局域网控制功能不受地区限制,在全球范围内均可用。如果 Home Assistant 所在的局域网内存在中枢网关,那么即便米家集成开启了小米局域网控制功能,该功能也不会生效。
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user