AppDaemon et mise à jour d'un sensor dans le passé

Bonjour,

j’ai fait un script Selenium / AppDaemon pour récupérer ma consommation d’eau. Ça marche. Le souci, c’est que j’ai ma conso à n-1. J’aurais donc besoin de définir le jour n vers 10H ma conso de n-1 à 23H59. Le souci, c’est que j’arrive à définir l’état, mais pas la date en fonction de l’état. Une idée ?

Question intéressante :
Perso j’ai fait une récupération complète des consommations depuis le site Iléo.
Ça fonctionne bien mais pour l’instant comme je récupère aussi à J-2 dans le meilleur des cas, ça remplit avec un gros négatif sur les jours suivants
Je suis prêt à partager ma config
Philippe

Mais tu as fait comment pour persisterl es valeurs ?

En passant par cette intégration, vous pouvez ajouter des données sur des jours passés.

Bonjour
Pas sûr que cette solution soit meilleure car il y a un ticket ouvert qui cite le même problème de génération de valeur négative
En fait on écrit bien une valeur SUM datée dans la base mais home assistant recalcule une valeur négative après avoir lui même ajouté une valeur dans STATE dans la base.
Je ne trouve pas de solution.
J’ai tenté de faire un automatisme qui écrit le dernier index toutes les 10 minutes à la date d’aujourd’hui . Ça bloque bien le recalcul
Mais quand on récupère l’index de la veille, c’est comme un index plus petit que celui de la journée et ça redonne des négatifs !

Je ne rencontre pas ce soucis de mon côté pour tout ce que j’importe de cette façon.

Je dois l’utiliser sans doute dans un autre contexte.

Peur être que ta dernière valeur importée est à la date du jour .
Dans mon cas, j’importe des valeurs qui sont au minimum anciennes de 2 jours

J’ai spook, je peux essayer avec du coup.

Oui ça fonctionne mais il n’y a rien à faire : toute introduction de valeur dans le passé provoque un recalcul récent et genere une valeur négative

J’ai bien avancé… Pour info je suis à la régie des eaux de Montpellier 3M

J’ai commencé par créer des entités pour

  • le login,
  • le mot de passe (avec des données issue de secrets.yaml),
  • la valeur d’hier,

Puis un script pour faciliter l’import (via spook) depuis AppDaemon :

input_number:
  watermeter_yesterday_index:
    name: Watermeter yesterday index
    icon: mdi:water
    unit_of_measurement: "L"
    min: 0
    max: 100000
    step: 1

input_text:
  secret_rdem3m_login:
    name: Secret rdem3m login
    icon: mdi:lock
  secret_rdem3m_password:
    name: Secret rdem3m password
    icon: mdi:lock
    mode: password
    initial: !secret regie_des_eaux_mtp_3m

template:
  - sensor:
      - name: "Watermeter index imported"
        unique_id: watermeter_index_imported
        icon: mdi:water
        unit_of_measurement: "L"
        device_class: water
        state_class: total_increasing
        state: "{{ states('input_number.watermeter_yesterday_index') | int }}"

script:
  import_water_meter_index:
    alias: "Import index d'eau à partir de Spook"
    mode: queued
    fields:
      start:
        description: "Horodatage ISO 8601"
        example: "2025-07-21T23:00:00+02:00"
      state:
        description: "Valeur de l’index en litres"
        example: "51161"
    sequence:
      action: recorder.import_statistics
      data:
        has_mean: false
        has_sum: true
        statistic_id: sensor.watermeter_index_imported
        source: recorder
        unit_of_measurement: L
        stats:
          - start: "{{ start }}"
            state: "{{ state }}"

Et de là j’y vais avec un script appdaemon :

import hassapi as hass
import datetime
import logging
import time
import locale
from appdaemon.entity import Entity
from zoneinfo import ZoneInfo
from selenium import webdriver
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By

# Configuration
RDEM3M_URL = "https://ael.regiedeseaux3m.fr"

class GetConso(hass.Hass):
    """
    Class get water consumption from the Regie des Eaux de Montpellier 3M portal.
    """
    index: int
    start: datetime.datetime
    driver: WebDriver
    watermeter_yesterday_index: Entity
    login: str
    password: str

    def initialize(self):
        """
        AppDaemon initializer.
        """
        self.log(">>>>> Initialize")

        time = datetime.time(11, 30, 0)
        self.run_daily(self.run_daily_callback, time)

    def run_daily_callback(self, kwargs):
        """
        AppDaemon dayly task.
        """
        self.log(">>>>> Daily run")
        self.watermeter_yesterday_index = self.get_entity("input_number.watermeter_yesterday_index")
        self.login = self.get_entity("input_text.secret_rdem3m_login").get_state()
        self.password = self.get_entity("input_text.secret_rdem3m_password").get_state()

        self.get_info_from_chrom()
        self.import_data()

    def get_info_from_chrom(self):
        """
        Call driver and get data.
        """
        self.launch_driver()

        try:
            # Login form
            self.log(">>>>> Chrome: Login with " + str(self.login))
            self.driver.get(RDEM3M_URL + "/#/login")
            time.sleep(5)
            loginFormLogin = self.driver.find_element(By.NAME, "txtUser")
            loginFormLogin.send_keys(self.login)
            loginFormPassword = self.driver.find_element(By.NAME, "txtPassword")
            loginFormPassword.send_keys(self.password)
            loginFormButton = self.driver.find_element(By.CSS_SELECTOR, ".text-right:nth-child(1) > .btn")
            loginFormButton.click()
            time.sleep(5)

            # Conso
            self.log(">>>>> Chrome: Go to Conso")
            self.driver.get(RDEM3M_URL + "/#/conso")
            time.sleep(5)
            configDashboard = self.driver.find_element(By.PARTIAL_LINK_TEXT, "Afficher les données")
            configDashboard.click()
            time.sleep(2)

            # consoConso = self.driver.find_element(By.CSS_SELECTOR, "#tabHistoriqueReleve > .ng-scope:nth-child(1) > .text-center:nth-child(2)")
            # self.log(">>>>> Consommation " + consoConso.text)

            consoDate = self.driver.find_element(By.CSS_SELECTOR, "#tabHistoriqueReleve > .ng-scope:nth-child(1) > .text-center:nth-child(1)")
            self.start = GetConso.string_to_iso(consoDate.text)
            self.log(">>>>> Chrome: Date de releve " + consoDate.text + " (" + str(self.start) + ")")

            consoIndex = self.driver.find_element(By.CSS_SELECTOR, "#tabHistoriqueReleve > .ng-scope:nth-child(1) > .text-center:nth-child(2)")
            self.index = GetConso.string_to_index(consoIndex.text)
            self.log(">>>>> Chrome: Index " + consoIndex.text + " (" + str(self.index) + ")")
        finally:
            self.close_driver()

    def launch_driver(self):
        """
        Init chrome webdriver.
        """
        self.log(">>>>> Init Webdriver")
        service = Service(executable_path=r'/usr/bin/chromedriver')
        options = webdriver.ChromeOptions()
        options.add_argument('--headless')
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-dev-shm-usage')
        self.driver = webdriver.Chrome(service=service, options=options)

    def close_driver(self):
        """
        Call driver and get data.
        """
        self.driver.quit()
        self.log(">>>>> Webdriver closed")

    def import_data(self):
        """
        Import data from a script.
        """
        self.log(">>>> Import index " + str(self.index) + " at " + self.start.isoformat())

        self.watermeter_yesterday_index.set_state(int(self.index))
        self.call_service("script/turn_on", entity_id="script.import_water_meter_index", variables={
            "start": self.start,
            "state": self.index
        })

    @staticmethod
    def string_to_index(str_index: str) -> int:
        """
        Convert index formated in integer.
        """
        return int(str_index.replace(" ", ""))

    @staticmethod
    def string_to_iso(str_date: str) -> datetime.datetime:
        """
        Convert string date to ISO.
        """
        month_fr = {
            "janvier": 1, "février": 2, "mars": 3,
            "avril": 4, "mai": 5, "juin": 6,
            "juillet": 7, "août": 8, "septembre": 9,
            "octobre": 10, "novembre": 11, "décembre": 12
        }

        day, month, year = str_date.split()
        month = month_fr[month.lower()]
        
        return datetime.datetime(int(year), month, int(day), 23, 0, 0, tzinfo=ZoneInfo("Europe/Paris"))

C’est plutôt propre, je n’ai pas des configs de partout et des secrets qui se baladent.

Maintenant je me pose des questions :

  • est-ce que mon sensor watermeter_index_imported est bien configuré ? C’est mon index, donc ma consommation qui s’incrémente ?
  • est-ce que mon import est bien configuré ? Avec un state affecté mais un has_sum à true ?

Car le but est de donner ça au dashboard énergie. J’ai fait un import des dernier jours aussi :

action: recorder.import_statistics
data:
  has_mean: false
  has_sum: true
  statistic_id: sensor.watermeter_index_imported
  source: recorder
  unit_of_measurement: L
  stats:
    - start: "2025-07-20 23:00:00+02:00"
      state: 50693
    - start: "2025-07-19 23:00:00+02:00"
      state: 50295
    - start: "2025-07-18 23:00:00+02:00"
      state: 49800
    - start: "2025-07-16 23:00:00+02:00"
      state: 48949
    - start: "2025-07-15 23:00:00+02:00"
      state: 48577
...

Je crois que j’ai réussi…

Tout d’abord l’import qui maintenant prend en compte à la fois l’état (consommation quotidienne en L) et le total (index en L) :

action: recorder.import_statistics
data:
  has_mean: false
  has_sum: true
  statistic_id: sensor.watermeter_imported_conso
  source: recorder
  unit_of_measurement: L
  stats:
    - start: "2025-07-22T23:00:00+02:00"
      state: 84
      sum: 51245
    - start: "2025-07-21T23:00:00+02:00"
      state: 468
      sum: 51161
    - start: "2025-07-20T23:00:00+02:00"
      state: 398
      sum: 50693
    - start: "2025-07-19T23:00:00+02:00"
      state: 495
      sum: 50295
...

Du coup mon package Home Assistant avec entitées et script:

input_number:
  watermeter_imported_yesterday_index:
    name: Watermeter imported yesterday index
    icon: mdi:water
    unit_of_measurement: "L"
    min: 0
    max: 100000
    step: 1
  watermeter_imported_yesterday_conso:
    name: Watermeter imported yesterday conso
    icon: mdi:water
    unit_of_measurement: "L"
    min: 0
    max: 100000
    step: 1

input_text:
  secret_rdem3m_login:
    name: Secret rdem3m login
    icon: mdi:lock
  secret_rdem3m_password:
    name: Secret rdem3m password
    icon: mdi:lock
    mode: password
    initial: !secret regie_des_eaux_mtp_3m

template:
  - sensor:
      - name: "Watermeter imported conso"
        unique_id: watermeter_imported_conso
        icon: mdi:water
        unit_of_measurement: "L"
        device_class: water
        state_class: total_increasing
        state: "{{ states('input_number.watermeter_imported_yesterday_conso') | int }}"

script:
  watermeter_imported_yesterday_conso:
    alias: "Import de la conso d'eau à partir de Spook"
    mode: queued
    fields:
      start:
        description: "Horodatage ISO 8601"
        example: "2025-07-21T23:00:00+02:00"
      state:
        description: "Valeur de la conso en litres"
        example: "471"
      sum:
        description: "Valeur de l'index en litres"
        example: "51161"
    sequence:
      action: recorder.import_statistics
      data:
        has_mean: false
        has_sum: false
        statistic_id: sensor.watermeter_imported_conso
        source: recorder
        unit_of_measurement: L
        stats:
          - start: "{{ start }}"
            state: "{{ state }}"
            sum: "{{ sum }}"

Et mon AppDaemon :

import hassapi as hass
import datetime
import logging
import time
import locale
from appdaemon.entity import Entity
from zoneinfo import ZoneInfo
from selenium import webdriver
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By

class GetConso(hass.Hass):
    """
    Class get water consumption from the Regie des Eaux de Montpellier 3M portal.
    """
    index: int
    conso: int
    start: datetime.datetime
    driver: WebDriver
    watermeter_yesterday_conso: Entity
    watermeter_yesterday_index: Entity
    url: str = "https://ael.regiedeseaux3m.fr"
    login: str
    password: str

    def initialize(self):
        """
        AppDaemon initializer.
        """
        self.log(">>>>> Initialize")

        time = datetime.time(11, 30, 0)
        self.run_daily(self.run_daily_callback, time)
        #self.run_daily_callback(time)

    def run_daily_callback(self, kwargs):
        """
        AppDaemon dayly task.
        """
        self.log(">>>>> Daily run")
        self.watermeter_yesterday_conso = self.get_entity("input_number.watermeter_imported_yesterday_conso")
        self.watermeter_yesterday_index = self.get_entity("input_number.watermeter_imported_yesterday_index")
        self.login = self.get_entity("input_text.secret_rdem3m_login").get_state()
        self.password = self.get_entity("input_text.secret_rdem3m_password").get_state()

        self.get_info_from_chrome()
        self.import_data()

    def get_info_from_chrome(self):
        """
        Call driver and get data.
        """
        self.launch_driver()

        try:
            # Login form
            self.log(">>>>> Chrome: Login with " + str(self.login))
            self.driver.get(self.url + "/#/login")
            time.sleep(5)
            loginFormLogin = self.driver.find_element(By.NAME, "txtUser")
            loginFormLogin.send_keys(self.login)
            loginFormPassword = self.driver.find_element(By.NAME, "txtPassword")
            loginFormPassword.send_keys(self.password)
            loginFormButton = self.driver.find_element(By.CSS_SELECTOR, ".text-right:nth-child(1) > .btn")
            loginFormButton.click()
            time.sleep(5)

            # Conso
            self.log(">>>>> Chrome: Go to Conso")
            self.driver.get(self.url + "/#/conso")
            time.sleep(5)
            configDashboard = self.driver.find_element(By.PARTIAL_LINK_TEXT, "Afficher les données")
            configDashboard.click()
            time.sleep(2)

            consoConso = self.driver.find_element(By.CSS_SELECTOR, "#tabHistoriqueReleve > .ng-scope:nth-child(1) > .text-center:nth-child(3)")
            self.conso = GetConso.string_m3_to_conso_liter(consoConso.text)
            self.log(">>>>> Consommation " + consoConso.text + " (" + str(self.conso) + ")")

            consoDate = self.driver.find_element(By.CSS_SELECTOR, "#tabHistoriqueReleve > .ng-scope:nth-child(1) > .text-center:nth-child(1)")
            self.start = GetConso.string_to_iso(consoDate.text)
            self.log(">>>>> Chrome: Date de releve " + consoDate.text + " (" + str(self.start) + ")")

            consoIndex = self.driver.find_element(By.CSS_SELECTOR, "#tabHistoriqueReleve > .ng-scope:nth-child(1) > .text-center:nth-child(2)")
            self.index = GetConso.string_m3_to_index_liter(consoIndex.text)
            self.log(">>>>> Chrome: Index " + consoIndex.text + " (" + str(self.index) + ")")
        finally:
            self.close_driver()

    def launch_driver(self):
        """
        Init chrome webdriver.
        """
        self.log(">>>>> Init Webdriver")
        service = Service(executable_path=r'/usr/bin/chromedriver')
        options = webdriver.ChromeOptions()
        options.add_argument('--headless')
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-dev-shm-usage')
        self.driver = webdriver.Chrome(service=service, options=options)

    def close_driver(self):
        """
        Call driver and get data.
        """
        self.driver.quit()
        self.log(">>>>> Webdriver closed")

    def import_data(self):
        """
        Import data from a script.
        """
        self.log(">>>> Import data " + str(self.conso) + " / " + str(self.index) + " at " + self.start.isoformat())

        self.watermeter_yesterday_conso.set_state(int(self.conso))
        self.watermeter_yesterday_index.set_state(int(self.index))

        self.call_service("script/turn_on", entity_id="script.watermeter_imported_yesterday_conso",
                                            variables={
                                                "start": self.start,
                                                "state": self.conso,
                                                "sum": self.index,
                                            })

    @staticmethod
    def string_m3_to_index_liter(str_index: str) -> int:
        """
        Convert index formated in integer.
        """
        return int(str_index.replace(" ", ""))*1000

    @staticmethod
    def string_m3_to_conso_liter(str_conso: str) -> int:
        """
        Convert conso formated in int.
        """
        return int(float(str_conso.replace(" ", ""))*1000)

    @staticmethod
    def string_to_iso(str_date: str) -> datetime.datetime:
        """
        Convert string date to ISO.
        """
        month_fr = {
            "janvier": 1, "février": 2, "mars": 3,
            "avril": 4, "mai": 5, "juin": 6,
            "juillet": 7, "août": 8, "septembre": 9,
            "octobre": 10, "novembre": 11, "décembre": 12
        }

        day, month, year = str_date.split()
        month = month_fr[month.lower()]
        
        return datetime.datetime(int(year), month, int(day), 23, 0, 0, tzinfo=ZoneInfo("Europe/Paris"))

A tester sur la durée, je ferais surement un article complet, mais si ça marche, j’ai bien ma consommation issue de la Régie des Eaux de Montpellier 3M… Reste peut-être à faire une automation qui lance et coupe AppDaemon plutôt que de le faire tourner 24/7.

Bon, pour J-1 je suis bon :

Par contre pour le jour « J »… Je crois que le sum est pas la bonne solution…

action: recorder.import_statistics
data:
  has_mean: false
  has_sum: true
  statistic_id: sensor.watermeter_imported_conso
  source: recorder
  unit_of_measurement: L
  stats:
    - start: "2025-07-22T23:00:00+02:00" => date de la relève à 23H00:00
      state: 84 => consomation du jour en L
      sum: 51245 => Index à ce jour en L

Ce sujet a été automatiquement fermé après 60 jours. Aucune réponse n’est permise dorénavant.