Plugin SPA Intex

Désolé, tellement un temps pourri que je ne l’ai pas encore sorti ,. Dès que je remets en route je te fais un feedback
À+

bonjour, j’ai eu le meme soucis, lors de la mise a jour des packages python et cela venait du changement de nom du module en provenance du git qui est devenu « aio-intex-spa ».

Avec la commande « pip freeze » tu auras la liste des modules python installés et tu pourras vérifier si la mise a jour a changé le nom du module.
Si il y a eu changement de nom du module le plus simple est de retélechargé les fichiers « example » depuis le git.

Merci de ces infos. En effet il a changé de nom. Il faut changer le script et scenario?
Mon niveau est faible.
Est ce que tu pourrais partager ce que tu as dans le script et scénario?

bonjour, non pas besoin de changer les scripts ni les scénarios, il te suffit juste de telecharger les fichier sur le git et de les remplacer dans le repertoire script/data/spa que tu as créé en suivant le tuto en debut de ce post.

edit: En revanche lors de la mise en place des nouveaux scripts python, j’ai eu des messages d’erreurs et j’ai effectué des modifs sur les fichiers en erreurs, mais je ne sais plus lesquels,lorsque tu seras arrivé à ce stade je pourrais t’orienter pour les modifs

Merci pour ton retour.
J’ai effectué la manip que tu m’indique mais ça ne marche pas J’ai cette erreur :

[2024-08-17 14:20:51] ERROR  : Erreur exécution de la commande [Energie  éclairage][Spa][intex_spa_set_bubble_off] : Erreur sur sudo chmod +x python3 2>/dev/null;python3 /var/www/html/plugins/script/data/spa/intex_spa_set_bubbles_off.PY 2>&1 valeur retournée : 1. Détails : Traceback (most recent call last):   File "/var/www/html/plugins/script/data/spa/intex_spa_set_bubbles_off.PY", line 7, in <module>     from aio_intex_spa import IntexSpa   File "/usr/local/lib/python3.7/dist-packages/aio_intex_spa/__init__.py", line 3, in <module>     from aio_intex_spa.intex_spa import IntexSpa   File "/usr/local/lib/python3.7/dist-packages/aio_intex_spa/intex_spa.py", line 8, in <module>     from .intex_spa_object_status import IntexSpaStatus   File "/usr/local/lib/python3.7/dist-packages/aio_intex_spa/intex_spa_object_status.py", line 8, in <module>     class IntexSpaStatus:   File "/usr/local/lib/python3.7/dist-packages/aio_intex_spa/intex_spa_object_status.py", line 70, in IntexSpaStatus     def current_temp(self) -> int | bool: TypeError: unsupported operand type(s) for |: 'type' and 'type'

Bonsoir, c’est les erreurs dont je te parlais et il faut modifié en SSH le fichier indiqué « /usr/local/lib/python3.7/dist-packages/aio_intex_spa/intex_spa_object_status.py » en ajoutant
from typing import Union

au début du script python et en modifiant à la ligne indiquée

int | bool

par

Union[int, bool]

moi pour etre sur j’ai modifié toutes les lignes contenant int | bool
Une fois le fichier modifié tu auras un autre fichier avec la même erreur et il faudra faire exactement la même chose.

Merci pour ton retour. Malheureusement ça depasse mes compétences :pensive:

J’abuse si je demande des captures d’écran? :yum:

Pas de soucis mais il n’y a rien de compliqué.
Ouvrir le fichier cité avec la commande nano et faire les modifs indiquées au post précédent
Capturejee
et
Capturejee1

Hello
Voici ce que j’ai lorsque j’ouvre ce fichier :

"""Load IntexSpaStatus."""

import logging

_LOGGER = logging.getLogger(__name__)

from typing import Union

class IntexSpaStatus:
    """Hold and expose the status response objects.

    Attributes
    ----------
    _raw_status : int
        The raw integer-encoded status data, as received from the spa
    power : bool
    filter : bool
    heater : bool
    jets : bool
    bubbles : bool
    sanitizer : bool
    unit : str
    current_temp : int
    preset_temp : int

    """

    @property
    def power(self) -> bool:
        """Power state of the spa."""
        return bool((self._raw_status >> 104) & 0b1)

    @property
    def filter(self) -> bool:
        """State of the filter function."""
        return bool((self._raw_status >> 105) & 0b1)

    @property
    def heater(self) -> bool:
        """State of the heater function."""
        return bool((self._raw_status >> 106) & 0b1)

    @property
    def jets(self) -> bool:
        """State of the jets function."""
        return bool((self._raw_status >> 107) & 0b1)

    @property
    def bubbles(self) -> bool:
        """State of the bubbles function."""
        return bool((self._raw_status >> 108) & 0b1)

    @property
    def sanitizer(self) -> bool:
        """State of sanitizer function."""
        return bool((self._raw_status >> 109) & 0b1)

    @property
    def unit(self) -> str:
        """Unit of the temperature values.

        * "°C" for Celsius*
        * "°F" for Farenheit*
        """
        if self.preset_temp <= 40:
            return "°C"
        else:
            return "°F"

    @property
    def current_temp(self) -> Union[int, bool]:
        """Current temperature of the water, expressed in `unit`."""
        raw_current_temp = (self._raw_status >> 88) & 0xFF

        # If current_temp encodes a temperature, return the temperature
        if raw_current_temp < 181:
            return raw_current_temp
        # Else if current_temp encodes an error (E81, ...), return False
        else:
            return False

    @property
    def error_code(self) -> Union[int, bool]:
        """Current error code of the spa."""
        raw_current_temp = (self._raw_status >> 88) & 0xFF

        # If current_temp encodes an error (E81, ...), return the error code
        if raw_current_temp >= 181:
            error_no = raw_current_temp - 100
            return f"E{error_no}"
        # Else if current_temp encodes a temperature, return False
        else:
            return False

    @property
    def preset_temp(self) -> int:
        """Preset temperature of the water, expressed in `unit`."""
        return (self._raw_status >> 24) & 0xFF

    def __init__(self, raw_status: int = None):
        """Initialize IntexSpaStatus class.

        Parameters
        ----------
        raw_status : int, optional
            The raw response data received from the spa

        """
        if raw_status is not None:
            self.update(raw_status)

    def update(self, raw_status: int):
        """Update the status of the spa from the received raw response.

        Parameters
        ----------
        raw_status : int
            The raw response data received from the spa

        """
        self._raw_status = raw_status
        _LOGGER.debug("Spa status: '%s'", self)

    def as_dict(self) -> dict:
        """Return main status attributes only, as dict.

        Returns
        -------
        status_attributes : dict
            IntexSpaStatus main status attributes as dict

        """
        try:
            return {
                "power": self.power,
                "filter": self.filter,
                "heater": self.heater,
                "jets": self.jets,
                "bubbles": self.bubbles,
                "sanitizer": self.sanitizer,
                "unit": self.unit,
                "current_temp": self.current_temp,
                "preset_temp": self.preset_temp,
                "error_code": self.error_code,
            }
        # If _raw_status is not defined
        except AttributeError:
            return {
                "power": None,
                "filter": None,
                "heater": None,
                "jets": None,
                "bubbles": None,
                "sanitizer": None,
                "unit": None,
                "current_temp": None,
                "preset_temp": None,
                "error_code": None,
            }

    def __repr__(self) -> str:
        """Represent IntexSpaStatus main attributes."""
        return repr(self.as_dict())

Voici mon fichier modifié, tu as juste à faire un copier-coller

Merci pour ta patience. J’ai réussi dans ce fichier.
Maintenant j’ai cette erreur, je suis allé faire la même chose dans ce fichier mais ça ne change rien.

Erreur sur sudo chmod +x python3 2>/dev/null;python3 /var/www/html/plugins/script/data/spa/intex_spa_set_heater_on.PY 2>&1 valeur retournée : 1. Détails : Traceback (most recent call last): File « /var/www/html/plugins/script/data/spa/intex_spa_set_heater_on.PY », line 7, in from aio_intex_spa import IntexSpa File « /usr/local/lib/python3.7/dist-packages/aio_intex_spa/init.py », line 3, in from aio_intex_spa.intex_spa import IntexSpa File « /usr/local/lib/python3.7/dist-packages/aio_intex_spa/intex_spa.py », line 15, in class IntexSpa: File « /usr/local/lib/python3.7/dist-packages/aio_intex_spa/intex_spa.py », line 50, in IntexSpa self, intent: str = « status », expected_state: Union[int, bool] = None NameError: name ‹ Union › is not defined

je te joins le contenu avec la modif fonctionnelle, tu as du oublier de déclarer la fonction union au debut du code

"""Load IntexSpa class."""

import logging
import asyncio

from .intex_spa_network_layer import IntexSpaNetworkLayer
from .intex_spa_query import IntexSpaQuery
from .intex_spa_object_status import IntexSpaStatus
from .intex_spa_object_info import IntexSpaInfo
from .intex_spa_exceptions import IntexSpaUnreachableException
from typing import Union

_LOGGER = logging.getLogger(__name__)


class IntexSpa:
    """Interface the user with Intex Spa.

    AsyncIO-enabled class to interface with Intex Spa wifi module

    Attributes
    ----------
    network : IntexSpaNetworkLayer
      The network layer object for communications with intex spa wifi module
    status : IntexSpaStatus
      The status object of the spa
    info : IntexSpaInfo
      The info object of the spa

    """

    def __init__(self, address: str = "SPA_DEVICE", port: str = "8990"):
        """Initialize IntexSpa object instance.

        Parameters
        ----------
        address : str, default = "SPA_DEVICE"
          The fqdn or IP of the intex spa wifi module
        port : str, default = "8990"
          The TCP service port the intex spa wifi module

        """
        _LOGGER.info("Initializing IntexSpa instance...")
        self.network = IntexSpaNetworkLayer(address, port)
        self._semaphore = asyncio.Semaphore(1)
        self.status = IntexSpaStatus()
        self.info = IntexSpaInfo()
        _LOGGER.info("IntexSpa instance initialized")

    async def _async_handle_intent(
        self, intent: str = "status", expected_state: Union[bool, int] = None
    ) -> IntexSpaStatus:
        """Handle any intent by conversing with the spa wifi module.

        An intent can be:
        * update: to refresh the status object of the spa
        * *command*: to change the function state or temperature preset on the spa

        A conversation with the spa is made of one or more queries:
        * A command intent always triggers a preliminary status update
        * Command is sent and response is received
        * Retries can happen as needed

        Parameters
        ----------
        intent : str
          The intent to handle (i.e.: "status", "heater", "preset_temp", ...)
        expected_state : bool | int, optional
          The expected state of the function or the temperature preset

        Returns
        -------
        status : IntexSpaStatus
          The status of the spa

        """

        _LOGGER.debug("'%s' intent: Handling new intent...", intent)

        # Trigger a preliminary update status intent if the provided intent is a command
        if intent != "status" and intent != "info":
            _LOGGER.debug(
                "'%s' intent: triggering a preliminary 'update' intent...", intent
            )
            await self.async_update_status()

        # Run concurrent requests senquentially
        async with self._semaphore:
            if (
                # the provided intent is an update status
                intent == "status"
                # the provided intent is an update info
                or intent == "info"
                # the provided intent is a command
                # and its expected_state differs from the current state
                or getattr(self.status, intent) != expected_state
            ):
                _LOGGER.debug("'%s' intent: a spa query is needed", intent)

                # Attempt maximum 3 times
                for _ in range(3):
                    try:
                        _LOGGER.debug("'%s' intent: new spa query...", intent)
                        # Initialize a query to the spa
                        query = IntexSpaQuery(intent, expected_state)

                        # Send the raw request bytes via the network object
                        await self.network.async_send(query.request_bytes)

                        # Receive the raw response bytes via the network object
                        received_bytes = await self.network.async_receive()
                        # Give the raw received_bytes back to the query object
                        # to render the new status
                        query.render_response_data(received_bytes)
                        if intent == "info":
                            # Update the info object with the data received
                            self.info = IntexSpaInfo(query.response_data)
                            _LOGGER.debug("'%s' intent: new info is rendered", intent)
                            return self.info
                        else:
                            # Update the status object with the data received
                            self.status = IntexSpaStatus(query.response_data)
                            _LOGGER.debug("'%s' intent: new status is rendered", intent)
                            return self.status

                    except (AssertionError,):
                        _LOGGER.info("Malformed spa response during spa querying")
                        await asyncio.sleep(2)
                        continue

                    except (
                        TimeoutError,
                        asyncio.IncompleteReadError,
                        ConnectionRefusedError,
                        ConnectionResetError,
                        ConnectionError,
                        OSError,
                    ):
                        _LOGGER.info("Network raised an exception during spa querying")
                        await self.network.async_force_disconnect()
                        await asyncio.sleep(2)
                        continue

                    else:
                        break
                else:  # No retry has succeeded
                    _LOGGER.info("Spa is unreachable")
                    self.status = IntexSpaStatus()
                    raise IntexSpaUnreachableException("Spa is unreachable")

        # Return a status even when getattr(self.status, intent) == expected_state
        # Fixes mathieu-mp/aio-intex-spa#17
        return self.status

    async def async_update_status(self) -> IntexSpaStatus:
        """Update known status of the spa.

        Returns
        -------
        status : IntexSpaStatus
          The updated spa status

        """
        return await self._async_handle_intent("status")

    async def async_set(
        self, parameter: str, expected_state: bool = True
    ) -> IntexSpaStatus:
        """Set specified parameter to `expected_state`.

        Returns
        -------
        status : IntexSpaStatus
          The updated spa status

        """
        return await self._async_handle_intent(parameter, expected_state)

    async def async_set_power(self, expected_state: bool = True) -> IntexSpaStatus:
        """Set power function to `expected_state`.

        Returns
        -------
        status : IntexSpaStatus
          The updated spa status

        """
        return await self.async_set("power", expected_state)

    async def async_set_filter(self, expected_state: bool = True) -> IntexSpaStatus:
        """Set filter function to `expected_state`.

        Returns
        -------
        status : IntexSpaStatus
          The updated spa status

        """
        return await self.async_set("filter", expected_state)

    async def async_set_heater(self, expected_state: bool = True) -> IntexSpaStatus:
        """Set heater function to `expected_state`.

        Returns
        -------
        status : IntexSpaStatus
          The updated spa status

        """
        return await self.async_set("heater", expected_state)

    async def async_set_jets(self, expected_state: bool = True) -> IntexSpaStatus:
        """Set jets function to `expected_state`.

        Returns
        -------
        status : IntexSpaStatus
          The updated spa status

        """
        return await self.async_set("jets", expected_state)

    async def async_set_bubbles(self, expected_state: bool = True) -> IntexSpaStatus:
        """Set bubbles function to `expected_state`.

        Returns
        -------
        status : IntexSpaStatus
          The updated spa status

        """
        return await self.async_set("bubbles", expected_state)

    async def async_set_sanitizer(self, expected_state: bool = True) -> IntexSpaStatus:
        """Set sanitizer function to `expected_state`.

        Returns
        -------
        status : IntexSpaStatus
          The updated spa status

        """
        return await self.async_set("sanitizer", expected_state)

    async def async_set_preset_temp(self, expected_state: int) -> IntexSpaStatus:
        """Set preset_temp function to `expected_state`.

        Returns
        -------
        status : IntexSpaStatus
          The updated spa status

        """
        return await self._async_handle_intent("preset_temp", expected_state)

    async def async_update_info(self) -> IntexSpaInfo:
        """Update known info from the spa.

        Returns
        -------
        info : IntexSpaInfo
          The updated spa info

        """
        return await self._async_handle_intent("info")

Normalement après tout devrait être opérationnel

Hello,
Super la communication est de retablie ! Merci de ton aide !
Par contre le statut remonte de manière differente maintenant. Tu as eu la même chose?

DEBUG:asyncio:Using selector: EpollSelector INFO:aio_intex_spa.intex_spa:Initializing IntexSpa instance… INFO:aio_intex_spa.intex_spa:IntexSpa instance initialized DEBUG:aio_intex_spa.intex_spa:‹ status › intent: Handling new intent… DEBUG:aio_intex_spa.intex_spa:‹ status › intent: a spa query is needed DEBUG:aio_intex_spa.intex_spa:‹ status › intent: new spa query… INFO:aio_intex_spa.intex_spa_network_layer:Not connected to the spa, trying to connect… DEBUG:aio_intex_spa.intex_spa_network_layer:Opening TCP connection with the spa at SPA_DEVICE:8990 with asyncio… INFO:aio_intex_spa.intex_spa_network_layer:TCP connection established with the spa DEBUG:aio_intex_spa.intex_spa_network_layer:Sending bytes to the spa: b’{« data »: « 8888060FEE0F01DA », « sid »: « 17242340445149 », « type »: 1}’ DEBUG:aio_intex_spa.intex_spa_network_layer:Receiving bytes from the spa: b’{« sid »:« 17242340445149 »,« data »:« FFFF110F010000150000000080808024000024 »,« result »:« ok »,« type »:2}\n’ DEBUG:aio_intex_spa.intex_spa_object_status:Spa status: ‹ {‹ power ›: False, ‹ filter ›: False, ‹ heater ›: False, ‹ jets ›: False, ‹ bubbles ›: False, ‹ sanitizer ›: False, ‹ unit ›: ‹ °C ›, ‹ current_temp ›: 21, ‹ preset_temp ›: 36, ‹ error_code ›: False} › DEBUG:aio_intex_spa.intex_spa:‹ status › intent: new status is rendered {‹ power ›: False, ‹ filter ›: False, ‹ heater ›: False, ‹ jets ›: False, ‹ bubbles ›: False, ‹ sanitizer ›: False, ‹ unit ›: ‹ °C ›, ‹ current_temp ›: 21, ‹ preset_temp ›: 36, ‹ error_code ›: False} le 2024-08-21 11:54:04

Le souci doit être là

J’ai réussi a retrouvé les feedback des commande en modifiant le scenario de @patrice_payen.
le power est maintenant en $val2. J’ai décalé l’ensemble d’un cran. Je ne sais pas pourquoi.
Et le preset temp ne fonctionne plus mais mes faible compétence me bloquent encore.

le voici adapté avec le nouveau nom « aio_intex_spa »

"""Usage example: Set spa preset temp to 25°C."""

import os
import logging
import asyncio
import sys

from aio_intex_spa import IntexSpa

SPA_ADDRESS = os.getenv("SPA_ADDRESS") or "SPA_DEVICE"


async def example_intex_spa():
    """Example for intex_spa"""
    intex_spa = IntexSpa(SPA_ADDRESS)
    n = int(sys.argv[1])

    print(await intex_spa.async_set_preset_temp(n))


asyncio.run(example_intex_spa())

Pour ce qui est du décalage de la valeur c’est bizarre, dans ton post précédent, on voit que la valeur power est bien la première de la liste de valeurs.
Peux-tu envoyer ce que la commande
python3 /var/www/html/plugins/script/data/spa/intex_spa_get_status.PY retourne

Merci pour ton retour ça fonctionne pour le preset.

Voici le retour de la commande :

pi@raspberrypi:~ $ pi@192.168.1.30's password:
> Linux raspberrypi 5.10.103-v7l+ #1529 SMP Tue Mar 8 12:24:00 GMT 2022 armv7l
>
> The programs included with the Debian GNU/Linux system are free software;
> the exact distribution terms for each program are described in the
> individual files in /usr/share/doc/*/copyright.
>
> Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
> permitted by applicable law.
> Last login: Thu Aug 22 14:46:51 2024 from 192.168.1.132
>
> Wi-Fi is currently blocked by rfkill.
> Use raspi-config to set the country before use.
>
> pi@raspberrypi:~ $ python3 /var/www/html/plugins/script/data/spa/intex_spa_get_status.PY
> INFO:aio_intex_spa.intex_spa:Initializing IntexSpa instance...
> INFO:aio_intex_spa.intex_spa:IntexSpa instance initialized
> INFO:aio_intex_spa.intex_spa_network_layer:Not connected to the spa, trying to connect...
> INFO:aio_intex_spa.intex_spa_network_layer:TCP connection established with the spa
> {'power': True, 'filter': False, 'heater': False, 'jets': False, 'bubbles': False, 'sanitizer': False, 'unit': '°C', 'current_temp': 29, 'preset_temp': 38, 'error_code': False}
> pi@raspberrypi:~ $

Et voila le scenario pour que ça fonctionne :

// Get data using status script

$cmdinfo = "#[Energie  éclairage][Spa][spa_statut]#";
$RetourInfo = cmd::byString($cmdinfo)->execCmd();
$scenario->setLog("Retour Info : " . $retourInfo);

// Get indidual data
list($val1, $val2,$val3,$val4,$val5,$val6,$val7,$val8,$val9,$val10) = explode(',', $RetourInfo);

// Set variables
$scenario->setData("spa_inconnu", substr($val1,strrpos($val1,": ")+2,));
$scenario->setData("spa_power", substr($val2,strrpos($val2,": ")+2,));
$scenario->setData("spa_filter", substr($val3,strrpos($val3,": ")+2,));
$scenario->setData("spa_heater", substr($val4,strrpos($val4,": ")+2,));
$scenario->setData("spa_jet", substr($val5,strrpos($val5,": ")+2,));
$scenario->setData("spa_bubble", substr($val6,strrpos($val6,": ")+2,));
$scenario->setData("spa_sanitizer", substr($val7,strrpos($val7,": ")+2,));
$scenario->setData("spa_unit", substr($val8,strrpos($val8,": ")+2,));
$scenario->setData("spa_current_temp", substr($val9,strrpos($val9,": ")+2,));
$scenario->setData("spa_preset_temp", substr($val10,strrpos($val10,": ")+2,));
$pos = strrpos($val10,": ");
$len = strlen($val10)-$pos-3;
$sub = substr($val10,$pos+2,$len);
$scenario->setData("spa_error", $sub);



// Show Variables

$scenario->setLog($scenario->getData("spa_inconnu"));
$scenario->setLog($scenario->getData("spa_power"));
$scenario->setLog($scenario->getData("spa_filter"));
$scenario->setLog($scenario->getData("spa_heater"));
$scenario->setLog($scenario->getData("spa_jet"));
$scenario->setLog($scenario->getData("spa_bubble"));
$scenario->setLog($scenario->getData("spa_sanitizer"));
$scenario->setLog($scenario->getData("spa_unit"));
$scenario->setLog($scenario->getData("spa_current_temp"));
$scenario->setLog($scenario->getData("spa_preset_temp"));

bizarre la valeur power est bien en première position.
Une petite question en passant, tu as bien remplacé les fichiers PY dans script/data/spa par les nouveaux sur le github?

Je viens de revérifier et c’est bien les nouveaux en from intex_spa.intex_spa.
On m’appel le chat noir :slight_smile:

Normalement les nouveaux ont

from aio_intex_spa import IntexSpa

en debut de code.
Si jamais tu ne les as pas

Tu pourras les retélécharger ici fichier aio_intex