diff --git a/custom_components/cozytouch/capability.py b/custom_components/cozytouch/capability.py index 16c1f4e..5c21fe9 100644 --- a/custom_components/cozytouch/capability.py +++ b/custom_components/cozytouch/capability.py @@ -68,6 +68,10 @@ def get_capability_infos(modelInfos: dict, capabilityId: int, capabilityValue: s capability.pop("lowestValueCapabilityId") capability.pop("highestValueCapabilityId") capability["icon"] = "mdi:heat-pump" + elif modelInfos["type"] == CozytouchDeviceType.THERMOSTAT: + capability["name"] = "heat" + capability["icon"] = "mdi:thermostat" + capability["targetCapabilityId"] = 40 else: capability["name"] = "heat" @@ -369,9 +373,8 @@ def get_capability_infos(modelInfos: dict, capabilityId: int, capabilityValue: s capability["highestValueCapabilityId"] = 161 elif capabilityId == 177: - if modelInfos["type"] == CozytouchDeviceType.GAZ_BOILER: + if modelInfos["type"] in [CozytouchDeviceType.GAZ_BOILER, CozytouchDeviceType.THERMOSTAT]: return {} - capability["name"] = "target_cool_temperature" capability["type"] = "temperature_adjustment_number" capability["category"] = "sensor" @@ -623,21 +626,24 @@ def get_capability_infos(modelInfos: dict, capabilityId: int, capabilityValue: s capability["icon"] = "mdi:fire" elif capabilityId == 100505: + if modelInfos["type"] == CozytouchDeviceType.THERMOSTAT: + return {} capability["name"] = "powerful_mode" capability["type"] = "switch" capability["category"] = "sensor" capability["icon"] = "mdi:wind-power" elif capabilityId == 100506: - if modelInfos["type"] == CozytouchDeviceType.TOWEL_RACK: - capability = {} - else: - capability["name"] = "presence_mode" - capability["type"] = "switch" - capability["category"] = "sensor" - capability["icon"] = "mdi:account" + if modelInfos["type"] in [CozytouchDeviceType.TOWEL_RACK, CozytouchDeviceType.THERMOSTAT]: + return {} + capability["name"] = "presence_mode" + capability["type"] = "switch" + capability["category"] = "sensor" + capability["icon"] = "mdi:account" elif capabilityId == 100507: + if modelInfos["type"] == CozytouchDeviceType.THERMOSTAT: + return {} capability["name"] = "eco_mode" capability["type"] = "switch" capability["category"] = "sensor" @@ -714,18 +720,24 @@ def get_capability_infos(modelInfos: dict, capabilityId: int, capabilityValue: s capability["category"] = "diag" elif capabilityId == 100802: + if modelInfos["type"] == CozytouchDeviceType.THERMOSTAT: + return {} capability["name"] = "quiet_mode" capability["type"] = "switch" capability["category"] = "sensor" capability["icon"] = "mdi:fan-minus" elif capabilityId == 100804: + if modelInfos["type"] == CozytouchDeviceType.THERMOSTAT: + return {} capability["name"] = "swing_mode" capability["type"] = "switch" capability["category"] = "sensor" capability["icon"] = "mdi:arrow-oscillating" elif capabilityId == 104044: + if modelInfos["type"] == CozytouchDeviceType.THERMOSTAT: + return {} capability["name"] = "boost_mode" capability["type"] = "switch" capability["category"] = "sensor" diff --git a/custom_components/cozytouch/hub.py b/custom_components/cozytouch/hub.py index f100069..94c7652 100644 --- a/custom_components/cozytouch/hub.py +++ b/custom_components/cozytouch/hub.py @@ -7,6 +7,7 @@ from datetime import UTC, datetime, time as t, timedelta, timezone import json import logging +from typing import Any, Dict from aiohttp import ClientSession, ContentTypeError, FormData @@ -226,7 +227,7 @@ def update_devices_from_json_data(self, json_data) -> None: "modelId": remote_device["modelId"], "productId": remote_device["productId"], "zoneId": remote_device["zoneId"], - "modelInfos": get_model_infos(remote_device["modelId"]), + "modelInfos": get_model_infos(remote_device["modelId"], remote_device["tags"]), "capabilities": [], "tags": [], } @@ -328,7 +329,7 @@ def get_zone_name(self, zoneId: int | None = None) -> str: return str(zoneId) - def get_model_infos(self, deviceId: int | None = None) -> str: + def get_model_infos(self, deviceId: int | None = None) -> dict: """Get model infos.""" if not deviceId: deviceId = self._deviceId @@ -336,6 +337,7 @@ def get_model_infos(self, deviceId: int | None = None) -> str: for dev in self._devices: if dev["deviceId"] == deviceId: zoneId = dev["zoneId"] + tags = dev["tags"] # Special case for sub-devices, use master zone Id for masterDev in self._devices: @@ -350,9 +352,9 @@ def get_model_infos(self, deviceId: int | None = None) -> str: zoneId = masterDev["zoneId"] break - return get_model_infos(dev["modelId"], self.get_zone_name(zoneId)) + return get_model_infos(dev["modelId"], tags, self.get_zone_name(zoneId)) - return get_model_infos(-1) + return get_model_infos(-1, []) def get_serial_number(self, deviceId: int | None = None) -> str: """Get serial number.""" @@ -374,7 +376,7 @@ def get_capabilities_for_device(self, deviceId: int | None = None): capabilities = [] for dev in self._devices: if dev["deviceId"] == deviceId: - modelInfos = get_model_infos(dev["modelId"]) + modelInfos = get_model_infos(dev["modelId"], dev["tags"]) for capability in dev["capabilities"]: capability_infos = get_capability_infos( modelInfos, diff --git a/custom_components/cozytouch/model.py b/custom_components/cozytouch/model.py index aa9df2c..427afa2 100644 --- a/custom_components/cozytouch/model.py +++ b/custom_components/cozytouch/model.py @@ -18,6 +18,7 @@ """ # noqa: D205 from enum import StrEnum +import logging from homeassistant.components.climate import HVACMode from homeassistant.components.climate.const import ( @@ -54,7 +55,7 @@ class CozytouchDeviceType(StrEnum): HUB = "hub" -def get_model_infos(modelId: int, zoneName: str | None = None): +def get_model_infos(modelId: int, tags: list, zoneName: str | None = None) -> dict: """Return infos from model ID.""" modelInfos = {"modelId": modelId, "HVACModesCapabilityId": {7, 8}} @@ -186,38 +187,114 @@ def get_model_infos(modelId: int, zoneName: str | None = None): } elif modelId >= 557 and modelId <= 561: - name = "Air Conditioner " + name = "Unknown product (" + str(modelId) + ")" + modelInfos["type"] = CozytouchDeviceType.UNKNOWN + modelInfos["HVACModes"] = { + 0: HVACMode.OFF, + 4: HVACMode.HEAT, + } + + childrenIds = [] + for tag in tags if tags is not None else []: + if ( + "label" in tag + and tag["label"] == "iothubChildrenIds" + and "value" in tag + ): + childrenIds = tag["value"].split(",") + break + + if any(childId.startswith("UI_") for childId in childrenIds): # Air conditioner detected + name = "Air Conditioner " + modelInfos["type"] = CozytouchDeviceType.AC + modelInfos["currentTemperatureAvailable"] = False + modelInfos["quietModeAvailable"] = True + + modelInfos["fanModes"] = { + 1: FAN_LOW, + 2: FAN_MEDIUM, + 3: FAN_HIGH, + 5: FAN_AUTO, + } + + modelInfos["swingModes"] = { + 1: SWING_MODE_UP, + 2: SWING_MODE_MIDDLE_UP, + 3: SWING_MODE_MIDDLE_DOWN, + 4: SWING_MODE_DOWN, + } + + modelInfos["HVACModes"] = { + 0: HVACMode.OFF, + 1: HVACMode.AUTO, + 3: HVACMode.COOL, + 4: HVACMode.HEAT, + 7: HVACMode.FAN_ONLY, + 8: HVACMode.DRY, + } + elif any(childId.startswith("THZONE_") for childId in childrenIds): # Thermostat detected + # NOTE: not sure about the name here as we are indeed controlling the Thermostat setting but this in-turn activate underfloor heating. + name = "Thermostat " + if zoneName is not None: + modelInfos["name"] = name + "(" + zoneName + ")" + else: + modelInfos["name"] = name + "(#" + str(modelId - 556) + ")" + + modelInfos["type"] = CozytouchDeviceType.THERMOSTAT + modelInfos["currentTemperatureAvailable"] = True + modelInfos["currentTemperatureAvailableZ1"] = True + modelInfos["currentTemperatureAvailableZ2"] = False + modelInfos["overrideModeAvailable"] = True + modelInfos["quietModeAvailable"] = False + + modelInfos["HVACModes"] = { + 0: HVACMode.OFF, + 4: HVACMode.HEAT, + } + modelInfos["HeatingModes"] = { + 0: HEATING_MODE_MANUAL, + 3: HEATING_MODE_ECO_PLUS, + 4: HEATING_MODE_PROG, + } + else: # Fallback to AC if none found to keep backward compatibility + name = "Air Conditioner " + if zoneName is not None: + modelInfos["name"] = name + "(" + zoneName + ")" + else: + modelInfos["name"] = name + "(#" + str(modelId - 556) + ")" + + modelInfos["type"] = CozytouchDeviceType.AC + modelInfos["currentTemperatureAvailable"] = False + modelInfos["quietModeAvailable"] = True + + modelInfos["fanModes"] = { + 1: FAN_LOW, + 2: FAN_MEDIUM, + 3: FAN_HIGH, + 5: FAN_AUTO, + } + + modelInfos["swingModes"] = { + 1: SWING_MODE_UP, + 2: SWING_MODE_MIDDLE_UP, + 3: SWING_MODE_MIDDLE_DOWN, + 4: SWING_MODE_DOWN, + } + + modelInfos["HVACModes"] = { + 0: HVACMode.OFF, + 1: HVACMode.AUTO, + 3: HVACMode.COOL, + 4: HVACMode.HEAT, + 7: HVACMode.FAN_ONLY, + 8: HVACMode.DRY, + } + if zoneName is not None: modelInfos["name"] = name + "(" + zoneName + ")" else: modelInfos["name"] = name + "(#" + str(modelId - 556) + ")" - modelInfos["type"] = CozytouchDeviceType.AC - modelInfos["currentTemperatureAvailable"] = False - modelInfos["quietModeAvailable"] = True - - modelInfos["fanModes"] = { - 1: FAN_LOW, - 2: FAN_MEDIUM, - 3: FAN_HIGH, - 5: FAN_AUTO, - } - - modelInfos["swingModes"] = { - 1: SWING_MODE_UP, - 2: SWING_MODE_MIDDLE_UP, - 3: SWING_MODE_MIDDLE_DOWN, - 4: SWING_MODE_DOWN, - } - - modelInfos["HVACModes"] = { - 0: HVACMode.OFF, - 1: HVACMode.AUTO, - 3: HVACMode.COOL, - 4: HVACMode.HEAT, - 7: HVACMode.FAN_ONLY, - 8: HVACMode.DRY, - } elif modelId >= 562 and modelId <= 570: name = "Air Conditioner User Interface " @@ -298,6 +375,18 @@ def get_model_infos(modelId: int, zoneName: str | None = None): 4: HVACMode.HEAT, } + elif modelId >= 1505 and modelId <= 1513: + name = "Thermostat Thermal Zone " + if zoneName is not None: + modelInfos["name"] = name + "(" + zoneName + ")" + else: + modelInfos["name"] = name + "(#" + str(modelId - 1504) + ")" + + modelInfos["type"] = CozytouchDeviceType.THERMOSTAT + modelInfos["HVACModes"] = { + 0: HVACMode.OFF, + } + elif modelId == 1543: modelInfos["name"] = "Asama Connecté II Ventilo 1750W Blanc" modelInfos["type"] = CozytouchDeviceType.TOWEL_RACK