Skip to content

Commit fa38293

Browse files
authored
Merge branch 'XiaoMi:main' into main
2 parents b7fc534 + caec202 commit fa38293

19 files changed

+746
-48
lines changed

CHANGELOG.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# CHANGELOG
2+
3+
## v0.1.2
4+
### Added
5+
- Support Xiaomi Heater devices. https://github.com/XiaoMi/ha_xiaomi_home/issues/124 https://github.com/XiaoMi/ha_xiaomi_home/issues/117
6+
- Language supports pt, pt-BR.
7+
### Changed
8+
- Adjust the minimum version of HASS core to 2024.4.4 and above versions.
9+
### Fixed
10+
11+
## v0.1.1
12+
### Added
13+
### Changed
14+
### Fixed
15+
- Fix humidifier trans rule. https://github.com/XiaoMi/ha_xiaomi_home/issues/59
16+
- Fix get homeinfo error. https://github.com/XiaoMi/ha_xiaomi_home/issues/22
17+
- Fix air-conditioner switch on. https://github.com/XiaoMi/ha_xiaomi_home/issues/37 https://github.com/XiaoMi/ha_xiaomi_home/issues/16
18+
- Fix invalid cover status. https://github.com/XiaoMi/ha_xiaomi_home/issues/11 https://github.com/XiaoMi/ha_xiaomi_home/issues/85
19+
- Water heater entity add STATE_OFF. https://github.com/XiaoMi/ha_xiaomi_home/issues/105 https://github.com/XiaoMi/ha_xiaomi_home/issues/17
20+
21+
## v0.1.0
22+
### Added
23+
- First version
24+
### Changed
25+
### Fixed

doc/CONTRIBUTING.md CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Contribution Guidelines
22

3-
[English](./CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md)
3+
[English](./CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md)
44

55
Thank you for considering contributing to our project! We appreciate your efforts to make our project better.
66

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -376,8 +376,8 @@ Example:
376376
## Documents
377377

378378
- [License](./LICENSE.md)
379-
- Contribution Guidelines: [English](./doc/CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md)
380-
- [ChangeLog](./doc/CHANGELOG.md)
379+
- Contribution Guidelines: [English](./CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md)
380+
- [ChangeLog](./CHANGELOG.md)
381381
- Development Documents: https://developers.home-assistant.io/docs/creating_component_index
382382

383383
## Directory Structure

custom_components/xiaomi_home/climate.py

+153-9
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,12 @@ async def async_setup_entry(
8282

8383
new_entities = []
8484
for miot_device in device_list:
85-
for data in miot_device.entity_list.get('climate', []):
85+
for data in miot_device.entity_list.get('air-conditioner', []):
8686
new_entities.append(
8787
AirConditioner(miot_device=miot_device, entity_data=data))
88+
for data in miot_device.entity_list.get('heater', []):
89+
new_entities.append(
90+
Heater(miot_device=miot_device, entity_data=data))
8891

8992
if new_entities:
9093
async_add_entities(new_entities)
@@ -115,7 +118,7 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
115118
def __init__(
116119
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
117120
) -> None:
118-
"""Initialize the Climate."""
121+
"""Initialize the Air conditioner."""
119122
super().__init__(miot_device=miot_device, entity_data=entity_data)
120123
self._attr_icon = 'mdi:air-conditioner'
121124
self._attr_supported_features = ClimateEntityFeature(0)
@@ -344,31 +347,31 @@ async def async_set_fan_mode(self, fan_mode):
344347
f'set climate prop.fan_mode failed, {fan_mode}, '
345348
f'{self.entity_id}')
346349

347-
@ property
350+
@property
348351
def target_temperature(self) -> Optional[float]:
349352
"""Return the target temperature."""
350353
return self.get_prop_value(
351354
prop=self._prop_target_temp) if self._prop_target_temp else None
352355

353-
@ property
356+
@property
354357
def target_humidity(self) -> Optional[int]:
355358
"""Return the target humidity."""
356359
return self.get_prop_value(
357360
prop=self._prop_target_humi) if self._prop_target_humi else None
358361

359-
@ property
362+
@property
360363
def current_temperature(self) -> Optional[float]:
361364
"""Return the current temperature."""
362365
return self.get_prop_value(
363366
prop=self._prop_env_temp) if self._prop_env_temp else None
364367

365-
@ property
368+
@property
366369
def current_humidity(self) -> Optional[int]:
367370
"""Return the current humidity."""
368371
return self.get_prop_value(
369372
prop=self._prop_env_humi) if self._prop_env_humi else None
370373

371-
@ property
374+
@property
372375
def hvac_mode(self) -> Optional[HVACMode]:
373376
"""Return the hvac mode. e.g., heat, cool mode."""
374377
if self.get_prop_value(prop=self._prop_on) is False:
@@ -377,7 +380,7 @@ def hvac_mode(self) -> Optional[HVACMode]:
377380
map_=self._hvac_mode_map,
378381
key=self.get_prop_value(prop=self._prop_mode))
379382

380-
@ property
383+
@property
381384
def fan_mode(self) -> Optional[str]:
382385
"""Return the fan mode.
383386
@@ -387,7 +390,7 @@ def fan_mode(self) -> Optional[str]:
387390
map_=self._fan_mode_map,
388391
key=self.get_prop_value(prop=self._prop_fan_level))
389392

390-
@ property
393+
@property
391394
def swing_mode(self) -> Optional[str]:
392395
"""Return the swing mode.
393396
@@ -473,3 +476,144 @@ def __ac_state_changed(self, prop: MIoTSpecProperty, value: any) -> None:
473476
self._value_ac_state.update(v_ac_state)
474477
_LOGGER.debug(
475478
'ac_state update, %s', self._value_ac_state)
479+
480+
481+
class Heater(MIoTServiceEntity, ClimateEntity):
482+
"""Heater entities for Xiaomi Home."""
483+
# service: heater
484+
_prop_on: Optional[MIoTSpecProperty]
485+
_prop_mode: Optional[MIoTSpecProperty]
486+
_prop_target_temp: Optional[MIoTSpecProperty]
487+
_prop_heat_level: Optional[MIoTSpecProperty]
488+
# service: environment
489+
_prop_env_temp: Optional[MIoTSpecProperty]
490+
_prop_env_humi: Optional[MIoTSpecProperty]
491+
492+
_heat_level_map: Optional[dict[int, str]]
493+
494+
def __init__(
495+
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
496+
) -> None:
497+
"""Initialize the Heater."""
498+
super().__init__(miot_device=miot_device, entity_data=entity_data)
499+
self._attr_icon = 'mdi:air-conditioner'
500+
self._attr_supported_features = ClimateEntityFeature(0)
501+
self._attr_preset_modes = []
502+
503+
self._prop_on = None
504+
self._prop_mode = None
505+
self._prop_target_temp = None
506+
self._prop_heat_level = None
507+
self._prop_env_temp = None
508+
self._prop_env_humi = None
509+
self._heat_level_map = None
510+
511+
# properties
512+
for prop in entity_data.props:
513+
if prop.name == 'on':
514+
self._attr_supported_features |= (
515+
ClimateEntityFeature.TURN_ON)
516+
self._attr_supported_features |= (
517+
ClimateEntityFeature.TURN_OFF)
518+
self._prop_on = prop
519+
elif prop.name == 'target-temperature':
520+
if not isinstance(prop.value_range, dict):
521+
_LOGGER.error(
522+
'invalid target-temperature value_range format, %s',
523+
self.entity_id)
524+
continue
525+
self._attr_min_temp = prop.value_range['min']
526+
self._attr_max_temp = prop.value_range['max']
527+
self._attr_target_temperature_step = prop.value_range['step']
528+
self._attr_temperature_unit = prop.external_unit
529+
self._attr_supported_features |= (
530+
ClimateEntityFeature.TARGET_TEMPERATURE)
531+
self._prop_target_temp = prop
532+
elif prop.name == 'heat-level':
533+
if (
534+
not isinstance(prop.value_list, list)
535+
or not prop.value_list
536+
):
537+
_LOGGER.error(
538+
'invalid heat-level value_list, %s', self.entity_id)
539+
continue
540+
self._heat_level_map = {
541+
item['value']: item['description']
542+
for item in prop.value_list}
543+
self._attr_preset_modes = list(self._heat_level_map.values())
544+
self._attr_supported_features |= (
545+
ClimateEntityFeature.PRESET_MODE)
546+
self._prop_heat_level = prop
547+
elif prop.name == 'temperature':
548+
self._prop_env_temp = prop
549+
elif prop.name == 'relative-humidity':
550+
self._prop_env_humi = prop
551+
552+
# hvac modes
553+
self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
554+
555+
async def async_turn_on(self) -> None:
556+
"""Turn the entity on."""
557+
await self.set_property_async(prop=self._prop_on, value=True)
558+
559+
async def async_turn_off(self) -> None:
560+
"""Turn the entity off."""
561+
await self.set_property_async(prop=self._prop_on, value=False)
562+
563+
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
564+
"""Set new target hvac mode."""
565+
await self.set_property_async(
566+
prop=self._prop_on, value=False
567+
if hvac_mode == HVACMode.OFF else True)
568+
569+
async def async_set_temperature(self, **kwargs):
570+
"""Set new target temperature."""
571+
if ATTR_TEMPERATURE in kwargs:
572+
temp = kwargs[ATTR_TEMPERATURE]
573+
if temp > self.max_temp:
574+
temp = self.max_temp
575+
elif temp < self.min_temp:
576+
temp = self.min_temp
577+
578+
await self.set_property_async(
579+
prop=self._prop_target_temp, value=temp)
580+
581+
async def async_set_preset_mode(self, preset_mode: str) -> None:
582+
"""Set the preset mode."""
583+
await self.set_property_async(
584+
self._prop_heat_level,
585+
value=self.get_map_value(
586+
map_=self._heat_level_map, description=preset_mode))
587+
588+
@property
589+
def target_temperature(self) -> Optional[float]:
590+
"""Return the target temperature."""
591+
return self.get_prop_value(
592+
prop=self._prop_target_temp) if self._prop_target_temp else None
593+
594+
@property
595+
def current_temperature(self) -> Optional[float]:
596+
"""Return the current temperature."""
597+
return self.get_prop_value(
598+
prop=self._prop_env_temp) if self._prop_env_temp else None
599+
600+
@property
601+
def current_humidity(self) -> Optional[int]:
602+
"""Return the current humidity."""
603+
return self.get_prop_value(
604+
prop=self._prop_env_humi) if self._prop_env_humi else None
605+
606+
@property
607+
def hvac_mode(self) -> Optional[HVACMode]:
608+
"""Return the hvac mode."""
609+
return (
610+
HVACMode.HEAT if self.get_prop_value(prop=self._prop_on)
611+
else HVACMode.OFF)
612+
613+
@property
614+
def preset_mode(self) -> Optional[str]:
615+
return (
616+
self.get_map_description(
617+
map_=self._heat_level_map,
618+
key=self.get_prop_value(prop=self._prop_heat_level))
619+
if self._prop_heat_level else None)

custom_components/xiaomi_home/config_flow.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,8 @@ async def display_device_filter_form(self, reason: str):
564564
last_step=False,
565565
)
566566

567-
@ staticmethod
568-
@ callback
567+
@staticmethod
568+
@callback
569569
def async_get_options_flow(
570570
config_entry: config_entries.ConfigEntry,
571571
) -> config_entries.OptionsFlow:

custom_components/xiaomi_home/cover.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ class Cover(MIoTServiceEntity, CoverEntity):
9797
_prop_motor_value_close: Optional[int]
9898
_prop_motor_value_pause: Optional[int]
9999
_prop_status: Optional[MIoTSpecProperty]
100-
_prop_status_opening: Optional[bool]
101-
_prop_status_closing: Optional[bool]
102-
_prop_status_stop: Optional[bool]
100+
_prop_status_opening: Optional[int]
101+
_prop_status_closing: Optional[int]
102+
_prop_status_stop: Optional[int]
103103
_prop_current_position: Optional[MIoTSpecProperty]
104104
_prop_target_position: Optional[MIoTSpecProperty]
105105
_prop_position_value_min: Optional[int]
@@ -120,6 +120,9 @@ def __init__(
120120
self._prop_motor_value_close = None
121121
self._prop_motor_value_pause = None
122122
self._prop_status = None
123+
self._prop_status_opening = None
124+
self._prop_status_closing = None
125+
self._prop_status_stop = None
123126
self._prop_current_position = None
124127
self._prop_target_position = None
125128
self._prop_position_value_min = None
@@ -159,11 +162,11 @@ def __init__(
159162
'status value_list is None, %s', self.entity_id)
160163
continue
161164
for item in prop.value_list:
162-
if item['name'].lower() in ['opening']:
165+
if item['name'].lower() in ['opening', 'open']:
163166
self._prop_status_opening = item['value']
164-
elif item['name'].lower() in ['closing']:
167+
elif item['name'].lower() in ['closing', 'close']:
165168
self._prop_status_closing = item['value']
166-
elif item['name'].lower() in ['stop']:
169+
elif item['name'].lower() in ['stop', 'pause']:
167170
self._prop_status_stop = item['value']
168171
self._prop_status = prop
169172
elif prop.name == 'current-position':

custom_components/xiaomi_home/light.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ def color_temp_kelvin(self) -> Optional[int]:
236236
"""Return the color temperature."""
237237
return self.get_prop_value(prop=self._prop_color_temp)
238238

239-
@ property
239+
@property
240240
def rgb_color(self) -> Optional[tuple[int, int, int]]:
241241
"""Return the rgb color value."""
242242
rgb = self.get_prop_value(prop=self._prop_color)
@@ -247,7 +247,7 @@ def rgb_color(self) -> Optional[tuple[int, int, int]]:
247247
b = rgb & 0xFF
248248
return r, g, b
249249

250-
@ property
250+
@property
251251
def effect(self) -> Optional[str]:
252252
"""Return the current mode."""
253253
return self.__get_mode_description(

custom_components/xiaomi_home/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"cryptography",
2626
"psutil"
2727
],
28-
"version": "v0.1.0",
28+
"version": "v0.1.2",
2929
"zeroconf": [
3030
"_miot-central._tcp.local."
3131
]

custom_components/xiaomi_home/miot/const.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,13 @@
110110
'zh-Hans': '简体中文',
111111
'zh-Hant': '繁體中文',
112112
'en': 'English',
113+
'de': 'Deutsch',
113114
'es': 'Español',
114-
'ru': 'Русский',
115115
'fr': 'Français',
116-
'de': 'Deutsch',
117-
'ja': '日本語'
116+
'ja': '日本語',
117+
'pt': 'Português',
118+
'pt-BR': 'Português (Brasil)',
119+
'ru': 'Русский',
118120
}
119121

120122
DEFAULT_CTRL_MODE: str = 'auto'

0 commit comments

Comments
 (0)