diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fc5f02..9af7631 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,20 @@ # CHANGELOG +## v0.4.0 +### Added +- Add the watch as the device tracker entity. [#1189](https://github.com/XiaoMi/ha_xiaomi_home/pull/1189) +- Add the wifi speaker and the television as the media player entity. [#706](https://github.com/XiaoMi/ha_xiaomi_home/pull/706) +- Add an option in CONFIGURE to set the cover closed position. [#1242](https://github.com/XiaoMi/ha_xiaomi_home/pull/1242) +- Add notifications to show the status of the local connection to the central hub gateway. [#1280](https://github.com/XiaoMi/ha_xiaomi_home/pull/1280) +- Import the device from the third party cloud. [#1258](https://github.com/XiaoMi/ha_xiaomi_home/pull/1258) +### Changed +- Add an alongside switch entity for viomi.waterheater.m1. [#1255](https://github.com/XiaoMi/ha_xiaomi_home/pull/1255) +- Do not subscribe BLE device online/offline state message. [#1264](https://github.com/XiaoMi/ha_xiaomi_home/pull/1264) +### Fixed +- Keep the first element of the discovered ip address list as the recently added address when getting mdns result. [#1250](https://github.com/XiaoMi/ha_xiaomi_home/pull/1250) +- Subscribe local topics every time when connected to the central hub gateway. [#1266](https://github.com/XiaoMi/ha_xiaomi_home/pull/1266) +- Record the "closing" and "closed" status that occur frequently in the motor-controller, the window-opener and the curtain service. [#1262](https://github.com/XiaoMi/ha_xiaomi_home/pull/1262) +- Fix xiaomi.aircondition.c24 total power consumption unit, adp.motor.adswb4 motor switch, cgllc.airm.cgd1st environment temperature, and shhf.light.sflt11 fan switch status. [#1256](https://github.com/XiaoMi/ha_xiaomi_home/pull/1256) + ## v0.3.4 ### Added - Exclude the unsupported device models. [#1205](https://github.com/XiaoMi/ha_xiaomi_home/pull/1205) diff --git a/README.md b/README.md index c385c94..862684c 100644 --- a/README.md +++ b/README.md @@ -83,9 +83,9 @@ Xiaomi Home Integration and the affiliated cloud interface is provided by Xiaomi ## FAQ -- Does Xiaomi Home Integration support all Xiaomi Home devices? +- Does Xiaomi Home Integration support all Xiaomi smart devices? - Xiaomi Home Integration currently supports most categories of Home device. Only a few categories are not supported. They are Bluetooth device, infrared device and virtual device. + Xiaomi Home Integration currently supports most categories of the smart device. Only a few categories are not supported. They are Bluetooth device, infrared device and virtual device. - Does Xiaomi Home Integration support multiple Xiaomi accounts? @@ -93,7 +93,7 @@ Xiaomi Home Integration and the affiliated cloud interface is provided by Xiaomi - Does Xiaomi Home Integration support local control? - Local control is implemented by [Xiaomi Central Hub Gateway](https://www.mi.com/shop/buy/detail?product_id=15755&cfrom=search) (firmware version 3.4.0_0000 above) or Xiaomi home devices with built-in central hub gateway (software version 0.8.0 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 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. Xiaomi central hub gateway is only available in mainland China. In other regions, it is not available. @@ -293,7 +293,7 @@ The value of the event instance name indicates `_attr_device_class` of the Home `spec_filter.yaml` is used to filter out the MIoT-Spec-V2 instance that will not be converted to Home Assistant entity. -The format of `spec_filter.json` is as follows. +The format of `spec_filter.yaml` is as follows. ```yaml : diff --git a/custom_components/xiaomi_home/config_flow.py b/custom_components/xiaomi_home/config_flow.py index 9d89a06..8b72ac4 100644 --- a/custom_components/xiaomi_home/config_flow.py +++ b/custom_components/xiaomi_home/config_flow.py @@ -75,6 +75,9 @@ from .miot.const import ( DEFAULT_CLOUD_SERVER, DEFAULT_CTRL_MODE, DEFAULT_INTEGRATION_LANGUAGE, + DEFAULT_COVER_CLOSED_POSITION, + MIN_COVER_CLOSED_POSITION, + MAX_COVER_CLOSED_POSITION, DEFAULT_NICK_NAME, DEFAULT_OAUTH2_API_HOST, DOMAIN, @@ -129,6 +132,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _cloud_server: str _integration_language: str + _cover_closed_position: int _auth_info: dict _nick_name: str _home_selected: dict @@ -151,6 +155,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._main_loop = asyncio.get_running_loop() self._cloud_server = DEFAULT_CLOUD_SERVER self._integration_language = DEFAULT_INTEGRATION_LANGUAGE + self._cover_closed_position = DEFAULT_COVER_CLOSED_POSITION self._storage_path = '' self._virtual_did = '' self._uid = '' @@ -951,6 +956,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 'action_debug': self._action_debug, 'hide_non_standard_entities': self._hide_non_standard_entities, + 'cover_closed_position': self._cover_closed_position, 'display_binary_mode': self._display_binary_mode, 'display_devices_changed_notify': self._display_devices_changed_notify @@ -995,6 +1001,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): _hide_non_standard_entities: bool _display_binary_mode: list[str] _display_devs_notify: list[str] + _cover_closed_position: int _oauth_redirect_url_full: str _auth_info: dict @@ -1015,6 +1022,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): _opt_lan_ctrl_cfg: bool _opt_network_detect_cfg: bool _opt_check_network_deps: bool + _cover_pos_new: int _trans_rules_count: int _trans_rules_count_success: int @@ -1043,6 +1051,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow): self._ctrl_mode = self._entry_data.get('ctrl_mode', DEFAULT_CTRL_MODE) self._integration_language = self._entry_data.get( 'integration_language', DEFAULT_INTEGRATION_LANGUAGE) + self._cover_closed_position = self._entry_data.get( + 'cover_closed_position', DEFAULT_COVER_CLOSED_POSITION) self._nick_name = self._entry_data.get('nick_name', DEFAULT_NICK_NAME) self._action_debug = self._entry_data.get('action_debug', False) self._hide_non_standard_entities = self._entry_data.get( @@ -1068,6 +1078,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): self._action_debug_new = False self._hide_non_standard_entities_new = False self._display_binary_mode_new = [] + self._cover_pos_new = self._cover_closed_position self._update_user_info = False self._update_devices = False self._update_trans_rules = False @@ -1340,6 +1351,12 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ): cv.multi_select( self._miot_i18n.translate( 'config.binary_mode')), # type: ignore + vol.Optional( + 'cover_closed_position', + default=self._cover_closed_position # type: ignore + ): vol.All(vol.Coerce(int), vol.Range( + min=MIN_COVER_CLOSED_POSITION, + max=MAX_COVER_CLOSED_POSITION)), vol.Required( 'update_trans_rules', default=self._update_trans_rules # type: ignore @@ -1378,6 +1395,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow): 'update_lan_ctrl_config', self._opt_lan_ctrl_cfg) self._opt_network_detect_cfg = user_input.get( 'network_detect_config', self._opt_network_detect_cfg) + self._cover_pos_new = user_input.get( + 'cover_closed_position', self._cover_closed_position) return await self.async_step_update_user_info() @@ -1926,6 +1945,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): 'nick_name': self._nick_name, 'lang_new': INTEGRATION_LANGUAGES[self._lang_new], 'nick_name_new': self._nick_name_new, + 'cover_pos_new': self._cover_pos_new, 'devices_add': len(self._devices_add), 'devices_remove': len(self._devices_remove), 'trans_rules_count': self._trans_rules_count, @@ -1952,6 +1972,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow): if self._lang_new != self._integration_language: self._entry_data['integration_language'] = self._lang_new self._need_reload = True + if self._cover_pos_new != self._cover_closed_position: + self._entry_data['cover_closed_position'] = self._cover_pos_new + self._need_reload = True if self._update_user_info: self._entry_data['nick_name'] = self._nick_name_new if self._update_devices: diff --git a/custom_components/xiaomi_home/cover.py b/custom_components/xiaomi_home/cover.py index 1116928..e03be9a 100644 --- a/custom_components/xiaomi_home/cover.py +++ b/custom_components/xiaomi_home/cover.py @@ -91,6 +91,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, class Cover(MIoTServiceEntity, CoverEntity): """Cover entities for Xiaomi Home.""" # pylint: disable=unused-argument + _cover_closed_position: int _prop_motor_control: Optional[MIoTSpecProperty] _prop_motor_value_open: Optional[int] _prop_motor_value_close: Optional[int] @@ -115,6 +116,9 @@ class Cover(MIoTServiceEntity, CoverEntity): self._attr_supported_color_modes = set() self._attr_supported_features = CoverEntityFeature(0) + self._cover_closed_position = ( + miot_device.miot_client.cover_closed_position) + self._prop_motor_control = None self._prop_motor_value_open = None self._prop_motor_value_close = None @@ -166,12 +170,13 @@ class Cover(MIoTServiceEntity, CoverEntity): self._prop_status_opening.append(item.value) elif item_name in { 'closing', 'close', 'down', 'dowm', 'falling', - 'dropping', 'downing', 'lower' + 'fallin', 'dropping', 'downing', 'lower' }: self._prop_status_closing.append(item.value) elif item_name in { - 'stopatlowest', 'stoplowerlimit', 'lowerlimitstop', - 'floor', 'lowerlimit' + 'closed', 'closeover', 'stopatlowest', + 'stoplowerlimit', 'lowerlimitstop', 'floor', + 'lowerlimit' }: self._prop_status_closed.append(item.value) self._prop_status = prop @@ -297,7 +302,7 @@ class Cover(MIoTServiceEntity, CoverEntity): def is_closed(self) -> Optional[bool]: """Return if the cover is closed.""" if self.current_cover_position is not None: - return self.current_cover_position == 0 + return self.current_cover_position <= self._cover_closed_position # The current position is prior to the status when determining # whether the cover is closed. if self._prop_status and self._prop_status_closed: diff --git a/custom_components/xiaomi_home/device_tracker.py b/custom_components/xiaomi_home/device_tracker.py new file mode 100644 index 0000000..ad2f4c9 --- /dev/null +++ b/custom_components/xiaomi_home/device_tracker.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2024 Xiaomi Corporation. + +The ownership and intellectual property rights of Xiaomi Home Assistant +Integration and related Xiaomi cloud service API interface provided under this +license, including source code and object code (collectively, "Licensed Work"), +are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi +hereby grants you a personal, limited, non-exclusive, non-transferable, +non-sublicensable, and royalty-free license to reproduce, use, modify, and +distribute the Licensed Work only for your use of Home Assistant for +non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize +you to use the Licensed Work for any other purpose, including but not limited +to use Licensed Work to develop applications (APP), Web services, and other +forms of software. + +You may reproduce and distribute copies of the Licensed Work, with or without +modifications, whether in source or object form, provided that you must give +any other recipients of the Licensed Work a copy of this License and retain all +copyright and disclaimers. + +Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied, including, without +limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR +OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or +FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible +for any direct, indirect, special, incidental, or consequential damages or +losses arising from the use or inability to use the Licensed Work. + +Xiaomi reserves all rights not expressly granted to you in this License. +Except for the rights expressly granted by Xiaomi under this License, Xiaomi +does not authorize you in any form to use the trademarks, copyrights, or other +forms of intellectual property rights of Xiaomi and its affiliates, including, +without limitation, without obtaining other written permission from Xiaomi, you +shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that +may make the public associate with Xiaomi in any form to publicize or promote +the software or hardware devices that use the Licensed Work. + +Xiaomi has the right to immediately terminate all your authorization under this +License in the event: +1. You assert patent invalidation, litigation, or other claims against patents +or other intellectual property rights of Xiaomi or its affiliates; or, +2. You make, have made, manufacture, sell, or offer to sell products that knock +off Xiaomi or its affiliates' products. + +Device tracker entities for Xiaomi Home. +""" +from __future__ import annotations +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.device_tracker import TrackerEntity + +from .miot.const import DOMAIN +from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData +from .miot.miot_spec import MIoTSpecProperty + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + 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('device_tracker', []): + new_entities.append( + DeviceTracker(miot_device=miot_device, entity_data=data)) + if new_entities: + async_add_entities(new_entities) + + +class DeviceTracker(MIoTServiceEntity, TrackerEntity): + """Tracker entities for Xiaomi Home.""" + _prop_battery_level: Optional[MIoTSpecProperty] + _prop_latitude: Optional[MIoTSpecProperty] + _prop_longitude: Optional[MIoTSpecProperty] + _prop_area_id: Optional[MIoTSpecProperty] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + super().__init__(miot_device=miot_device, entity_data=entity_data) + self._prop_battery_level = None + self._prop_latitude = None + self._prop_longitude = None + self._prop_area_id = None + + # properties + for prop in entity_data.props: + if prop.name == 'battery-level': + self._prop_battery_level = prop + elif prop.name == 'latitude': + self._prop_latitude = prop + elif prop.name == 'longitude': + self._prop_longitude = prop + elif prop.name == 'area-id': + self._prop_area_id = prop + + @property + def battery_level(self) -> Optional[int]: + """The battery level of the device.""" + return None if (self._prop_battery_level + is None) else self.get_prop_value( + prop=self._prop_battery_level) + + @property + def latitude(self) -> Optional[float]: + """The latitude coordinate of the device.""" + return None if self._prop_latitude is None else self.get_prop_value( + prop=self._prop_latitude) + + @property + def longitude(self) -> Optional[float]: + """The longitude coordinate of the device.""" + return None if self._prop_longitude is None else self.get_prop_value( + prop=self._prop_longitude) + + @property + def location_name(self) -> Optional[str]: + """The location name of the device.""" + return None if self._prop_area_id is None else self.get_prop_value( + prop=self._prop_area_id) diff --git a/custom_components/xiaomi_home/fan.py b/custom_components/xiaomi_home/fan.py index fa36035..0743cf3 100644 --- a/custom_components/xiaomi_home/fan.py +++ b/custom_components/xiaomi_home/fan.py @@ -221,7 +221,7 @@ class Fan(MIoTServiceEntity, FanEntity): # preset_mode if preset_mode: await self.set_property_async( - self._prop_mode, + prop=self._prop_mode, value=self.get_map_key( map_=self._mode_map, value=preset_mode)) @@ -258,7 +258,7 @@ class Fan(MIoTServiceEntity, FanEntity): async def async_set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode.""" await self.set_property_async( - self._prop_mode, + prop=self._prop_mode, value=self.get_map_key( map_=self._mode_map, value=preset_mode)) diff --git a/custom_components/xiaomi_home/manifest.json b/custom_components/xiaomi_home/manifest.json index 54c8708..a313a36 100644 --- a/custom_components/xiaomi_home/manifest.json +++ b/custom_components/xiaomi_home/manifest.json @@ -25,7 +25,7 @@ "cryptography", "psutil" ], - "version": "v0.3.4", + "version": "v0.4.0", "zeroconf": [ "_miot-central._tcp.local." ] diff --git a/custom_components/xiaomi_home/media_player.py b/custom_components/xiaomi_home/media_player.py new file mode 100644 index 0000000..f863d92 --- /dev/null +++ b/custom_components/xiaomi_home/media_player.py @@ -0,0 +1,470 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2024 Xiaomi Corporation. + +The ownership and intellectual property rights of Xiaomi Home Assistant +Integration and related Xiaomi cloud service API interface provided under this +license, including source code and object code (collectively, "Licensed Work"), +are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi +hereby grants you a personal, limited, non-exclusive, non-transferable, +non-sublicensable, and royalty-free license to reproduce, use, modify, and +distribute the Licensed Work only for your use of Home Assistant for +non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize +you to use the Licensed Work for any other purpose, including but not limited +to use Licensed Work to develop applications (APP), Web services, and other +forms of software. + +You may reproduce and distribute copies of the Licensed Work, with or without +modifications, whether in source or object form, provided that you must give +any other recipients of the Licensed Work a copy of this License and retain all +copyright and disclaimers. + +Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied, including, without +limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR +OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or +FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible +for any direct, indirect, special, incidental, or consequential damages or +losses arising from the use or inability to use the Licensed Work. + +Xiaomi reserves all rights not expressly granted to you in this License. +Except for the rights expressly granted by Xiaomi under this License, Xiaomi +does not authorize you in any form to use the trademarks, copyrights, or other +forms of intellectual property rights of Xiaomi and its affiliates, including, +without limitation, without obtaining other written permission from Xiaomi, you +shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that +may make the public associate with Xiaomi in any form to publicize or promote +the software or hardware devices that use the Licensed Work. + +Xiaomi has the right to immediately terminate all your authorization under this +License in the event: +1. You assert patent invalidation, litigation, or other claims against patents +or other intellectual property rights of Xiaomi or its affiliates; or, +2. You make, have made, manufacture, sell, or offer to sell products that knock +off Xiaomi or its affiliates' products. + +Media player 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.media_player import (MediaPlayerEntity, + MediaPlayerEntityFeature, + MediaPlayerDeviceClass, + MediaPlayerState, MediaType) + +from .miot.const import DOMAIN +from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData +from .miot.miot_spec import MIoTSpecProperty, MIoTSpecAction + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(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] + + new_entities = [] + for miot_device in device_list: + for data in miot_device.entity_list.get('wifi-speaker', []): + new_entities.append( + WifiSpeaker(miot_device=miot_device, entity_data=data)) + for data in miot_device.entity_list.get('television', []): + new_entities.append( + Television(miot_device=miot_device, entity_data=data)) + + if new_entities: + async_add_entities(new_entities) + + +class FeatureVolumeMute(MIoTServiceEntity, MediaPlayerEntity): + """VOLUME_MUTE feature of the media player entity.""" + _prop_mute: Optional[MIoTSpecProperty] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the feature class.""" + self._prop_mute = None + + super().__init__(miot_device=miot_device, entity_data=entity_data) + # properties + for prop in entity_data.props: + if prop.name == 'mute': + self._attr_supported_features |= ( + MediaPlayerEntityFeature.VOLUME_MUTE) + self._prop_mute = prop + + @property + def is_volume_muted(self) -> Optional[bool]: + """True if volume is currently muted.""" + return self.get_prop_value( + prop=self._prop_mute) if self._prop_mute else None + + async def async_mute_volume(self, mute: bool) -> None: + """Mute the volume.""" + await self.set_property_async(prop=self._prop_mute, value=mute) + + +class FeatureVolumeSet(MIoTServiceEntity, MediaPlayerEntity): + """VOLUME_SET feature of the media player entity.""" + _prop_volume: Optional[MIoTSpecProperty] + _volume_value_min: Optional[float] + _volume_value_max: Optional[float] + _volume_value_range: Optional[float] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the feature class.""" + self._prop_volume = None + self._volume_value_min = None + self._volume_value_max = None + self._volume_value_range = None + + super().__init__(miot_device=miot_device, entity_data=entity_data) + # properties + for prop in entity_data.props: + if prop.name == 'volume': + if not prop.value_range: + _LOGGER.error('invalid volume value_range format, %s', + self.entity_id) + continue + self._volume_value_min = prop.value_range.min_ + self._volume_value_max = prop.value_range.max_ + self._volume_value_range = (prop.value_range.max_ - + prop.value_range.min_) + self._attr_volume_step = (prop.value_range.step / + self._volume_value_range) + self._attr_supported_features |= ( + MediaPlayerEntityFeature.VOLUME_SET | + MediaPlayerEntityFeature.VOLUME_STEP) + self._prop_volume = prop + + async def async_set_volume_level(self, volume: float) -> None: + """Set volume level.""" + value = volume * self._volume_value_range + self._volume_value_min + if value > self._volume_value_max: + value = self._volume_value_max + elif value < self._volume_value_min: + value = self._volume_value_min + await self.set_property_async(prop=self._prop_volume, value=value) + + @property + def volume_level(self) -> Optional[float]: + """The current volume level, range [0, 1].""" + value = self.get_prop_value( + prop=self._prop_volume) if self._prop_volume else None + if value is None: + return None + return (value - self._volume_value_min) / self._volume_value_range + + +class FeaturePlay(MIoTServiceEntity, MediaPlayerEntity): + """PLAY feature of the media player entity.""" + _action_play: Optional[MIoTSpecAction] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the feature class.""" + self._action_play = None + + super().__init__(miot_device=miot_device, entity_data=entity_data) + # actions + for act in entity_data.actions: + if act.name == 'play': + self._attr_supported_features |= (MediaPlayerEntityFeature.PLAY) + self._action_play = act + + async def async_media_play(self) -> None: + """Send play command.""" + await self.action_async(action=self._action_play) + + +class FeaturePause(MIoTServiceEntity, MediaPlayerEntity): + """PAUSE feature of the media player entity.""" + _action_pause: Optional[MIoTSpecAction] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the feature class.""" + self._action_pause = None + + super().__init__(miot_device=miot_device, entity_data=entity_data) + # actions + for act in entity_data.actions: + if act.name == 'pause': + self._attr_supported_features |= ( + MediaPlayerEntityFeature.PAUSE) + self._action_pause = act + + async def async_media_pause(self) -> None: + """Send pause command.""" + await self.action_async(action=self._action_pause) + + +class FeatureStop(MIoTServiceEntity, MediaPlayerEntity): + """STOP feature of the media player entity.""" + _action_stop: Optional[MIoTSpecAction] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the feature class.""" + self._action_stop = None + + super().__init__(miot_device=miot_device, entity_data=entity_data) + # actions + for act in entity_data.actions: + if act.name == 'stop': + self._attr_supported_features |= (MediaPlayerEntityFeature.STOP) + self._action_stop = act + + async def async_media_stop(self) -> None: + """Send stop command.""" + await self.action_async(action=self._action_stop) + + +class FeatureNextTrack(MIoTServiceEntity, MediaPlayerEntity): + """NEXT_TRACK feature of the media player entity.""" + _action_next: Optional[MIoTSpecAction] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the feature class.""" + self._action_next = None + + super().__init__(miot_device=miot_device, entity_data=entity_data) + # actions + for act in entity_data.actions: + if act.name == 'next': + self._attr_supported_features |= ( + MediaPlayerEntityFeature.NEXT_TRACK) + self._action_next = act + + async def async_media_next_track(self) -> None: + """Send next track command.""" + await self.action_async(action=self._action_next) + + +class FeaturePreviousTrack(MIoTServiceEntity, MediaPlayerEntity): + """PREVIOUS_TRACK feature of the media player entity.""" + _action_previous: Optional[MIoTSpecAction] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the feature class.""" + self._action_previous = None + + super().__init__(miot_device=miot_device, entity_data=entity_data) + # actions + for act in entity_data.actions: + if act.name == 'previous': + self._attr_supported_features |= ( + MediaPlayerEntityFeature.PREVIOUS_TRACK) + self._action_previous = act + + async def async_media_previous_track(self) -> None: + """Send previous track command.""" + await self.action_async(action=self._action_previous) + + +class FeatureSoundMode(MIoTServiceEntity, MediaPlayerEntity): + """SELECT_SOUND_MODE feature of the media player entity.""" + _prop_play_loop_mode: Optional[MIoTSpecProperty] + _sound_mode_map: Optional[dict[int, str]] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the feature class.""" + self._prop_play_loop_mode = None + self._sound_mode_map = None + + super().__init__(miot_device=miot_device, entity_data=entity_data) + # properties + for prop in entity_data.props: + if prop.name == 'play-loop-mode': + if not prop.value_list: + _LOGGER.error('invalid play-loop-mode value_list, %s', + self.entity_id) + continue + self._sound_mode_map = prop.value_list.to_map() + self._attr_sound_mode_list = list(self._sound_mode_map.values()) + self._attr_supported_features |= ( + MediaPlayerEntityFeature.SELECT_SOUND_MODE) + self._prop_play_loop_mode = prop + + async def async_select_sound_mode(self, sound_mode: str): + """Switch the sound mode of the entity.""" + await self.set_property_async(prop=self._prop_play_loop_mode, + value=self.get_map_key( + map_=self._sound_mode_map, + value=sound_mode)) + + @property + def sound_mode(self) -> Optional[str]: + """The current sound mode.""" + return (self.get_map_value(map_=self._sound_mode_map, + key=self.get_prop_value( + prop=self._prop_play_loop_mode)) + if self._prop_play_loop_mode else None) + + +class FeatureTurnOn(MIoTServiceEntity, MediaPlayerEntity): + """TURN_ON feature of the media player entity.""" + _action_turn_on: Optional[MIoTSpecAction] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the feature class.""" + self._action_turn_on = None + + super().__init__(miot_device=miot_device, entity_data=entity_data) + # actions + for act in entity_data.actions: + if act.name == 'turn-on': + self._attr_supported_features |= ( + MediaPlayerEntityFeature.TURN_ON) + self._action_turn_on = act + + async def async_turn_on(self) -> None: + """Turn the media player on.""" + await self.action_async(action=self._action_turn_on) + + +class FeatureTurnOff(MIoTServiceEntity, MediaPlayerEntity): + """TURN_OFF feature of the media player entity.""" + _action_turn_off: Optional[MIoTSpecAction] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the feature class.""" + self._action_turn_off = None + + super().__init__(miot_device=miot_device, entity_data=entity_data) + # actions + for act in entity_data.actions: + if act.name == 'turn-off': + self._attr_supported_features |= ( + MediaPlayerEntityFeature.TURN_OFF) + self._action_turn_off = act + + async def async_turn_off(self) -> None: + """Turn the media player off.""" + await self.action_async(action=self._action_turn_off) + + +class FeatureSource(MIoTServiceEntity, MediaPlayerEntity): + """SELECT_SOURCE feature of the media player entity.""" + _prop_input_control: Optional[MIoTSpecProperty] + _input_source_map: Optional[dict[int, str]] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the feature class.""" + self._prop_input_control = None + self._input_source_map = None + + super().__init__(miot_device=miot_device, entity_data=entity_data) + # properties + for prop in entity_data.props: + if prop.name == 'input-control': + if not prop.value_list: + _LOGGER.error('invalid input-control value_list, %s', + self.entity_id) + continue + self._input_source_map = prop.value_list.to_map() + self._attr_source_list = list(self._input_source_map.values()) + self._attr_supported_features |= ( + MediaPlayerEntityFeature.SELECT_SOURCE) + self._prop_input_control = prop + + async def async_select_source(self, source: str) -> None: + """Select input source.""" + await self.set_property_async(prop=self._prop_input_control, + value=self.get_map_key( + map_=self._input_source_map, + value=source)) + + @property + def source(self) -> Optional[str]: + """The current input source.""" + return (self.get_map_value(map_=self._input_source_map, + key=self.get_prop_value( + prop=self._prop_input_control)) + if self._prop_input_control else None) + + +class FeatureState(MIoTServiceEntity, MediaPlayerEntity): + """States feature of the media player entity.""" + _prop_playing_state: Optional[MIoTSpecProperty] + _playing_state_map: Optional[dict[int, str]] + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the feature class.""" + self._prop_playing_state = None + self._playing_state_map = None + + super().__init__(miot_device=miot_device, entity_data=entity_data) + # properties + for prop in entity_data.props: + if prop.name == 'playing-state': + if not prop.value_list: + _LOGGER.error('invalid mode value_list, %s', self.entity_id) + continue + self._playing_state_map = {} + for item in prop.value_list.items: + if item.name in {'off'}: + self._playing_state_map[ + item.value] = MediaPlayerState.OFF + elif item.name in {'idle', 'stop', 'stopped'}: + self._playing_state_map[ + item.value] = MediaPlayerState.IDLE + elif item.name in {'playing'}: + self._playing_state_map[ + item.value] = MediaPlayerState.PLAYING + elif item.name in {'pause', 'paused'}: + self._playing_state_map[ + item.value] = MediaPlayerState.PAUSED + 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) + + +class WifiSpeaker(FeatureVolumeSet, FeatureVolumeMute, FeaturePlay, + FeaturePause, FeatureStop, FeatureNextTrack, + FeaturePreviousTrack, FeatureSoundMode, FeatureState): + """WiFi speaker, aka XiaoAI sound speaker.""" + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the device.""" + super().__init__(miot_device=miot_device, entity_data=entity_data) + + self._attr_device_class = MediaPlayerDeviceClass.SPEAKER + self._attr_media_content_type = MediaType.MUSIC + + +class Television(FeatureVolumeSet, FeatureVolumeMute, FeaturePlay, FeaturePause, + FeatureStop, FeatureNextTrack, FeaturePreviousTrack, + FeatureSoundMode, FeatureState, FeatureSource, FeatureTurnOn, + FeatureTurnOff): + """Television""" + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: + """Initialize the device.""" + super().__init__(miot_device=miot_device, entity_data=entity_data) + + self._attr_device_class = MediaPlayerDeviceClass.TV + self._attr_media_content_type = MediaType.VIDEO diff --git a/custom_components/xiaomi_home/miot/const.py b/custom_components/xiaomi_home/miot/const.py index ba356b8..37eecbc 100644 --- a/custom_components/xiaomi_home/miot/const.py +++ b/custom_components/xiaomi_home/miot/const.py @@ -71,10 +71,12 @@ SUPPORTED_PLATFORMS: list = [ 'button', 'climate', 'cover', + 'device_tracker', 'event', 'fan', 'humidifier', 'light', + 'media_player', 'notify', 'number', 'select', @@ -118,6 +120,10 @@ INTEGRATION_LANGUAGES = { 'zh-Hant': '繁體中文' } +DEFAULT_COVER_CLOSED_POSITION: int = 0 +MIN_COVER_CLOSED_POSITION: int = 0 +MAX_COVER_CLOSED_POSITION: int = 5 + DEFAULT_CTRL_MODE: str = 'auto' # Registered in Xiaomi OAuth 2.0 Service diff --git a/custom_components/xiaomi_home/miot/i18n/de.json b/custom_components/xiaomi_home/miot/i18n/de.json index 05ae2bf..f8682c9 100644 --- a/custom_components/xiaomi_home/miot/i18n/de.json +++ b/custom_components/xiaomi_home/miot/i18n/de.json @@ -99,6 +99,10 @@ "device_list_offline": "\n**{count} Geräte offline:** \n{message}", "network_status_online": "Online", "network_status_offline": "Offline", + "central_state_changed_title": "Verbindungsstatus des Zentral-Gateways", + "central_state_changed": "**{nick_name}({uid}, {cloud_server})** Lokale Verbindungsstrecke des Zentral-Gateways: {conn_status}", + "central_state_connected": "verbunden", + "central_state_disconnected": "getrennt", "device_exec_error": "Fehler bei der Ausführung" } }, diff --git a/custom_components/xiaomi_home/miot/i18n/en.json b/custom_components/xiaomi_home/miot/i18n/en.json index 47187ad..64f2a2f 100644 --- a/custom_components/xiaomi_home/miot/i18n/en.json +++ b/custom_components/xiaomi_home/miot/i18n/en.json @@ -99,6 +99,10 @@ "device_list_offline": "\n**{count} devices offline:** \n{message}", "network_status_online": "Online", "network_status_offline": "Offline", + "central_state_changed_title": "Central Hub Gateway Connection Status", + "central_state_changed":"**{nick_name}({uid}, {cloud_server})** local connection to Xiaomi central hub gateway: {conn_status}", + "central_state_connected": "Connected", + "central_state_disconnected": "Disconnected", "device_exec_error": "Execution error" } }, diff --git a/custom_components/xiaomi_home/miot/i18n/es.json b/custom_components/xiaomi_home/miot/i18n/es.json index c6f78df..cc8489c 100644 --- a/custom_components/xiaomi_home/miot/i18n/es.json +++ b/custom_components/xiaomi_home/miot/i18n/es.json @@ -99,6 +99,10 @@ "device_list_offline": "\n**{count} dispositivos sin conexión:** \n{message}", "network_status_online": "En línea", "network_status_offline": "Desconectado", + "central_state_changed_title": "Estado de conexión de la puerta de enlace central", + "central_state_changed": "**{nick_name}({uid}, {cloud_server})** enlace de conexión local de la puerta de enlace central: {conn_status}", + "central_state_connected": "conectado", + "central_state_disconnected": "desconectado", "device_exec_error": "Error de ejecución" } }, diff --git a/custom_components/xiaomi_home/miot/i18n/fr.json b/custom_components/xiaomi_home/miot/i18n/fr.json index 2789cc6..f84cc61 100644 --- a/custom_components/xiaomi_home/miot/i18n/fr.json +++ b/custom_components/xiaomi_home/miot/i18n/fr.json @@ -99,6 +99,10 @@ "device_list_offline": "\n**{count} appareils hors ligne :** \n{message}", "network_status_online": "En ligne", "network_status_offline": "Hors ligne", + "central_state_changed_title": "État de connexion de la passerelle centrale", + "central_state_changed": "**{nick_name}({uid}, {cloud_server})** liaison de connexion locale de la passerelle centrale : {conn_status}", + "central_state_connected": "connecté", + "central_state_disconnected": "déconnecté", "device_exec_error": "Erreur d'exécution" } }, diff --git a/custom_components/xiaomi_home/miot/i18n/it.json b/custom_components/xiaomi_home/miot/i18n/it.json index 8ec19d3..73bc6b5 100644 --- a/custom_components/xiaomi_home/miot/i18n/it.json +++ b/custom_components/xiaomi_home/miot/i18n/it.json @@ -99,6 +99,10 @@ "device_list_offline": "\n**{count} dispositivi offline:** \n{message}", "network_status_online": "Online", "network_status_offline": "Offline", + "central_state_changed_title": "Stato di connessione del gateway centrale", + "central_state_changed": "**{nick_name}({uid}, {cloud_server})** collegamento locale del gateway centrale: {conn_status}", + "central_state_connected": "connesso", + "central_state_disconnected": "disconnesso", "device_exec_error": "Errore di esecuzione" } }, diff --git a/custom_components/xiaomi_home/miot/i18n/ja.json b/custom_components/xiaomi_home/miot/i18n/ja.json index a32d997..01280f9 100644 --- a/custom_components/xiaomi_home/miot/i18n/ja.json +++ b/custom_components/xiaomi_home/miot/i18n/ja.json @@ -99,6 +99,10 @@ "device_list_offline": "\n**{count} デバイスがオフライン:** \n{message}", "network_status_online": "オンライン", "network_status_offline": "オフライン", + "central_state_changed_title": "中枢ゲートウェイ接続ステータス", + "central_state_changed": "**{nick_name}({uid}, {cloud_server})** 中枢ゲートウェイのローカル接続リンク: {conn_status}", + "central_state_connected": "接続済み", + "central_state_disconnected": "切断されました", "device_exec_error": "実行エラー" } }, diff --git a/custom_components/xiaomi_home/miot/i18n/nl.json b/custom_components/xiaomi_home/miot/i18n/nl.json index 5417348..6ec7425 100644 --- a/custom_components/xiaomi_home/miot/i18n/nl.json +++ b/custom_components/xiaomi_home/miot/i18n/nl.json @@ -99,6 +99,10 @@ "device_list_offline": "\n**{count} apparaten offline:** \n{message}", "network_status_online": "Online", "network_status_offline": "Offline", + "central_state_changed_title": "Verbindingsstatus van centrale gateway", + "central_state_changed": "**{nick_name}({uid}, {cloud_server})** Lokale verbinding van centrale gateway: {conn_status}", + "central_state_connected": "verbonden", + "central_state_disconnected": "verbinding verbroken", "device_exec_error": "Uitvoeringsfout" } }, diff --git a/custom_components/xiaomi_home/miot/i18n/pt-BR.json b/custom_components/xiaomi_home/miot/i18n/pt-BR.json index 553a90c..70f9616 100644 --- a/custom_components/xiaomi_home/miot/i18n/pt-BR.json +++ b/custom_components/xiaomi_home/miot/i18n/pt-BR.json @@ -99,6 +99,10 @@ "device_list_offline": "\n**{count} dispositivos offline**: \n{message}", "network_status_online": "online", "network_status_offline": "offline", + "central_state_changed_title": "Status de conexão do gateway central", + "central_state_changed": "**{nick_name}({uid}, {cloud_server})** conexão local do gateway central: {conn_status}", + "central_state_connected": "conectado", + "central_state_disconnected": "desconectado", "device_exec_error": "Erro na execução" } }, diff --git a/custom_components/xiaomi_home/miot/i18n/pt.json b/custom_components/xiaomi_home/miot/i18n/pt.json index 6466994..ad7b03a 100644 --- a/custom_components/xiaomi_home/miot/i18n/pt.json +++ b/custom_components/xiaomi_home/miot/i18n/pt.json @@ -99,6 +99,10 @@ "device_list_offline": "\n**{count} dispositivos offline**: \n{message}", "network_status_online": "Online", "network_status_offline": "Offline", + "central_state_changed_title": "Estado da ligação do gateway central", + "central_state_changed": "**{nick_name}({uid}, {cloud_server})** ligação local do gateway central: {conn_status}", + "central_state_connected": "ligado", + "central_state_disconnected": "desligado", "device_exec_error": "Erro de execução" } }, diff --git a/custom_components/xiaomi_home/miot/i18n/ru.json b/custom_components/xiaomi_home/miot/i18n/ru.json index b342ca1..c46467c 100644 --- a/custom_components/xiaomi_home/miot/i18n/ru.json +++ b/custom_components/xiaomi_home/miot/i18n/ru.json @@ -99,6 +99,10 @@ "device_list_offline": "\n**{count} устройств недоступно:** \n{message}", "network_status_online": "В сети", "network_status_offline": "Не в сети", + "central_state_changed_title": "Статус подключения центрального шлюза", + "central_state_changed": "**{nick_name}({uid}, {cloud_server})** локальное подключение центрального шлюза: {conn_status}", + "central_state_connected": "подключено", + "central_state_disconnected": "разъединено", "device_exec_error": "Ошибка выполнения" } }, diff --git a/custom_components/xiaomi_home/miot/i18n/zh-Hans.json b/custom_components/xiaomi_home/miot/i18n/zh-Hans.json index ed69254..f763ffb 100644 --- a/custom_components/xiaomi_home/miot/i18n/zh-Hans.json +++ b/custom_components/xiaomi_home/miot/i18n/zh-Hans.json @@ -99,6 +99,10 @@ "device_list_offline": "\n**{count} 个设备离线**: \n{message}", "network_status_online": "在线", "network_status_offline": "离线", + "central_state_changed_title": "中枢网关连接状态", + "central_state_changed":"**{nick_name}({uid}, {cloud_server})** 中枢网关本地连接链路: {conn_status}", + "central_state_connected": "已连接", + "central_state_disconnected": "断连", "device_exec_error": "执行错误" } }, diff --git a/custom_components/xiaomi_home/miot/i18n/zh-Hant.json b/custom_components/xiaomi_home/miot/i18n/zh-Hant.json index c354733..d3f1ee6 100644 --- a/custom_components/xiaomi_home/miot/i18n/zh-Hant.json +++ b/custom_components/xiaomi_home/miot/i18n/zh-Hant.json @@ -99,6 +99,10 @@ "device_list_offline": "\n**{count} 個設備離線:** \n{message}", "network_status_online": "在線", "network_status_offline": "離線", + "central_state_changed_title": "中枢網關連接狀態", + "central_state_changed":"**{nick_name}({uid}, {cloud_server})** 中枢網關本地連接鏈路: {conn_status}", + "central_state_connected": "已連接", + "central_state_disconnected": "断連", "device_exec_error": "執行錯誤" } }, diff --git a/custom_components/xiaomi_home/miot/miot_client.py b/custom_components/xiaomi_home/miot/miot_client.py index e1918d1..fb36c3f 100644 --- a/custom_components/xiaomi_home/miot/miot_client.py +++ b/custom_components/xiaomi_home/miot/miot_client.py @@ -61,16 +61,10 @@ from homeassistant.components import zeroconf # pylint: disable=relative-beyond-top-level from .common import MIoTMatcher, slugify_did -from .const import ( - DEFAULT_CTRL_MODE, - DEFAULT_INTEGRATION_LANGUAGE, - DEFAULT_NICK_NAME, - DOMAIN, - MIHOME_CERT_EXPIRE_MARGIN, - NETWORK_REFRESH_INTERVAL, - OAUTH2_CLIENT_ID, - SUPPORT_CENTRAL_GATEWAY_CTRL, -) +from .const import (DEFAULT_CTRL_MODE, DEFAULT_INTEGRATION_LANGUAGE, + DEFAULT_NICK_NAME, DOMAIN, MIHOME_CERT_EXPIRE_MARGIN, + NETWORK_REFRESH_INTERVAL, OAUTH2_CLIENT_ID, + SUPPORT_CENTRAL_GATEWAY_CTRL, DEFAULT_COVER_CLOSED_POSITION) from .miot_cloud import MIoTHttpClient, MIoTOauthClient from .miot_error import MIoTClientError, MIoTErrorCode from .miot_mips import ( @@ -502,6 +496,11 @@ class MIoTClient: def display_binary_bool(self) -> bool: return self._display_binary_bool + @property + def cover_closed_position(self) -> int: + return self._entry_data.get('cover_closed_position', + DEFAULT_COVER_CLOSED_POSITION) + @display_devices_changed_notify.setter def display_devices_changed_notify(self, value: list[str]) -> None: if set(value) == set(self._display_devs_notify): @@ -1094,8 +1093,8 @@ class MIoTClient: from_new = "lan" if (from_new is None and did in self._device_list_cloud and - self._device_list_cloud[did].get("online", False)): - from_new = "cloud" + self._device_list_cloud[did].get('online', False)): + from_new = 'cloud' if from_new == from_old: # No need to update return @@ -1212,7 +1211,7 @@ class MIoTClient: _LOGGER.info("local mips state changed, %s, %s", group_id, state) mips = self._mips_local.get(group_id, None) if not mips: - _LOGGER.error("local mips state changed, mips not exist, %s", + _LOGGER.error('local mips state changed, mips not exist, %s', group_id) return if state: @@ -1248,6 +1247,7 @@ class MIoTClient: if sub and sub.handler: sub.handler(did, MIoTDeviceState.OFFLINE, sub.handler_ctx) self.__request_show_devices_changed_notify() + self.__show_central_state_changed_notify(state) @final async def __on_miot_lan_state_change(self, state: bool) -> None: @@ -1606,7 +1606,7 @@ class MIoTClient: if did not in filter_dids: continue device_old = self._device_list_gateway.get(did, None) - gw_state_old = device_old.get("online", + gw_state_old = device_old.get('online', False) if device_old else False gw_state_new: bool = False device_new = gw_list.pop(did, None) @@ -1621,7 +1621,7 @@ class MIoTClient: if device_old: device_old["online"] = False # Update cache group_id - info["group_id"] = group_id + info['group_id'] = group_id if gw_state_old == gw_state_new: continue self.__update_device_msg_sub(did=did) @@ -2065,6 +2065,28 @@ class MIoTClient: self._show_devices_changed_notify_timer = self._main_loop.call_later( delay_sec, self.__show_devices_changed_notify) + @final + def __show_central_state_changed_notify(self, connected: bool) -> None: + conn_status: str = ( + self._i18n.translate('miot.client.central_state_connected') + if connected else + self._i18n.translate('miot.client.central_state_disconnected')) + self._persistence_notify( + self.__gen_notify_key('central_state_changed'), + self._i18n.translate('miot.client.central_state_changed_title'), + self._i18n.translate(key='miot.client.central_state_changed', + replace={ + 'nick_name': + self._entry_data.get( + 'nick_name', DEFAULT_NICK_NAME), + 'uid': + self._uid, + 'cloud_server': + self._cloud_server, + 'conn_status': + conn_status + })) + @staticmethod async def get_miot_instance_async( diff --git a/custom_components/xiaomi_home/miot/miot_cloud.py b/custom_components/xiaomi_home/miot/miot_cloud.py index 8f5a220..086e0bb 100644 --- a/custom_components/xiaomi_home/miot/miot_cloud.py +++ b/custom_components/xiaomi_home/miot/miot_cloud.py @@ -542,7 +542,12 @@ class MIoTHttpClient: self, dids: list[str], start_did: Optional[str] = None) -> dict[str, dict]: - req_data: dict = {"limit": 200, "get_split_device": True, "dids": dids} + req_data: dict = { + 'limit': 200, + 'get_split_device': True, + 'get_third_device': True, + 'dids': dids + } if start_did: req_data["start_did"] = start_did device_infos: dict = {} diff --git a/custom_components/xiaomi_home/miot/miot_mdns.py b/custom_components/xiaomi_home/miot/miot_mdns.py index ba661aa..2380909 100644 --- a/custom_components/xiaomi_home/miot/miot_mdns.py +++ b/custom_components/xiaomi_home/miot/miot_mdns.py @@ -110,7 +110,6 @@ class MipsServiceData: version=IPVersion.V4Only) if not self.addresses: raise MipsServiceError('invalid addresses') - self.addresses.sort() if not service_info.port: raise MipsServiceError('invalid port') self.port = service_info.port @@ -226,7 +225,7 @@ class MipsService: state_change: ServiceStateChange ) -> None: _LOGGER.debug( - 'mips service state changed, %s, %s, %s', + 'mdns discovery changed, %s, %s, %s', state_change, name, service_type) if state_change is ServiceStateChange.Removed: diff --git a/custom_components/xiaomi_home/miot/miot_mips.py b/custom_components/xiaomi_home/miot/miot_mips.py index 86d8cd6..ef814fb 100644 --- a/custom_components/xiaomi_home/miot/miot_mips.py +++ b/custom_components/xiaomi_home/miot/miot_mips.py @@ -1014,12 +1014,13 @@ class MipsCloudClient(_MipsClient): if handler: self.log_debug("cloud, device state changed, %s", payload) handler( - did, - MIoTDeviceState.ONLINE - if msg["event"] == "online" else MIoTDeviceState.OFFLINE, - ctx, - ) + did, MIoTDeviceState.ONLINE if msg['event'] == 'online' else + MIoTDeviceState.OFFLINE, ctx) + if did.startswith('blt.'): + # MIoT cloud may not publish BLE device online/offline state message. + # Do not subscribe BLE device online/offline state. + return True return self.__reg_broadcast_external(topic=topic, handler=on_state_msg, handler_ctx=handler_ctx) diff --git a/custom_components/xiaomi_home/miot/specs/spec_add.json b/custom_components/xiaomi_home/miot/specs/spec_add.json index 64f5113..8d14aec 100644 --- a/custom_components/xiaomi_home/miot/specs/spec_add.json +++ b/custom_components/xiaomi_home/miot/specs/spec_add.json @@ -203,6 +203,26 @@ ] } ], + "urn:miot-spec-v2:device:water-heater:0000A02A:viomi-m1:2": [ + { + "iid": 2, + "type": "urn:miot-spec-v2:service:switch:0000780C:viomi-m1:1", + "description": "Water Heater", + "properties": [ + { + "iid": 6, + "type": "urn:miot-spec-v2:property:on:00000006:viomi-m1:1", + "description": "Switch Status", + "format": "bool", + "access": [ + "read", + "write", + "notify" + ] + } + ] + } + ], "urn:miot-spec-v2:device:water-heater:0000A02A:xiaomi-yms2:1": [ { "iid": 2, diff --git a/custom_components/xiaomi_home/miot/specs/spec_modify.yaml b/custom_components/xiaomi_home/miot/specs/spec_modify.yaml index f656d30..16fbb56 100644 --- a/custom_components/xiaomi_home/miot/specs/spec_modify.yaml +++ b/custom_components/xiaomi_home/miot/specs/spec_modify.yaml @@ -6,6 +6,8 @@ urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:1: unit: none urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:1 urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c24:1: + prop.8.6: + unit: kWh prop.10.6: unit: none urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c24:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c24:1 @@ -26,6 +28,12 @@ urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-mt0:1: prop.10.6: unit: none urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-mt0:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-mt0:1 +urn:miot-spec-v2:device:air-monitor:0000A008:cgllc-cgd1st:1: + prop.3.7: + value-range: + - -30 + - 100 + - 0.1 urn:miot-spec-v2:device:air-monitor:0000A008:cgllc-s1:1: prop.2.5: name: voc-density @@ -153,6 +161,9 @@ urn:miot-spec-v2:device:light:0000A001:shhf-sfla10:1: urn:miot-spec-v2:device:light:0000A001:shhf-sfla12:1: prop.8.11: name: on-a +urn:miot-spec-v2:device:light:0000A001:shhf-sflt11:1:0000C802: + prop.11.14: + name: on-power urn:miot-spec-v2:device:magnet-sensor:0000A016:linp-m1:1: prop.2.1004: name: contact-state @@ -168,6 +179,9 @@ urn:miot-spec-v2:device:motion-sensor:0000A014:lumi-acn001:1: - read - notify unit: mV +urn:miot-spec-v2:device:motor-controller:0000A01D:adp-adswb4:1:0000C837: + prop.2.1: + name: motor-switch urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:ainice-3b:1: urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:ainice-3b:2 urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:ainice-3b:2: prop.2.8: diff --git a/custom_components/xiaomi_home/miot/specs/specv2entity.py b/custom_components/xiaomi_home/miot/specs/specv2entity.py index 68632dd..48d56c2 100644 --- a/custom_components/xiaomi_home/miot/specs/specv2entity.py +++ b/custom_components/xiaomi_home/miot/specs/specv2entity.py @@ -50,18 +50,11 @@ from homeassistant.components.sensor import SensorStateClass from homeassistant.components.event import EventDeviceClass from homeassistant.components.binary_sensor import BinarySensorDeviceClass -from homeassistant.const import ( - CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - EntityCategory, - LIGHT_LUX, - UnitOfEnergy, - UnitOfPower, - UnitOfElectricCurrent, - UnitOfElectricPotential, - UnitOfTemperature, - UnitOfPressure, - PERCENTAGE -) +from homeassistant.const import (CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + EntityCategory, LIGHT_LUX, UnitOfEnergy, + UnitOfPower, UnitOfElectricCurrent, + UnitOfElectricPotential, UnitOfTemperature, + UnitOfPressure, PERCENTAGE) # pylint: disable=pointless-string-statement """SPEC_DEVICE_TRANS_MAP @@ -107,7 +100,7 @@ SPEC_DEVICE_TRANS_MAP: dict = { 'humidifier': { 'required': { 'humidifier': { - 'required': { + 'required': { 'properties': { 'on': {'read', 'write'} } @@ -119,7 +112,7 @@ SPEC_DEVICE_TRANS_MAP: dict = { }, 'optional': { 'environment': { - 'required': { + 'required': { 'properties': { 'relative-humidity': {'read'} } @@ -131,7 +124,7 @@ SPEC_DEVICE_TRANS_MAP: dict = { 'dehumidifier': { 'required': { 'dehumidifier': { - 'required': { + 'required': { 'properties': { 'on': {'read', 'write'} } @@ -143,7 +136,7 @@ SPEC_DEVICE_TRANS_MAP: dict = { }, 'optional': { 'environment': { - 'required': { + 'required': { 'properties': { 'relative-humidity': {'read'} } @@ -155,15 +148,13 @@ SPEC_DEVICE_TRANS_MAP: dict = { 'vacuum': { 'required': { 'vacuum': { - 'required': { + 'required': { 'actions': {'start-sweep', 'stop-sweeping'}, }, 'optional': { 'properties': {'status', 'fan-level'}, 'actions': { - 'pause-sweeping', - 'continue-sweep', - 'stop-and-gocharge' + 'pause-sweeping', 'continue-sweep', 'stop-and-gocharge' } } } @@ -171,7 +162,7 @@ SPEC_DEVICE_TRANS_MAP: dict = { 'optional': { 'identify': { 'required': { - 'actions': {'identify'} + 'actions': {'identify'} } }, 'battery': { @@ -204,10 +195,9 @@ SPEC_DEVICE_TRANS_MAP: dict = { 'required': {}, 'optional': { 'properties': { - 'on', - 'fan-level', - 'horizontal-swing', - 'vertical-swing'}} + 'on', 'fan-level', 'horizontal-swing', 'vertical-swing' + } + } }, 'environment': { 'required': {}, @@ -235,8 +225,8 @@ SPEC_DEVICE_TRANS_MAP: dict = { }, 'optional': { 'properties': { - 'target-temperature', 'mode', 'fan-level', - 'temperature'} + 'target-temperature', 'mode', 'fan-level', 'temperature' + } } } }, @@ -278,7 +268,7 @@ SPEC_DEVICE_TRANS_MAP: dict = { 'ptc-bath-heater': { 'required': { 'properties': { - 'mode':{'read', 'write'} + 'mode': {'read', 'write'} } }, 'optional': { @@ -321,6 +311,89 @@ SPEC_DEVICE_TRANS_MAP: dict = { 'optional': {}, 'entity': 'electric-blanket' }, + 'speaker': { + 'required': { + 'speaker': { + 'required': { + 'properties': { + 'volume': {'read', 'write'} + } + }, + 'optional': { + 'properties': {'mute'} + } + }, + 'play-control': { + 'required': { + 'actions': {'play'} + }, + 'optional': { + 'properties': {'playing-state'}, + 'actions': {'pause', 'stop', 'next', 'previous'} + } + } + }, + 'optional': {}, + 'entity': 'wifi-speaker' + }, + 'television': { + 'required': { + 'speaker': { + 'required': { + 'properties': { + 'volume': {'read', 'write'} + } + }, + 'optional': { + 'properties': {'mute'} + } + }, + 'television': { + 'required': { + 'actions': {'turn-off'} + }, + 'optional': { + 'properties': {'input-control'}, + 'actions': {'turn-on'} + } + } + }, + 'optional': { + 'play-control': { + 'required': {}, + 'optional': { + 'properties': {'playing-state'}, + 'actions': {'play', 'pause', 'stop', 'next', 'previous'} + } + } + }, + 'entity': 'television' + }, + 'watch': { + 'required': { + 'watch': { + 'required': { + 'properties': { + 'longitude': {'read'}, + 'latitude': {'read'} + } + }, + 'optional': { + 'properties': {'area-id'} + } + } + }, + 'optional': { + 'battery': { + 'required': { + 'properties': { + 'battery-level': {'read'} + } + } + } + }, + 'entity': 'device_tracker' + } } """SPEC_SERVICE_TRANS_MAP @@ -351,9 +424,7 @@ SPEC_SERVICE_TRANS_MAP: dict = { } }, 'optional': { - 'properties': { - 'mode', 'brightness', 'color', 'color-temperature' - } + 'properties': {'mode', 'brightness', 'color', 'color-temperature'} }, 'entity': 'light' }, @@ -368,7 +439,8 @@ SPEC_SERVICE_TRANS_MAP: dict = { }, 'optional': { 'properties': { - 'mode', 'brightness', + 'mode', + 'brightness', } }, 'entity': 'light', @@ -401,16 +473,14 @@ SPEC_SERVICE_TRANS_MAP: dict = { }, 'entity': 'water_heater' }, - 'curtain': { + 'curtain': { 'required': { 'properties': { 'motor-control': {'write'} } }, 'optional': { - 'properties': { - 'status', 'current-position', 'target-position' - } + 'properties': {'status', 'current-position', 'target-position'} }, 'entity': 'cover' }, @@ -600,6 +670,4 @@ SPEC_EVENT_TRANS_MAP: dict[str, str] = { 'doorbell-ring': EventDeviceClass.DOORBELL } -SPEC_ACTION_TRANS_MAP = { - -} +SPEC_ACTION_TRANS_MAP = {} diff --git a/custom_components/xiaomi_home/translations/de.json b/custom_components/xiaomi_home/translations/de.json index 15f5c0f..4bf7d72 100644 --- a/custom_components/xiaomi_home/translations/de.json +++ b/custom_components/xiaomi_home/translations/de.json @@ -4,7 +4,7 @@ "step": { "eula": { "title": "Risikohinweis", - "description": "1. Ihre **Xiaomi-Benutzerinformationen und Geräteinformationen** werden in Ihrem Home Assistant-System gespeichert. **Xiaomi kann die Sicherheit des Home Assistant-Speichermechanismus nicht garantieren**. Sie sind dafür verantwortlich, Ihre Informationen vor Diebstahl zu schützen.\r\n2. Diese Integration wird von der Open-Source-Community unterstützt und gewartet. Es können jedoch Stabilitätsprobleme oder andere Probleme auftreten. Wenn Sie auf ein Problem stoßen, das mit dieser Integration zusammenhängt, sollten Sie **die Open-Source-Community um Hilfe bitten, anstatt sich an den Xiaomi Home Kundendienst zu wenden**.\r\n3. Sie benötigen bestimmte technische Fähigkeiten, um Ihre lokale Laufzeitumgebung zu warten. Diese Integration ist für Anfänger nicht geeignet. \r\n4. Bevor Sie diese Integration verwenden, lesen Sie bitte die **README-Datei sorgfältig durch**.\r\n5. Um eine stabile Nutzung der Integration zu gewährleisten und Missbrauch der Schnittstelle zu verhindern, **darf diese Integration nur in Home Assistant verwendet werden. Weitere Informationen finden Sie in der LICENSE**.", + "description": "1. Ihre **Xiaomi-Benutzerinformationen und Geräteinformationen** werden in Ihrem Home Assistant-System gespeichert. **Xiaomi kann die Sicherheit des Home Assistant-Speichermechanismus nicht garantieren**. Sie sind dafür verantwortlich, Ihre Informationen vor Diebstahl zu schützen.\r\n2. Diese Integration wird von der Open-Source-Community unterstützt und gewartet. Es können jedoch Stabilitätsprobleme oder andere Probleme auftreten. Wenn Sie auf ein Problem stoßen, das mit dieser Integration zusammenhängt, sollten Sie **die Open-Source-Community um Hilfe bitten, anstatt sich an den Xiaomi Home Kundendienst zu wenden**.\r\n3. Sie benötigen bestimmte technische Fähigkeiten, um Ihre lokale Laufzeitumgebung zu warten. Diese Integration ist für Anfänger nicht geeignet.\r\n4. Bevor Sie diese Integration verwenden, lesen Sie bitte die **README-Datei sorgfältig durch**.\r\n5. Um eine stabile Nutzung der Integration zu gewährleisten und Missbrauch der Schnittstelle zu verhindern, **darf diese Integration nur in Home Assistant verwendet werden. Weitere Informationen finden Sie in der LICENSE**.", "data": { "eula": "Ich habe das oben genannte Risiko zur Kenntnis genommen und übernehme freiwillig die damit verbundenen Risiken durch die Verwendung der Integration." } @@ -124,7 +124,8 @@ "display_devices_changed_notify": "Gerätestatusänderungen anzeigen", "update_trans_rules": "Entitätskonvertierungsregeln aktualisieren", "update_lan_ctrl_config": "LAN-Steuerungskonfiguration aktualisieren", - "network_detect_config": "Integrierte Netzwerkkonfiguration" + "network_detect_config": "Integrierte Netzwerkkonfiguration", + "cover_closed_position": "Die Position der geschlossenen Vorhänge" } }, "update_user_info": { @@ -183,7 +184,7 @@ }, "config_confirm": { "title": "Bestätigen Sie die Konfiguration", - "description": "**{nick_name}**, bitte bestätigen Sie die neuesten Konfigurationsinformationen und klicken Sie dann auf \"Senden\". Die Integration wird mit den aktualisierten Konfigurationen erneut geladen.\r\n\r\nIntegrationsprache:\t{lang_new}\r\nBenutzername:\t{nick_name_new}\r\nAction-Debug-Modus:\t{action_debug}\r\nVerstecke Nicht-Standard-Entitäten:\t{hide_non_standard_entities}\r\nGerätestatusänderungen anzeigen:\t{display_devices_changed_notify}\r\nGeräteänderungen:\t{devices_add} neue Geräte hinzufügen, {devices_remove} Geräte entfernen\r\nKonvertierungsregeländerungen:\tInsgesamt {trans_rules_count} Regeln, aktualisiert {trans_rules_count_success} Regeln", + "description": "**{nick_name}**, bitte bestätigen Sie die neuesten Konfigurationsinformationen und klicken Sie dann auf \"Senden\". Die Integration wird mit den aktualisierten Konfigurationen erneut geladen.\r\n\r\nIntegrationsprache:\t{lang_new}\r\nBenutzername:\t{nick_name_new}\r\nAction-Debug-Modus:\t{action_debug}\r\nVerstecke Nicht-Standard-Entitäten:\t{hide_non_standard_entities}\r\nDie Position der geschlossenen Vorhänge:\t{cover_pos_new}\r\nGerätestatusänderungen anzeigen:\t{display_devices_changed_notify}\r\nGeräteänderungen:\t{devices_add} neue Geräte hinzufügen, {devices_remove} Geräte entfernen\r\nKonvertierungsregeländerungen:\tInsgesamt {trans_rules_count} Regeln, aktualisiert {trans_rules_count_success} Regeln", "data": { "confirm": "Änderungen bestätigen" } diff --git a/custom_components/xiaomi_home/translations/en.json b/custom_components/xiaomi_home/translations/en.json index 7832fd1..82fc15b 100644 --- a/custom_components/xiaomi_home/translations/en.json +++ b/custom_components/xiaomi_home/translations/en.json @@ -124,7 +124,8 @@ "display_devices_changed_notify": "Display device status change notifications", "update_trans_rules": "Update entity conversion rules", "update_lan_ctrl_config": "Update LAN control configuration", - "network_detect_config": "Integrated Network Configuration" + "network_detect_config": "Integrated network configuration", + "cover_closed_position": "Cover closed position" } }, "update_user_info": { @@ -183,7 +184,7 @@ }, "config_confirm": { "title": "Confirm Configuration", - "description": "Hello **{nick_name}**, please confirm the latest configuration information and then Click SUBMIT.\r\nThe integration will reload using the updated configuration.\r\n\r\nIntegration Language: \t{lang_new}\r\nNickname: \t{nick_name_new}\r\nDebug mode for action: \t{action_debug}\r\nHide non-standard created entities: \t{hide_non_standard_entities}\r\nDisplay device status change notifications:\t{display_devices_changed_notify}\r\nDevice Changes: \tAdd **{devices_add}** devices, Remove **{devices_remove}** devices\r\nTransformation rules change: \tThere are a total of **{trans_rules_count}** rules, and updated **{trans_rules_count_success}** rules", + "description": "Hello **{nick_name}**, please confirm the latest configuration information and then Click SUBMIT.\r\nThe integration will reload using the updated configuration.\r\n\r\nIntegration Language:\t{lang_new}\r\nNickname:\t{nick_name_new}\r\nDebug mode for action:\t{action_debug}\r\nHide non-standard created entities:\t{hide_non_standard_entities}\r\nCover closed position:\t{cover_pos_new}\r\nDisplay device status change notifications:\t{display_devices_changed_notify}\r\nDevice Changes:\tAdd **{devices_add}** devices, Remove **{devices_remove}** devices\r\nTransformation rules change:\tThere are a total of **{trans_rules_count}** rules, and updated **{trans_rules_count_success}** rules", "data": { "confirm": "Confirm the change" } diff --git a/custom_components/xiaomi_home/translations/es.json b/custom_components/xiaomi_home/translations/es.json index eb52c74..f03ac55 100644 --- a/custom_components/xiaomi_home/translations/es.json +++ b/custom_components/xiaomi_home/translations/es.json @@ -4,7 +4,7 @@ "step": { "eula": { "title": "Aviso de riesgo", - "description": "1. Su **información de usuario de Xiaomi e información del dispositivo** se almacenará en su sistema Home Assistant. **Xiaomi no puede garantizar la seguridad del mecanismo de almacenamiento de Home Assistant**. Usted es responsable de evitar que su información sea robada.\r\n2. Esta integración es mantenida por la comunidad de código abierto y puede haber problemas de estabilidad u otros problemas. Cuando tenga problemas relacionados con el uso de esta integración, **busque ayuda en la comunidad de código abierto en lugar de contactar al servicio al cliente de Xiaomi**.\r\n3. Es necesario tener ciertas habilidades técnicas para mantener su entorno de ejecución local, esta integración no es amigable para los usuarios novatos.\r\n4. Antes de utilizar esta integración, por favor **lea detenidamente el archivo README**. \r\n5. Para garantizar el uso estable de la integración y prevenir el abuso de la interfaz, **esta integración solo está permitida en Home Assistant. Para más detalles, consulte la LICENSE**.", + "description": "1. Su **información de usuario de Xiaomi e información del dispositivo** se almacenará en su sistema Home Assistant. **Xiaomi no puede garantizar la seguridad del mecanismo de almacenamiento de Home Assistant**. Usted es responsable de evitar que su información sea robada.\r\n2. Esta integración es mantenida por la comunidad de código abierto y puede haber problemas de estabilidad u otros problemas. Cuando tenga problemas relacionados con el uso de esta integración, **busque ayuda en la comunidad de código abierto en lugar de contactar al servicio al cliente de Xiaomi**.\r\n3. Es necesario tener ciertas habilidades técnicas para mantener su entorno de ejecución local, esta integración no es amigable para los usuarios novatos.\r\n4. Antes de utilizar esta integración, por favor **lea detenidamente el archivo README**.\r\n5. Para garantizar el uso estable de la integración y prevenir el abuso de la interfaz, **esta integración solo está permitida en Home Assistant. Para más detalles, consulte la LICENSE**.", "data": { "eula": "He leído y entiendo los riesgos anteriores, y estoy dispuesto a asumir cualquier riesgo relacionado con el uso de esta integración." } @@ -124,7 +124,8 @@ "display_devices_changed_notify": "Mostrar notificaciones de cambio de estado del dispositivo", "update_trans_rules": "Actualizar reglas de conversión de entidad", "update_lan_ctrl_config": "Actualizar configuración de control LAN", - "network_detect_config": "Configuración de Red Integrada" + "network_detect_config": "Configuración de Red Integrada", + "cover_closed_position": "La posición de las cortinas cerradas" } }, "update_user_info": { @@ -183,7 +184,7 @@ }, "config_confirm": { "title": "Confirmar configuración", - "description": "¡Hola, **{nick_name}**! Por favor, confirme la última información de configuración y haga clic en \"Enviar\" para finalizar la configuración.\r\nLa integración se volverá a cargar con la nueva configuración.\r\n\r\nIdioma de la integración:\t{lang_new}\r\nApodo de usuario:\t{nick_name_new}\r\nModo de depuración de Action:\t{action_debug}\r\nOcultar entidades generadas no estándar:\t{hide_non_standard_entities}\r\nMostrar notificaciones de cambio de estado del dispositivo:\t{display_devices_changed_notify}\r\nCambios de dispositivos:\t{devices_add} dispositivos agregados, {devices_remove} dispositivos eliminados\r\nCambios en las reglas de conversión:\t{trans_rules_count} reglas en total, {trans_rules_count_success} reglas actualizadas", + "description": "¡Hola, **{nick_name}**! Por favor, confirme la última información de configuración y haga clic en \"Enviar\" para finalizar la configuración.\r\nLa integración se volverá a cargar con la nueva configuración.\r\n\r\nIdioma de la integración:\t{lang_new}\r\nApodo de usuario:\t{nick_name_new}\r\nModo de depuración de Action:\t{action_debug}\r\nOcultar entidades generadas no estándar:\t{hide_non_standard_entities}\r\nLa posición de las cortinas cerradas:\t{cover_pos_new}\r\nMostrar notificaciones de cambio de estado del dispositivo:\t{display_devices_changed_notify}\r\nCambios de dispositivos:\t{devices_add} dispositivos agregados, {devices_remove} dispositivos eliminados\r\nCambios en las reglas de conversión:\t{trans_rules_count} reglas en total, {trans_rules_count_success} reglas actualizadas", "data": { "confirm": "Confirmar modificación" } diff --git a/custom_components/xiaomi_home/translations/fr.json b/custom_components/xiaomi_home/translations/fr.json index 07c2245..02675a2 100644 --- a/custom_components/xiaomi_home/translations/fr.json +++ b/custom_components/xiaomi_home/translations/fr.json @@ -124,7 +124,8 @@ "display_devices_changed_notify": "Afficher les notifications de changement d'état de l'appareil", "update_trans_rules": "Mettre à jour les règles de conversion d'entités", "update_lan_ctrl_config": "Mettre à jour la configuration de contrôle LAN", - "network_detect_config": "Configuration Réseau Intégrée" + "network_detect_config": "Configuration Réseau Intégrée", + "cover_closed_position": "La position des rideaux fermés" } }, "update_user_info": { @@ -183,7 +184,7 @@ }, "config_confirm": { "title": "Confirmer la configuration", - "description": "**{nick_name}** Bonjour ! Veuillez confirmer les dernières informations de configuration et cliquer sur \"Soumettre\".\r\nL'intégration rechargera avec la nouvelle configuration.\r\n\r\nLangue d'intégration : {lang_new}\r\nPseudo utilisateur : {nick_name_new}\r\nMode de débogage d'action : {action_debug}\r\nMasquer les entités générées non standard : {hide_non_standard_entities}\r\nAfficher les notifications de changement d'état de l'appareil:\t{display_devices_changed_notify}\r\nModifications des appareils : Ajouter **{devices_add}** appareils, supprimer **{devices_remove}** appareils\r\nModifications des règles de conversion : **{trans_rules_count}** règles au total, mise à jour de **{trans_rules_count_success}** règles", + "description": "**{nick_name}** Bonjour ! Veuillez confirmer les dernières informations de configuration et cliquer sur \"Soumettre\".\r\nL'intégration rechargera avec la nouvelle configuration.\r\n\r\nLangue d'intégration : {lang_new}\r\nPseudo utilisateur : {nick_name_new}\r\nMode de débogage d'action : {action_debug}\r\nMasquer les entités générées non standard : {hide_non_standard_entities}\r\nLa position des rideaux fermés:\t{cover_pos_new}\r\nAfficher les notifications de changement d'état de l'appareil:\t{display_devices_changed_notify}\r\nModifications des appareils : Ajouter **{devices_add}** appareils, supprimer **{devices_remove}** appareils\r\nModifications des règles de conversion : **{trans_rules_count}** règles au total, mise à jour de **{trans_rules_count_success}** règles", "data": { "confirm": "Confirmer la modification" } diff --git a/custom_components/xiaomi_home/translations/it.json b/custom_components/xiaomi_home/translations/it.json index 20439aa..4a06b58 100644 --- a/custom_components/xiaomi_home/translations/it.json +++ b/custom_components/xiaomi_home/translations/it.json @@ -124,7 +124,8 @@ "display_devices_changed_notify": "Mostra notifiche di cambio stato del dispositivo", "update_trans_rules": "Aggiorna le regole di conversione delle entità", "update_lan_ctrl_config": "Aggiorna configurazione del controllo LAN", - "network_detect_config": "Configurazione di Rete Integrata" + "network_detect_config": "Configurazione di Rete Integrata", + "cover_closed_position": "La posizione delle tende chiuse" } }, "update_user_info": { @@ -183,7 +184,7 @@ }, "config_confirm": { "title": "Conferma Configurazione", - "description": "Ciao **{nick_name}**, si prega di confermare le informazioni di configurazione più recenti e poi fare clic su INVIA.\r\nL'integrazione verrà ricaricata utilizzando la configurazione aggiornata.\r\n\r\nLingua dell'Integrazione: \t{lang_new}\r\nSoprannome: \t{nick_name_new}\r\nModalità di debug per azione: \t{action_debug}\r\nNascondi entità create non standard: \t{hide_non_standard_entities}\r\nMostra notifiche di cambio stato del dispositivo:\t{display_devices_changed_notify}\r\nCambiamenti del Dispositivo: \tAggiungi **{devices_add}** dispositivi, Rimuovi **{devices_remove}** dispositivi\r\nCambiamenti delle regole di trasformazione: \tCi sono un totale di **{trans_rules_count}** regole, e aggiornate **{trans_rules_count_success}** regole", + "description": "Ciao **{nick_name}**, si prega di confermare le informazioni di configurazione più recenti e poi fare clic su INVIA.\r\nL'integrazione verrà ricaricata utilizzando la configurazione aggiornata.\r\n\r\nLingua dell'Integrazione:\t{lang_new}\r\nSoprannome:\t{nick_name_new}\r\nModalità di debug per azione:\t{action_debug}\r\nNascondi entità create non standard:\t{hide_non_standard_entities}\r\nLa posizione delle tende chiuse:\t{cover_pos_new}\r\nMostra notifiche di cambio stato del dispositivo:\t{display_devices_changed_notify}\r\nCambiamenti del Dispositivo:\tAggiungi **{devices_add}** dispositivi, Rimuovi **{devices_remove}** dispositivi\r\nCambiamenti delle regole di trasformazione:\tCi sono un totale di **{trans_rules_count}** regole, e aggiornate **{trans_rules_count_success}** regole", "data": { "confirm": "Conferma la modifica" } diff --git a/custom_components/xiaomi_home/translations/ja.json b/custom_components/xiaomi_home/translations/ja.json index 2dda890..ffa512e 100644 --- a/custom_components/xiaomi_home/translations/ja.json +++ b/custom_components/xiaomi_home/translations/ja.json @@ -124,7 +124,8 @@ "display_devices_changed_notify": "デバイスの状態変化通知を表示", "update_trans_rules": "エンティティ変換ルールを更新する", "update_lan_ctrl_config": "LAN制御構成を更新する", - "network_detect_config": "統合ネットワーク構成" + "network_detect_config": "統合ネットワーク構成", + "cover_closed_position": "カーテンを閉じた位置" } }, "update_user_info": { @@ -183,7 +184,7 @@ }, "config_confirm": { "title": "構成を確認する", - "description": "**{nick_name}** さん、こんにちは! 最新の構成情報を確認してください。[送信] をクリックして、更新された構成を使用して再度読み込みます。\r\n\r\n統合言語:\t{lang_new}\r\nユーザー名:\t{nick_name_new}\r\nAction デバッグモード:\t{action_debug}\r\n非標準生成エンティティを非表示にする:\t{hide_non_standard_entities}\r\nデバイスの状態変化通知を表示:\t{display_devices_changed_notify}\r\nデバイス変更:\t追加 **{devices_add}** 個のデバイス、削除 **{devices_remove}** 個のデバイス\r\n変換ルール変更:\t合計 **{trans_rules_count}** 個の規則、更新 **{trans_rules_count_success}** 個の規則", + "description": "**{nick_name}** さん、こんにちは! 最新の構成情報を確認してください。[送信] をクリックして、更新された構成を使用して再度読み込みます。\r\n\r\n統合言語:\t{lang_new}\r\nユーザー名:\t{nick_name_new}\r\nAction デバッグモード:\t{action_debug}\r\n非標準生成エンティティを非表示にする:\t{hide_non_standard_entities}\r\nカーテンを閉じた位置:\t{cover_pos_new}\r\nデバイスの状態変化通知を表示:\t{display_devices_changed_notify}\r\nデバイス変更:\t追加 **{devices_add}** 個のデバイス、削除 **{devices_remove}** 個のデバイス\r\n変換ルール変更:\t合計 **{trans_rules_count}** 個の規則、更新 **{trans_rules_count_success}** 個の規則", "data": { "confirm": "変更を確認する" } diff --git a/custom_components/xiaomi_home/translations/nl.json b/custom_components/xiaomi_home/translations/nl.json index c9d8e40..125403f 100644 --- a/custom_components/xiaomi_home/translations/nl.json +++ b/custom_components/xiaomi_home/translations/nl.json @@ -124,7 +124,8 @@ "display_devices_changed_notify": "Apparaatstatuswijzigingen weergeven", "update_trans_rules": "Werk entiteitsconversieregels bij", "update_lan_ctrl_config": "Werk LAN controleconfiguratie bij", - "network_detect_config": "Geïntegreerde Netwerkconfiguratie" + "network_detect_config": "Geïntegreerde Netwerkconfiguratie", + "cover_closed_position": "De positie van de gesloten gordijnen" } }, "update_user_info": { @@ -183,7 +184,7 @@ }, "config_confirm": { "title": "Bevestig Configuratie", - "description": "Hallo **{nick_name}**, bevestig alstublieft de nieuwste configuratie-informatie en klik vervolgens op INDENKEN.\r\nDe integratie zal opnieuw laden met de bijgewerkte configuratie.\r\n\r\nIntegratietaal: \t{lang_new}\r\nBijnaam: \t{nick_name_new}\r\nDebugmodus voor actie: \t{action_debug}\r\nVerberg niet-standaard gemaakte entiteiten: \t{hide_non_standard_entities}\r\nApparaatstatuswijzigingen weergeven:\t{display_devices_changed_notify}\r\nWijzigingen in apparaten: \tVoeg **{devices_add}** apparaten toe, Verwijder **{devices_remove}** apparaten\r\nWijzigingen in transformateregels: \tEr zijn in totaal **{trans_rules_count}** regels, en **{trans_rules_count_success}** regels zijn bijgewerkt", + "description": "Hallo **{nick_name}**, bevestig alstublieft de nieuwste configuratie-informatie en klik vervolgens op INDENKEN.\r\nDe integratie zal opnieuw laden met de bijgewerkte configuratie.\r\n\r\nIntegratietaal:\t{lang_new}\r\nBijnaam:\t{nick_name_new}\r\nDebugmodus voor actie:\t{action_debug}\r\nVerberg niet-standaard gemaakte entiteiten:\t{hide_non_standard_entities}\r\nDe positie van de gesloten gordijnen:\t{cover_pos_new}\r\nApparaatstatuswijzigingen weergeven:\t{display_devices_changed_notify}\r\nWijzigingen in apparaten:\tVoeg **{devices_add}** apparaten toe, Verwijder **{devices_remove}** apparaten\r\nWijzigingen in transformateregels:\tEr zijn in totaal **{trans_rules_count}** regels, en **{trans_rules_count_success}** regels zijn bijgewerkt", "data": { "confirm": "Bevestig de wijziging" } diff --git a/custom_components/xiaomi_home/translations/pt-BR.json b/custom_components/xiaomi_home/translations/pt-BR.json index 12286d5..a383b8b 100644 --- a/custom_components/xiaomi_home/translations/pt-BR.json +++ b/custom_components/xiaomi_home/translations/pt-BR.json @@ -124,7 +124,8 @@ "display_devices_changed_notify": "Exibir notificações de mudança de status do dispositivo", "update_trans_rules": "Atualizar regras de conversão de entidades", "update_lan_ctrl_config": "Atualizar configuração de controle LAN", - "network_detect_config": "Configuração de Rede Integrada" + "network_detect_config": "Configuração de Rede Integrada", + "cover_closed_position": "A posição das cortinas fechadas" } }, "update_user_info": { @@ -183,7 +184,7 @@ }, "config_confirm": { "title": "Confirmar Configuração", - "description": "Olá **{nick_name}**, confirme as informações da configuração mais recente e depois clique em ENVIAR.\r\nA integração será recarregada com a configuração atualizada.\r\n\r\nIdioma da Integração:\t{lang_new}\r\nApelido:\t{nick_name_new}\r\nModo de depuração para ação:\t{action_debug}\r\nOcultar entidades não padrão criadas:\t{hide_non_standard_entities}\r\nExibir notificações de mudança de status do dispositivo:\t{display_devices_changed_notify}\r\nAlterações de Dispositivos:\tAdicionar **{devices_add}** dispositivos, Remover **{devices_remove}** dispositivos\r\nAlteração nas Regras de Transformação:\tUm total de **{trans_rules_count}** regras, e **{trans_rules_count_success}** regras atualizadas", + "description": "Olá **{nick_name}**, confirme as informações da configuração mais recente e depois clique em ENVIAR.\r\nA integração será recarregada com a configuração atualizada.\r\n\r\nIdioma da Integração:\t{lang_new}\r\nApelido:\t{nick_name_new}\r\nModo de depuração para ação:\t{action_debug}\r\nOcultar entidades não padrão criadas:\t{hide_non_standard_entities}\r\nA posição das cortinas fechadas:\t{cover_pos_new}\r\nExibir notificações de mudança de status do dispositivo:\t{display_devices_changed_notify}\r\nAlterações de Dispositivos:\tAdicionar **{devices_add}** dispositivos, Remover **{devices_remove}** dispositivos\r\nAlteração nas Regras de Transformação:\tUm total de **{trans_rules_count}** regras, e **{trans_rules_count_success}** regras atualizadas", "data": { "confirm": "Confirmar a mudança" } diff --git a/custom_components/xiaomi_home/translations/pt.json b/custom_components/xiaomi_home/translations/pt.json index 2287585..d747658 100644 --- a/custom_components/xiaomi_home/translations/pt.json +++ b/custom_components/xiaomi_home/translations/pt.json @@ -124,7 +124,8 @@ "display_devices_changed_notify": "Exibir notificações de mudança de status do dispositivo", "update_trans_rules": "Atualizar regras de conversão de entidades", "update_lan_ctrl_config": "Atualizar configuração de controlo LAN", - "network_detect_config": "Configuração de Rede Integrada" + "network_detect_config": "Configuração de Rede Integrada", + "cover_closed_position": "A posição das cortinas fechadas" } }, "update_user_info": { @@ -183,7 +184,7 @@ }, "config_confirm": { "title": "Confirmar Configuração", - "description": "Olá **{nick_name}**, confirme a informação da configuração mais recente e depois clique em SUBMETER.\r\nA integração será recarregada com a configuração atualizada.\r\n\r\nIdioma da Integração:\t{lang_new}\r\nAlcunha:\t{nick_name_new}\r\nModo de depuração de ação:\t{action_debug}\r\nOcultar entidades não padrão:\t{hide_non_standard_entities}\r\nExibir notificações de mudança de status do dispositivo:\t{display_devices_changed_notify}\r\nAlterações aos Dispositivos:\tAdicionar **{devices_add}** dispositivos, Remover **{devices_remove}** dispositivos\r\nAlteração das Regras de Transformação:\tExistem **{trans_rules_count}** regras no total, com **{trans_rules_count_success}** regras atualizadas", + "description": "Olá **{nick_name}**, confirme a informação da configuração mais recente e depois clique em SUBMETER.\r\nA integração será recarregada com a configuração atualizada.\r\n\r\nIdioma da Integração:\t{lang_new}\r\nAlcunha:\t{nick_name_new}\r\nModo de depuração de ação:\t{action_debug}\r\nOcultar entidades não padrão:\t{hide_non_standard_entities}\r\nA posição das cortinas fechadas:\t{cover_pos_new}\r\nExibir notificações de mudança de status do dispositivo:\t{display_devices_changed_notify}\r\nAlterações aos Dispositivos:\tAdicionar **{devices_add}** dispositivos, Remover **{devices_remove}** dispositivos\r\nAlteração das Regras de Transformação:\tExistem **{trans_rules_count}** regras no total, com **{trans_rules_count_success}** regras atualizadas", "data": { "confirm": "Confirmar a alteração" } diff --git a/custom_components/xiaomi_home/translations/ru.json b/custom_components/xiaomi_home/translations/ru.json index fba3edc..09bbcd5 100644 --- a/custom_components/xiaomi_home/translations/ru.json +++ b/custom_components/xiaomi_home/translations/ru.json @@ -124,7 +124,8 @@ "display_devices_changed_notify": "Отображать уведомления о изменении состояния устройства", "update_trans_rules": "Обновить правила преобразования сущностей", "update_lan_ctrl_config": "Обновить конфигурацию управления LAN", - "network_detect_config": "Интегрированная Сетевая Конфигурация" + "network_detect_config": "Интегрированная Сетевая Конфигурация", + "cover_closed_position": "Положение закрытых штор" } }, "update_user_info": { @@ -183,7 +184,7 @@ }, "config_confirm": { "title": "Подтверждение настройки", - "description": "**{nick_name}** Здравствуйте! Подтвердите последнюю информацию о настройке и нажмите «Отправить». Интеграция будет перезагружена с использованием обновленных настроек.\r\n\r\nЯзык интеграции:\t{lang_new}\r\nИмя пользователя:\t{nick_name_new}\r\nРежим отладки Action:\t{action_debug}\r\nСкрыть непроизводственные сущности:\t{hide_non_standard_entities}\r\nОтображать уведомления о изменении состояния устройства:\t{display_devices_changed_notify}\r\nИзменение устройства:\tДобавлено **{devices_add}** устройство, удалено **{devices_remove}** устройства\r\nИзменение правил преобразования:\tВсего **{trans_rules_count}** правил, обновлено **{trans_rules_count_success}** правил", + "description": "**{nick_name}** Здравствуйте! Подтвердите последнюю информацию о настройке и нажмите «Отправить». Интеграция будет перезагружена с использованием обновленных настроек.\r\n\r\nЯзык интеграции:\t{lang_new}\r\nИмя пользователя:\t{nick_name_new}\r\nРежим отладки Action:\t{action_debug}\r\nСкрыть непроизводственные сущности:\t{hide_non_standard_entities}\r\nПоложение закрытых штор:\t{cover_pos_new}\r\nОтображать уведомления о изменении состояния устройства:\t{display_devices_changed_notify}\r\nИзменение устройства:\tДобавлено **{devices_add}** устройство, удалено **{devices_remove}** устройства\r\nИзменение правил преобразования:\tВсего **{trans_rules_count}** правил, обновлено **{trans_rules_count_success}** правил", "data": { "confirm": "Подтвердить изменения" } diff --git a/custom_components/xiaomi_home/translations/zh-Hans.json b/custom_components/xiaomi_home/translations/zh-Hans.json index 67c134c..328f62c 100644 --- a/custom_components/xiaomi_home/translations/zh-Hans.json +++ b/custom_components/xiaomi_home/translations/zh-Hans.json @@ -124,7 +124,8 @@ "display_devices_changed_notify": "显示设备状态变化通知", "update_trans_rules": "更新实体转换规则", "update_lan_ctrl_config": "更新局域网控制配置", - "network_detect_config": "集成网络配置" + "network_detect_config": "集成网络配置", + "cover_closed_position": "窗帘关闭位置" } }, "update_user_info": { @@ -183,7 +184,7 @@ }, "config_confirm": { "title": "确认配置", - "description": "**{nick_name}** 您好!请确认最新的配置信息,然后点击“提交”。\r\n集成将会使用更新后的配置重新载入。\r\n\r\n集成语言:\t{lang_new}\r\n用户昵称:\t{nick_name_new}\r\nAction 调试模式:\t{action_debug}\r\n隐藏非标准生成实体:\t{hide_non_standard_entities}\r\n显示设备状态变化通知:\t{display_devices_changed_notify}\r\n设备变化:\t新增 **{devices_add}** 个设备,移除 **{devices_remove}** 个设备\r\n转换规则变化:\t共条 **{trans_rules_count}** 规则,更新 **{trans_rules_count_success}** 条规则", + "description": "**{nick_name}** 您好!请确认最新的配置信息,然后点击“提交”。\r\n集成将会使用更新后的配置重新载入。\r\n\r\n集成语言:\t{lang_new}\r\n用户昵称:\t{nick_name_new}\r\nAction 调试模式:\t{action_debug}\r\n隐藏非标准生成实体:\t{hide_non_standard_entities}\r\n窗帘关闭位置:\t{cover_pos_new}\r\n显示设备状态变化通知:\t{display_devices_changed_notify}\r\n设备变化:\t新增 **{devices_add}** 个设备,移除 **{devices_remove}** 个设备\r\n转换规则变化:\t共条 **{trans_rules_count}** 规则,更新 **{trans_rules_count_success}** 条规则", "data": { "confirm": "确认修改" } diff --git a/custom_components/xiaomi_home/translations/zh-Hant.json b/custom_components/xiaomi_home/translations/zh-Hant.json index 68cc982..e2997a7 100644 --- a/custom_components/xiaomi_home/translations/zh-Hant.json +++ b/custom_components/xiaomi_home/translations/zh-Hant.json @@ -124,7 +124,8 @@ "display_devices_changed_notify": "顯示設備狀態變化通知", "update_trans_rules": "更新實體轉換規則", "update_lan_ctrl_config": "更新局域網控制配置", - "network_detect_config": "集成網絡配置" + "network_detect_config": "集成網絡配置", + "cover_closed_position": "窗簾關閉位置" } }, "update_user_info": { @@ -183,7 +184,7 @@ }, "config_confirm": { "title": "確認配置", - "description": "**{nick_name}** 您好!請確認最新的配置信息,然後點擊“提交”。\r\n集成將會使用更新後的配置重新載入。\r\n\r\n集成語言:\t{lang_new}\r\n用戶暱稱:\t{nick_name_new}\r\nAction 調試模式:\t{action_debug}\r\n隱藏非標準生成實體:\t{hide_non_standard_entities}\r\n顯示設備狀態變化通知:\t{display_devices_changed_notify}\r\n設備變化:\t新增 **{devices_add}** 個設備,移除 **{devices_remove}** 個設備\r\n轉換規則變化:\t共條 **{trans_rules_count}** 規則,更新 **{trans_rules_count_success}** 條規則", + "description": "**{nick_name}** 您好!請確認最新的配置信息,然後點擊“提交”。\r\n集成將會使用更新後的配置重新載入。\r\n\r\n集成語言:\t{lang_new}\r\n用戶暱稱:\t{nick_name_new}\r\nAction 調試模式:\t{action_debug}\r\n隱藏非標準生成實體:\t{hide_non_standard_entities}\r\n窗簾關閉位置:\t{cover_pos_new}\r\n顯示設備狀態變化通知:\t{display_devices_changed_notify}\r\n設備變化:\t新增 **{devices_add}** 個設備,移除 **{devices_remove}** 個設備\r\n轉換規則變化:\t共條 **{trans_rules_count}** 規則,更新 **{trans_rules_count_success}** 條規則", "data": { "confirm": "確認修改" }