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
...