Tempo : Injection quotidienne dans suiviconso des données Enedis au pas de 30', sans clé linky

Bonjour SuperBricolo,

J’ai fini par faire un scénario qui va tous les matins à 04h00 récupérer les consommations horaires au pas de 30’ de la veille dans l’historique du plugin Enedis et les injecter directement dans la table ‹ conso_teleinfo › du plugin-suiviconso
J’y ajoute en même temps le papp (c’est facile car c’est 2x la valeur du pas de 30’ de Enedis), le petc… et bientôt la t°.
A priori l’injection c’est bien passé, je suis même remonté jusqu’au 3 octobre 2023 , démarrage de mon abonnement tempo.

Je me doute que ce n’est pas forcement une utilisation prévue, mais après en avoir essayé plusieurs, c’est la seule qui pourrait ,à priori, donner, certes avec 1j de retard, la pleine puissance de cet outils dans le reporting. Et voir les évolutions des conso au pas de 30’.
Et pour moi dont le compteur est hors de portée d’une TIC c’est un peu la seule solution.

Question : est ce que le fait de ne pas ajouter de commande va bloquer la diggestion et le rendu des données des tableaux ?

Si oui comment faire pour ajouter des commandes qui ne crées pas de ligne dans la table ? -car cela biaise les index dans le scénarios actuel et prends de la place pour rien

merci

Bravo, pour l’intégration. A l’occasion ton script m’intéresse pour mettre dans la doc du plugin.
Sinon, pour tes questions, je ne sais pas, j’ai jamais essayé. Sans commande, est ce que le dashboard s’affiche? As tu testé?
Si cela ne fonctionne pas, on pourra faire une petite modif pour arranger ça

non je n’ai rien sur le dashboard. Je ne suis pas surpris car aucune commande n’a été crée finalement.
:grinning: je veux bien une petite modification pour arranger cela à l’occasion !!

Pour le script je le bascule sur ce fil dès que je finis sa V2 pour le rendre facile a utiliser. Je suis en train de le commenter et de modifier la détermination des couleurs pour taper non plus sur un virtuel mais sur le plugin Ecowat. Car Jeedom surprime tous les matins les données qui se répètent en suivant dans les historiques. Donc un Blue, Blue,Blue…il ne subsiste que le dernier à chaque fois… Je vais aller cherche HPJB/etc… de Ecowatt directement dans le scénario.

**Evolution V2.00 du 20/10/2025
Optimisation des injections
Récupères et injecte dynamiquement depuis la date du dernier index
Ajout d’une tolérance pour journée incomplète
Force journée= bleu si pas e couleur dans l’historique
Amelrioation des logs

Correction du 13/01/2024
Alignement des injections à 29/59 au lieu de 00/30. cela permet de ne pas avoir à 00h00 la consommation de la veille pour 23h30-00h00 pris en compte dans le calcul du jour.
Ajout d’une ligne à 0 à 0h00 pour le bon fonctionnement. elle ne sert à rien dans les calculs mais est nécessaire pour le moment pour la synchro car crée un évènement sur la journée.

je laisse les logs pour le moment

Correction du 05/12/2023
régression corrigé , j’avais omis de remettre l’appel pour les derniers index dans la base.
j’ai laissé les logs pour le moment

Correction du 04/12/2023

  • corrige la couleur en prenant en compte la couleur de 06h00 à 05h59 le lendemain. au lieu de la journée
  • ne nécessite plus de modifier avec adminer les index de la table conso_teleinfo en supprimant « unique » pour timestamp

Restriction connues :

  • si pas de données dans ecowatt maintenant => le scenario s’arrête
    solutions : importer les données via un scenario, en respectant les heures (>06h00
    pour HPx et >22h00 pour HCx

Voici le script
<<<<<<<<<<<<c’est une beta>>>>>>>>>>> donc soyez prudent et surtout indulgent. C’est mon premier codage …je débute tout juste le php et le ‹ jeedom ›.

nécessite :

  • plugin suiviconso :grinning:… Vous créez un nouvel élément sur lequel vous ne cocher surtout rien afin qu’aucune commandes ne soient appelées
  • plugin Enedis
  • plugin Ecowat

Le bloc code

// =========================
// CONFIGURATION
// =========================

$id_equipement = 258; // celui de suivi_conso

$toleranceManquants = 2; // entre 0 et 48creneaux manquants
// Commandes Jeedom
$cmdConsoPath = "#[Maison][Poitiers][Consommation Horaire]#"; //chemin historique données horaire du pluggin Enedis
$cmdTempoPath = "#[Maison][Tempo][Maintenant]#"; //chemin historique données couleur du pluggin Tempo

// Fenêtres HP/HC
$HP_START = "06:00:01";
$HC_START = "22:00:01";

// Conso horaire (kWh) ? Oui => demi-heure = /2
$DIVISE_CONSO = 2;

// Index initiaux (raccord compteur)
$hchp   = 15615695; // HP Bleu
$hchc   = 6262995;  // HC Bleu
$hchp2  = 1062795;         // HP Blanc
$hchc2  = 471645;         // HC Blanc
$hchp3  = 550000;         // HP Rouge
$hchc3  = 237835;         // HC Rouge

// Comportements
$FORCE_BLEU_IF_NO_TEMPO = true;
$LOG_DETAIL             = false;
$LOG_ABS_CONSO_AGG      = true;

// Tableaux pour récapitulatif
$joursManquants       = [];
$joursSansCouleur     = [];
$joursManquantsDetail = [];

// =========================
// Récupération des index cumulés depuis la dernière ligne
// =========================
function chargerIndexDerniereLigne($id_equipement, $scenario) {
    $sql = "SELECT hchp, hchc, hchp2, hchc2, hchp3, hchc3
            FROM conso_teleinfo
            WHERE id_equipement = :ide
            ORDER BY timestamp DESC
            LIMIT 1";
    $row = DB::Prepare($sql, array(':ide' => $id_equipement), DB::FETCH_TYPE_ROW);
    if (!empty($row)) {
        $scenario->setLog("[DEBUG] Index init depuis base: HP={$row['hchp']} HC={$row['hchc']} HP2={$row['hchp2']} HC2={$row['hchc2']} HP3={$row['hchp3']} HC3={$row['hchc3']}");
        return array_map('intval', $row);
    } else {
        $scenario->setLog("[DEBUG] Aucune ligne en base: utilisation des index init de configuration");
        return null;
    }
}

// Charger les index init depuis la dernière ligne en base (si disponible)
$idxInit = chargerIndexDerniereLigne($id_equipement, $scenario);
if ($idxInit) {
    $hchp  = $idxInit['hchp'];
    $hchc  = $idxInit['hchc'];
    $hchp2 = $idxInit['hchp2'];
    $hchc2 = $idxInit['hchc2'];
    $hchp3 = $idxInit['hchp3'];
    $hchc3 = $idxInit['hchc3'];
}

// =========================
// RÉCUP COMMANDES
// =========================
$cmdConso = cmd::byString($cmdConsoPath);
if (!is_object($cmdConso)) {
    $scenario->setLog("ERREUR: Commande conso introuvable: " . $cmdConsoPath);
    return;
}
$idConso = $cmdConso->getId();

$cmdTempo = cmd::byString($cmdTempoPath);
if (!is_object($cmdTempo)) {
    $scenario->setLog("ERREUR: Commande Tempo introuvable: " . $cmdTempoPath);
    return;
}
$idTempo = $cmdTempo->getId();

$scenario->setLog("OKcommandes: consoId=$idConso, tempoId=$idTempo");

// =========================
// SQL INSERT
// =========================
$sqlInsert = "INSERT INTO `conso_teleinfo`
(timestamp, rec_date, rec_time, hchp, hchc, hchp2, hchc2, hchp3, hchc3, ptec, papp, id_equipement)
VALUES (:ts, :rdate, :rtime, :hchp, :hchc, :hchp2, :hchc2, :hchp3, :hchc3, :ptec, :papp, :ide)
ON DUPLICATE KEY UPDATE timestamp = timestamp";

// =========================
// FONCTIONS UTILES
// =========================
function isHP($time, $HP_START, $HC_START) {
    $t  = strtotime($time);
    $hp = strtotime($HP_START);
    $hc = strtotime($HC_START);
    return ($t >= $hp && $t < $hc);
}

function tempoWindow($dayDate) {
    $start = clone $dayDate; $start->setTime(6, 0, 0);
    $end = clone $start; $end->add(new DateInterval('P1D')); $end->sub(new DateInterval('PT1S'));
    return array($start, $end);
}

function jourManquant($dateStr, $idConso, $scenario, $toleranceManquants) {
    global $joursManquantsDetail;

    $J = new DateTime($dateStr);
    $pub0 = clone $J;
    $pub0->setTime(0, 30, 0);

    $manquants = 0;
    $joursManquantsDetail[$dateStr] = []; // initialise le tableau pour ce jour

    for ($k = 0; $k < 48; $k++) {
        $pub = clone $pub0;
        $pub->add(new DateInterval("PT" . ($k * 30) . "M"));

        $start = clone $pub;
        $start->sub(new DateInterval("PT30M"));
        $start->add(new DateInterval("PT1S"));

        $stat = history::getStatistique(
            $idConso,
            $start->format('Y-m-d H:i:s'),
            $pub->format('Y-m-d H:i:s')
        );

        $watt = isset($stat["sum"]) ? (float)$stat["sum"] : 0.0;
        if ($watt <= 0) {
            $manquants++;
            // On stocke l’intervalle manquant pour audit
            $joursManquantsDetail[$dateStr][] = $start->format('H:i') . '-' . $pub->format('H:i');
        }
    }

    $scenario->setLog("[DEBUG] Vérif historique $dateStr : $manquants publications manquantes (tolérance=$toleranceManquants)");

    // Jour considéré comme manquant uniquement si le nombre de créneaux manquants dépasse la tolérance
    return ($manquants > $toleranceManquants);
}



// =========================
// INJECTION
// =========================
function injecterJour($dateStr, $idConso, $idTempo, $scenario,
                      $HP_START, $HC_START, $FORCE_BLEU_IF_NO_TEMPO,
                      $LOG_DETAIL, $LOG_ABS_CONSO_AGG,
                      $sqlInsert, $id_equipement,
                      &$hchp, &$hchc, &$hchp2, &$hchc2, &$hchp3, &$hchc3) {

    global $joursManquantsDetail, $joursSansCouleur;

    $D = new DateTime($dateStr);
    $D->setTime(0, 0, 0);
    $jourStr = $D->format('Y-m-d');

    // Fenêtre Tempo
    list($tempoStart, $tempoEnd) = tempoWindow($D);
    $tempoStat = history::getStatistique(
        $idTempo,
        $tempoStart->format('Y-m-d H:i:s'),
        $tempoEnd->format('Y-m-d H:i:s')
    );
    $tempoVal = $tempoStat["last"] ?? null;

    $scenario->setLog("[DEBUG] Injection $jourStr avec tempo=$tempoVal");

    // Préparation batch SQL
    $values = [];
    $params = [];
    $lastPtec = null;

    // Point de départ : 00h30
    $pub = clone $D;
    $pub->setTime(0, 30, 0);

    for ($k = 0; $k < 48; $k++) {
        $end = clone $pub;
        $end->add(new DateInterval("PT" . ($k * 30) . "M"));

        $start = clone $end;
        $start->sub(new DateInterval("PT30M"));
        $start->add(new DateInterval("PT1S"));

        $stat = history::getStatistique(
            $idConso,
            $start->format('Y-m-d H:i:s'),
            $end->format('Y-m-d H:i:s')
        );

        $watt = isset($stat["sum"]) ? (float)$stat["sum"] : 0.0;
        $wh   = $watt / 2.0;

        if ($watt <= 0) {
            //$joursManquantsDetail[$jourStr][] = $start->format('H:i').'-'.$end->format('H:i');
            continue;
        }

        // Détermination HP/HC
        $timeStr = $end->format('H:i:s');
        $isHP = isHP($timeStr, $HP_START, $HC_START);

        // Détermination couleur Tempo
        $tempoColor = $tempoVal;
        if ($FORCE_BLEU_IF_NO_TEMPO && empty($tempoColor)) {
            $tempoColor = 'JB'; // forcé en Jour Bleu
            $joursSansCouleur[] = $jourStr;
            $scenario->setLog("[INFO][TEMPO] >>> Couleur forcée en BLEU le $jourStr <<<");
        }

        // Mise à jour index cumulés
        if (strpos($tempoColor, 'JB') !== false) {
            if ($isHP) $hchp  += ($wh * 1000); else $hchc  += ($wh * 1000);
            $ptec = $isHP ? 'HPJB' : 'HCJB';
        } elseif (strpos($tempoColor, 'JW') !== false) {
            if ($isHP) $hchp2 += ($wh * 1000); else $hchc2 += ($wh * 1000);
            $ptec = $isHP ? 'HPJW' : 'HCJW';
        } elseif (strpos($tempoColor, 'JR') !== false) {
           if ($isHP) $hchp3 += ($wh * 1000); else $hchc3 += ($wh * 1000);
            $ptec = $isHP ? 'HPJR' : 'HCJR';
        } else {
            // fallback si valeur inattendue
            $ptec = $isHP ? 'HP' : 'HC';
        }

        $lastPtec = $ptec; // mémorise le dernier PTEC du jour

        // Préparation ligne batch
        $inj = clone $end;
        $inj->sub(new DateInterval("PT1M"));

        $values[] = "(?,?,?,?,?,?,?,?,?,?,?,?)";
        $params[] = $inj->getTimestamp();
        $params[] = $inj->format('Y-m-d');
        $params[] = $inj->format('H:i:s');
        $params [] = round ( $hchp );
        $params [] = round ( $hchc );
        $params [] = round ( $hchp2 );
        $params [] = round ( $hchc2 );
        $params [] = round ( $hchp3 );
        $params [] = round ( $hchc3 );
        $params[] = $ptec;
        $params[] = round($watt);
        $params[] = $id_equipement;
    }

    // Ligne d’ancrage J+1 00:00:00
    $D_next = clone $D;
    $D_next->add(new DateInterval('P1D'));
    $D_next->setTime(0, 0, 0);

    $values[] = "(?,?,?,?,?,?,?,?,?,?,?,?)";
    $params[] = $D_next->getTimestamp();
    $params[] = $D_next->format('Y-m-d');
    $params[] = '00:00:00';
    $params [] = round ( $hchp );
    $params [] = round ( $hchc );
    $params [] = round ( $hchp2 );
    $params [] = round ( $hchc2 );
    $params [] = round ( $hchp3 );
    $params [] = round ( $hchc3 );
    $params[] = $lastPtec ?? 'ANCHOR'; // reprend le dernier PTEC du jour
    $params[] = 0;
    $params[] = $id_equipement;

    // Exécution batch
    if (!empty($values)) {
        $sql = "INSERT INTO conso_teleinfo
        (timestamp, rec_date, rec_time, hchp, hchc, hchp2, hchc2, hchp3, hchc3, ptec, papp, id_equipement)
        VALUES " . implode(',', $values) . "
        ON DUPLICATE KEY UPDATE timestamp=timestamp";

        DB::Prepare($sql, $params, DB::FETCH_TYPE_ALL);
    }

    $scenario->setLog("[DEBUG][CARRY] Ligne d’ancrage créée à " . $D_next->format('Y-m-d 00:00:00') . " avec PTEC=$lastPtec");

    return true;
}


// =========================
// DERNIÈRE INJECTION
// =========================
$sqlLast = "SELECT rec_date, rec_time
            FROM conso_teleinfo
            WHERE id_equipement = :ide
            ORDER BY timestamp DESC
            LIMIT 1";

$res = DB::Prepare($sqlLast, array(':ide' => $id_equipement), DB::FETCH_TYPE_ROW);

if ($res) {
    $lastDate = trim($res['rec_date']);
    $lastTime = trim($res['rec_time']);
    $scenario->setLog("[DEBUG] Dernière injection conso_teleinfo : $lastDate $lastTime");

    if ($lastTime === '00:00:00') {
        // Ligne d’ancrage → on reprend à partir de ce jour
        $loopStart = new DateTime($lastDate);
        $scenario->setLog("[DEBUG] Reprise depuis ancrage $lastDate");
    } else {
        // Ancien mode → reprise classique
        $startDate = new DateTime("$lastDate $lastTime");
        $startDate->add(new DateInterval("PT30M"));
        $loopStart = clone $startDate;
        $loopStart->setTime(0, 0, 0);
        $scenario->setLog("[DEBUG] Reprise legacy depuis " . $startDate->format("Y-m-d H:i:s"));
    }
} else {
    $scenario->setLog("[DEBUG] Aucune donnée trouvée → injection complète depuis la veille");
    $today = new DateTime();
    $yesterday = clone $today;
    $yesterday->sub(new DateInterval("P1D"));
    $yesterday->setTime(0, 0, 0);
    $loopStart = $yesterday;
}

// =========================
// BOUCLE PRINCIPALE
// =========================
$today = new DateTime();
$yesterday = clone $today;
$yesterday->sub(new DateInterval("P1D"));
$yesterday->setTime(23, 59, 59);

$period = new DatePeriod($loopStart, new DateInterval("P1D"), $yesterday);

// Liste des jours injectés sous tolérance
$joursToleres = [];
global $joursManquantsDetail;
foreach ($period as $day) {
    $dateStr = $day->format("Y-m-d");

    if (jourManquant($dateStr, $idConso, $scenario, $toleranceManquants)) {
        $scenario->setLog("[DEBUG][BACKFILL] Jour manquant $dateStr → pas d'injection");
        $joursManquants[] = $dateStr;
    } else {
        // Injection acceptée (même si jour incomplet mais <= tolérance)
        $scenario->setLog("[DEBUG][BACKFILL] Injection $dateStr");
///test//
      $scenario->setLog("[DEBUG][CHECK] $dateStr → manquants détectés = " . count($joursManquantsDetail[$dateStr]));

        // Vérifie si le jour a été injecté sous tolérance
        if (!empty($joursManquantsDetail[$dateStr])) {
            $nbManquants = count($joursManquantsDetail[$dateStr]);
            if ($nbManquants > 0 && $nbManquants <= $toleranceManquants) {
                $scenario->setLog("[DEBUG][TOLERANCE] $dateStr injecté malgré $nbManquants créneaux manquants (tolérance=$toleranceManquants)");
                $joursToleres[] = $dateStr . " ($nbManquants manquants)";
            }
        }

        injecterJour(
            $dateStr, $idConso, $idTempo, $scenario,
            $HP_START, $HC_START, $FORCE_BLEU_IF_NO_TEMPO,
            $LOG_DETAIL, $LOG_ABS_CONSO_AGG,
            $sqlInsert, $id_equipement,
            $hchp, $hchc, $hchp2, $hchc2, $hchp3, $hchc3
        );
    }
}

// =========================
// LOG DE SYNTHÈSE
// =========================
if (!empty($joursManquants)) {
    $scenario->setLog("[RÉCAP] Jours manquants : " . implode(', ', $joursManquants));
} else {
    $scenario->setLog("[RÉCAP] Aucun jour manquant");
}

if (!empty($joursSansCouleur)) {
    $scenario->setLog("[RÉCAP] Couleur forcée en BLEU pour " . count($joursSansCouleur) . " jours : " . implode(', ', $joursSansCouleur));
} else {
    $scenario->setLog("[RÉCAP] Aucun jour forcé en BLEU");
}

if (!empty($joursToleres)) {
    $scenario->setLog("[RÉCAP] Jours injectés sous tolérance : " . implode(', ', $joursToleres));
}

$joursInjectes = [];
foreach ($period as $day) {
    $d = $day->format('Y-m-d');
    if (!in_array($d, $joursManquants)) {
        $joursInjectes[] = $d;
    }
}

if (!empty($joursInjectes)) {
    $scenario->setLog("[RÉCAP] Jours injectés normalement : " . implode(', ', $joursInjectes));
} else {
    $scenario->setLog("[RÉCAP] Aucun jour injecté normalement");
}

$scenario->setLog("[DEBUG][BACKFILL] Terminé");



Le timestamp est utilisée comme clé de recherche dans certains cas. Donc ce n’est pas une bonne idée de supprimer l’unicité du champs. Il vaudrait mieux en calculer un à partir de rec_date et rec_time

Sinon tu as essayé de faire "Synchroniser tout’ pour voir si les données de conso_teleinfo se propageait bien vers conso_jour, ce qui permettrait d’avoir un dashboard aevc les graphiques, je pense.

Pour le code soit lisible il faut que tu mettes des balises.Utilises cet icone quand tu rédiges le post
image
Cela donne un truc comme ça:
image

je l’ai supprimé car j’ai deux compteurs.
Donc je vais avoir systématiquement 2 entrées qui auront la même timestamp mais sur un id_equipement différent. Mais si je laisse l’unicité du timestamp, seule une des deux sera injecter dans la table.

Tu fais une différence 5 secondes entre les 2 équipements et ce sera réglé. En tout cas je vais regarder ton script et voir si je ne pourrais pas l’intégrer directement dans le plugin.
J’ai regardé vite fait en travers, mais tes infos tu les récupères sur l’historiques Jeedom? C’est ça?

j’ai trouvé pourquoi le dashboard n’affichait pas ce qu’il fallait. j’injectais en KWh et non en Wh
j’ai modifié le scénario

voici ce que j’obtient

avec 0 commande

oui c’est le plus simple que j’ai trouvé. Tous les matins à 04h00 (il me semble) le plugin Enedis incrémente les données de la veille dans l’historique.

C’est nickel. Du coup, il n’y a rien à faire pour que cela fonctionne, si ce n’est comme je disais avant, intégrer peut-être ton script comme mode possible pour l’intégration des données dans SuiviConso.

bonne idée que de l’intégrer.
Attends quelques jours que je finisse de l’éprouver.

Quelques idées de modification

1- la couleur du jour et la tarification n’apparaisse pas sur le dasboard car aucune référence n’est entrée dans l’équipement

2- la vue jour devrait être par défaut affichée sur la veille

3- la vue jour devrait être en conso et non en puissance ?

4- résolu, c’est deux valeurs une HT et l’autre TTC

5-pourquoi ne pas afficher les variations de puissance, via la donnée papp dans le bloc en haut à droite ?

6- et enfin as tu la possibilité de mettre des couts d’abonnements différents sur deux éléments différents ?.
j’ai deux compteurs, le premier 36kva et l’autre 9 kva

Ca devrait pouvoir se faire.

C’est à voir si je peux

Non, tu as le graphe 7 derniers jours pour cela

Ce tableau est prévu pour le fil de l’eau. Et comme là tu es sur des données de la veille, je ne vois pas l’intérêt. De plus il n’est pas assez grand pour tout afficher

C’est possible si tu utilises le type Prod Elect (C’est prévu pour la production, mais tu peux le détourner)

Salut,

Pour être tout à fait précis, le plugin Enedis effectue le 1er appel de la journée aléatoirement entre 3h01 et 6h59.

Ha !!! Ba mince alors !
Merci pour cette info, je vais programmer la récurrence du scénario à 07h00.

Idéalement il faudrait que je fasse un appel de date de collecte à 3h00 pour savoir si il lance ou non le scenario et resteste toute les 30’ ensuite jusqu’à succès…mais je ne suis pas sur d’y arriver facilement.

merci , vu ! par contre sauf si j’ai mal fait, il n’y a plus de facture… dommage

Si on peut avoir des factures, il faut juste créer les périodes correspondantes et dire que tu veux une facture

ok ! top !
merci

Bonjour,

Ce script pourrait-il être utilisé pour le compteur gaz via le plugin gazpar ? En effet j’avais essayé de créer un équipement qui prenait les infos remontées par le plugin, mais à moins que je n’ai pas compris et mal fait qqch, ça me remplissait la table teleinfo avec des valeurs identiques toutes les minutes. Insérer a posteriori que les quelques lignes pertinentes serait suffisant.

Bonjour,

il y a cela dans la table de remonté de GRDFconnect
image
Donc pas de conso horaire.
Importer la conso jour doit être faisable assez facilement… à priori… j’ai juste quelques journées d’expérience dans le codage et de connaissance de jeedom… par contre je ne sais pas quel rendu cela pourrait avoir sur suiviconso

Après importer régulièrement un fichier au pas horaire issue du site il suffit de prendre le tuto de superbricolo

Bonjour SuperBricolo

j’ai mis le script à jour pour corriger un problème de prise en compte des couleurs.
j’y ai laissé les logs pour le moment