Passerelle VMC Helios

De Wiki LOGre
Aller à : navigation, rechercher

Projet réalisé par fma38.

Abandonné

Présentation

Le but de ce projet est de hacker le protocole de dialogue d'une VMC double flux Helios KWL EC 370 Pro afin de pouvoir l'intégrer dans une supervision domotique (basée sur Linknx).

La VMC peut dialoguer avec 4 petits contrôleurs (1 seul fourni) permettant de voir un certain nombre d'informations, et de modifier un certain nombre de consignes. La VMC peut également interfacer 4 sondes d'humidité, et 4 sondes CO2 (non fournies).

L'idée serait de pouvoir se faire passer pour l'un des contrôleurs.

Couche physique

La connexion entre tous les élements cités ci-dessus se fait via une liaison RS-485. De ce que j'ai pu voir, les drivers de ligne utilisés sont des 3082 de chez Ti, qui s'utilisent exactement comme les classiques MAX485 de chez Maxim.

Couche logicielle

Sniffer la ligne RS-485

Pour commencer, j'ai intercalé un convertisseur RS485-RS232 sur la ligne, afin de voir comment la VMC et le contrôleur dialoguaient. Premier souci : retrouver qui parle à un instant t ! En effet, c'est une ligne half-duplex, et chaque device parle à tour de rôle, mais il n'y a aucun moyen de savoir lequel emet et lequel reçoit. Après avoir essayé vainement de bricoler un truc avec un Arduino, j'ai finalement regardé de plus près le contrôleur et vu qu'il y avait un petit connecteur HE10, dont une des pattes est directement reliée à la broche D (pin 4) du driver de ligne. En se connectant dessus, on peut donc savoir ce que le contrôleur envoie sur le bus. Et, coup de bol, il y a la même chose sur la VMC, juste derrière la trappe de visite !

Du coup, en connectant 2 convertisseurs USB-TTL genre TTL-232R-5V de chez [FTDI, on peut sniffer la ligne très simplement. Voici un petit bout de code python que j'utilise pour ça :

#!/usr/bin/python                                                                                                                                         
 
import time                                                                                                                                               
 
import serial
 
s0 = serial.Serial("/dev/ttyUSB0")
s1 = serial.Serial("/dev/ttyUSB1")
 
s0.flushInput()
s1.flushInput()
 
t = time.time()
while True:
    a = ''
    a_ = 0
    b = ''
    b_ = 0
    if s0.inWaiting():
        a = hex(ord(s0.read(1)))
        a_ = eval(a)
    if s1.inWaiting():
        b = hex(ord(s1.read(1)))
        b_ = eval(b)
    if a or b:
        print "%.3f:\t%4s (%3d)\t%4s (%3d)" % (time.time() - t, a, a_, b, b_)

Décodage du protocole

C'est là que les choses se compliquent.! En effet, Helios, appliquant le célèbre adage pourquoi faire simple quand on peut faire compliqué, a choisi de faire son propre protocole, binaire, bien fermé, histoire de nous vendre plus de matos, genre la passerelle KNX qu'ils n'ont même plus au catalogue (vu que la boîte qui leur faisait s'est cassé la gueule) et dont on ne connaît pas la date de sortie de la prochaine !

Du coup, ben, faut se farcir les trames qui circulent, et essayer de comprendre leur signification. En gros, il faut essayer de rentrer dans la logique des développeurs allemands, ce qui, je vous le dis, n'est pas une mince affaire !!!

Comme dit plus haut, il s'agit d'une ligne RS-485 half-duplex, donc un seul participant peut parler à la fois. Géneralement, on a un maître, et des escalves, afin que ce ne soit pas le bordel. C'est le cas ici (enfin, ici, y'a un maître et des esclaves, mais c'est aussi le bordel !).

VMC seule

Pour commencer, j'ai débranché le contrôleur, et regardé un peu ce que la VMC envoyait sur le bus. Plusieurs trames sont répétées à intervalle régulier. Voici en gros ce qu'on peut voir (le premier chiffre est le timing, le second, la valeur lue, en hexa) :

0.365:    0x10
0.366:    0x21
0.367:    0xfe

0.465:    0x10
0.466:    0xa1
0.467:    0xfe

0.566:    0x11
0.566:    0xa1
0.567:    0xfe

0.665:    0x11
0.666:    0x21
0.667:    0xfe

0.765:    0xff
0.768:    0xb1
0.768:    0xa7
0.770:    0x92
0.770:    0x94
0.771:    0x08
0.772:    0x10
0.773:    0x21
0.774:    0x9a
0.775:    0x8c
0.776:    0xd0
0.777:    0xa1
0.778:    0x8a
0.779:    0x08
0.780:    0xf6
0.781:    0xff

0.866:    0x10
0.866:    0x21
0.867:    0xfe

0.965:    0x10
0.966:    0xa1
0.968:    0xfe

1.067:    0x11
1.067:    0xa1
1.068:    0xfe

1.165:    0x11
1.167:    0x21
1.168:    0xfe

1.266:    0xff
1.267:    0xb1
1.268:    0xa7
1.269:    0x92
1.270:    0x94
1.271:    0x08
1.272:    0x10
1.273:    0x21
1.274:    0x9a
1.275:    0x8c
1.276:    0xd0
1.277:    0xa1
1.278:    0x8a
1.279:    0x08
1.280:    0xf6
1.281:    0xff

1.367:    0x10
1.367:    0x21
1.368:    0xfe

1.466:    0x10
1.467:    0xa1
1.468:    0xfe

1.567:    0x11
1.567:    0xa1
1.568:    0xfe

1.665:    0x11
1.667:    0x21
1.668:    0xfe

1.766:    0xff
1.768:    0xb1
1.768:    0xa7
1.770:    0x92
1.770:    0x8b
1.771:    0x11
1.772:    0x21
1.773:    0x82
1.774:    0x28
1.775:    0x18
1.776:    0xd0
1.777:    0xa1
1.778:    0x8a
1.779:    0x08
1.780:    0xf6
1.781:    0xff

1.867:    0x1a
1.867:    0xb8

1.965:    0x10
1.966:    0xa1
1.967:    0xfe

2.065:    0x11
2.067:    0xa1
2.068:    0xfe

2.165:    0x11
2.167:    0x21
2.168:    0xfe

2.266:    0x14
2.267:    0xa1
2.268:    0xff

2.366:    0x15
2.367:    0xa1
2.368:    0xff

2.467:    0x14
2.467:    0x21
2.468:    0xff

2.565:    0x16
2.567:    0x21
2.568:    0xff

2.664:    0xff
2.667:    0xb1
2.668:    0xa7
2.669:    0x92
2.670:    0x94
2.671:    0x08
2.671:    0x10
2.673:    0x21
2.673:    0x9a
2.675:    0x8c
2.676:    0xd0
2.677:    0xa1
2.677:    0x8a
2.678:    0x08
2.680:    0xf6
2.681:    0xff

J'ai séparé les trames en me basant sur les délais entre caractres. J'ai identifié 3 types de trames :

  • trames à 3 octets périodiques :
    • 0x10 0x21 0xfe
    • 0x10 0xa1 0xfe
    • 0x11 0xa1 0xfe
    • 0x11 0x21 0xfe
  • trames à 3 octets plus épisodiques :
    • 0x14 0xa1 0xff
    • 0x15 0xa1 0xff
    • 0x14 0x21 0xff
    • 0x16 0x21 0xff
  • trame à 16 octets (encadrée pr 0xff...0xff) périodique :
    • 0xff 0xb1 0xa7 0x92 0x8b 0x11 0x21 0x82 0x28 0x18 0xd0 0xa1 0x8a 0x08 0xf6 0xff
  • trame à 2 octets, épisodique :
    • 0x1a 0xb8

Pour l'instant, difficile de savoir de quoi il retourne.

VMC + contrôleur

Branchons donc le contrôleur, et regardons de nouveau :

0.323:    0x10
0.324:    0x21
0.325:    0xfe
0.327:            0x30
0.329:            0x23
0.330:            0xfd
0.331:            0xfd

0.341:    0x70
0.341:    0x21
0.343:    0x41
0.344:    0xff

0.424:    0x10
0.424:    0xa1
0.425:    0xff

0.524:    0x11
0.524:    0xa1
0.525:    0xfe

0.622:    0x11
0.624:    0x21
0.626:    0xfe

0.723:    0xff
0.726:    0xb1
0.726:    0xa7
0.728:    0x92
0.728:    0xac
0.729:    0x08
0.730:    0x10
0.731:    0x21
0.732:    0x9a
0.733:    0x8c
0.735:    0xd0
0.735:    0x61
0.736:    0x8c
0.737:    0x08
0.738:    0x56
0.739:    0xff

0.823:    0x10
0.826:    0x21
0.826:    0xfe
0.828:            0x30
0.829:            0x23
0.830:            0xfd
0.832:            0xfd
0.839:    0x70
0.840:    0x21
0.842:    0x41
0.843:    0xff

0.923:    0x10
0.924:    0xa1
0.925:    0xfe

1.024:    0x11
1.024:    0xa1
1.025:    0xfe

1.123:    0x11
1.124:    0x21
1.125:    0xfe

1.223:    0xff
1.225:    0xb1
1.225:    0xa7
1.227:    0x92
1.227:    0xac
1.228:    0x08
1.229:    0x10
1.230:    0x21
1.231:    0x9a
1.232:    0x8c
1.233:    0xd0
1.234:    0xa1
1.235:    0x8a
1.236:    0x08
1.237:    0x56
1.238:    0xff

1.322:    0x10
1.324:    0x21
1.325:    0xfe
1.328:            0x30
1.329:            0x23
1.330:            0xfd
1.332:            0xfd
1.341:    0x70
1.341:    0x21
1.342:    0x41
1.343:    0xff

1.423:    0x10
1.424:    0xa1
1.425:    0xfe

1.523:    0x11
1.524:    0xa1
1.525:    0xfe

1.623:    0x11
1.624:    0x21
1.625:    0xfe

1.723:    0xff
1.725:    0xb1
1.725:    0xa7
1.727:    0x92
1.727:    0xac
1.728:    0x08
1.729:    0x10
1.730:    0x21
1.731:    0x9a
1.732:    0x8c
1.733:    0xd0
1.734:    0xa1
1.735:    0x8a
1.736:    0x08
1.737:    0x56
1.738:    0xff

1.822:    0x10
1.824:    0x21
1.825:    0xfe
1.828:            0x30
1.829:            0x23
1.831:            0xfd
1.832:            0xfd
1.841:    0x70
1.841:    0x21
1.842:    0x41
1.843:    0xff

1.923:    0x10
1.924:    0xa1
1.925:    0xfe

2.023:    0x11
2.024:    0xa1
2.025:    0xfe

2.124:    0x11
2.124:    0x21
2.125:    0xfe

2.224:    0x14
2.224:    0xa1
2.225:    0xff

2.322:    0x15
2.324:    0xa1
2.325:    0xff

2.423:    0x14
2.424:    0x21
2.425:    0xff

2.523:    0x16
2.524:    0x21
2.525:    0xff

2.622:    0xff
2.624:    0xb1
2.625:    0xa7
2.627:    0x92
2.627:    0x2b
2.628:    0x11
2.628:    0x21
2.630:    0x82
2.631:    0x28
2.632:    0x18
2.634:    0xd0
2.634:    0xa1
2.634:    0x8a
2.636:    0x08
2.638:    0x56
2.638:    0xff

Ça commence à se mettre en place ! En effet, on voit que le contrôleur répond lorsqu'il voit la trame 0x10 0x21 0xfe. Et lorsqu'il ne fait rien de spécial, il envoie 0x30 0x23 0xfd 0xfd, trame à laquelle la VMC répond par 0x70 0x21 0x41 0xff. Même si on se sait pas ce que ça veut dire, on progresse !

La trame 0x10 0x21 0xfe permet de donner la main au premier contrôleur. Par déduction, les trames 0x10 0xa1 0xfe, 0x11 0xa1 0xfe et 0x11 0x21 0xfe permettent sans doute de donner la main aux 3 autres contrôleurs qu'il est possible de relier au bus. On voit qu'il y a un délai d'environ 100ms entre chacune de ces trames, pour laisser le temps aux contrôleurs de répondre (ou pas !). Ou, plus exactement, les trames sont envoyées à intervalle de 100ms. Mais comme elles sont courtes...

Reste à savoir à quoi servent les 4 trames du même genre qui apparaissent de temps en temps (celles qui se terminent en 0xff).

Lecture des températures

Le petit contrôleur a un écran dédié qui affiche les températures d'air de la VMC. Par défaut, il y a 4 températures d'air affichées :

  • extérieur : température de l'air neuf juste avant l'échangeur
  • insufflé : température de l'air neuf juste après échangeur
  • repris : température de l'air vicié juste avant échangeur
  • rejeté : température de l'air vicié juste après échangeur

En option, il peut afficher 2 températures supplémentaires :

  • pré-chauffage : en hiver, afin d'éviter de givrer l'échangeur lorsque l'air extérieur est trop froid, on peut installer une batterie de pré-chauffage sur l'air neuf, avant l'échangeur. Une sonde supplémentaire donne la température de l'air avant cette batterie, et remplace donc la sonde de température extérieur, cette dernière donnant alors la température de l'air neuf entre la batterie de pré-chauffage et l'échangeur
  • chauffage : de la même manière, pour chauffer la maison, on peut installer une batterie de chauffage sur l'air neuf après l'échangeur. La sonde supplémentaire contrôle donc la température de l'air après cette batterie

Les trames échangées lorsque le contrôleur affiche les 4 températures standards sont de la forme :

5.268:    0x10
5.268:    0x21
5.269:    0xfe
5.271:            0x10
5.272:            0xa3
5.274:            0xc9
5.283:    0x30
5.283:    0xaf
5.284:    0xc5
5.286:    0xd5
5.286:    0x68
5.287:    0x10
5.288:    0x21
5.289:    0x41
5.291:    0x9c
5.291:    0xa9
5.292:    0x71
5.293:    0xa5
5.294:    0x8a
5.295:    0x8d

5.368:    0x10
5.368:    0xa1
5.369:    0xfe

5.468:    0x11
5.468:    0xa1
5.469:    0xfe

5.568:    0x85
5.568:    0x08

5.666:    0xff
5.667:    0x91
5.669:    0xa5
5.670:    0x92
5.671:    0x54
5.673:    0x11
5.673:    0x21
5.674:    0x82
5.675:    0x28
5.676:    0x18
5.678:    0xd0
5.678:    0xa1
5.679:    0x8a
5.680:    0x08
5.682:    0xb6
5.682:    0xff

5.768:    0x10
5.768:    0x21
5.769:    0xfe

5.867:    0x10
5.868:    0xa1
5.869:    0xfe

5.968:    0x11
5.968:    0xa1
5.969:    0xfe

6.067:    0x11
6.067:    0x21
6.068:    0xfe

6.166:    0x10
6.168:    0xa1
6.169:    0xff

6.268:    0x11
6.268:    0xa1
6.269:    0xff

6.366:    0x10
6.368:    0x21
6.369:    0xff

6.468:    0x12
6.468:    0x21
6.469:    0xff

6.566:    0xff
6.568:    0x91
6.569:    0xa5
6.570:    0x92
6.571:    0x54
6.573:    0x11
6.573:    0x21
6.574:    0x82
6.575:    0x28
6.576:    0x18
6.578:    0xd0
6.578:    0x61
6.579:    0x8c
6.580:    0x08
6.581:    0xb6
6.582:    0xff

6.668:    0x10
6.668:    0x21
6.669:    0xfe

6.768:    0x10
6.768:    0xa1
6.769:    0xfe

6.867:    0x11
6.868:    0xa1
6.869:    0xfe

6.968:    0x11
6.968:    0x21
6.969:    0xfe

7.066:    0xff
7.067:    0x91
7.069:    0xa5
7.070:    0x92
7.071:    0x54
7.073:    0x11
7.073:    0x21
7.074:    0x82
7.075:    0x28
7.076:    0x18
7.078:    0xd0
7.078:    0xa1
7.079:    0x8a
7.080:    0x08
7.082:    0xb6
7.082:    0xff

7.167:    0x10
7.167:    0x21
7.168:    0xfe
7.172:            0x10
7.173:            0xa3
7.174:            0xc9
7.182:    0x30
7.183:    0xaf
7.183:    0xc5
7.184:    0xd5
7.186:    0x68
7.187:    0x10
7.187:    0xa1
7.188:    0x41
7.190:    0x9c
7.191:    0xa9
7.191:    0x71
7.192:    0xa5
7.193:    0x8a
7.194:    0x85

On voit que la trame envoyée par le contrôleur à changé : de 4 octets, on passe à 3 : 0x10 0xa3 0xc9. À cela, la VMC répond par une trame de 14 octets, comme ici 0x30 0xaf 0xc5 0xd5 0x68 0x10 0x21 0x41 0x9c 0xa9 0x71 0xa5 0x8a 0x8d. Clairement, cette trame contient les valeurs des températures à afficher ; reste à la décoder ! Voilà quelques nuits que je me casse les dents dessus...

Au passage, on voit que, grosso-modo, la VMC done la main à chacun des contrôleurs toutes les 1/2 seconde. Lorsqu'il ne fait rien, le contrôleur répond à chaque fois (avec la trame de 4 octets). Par contre, en mode affichage de température, on voit qu'il ne se manifeste que toutes les 2 secondes ; c'est très exactement le délai du léger clignotement constaté sur l'écran.

Pour le moment, je n'arrive pas à trouver de corrélation entre les valeurs dans la trame et les températures affichées. J'ai pourtant relevé les trames à différentes températures, mais ce n'est pas simple, surtout lorsqu'on sait que sur le protocole d'une autre VMC Helios, la valeur est calculée comme suit :

temp = (valeur - 100) / 3

Non-non, les allemands n'ont absolument pas l'esprit tordu !!! Donc s'ils ont fait un truc du même genre, ou s'ils se sont amusés à faire des décalages de bits d'un octet à l'autre (stream de n bits par valeur, n n'étant pas calé sur un octet, ni peut-être même pas sur un quartet), on n'est pas dans la merde !

Mais j'ai une piste de travail : puisqu'il est possible de monter 2 sondes de température supplémentaires (par des tests successifs, j'ai trouvé qu'il faut une thermistance de 40k environ), en mettant un potar de 40k, on peut donc faire varier la température de pré-chauffage ou de chauffage, températures qui s'affichent alors à la suite des autres sur le contrôleur. J'ai ainsi pu voir des changements significatifs dans les trames, mais rien encore pour en tirer des conclusions.

Pour le moment, je lance mon script python, je sauve les trames (qui défilent à fond !), je relève les températures et je dépouille tout ça à la main. Par efficace. Je compte donc écrire un autre script qui va me permettre de me faire passer pour le contrôleur ; je pourrais alors faire des lectures de température quand je veux (et non pas limitées à quelques secondes, délai au bout duquel le contrôleur repasse en veille !), et les stocker dans un tableau, pour voir un peu les petites variations (constatées sur l'écran, généralement +-0,1°C).

En mettant des valeurs significatives pour les températures de pré-chauffage et chauffage (genre 0°C), on devrait pouvoir affiner l'analyse.

À suivre !

Divers

  • piste à creuser : la notice Helios indique qu'on peut connecter plusieurs VMC ; il doit donc y avoir quelque part l'adresse de la VMC qui émet, et qui doit être reprise par le device qui communique en réponse.
  • questions :
    • que signifie la trame envoyée régulièrement par la VMC, encadrée par 0xff ?
    • que signifie la trame 0x1a 0xb8 ?

Ressources

Datasheets