Accéder aux variables depuis un HTML sur un design

Bonjour à tous !

Après avoir vu le post sur l’utilisation de plugin-htmldisplay pour afficher sur un design un graphique, je m’en suis inspiré pour fabriquer le mien.
Ayant un système elec Enphase, j’apprécie beaucoup la présentation des puissances/énergies sur leur site :


J’entreprends de refaire ce graph chez moi :

Le code dans mon équipement plugin-htmldisplay :

<div id="controls" style="margin-bottom: 1em;">
  <label><input type="radio" name="duree" value="1" checked> 24h glissant</label>
  <label><input type="radio" name="duree" value="2"> Depuis 5h (veille)</label>
  &nbsp;|&nbsp;
  <button onclick="changerStack(-1)">−</button>
  <span id="stackWidthDisplay">2</span> min
  <button onclick="changerStack(1)">+</button>
</div>

<div id="container" style="height:400px;"></div>

<script>
var duree = typeof duree !== 'undefined' ? duree : 1;
var stackWidth = typeof stackWidth !== 'undefined' ? stackWidth : 2;
var stackSteps = stackSteps || [2, 5, 10, 15, 20, 30, 60];

  
document.querySelectorAll('input[name="duree"]').forEach(radio => {
  radio.addEventListener('change', e => {
    duree = parseInt(e.target.value);
    chargerDonnees();
  });
});

function changerStack(direction) {
  const currentIndex = stackSteps.indexOf(stackWidth);
  const nextIndex = currentIndex + direction;
  if (nextIndex >= 0 && nextIndex < stackSteps.length) {
    stackWidth = stackSteps[nextIndex];
    document.getElementById('stackWidthDisplay').textContent = stackWidth;
    chargerDonnees();
  }
}
function chargerDonnees() {
  //const duree = 1; //1 = 24h glissant, 2= depuis la veille à 5h
  //const stackWidth = 2; //pour regrouper par 5min, 10 min, 15 minutes etc...
  const now = new Date(); // heure locale
  const endTime = now.getTime(); // maintenant, en local → timestamp (UTC millisecondes)

  let startTime;
  if (duree == 1) {
    // Glissant 24h
    startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000).getTime();
  } else {
    // De 5h la veille jusqu'à maintenant
    startTime = new Date(
      now.getFullYear(),
      now.getMonth(),
      now.getDate() - 1, // ← veille !
      5, 0, 0, 0
    ).getTime();
  }

  const puissances = {
    prod: new Map(),
    cons: new Map(),
    exp: new Map(),
    imp: new Map()
  };

  const arrondiXmin = timestamp => Math.floor(timestamp / (stackWidth * 60 * 1000)) * (stackWidth * 60 * 1000);

  let chargements = 0;

  const noms = {
    5098: { map: puissances.prod, facteur: 1 },
    5104: { map: puissances.cons, facteur: -1 },
    5115: { map: puissances.exp, facteur: -1 },
    5116: { map: puissances.imp, facteur: 1 }
  };

  Object.entries(noms).forEach(([cmd_id, obj]) => {
    jeedom.history.get({
      cmd_id: parseInt(cmd_id),
      dateStart: new Date(startTime).toISOString().slice(0, 19).replace('T', ' '),
      dateEnd: new Date(endTime).toISOString().slice(0, 19).replace('T', ' '),
      success: function (result) {
        result.data.forEach(entry => {
          const t = arrondiXmin(entry[0]);
          //if (t < startTime || t > endTime) return; // filtre sécurité
          const val = obj.facteur * entry[1];
          const list = obj.map.get(t) || [];
          list.push(val);
          obj.map.set(t, list);
        });
        chargements++;
        if (chargements === 4) dessinerGraphique();
      }
    });
  });

  
  function dessinerGraphique() {
    const allTimestamps = Array.from(
      new Set([
        ...puissances.prod.keys(),
        ...puissances.cons.keys(),
        ...puissances.exp.keys(),
        ...puissances.imp.keys()
      ])
    ).sort((a, b) => a - b);

    const moy = arr => arr && arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : 0;

    const serie_prod = [], serie_imp = [], serie_cons = [], serie_exp = [];

    allTimestamps.forEach(ts => {
      serie_prod.push([ts, Math.trunc(moy(puissances.prod.get(ts)))]);
      serie_imp.push([ts, Math.trunc(moy(puissances.imp.get(ts)))]);
      serie_cons.push([ts, Math.trunc(moy(puissances.cons.get(ts)))]);
      serie_exp.push([ts, Math.trunc(moy(puissances.exp.get(ts)))]);
    });

    Highcharts.setOptions({
      global: {
    	useUTC: false
      },
      lang: {
        months: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin',
          'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],
        weekdays: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
        shortMonths: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin',
          'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.']
      }
    });

    Highcharts.chart('container', {
      chart: { type: 'column' },
      title: { text: null, align: 'left' },
      xAxis: {
        type: 'datetime',
        min: startTime,
        max: endTime,
        tickInterval: 60 * 60 * 1000, // 1 heure
        labels: {
          format: '{value:%H}h'
        }
      },
      yAxis: {
        title: { text: 'Puissance (W)' },
        labels: {
    		formatter: function () {
      			return Math.abs(this.value);
    		}
  		},
        stackLabels: { enabled: false }
      },
      tooltip: {
  		xDateFormat: '%H:%M',
  		shared: true,
  		formatter: function () {
          	const pointStart = this.x;
    		const pointEnd = pointStart + stackWidth * 60 * 1000; // en ms
    		const formatHeure = ts => Highcharts.dateFormat('%H:%M', ts);
    		let s = `<b>${formatHeure(pointStart)} → ${formatHeure(pointEnd)}</b><br/>`;
    		//let s = `<b>${Highcharts.dateFormat('%H:%M', this.x)}</b><br/>`;
    		this.points.forEach(point => {
      		const val = Math.abs(point.y);
              if (val !== 0) {
        		s += `<span style="color:${point.series.color}">●</span> ${point.series.name}: <b>${Math.trunc(val)}</b><br/>`;
      		}
    		});
    		return s;
  		}
      },
      plotOptions: {
        column: {
          stacking: 'normal',
          groupPadding: 0.1, // ≈ 90% de largeur
          pointPadding: 0,
          borderWidth: 0
        }
      },
      series: [
        {
          name: 'Exportation',
          data: serie_exp,
          stack: 'Puissance',
          color: '#6c7073'
        },
        {
          name: 'Importation',
          data: serie_imp,
          stack: 'Puissance',
          color: '#6c7073'
        },
        {
          name: 'Production',
          data: serie_prod,
          stack: 'Puissance',
          color: '#01b4de'
        },
        {
          name: 'Consommation',
          data: serie_cons,
          stack: 'Puissance',
          color: '#f37320'
        },
      ]
    });
  }
}

// Initialisation après chargement
function attendreEtCharger() {
  if (!document.getElementById('container')) {
    setTimeout(attendreEtCharger, 100);
    return;
  }
  chargerDonnees();
}
attendreEtCharger();
</script>

Comme vous pouvez le voir, j’ai deux commandes en haut du visuel qui permettent de modifier la durée à afficher entre deux choix (variable « duree »), et modifier la largeurs des barres parmi : 2, 5, 10, 15, 20, 30, 60 minutes (variable « stackWidth »).

Ca marche mais à chaque fois que je recharge la page ça repart vers une valeur par défaut.

Idéalement j’aimerais que mes deux informations duree et stackWidth soient lues dans les variables Jeedom et que ces variables soient réécrites à chaque fois qu’on modifie les paramètres.
J’ai demandé de l’aide à mon pote ChatGPT et Mistral mais je pense qu’il ne connaissent pas précisément les possibilités de jeedom.

Ils me propose d’utiliser des méthode jeedom.var.get() et jeedom.var.save() mais ça ne semble pas exister.
La partie du code modifiée et la proposition qui ne fonctionne pas :

  let nbOK = 0;

  jeedom.var.get({
    name: 'graph_duree',
    success: function (val) {
      if (val !== null) {
        duree = parseInt(val);
        document.querySelector(`input[name="duree"][value="${duree}"]`).checked = true;
      }
      if (++nbOK === 2) chargerDonnees();
    }
  });

  jeedom.var.get({
    name: 'graph_stackWidth',
    success: function (val) {
      if (val !== null) {
        stackWidth = parseInt(val);
      }
      document.getElementById('stackWidthDisplay').textContent = stackWidth;
      if (++nbOK === 2) chargerDonnees();
    }
  });

  // Ajouter les écouteurs sur les boutons radio
  document.querySelectorAll('input[name="duree"]').forEach(radio => {
    radio.addEventListener('change', function () {
      duree = parseInt(this.value);
      jeedom.var.save({ name: 'graph_duree', value: duree });
      chargerDonnees();
    });
  });
}

function changerStack(direction) {
  const index = stackSteps.indexOf(stackWidth);
  const newIndex = Math.max(0, Math.min(stackSteps.length - 1, index + direction));
  if (newIndex !== index) {
    stackWidth = stackSteps[newIndex];
    document.getElementById('stackWidthDisplay').textContent = stackWidth;
    jeedom.var.save({ name: 'graph_stackWidth', value: stackWidth });
    chargerDonnees();
  }
}

J’aurais voulu savoir dans la communauté si quelqu’un savait comment faire pour accéder aux variables depuis du html affiché sur un design, un peu comme on connait les commande php depuis un scenario :

$scenario->setData($key, $value); : Sauvegarde une donnée (variable).
     $key : clé de la valeur (int ou string).
     $value : valeur à stocker (int, string, array ou object).
$scenario->getData($key); : Récupère une donnée (variable).
     $key => 1 : clé de la valeur (int ou string).

Merci à tous !

PS :

Je remercie tous ceux qui ont pris le temps de lire et s’intéresser à mon problème ! J’ai vu qu’il y avait eu quand même pas mal de lecture.
Après un peu de patience, la compréhension des fonctions getData et setData qui utilisent la classe dataStore, j’ai finalement réussi à faire ce que je voulais.
Je vous le présente :

Le visuel HTML de mon graphique va envoyer des appel AJAX à un script php perso qui va faire appel à cette classe qui permet de lire et écrire dans les variables :

J’ai place ce script variableGetSet.php dans ./html/script de jeedom :

<?php
require_once __DIR__ . '/../core/php/core.inc.php';

header('Content-Type: application/json');

$action = isset($_GET['action']) ? $_GET['action'] : '';
$key = isset($_GET['key']) ? $_GET['key'] : '';
$value = isset($_GET['value']) ? $_GET['value'] : '';

try {
    if ($action == 'set') {
        setDataStoreValue($key, $value);
        echo json_encode(['success' => true]);
    } elseif ($action == 'get') {
        $data = getDataStoreValue($key);
        echo json_encode(['success' => true, 'value' => $data]);
    } else {
        throw new Exception('Action non valide');
    }
} catch (Exception $e) {
    echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}

function setDataStoreValue($key, $value, $type = 'scenario', $link_id = -1) {
    $dataStore = new dataStore();
    $dataStore->setType($type);
    $dataStore->setLink_id($link_id);
    $dataStore->setKey($key);
    $dataStore->setValue($value);
    $dataStore->save();
}

function getDataStoreValue($key, $type = 'scenario', $link_id = -1, $default = '') {
    $dataStore = dataStore::byTypeLinkIdKey($type, $link_id, $key);
    if (is_object($dataStore)) {
        return $dataStore->getValue($default);
    }
    return $default;
}
?>

Voici ensuite le code de mon équipement plugin-htmldisplay :

<div style="margin-bottom: 10px;">
  <label>
    <input type="radio" name="duree" value="1"> Glissant 24h
  </label>
  <label style="margin-left: 10px;">
    <input type="radio" name="duree" value="2"> Depuis la veille à 5h
  </label>
  <span style="margin-left: 30px;">
    Regroupement :
    <button onclick="changerStack(-1)">−</button>
    <span id="stackWidthDisplay">?</span> min
    <button onclick="changerStack(1)">+</button>
  </span>
</div>

<div id="container" style="height:400px;"></div>

<script>
var stackSteps = [2, 5, 10, 15, 20, 30, 60];
var duree = 1;
var stackWidth = 2;

// Fonction pour récupérer une valeur
function getData(key, callback) {
    const url = `/../script/variableGetSet.php?action=get&key=${encodeURIComponent(key)}`;

    fetch(url)
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                callback(data.value);
            } else {
                console.error('Erreur lors de la récupération de la donnée:', data.error);
            }
        })
        .catch(error => console.error('Erreur:', error));
}

// Fonction pour sauvegarder une valeur
function setData(key, value) {
    const url = `/../script/variableGetSet.php?action=set&key=${encodeURIComponent(key)}&value=${encodeURIComponent(value)}`;

    fetch(url)
        .then(response => response.json())
        .then(data => {
            if (!data.success) {
                console.error('Erreur lors de la sauvegarde de la donnée:', data.error);
            }
        })
        .catch(error => console.error('Erreur:', error));
}
  
function chargerParametresEtDonnées() {

let nbOK = 0;

// Récupérer graph_duree
getData('graph_duree', function(val) {
    if (val !== null) {
        duree = parseInt(val);
        document.querySelector(`input[name="duree"][value="${duree}"]`).checked = true;
    }
    if (++nbOK === 2) chargerDonnees();
});

// Récupérer graph_stackWidth
getData('graph_stackWidth', function(val) {
    if (val !== null) {
        stackWidth = parseInt(val);
    }
    document.getElementById('stackWidthDisplay').textContent = stackWidth;
    if (++nbOK === 2) chargerDonnees();
});

// Ajouter les écouteurs sur les boutons radio
document.querySelectorAll('input[name="duree"]').forEach(radio => {
    radio.addEventListener('change', function() {
        duree = parseInt(this.value);
        setData('graph_duree', duree);
        chargerDonnees();
    });
});
}

function changerStack(direction) {
    const index = stackSteps.indexOf(stackWidth);
    const newIndex = Math.max(0, Math.min(stackSteps.length - 1, index + direction));
    if (newIndex !== index) {
        stackWidth = stackSteps[newIndex];
        document.getElementById('stackWidthDisplay').textContent = stackWidth;
        setData('graph_stackWidth', stackWidth);
        chargerDonnees();
    }
}

function chargerDonnees() {
  const now = new Date();
  const endTime = now.getTime();
  let startTime;

  if (duree === 1) {
    startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000).getTime();
  } else {
    startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, 5, 0, 0, 0).getTime();
  }

  const puissances = {
    prod: new Map(),
    cons: new Map(),
    exp: new Map(),
    imp: new Map()
  };

  const arrondiXmin = timestamp => Math.floor(timestamp / (stackWidth * 60 * 1000)) * (stackWidth * 60 * 1000);
  let chargements = 0;

  const noms = {
    5098: { map: puissances.prod, facteur: 1 },
    5104: { map: puissances.cons, facteur: -1 },
    5115: { map: puissances.exp, facteur: -1 },
    5116: { map: puissances.imp, facteur: 1 }
  };

  Object.entries(noms).forEach(([cmd_id, obj]) => {
    jeedom.history.get({
      cmd_id: parseInt(cmd_id),
      dateStart: new Date(startTime).toISOString().slice(0, 19).replace('T', ' '),
      dateEnd: new Date(endTime).toISOString().slice(0, 19).replace('T', ' '),
      success: function (result) {
        result.data.forEach(entry => {
          const t = arrondiXmin(entry[0]);
          const val = obj.facteur * entry[1];
          const list = obj.map.get(t) || [];
          list.push(val);
          obj.map.set(t, list);
        });
        chargements++;
        if (chargements === 4) dessinerGraphique(startTime, endTime, puissances);
      }
    });
  });
}

function dessinerGraphique(startTime, endTime, puissances) {
  const allTimestamps = Array.from(
    new Set([
      ...puissances.prod.keys(),
      ...puissances.cons.keys(),
      ...puissances.exp.keys(),
      ...puissances.imp.keys()
    ])
  ).sort((a, b) => a - b);

  const moy = arr => arr && arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : 0;

  const serie_prod = [], serie_imp = [], serie_cons = [], serie_exp = [];

  allTimestamps.forEach(ts => {
    serie_prod.push([ts, Math.trunc(moy(puissances.prod.get(ts)))]);
    serie_imp.push([ts, Math.trunc(moy(puissances.imp.get(ts)))]);
    serie_cons.push([ts, Math.trunc(moy(puissances.cons.get(ts)))]);
    serie_exp.push([ts, Math.trunc(moy(puissances.exp.get(ts)))]);
  });

  Highcharts.setOptions({
    global: { useUTC: false },
    lang: {
      months: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin',
        'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],
      weekdays: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
      shortMonths: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin',
        'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.']
    }
  });

  Highcharts.chart('container', {
    chart: { type: 'column' },
    title: { text: null, align: 'left' },
    xAxis: {
      type: 'datetime',
      min: startTime,
      max: endTime,
      tickInterval: 60 * 60 * 1000,
      labels: { format: '{value:%H}h' }
    },
    yAxis: {
      title: { text: 'Puissance (W)' },
      labels: {
        formatter: function () {
          return Math.abs(this.value);
        }
      },
      stackLabels: { enabled: false }
    },
    tooltip: {
      xDateFormat: '%H:%M',
      shared: true,
      formatter: function () {
        const pointStart = this.x;
        const pointEnd = pointStart + stackWidth * 60 * 1000;
        const formatHeure = ts => Highcharts.dateFormat('%H:%M', ts);
        let s = `<b>${formatHeure(pointStart)} → ${formatHeure(pointEnd)}</b><br/>`;
        this.points.forEach(point => {
          const val = Math.abs(point.y);
          if (val !== 0) {
            s += `<span style="color:${point.series.color}">●</span> ${point.series.name}: <b>${Math.trunc(val)}</b><br/>`;
          }
        });
        return s;
      }
    },
    plotOptions: {
      column: {
        stacking: 'normal',
        groupPadding: 0.1,
        pointPadding: 0,
        borderWidth: 0
      }
    },
    series: [
      {
        name: 'Exportation',
        data: serie_exp,
        stack: 'Puissance',
        color: '#6c7073'
      },
      {
        name: 'Importation',
        data: serie_imp,
        stack: 'Puissance',
        color: '#6c7073'
      },
      {
        name: 'Production',
        data: serie_prod,
        stack: 'Puissance',
        color: '#01b4de'
      },
      {
        name: 'Consommation',
        data: serie_cons,
        stack: 'Puissance',
        color: '#f37320'
      },
    ]
  });
}

// Démarrage après chargement du DOM
function attendreEtCharger() {
  if (!document.getElementById('container')) {
    setTimeout(attendreEtCharger, 100);
    return;
  }
  chargerParametresEtDonnées();
}
attendreEtCharger();
</script>

Et voici le rendu final, avec les settings qui s’enregistrent dans les variables graph_duree et graph_stackWidth :

L’utilité est discutable mais ça pourrait être utile pour d’autres usages alors je partage !

5 « J'aime »