Pilotage Rad Acova IR / Sniff protocole + Pilotage via ESP&http + Pilotage via Pi&MQTT

Bonjour à tous,

Petit retour d’expérience sur mon utilisation du chauffe-serviette/radiateur soufflant Acova Madras qui fait exactement ce pour quoi il est conçu, mais manque cruellement de « connectivité ».
J’ai vu beaucoup de monde se poser des questions sur ces radiateurs au détour d’internet et par ici :

Donc vous l’aurez compris, ça faisait 2 ans que j’essayais de domotiser ce radiateur.
Alors Chalenge Accepted ! Voici mes contraintes :

  • Me passer complètement de la télécommande infrarouge (pilotage intégral par Jeedom)
  • Ne pas abimer la télécommande (exit le soudage de fils pour contrôler ses boutons !)
  • Garder les fonctionnalités (4 mode principaux : off, consigne à 17°/25°, consigne à 25°+soufflant)

Il n’y a visiblement pas de bus de donné apparent, pas de platine de test exploitable donc pas de façon simple de piloter le radiateur en lui rajoutant juste un ESP ou autre sur la carte interne…

Ne reste plus qu’à décoder le protocole infrarouge et se substituer à la télécommande.
Je vais la faire courte : FAIL ! (J’ai quand même retrouvé une grande partie des informations qui sont envoyées, mais la partie du protocole qui semble être une somme de contrôle des échanges, reste un mystère. Pour ceux qui voudraient plus de détails, il suffit de demander. Et, même après avoir essayé de les contacter à de nombreuses reprises, les équipes R1D d’Acova ne m’ont jamais répondu.)

Après un temps bien trop important à analyser les trames de données infrarouge, j’ai décidé qu’il n’était finalement pas nécessaire que je les comprenne complètement, et qu’il me suffisait juste de les rejouer et ça, c’est super simple.

Niveau setup, pour le scan j’ai utilisé un Rasberry Pi avec LIRC (autre tuto ici) et « mode2 » pour récupérer les « raw_codes », après un peu de nettoyage dans notepad++ et excel (ou je suis un ouf), voici le fichier à déposer dans « /etc/lirc/lircd.conf.d/ » permettant d’envoyer des ordres au Rad :

Contenu du fichier rad.conf
begin remote
        name  RAD_SDB
        flags RAW_CODES
        eps     30
        aeps    100

        ptrail  0
        repeat  0 0
        gap     40991

        begin raw_codes
                name OFF
300     2000    1000    2000    2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    2000    1000    1000    1000    1000    1000    1000    218700
300     2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    2000    1000    1000    2000    1000    1000    2000    2000    2000    2000    2000    2000    1000    1000    2000

                name ON_17_5
300     2000    1000    2000    2000    1000    2000    2000    2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    1000    1000    1000    2000    1000    2000    1000    214700
300     2000    1000    1000    1000    1000    2000    2000    2000    1000    1000    2000    2000    1000    1000    2000    1000    2000    1000    2000    1000    2000    2000    1000    1000    1000    1000

                name ON_21_0
300     2000    1000    2000    2000    2000    1000    1000    1000    2000    2000    1000    1000    1000    1000    1000    1000    1000    1000    2000    1000    1000    1000    1000    2000    2000    1000    214700
300     2000    1000    1000    1000    2000    1000    1000    1000    2000    2000    2000    2000    1000    1000    2000    1000    1000    2000    2000    1000    2000    2000    2000    2000    2000    2000

                name ON_22_5
300     2000    1000    2000    2000    2000    1000    1000    2000    2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    2000    1000    2000    1000    1000    214700
300     2000    1000    1000    1000    2000    1000    1000    2000    2000    1000    2000    2000    1000    1000    2000    1000    1000    2000    1000    1000    1000    1000    2000    2000    1000    2000

                name ON_23_0
300     2000    1000    2000    2000    1000    2000    2000    2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    1000    1000    1000    2000    1000    2000    1000    214700
300     2000    1000    1000    1000    1000    2000    2000    2000    1000    1000    2000    2000    1000    1000    2000    1000    2000    1000    2000    1000    2000    2000    1000    1000    1000    1000

                name ON_23_5
300     2000    1000    2000    2000    2000    1000    2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    2000    2000    1000    1000    1000    2000    1000    214700
300     2000    1000    1000    1000    2000    1000    2000    1000    1000    1000    2000    2000    1000    1000    2000    1000    1000    2000    2000    2000    1000    2000    2000    1000    2000    2000

                name ON_24_0
300     2000    1000    2000    2000    2000    1000    2000    1000    1000    2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    1000    1000    216700
300     2000    1000    1000    1000    2000    1000    2000    1000    1000    2000    2000    2000    1000    1000    2000    1000    1000    2000    1000    1000    2000    2000    2000    2000    1000    2000

                name ON_24_5
300     2000    1000    2000    2000    2000    1000    2000    1000    2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    2000    1000    1000    2000    1000    1000    1000    214700
300     2000    1000    1000    1000    2000    1000    2000    1000    2000    1000    2000    2000    1000    1000    2000    1000    1000    2000    2000    2000    2000    2000    1000    1000    1000    2000

                name ON_25_0
300     3951    1000    2000    2000    2000    1000    2000    1000    2000    2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    1000    2000    2000    2000    1000    210749
300     2000    1000    1000    1000    2000    1000    2000    1000    2000    2000    2000    2000    1000    1000    2000    1000    1000    2000    1000    1000    1000    2000    1000    2000    2000    2000

                name ON_25_5
300     2000    1000    2000    2000    2000    1000    2000    2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    1000    2000    2000    1000    2000    2000    1000    212700
300     2000    1000    1000    1000    2000    1000    2000    2000    1000    1000    2000    2000    1000    1000    2000    1000    1000    2000    2000    1000    1000    1000    2000    2000    2000    2000

                name ON_26_0
300     2000    1000    2000    2000    2000    1000    2000    2000    1000    2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    1000    2000    1000    1000    1000    1000    214700
300     2000    1000    1000    1000    2000    1000    2000    2000    1000    2000    2000    2000    1000    1000    2000    1000    1000    2000    1000    2000    2000    1000    2000    1000    1000    2000

                name ON_28_0
300     2000    1000    2000    2000    2000    2000    1000    1000    1000    2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    2000    1000    2000    1000    214700
300     2000    1000    1000    1000    2000    2000    1000    1000    1000    2000    2000    2000    1000    1000    2000    1000    1000    2000    1000    1000    2000    1000    1000    1000    2000    2000

                name ON_23_0_BOOST_30
300     2000    1000    1000    2000    2000    1000    1000    2000    2000    2000    1000    1000    1000    2000    2000    2000    2000    1000    2000    2000    1000    1000    2000    2000    2000    1000    208700
300     2000    1000    1000    1000    2000    1000    1000    2000    2000    2000    2000    2000    1000    1000    2000    1000    1000    2000    2000    2000    2000    1000    2000    1000    2000    2000

                name ON_26_0_BOOST_30
300     2000    1000    1000    2000    2000    1000    2000    2000    1000    2000    1000    1000    1000    2000    2000    2000    2000    1000    1000    2000    1000    1000    2000    2000    1000    1000    210700
300     2000    1000    1000    1000    2000    1000    2000    2000    1000    2000    2000    2000    1000    1000    2000    1000    1000    2000    1000    2000    2000    1000    2000    1000    1000    2000

                name ON_28_0_BOOST_30
300     2000    1000    1000    2000    2000    2000    1000    1000    1000    2000    1000    1000    1000    2000    2000    2000    2000    1000    1000    1000    1000    1000    1000    2000    2000    1000    212700
300     2000    1000    1000    1000    2000    2000    1000    1000    1000    2000    2000    2000    1000    1000    2000    1000    1000    2000    1000    1000    2000    1000    1000    1000    2000    2000
        end raw_codes
end remote

Il faut recharger le deamon LIRC pour que le fichier soit pris en compte.
Voici des exemples de commandes shell sur le PI pour envoyer des ordres infrarouge au radiateur :

  • Compétemment éteint : irsend SEND_ONCE RAD_SDB OFF
  • Consigne à 17,5° : irsend SEND_ONCE RAD_SDB ON_17_5
  • Consigne à 28,0° : irsend SEND_ONCE RAD_SDB ON_28_0
  • Consigne à 28,0° et Soufflant 30 mins : irsend SEND_ONCE RAD_SDB ON_28_0_BOOST_30

Le pilotage peut être réalisé avec le plugin script (en local sur Jeedom ou en SSH sur un autre Pi).
Ayant un Pi en salle de bain pour la gestion de la musique, je n’avais pas besoin de plus,
mais un ESP8266-01 trainait par là et ne demandait qu’à être utilisé :

Voici le schéma, le code et la config Arduino pour la programmation de l'ESP


R = 100 ohm est peut être un peu faible, j’avais ça sous la main, ça marche.

Je ne me souviens plus bien des dépendances, mais voici le code (enlever le .txt à la fin) :
ESP_Rad_SdB.ino.txt (20,8 Ko)

Vue de la WebUI :

Config d’Arduino IDE pour la compilation :

 

Depuis peu ce montage est chez un ami et je suis repassé sur le PI de la salle de bain, mais avec le code Python pour le couplage MQTT :

Code Python et Service Systemd

Remplacer #BROKER_MQTT# par votre broker.
/root/jeedom_scripts/mqtt2rad.py:

#!/usr/bin/python3

import sys, os, signal, time, json, logging
import paho.mqtt.client as mqtt

DAEMON_PIDFILE  = "/run/mqtt2rad.pid"
ROOT_TOPIC      = "sdb-rad"

class Mqtt2rad:
    def enable_logger(self):
        root = logging.getLogger()
        root.setLevel(logging.DEBUG)
        handler = logging.StreamHandler(sys.stdout)
        handler.setLevel(logging.DEBUG)
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
        root.addHandler(handler)
        self.client.enable_logger(root)
        self.logger = root

    def irsend(self, msg):
        if self.logger:
            self.logger.info("IRSEND: %s", msg)
        os.system('irsend SEND_ONCE RAD_SDB '+msg)

    def on_connect(self, client, userdata, flags, rc):
        # print("Connected with result code "+str(rc))
        client.publish(ROOT_TOPIC+"/status", payload="online", qos=0, retain=True) # LWT
        client.subscribe(ROOT_TOPIC+"/global")
        client.subscribe(ROOT_TOPIC+"/rad")
        client.subscribe(ROOT_TOPIC+"/fan")
        client.subscribe(ROOT_TOPIC+"/daemon")

    def transmit(self):
        now = time.monotonic()
        if self.logger:
            self.logger.info("Delta T: "+str(now - self.last_sent))
        #send values to rad:
        if not self.state["global"]: # Disabled
            self.irsend("OFF")
        elif self.state["fan"]:      # Boost
            self.irsend("ON_28_0_BOOST_30")
        elif self.state["rad"]:      # Comfort
            self.irsend("ON_28_0")
        else:                        # HG
            self.irsend("ON_17_5")
        if self.logger:
            self.logger.info("Sending: G:%r R: %r F:%r", self.state["global"], self.state["rad"], self.state["fan"])
        packet = json.dumps(self.state)
        self.client.publish(ROOT_TOPIC+"/state", payload=packet, qos=0, retain=False)
        self.last_sent = now

    def on_message(self, client, userdata, msg):
        cmd = msg.topic.split("/", 1)[-1]
        packet = str(msg.payload.decode("utf-8","ignore"))
        # print(msg.topic+"="+cmd+": "+packet)#+"("+str(isinstance(msg.payload, bool))+")=>"+str(bmsg))

        if cmd == "daemon":
            if packet == "stop":
                self.run = False
                return
        if cmd == "global":
            self.state["global"] = packet.lower() in ['true', '1']
            if not self.state["global"]:
                self.state["rad"] = False
                self.state["fan"] = False
        elif cmd == "rad":
            self.state["rad"] = packet.lower() in ['true', '1']
            if self.state["rad"]:
                self.state["global"] = True
        elif cmd == "fan":
            self.state["fan"] = packet.lower() in ['true', '1']
            if self.state["fan"]:
                self.state["global"] = True
        self.transmit()

    def exit_gracefully(self,signum, frame):
        self.run = False
        if self.logger:
            self.logger.info("Received SIGINT or SIGTERM!")

    def __init__(self, should_log = False):
        signal.signal(signal.SIGINT, self.exit_gracefully)
        signal.signal(signal.SIGTERM, self.exit_gracefully)

        self.run                = True
        self.state              = {}
        self.state["global"]    = False
        self.state["rad"]       = False
        self.state["fan"]       = False
        self.last_sent          = time.monotonic()
        self.logger             = None

        self.client = mqtt.Client(client_id = "sdb-rad")
        if should_log:
            self.enable_logger()

        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        self.client.will_set(ROOT_TOPIC+"/status", payload="offline", qos=0, retain=True) # LWT

        try:
            self.client.connect("#BROKER_MQTT#", 1883, 60)
            self.client.loop_start()
            while self.run:
                if time.monotonic() - self.last_sent > 180:
                    self.transmit()
                time.sleep(.5)
            self.client.disconnect()
        except:
            self.client.loop_stop()
            if self.logger:
                self.logger.exception("main:")
            sys.exit(1)
        sys.exit(0)

if __name__ == "__main__":
    Mqtt2rad("-v" in sys.argv or "--verbose" in sys.argv)

/etc/systemd/system/mqtt2rad.service:

[Unit]
Description=Rad MQTT daemon
After=network-online.target

[Service]
Type=simple
Restart=always
RestartSec=3
WorkingDirectory=/root/jeedom_scripts
ExecStart=/usr/bin/python3 mqtt2rad.py
PIDFile=/run/mqtt2rad.pid

[Install]
WantedBy=multi-user.target

Config jMQTT dans Jeedom :



image

J’espère que ça pourra aider certains :wink:

/Bad

3 « J'aime »

Bonjour,

je trouve votre travail fort intéressant. J’ai un vieux radiateur acova avec une télécommande rongée par la rouille. J’aurais aimé programmer un PIC ou un atmel avec les séquences que vous donnez dans votre article. Mais à quoi correspondent-elles? A des millisecondes? Le premier temps donne la durée du front haut, le 2ème la durée du front bas etc?
Merci d’avance pour votre réponse.
CALOMARO

Bonjour Calomaro,

Merci pour ton intérêt, je savais bien que quelqu’un finirait un jour par s’intéresser à mon radiateur :smiley:

Les valeurs que j’ai dans le fichier de configuration de LIRC :

300     2000    1000    2000    2000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    1000    2000    2000    1000    1000    1000    1000    1000    1000    218700

sont en effet des alternances de fronts haut et bas en microsecondes.

Ma télécommande est en plastique, il est possible qu’on ne soit pas sur le même type de radiateur et qu’il faille que tu enregistres les codes envoyés par la tienne.

Perso, j’ai utilisé un raspberry Pi, un récepteur IR 38khz, une led IR (pour tester la réémission des signaux) et le programme LIRC (mode2 pour la captation et irsend pour le rejeu).
Voici un tuto qui décrit pas trop mal ce que j’ai fait.
Mais il est aussi possible de le faire sur Arduino ou ESP (tuto ReceiveDump, tuto SendRawDemo),

Mais plutôt que de réinventer la roue, il existe des firmwares adaptables pour les ESP qui font déjà tout, par exemple Tasmoda IR Remote et des hardware conçu pour, par exemple kbx81IRBlaster. Je te recommande donc un montage à base d’ESP32 avec Tasmoda, une ou deux LED IR et un récepteur IR 38khz le temps de l’aprentissage, ces modules sont incroyablement polyvalents.

N’hésite pas si tu as d’autres questions :wink:

Bonjour,

Merci pour ta réponse rapide. Ma télécommande est HS. J’ai regardé pour en racheter une. Pas loin de 100 euros… J’essayerai déjà avec un PIC (sans être un pro ou ingénieur cela fait bientôt 20 ans que j’en programme). Mais merci pour les autres projets à base d’ESP et pour les timings.
Cordialement

1 « J'aime »

Bonjour,
Tout d’abord bravo pour travail d’investigation permettant de piloter par IR certains types de radiateurs électriques ACOVA.
J’aimerai pouvoir piloter un séche-serviettes ACOVA (type KEVA TCKI-075-050 750W) géré actuellement en IR par un thermostat ACOVA.
Comme trop souvent, le protocole IR utilisé est propriétaire, non documenté, et ne correspond à aucune des télécommandes référencées pour LIRC.
Je compte utiliser un Raspberry PI (buster) utilisant LIRC.
Je reçois bien les ordres envoyés par le thermostat, mais je bute sur la création des fichiers « conf » de LIRC pour envoyer les ordres correspondant à un fichier lirc.conf.
J’ai pu analyser les signaux transmis par le thermostat avec LIRC et IRscrutinizer, mais les résultats obtenus (sous plusieurs formats différents) ne m’ont pas permis de mettre en œuvre une solution satisfaisante.

Pourrais-tu me communiquer des informations sur la manière dont:

  • tu as pu créer les fichiers de configuration de LIRC (mod2, WIR, wir?, autre outil?) afin de créer les fichiers de configuration permettant de simuler l’envoi de commandes tels que ceux que tu fournis dans le post.
  • tu as assuré la liaison entre LIRC et JEEDOM pour recevoir et transmettre les commandes destinées au thermostat.
    En te remerciant par avance pour ton aide,
    Cordialement,
    D.P.

Hello @pouilld et merci :wink:

J’ai globalement utilisé ce qu’il y a dans ce tuto : IR Blaster (LIRC) – piddlerintheroot
et pour la captation, la commande irrecord -d /dev/lirc0 ~/acova.conf

Dans un premier temps, j’avais fait des scripts bash pour lancer les différentes commandes en SSH depuis jeedom, mais les temps de réponses sont juste ULTRA longs… Donc j’ai fait un service qui se charge au boot du Pi et se connecte à mon broker MQTT, cf section masquée :

Hope it helps,
Bad

Merci beaucoup pour ta réponse qui va me permettre d’avancer.

Cordialement,
D.P.

1 « J'aime »