Remontées d'informations sur Jeedom depuis un WiserLink 31800

Tags: #<Tag:0x00007fcbacfc55d8>

Bonjour,

Je cherche à remonter des informations depuis mon compteurs Wiser Link. Malheureusement j’ai le nouveau 31800 (et non le 31600) et celui-ci n’est pas compatible avec le plugin officiel Wiserlink. Je ne dois pas être le seul :smile:

Je suis tombé sur un message d’une tentative sur l’ancien forum (ici), et semble avoir presque réussi,mais c’est du chinois pour moi.

Du coup ce petit post pour savoir si quelqu’un avait réussit ou des pistes, soit en local soit en se connectant au site web Schneider

1 J'aime

Bonjour,
Je suis dans le même cas que toi.
Pour suivre.

1 J'aime

Bonjour,

Je suis aussi dans la même situation. Au vu du post sur l’ancien forum, le module 31800 ne communique par directement via une api REST mais via un protocole SOAP spécifique, le DPWS.

J’arrive à découvrir le service en python (je ne connais pas le php), pas encore à l’interroger.
Je vous ferais par de mes avancée si j’arrive à quelque chose.

1 J'aime

Bon, j’avance un peu. Mais n’étant ni un expert en réseau, ni en java, c’est un peu laborieux.

En scannant les port du module (nmap) , j’ai 80 (http), 443 (https), 5357 (wsdapi) et 5000

  • wsdapi c’est du soap sur udp (par opposition à tcp qui utilise les adresse ip) pour faire de la découverte de device sur un réseau sans connaitre à l’avance son ip. (Comme udp permet de faire du broadcast ). L’étape de discovery, j’arrive à la répliquer avec ws-discovery en python. A ce stade, j’ai une ip, une url (xaddr) de type http:{ip}:5357/dpws/<uri_du_device>.
    En oscultant le trafic avec wireshark, je vois bien que je fais une requête au réseau 239.255.255.250 (broadcast) , et que c’est le module qui me répond directement à partir de {ip}

A ce stade là, je ne sais pas vraiment quoi en faire. Toutes mes tentatives de requête sur cette url coincent.

  • en get sur port 80 : erreur 404 (ressource non trouvé)
  • en get sur port 443 : erreur 401 (unauthorized)
  • en post sur 5357 avec une envelope soap xml : retour d’erreur dans une autre envelope soap : action not supported (ne m’en demandez par plus, le soap c’est vraiment pas mon truc)

J’ai justement essayé de faire du soap, mais impossible de trouvé le fichier wsdl qui est censé documenté le service. A ce stade, je suis à peu prêt sur que soap n’est utilisé que pour la découverte de device, pas pour les échanges de données (c’est vraiment barbare le soap).

De l’autre côté, je me suis inspiré de la démarche de didjee sur l’ancien forum : télécharger l’apk de esetup, le décompiler (sur decompiler.com) et parcourir les sources de l’application en java.

Je ne suis pas un expert en java, mais à force de regarder et de faire des grep dans tout les sens, tout en regardant le fonctionnement des applications esetup et energywiser (que j’arrive à faire marcher sans problème par ailleurs et qui me remonte bien mes conso), je commence à y voir un peu plus claire.

Dans esetup, il faut que l’on soit sur le même réseau que le module et que le bluetooth soit activé. pour faire l’appariement, il faut appairer manuellement en appuyant sur un bouton en façade du module.

Donc mon hypothèse ;

  • l’app détecte le module avec soap sur udp et trouve une ip, un uri avec l’adresse mac du module et le type du device. a ce stade, il s’agit peut être juste de savoir qu’il existe un module. l’url et l’ip ne sont peut être même pas utilisée.
  • l’app demande l’appairage via bluetooth avec validation manuelle (pas encore isolé cette partie dans le code)
  • l’app se connecte en utilisant le mot de passe par défaut de l’application (admin: admin)
  • La suite se passe bien via des requête REST classique (ouf).

Les url pour faire les remontées de données ont l’air d’être les mêmes que celles des modules php de jeedom (/vesta/UsageMeter). A ce stade je penche sur un problème liée à l’appairage ou au login.

Il est possible qu’un renforcement de sécurité ai cassé le fonctionnement qui marchait avec le module EER31600…

Je continue de creuser.

Je crois que j’avance.

Dans le fichier DetectedDevicesListActivity.java, voila la fonction qui s’occupe du login:

 private void requestLogin(SchneiderWLSLDevice schneiderWLSLDevice) {
        ScotDialogFragment instanceProgress = ScotDialogFragment.instanceProgress(true);
        this.dialog = instanceProgress;
        instanceProgress.show(getSupportFragmentManager(), (String) null);
        String replaceAll = schneiderWLSLDevice.getMacAddress().toUpperCase().replaceAll(":", "");
        if (schneiderWLSLDevice.getDeviceType().isSmartlinkB()) {
            doLoginSmartlink(schneiderWLSLDevice, replaceAll, true);
        } else if (schneiderWLSLDevice.getDeviceType().isWiserV1()) {
            doLoginWiserlink(schneiderWLSLDevice, new LoginReq("admin", "admin"));
        } else if (schneiderWLSLDevice.getDeviceType().isAramis()) {
            fetchAramisGatewayKey(schneiderWLSLDevice, replaceAll);
        }
    }

En cherchant la définition de isWiserV1 :

    public boolean isAramis() {
        return this == MIP_V2 || this == SL_EL_D || this == ELKO;
    }

    public boolean isWiserV1() {
        return this == MIP_V1;
    }

    public boolean isWiserV2() {
        return this == MIP_V2 || this == ELKO;
    }

Et en pistant les messages affichés par l’application, le modèle 31800 est référé en tant que aramis_wiserlink.

Je pense qu’Aramis est le petit nom du module bluetooth qui s’occupe de l’appariement. Dans ce cas de figure, on ne passe plus par login :admin et mdp: admin (peut êtte pas plus mal…).

A la place on passe par fetchAramisGatewayKeys.

ça ressemble à une méthode pour acquérir une clef d’api via le bouton d’association bt …

[edit ]
Bon en fait, le clef d’api est généré par un algorithme déterministe à partir de l’adresse mac du module.

J’essai de convertir le java en python et voir si j’arrive à forcer la serrure de mon module :slight_smile:

[edit 2]

Bon, j’arrive à générer un clef qui a l’air valide. j’arrive à récupérer les information du module en REST avec.

Sans la clef :

GET https://192.168.xxx.xxx/rsa1/ProductDetails

401 unauthorized (‹ WWW-Authenticate ›: ‹ Basic realm="" ›)

Avec la clef : (m2madmin : key )

GET https://192.168.xxx.xxx/rsa1/ProductDetails -> 401 unauthorize

'{ "fw": "V1.7.5", "coS": true, "name": "myWiserEnergy-B2A1", "sn": "RN-2020-W17-1-0045", "pc": "EER31800", "cSsem": true }'

[Edit 3] Victory !

Avec la clef :

GET /rsa1/MeterInstantData

'{ "MeterInstantData": [ { "currentA": 0, "currentB": 0, "currentC": 0, "voltageAB": 0, "voltageBC": 0, "voltageCA": 0, "voltageAN": 0, "voltageBN": 0, "voltageCN": 0, "powerA": 0, "powerB": 0, "powerC": 0, "powerTActive": 0, "powerTReactive": 0, "powerTApparent": 0, "powerFactorT": 0, "frequency": 0, "slaveId": 205, "channel": 5 }, { "currentA": 0, "currentB": 0, "currentC": 0, "voltageAB": 0, "voltageBC": 0, "voltageCA": 0, "voltageAN": 0, "voltageBN": 0, "voltageCN": 0, "powerA": 0, "powerB": 0, "powerC": 0, "powerTActive": 0, "powerTReactive": 0, "powerTApparent": 0, "powerFactorT": 0, "frequency": 0, "slaveId": 205, "channel": 0 }, { "currentA": 0, "currentB": 0, "currentC": 0, "voltageAB": 0, "voltageBC": 0, "voltageCA": 0, "voltageAN": 0, "voltageBN": 0, "voltageCN": 0, "powerA": 0, "powerB": 0, "powerC": 0, "powerTActive": 0, "powerTReactive": 0, "powerTApparent": 0, "powerFactorT": 0, "frequency": 0, "slaveId": 205, "channel": 1 }, { "currentA": 0, "currentB": 0, "currentC": 0, "voltageAB": 0, "voltageBC": 0, "voltageCA": 0, "voltageAN": 0, "voltageBN": 0, "voltageCN": 0, "powerA": 0, "powerB": 0, "powerC": 0, "powerTActive": 0, "powerTReactive": 0, "powerTApparent": 0, "powerFactorT": 0, "frequency": 0, "slaveId": 205, "channel": 2 }, { "currentA": 0, "currentB": 0, "currentC": 0, "voltageAB": 0, "voltageBC": 0, "voltageCA": 0, "voltageAN": 0, "voltageBN": 0, "voltageCN": 0, "powerA": 0, "powerB": 0, "powerC": 0, "powerTActive": 0, "powerTReactive": 0, "powerTApparent": 0, "powerFactorT": 0, "frequency": 0, "slaveId": 205, "channel": 3 }, { "currentA": 0, "currentB": 0, "currentC": 0, "voltageAB": 0, "voltageBC": 0, "voltageCA": 0, "voltageAN": 0, "voltageBN": 0, "voltageCN": 0, "powerA": 0, "powerB": 0, "powerC": 0, "powerTActive": 0, "powerTReactive": 0, "powerTApparent": 0, "powerFactorT": 0, "frequency": 0, "slaveId": 205, "channel": 4 }, { "currentA": 0, "currentB": 0, "currentC": 0, "voltageAB": 0, "voltageBC": 0, "voltageCA": 0, "voltageAN": 0, "voltageBN": 0, "voltageCN": 0, "powerA": 0, "powerB": 0, "powerC": 0, "powerTActive": 0, "powerTReactive": 0, "powerTApparent": 0, "powerFactorT": 0, "frequency": 0, "slaveId": 201, "channel": 0 }, { "currentA": 0, "currentB": 0, "currentC": 0, "voltageAB": 0, "voltageBC": 0, "voltageCA": 0, "voltageAN": 0, "voltageBN": 0, "voltageCN": 0, "powerA": 0, "powerB": 0, "powerC": 0, "powerTActive": 134, "powerTReactive": 0, "powerTApparent": 0, "powerFactorT": 0, "frequency": 0, "slaveId": 201, "channel": 1 }, { "currentA": 0, "currentB": 0, "currentC": 0, "voltageAB": 0, "voltageBC": 0, "voltageCA": 0, "voltageAN": 0, "voltageBN": 0, "voltageCN": 0, "powerA": 0, "powerB": 0, "powerC": 0, "powerTActive": 0, "powerTReactive": 0, "powerTApparent": 0, "powerFactorT": 0, "frequency": 0, "slaveId": 201, "channel": 2 }, { "currentA": 0, "currentB": 0, "currentC": 0, "voltageAB": 0, "voltageBC": 0, "voltageCA": 0, "voltageAN": 0, "voltageBN": 0, "voltageCN": 0, "powerA": 0, "powerB": 0, "powerC": 0, "powerTActive": 158, "powerTReactive": 0, "powerTApparent": 0, "powerFactorT": 0, "frequency": 0, "slaveId": 201, "channel": 3 }, { "currentA": 0, "currentB": 0, "currentC": 0, "voltageAB": 0, "voltageBC": 0, "voltageCA": 0, "voltageAN": 0, "voltageBN": 0, "voltageCN": 0, "powerA": 0, "powerB": 0, "powerC": 0, "powerTActive": 201, "powerTReactive": 0, "powerTApparent": 0, "powerFactorT": 0, "frequency": 0, "slaveId": 201, "channel": 4 } ] }'

Et bien tu parles chinois pour moi :joy:
Si tu as besoin que je teste quelque chose, n’hésites pas.
As-tu regardé comment fonctionne le plugin qui marche pour le 31600? ça te donneras peut-être des idées car schneider a du conserver le fonctionnement de l’ancien module dans les grandes lignes (ou peut-être pas :frowning: )