LocalTuya et climate.py

Bonjour,
Je cherche a modifier le fichier : /homeassistant/custom_components/localtuya/climate.py
Afin de pouvoir afficher le mode de ventilation , exemple : auto, bas, moyen haut, comme l’image ci-dessous
Avez vous des exemples ?
comme la carte suivante :

Salut,

C’est plutôt la carte qu’il faut modifier et non pas le fichier python du composant
Quelle est-elle ?

@Pulpy-Luke ,
C’est la carte par defaut lorsque tu intègre en LocalTuya un termostat de climatiseur, voir l’image.

Ok donc c’est la carte de l’entité dans HA (rien à voir avec local tuya) quand on clique sur l’entité.
Il n’y a rien à faire pour cette carte.
Par contre, on peut créer/utiliser des carte dans un dashboard

type: thermostat
entity: climate.radiateur_rdc
show_current_as_primary: true

Et on peut composer sa propre carte à partir de là

Hello @Eric_Veyrat ,

J’ai fais ça pour ma clim Airton et voici comment.

Dans le fichier climate.py:

  1. ajoute une déclaration comme ça (à adapter bien sur):
# JMC
HVAC_FAN_MODE_SETS = {
    "JMC Airton": {
        # auto, low, low_mid, mid, mid_high, high, mute, turbo
        FAN_AUTO: "auto",
        FAN_LOW: "low",
        "low_mid": "low_mid",
        FAN_MEDIUM: "medium",
        "mid_high": "mid_high",
        FAN_HIGH: "high",
        "mute": "mute",
        "turbo": "turbo",
    },
}

Dans:
def flow_schema(..

Ajoute ça:

# JMC
        vol.Optional(CONF_HVAC_FAN_MODE_DP): vol.In(dps),
        vol.Optional(CONF_HVAC_FAN_MODE_SET): vol.In(list(HVAC_FAN_MODE_SETS.keys())),

et dans class LocaltuyaClimate methode __init__ ajoute ça :

# JMC
        self._conf_hvac_fan_mode_dp = self._config.get(CONF_HVAC_FAN_MODE_DP)
        self._conf_hvac_fan_mode_set = HVAC_FAN_MODE_SETS.get(
            self._config.get(CONF_HVAC_FAN_MODE_SET), {}
        )

Après y a des méthodes a ajouter, je vais plutot te donner la classe complete. Cherche # JMC pour avoir mes modifs:

"""Platform to locally control Tuya-based climate devices."""
import asyncio
import logging
from functools import partial

import voluptuous as vol
from homeassistant.components.climate import (
    DEFAULT_MAX_TEMP,
    DEFAULT_MIN_TEMP,
    DOMAIN,
    ClimateEntity,
)
from homeassistant.components.climate.const import (
    CURRENT_HVAC_HEAT,
    CURRENT_HVAC_IDLE,
    CURRENT_HVAC_COOL,
    HVAC_MODE_AUTO,
    HVAC_MODE_HEAT,
    HVAC_MODE_COOL,
    HVAC_MODE_OFF,
    PRESET_AWAY,
    PRESET_ECO,
    PRESET_HOME,
    PRESET_NONE,
    SUPPORT_PRESET_MODE,
    SUPPORT_TARGET_TEMPERATURE,
    SUPPORT_TARGET_TEMPERATURE_RANGE,
    # JMC
    FAN_OFF,
    FAN_LOW,
    FAN_MEDIUM,
    FAN_HIGH,
    FAN_AUTO,
    SUPPORT_FAN_MODE,
)
from homeassistant.const import (
    ATTR_TEMPERATURE,
    CONF_TEMPERATURE_UNIT,
    PRECISION_HALVES,
    PRECISION_TENTHS,
    PRECISION_WHOLE,
    TEMP_CELSIUS,
    TEMP_FAHRENHEIT,
)

from .common import LocalTuyaEntity, async_setup_entry
from .const import (
    CONF_CURRENT_TEMPERATURE_DP,
    CONF_ECO_DP,
    CONF_ECO_VALUE,
    CONF_HEURISTIC_ACTION,
    CONF_HVAC_ACTION_DP,
    CONF_HVAC_ACTION_SET,
    CONF_HVAC_MODE_DP,
    CONF_HVAC_MODE_SET,
    CONF_MAX_TEMP_DP,
    CONF_MIN_TEMP_DP,
    CONF_PRECISION,
    CONF_PRESET_DP,
    CONF_PRESET_SET,
    CONF_TARGET_PRECISION,
    CONF_TARGET_TEMPERATURE_DP,
    CONF_TEMPERATURE_STEP,
    # JMC
    CONF_HVAC_FAN_MODE_DP,
    CONF_HVAC_FAN_MODE_SET,
)

_LOGGER = logging.getLogger(__name__)

HVAC_MODE_SETS = {
    # PATCH JMC pour avoir la clim avec localTuya
    "JMC Airton": {
        HVAC_MODE_HEAT: "heat",
        HVAC_MODE_COOL: "cold",
        "fan_only": "fan_only",
    },
    "manual/auto": {
        HVAC_MODE_HEAT: "manual",
        HVAC_MODE_AUTO: "auto",
    },
    "Manual/Auto": {
        HVAC_MODE_HEAT: "Manual",
        HVAC_MODE_AUTO: "Auto",
    },
    "Manual/Program": {
        HVAC_MODE_HEAT: "Manual",
        HVAC_MODE_AUTO: "Program",
    },
    "m/p": {
        HVAC_MODE_HEAT: "m",
        HVAC_MODE_AUTO: "p",
    },
    "True/False": {
        HVAC_MODE_HEAT: True,
    },
    "1/0": {
        HVAC_MODE_HEAT: "1",
        HVAC_MODE_AUTO: "0",
    },
}
HVAC_ACTION_SETS = {
    "True/False": {
        CURRENT_HVAC_HEAT: True,
        CURRENT_HVAC_IDLE: False,
    },
    "open/close": {
        CURRENT_HVAC_HEAT: "open",
        CURRENT_HVAC_IDLE: "close",
    },
    "heating/no_heating": {
        CURRENT_HVAC_HEAT: "heating",
        CURRENT_HVAC_IDLE: "no_heating",
    },
    "Heat/Warming": {
        CURRENT_HVAC_HEAT: "Heat",
        CURRENT_HVAC_IDLE: "Warming",
    },
    "JMC Airton": {
        CURRENT_HVAC_HEAT: "heat",
        CURRENT_HVAC_COOL: "cool",
        CURRENT_HVAC_IDLE: "idle",
    },
}
PRESET_SETS = {
    "Manual/Holiday/Program": {
        PRESET_AWAY: "Holiday",
        PRESET_HOME: "Program",
        PRESET_NONE: "Manual",
    },
}

# JMC
HVAC_FAN_MODE_SETS = {
    "JMC Airton": {
        # auto, low, low_mid, mid, mid_high, high, mute, turbo
        FAN_AUTO: "auto",
        FAN_LOW: "low",
        "low_mid": "low_mid",
        FAN_MEDIUM: "medium",
        "mid_high": "mid_high",
        FAN_HIGH: "high",
        "mute": "mute",
        "turbo": "turbo",
    },
}

TEMPERATURE_CELSIUS = "celsius"
TEMPERATURE_FAHRENHEIT = "fahrenheit"
DEFAULT_TEMPERATURE_UNIT = TEMPERATURE_CELSIUS
DEFAULT_PRECISION = PRECISION_TENTHS
DEFAULT_TEMPERATURE_STEP = PRECISION_HALVES
# Empirically tested to work for AVATTO thermostat
MODE_WAIT = 0.1


def flow_schema(dps):
    """Return schema used in config flow."""
    return {
        vol.Optional(CONF_TARGET_TEMPERATURE_DP): vol.In(dps),
        vol.Optional(CONF_CURRENT_TEMPERATURE_DP): vol.In(dps),
        vol.Optional(CONF_TEMPERATURE_STEP): vol.In(
            [PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
        ),
        vol.Optional(CONF_MAX_TEMP_DP): vol.In(dps),
        vol.Optional(CONF_MIN_TEMP_DP): vol.In(dps),
        vol.Optional(CONF_PRECISION): vol.In(
            [PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
        ),
        vol.Optional(CONF_HVAC_MODE_DP): vol.In(dps),
        vol.Optional(CONF_HVAC_MODE_SET): vol.In(list(HVAC_MODE_SETS.keys())),
        vol.Optional(CONF_HVAC_ACTION_DP): vol.In(dps),
        vol.Optional(CONF_HVAC_ACTION_SET): vol.In(list(HVAC_ACTION_SETS.keys())),
        # JMC
        vol.Optional(CONF_HVAC_FAN_MODE_DP): vol.In(dps),
        vol.Optional(CONF_HVAC_FAN_MODE_SET): vol.In(list(HVAC_FAN_MODE_SETS.keys())),
        vol.Optional(CONF_ECO_DP): vol.In(dps),
        vol.Optional(CONF_ECO_VALUE): str,
        vol.Optional(CONF_PRESET_DP): vol.In(dps),
        vol.Optional(CONF_PRESET_SET): vol.In(list(PRESET_SETS.keys())),
        vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(
            [TEMPERATURE_CELSIUS, TEMPERATURE_FAHRENHEIT]
        ),
        vol.Optional(CONF_TARGET_PRECISION): vol.In(
            [PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
        ),
        vol.Optional(CONF_HEURISTIC_ACTION): bool,
    }


class LocaltuyaClimate(LocalTuyaEntity, ClimateEntity):
    """Tuya climate device."""

    def __init__(
        self,
        device,
        config_entry,
        switchid,
        **kwargs,
    ):
        """Initialize a new LocaltuyaClimate."""
        super().__init__(device, config_entry, switchid, _LOGGER, **kwargs)
        self._state = None
        self._target_temperature = None
        self._current_temperature = None
        self._hvac_mode = None
        # JMC
        self._fan_mode = None

        self._preset_mode = None
        self._hvac_action = None
        self._precision = self._config.get(CONF_PRECISION, DEFAULT_PRECISION)
        self._target_precision = self._config.get(
            CONF_TARGET_PRECISION, self._precision
        )
        self._conf_hvac_mode_dp = self._config.get(CONF_HVAC_MODE_DP)
        self._conf_hvac_mode_set = HVAC_MODE_SETS.get(
            self._config.get(CONF_HVAC_MODE_SET), {}
        )
        self._conf_preset_dp = self._config.get(CONF_PRESET_DP)
        self._conf_preset_set = PRESET_SETS.get(self._config.get(CONF_PRESET_SET), {})
        self._conf_hvac_action_dp = self._config.get(CONF_HVAC_ACTION_DP)
        self._conf_hvac_action_set = HVAC_ACTION_SETS.get(
            self._config.get(CONF_HVAC_ACTION_SET), {}
        )
        self._conf_eco_dp = self._config.get(CONF_ECO_DP)
        self._conf_eco_value = self._config.get(CONF_ECO_VALUE, "ECO")
        self._has_presets = self.has_config(CONF_ECO_DP) or self.has_config(
            CONF_PRESET_DP
        )

        # JMC
        self._conf_hvac_fan_mode_dp = self._config.get(CONF_HVAC_FAN_MODE_DP)
        self._conf_hvac_fan_mode_set = HVAC_FAN_MODE_SETS.get(
            self._config.get(CONF_HVAC_FAN_MODE_SET), {}
        )

        _LOGGER.debug("Initialized climate [%s]", self.name)

    @property
    def supported_features(self):
        """Flag supported features."""
        supported_features = 0
        if self.has_config(CONF_TARGET_TEMPERATURE_DP):
            supported_features = supported_features | SUPPORT_TARGET_TEMPERATURE
        if self.has_config(CONF_MAX_TEMP_DP):
            supported_features = supported_features | SUPPORT_TARGET_TEMPERATURE_RANGE
        if self.has_config(CONF_PRESET_DP) or self.has_config(CONF_ECO_DP):
            supported_features = supported_features | SUPPORT_PRESET_MODE
        if self.has_config(CONF_HVAC_FAN_MODE_DP):
            supported_features = supported_features | SUPPORT_FAN_MODE
        return supported_features

    @property
    def precision(self):
        """Return the precision of the system."""
        return self._precision

    @property
    def target_precision(self):
        """Return the precision of the target."""
        return self._target_precision

    @property
    def temperature_unit(self):
        """Return the unit of measurement used by the platform."""
        if (
            self._config.get(CONF_TEMPERATURE_UNIT, DEFAULT_TEMPERATURE_UNIT)
            == TEMPERATURE_FAHRENHEIT
        ):
            return TEMP_FAHRENHEIT
        return TEMP_CELSIUS

    @property
    def hvac_mode(self):
        """Return current operation ie. heat, cool, idle."""
        return self._hvac_mode

    @property
    def hvac_modes(self):
        """Return the list of available operation modes."""
        if not self.has_config(CONF_HVAC_MODE_DP):
            return None
        return list(self._conf_hvac_mode_set) + [HVAC_MODE_OFF]

    @property
    def hvac_action(self):
        """Return the current running hvac operation if supported.

        Need to be one of CURRENT_HVAC_*.
        """
        if self._config.get(CONF_HEURISTIC_ACTION, False):
            if self._hvac_mode == HVAC_MODE_HEAT:
                if self._current_temperature < (
                    self._target_temperature - self._precision
                ):
                    self._hvac_action = CURRENT_HVAC_HEAT
                if self._current_temperature == (
                    self._target_temperature - self._precision
                ):
                    if self._hvac_action == CURRENT_HVAC_HEAT:
                        self._hvac_action = CURRENT_HVAC_HEAT
                    if self._hvac_action == CURRENT_HVAC_IDLE:
                        self._hvac_action = CURRENT_HVAC_IDLE
                if (
                    self._current_temperature + self._precision
                ) > self._target_temperature:
                    self._hvac_action = CURRENT_HVAC_IDLE
            return self._hvac_action
        return self._hvac_action

    @property
    def preset_mode(self):
        """Return current preset."""
        return self._preset_mode

    @property
    def preset_modes(self):
        """Return the list of available presets modes."""
        if not self._has_presets:
            return None
        presets = list(self._conf_preset_set)
        if self._conf_eco_dp:
            presets.append(PRESET_ECO)
        return presets

    @property
    def current_temperature(self):
        """Return the current temperature."""
        return self._current_temperature

    @property
    def target_temperature(self):
        """Return the temperature we try to reach."""
        return self._target_temperature

    @property
    def target_temperature_step(self):
        """Return the supported step of target temperature."""
        return self._config.get(CONF_TEMPERATURE_STEP, DEFAULT_TEMPERATURE_STEP)

    @property
    def fan_mode(self):
        """Return the fan setting."""
        # JMC
        return self._fan_mode

    @property
    def fan_modes(self):
        """Return the list of available fan modes."""
        # JMC
        if not self.has_config(CONF_HVAC_FAN_MODE_DP):
            return None
        return list(self._conf_hvac_fan_mode_set) + [FAN_OFF]

    async def async_set_temperature(self, **kwargs):
        """Set new target temperature."""
        if ATTR_TEMPERATURE in kwargs and self.has_config(CONF_TARGET_TEMPERATURE_DP):
            temperature = round(kwargs[ATTR_TEMPERATURE] / self._target_precision)
            await self._device.set_dp(
                temperature, self._config[CONF_TARGET_TEMPERATURE_DP]
            )

    # JMC
    async def async_set_fan_mode(self, fan_mode):
        """Set new target fan mode."""
        if fan_mode == FAN_OFF:
            await self.async_set_hvac_mode(
                HVAC_MODE_OFF
            )  # turning off fan same as all off
            return
        if self._conf_hvac_fan_mode_dp is not None:
            if not self._state and self._conf_hvac_fan_mode_dp != self._dp_id:
                await self._device.set_dp(True, self._dp_id)
                # Some thermostats need a small wait before sending another update
                await asyncio.sleep(MODE_WAIT)
            await self._device.set_dp(
                self._conf_hvac_fan_mode_set[fan_mode], self._conf_hvac_fan_mode_dp
            )

    # JMC
    # def set_fan_mode(self, fan_mode):
    #    """Set new target fan mode."""
    #    return NotImplementedError()

    async def async_set_hvac_mode(self, hvac_mode):
        """Set new target operation mode."""
        if hvac_mode == HVAC_MODE_OFF:
            await self._device.set_dp(False, self._dp_id)
            return
        if not self._state and self._conf_hvac_mode_dp != self._dp_id:
            await self._device.set_dp(True, self._dp_id)
            # Some thermostats need a small wait before sending another update
            await asyncio.sleep(MODE_WAIT)
        await self._device.set_dp(
            self._conf_hvac_mode_set[hvac_mode], self._conf_hvac_mode_dp
        )

    async def async_turn_on(self) -> None:
        """Turn the entity on."""
        await self._device.set_dp(True, self._dp_id)

    async def async_turn_off(self) -> None:
        """Turn the entity off."""
        await self._device.set_dp(False, self._dp_id)

    async def async_set_preset_mode(self, preset_mode):
        """Set new target preset mode."""
        if preset_mode == PRESET_ECO:
            await self._device.set_dp(self._conf_eco_value, self._conf_eco_dp)
            return
        await self._device.set_dp(
            self._conf_preset_set[preset_mode], self._conf_preset_dp
        )

    @property
    def min_temp(self):
        """Return the minimum temperature."""
        if self.has_config(CONF_MIN_TEMP_DP):
            return self.dps_conf(CONF_MIN_TEMP_DP)
        return DEFAULT_MIN_TEMP

    @property
    def max_temp(self):
        """Return the maximum temperature."""
        if self.has_config(CONF_MAX_TEMP_DP):
            return self.dps_conf(CONF_MAX_TEMP_DP)
        return DEFAULT_MAX_TEMP

    def status_updated(self):
        """Device status was updated."""
        self._state = self.dps(self._dp_id)

        if self.has_config(CONF_TARGET_TEMPERATURE_DP):
            self._target_temperature = (
                self.dps_conf(CONF_TARGET_TEMPERATURE_DP) * self._target_precision
            )

        if self.has_config(CONF_CURRENT_TEMPERATURE_DP):
            self._current_temperature = (
                self.dps_conf(CONF_CURRENT_TEMPERATURE_DP) * self._precision
            )

        if self._has_presets:
            if (
                self.has_config(CONF_ECO_DP)
                and self.dps_conf(CONF_ECO_DP) == self._conf_eco_value
            ):
                self._preset_mode = PRESET_ECO
            else:
                for preset, value in self._conf_preset_set.items():  # todo remove
                    if self.dps_conf(CONF_PRESET_DP) == value:
                        self._preset_mode = preset
                        break
                else:
                    self._preset_mode = PRESET_NONE

        # Update the HVAC status
        if self.has_config(CONF_HVAC_MODE_DP):
            if not self._state:
                self._hvac_mode = HVAC_MODE_OFF
            else:
                for mode, value in self._conf_hvac_mode_set.items():
                    if self.dps_conf(CONF_HVAC_MODE_DP) == value:
                        self._hvac_mode = mode
                        break
                else:
                    # in case hvac mode and preset share the same dp
                    self._hvac_mode = HVAC_MODE_AUTO

        # Update the current action
        for action, value in self._conf_hvac_action_set.items():
            if self.dps_conf(CONF_HVAC_ACTION_DP) == value:
                self._hvac_action = action

        # JMC
        # Update the fan status
        if self.has_config(CONF_HVAC_FAN_MODE_DP):
            if not self._state:
                self._fan_mode = FAN_OFF
            else:
                for mode, value in self._conf_hvac_fan_mode_set.items():
                    if self.dps_conf(CONF_HVAC_FAN_MODE_DP) == value:
                        self._fan_mode = mode
                        break
                else:
                    self._fan_mode = FAN_AUTO  # this might need to be revisisted


async_setup_entry = partial(async_setup_entry, DOMAIN, LocaltuyaClimate, flow_schema)

Ce que tu peux faire c’est la reprendre tel quel et changer les déclarations au-début.

Salut,

Donc à chaque mise à jour de local tuya il faut refaire (voire réadapter) le code…
C’est pas simple pour l’utilisateur moyen :zipper_mouth_face:
Tu as essayé de surcharger une entité climate avec le customize.yaml à la place ?

1 « J'aime »

Ah oui c’est pas pour l’utilisateur moyen et c’est dégeulasse. On est en phase.

1 « J'aime »

@Jean-Marc_Collin ,
Si vous avez une autre solution avec un exemple, je suis intéressé car pour l’instant a part la méthode « degueulasse » qui marche aussi pour le « Cold, Dry, Fan, Auto et warm » car par prévu dans le LocalTuya ce qui est vraiment un problème pour un climatiseur.

@Pulpy-Luke , oui mais si l’info du mode « cold, fan, dry, warm, auto » ou encore l’info sur la vitesse de ventilation comme « low,hight, auto » ne sont pas remonté par le LocalTuya, et bien tu ne pourras pas ajouter une nouvelle carte comme tu dis avec les informations que je cite.

Possible, d’où la deuxième idée de passer par le customize.yaml
Modifier les sources c’est vraiment pas efficace. Autant demander la correction dans le component

Y a pas mal de PR proposée pour fixer ça et je m’en suis largement inspirée mais le code owner ne veut pas les prendre. Il a attend qqe-chose de beaucoup générique. Donc ca finira certainement par arriver, c’était mon raisonnement.

@Jean-Marc_Collin
As tu le lien URL, des PR proposées, car je n’ai pas trouvé, je cherche aussi pour les modes de la climatisation : « cold, warm, dry, fan, auto »

Vraie question, je ne suis pas certain d’avoir tout compris, mais…
Il n’y aurait pas moyen de « forcer » les choses via customize : Customizing entities - Home Assistant

à mon petit niveau je m’en sers pour limiter les modes de ‹ swing › de mes clims.
Dans le fichier configuration.yaml :

homeassistant:
  customize_domain:
    climate:
      swing_modes: ["off", "vertical"]
1 « J'aime »

https://github.com/rospogrigio/localtuya/pulls?q=is%3Apr+is%3Aopen+fan

La première est celle que j’ai utilsié.

Je comprends pas bien customize, mais à partir du moment ou le climate fournit par LocalTuya ne donne pas les infos/attributs qu’il faut, je ne vois pas comment ça pourrait marcher.

Après, j’ai lu la doc et à part modifier des icon, des friendly_name ou des champs qui servent purement à la présentation, je vois pas comment on peut l’utiliser.

Si qq’1 sait, je veux bien un exemple.

ok, je pense avoir compris, mon customize ne correspondra pas à ton besoin de récupérer l’information, uniquement de l’afficher dans ta carte climate…

bébé exemple débile :

homeassistant:
  customize:
    light.coin_salle_a_manger:
      swing_modes: ["off", "vertical"]

=>

min_color_temp_kelvin: 2202
max_color_temp_kelvin: 4000
min_mireds: 250
max_mireds: 454
supported_color_modes:
  - color_temp
color_mode: color_temp
brightness: 254
color_temp_kelvin: 2702
color_temp: 370
hs_color:
  - 28.391
  - 65.659
rgb_color:
  - 255
  - 166
  - 87
xy_color:
  - 0.526
  - 0.387
friendly_name: coin salle a manger
supported_features: 32
swing_modes:
  - "off"
  - vertical

ça m’a bien rajouté un attribut, qui serait donc potentiellement affichable dans une carte. Mais pas plus, désolé :expressionless:

1 « J'aime »

Le customize permet justement de donner/surcharger les attributs d’une entité pas que climate. J’ai ce genre de bidouille avec une clim mideas

Là aussi le climate par défaut n’a pas les bonnes infos (le preset sleep n’existe pas au début) et pourtant ça fonctionne comme il faut à la fin

C’est fou ça… Mais ok. Ca me parait miraculeux. Si le climatiseur ne le fait pas de base, comment ça peut marcher ? Ou alors il le fait de base, mais c’était masqué par la card et du coup ça l’a démasqué.

À mon sens :
La carte c’est bidon ça n’affiche que le contenu de l’entité.
L’entité climate ha propose des services uniformisés (tous les mêmes pour des clim de marques différentes).C’est la notion d’interface en prog objet.
L’intégration (de la marque) créé une entité, qui implémente l’interface ha.
Donc dans l’absolu, on a une bête liste d’option.
Et côté implémentation c’est pas nécessaire de convertir la liste en truc lisible, c’est sûrement les valeurs de l’api qui servent. Et manifestement ces valeurs sont textuelles

@Jean-Marc_Collin ,
Un très grand merci, j’ai trouvé mon bonheur grâce a votre lien et voici ce que cela donne :

1 « J'aime »