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 :
- seche-serviettes-acova-fil-pilote-plugin-thermostat
- radiateur-acova-en-hors-gel
- integration-jeedom-radiateurs-acova-alcantara-2-zigbee
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 :
J’espère que ça pourra aider certains
/Bad