diff --git a/custom_components/xiaomi_home/climate.py b/custom_components/xiaomi_home/climate.py index 909fee4..ccda2b6 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) @@ -473,3 +476,108 @@ 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] + # service: environment + _prop_env_temp: Optional[MIoTSpecProperty] + _prop_env_humi: Optional[MIoTSpecProperty] + + 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._prop_on = None + self._prop_mode = None + self._prop_target_temp = None + self._prop_env_temp = None + self._prop_env_humi = 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 == '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) + + @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) diff --git a/custom_components/xiaomi_home/miot/specs/specv2entity.py b/custom_components/xiaomi_home/miot/specs/specv2entity.py index 5c64083..084cb1a 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'} + }, + } + }, + 'optional': { + 'environment': { + 'required': {}, + 'optional': { + 'properties': {'temperature', 'relative-humidity'} + } + }, + }, + 'entity': 'heater' + } } """SPEC_SERVICE_TRANS_MAP