Améliorer ma gestion des données

Bonjour à toutes et à tous,

Mon problème

Depuis le début de mon installation je garde mes données uniquement sur HA (j’ai mis en place le fait de ne conserver que les entités listées).
Toutefois, je suis un peu limité dans ce que je peux en faire, et c’est bloqué dans HA.

Je vais prendre en exemple, celui qui m’intéresse en premier.
J’ai les données de ma consommation électrique qui remontent via Zlinky. Et ça me permet de suivre mois par mois et années par années via la page Energie.
Toutefois, je ne peux pas rajouter les consommations des années précédentes. Et surtout, je ne maitrise pas comment HA fait du sampling des mois et années précédentes.

Ma question

Je voudrais vos conseils sur ce qu’il faudrait que je mette en place pour pouvoir mieux gérer mes données.

Hello,

Il n’y a pas vraiment de solution directement dans HA, surtout pour ajouter de l’historique qui vient d’ailleurs.

De mon côté j’ai un serveur MariaDB sur lequel j’ai mis la base de HA, et qui contient d’autre bases de données, dont une pour l’historique que je veux garder, une simple table avec 1 entrée par jour avec tout ce que je veux sauvegarder, principalement lié à la conso & production d’énergie.
J’y ai collé mon historique d’avant HA, sur Domoticz. Et depuis HA, j’ai des requêtes planifiées qui vont taper dans la base de HA.
C’était un peu chiant quand HA a changé sa base sur plusieurs releases y’a 1 ou 2 ans, mais depuis c’est stable.

En revanche effectivement pour les statistiques à long-terme il faut essayer de comprendre comment ça marche, je n’ai jamais essayé, donc je ne sais pas.

Sinon il reste la solution influxdb

Bon,

Je suis en train de tester pour passer mon historique long terme sur influxDB.
Pour commencer, j’ai extrait mes données de HA via des requêtes de cette forme :

SELECT
    sm.statistic_id,
	sm.unit_of_measurement AS Unit,
	s.start_ts          AS start_ts,
    s.state				As state
FROM
    statistics AS s
JOIN
    statistics_meta AS sm
      ON s.metadata_id = sm.id
WHERE
    sm.statistic_id = 'sensor.zlinky_tic_easf01'
ORDER BY
    s.start_ts ASC;

Via l’interface du module complémentaire SQLite Web j’ai exporté direcetement en csv.

A noter que pour certain capteur, il faut remplacer s.state par s.mean, en fonction des valeurs enregistrées dans la base de données.

Ensuite, j’ai créé ce script python pour transformer le format de ces csv en format compatible avec influxDB :

# --- Nom fichier .csv ---
CSV_FILE = "zlinky_tic_easf01.csv"

import pandas as pd
import math
import unicodedata

def escape_tag(s: str) -> str:
    """Échappe espaces, virgules, égal et backslash dans les tags."""
    return (
        str(s)
        .replace("\\", "\\\\")  # échapper backslash d'abord
        .replace(" ", "\\ ")
        .replace(",", "\\,")
        .replace("=", "\\=")
    )

def escape_measurement(s: str) -> str:
    """Échappe espaces, virgules et backslash dans le nom de mesure (Influx LP)."""
    return (
        str(s)
        .replace("\\", "\\\\")
        .replace(" ", "\\ ")
        .replace(",", "\\,")
    )

def sanitize_float(x):
    """Convertit en float (point décimal), retourne None si non numérique ou non-fini."""
    if pd.isna(x):
        return None
    s = str(x).strip().replace(",", ".")
    try:
        val = float(s)
        return val if math.isfinite(val) else None
    except ValueError:
        return None

def to_ns_from_epoch_seconds(ts):
    """
    Convertit un timestamp (secondes Epoch, potentiel float/str) en nanosecondes (int).
    Retourne None si invalide.
    """
    if ts is None or (isinstance(ts, float) and math.isnan(ts)):
        return None
    s = str(ts).strip()
    try:
        seconds = float(s)
        return int(seconds * 1e9)
    except Exception:
        # Fallback: si ce n'est pas un nombre, tenter une date ISO
        try:
            t = pd.Timestamp(s)
            if pd.isna(t):
                return None
            return int(t.timestamp() * 1e9)
        except Exception:
            return None

# --- Paramétrage ---
DEFAULT_MEASUREMENT = "value"   # si Unit est manquant, on met 'value'
SOURCE_TAG = "HA"     # pour distinguer de l'historique brut

# Lecture CSV en UTF-8
df = pd.read_csv(CSV_FILE, encoding="utf-8")

# Colonnes attendues (format statistiques long terme)
required_cols = {"statistic_id", "Unit", "start_ts", "state"}
missing = required_cols - set(df.columns)
if missing:
    raise ValueError(f"Colonnes manquantes dans le CSV: {missing}")

lines = []
ignored = 0

for _, row in df.iterrows():
    # --- entity_id : enlever le prefixe 'sensor.' s'il est présent ---
    raw_entity = str(row["statistic_id"])
    if raw_entity.startswith("sensor."):
        raw_entity = raw_entity[len("sensor."):]

    # --- Unit / Measurement ---
    raw_unit = str(row["Unit"]).strip() if not pd.isna(row["Unit"]) else ""
    measurement_name = raw_unit if raw_unit else DEFAULT_MEASUREMENT

    # Échappement + normalisation Unicode (NFC)
    measurement = unicodedata.normalize("NFC", escape_measurement(measurement_name))
    entity = unicodedata.normalize("NFC", escape_tag(raw_entity))
    source = unicodedata.normalize("NFC", escape_tag(SOURCE_TAG))
    domain = unicodedata.normalize("NFC", escape_tag("sensor"))
    unit_tag = unicodedata.normalize("NFC", escape_tag(raw_unit)) if raw_unit else None

    # Tags
    tags = f"domain={domain},entity_id={entity},source={source}"

    # Field (float)
    value = sanitize_float(row["state"])
    if value is None:
        ignored += 1
        continue
    fields = f"value={value}"

    # Timestamp en ns (à partir de start_ts en secondes Epoch)
    ts_ns = to_ns_from_epoch_seconds(row["start_ts"])
    if ts_ns is None:
        ignored += 1
        continue

    # Ligne LP
    line = f"{measurement},{tags} {fields} {ts_ns}"
    lines.append(line)

# Écriture du fichier LP en UTF-8 (sans BOM)
with open("data_line_protocol.txt", "w", encoding="utf-8", newline="\n") as f:
    f.write("\n".join(lines))

print(
    f"{len(lines)} lignes LP écrites dans 'data_line_protocol.txt' (UTF-8, measurement: variable par Unit). "
    f"Lignes ignorées: {ignored}"
)

J’ai un peu galéré avec les capteurs ayant pour unité °C, car influxDB ne voulait pas reconnaitre correctement le caractère °.

Une fois ce script lancé sur chacun des fichiers csv, je peux désormais l’importer dans le bucket voulu via Line Protocol.

Maintenant que j’ai mis des valeurs dans un bucket de test, j’en suis à regarder comment récupérer et afficher ça proprement dans HA.

Mais pourquoi tant de haine :scream:

Alors que l’export des données vers influxdb est fait nativement dans HA…

En effet, il y a l’intégration native qui existe.
Par contre, je ne suis pas certain que l’historique actuel va y être basculé sans perte (donc une petite sauvegarde ne fait pas de mal). Si tu peux me confirmer, ce serait top.

Aussi, ce que j’ai fait, là, ce n’est que créer un bucket de test (avec les valeurs récupérer avec tant d’amour :innocent:), et je regarde comment afficher les valeurs dans HA. Quand j’aurai bien pris en main cette partie, je pourrais faire le grand saut (soit via l’implémentation toute simple depuis l’intégration officielle, soit en mode bourrin comme j’ai préparé).

Bonjour
Il y a quelques temps, j’avais importé des données avec spook, un début d’année qui me manquait lors du tranferts jeedom HA et l’historique de l’année précédente, par contre pour une année on ne peut réintroduitre qu’un mois entier sinon c’est fastideux

Sinon avec un module lixee et l’intégration SQL comme toutes les données remontent, on peut se créer un sensor des années précédentes (ça dépend ce qu’on veut mais si on a un module lixee et qu’on veut afficher une année, cette solution et plus facile à mettre en oeuvre)

aucune idéee j’ai mis influxdb des le debut en parallèle de sqlite

ça c’est pas génial , je garde mes dashboards grafana et au final je en vois pas l’interet d’avoir ces datas dans HA je passe pas ma vie a me caresser devant mes courbes LOL

J’avais aussi installé InfluxDB / Grafana dès le début mais j’utilise peu.
Au passage, si quelqu’un à un capteur fiable de la taille de la base InfluxDB je veux bien, celui-ci remonte 24 Mo, je n’y crois pas trop.

  - platform: influxdb
    api_version: 1
    host: a0d7b954-influxdb
    port: 8086
    username: !secret influxdb_user
    password: !secret influxdb_password
    queries:
      - name: InfluxDB Database Size
        unit_of_measurement: MB
        value_template: '{{ (value | float / 1024 /1024) | round(1) }}'
        group_function: mean
        measurement: '"monitor"."shard"'
        database: _internal
        where: 'time > now() - 1m AND "database"=''haMassimoDB'''
        field: diskBytes

      - name: influxdb Valeur Max Temp Ext
        unit_of_measurement: '°C'
        value_template: '{{ value | round(1) }}'
        group_function: max
        where: '"entity_id" = ''temperature_ext'' AND time < now() - 30d'
        measurement: '"°C"'
        field: value
        database: haBobDB

D’ailleurs en copiant le code je me demande s’il ne manquerait pas dans le premier « capteur »:

database: haBobDB

Bob