diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a14aec7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,25 @@ +# CHANGELOG + +## v0.1.2 +### Added +- Support Xiaomi Heater devices. https://github.com/XiaoMi/ha_xiaomi_home/issues/124 https://github.com/XiaoMi/ha_xiaomi_home/issues/117 +- Language supports pt, pt-BR. +### Changed +- Adjust the minimum version of HASS core to 2024.4.4 and above versions. +### Fixed + +## v0.1.1 +### Added +### Changed +### Fixed +- Fix humidifier trans rule. https://github.com/XiaoMi/ha_xiaomi_home/issues/59 +- Fix get homeinfo error. https://github.com/XiaoMi/ha_xiaomi_home/issues/22 +- Fix air-conditioner switch on. https://github.com/XiaoMi/ha_xiaomi_home/issues/37 https://github.com/XiaoMi/ha_xiaomi_home/issues/16 +- Fix invalid cover status. https://github.com/XiaoMi/ha_xiaomi_home/issues/11 https://github.com/XiaoMi/ha_xiaomi_home/issues/85 +- Water heater entity add STATE_OFF. https://github.com/XiaoMi/ha_xiaomi_home/issues/105 https://github.com/XiaoMi/ha_xiaomi_home/issues/17 + +## v0.1.0 +### Added +- First version +### Changed +### Fixed diff --git a/doc/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 98% rename from doc/CONTRIBUTING.md rename to CONTRIBUTING.md index ff13ba1..dfcdb65 100644 --- a/doc/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contribution Guidelines -[English](./CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md) +[English](./CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md) Thank you for considering contributing to our project! We appreciate your efforts to make our project better. diff --git a/README.md b/README.md index 6f97f2a..fe3b75c 100644 --- a/README.md +++ b/README.md @@ -323,7 +323,7 @@ Device information service (urn:miot-spec-v2:service:device-information:00007801 ## Multiple Language Support -There are 8 languages available for selection in the config flow language option of Xiaomi Home, including Simplified Chinese, Traditional Chinese, English, Spanish, Russian, French, German, and Japanese. The config flow page in Simplified Chinese and English has been manually reviewed by the developer. Other languages are translated by machine translation. If you want to modify the words and sentences in the config flow page, you need to modify the json file of the certain language in `custom_components/xiaomi_home/translations/` directory. +There are 8 languages available for selection in the config flow language option of Xiaomi Home, including Simplified Chinese, Traditional Chinese, English, Spanish, Russian, French, German, and Japanese. The config flow page in Simplified Chinese and English has been manually reviewed by the developer. Other languages are translated by machine translation. If you want to modify the words and sentences in the config flow page, you need to modify the json file of the certain language in `custom_components/xiaomi_home/translations/` and `custom_components/xiaomi_home/miot/i18n/` directory. When displaying Home Assistant entity name, Xiaomi Home downloads the multiple language file configured by the device vendor from MIoT Cloud, which contains translations for MIoT-Spec-V2 instances of the device. `multi_lang.json` is a locally maintained multiple language dictionary, which has a higher priority than the multiple language file obtained from the cloud and can be used to supplement or modify the multiple language translation of devices. @@ -376,8 +376,8 @@ Example: ## Documents - [License](./LICENSE.md) -- Contribution Guidelines: [English](./doc/CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md) -- [ChangeLog](./doc/CHANGELOG.md) +- Contribution Guidelines: [English](./CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md) +- [ChangeLog](./CHANGELOG.md) - Development Documents: https://developers.home-assistant.io/docs/creating_component_index ## Directory Structure diff --git a/custom_components/xiaomi_home/climate.py b/custom_components/xiaomi_home/climate.py index cc12373..106abb9 100644 --- a/custom_components/xiaomi_home/climate.py +++ b/custom_components/xiaomi_home/climate.py @@ -82,9 +82,12 @@ async def async_setup_entry( new_entities = [] for miot_device in device_list: - for data in miot_device.entity_list.get('climate', []): + for data in miot_device.entity_list.get('air-conditioner', []): new_entities.append( AirConditioner(miot_device=miot_device, entity_data=data)) + for data in miot_device.entity_list.get('heater', []): + new_entities.append( + Heater(miot_device=miot_device, entity_data=data)) if new_entities: async_add_entities(new_entities) @@ -115,7 +118,7 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity): def __init__( self, miot_device: MIoTDevice, entity_data: MIoTEntityData ) -> None: - """Initialize the Climate.""" + """Initialize the Air conditioner.""" super().__init__(miot_device=miot_device, entity_data=entity_data) self._attr_icon = 'mdi:air-conditioner' self._attr_supported_features = ClimateEntityFeature(0) @@ -344,31 +347,31 @@ async def async_set_fan_mode(self, fan_mode): f'set climate prop.fan_mode failed, {fan_mode}, ' f'{self.entity_id}') - @ property + @property def target_temperature(self) -> Optional[float]: """Return the target temperature.""" return self.get_prop_value( prop=self._prop_target_temp) if self._prop_target_temp else None - @ property + @property def target_humidity(self) -> Optional[int]: """Return the target humidity.""" return self.get_prop_value( prop=self._prop_target_humi) if self._prop_target_humi else None - @ property + @property def current_temperature(self) -> Optional[float]: """Return the current temperature.""" return self.get_prop_value( prop=self._prop_env_temp) if self._prop_env_temp else None - @ property + @property def current_humidity(self) -> Optional[int]: """Return the current humidity.""" return self.get_prop_value( prop=self._prop_env_humi) if self._prop_env_humi else None - @ property + @property def hvac_mode(self) -> Optional[HVACMode]: """Return the hvac mode. e.g., heat, cool mode.""" if self.get_prop_value(prop=self._prop_on) is False: @@ -377,7 +380,7 @@ def hvac_mode(self) -> Optional[HVACMode]: map_=self._hvac_mode_map, key=self.get_prop_value(prop=self._prop_mode)) - @ property + @property def fan_mode(self) -> Optional[str]: """Return the fan mode. @@ -387,7 +390,7 @@ def fan_mode(self) -> Optional[str]: map_=self._fan_mode_map, key=self.get_prop_value(prop=self._prop_fan_level)) - @ property + @property def swing_mode(self) -> Optional[str]: """Return the swing mode. @@ -473,3 +476,144 @@ def __ac_state_changed(self, prop: MIoTSpecProperty, value: any) -> None: self._value_ac_state.update(v_ac_state) _LOGGER.debug( 'ac_state update, %s', self._value_ac_state) + + +class Heater(MIoTServiceEntity, ClimateEntity): + """Heater entities for Xiaomi Home.""" + # service: heater + _prop_on: Optional[MIoTSpecProperty] + _prop_mode: Optional[MIoTSpecProperty] + _prop_target_temp: Optional[MIoTSpecProperty] + _prop_heat_level: Optional[MIoTSpecProperty] + # service: environment + _prop_env_temp: Optional[MIoTSpecProperty] + _prop_env_humi: Optional[MIoTSpecProperty] + + _heat_level_map: Optional[dict[int, str]] + + def __init__( + self, miot_device: MIoTDevice, entity_data: MIoTEntityData + ) -> None: + """Initialize the Heater.""" + super().__init__(miot_device=miot_device, entity_data=entity_data) + self._attr_icon = 'mdi:air-conditioner' + self._attr_supported_features = ClimateEntityFeature(0) + self._attr_preset_modes = [] + + self._prop_on = None + self._prop_mode = None + self._prop_target_temp = None + self._prop_heat_level = None + self._prop_env_temp = None + self._prop_env_humi = None + self._heat_level_map = None + + # properties + for prop in entity_data.props: + if prop.name == 'on': + self._attr_supported_features |= ( + ClimateEntityFeature.TURN_ON) + self._attr_supported_features |= ( + ClimateEntityFeature.TURN_OFF) + self._prop_on = prop + elif prop.name == 'target-temperature': + if not isinstance(prop.value_range, dict): + _LOGGER.error( + 'invalid target-temperature value_range format, %s', + self.entity_id) + continue + self._attr_min_temp = prop.value_range['min'] + self._attr_max_temp = prop.value_range['max'] + self._attr_target_temperature_step = prop.value_range['step'] + self._attr_temperature_unit = prop.external_unit + self._attr_supported_features |= ( + ClimateEntityFeature.TARGET_TEMPERATURE) + self._prop_target_temp = prop + elif prop.name == 'heat-level': + if ( + not isinstance(prop.value_list, list) + or not prop.value_list + ): + _LOGGER.error( + 'invalid heat-level value_list, %s', self.entity_id) + continue + self._heat_level_map = { + item['value']: item['description'] + for item in prop.value_list} + self._attr_preset_modes = list(self._heat_level_map.values()) + self._attr_supported_features |= ( + ClimateEntityFeature.PRESET_MODE) + self._prop_heat_level = prop + elif prop.name == 'temperature': + self._prop_env_temp = prop + elif prop.name == 'relative-humidity': + self._prop_env_humi = prop + + # hvac modes + self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF] + + async def async_turn_on(self) -> None: + """Turn the entity on.""" + await self.set_property_async(prop=self._prop_on, value=True) + + async def async_turn_off(self) -> None: + """Turn the entity off.""" + await self.set_property_async(prop=self._prop_on, value=False) + + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Set new target hvac mode.""" + await self.set_property_async( + prop=self._prop_on, value=False + if hvac_mode == HVACMode.OFF else True) + + async def async_set_temperature(self, **kwargs): + """Set new target temperature.""" + if ATTR_TEMPERATURE in kwargs: + temp = kwargs[ATTR_TEMPERATURE] + if temp > self.max_temp: + temp = self.max_temp + elif temp < self.min_temp: + temp = self.min_temp + + await self.set_property_async( + prop=self._prop_target_temp, value=temp) + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set the preset mode.""" + await self.set_property_async( + self._prop_heat_level, + value=self.get_map_value( + map_=self._heat_level_map, description=preset_mode)) + + @property + def target_temperature(self) -> Optional[float]: + """Return the target temperature.""" + return self.get_prop_value( + prop=self._prop_target_temp) if self._prop_target_temp else None + + @property + def current_temperature(self) -> Optional[float]: + """Return the current temperature.""" + return self.get_prop_value( + prop=self._prop_env_temp) if self._prop_env_temp else None + + @property + def current_humidity(self) -> Optional[int]: + """Return the current humidity.""" + return self.get_prop_value( + prop=self._prop_env_humi) if self._prop_env_humi else None + + @property + def hvac_mode(self) -> Optional[HVACMode]: + """Return the hvac mode.""" + return ( + HVACMode.HEAT if self.get_prop_value(prop=self._prop_on) + else HVACMode.OFF) + + @property + def preset_mode(self) -> Optional[str]: + return ( + self.get_map_description( + map_=self._heat_level_map, + key=self.get_prop_value(prop=self._prop_heat_level)) + if self._prop_heat_level else None) diff --git a/custom_components/xiaomi_home/config_flow.py b/custom_components/xiaomi_home/config_flow.py index 4881b68..50eee36 100644 --- a/custom_components/xiaomi_home/config_flow.py +++ b/custom_components/xiaomi_home/config_flow.py @@ -602,8 +602,8 @@ async def async_step_advanced_options(self, user_input=None): last_step=True, ) - @ staticmethod - @ callback + @staticmethod + @callback def async_get_options_flow( config_entry: config_entries.ConfigEntry, ) -> config_entries.OptionsFlow: diff --git a/custom_components/xiaomi_home/light.py b/custom_components/xiaomi_home/light.py index 610882e..49229f5 100644 --- a/custom_components/xiaomi_home/light.py +++ b/custom_components/xiaomi_home/light.py @@ -236,7 +236,7 @@ def color_temp_kelvin(self) -> Optional[int]: """Return the color temperature.""" return self.get_prop_value(prop=self._prop_color_temp) - @ property + @property def rgb_color(self) -> Optional[tuple[int, int, int]]: """Return the rgb color value.""" rgb = self.get_prop_value(prop=self._prop_color) @@ -247,7 +247,7 @@ def rgb_color(self) -> Optional[tuple[int, int, int]]: b = rgb & 0xFF return r, g, b - @ property + @property def effect(self) -> Optional[str]: """Return the current mode.""" return self.__get_mode_description( diff --git a/custom_components/xiaomi_home/manifest.json b/custom_components/xiaomi_home/manifest.json index 67ff027..406fe9d 100644 --- a/custom_components/xiaomi_home/manifest.json +++ b/custom_components/xiaomi_home/manifest.json @@ -25,7 +25,7 @@ "cryptography", "psutil" ], - "version": "v0.1.0", + "version": "v0.1.2", "zeroconf": [ "_miot-central._tcp.local." ] diff --git a/custom_components/xiaomi_home/miot/const.py b/custom_components/xiaomi_home/miot/const.py index 7aec73d..7518ca2 100644 --- a/custom_components/xiaomi_home/miot/const.py +++ b/custom_components/xiaomi_home/miot/const.py @@ -110,11 +110,13 @@ 'zh-Hans': '简体中文', 'zh-Hant': '繁體中文', 'en': 'English', + 'de': 'Deutsch', 'es': 'Español', - 'ru': 'Русский', 'fr': 'Français', - 'de': 'Deutsch', - 'ja': '日本語' + 'ja': '日本語', + 'pt': 'Português', + 'pt-BR': 'Português (Brasil)', + 'ru': 'Русский', } DEFAULT_CTRL_MODE: str = 'auto' diff --git a/custom_components/xiaomi_home/miot/i18n/pt-BR.json b/custom_components/xiaomi_home/miot/i18n/pt-BR.json new file mode 100644 index 0000000..8df352b --- /dev/null +++ b/custom_components/xiaomi_home/miot/i18n/pt-BR.json @@ -0,0 +1,95 @@ +{ + "config": { + "other": { + "devices": "dispositivos", + "found_central_gateway": "encontrado o gateway central local" + }, + "control_mode": { + "auto": "automático", + "cloud": "nuvem" + }, + "room_name_rule": { + "none": "não sincronizado", + "home_room": "Nome da casa e nome do quarto (Xiaomi Home Quarto)", + "room": "Nome do quarto (Quarto)", + "home": "Nome da casa (Xiaomi Home)" + }, + "option_status": { + "enable": "habilitado", + "disable": "desabilitado" + }, + "lan_ctrl_config": { + "notice_net_dup": "\r\n**[Aviso]** Detectado múltiplas interfaces de rede que podem estar conectando à mesma rede, por favor, selecione a correta.", + "net_unavailable": "Interface indisponível" + } + }, + "miot": { + "client": { + "invalid_oauth_info": "Informações de autenticação inválidas, a conexão com a nuvem estará indisponível. Vá para a página de integração do Xiaomi Home e clique em 'Opções' para reautenticar.", + "invalid_device_cache": "Informações de dispositivo no cache inválidas. Vá para a página de integração do Xiaomi Home e clique em 'Opções -> Atualizar lista de dispositivos' para atualizar as informações locais.", + "invalid_cert_info": "Certificado de usuário inválido. A conexão local do gateway central estará indisponível. Vá para a página de integração do Xiaomi Home e clique em 'Opções' para reautenticar.", + "device_cloud_error": "Erro ao obter informações do dispositivo da nuvem. Verifique a conexão da rede local.", + "xiaomi_home_error_title": "Erro de Integração do Xiaomi Home", + "xiaomi_home_error": "Erro detectado em **{nick_name}({uid}, {cloud_server})**. Vá para a página de opções para reconfigurar.\n\n**Erro**: \n{message}", + "device_list_changed_title": "Mudança na lista de dispositivos do Xiaomi Home", + "device_list_changed": "Detectado que as informações do dispositivo **{nick_name}({uid}, {cloud_server})** mudaram. Vá para a página de integração e clique em 'Opções -> Atualizar lista de dispositivos' para atualizar as informações locais.\n\nStatus atual da rede: {network_status}\n{message}\n", + "device_list_add": "\n**{count} dispositivos novos**: \n{message}", + "device_list_del": "\n**{count} dispositivos não disponíveis**: \n{message}", + "device_list_offline": "\n**{count} dispositivos offline**: \n{message}", + "network_status_online": "online", + "network_status_offline": "offline", + "device_exec_error": "Erro na execução" + } + }, + "error": { + "common": { + "-10000": "Erro desconhecido", + "-10001": "Serviço indisponível", + "-10002": "Parâmetro inválido", + "-10003": "Recursos insuficientes", + "-10004": "Erro interno", + "-10005": "Permissões insuficientes", + "-10006": "Execução expirada", + "-10007": "Dispositivo offline ou inexistente", + "-10020": "OAuth2 não autorizado", + "-10030": "Token inválido (HTTP)", + "-10040": "Formato de mensagem inválido", + "-10050": "Certificado inválido", + "-704000000": "Erro desconhecido", + "-704010000": "Não autorizado (o dispositivo pode ter sido excluído)", + "-704014006": "Descrição do dispositivo não encontrada", + "-704030013": "Propriedade não pode ser lida", + "-704030023": "Propriedade não pode ser escrita", + "-704030033": "Propriedade não pode ser assinada", + "-704040002": "Serviço inexistente", + "-704040003": "Propriedade inexistente", + "-704040004": "Evento inexistente", + "-704040005": "Ação inexistente", + "-704040999": "Funcionalidade não lançada", + "-704042001": "Dispositivo inexistente", + "-704042011": "Dispositivo offline", + "-704053036": "Tempo de operação do dispositivo expirado", + "-704053100": "Dispositivo não pode executar esta operação no estado atual", + "-704083036": "Tempo de operação do dispositivo expirado", + "-704090001": "Dispositivo inexistente", + "-704220008": "ID inválido", + "-704220025": "Número de parâmetros de ação incompatível", + "-704220035": "Parâmetro de ação incorreto", + "-704220043": "Valor da propriedade incorreto", + "-704222034": "Valor de retorno de ação incorreto", + "-705004000": "Erro desconhecido", + "-705004501": "Erro desconhecido", + "-705201013": "Propriedade não pode ser lida", + "-705201015": "Erro na execução da ação", + "-705201023": "Propriedade não pode ser escrita", + "-705201033": "Propriedade não pode ser assinada", + "-706012000": "Erro desconhecido", + "-706012013": "Propriedade não pode ser lida", + "-706012015": "Erro na execução da ação", + "-706012023": "Propriedade não pode ser escrita", + "-706012033": "Propriedade não pode ser assinada", + "-706012043": "Valor da propriedade incorreto", + "-706014006": "Descrição do dispositivo não encontrada" + } + } +} \ No newline at end of file diff --git a/custom_components/xiaomi_home/miot/i18n/pt.json b/custom_components/xiaomi_home/miot/i18n/pt.json new file mode 100644 index 0000000..dd30774 --- /dev/null +++ b/custom_components/xiaomi_home/miot/i18n/pt.json @@ -0,0 +1,95 @@ +{ + "config": { + "other": { + "devices": "dispositivos", + "found_central_gateway": ", encontrou a central de gateway local" + }, + "control_mode": { + "auto": "Automático", + "cloud": "Nuvem" + }, + "room_name_rule": { + "none": "Não sincronizar", + "home_room": "Nome da casa e Nome do quarto (Xiaomi Home Quarto)", + "room": "Nome do quarto (Quarto)", + "home": "Nome da casa (Xiaomi Home)" + }, + "option_status": { + "enable": "Habilitar", + "disable": "Desabilitar" + }, + "lan_ctrl_config": { + "notice_net_dup": "\r\n**[Aviso]** Detectado que várias interfaces podem estar conectadas à mesma rede, escolha com cuidado.", + "net_unavailable": "Interface indisponível" + } + }, + "miot": { + "client": { + "invalid_oauth_info": "Informações de autenticação inválidas, a conexão na nuvem ficará indisponível. Por favor, acesse a página de integração do Xiaomi Home e clique em 'Opções' para autenticar novamente.", + "invalid_device_cache": "Erro no cache de informações do dispositivo. Por favor, acesse a página de integração do Xiaomi Home e clique em 'Opções -> Atualizar lista de dispositivos' para atualizar as informações locais.", + "invalid_cert_info": "Certificado de usuário inválido, a conexão com a central local ficará indisponível. Por favor, acesse a página de integração do Xiaomi Home e clique em 'Opções' para autenticar novamente.", + "device_cloud_error": "Erro ao obter informações do dispositivo na nuvem. Verifique a conexão de rede local.", + "xiaomi_home_error_title": "Erro de integração do Xiaomi Home", + "xiaomi_home_error": "Detectado erro em **{nick_name}({uid}, {cloud_server})**. Por favor, acesse a página de opções para reconfigurar.\n\n**Informação do erro**: \n{message}", + "device_list_changed_title": "Mudança na lista de dispositivos do Xiaomi Home", + "device_list_changed": "Detectada alteração nas informações do dispositivo de **{nick_name}({uid}, {cloud_server})**. Por favor, acesse a página de opções de integração e clique em 'Opções -> Atualizar lista de dispositivos' para atualizar as informações locais.\n\nStatus atual da rede: {network_status}\n{message}\n", + "device_list_add": "\n**{count} novos dispositivos**: \n{message}", + "device_list_del": "\n**{count} dispositivos indisponíveis**: \n{message}", + "device_list_offline": "\n**{count} dispositivos offline**: \n{message}", + "network_status_online": "Online", + "network_status_offline": "Offline", + "device_exec_error": "Erro de execução" + } + }, + "error": { + "common": { + "-10000": "Erro desconhecido", + "-10001": "Serviço indisponível", + "-10002": "Parâmetro inválido", + "-10003": "Recursos insuficientes", + "-10004": "Erro interno", + "-10005": "Permissão negada", + "-10006": "Tempo limite de execução", + "-10007": "Dispositivo offline ou inexistente", + "-10020": "Não autorizado (OAuth2)", + "-10030": "Token inválido (HTTP)", + "-10040": "Formato de mensagem inválido", + "-10050": "Certificado inválido", + "-704000000": "Erro desconhecido", + "-704010000": "Não autorizado (o dispositivo pode ter sido removido)", + "-704014006": "Descrição do dispositivo não encontrada", + "-704030013": "Propriedade não legível", + "-704030023": "Propriedade não gravável", + "-704030033": "Propriedade não subscritível", + "-704040002": "Serviço inexistente", + "-704040003": "Propriedade inexistente", + "-704040004": "Evento inexistente", + "-704040005": "Ação inexistente", + "-704040999": "Funcionalidade não disponível", + "-704042001": "Dispositivo inexistente", + "-704042011": "Dispositivo offline", + "-704053036": "Tempo limite de operação do dispositivo", + "-704053100": "O dispositivo não pode executar esta operação no estado atual", + "-704083036": "Tempo limite de operação do dispositivo", + "-704090001": "Dispositivo inexistente", + "-704220008": "ID inválido", + "-704220025": "Número de parâmetros da ação não corresponde", + "-704220035": "Erro nos parâmetros da ação", + "-704220043": "Valor de propriedade inválido", + "-704222034": "Erro no valor de retorno da ação", + "-705004000": "Erro desconhecido", + "-705004501": "Erro desconhecido", + "-705201013": "Propriedade não legível", + "-705201015": "Erro na execução da ação", + "-705201023": "Propriedade não gravável", + "-705201033": "Propriedade não subscritível", + "-706012000": "Erro desconhecido", + "-706012013": "Propriedade não legível", + "-706012015": "Erro na execução da ação", + "-706012023": "Propriedade não gravável", + "-706012033": "Propriedade não subscritível", + "-706012043": "Valor de propriedade inválido", + "-706014006": "Descrição do dispositivo não encontrada" + } + } +} \ No newline at end of file diff --git a/custom_components/xiaomi_home/miot/miot_client.py b/custom_components/xiaomi_home/miot/miot_client.py index 771de41..31c87f0 100644 --- a/custom_components/xiaomi_home/miot/miot_client.py +++ b/custom_components/xiaomi_home/miot/miot_client.py @@ -1760,7 +1760,7 @@ def __request_show_devices_changed_notify( delay_sec, self.__show_devices_changed_notify) -@ staticmethod +@staticmethod async def get_miot_instance_async( hass: HomeAssistant, entry_id: str, entry_data: Optional[dict] = None, persistent_notify: Optional[Callable[[str, str, str], None]] = None diff --git a/custom_components/xiaomi_home/miot/miot_device.py b/custom_components/xiaomi_home/miot/miot_device.py index 56637d6..fa48345 100644 --- a/custom_components/xiaomi_home/miot/miot_device.py +++ b/custom_components/xiaomi_home/miot/miot_device.py @@ -54,8 +54,8 @@ from homeassistant.const import ( CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, - CONCENTRATION_PARTS_PER_MILLION, CONCENTRATION_PARTS_PER_BILLION, + CONCENTRATION_PARTS_PER_MILLION, LIGHT_LUX, PERCENTAGE, SIGNAL_STRENGTH_DECIBELS, @@ -72,7 +72,6 @@ UnitOfPower, UnitOfVolume, UnitOfVolumeFlowRate, - UnitOfConductivity ) from homeassistant.helpers.entity import DeviceInfo from homeassistant.components.switch import SwitchDeviceClass @@ -505,7 +504,8 @@ def parse_miot_property_entity( prop_access.add('read') if prop.writable: prop_access.add('write') - if prop_access != (SPEC_PROP_TRANS_MAP['entities'][platform]['access']): + if prop_access != (SPEC_PROP_TRANS_MAP[ + 'entities'][platform]['access']): return None if prop.format_ not in SPEC_PROP_TRANS_MAP[ 'entities'][platform]['format']: @@ -584,7 +584,8 @@ def spec_transform(self) -> None: self.append_action(action=action) def unit_convert(self, spec_unit: str) -> Optional[str]: - return { + """Convert MIoT unit to Home Assistant unit.""" + unit_map = { 'percentage': PERCENTAGE, 'weeks': UnitOfTime.WEEKS, 'days': UnitOfTime.DAYS, @@ -616,11 +617,21 @@ def unit_convert(self, spec_unit: str) -> Optional[str]: 'm': UnitOfLength.METERS, 'km': UnitOfLength.KILOMETERS, 'm3/h': UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, - 'μS/cm': UnitOfConductivity.MICROSIEMENS_PER_CM, 'gram': UnitOfMass.GRAMS, 'dB': SIGNAL_STRENGTH_DECIBELS, 'kB': UnitOfInformation.KILOBYTES, - }.get(spec_unit, None) + } + + # Handle UnitOfConductivity separately since + # it might not be available in all HA versions + try: + # pylint: disable=import-outside-toplevel + from homeassistant.const import UnitOfConductivity + unit_map['μS/cm'] = UnitOfConductivity.MICROSIEMENS_PER_CM + except ImportError: + unit_map['μS/cm'] = 'μS/cm' + + return unit_map.get(spec_unit, None) def icon_convert(self, spec_unit: str) -> Optional[str]: if spec_unit in ['percentage']: @@ -1170,8 +1181,8 @@ async def async_added_to_hass(self) -> None: handler=self.__on_device_state_changed) # Sub value changed self.miot_device.sub_event( - handler=self.__on_event_occurred, siid=self.service.iid, - eiid=self.spec.iid) + handler=self.__on_event_occurred, + siid=self.service.iid, eiid=self.spec.iid) async def async_will_remove_from_hass(self) -> None: self.miot_device.unsub_device_state( diff --git a/custom_components/xiaomi_home/miot/miot_lan.py b/custom_components/xiaomi_home/miot/miot_lan.py index 4635572..525be2f 100644 --- a/custom_components/xiaomi_home/miot/miot_lan.py +++ b/custom_components/xiaomi_home/miot/miot_lan.py @@ -564,11 +564,11 @@ def __init__( 0, lambda: self._main_loop.create_task( self.init_async())) - @ property + @property def virtual_did(self) -> str: return self._virtual_did - @ property + @property def mev(self) -> MIoTEventLoop: return self._mev diff --git a/custom_components/xiaomi_home/miot/specs/specv2entity.py b/custom_components/xiaomi_home/miot/specs/specv2entity.py index 5c64083..4a57867 100644 --- a/custom_components/xiaomi_home/miot/specs/specv2entity.py +++ b/custom_components/xiaomi_home/miot/specs/specv2entity.py @@ -208,9 +208,32 @@ } } }, - 'entity': 'climate' + 'entity': 'air-conditioner' }, - 'air-condition-outlet': 'air-conditioner' + 'air-condition-outlet': 'air-conditioner', + 'heater': { + 'required': { + 'heater': { + 'required': { + 'properties': { + 'on': {'read', 'write'} + } + }, + 'optional': { + 'properties': {'target-temperature', 'heat-level'} + }, + } + }, + 'optional': { + 'environment': { + 'required': {}, + 'optional': { + 'properties': {'temperature', 'relative-humidity'} + } + }, + }, + 'entity': 'heater' + } } """SPEC_SERVICE_TRANS_MAP diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md deleted file mode 100644 index 4fc27b4..0000000 --- a/doc/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -# CHANGELOG - -## 0.1.0 -### Added -- first version -### Changed -### Fixed diff --git a/doc/README_zh.md b/doc/README_zh.md index cee5c67..742c0ea 100644 --- a/doc/README_zh.md +++ b/doc/README_zh.md @@ -325,7 +325,7 @@ event instance name 下的值表示转换后实体所用的 `_attr_device_class` ## 多语言支持 -米家集成配置选项中可选择的集成使用的语言有简体中文、繁体中文、英文、西班牙语、俄语、法语、德语、日语这八种语言。目前,米家集成配置页面的简体中文和英文已经过人工校审,其他语言由机器翻译。如果您希望修改配置页面的词句,则需要修改 `custom_components/xiaomi_home/translations/` 目录下相应语言的 json 文件。 +米家集成配置选项中可选择的集成使用的语言有简体中文、繁体中文、英文、西班牙语、俄语、法语、德语、日语这八种语言。目前,米家集成配置页面的简体中文和英文已经过人工校审,其他语言由机器翻译。如果您希望修改配置页面的词句,则需要修改 `custom_components/xiaomi_home/translations/` 以及 `custom_components/xiaomi_home/miot/i18n/` 目录下相应语言的 json 文件。 在显示 Home Assistant 实体名称时,米家集成会从小米云下载设备厂商为设备配置的多语言文件,该文件包含设备 MIoT-Spec-V2 实例的多语言翻译。 `multi_lang.json` 是本地维护的多语言配置字典,其优先级高于从云端获取的多语言文件,可用于补充或修改设备的多语言翻译。 diff --git a/install.sh b/install.sh index f625e34..5873f13 100755 --- a/install.sh +++ b/install.sh @@ -20,8 +20,13 @@ rm -rf "$config_path/custom_components/xiaomi_home" script_path=$(dirname "$0") # Change to the script path. cd "$script_path" + # Copy the new version. -cp -r custom_components/xiaomi_home/ "$config_path/custom_components/" +if [ -d "$config_path/custom_components" ]; then + cp -r custom_components/xiaomi_home/ "$config_path/custom_components/" +else + cp -r custom_components/ "$config_path/custom_components/" +fi # Done. echo "Xiaomi Home installation is completed. Please restart Home Assistant."