Pendant l’automne 2019, une discussion avec mon voisin m’avait poussé à me pencher à nouveau sur mon système de chauffage, après déjà plusieurs tentatives infructueuses pour comprendre son fonctionnement (en fait, je me repenche globalement dessus au début de chaque hiver). Et comme chaque année, je me suis retrouvé bloqué. J’avais cette fois jeté une bouteille à la mer par le biais de cet article. Ce billet est le prolongement de celui-ci, aussi je vous invite à le lire en premier si ce n’est pas déjà fait pour replacer le contexte. Pour rappel, mon objectif est de récupérer la température de chaque pièce.
Un ami s’est alors penché sur le problème et m’a demandé un exemple des trames reçues. Ravi que quelqu’un s’intéresse à mon cas, d’autant que je ne sais absolument plus où chercher, je lui fournis donc un échantillon :
0x1 0x3 0x0 0x0 0x0 0x8 0x44 0xc 0x1 0x3 0x3 0xe8 0x0 0x2 0x44 0x7b 0x1 0x3 0x4 0x0 0x1 0x0 0x1 0x6a 0x33 0x1 0x3 0x3 0xea 0x0 0x1 0xa5 0xba 0x1 0x3 0x2 0x0 0x0 0xb8 0x44 0x1 0x10 0x0 0x3e 0x0 0x2 0x4 0x0 0x0 0x0 0x2 0xf0 0xf6 0x1 0x10 0x0 0x3e 0x0 0x2 0x20 0x4 [...] 0x2 0x3 0x0 0x0 0x0 0x8 0x44 0x3f 0x2 0x10 0x0 0x70 0x0 0x2 0x40 0x20 0x2 0x10 0x0 0x14 0x0 0x2 0x4 0x41 0x98 0x0 0x0 0x69 0xc7 0x2 0x10 0x0 0x14 0x0 0x2 0x1 0xff 0x2 0x10 0x0 0x3c 0x0 0x2 0x4 0x0 0x0 0x0 0x1 0x3e 0x6a 0x2 0x10 0x0 0x3c 0x0 0x2 0x81 0xf7 0x2 0x10 0x0 0x50 0x0 0x4 0xc1 0xe8 [...] 0x3 0x3 0x3 0xe8 0x0 0x2 0x45 0x99 0x3 0x3 0x4 0x0 0x1 0x0 0x1 0x49 0xf3 0x3 0x3 0x3 0xea 0x0 0x1 0xa4 0x58 0x3 0x3 0x2 0x0 0x0 0xc1 0x84 0x3 0x10 0x0 0x50 0x0 0x4 0xc0 0x39 [...] 0x4 0x3 0x3 0xe8 0x0 0x2 0x44 0x2e 0x4 0x3 0x4 0x0 0x1 0x0 0x1 0x3f 0x33 0x4 0x3 0x3 0xea 0x0 0x1 0xa5 0xef 0x4 0x10 0x0 0x50 0x0 0x4 0xc1 0x8e 0x4 0x10 0x0 0x70 0x0 0x2 0x40 0x46 0x4 0x10 0x0 0x14 0x0 0x2 0x1 0x99 0x4 0x10 0x0 0x3c 0x0 0x2 0x81 0x91
J’ai bien remarqué que la trame débute par une valeur entre 1 et 4, et j’ai justement 4 thermostats. Je me demande alors si ce premier octet est une adresse physique, ou alors une sorte de jeton qui serait incrémenté entre chaque périphérique. Pour le savoir, il faut déconnecter l’un des thermostats :
0x1 0x3 0x0 0x0 0x0 0x8 0x44 0xc [...] 0x2 0x3 0x0 0x0 0x0 0x8 0x44 0x3f [...] 0x4 0x3 0x3 0xe8 0x0 0x2 0x44 0x2e [...]
Si c’était un numéro de jeton, sa valeur serait comprise entre 1 et 3. Ici, on a donc bien affaire à une adresse matérielle.
Je remarque aussi que les deux derniers octets sont assez différents des précédents et qu’ils ressemblent bien à un CRC. Je cherche donc comment est calculé celui du protocole Bacnet, afin de vérifier s’il correspondent. La description officielle du protocole n’étant pas librement accessible, je trouve la réponse ici :
J’ai donc le polynôme et je peux vérifier les CRCs ! Ils concordent. Cela me conforte dans l’idée que du Bacnet a bien quelque chose à voir là-dedans, mais je ne comprends pas pourquoi les trames n’ont pas du tout la structure attendue. J’envisage qu’il y ait une surcouche propriétaire, ou même que les données soit chiffrées : deux hypothèses assez démoralisantes.
Par contre, cela valide au moins mon interfaçage sur la couche physique : j’avais également envisagé l’hypothèse que malgré les apparences, il y ait une astuce au niveau matériel qui explique que je ne lise pas ce qui transite réellement sur le bus.
J’en conclus que je suis face à un échec supplémentaire et j’envoie la copie des trames à l’ami en question, sans trop y croire.
Mais pourtant, quelques jours plus tard, je reçois ce message :
Voilà une sacré information ! A partir de ce moment, tout se débloque. Cependant, absolument rien ne fait mention de ce protocole dans la documentation du système. Il est en revanche explicitement indiqué que la centrale fonctionne via Bacnet. Du coup, je n’avais même pas cherché si ces paquets correspondaient à d’autres protocoles usuels.
Mais il s’avère que les messages que je reçois sont bel et bien conformes à la structure d’une trame MODBUS :
Les CRC de ces deux protocoles utilisent par hasard les mêmes polynômes, ce qui m’a induit en erreur. Du coup, mon objectif n’est plus qu’une question de temps !
Le protocole MODBUS est de type maître-esclaves. Le maître fait une requête à un esclave, qui y répond ensuite. J’utilise le site www.simplymodbus.ca pour obtenir des exemples de trames et me documenter sur ce protocole que je ne connais que de nom.
- Le premier octet est l’adresse de l’esclave adressé. Dans mon cas, sa valeur comprise entre 1 et 4 est cohérente.
- L’octet suivant indique le type d’action demandée par le maître. Dans mon cas, elle vaut 0x3 ou 0x10, très occasionnellement 0x11. La valeur 0x3 signifie Read Holding Register : elle est utilisée par le maître pour demander à un esclave de renvoyer le contenu d’un ou plusieurs de ses registres. La valeur 0x10 signifie quant-à elle Preset Multiple Registers et permet de faire l’opération inverse, à savoir un cycle d’écriture. Pour terminer, la fonction 0x11 semble dédiée à du diagnostic ou du débogage et peut être ignorée.
La centrale de gestion passe donc son temps à lire et écrire des registres des sondes de température. J’approche maintenant du but : il ne reste plus qu’à déterminer à quoi servent les différents registres, et écouter leur valeur au passage.
L’outil d’écoute
Pour deviner la signification de chaque registre, il me faut un outil permettant d’afficher leurs valeurs d’une manière plus intuitive que des lignes qui défilent dans un terminal.
Je cherche alors rapidement si ce travail existe déjà. Je tombe sur plusieurs logiciels ou bibliothèques permettant de s’interfacer avec des maîtres ou des esclaves Modbus, mais ce n’est pas ce que je cherche : j’ai besoin d’un outil qui écoute passivement le bus, et récupère les informations au passage. Puisque l’outil idéal n’existe pas encore, je dois le fabriquer.
Le principe est simple : en écoutant la requête du maître puis la réponse de l’esclave, je peux récupérer la donnée stockée dans les registres concernés, que ce soit un cycle de lecture ou d’écriture. La seule difficulté est de déterminer, pour chaque message, s’il s’agit d’une requête ou d’une réponse. En effet, même si l’analyse de la trame permet de déterminer la direction de la plupart des messages sans ambigüité, il en existe certaines qui sont structurellement correctes dans les deux sens. Par conséquent, j’utilise une autre méthode pour obtenir le sens d’échange du paquet : lorsque l’adresse de l’esclave change, c’est un paquet qui provient forcément du maître. A partir de là, on sait que la direction s’inverse à chaque trame.
Pour commencer, mon outil analyse chaque trame et affiche, pour chacune d’elle, les adresses des registres lus ou écrits ainsi que leur valeur. J’obtiens donc un affichage sous cette forme :
La trame elle-même est affichée en blanc, suivie de sa signification en français. Elle est écrite en magenta s’il s’agit d’une requête du maître ou en vert s’il s’agit d’une réponse de l’esclave.
Juste en dessous, j’affiche l’adresse de chaque registre concerné, associé à sa valeur : cela me permet de valider l’analyse des trames. Je peux alors m’atteler à la mise en forme.
J’opte pour un tableau à double entrée. Les lignes sont peuplées par les adresses des registres, et les colonnes par les différents esclaves. Ainsi, la valeur de chaque registre de chaque esclave apparaît à l’intersection des deux :
A gauche de chaque valeur se trouve la lettre R ou W, suivant si elle a été obtenue grâce à un cycle de lecture ou d’écriture. La valeur elle-même apparaît en couleur magenta si elle a été mise à jour récemment, en blanc sinon. Les intersections vides correspondent aux registres inutilisés de certains esclaves (aucun cycle de lecture ni d’écriture). L’affichage se rafraîchit en temps réel.
On remarque que le thermostat n°1 est différent des autres : il possède des registres que les autres n’ont pas, et inversement. En effet, c’est le thermostat principal, à partir duquel on peut notamment choisir le mode de chauffage global (Confort, Eco, Stop…).
Bref, avec cela, découvrir la signification des registres principaux est un jeu d’enfant : il suffit de regarder quelles valeurs sont modifiées lorsque différentes actions se produisent. Après quelques manipulations, j’obtiens le résultat suivant :
Il est constitué des fichiers suivant :
- main_modbus.c/h : ouvre le port série, assemble les caractères reçus en trames et les envoie à la librairie pour décodage.
- modbus_affichage.c/h : gère l’affichage sur le terminal.
- modbus.c/h : s’occupe du décodage des trames.
- serial.c/h : gère le port série (seulement pour Linux).
- fonctions.c/h : implémente différentes petites fonctions diverses.
Le programme est organisé de façon à ce que modbus.c/h soit une sorte de librairie (il faut le dire vite). Lorsque j’aurai à implémenter le décodage dans le microcontrôleur, je n’aurai que ce fichier à inclure au projet.
Réalisation matérielle
Maintenant que je sais enfin comment lire la température de chaque pièce, je dois réaliser la partie matérielle permettant de connecter proprement le bus des thermostats à mon système domotique. Pour cela, je décide de réaliser une autre carte d’interface plutôt que de seulement apporter le bus à ma carte existante pour deux raisons.
Premièrement, je ne vais pas me contenter de la température de chaque pièce : j’aimerais également savoir, à tout moment, quelle(s) pièce(s) est(sont) en train de chauffer et quelle est la vitesse de la soufflerie. Je voudrais aussi pouvoir agir : pour cela, une entrée logique de la centrale de gestion permet de basculer entre le mode Eco (16°C dans tout l’appartement) et le mode Confort (consigne réglée séparément dans chaque pièce). C’est un bon début, mais ce n’est pas suffisant : je voudrais pouvoir choisir individuellement quelle pièce chauffer, quelle que soit la température réelle et la consigne programmée. Pour cela, je dois inhiber le fonctionnement de la centrale et prendre moi-même le contrôle des différents actionneurs (chaudière, clapets et soufflerie). J’ai donc besoin d’un certain nombre d’entrées-sorties supplémentaires.
Deuxièmement, la chaudière et le système de chauffage sont situés dans une sorte de placard dans la salle de bain. C’est aussi à cet endroit que sont présentes l’arrivée d’eau et les nourrices permettant de la distribuer dans l’appartement. Je vais en profiter pour installer un compteur d’eau sur le circuit, il me faut donc une alimentation et une autre entrée logique. Pour terminer, l’un des murs intérieurs du placard est mitoyen de ma cuisine et j’ai expliqué dans mon article sur le cadre photo que j’asservis le fonctionnement du cadre à l’éclairage des plans de travail. Il est donc beaucoup plus pratique pour moi d’apporter cette information sur une dernière entrée logique d’une carte d’interface présente à cet endroit.
Comme l’architecture de ma domotique commence à se complexifier un peu, je vous ai préparé une feuille de pompe :
Compte tenu du nombre de signaux gérés par cette nouvelle carte d’interface, son usage est pleinement justifié. Donc, au boulot !
D’abord, la théorie…
Voici son schéma électronique (cliquez pour l’agrandir). Je vais détailler chaque bloc par la suite.
Le cœur est un microcontrôleur STM32F042 installé sur une carte Nucleo afin de permettre son intégration sur une carte à Veroboard (aussi appelée « plaque à bandes » ou « plaque à pastilles »). Pour plus de sécurité et de fiabilité, j’ai décidé d’insérer une isolation galvanique entre le système électrique du chauffage et le reste de ma domotique. Je trouve que sans elle, la masse électrique du serveur serpenterait un peu trop loin dans tout l’appartement. De plus, l’interface du chauffage pilote des actionneurs 230V (bien sûr à travers une autre isolation galvanique), mais de cette manière, une double isolation est présente entre le secteur et le serveur.
J’ai beaucoup hésité à mettre le microcontrôleur d’un côté ou de l’autre de l’isolation. Il me paraissait plus naturel de l’installer côté serveur, et d’isoler l’interfaçage au bus et à la centrale. Néanmoins, le signal 0-10V du ventilateur, que je dois lire et écrire, m’aurait posé problème : il est plus complexe d’isoler galvaniquement un signal analogique que numérique. Du coup, j’ai décidé de placer le microcontrôleur côté centrale de chauffage.
Partie alim
Le bus des thermostats transporte en plus de la liaison Modbus une alimentation de +15V. J’alimente donc le microcontrôleur grâce à elle, à travers un convertisseur DC/DC TSR 1-2433 qui délivre 3,3V. J’ai prévu de piloter les actionneurs 230V grâce à de classiques relais électromécaniques. Comme je pensais initialement mettre l’isolation galvanique de l’autre côté, j’avais à ma disposition le 19V du serveur pour les alimenter : les modèles à bobine 24V parvenaient alors à coller dans ces conditions. Malheureusement, 15V n’est plus suffisant. Pour remédier à cela, j’ai ajouté un convertisseur DC/DC 3,3V->3,3V isolé : en câblant son secondaire en série sur le 15V, je le réhausse à 18,3V ce qui est acceptable pour les relais.
Partie interface secteur
L’interface de puissance pour le 230V est composée de 5 blocs identiques, pour commander les 4 clapets ainsi que la chaudière. Ils sont équivalents au le schéma suivant :
Le contact du relais est câblé en parallèle de celui intégré dans la centrale d’origine. Ainsi, la charge reste pilotée de manière transparente par la centrale, mais je peux aussi forcer son activation si besoin via l’interface. De plus, la LED d’un optocoupleur (avec ses résistances pour limiter le courant à environ 125µA) est également câblée en parallèle. Ainsi, lorsque la charge n’est pas alimentée, la LED conduit en s’alimentant à travers elle. Par contre, si l’un des deux relais colle, la LED est court-circuitée et ne conduit plus : c’est exactement ce fonctionnement qui est utilisé sur les interrupteurs domestiques qui comportent un voyant. Une autre LED, câblée en antiparallèle, permet d’obtenir un témoin lumineux tout en protégeant l’optocoupleur de la forte tension inverse (325V crête) qui apparaîtrait. Le phototransistor de l’optocoupleur est connecté curieusement au système : le collecteur est relié au +18V au lieu du +3,3V. C’est une erreur que j’ai faite lors de la réalisation matérielle, complexe à corriger physiquement et dont je me suis évidemment rendu compte une fois les 5 blocs soudés. Comme le courant dans la LED est faible, tout comme le taux de transfert de l’optocoupleur, je peux me permettre de limiter la tension à son émetteur grâce à une diode TVS. Il fonctionne alors en régime linéaire mais ce n’est pas bien gênant.
Interfaçage avec la centrale
Ici, rien de particulier :
On trouve :
- Un transceiver MAX3485 pour le bus.
- Un pont diviseur pour lire la tension 0-10V appliquée au ventilateur par la centrale.
- Un AOP LM358 précédé d’un filtre RC passe-bas pour générer à l’inverse une tension de commande 0-10V à partir d’un canal PWM du µC.
- Deux optocoupleurs pour piloter les entrées numériques de la centrale, que j’utilise en tant en simples transistors, sans mettre à profit l’isolation galvanique qu’ils procurent. Mais si je veux la rajouter dans le futur, j’aurai déjà préparé le travail. Seul le signal Confort/Eco est utilisé pour l’instant.
Interfaçage avec le serveur
Ici, rien d’extravagant non plus. Cette carte d’interface ne communique pas directement avec le serveur, mais avec la carte d’interface précédente via une liaison série à 9600 bauds. Je voulais absolument isoler les signaux ici, c’est pourquoi j’ai utilisé des circuits H11L1M. Ces optocoupleurs ont une sortie logique push-pull, ce qui me permet d’avoir une transmission de bien meilleure qualité et beaucoup plus rapide que celle que j’aurais pu obtenir grâce à un optocoupleur à sortie transistor classique. Bien sûr, une vraie liaison RS232 apporterait encore une meilleure immunité, mais j’estime que la relative faible distance à parcourir et la vitesse de transmission lente permettent de s’épargner l’intégration de translateurs de niveaux . L’inconvénient est qu’il faut alimenter leur partie réceptrice, j’ai donc dû monter un LDO 3,3V LE33 pour générer cette tension à partir du 19V.
En plus de la liaison série communicant avec l’interface du serveur, j’envoie dans le câble le signal brut du Modbus. Il n’est habituellement pas utilisé, mais pour les tests, il me suffit de brancher un convertisseur USB/série entre ce signal et le serveur (ou un PC quelconque) pour récupérer les trames. Cela me permet de l’apporter à proximité du serveur sans faire courir de câble au milieu de l’appartement, même pour des essais. Pour terminer, deux autres optocoupleurs, cette fois à sortie transistor, transmette les informations du compteur d’eau et de l’éclairage de la cuisine au microcontrôleur.
Et maintenant, la pratique !
Je commence donc à souder les composants sur un morceau de Veroboard, de taille adaptée à un coffret Schneider de récupération :
L’optocoupleur, la LED témoin CMS et les résistances de limitation de courant sont soudées sur la face arrière :
Un peu plus tard…
Avant de l’installer, je dois tirer un câble entre la salle de bains et le serveur. Les deux ne sont pas très éloignés, quelques mètres seulement, et je peux passer dans le faux plafond. Néanmoins, ce n’est en pratique pas si facile. Heureusement, le câble alimentant la chaudière depuis le tableau électrique circule librement dans le faux plafond. Je peux donc l’utiliser comme aiguille : je le déconnecte de la chaudière en attachant solidement un fil de fer à son extrémité. Depuis le tableau électrique, je le tire entièrement jusqu’à récupérer le fil de fer, avant de re-tirer ce dernier dans le sens inverse après avoir ajouté mon nouveau câble.
Il ne reste plus qu’à ajouter sur la première carte d’interface (décrite dans l’épisode 1 de la domotique) un connecteur RJ45 pour réceptionner son extrémité :
C’est alors le moment de faire un peu de plomberie et d’ajouter le compteur d’eau. Pour cela, je dégotte sur leboncoin.fr un compteur d’occasion pour seulement quelques euros. Comme la très grande majorité des compteurs d’eau abordables, il ne possède pas de sortie impulsion.
Je le démonte donc pour ajouter un capteur inductif en face de l’axe qui entraîne le mécanisme d’affichage. Je colle dessus un morceau de papier aluminium sur la moitié de sa périphérie. Puisqu’il fait un tour par litre d’eau consommé, j’ai une résolution de 0,5L :
Voilà le résultat obtenu. Plus qu’à l’installer !
Je l’insère donc en série sur le circuit d’arrivée d’eau, juste après la vanne de coupure principale. Le circuit d’origine est dessiné en rouge et mes modifications apparaissent en vert :
J’ai alors tous les éléments pour faire les premières connexions, avec les différents câbles pas encore très ordonnés :
Avant de ranger tout ceci un peu mieux :
La réalisation matérielle est maintenant terminée. Une bonne chose de faite 🙂
Réalisation logicielle
Comme d’habitude, ce beau monde serait bien inutile sans logiciel embarqué. De manière similaire à l’interface du serveur, celle-ci communique avec les trames suivantes :
Sens interface -> serveur
O\n : impulsion du compteur... d'eau C0\n : éclairage cuisine éteint C1\n : éclairage cuisine allumé T:Ventil=UU.U;Chaudiere=X;S=X,TT.T,CC.C;Ch1=X, TT.T,CC.C ;Ch2=X, TT.T,CC.C ;Ch3=X, TT.T,CC.C\n : UU.U : tension du ventilo (0-10V) X : booléen, 0 ou 1, indique si le canal est en fonctionnement ou pas CC.C : température de consigne TT.T : température réelle
Sens serveur -> interface
RG\n : allumage du chauffage général (via signal Eco/Confort) Rg\n : extinction du chauffage général RS\n : forçage du chauffage du salon activé Rs\n : forçage du chauffage du salon désactivé RC\n : forçage de du chauffage la chambre activé Rc\n : forçage du chauffage de la chambre désactivé RB\n : forçage du chauffage du bureau activé Rb\n : forçage du chauffage du bureau désactivé RA\n : forçage du chauffage de la chambre d'Ami activé Ra\n : forçage du chauffage de la chambre d'Ami désactivé RV\n : forçage du ventilateur dans toutes pièces sans chauffage activé Rv\n : forçage u ventilateur dans toutes pièces sans chauffage désactivé
Les deux dernières trames permettent d’activer/désactiver le ventilateur à puissance maximale en ouvrant tous les clapets, mais sans activer la chaudière. Cela permet d’homogénéiser la température dans l’appartement : il arrive que l’étage surchauffe (24°C) même en hiver grâce à l’apport solaire, mais que les chambres en dessous restent très fraîches (17°C). Le but est donc de moyenner ces températures.
Ces trames transitant à travers l’autre interface, je dois légèrement modifier son logiciel afin que cette carte intermédiaire soit transparente pour les deux interlocuteurs. Pour cela, elle doit retransmettre au système de contrôle du chauffage toute trame provenant du serveur qui ne la concerne pas. De même, elle retransmet au serveur n’importe quelle trame provenant de l’interface de la chaudière.
Comme d’habitude, c’est le site Web qui joue le rôle d’interface homme-machine, en association avec le démon qui gère la domotique en arrière-plan.
- Pour le compteur d’eau, le fonctionnement est exactement le même que pour le compteur de gaz : à chaque impulsion, le timestamp courant est ajouté à un fichier texte créé quotidiennement.
- Lors du changement d’état de l’éclairage de la cuisine, une requête HTTP est envoyée au cadre photo via cURL pour qu’il le suive.
- Les trames apportant l’état du chauffage sont également stockées dans un fichier texte portant la date du jour.
Voici alors le résultat obtenu :
Comme pour le gaz, les deux graphiques prennent leur source dans les mêmes données. Le premier affiche la consommation cumulée depuis le début de la journée, tandis que le second est remis à zéro après un certain temps sans consommation, ce qui permet de séparer les différents postes de dépense.
Pour le chauffage, deux graphiques sont tracés. Le premier donne l’évolution de la température de chaque pièce, tandis que le second donne un retour sur l’état du chauffage. On voit apparaître quelles pièces ont été chauffées et quand.
Pour le forçage du chauffage de certaines pièces, je réutilise le code que j’avais écrit pour les sèche-serviettes. Chaque pièce (+ la régulation automatique et la ventilation seule) est vue au niveau du logiciel comme un radiateur, dont je peux programmer les heures de chauffe pour chaque jour de la semaine ou forcer le fonctionnement pendant une durée prédéterminée :
Je suis maintenant capable piloter le chauffage exactement comme je le souhaite. J’aurais pu me limiter au contrôle de l’entrée Confort/Eco de la centrale, ce qui m’aurait beaucoup simplifié la tâche (la carte d’interface aurait alors pu se limiter au microcontrôleur + trois optocoupleurs). En contrepartie, je n’aurais pu qu’activer, ou non, le chauffage dans la globalité, sans pouvoir gérer individuellement chaque pièce. C’eut été dommage : ainsi, je peux limiter mon impact carbone en programmant des plages horaires de chauffage différentes pour chaque pièce et ne pas toujours chauffer l’appartement entier.
Merci de m’avoir lu. Comme nous sommes déjà fin mars, les apports solaires suffisent à chauffer l’appartement (merci les logements basse consommation !). Je n’aurai donc certainement pas l’usage concret de tout ceci avant l’hiver prochain. On verra bien à ce moment-là ce que ça donne 🙂 .