From 80e646637af2422a5fb195bf302db844c9faf1cb Mon Sep 17 00:00:00 2001 From: Setsuna Date: Thu, 16 Oct 2025 00:27:04 +0800 Subject: [PATCH] feat: Add COLMO Turing Central AC 1. Add device mapping of multiple Turing ACs in T0xAC.py 2. Necessary changes in load_device_config() to support tuple or re pattern as the device map key 3. Update in climate.py to support target humidity feature 4. Add missing translations and icons --- .../midea_auto_cloud/__init__.py | 15 +++- custom_components/midea_auto_cloud/climate.py | 57 +++++++++++++ .../midea_auto_cloud/device_mapping/T0xAC.py | 81 +++++++++++++++++++ custom_components/midea_auto_cloud/icons.json | 26 ++++++ .../midea_auto_cloud/translations/en.json | 21 ++++- .../translations/zh-Hans.json | 25 +++++- 6 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 custom_components/midea_auto_cloud/icons.json diff --git a/custom_components/midea_auto_cloud/__init__.py b/custom_components/midea_auto_cloud/__init__.py index 3012673..bafe64a 100644 --- a/custom_components/midea_auto_cloud/__init__.py +++ b/custom_components/midea_auto_cloud/__init__.py @@ -1,6 +1,7 @@ import os import base64 from importlib import import_module +import re from homeassistant.config_entries import ConfigEntry from homeassistant.util.json import load_json @@ -100,10 +101,16 @@ async def load_device_config(hass: HomeAssistant, device_type, sn8): device_path = f".device_mapping.{'T0x%02X' % device_type}" try: mapping_module = import_module(device_path, __package__) - if sn8 in mapping_module.DEVICE_MAPPING.keys(): - json_data = mapping_module.DEVICE_MAPPING[sn8] - elif "default" in mapping_module.DEVICE_MAPPING: - json_data = mapping_module.DEVICE_MAPPING["default"] + for key, config in mapping_module.DEVICE_MAPPING.items(): + # support tuple & regular expression pattern to support multiple sn8 sharing one mapping + if (key == sn8) or (isinstance(key, tuple) and sn8 in key) or (isinstance(key, str) and re.match(key, sn8)): + json_data = config + break + else: + if "default" in mapping_module.DEVICE_MAPPING: + json_data = mapping_module.DEVICE_MAPPING["default"] + else: + MideaLogger.warning(f"No mapping found for sn8 {sn8} in type {'T0x%02X' % device_type}") except ModuleNotFoundError: MideaLogger.warning(f"Can't load mapping file for type {'T0x%02X' % device_type}") diff --git a/custom_components/midea_auto_cloud/climate.py b/custom_components/midea_auto_cloud/climate.py index 5291bfd..6a827e7 100644 --- a/custom_components/midea_auto_cloud/climate.py +++ b/custom_components/midea_auto_cloud/climate.py @@ -79,6 +79,10 @@ class MideaClimateEntity(MideaEntity, ClimateEntity): self._key_max_temp = self._config.get("max_temp") self._key_current_temperature = self._config.get("current_temperature") self._key_target_temperature = self._config.get("target_temperature") + self._key_min_humidity = self._config.get("min_humidity") + self._key_max_humidity = self._config.get("max_humidity") + self._key_current_humidity = self._config.get("current_humidity") + self._key_target_humidity = self._config.get("target_humidity") self._attr_temperature_unit = self._config.get("temperature_unit") self._attr_precision = self._config.get("precision") @@ -89,6 +93,8 @@ class MideaClimateEntity(MideaEntity, ClimateEntity): features |= ClimateEntityFeature.TURN_OFF if self._key_target_temperature is not None: features |= ClimateEntityFeature.TARGET_TEMPERATURE + if self._key_target_humidity is not None: + features |= ClimateEntityFeature.TARGET_HUMIDITY if self._key_preset_modes is not None: features |= ClimateEntityFeature.PRESET_MODE # if self._key_aux_heat is not None: @@ -137,6 +143,28 @@ class MideaClimateEntity(MideaEntity, ClimateEntity): return None return None + @property + def current_humidity(self) -> float | None: + """Return the current humidity.""" + humidity = self._get_nested_value(self._key_current_humidity) + if humidity is not None: + try: + return float(humidity) + except (ValueError, TypeError): + return None + return None + + @property + def target_humidity(self) -> float | None: + """Return the humidity we try to reach.""" + humidity = self._get_nested_value(self._key_target_humidity) + if humidity is not None: + try: + return float(humidity) + except (ValueError, TypeError): + return None + return None + @property def min_temp(self): if isinstance(self._key_min_temp, str): @@ -151,6 +179,20 @@ class MideaClimateEntity(MideaEntity, ClimateEntity): else: return float(self._key_max_temp) + @property + def min_humidity(self): + if isinstance(self._key_min_humidity, str): + return float(self.device_attributes.get(self._key_min_humidity, 45)) + else: + return float(self._key_min_humidity) + + @property + def max_humidity(self): + if isinstance(self._key_max_humidity, str): + return float(self.device_attributes.get(self._key_max_humidity, 65)) + else: + return float(self._key_max_humidity) + @property def target_temperature_low(self): return self.min_temp @@ -159,6 +201,14 @@ class MideaClimateEntity(MideaEntity, ClimateEntity): def target_temperature_high(self): return self.max_temp + @property + def target_humidity_low(self): + return self.min_humidity + + @property + def target_humidity_high(self): + return self.max_humidity + @property def preset_modes(self): return list(self._key_preset_modes.keys()) @@ -253,6 +303,13 @@ class MideaClimateEntity(MideaEntity, ClimateEntity): new_status[self._key_target_temperature] = temperature await self.async_set_attributes(new_status) + async def async_set_humidity(self, humidity: int): + if self._key_target_humidity is None: + return + new_status = {} + new_status[self._key_target_humidity] = int(humidity) + await self.async_set_attributes(new_status) + async def async_set_fan_mode(self, fan_mode: str): if self._is_central_ac: fan_speed = self._key_fan_modes.get(fan_mode) diff --git a/custom_components/midea_auto_cloud/device_mapping/T0xAC.py b/custom_components/midea_auto_cloud/device_mapping/T0xAC.py index bcb10dc..c8baac9 100644 --- a/custom_components/midea_auto_cloud/device_mapping/T0xAC.py +++ b/custom_components/midea_auto_cloud/device_mapping/T0xAC.py @@ -267,5 +267,86 @@ DEVICE_MAPPING = { }, } } + }, + # Colmo Turing Central AC indoor units, different cooling capacity models share the same config. + ("22396961", "22396963", "22396965", "22396969", "22396973"): { + "rationale": ["off", "on"], + "queries": [{}, {"query_type":"run_status"}], + "centralized": [], + "entities": { + Platform.CLIMATE: { + "thermostat": { + "translation_key": "colmo_turing_central_ac_climate", + "power": "power", + "hvac_modes": { + "off": {"power": "off"}, + "heat": {"power": "on", "mode": "heat"}, + "cool": {"power": "on", "mode": "cool"}, + "fan_only": {"power": "on", "mode": "fan"}, + "dry": {"power": "on", "mode": "dryauto"}, + "auto": {"power": "on", "mode": "dryconstant"}, + # Note: + # For Colmo Turing AC, dry and auto mode is not displayed in the app/controller explicitly. + # Instead it defined 2 custom modes: dryauto (自动抽湿) and dryconstant (温湿灵控/恒温恒湿). + # So I mapped the custom modes to the similar pre-defineds: + # - auto -> dryconstant (温湿灵控/恒温恒湿): able to set target T and H, and auto adjust them to maintain a comfortable environment. + # - dry -> dryauto (自动抽湿): dehumidification mode, under which temperature is not adjustable. + # Translations are also modified (for only colmo_turing_central_ac_climate) accordingly. + }, + "preset_modes": { + "none": { + "energy_save": "off", + }, + "sleep": {"energy_save": "on"} + }, + "fan_modes": { + "silent": {"wind_speed": 20}, + "low": {"wind_speed": 40}, + "medium": {"wind_speed": 60}, + "high": {"wind_speed": 80}, + "full": {"wind_speed": 100}, + "auto": {"wind_speed": 102} + }, + "target_temperature": ["temperature", "small_temperature"], + "current_temperature": "indoor_temperature", + "target_humidity": "dehumidity", + "current_humidity": "indoor_humidity", + "pre_mode": "mode", + "aux_heat": "ptc", + "min_temp": 16, + "max_temp": 30, + "min_humidity": 45, + "max_humidity": 65, + "temperature_unit": UnitOfTemperature.CELSIUS, + "precision": PRECISION_HALVES, + } + }, + Platform.SENSOR: { + "mode": { + "device_class": SensorDeviceClass.ENUM, + }, + "indoor_temperature": { + "device_class": SensorDeviceClass.TEMPERATURE, + "unit_of_measurement": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT + }, + "indoor_humidity": { + "device_class": SensorDeviceClass.HUMIDITY, + "unit_of_measurement": "%", + "state_class": SensorStateClass.MEASUREMENT + }, + "wind_speed_real": { + "device_class": SensorDeviceClass.WIND_SPEED, + "unit_of_measurement": "%", + "state_class": SensorStateClass.MEASUREMENT + } + }, + Platform.SWITCH: { + "power": { + "name": "电源", + "device_class": SwitchDeviceClass.SWITCH, + }, + }, + } } } diff --git a/custom_components/midea_auto_cloud/icons.json b/custom_components/midea_auto_cloud/icons.json new file mode 100644 index 0000000..f482401 --- /dev/null +++ b/custom_components/midea_auto_cloud/icons.json @@ -0,0 +1,26 @@ +{ + "entity": { + "climate": { + "thermostat": { + "state_attributes": { + "fan_mode": { + "state": { + "silent": "mdi:weather-night", + "full": "mdi:fan" + } + } + } + }, + "colmo_turing_central_ac_climate": { + "state_attributes": { + "fan_mode": { + "state": { + "silent": "mdi:weather-night", + "full": "mdi:fan" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/custom_components/midea_auto_cloud/translations/en.json b/custom_components/midea_auto_cloud/translations/en.json index 6559fce..dbd9e54 100644 --- a/custom_components/midea_auto_cloud/translations/en.json +++ b/custom_components/midea_auto_cloud/translations/en.json @@ -336,7 +336,26 @@ "name": "Storage Zone" }, "thermostat": { - "name": "Thermostat" + "name": "Thermostat", + "state_attributes": { + "fan_mode": { + "state": { + "silent": "Silent", + "full": "Full" + } + } + } + }, + "colmo_turing_central_ac_climate": { + "name": "Thermostat", + "state_attributes": { + "fan_mode": { + "state": { + "silent": "Silent", + "full": "Full" + } + } + } }, "water_heater": { "name": "Water Heater" diff --git a/custom_components/midea_auto_cloud/translations/zh-Hans.json b/custom_components/midea_auto_cloud/translations/zh-Hans.json index eb78889..d375813 100644 --- a/custom_components/midea_auto_cloud/translations/zh-Hans.json +++ b/custom_components/midea_auto_cloud/translations/zh-Hans.json @@ -336,7 +336,30 @@ "name": "冷藏区" }, "thermostat": { - "name": "温控器" + "name": "温控器", + "state_attributes": { + "fan_mode": { + "state": { + "silent": "静音", + "full": "强劲" + } + } + } + }, + "colmo_turing_central_ac_climate": { + "name": "温控器", + "state_attributes": { + "fan_mode": { + "state": { + "silent": "静音", + "full": "强劲" + } + } + }, + "state": { + "auto": "温湿灵控", + "dry": "自动抽湿" + } }, "water_heater": { "name": "热水器"