yukulehe / gazpar2mqtt

Python script to fetch GRDF's website data and publish data to a mqtt broker.
GNU General Public License v3.0
21 stars 9 forks source link

GRDF API breakout #22

Closed echauvet closed 2 years ago

echauvet commented 2 years ago

It seems that from this day, the calls to GRDF mon espace do not work anymore. I may help to solve this. It seems that we are talking about complete rewrite of gazpar.py.

yukulehe commented 2 years ago

Bonjour Eric. En effet, GRDF a changé le design du site. Le script gazpar.py doit être complètement revu. Je n'ai encore aucune idée de comment faire. Alors toute aide est la bienvenue et je te remercie pour ton message.

yukulehe commented 2 years ago

J'ai créé une branche 0.5 pour travailler sur ce redesign. Vous pourriez forker depuis cette branche.

echauvet commented 2 years ago

Pour le moment l'authentification fonctionne. Il semble que ce soit plus simple qu'avec l'ancienne version... A confirmer.

echauvet commented 2 years ago

Ca y est, je parviens à avoir la liste de mes PCE et mes conso jour par jour : Appel 1: session.get('https://monespace.grdf.fr/client/particulier/accueil') -> pour récupérer auth_nonce = session.cookies.get('auth_nonce') Appel 2: payload = { 'email': 'mail@mail.com', 'password': 'password', 'capp': 'meg', 'goto': 'https://sofa-connexion.grdf.fr:443/openam/oauth2/externeGrdf/authorize?response_type=code&scope=openid%20profile%20email%20infotravaux%20%2Fv1%2Faccreditation%20%2Fv1%2Faccreditations%20%2Fdigiconso%2Fv1%20%2Fdigiconso%2Fv1%2Fconsommations%20new_meg%20%2FDemande.read%20%2FDemande.write&client_id=prod_espaceclient&state=0&redirect_uri=https%3A%2F%2Fmonespace.grdf.fr%2F_codexch&nonce=' + auth_nonce + '&by_pass_okta=1&capp=meg' } req = session.post(LOGIN_BASE_URI, data=payload, allow_redirects=False) -> Pour s'authentifier, mais cela ne suffit pas, il faut également : Appel 3: req = session.get('https://sofa-connexion.grdf.fr:443/openam/oauth2/externeGrdf/authorize?response_type=code&scope=openid%20profile%20email%20infotravaux%20%2Fv1%2Faccreditation%20%2Fv1%2Faccreditations%20%2Fdigiconso%2Fv1%20%2Fdigiconso%2Fv1%2Fconsommations%20new_meg%20%2FDemande.read%20%2FDemande.write&client_id=prod_espaceclient&state=0&redirect_uri=https%3A%2F%2Fmonespace.grdf.fr%2F_codexch&nonce=' + auth_nonce + '&by_pass_okta=1&capp=meg') -> On récupère d'autres cookies Appel 4: req = session.get('https://monespace.grdf.fr/api/e-connexion/users/whoami') -> Toutes les données contractuelles en JSON Appel 5: req = session.get('https://monespace.grdf.fr/api/e-conso/pce') ->liste des pce Appel 6: req = session.get('https://monespace.grdf.fr/api/e-conso/pce/consommation/informatives?dateDebut=2018-11-27&dateFin=2021-11-27&pceList%5B%5D=NUMERO(S)_DE_PCE_DE_L_APPEL_PRECEDENT') -> Toutes les données entre ces dates jour par jour (3 ans max)

PoC: Voici le code modifié de login@gazpar.py pour démontrer la chaîne complète. Il suffit de remplacer MAIL, PASS et PCE pour obtenir de jolis JSON qui contiennent tout ce qu'il faut. J'ai l'impression que cela pourra être plus facile à maintenir car simple par rapport au client web qui télécharge les données. En tout cas, c'est beaucoup plus rapide :) Comment veux tu qu'on avance?

def login(username, password):

    """Logs the user into the Linky API.
    """
    global JAVAVXS
    session = requests.Session()

    session.headers = {
        'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Mobile Safari/537.36',
        'Accept-Encoding':'gzip, deflate, br',
        'Accept':'application/json, */*',
        'Connection': 'keep-alive'
    }

    #Get essential cookies
    req = session.get('https://monespace.grdf.fr/client/particulier/accueil')
    if not 'auth_nonce' in session.cookies:
        raise GazparLoginException("Cannot get auth_nonce.")
    auth_nonce = session.cookies.get('auth_nonce')
    logging.info("auth_nonce: " + auth_nonce)

    payload = {
        'email': '***MAIL***',
        'password': '***PASS***',
        'capp': 'meg',
        'goto': 'https://sofa-connexion.grdf.fr:443/openam/oauth2/externeGrdf/authorize?response_type=code&scope=openid%20profile%20email%20infotravaux%20%2Fv1%2Faccreditation%20%2Fv1%2Faccreditations%20%2Fdigiconso%2Fv1%20%2Fdigiconso%2Fv1%2Fconsommations%20new_meg%20%2FDemande.read%20%2FDemande.write&client_id=prod_espaceclient&state=0&redirect_uri=https%3A%2F%2Fmonespace.grdf.fr%2F_codexch&nonce=' + auth_nonce + '&by_pass_okta=1&capp=meg'
    }

    req = session.post('https://login.monespace.grdf.fr/sofit-account-api/api/v1/auth', data=payload, allow_redirects=False)
    if not 'XSRF-TOKEN' in session.cookies:
        raise GazparLoginException("Login unsuccessful. Check your credentials.")

    req = session.get('https://sofa-connexion.grdf.fr:443/openam/oauth2/externeGrdf/authorize?response_type=code&scope=openid%20profile%20email%20infotravaux%20%2Fv1%2Faccreditation%20%2Fv1%2Faccreditations%20%2Fdigiconso%2Fv1%20%2Fdigiconso%2Fv1%2Fconsommations%20new_meg%20%2FDemande.read%20%2FDemande.write&client_id=prod_espaceclient&state=0&redirect_uri=https%3A%2F%2Fmonespace.grdf.fr%2F_codexch&nonce=' + auth_nonce + '&by_pass_okta=1&capp=meg')

    req = session.get('https://monespace.grdf.fr/api/e-connexion/users/whoami')

    req = session.get('https://monespace.grdf.fr/api/e-conso/pce')
    logging.info("PCEs:")
    logging.info(req.text)

    req = session.get('https://monespace.grdf.fr/api/e-conso/pce/consommation/informatives?dateDebut=2018-11-27&dateFin=2021-11-27&pceList%5B%5D=**PCE***')
    logging.info("CONSO:")
    logging.info(req.text)

    return session
yukulehe commented 2 years ago

Bonjour Eric D'abord merci pour tout ton travail, c'est vraiment top ! Je vais essayer de me libérer de mes contraintes perso pour intégrer ton script en fin de soirée. J'aurais un peu plus de temps en début de semaine prochaine. Par contre, je m'interroge sur la pérennité des devs puisque le site GRDF est encore en train d'évoluer. Je crains qu'on se retrouve bloqué par l'authentification. Encore merci

echauvet commented 2 years ago

Je pense que le backend ne change pas souvent. Si le site évolue, cela n'a pas d'impact car on se fait que récupérer des cookies en première partie (le parcours obligatoire) avec de simple gets et 1 post avec les crédits. De mon point de vue, cela me paraît être une solution facilement très facile à faire évoluer comparé au pilotage de la WebView. Cela paraîtrait fou de revoir le backend d'ici peu... L'avantage est de n'avoir qu'à éventuellement corriger les URL des web services si ils venaient à évoluer... Je suis assez optimiste vu la profondeur de la refonte qui vient d'être faite. J'aime beaucoup la mise en base que tu as faite pour enedis qui pourrait totalement être pertinente pour éviter de recharger tout l'historique pour sortir des stats d'évolution. Si le jour ne contient pas de données de consommation plus de N fois, on ne tente plus la mise à jour... Donc a partir d'une date donnée au fur et à mesure. Pour les données manquantes, nous pouvons également extrapoler avec le taux moyen de conversion et les index. Tiens moi au courant du bon fonctionnement pour toi du PoC. Edit: Le goto peut être simplement 'goto': 'https://sofa-connexion.grdf.fr:443/openam/oauth2/externeGrdf/authorize' Je constate que l'appel 3 peut être omis et que l'appel 4 ne fonctionnera pas le premier coup, mais le deuxième coup... Si besoin de faire évoluer, je ne pense pas que cela me prendra du temps. Avec un peu plus d'investigation sur le site, je pourrais peut être simplifier un peu plus si je comprends la logique de récupération des cookies.

yukulehe commented 2 years ago

J'ai exécuté ton bout de code et je suis tombé sur un callback du captcha de google :

2021-11-27 20:38:47,958 auth_nonce: e851a3ab76ff56cd940e651e5f0ca242
2021-11-27 20:38:48,126 PCEs:
2021-11-27 20:38:48,126 <!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Mire de connexion</title>
  <base href="/mire/">
  <script type="text/javascript">
    var onloadCallback = function() {
      setTimeout(onloadCallback, 500)
    };
  </script>
  <meta name="viewport" content="width=device-height, initial-scale=1, maximum-scale=0.42">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <!--<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async></script>-->
</head>
<body>
  <app-root></app-root>
<script src="runtime.e7df24b5d39b2102df5c.js" defer></script><script src="polyfills.35a5ca1855eb057f016a.js" defer></script><script src="styles.bb6b60ca59ce3dec0b8a.js" defer></script><script src="main.ec88b36544257b014091.js" defer></script></body>
</html>

2021-11-27 20:38:48,179 CONSO:
2021-11-27 20:38:48,179 <!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Mire de connexion</title>
  <base href="/mire/">
  <script type="text/javascript">
    var onloadCallback = function() {
      setTimeout(onloadCallback, 500)
    };
  </script>
  <meta name="viewport" content="width=device-height, initial-scale=1, maximum-scale=0.42">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <!--<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async></script>-->
</head>
<body>
  <app-root></app-root>
<script src="runtime.e7df24b5d39b2102df5c.js" defer></script><script src="polyfills.35a5ca1855eb057f016a.js" defer></script><script src="styles.bb6b60ca59ce3dec0b8a.js" defer></script><script src="main.ec88b36544257b014091.js" defer></script></body>
</html>

Tu as eu le même souci ?

echauvet commented 2 years ago

L'authentification n'a pas fonctionné. Pour savoir où il faut regarder les req.text et le session.cookies à chaque étape pour voir ou cela ne marche pas. Tu peux directement me contacter @gmail.com pour échanger si les retours contiennent des données sensibles.

yukulehe commented 2 years ago

ok merci, je vais debug plus précisément

echauvet commented 2 years ago

ok merci, je vais debug plus précisément

Check que tes credential que tu as remplacé dans le bout de code fonctionnent sur mon espace de GrDF en faisant des copier coller. En faisant F12 dans un browser, onglet Network avec l'option préserve en haut, tu vas retrouver les appels des web services et comparer.

yukulehe commented 2 years ago

Erreur à la con de mon coté, ça fonctionne :)

echauvet commented 2 years ago

ok merci, je vais debug plus précisément

Check que tes credential que tu as remplacé dans le bout de code fonctionnent sur mon espace de GrDF en faisant des copier coller. En faisant F12 dans un browser, onglet Network avec l'option préserve en haut, tu vas retrouver les appels des web services et comparer.

Erreur à la con de mon coté, ça fonctionne :)

Great.

yukulehe commented 2 years ago

C'est super ce que tu as fait. Je m'occupe maintenant de la déco en traitant les valeurs json. Je te tiens au jus.

echauvet commented 2 years ago

C'est super ce que tu as fait. Je m'occupe maintenant de la déco en traitant les valeurs json. Je te tiens au jus.

La partie plaisir :) c'est a dire l'intégration. Bravo, je regarde ton code pour faire quelques PR avec mes tests. Il faudrait arriver au même niveau que le projet enedis2mqtt et sa carte Lovelace (Ce serait top)

yukulehe commented 2 years ago

Oui enedis est un beau projet, madmartignan y a passé beaucoup de temps. Ok avec toi pour la carte lovelace, ça serait top.

echauvet commented 2 years ago

Oui enedis est un beau projet, madmartignan y a passé beaucoup de temps. Ok avec toi pour la carte lovelace, ça serait top.

Ok je suis. On peut faire une simple évolution de la carte pour gérer le gaz a la place de l'électricité sans HC, ça reste des kW. A voir comment on s'organise ensemble car il y a un peu de travail.

yukulehe commented 2 years ago

Hello Eric.

Je m'y remets aujourd'hui. La priorité c'est de rétablir le service à minima. Penses-tu qu'il est possible de récupérer via l'APi d'autres infos, par exemples les données calculées au mois ou à la semaine ? Sinon, ça nous obligerait de les calculer nous-mêmes avec un risque d'avoir des écarts avec le site de Grdf... Bonne journée.

echauvet commented 2 years ago

J'ai regardé, c'est bien le frontend qui fait les calculs dans le tableau de bord. Sache que les relèves (publications au fournisseur) sont disponibles ici : https://monespace.grdf.fr/api/e-conso/pce/consommation/publiees?dateDebut=2018-11-26&dateFin=2021-11-29&pceList[]=XXXXXXXXXXXXXX Ce jour, je constate que je n'ai pas de relevé depuis le 24 à 0h00. Les données affichées dans "Ma consommation détaillée" ne provoque pas de reload des infos, simplement la récupération de la météo pour afficher des températures journalières moyennes.

Si nous intégrons la base de données par la suite, il faudra de toute façon les calculer non même car les statistiques d'évolution de consommation se font sur mois/semaine glissantes (par rapport mois/semaine précédents/mois de l'année précedente ou mois/semaine de l'année précédente) Je pense qu'en combinant les index et les kWh (pour obtenir les taux de conversion), nous pouvons combler les trous dans les données quand cela arrive.

Le code du frontend est minifié donc on ne peut pas réutiliser les algos, mais je ne suis pas inquiet - sur ta base de travail je m'ocupperais de les calculer. Je vais regarder sur un mois ou il manque des données (indexes à null) quelle valeur affiche le site.

yukulehe commented 2 years ago

Ok merci pour ces infos. De mon côté, je revois le code gazpar.py en implémentant des classes objets qui seront utiles pour implémenter facilement différentes fonctions (de calcul notamment).

echauvet commented 2 years ago

Oui, super si tu met au propre l'architecture. Je valide que le site ne compte pas les jours ou les données ne sont pas disponible. Par exemple, ce mois ci, si je compte avec mes index, j'ai dépensé 102m3 (jusqu'au 24). hors le site ne fait que cumuler les kWh des relevés et m'affiche "que" 985kWh. Hors avec le taux actuel de conversion de 11.15, cela donne pluôt 1137.3kWh. Donc le frontend fait n'importe quoi, sachant que les données kWh quotidiennes sont des arrondis au kWh près donc peu précises. De mon point de vue, ce sont les index en m3 qui sont justes. Le webservice des quantité publiées au fournisseur permet de calculer plus justement le taux de conversion sur une periode et de coller avec la facturation! Donc une petite table date_debut/date_fin/taux à mettre à jour regulièrement avec les dernières periodes publiées permet de connaitre le dernier taux de conversion pour estimer les vrais kWh qui seront facturés.

yukulehe commented 2 years ago

ok, je prends en compte ce point, merci. Par ailleurs, je vais devoir gérer le cas où quelqu'un possède plusieurs PCE associés à son compte GRDF. Est-ce que tu as un alias associé à ton PCE ? Et si oui, est-ce toi qui l'a modifié ou c'est une valeur par défaut (si oui laquelle) ? L'API renvoie l'alias, ça me parait pas mal d'utiliser l'alias plutot que le numéro du PCE dans le device name de Home assistant.

echauvet commented 2 years ago

Je suis pas sur de comprendre, mais dans la liste des Pce, il y a alias, idObject et pce qui contiennent la donnée. Je prendrais pce pour le nom et la jointure avec les relevés pour etre sur d'avoir une donnée qui ne bougera pas.

yukulehe commented 2 years ago

Sur la page d'accueil, tu peux nommer ton PCE : image

echauvet commented 2 years ago

Oui, alias est bien mis à jour. Tu peux l'utiliser mais attention, il faudrait pas que les topics changent pour autant si on l'édite... C'est le pce l'id unique. Peut etre le mettre dans les données retournée par le topic donc le sensor.

yukulehe commented 2 years ago

Je suis d'accord que c'est risqué au cas où il change, mais avoir un device id avec un numéro de PCE, je trouve ça tellement moche....

echauvet commented 2 years ago

Je suis d'accord que c'est risqué au cas où il change, mais avoir un device id avec un numéro de PCE, je trouve ça tellement moche....

Oui, après on a besoin d'un id fixe pour le recorder... S'il change on perd la continuité des données dans home assistant. Mieux vaut utiliser la propriété friendly_name au niveau du sensor declaré dans l'auto discovery.

yukulehe commented 2 years ago

Ok , je mettrais le friendly name ! Bon je viens de déployer la version 0.5-dev dans Docker. Pour l'instant je n'ai fait que rebrancher les câbles (en plus de boucler sur les PCEs). Ca fonctionne si on fait pas trop de connerie dans les paramètres :)

yukulehe commented 2 years ago

Evidemment, pour l'instant, j'ai désactiver les valeurs mensuelles. De toute façon, il y aura un gros rework à prévoir la dessus. J'enrichirai demain les sensors avec les nouvelles infos remontées par ton api.

echauvet commented 2 years ago

Ok, je te laisse avancer pour que le socle soit le plus intégrable et propre possible. Je reviendrais sur le code pour des statistiques avancées et la mise en base. Peux tu gérer aussi la récupération de: https://monespace.grdf.fr/api/e-conso/pce/consommation/publiees?dateDebut=2018-11-26&dateFin=2021-11-29&pceList[]=XXXXXXXXXXXXXX Afin de nourrir la future table des conversion m3->kWh. Pour info, j'ai eu la chance de discuter avec un spécialiste aujourd'hui dans l'entretient de chaudière et les taux de conversion vont changer radicallement à partir de 2025, puis 2029 à cause de l'origine du gaz. Cela veut dire que l'indice de conversion va devenir une variable importante prochainement. Sur 2021, il a varié de 10.8 à 11.4 environ. La récupération des relèves est notre seul moyen d'avoir les taux de conversion à priori vu les données que j'ai pu voir dans les XHR de GRDF. En gros, pour les ceux qui n'ont pas une chaudière compatible "biogaz", c'est à dire sur pauvre pouvoir calorifique, cela va demander un réglage de la chaudière chaque année. J'ai un modèle Vaillant (ca marche pour Saunier-Duval sur la dernière génération 2021) qui s'adapte avec sonde automatiquement. On pourra donc dans la carte indiquer que la chaudière doit être reglée selon le modèle et le facteur de conversion. Pour info, un bruleur mal réglé, c'est souvent une cause d'encrassement ou de présence de CO sur mélange pauvre.

yukulehe commented 2 years ago

Ca me va très bien si tu t'occupes de la base de données et des statistiques avancées. Tu as l'air bien plus calé que moi en terme de conso gaz. Je suis en train de peaufiner le core model dans le script gazpar.py afin de pouvoir manipuler les objets quelque soit la source des données (GRDF ou de la base de données). Je vais aussi redesign la partie HA et rajouter de nouvelles infos. Par contre, les relevés restent bloqués au 23 côté GRDF. Ca serait bien qu'ils terminent leur mise en prod.

echauvet commented 2 years ago

Ca serait bien qu'ils terminent leur mise en prod.

OUI!, Après on peut faire exprès de tronquer dans le code par configuration pour faire des tests U

echauvet commented 2 years ago

Bravo, Ça marche bien au premier coup, après souvent des échec au whoami ou a la connexion... Je pense qu'on peut ajouter le dernier index connu et ajouter state class pour indiquer que c'est un cumul pour pouvoir le sélectionner dans énergie :)

yukulehe commented 2 years ago

Yep je suis en train de refondre le script hass. J'ajouterai les index comme tu le proposes. J'ajouterai également les 7 derniers relevés + des infos liés au compte. Plus d'autres infos si t'as des idées.

yukulehe commented 2 years ago

Par ailleurs, je me demandais si on pouvait enrichir l'appli en calculant les DJU à partir des infos de localisation du PCE et quelques inputs utilisateurs.

yukulehe commented 2 years ago

@echauvet Salut Eric, t'es toujours chaud pour mettre en place des calculs "avancés" sur la conso de gaz ? Je te propose de mettre à disposition des modules python dédiés à ces calculs et à la gestion de la BD. L'objectif est que ça reste le plus modulaire possible. On peut en rediscuter.

echauvet commented 2 years ago

Oui parfaitement, tout est clair pour la partie algorithme, et avec un socle propre avec les modules nécessaires ça serait top.

yukulehe commented 2 years ago

Cool. Je finis de préparer le socle. Ca sera un premier jet, je pourrais le faire évoluer en fonction de tes besoins. Je te tiens au jus.

echauvet commented 2 years ago

Cool. Je finis de préparer le socle. Ca sera un premier jet, je pourrais le faire évoluer en fonction de tes besoins. Je te tiens au jus.

Noice. Ça aide bien car le python et ses use cases ce n'est pas tous les jours... Si on parle archi, kernel, dpdk, buildroot, smid et même database c'est autre chose. Par chance je vais en faire en ce moment pour corriger des tests auto!

yukulehe commented 2 years ago

Si tu m'indiques comment calculer certains indicateurs "avancés", je peux m'occuper de leur programmation en python.

echauvet commented 2 years ago

Comme je compte mettre en base de donnée, les requêtes vont faire le gros du travail.

echauvet commented 2 years ago

Peux t'on se contacter de manière privée? Tel/Signal?

yukulehe commented 2 years ago

ok je t'ai envoyé un mail

yukulehe commented 2 years ago

bizarre, j'ai repris le mail qui était dans ta fiche de profil