Sauvegarder ou pas l'historique d'une fonctionnalités précise

Ok pour le faire en 2 PR, c’est mieux, sauf que pour ma part ca ne resout que 1% de mon problèmes et je me trompe peut-être mais j’ai compris les arguments des autres personnes dans le meme sens.
Je souhaite garder la quasi totalité des historiques. Ce qui fait que la base grossie a vue d’oeil c’est le fait de conserver des doublons tres souvent inutiles…
Donc le seul keep_history ne resout pas 99% du probleme dans mon cas. Est-ce qu’il en va de meme pour vous @lmilcent et @VonOx ? Ou j’ai mal compris pour votre cas d’usage ?

Exemple tout bête :
J’ai une prise sonoff POWR2 sous Tasmota avec alimentation d’une pompe + puissance consommée + 1 contact soudé sur le pin IO4 pour un niveau qui coupe la pompe et ouvre et ferme des vannes via une scene.

  • L’historique de la puissance m’interesse quasi tout le temps,
  • L’historique de la pompe ne m’interesse pas,
  • L’historique du niveau bas m’interesse mais seulement lorsqu’il change d’etat (1 fois par semaine des fois / 1 fois par mois d’autre fois) or pour le moment il conserve toutes les minutes…
1 « J'aime »

(J’ai déplacé ce message dans un topic spécifique pour ne pas polluer le feed du retour de @lmilcent )

2 « J'aime »

Je m’occupe de la 1ere PR au plus vite !!

Oui le besoin est similaire de mon côté on s’est bien compris.

D’un point de vue design/logique c’est ultra compliqué pour l’agrégation car elle va considérer qu’il n’y a pas eu de valeurs sur un laps de temps.

Donc si changement d’état/valeur il faut d’abord insérer le state précédent juste avant(pour avoir une chronologie correcte. C’est un peut tricky mais ça peut le faire.

Ça se test assez facilement ( pour voir le comportement de l’agrégation et le résultat sur des graphes)

Exemple : temperature d’une pièce
A 8h => 19deg
A 9h => 19deg (je ne fais rien en db)
A 9h30 => 19.5 ( la j’ecrit en db l’ancienne valeur puis la nouvelle)

C’est ultra simpliste mais c’était pour illustrer.

Edit: je parle de la 2ème PR du coup :sweat_smile:

Je suis pas sûr qu’on parle de la même chose ou alors j’ai rien compris ?

Je pensais qu’on parlait d’avoir un paramètres « par device » de durée de rétention des données

Oui c est ce que je proposais dans mon autre post. Et c est ce que j’ai fait sur Netatmo pour le coup
Je pourrais meme vous montrer ce que ca donne sur l agrega pour le coup

Par features, et aussi de stocker des valeurs identiques quand tu as des devices verbeux.

1 « J'aime »

Bah les states c’est bien par feature, donc c’est dans la meme logique que çadu coup.
Pour une lampe philips hue par exemple, ca peut etre interessant de garder le ON/OFF, mais pour le coup les couleurs … pour ma part je m en fiche … non ?

@pierre-gilles, @AlexTrovato, @VonOx,
Je me permet de relancer le sujet le considérant comme possiblement critique sur certaines instance un peu lourdes.
Pour rappel les sujets du débat :

Si on peut définir la feuille de route au complet ainsi que valider un cahier des charges, je veux bien m’en occuper autant que possible et sortir un premier jet rapidement.

Pour rappel également, la proposition que je faisais (modifié selon la proposition de Pierre-Gilles dans l’ordre) pour entamer la discussion :

  • 1ère PR :

    • Côté front :
      • un champs de sélections sur chaque feature “Conserver l’historique des états de la feature” proposant les mêmes choix que dans l’onglet Paramètres/Systèmes,
      • Le champs est sélectionné d’origine sur la configuration générale de Gladys (sauf pour les caméras
    • Côté server :
      • La colonne « keep_history » est déjà présente donc rien à faire de ce côté
      • Soit :
        • on ajoute dans la table t_device_feature une colonne “delay_keep_history” d’origine sur la conf générale de Gladys ?
        • ou on modifie la colonne “keep_history” pour pouvoir y mettre directement la valeur ?
        • ou on passe par un parametre de device avec par exemple :
        {
          "name": "[FEATURE_SELECTOR]_HISTORY_IN_DAYS",
          "value": [Delay en jours]
        }
        
        si existant alors on tient compte de ce paramètre pour les values de la feature, sinon on prend le paramètre général de Gladys comme aujourd’hui.
      • revoir le destroy_states selon ce paramètre.
  • 2ème PR :

    • Côté front :
      • une case à cocher sur chaque feature “Enregistrer uniquement les changements d’états”,
      • cette case est décochée d’origine pour rester comme maintenant,
    • Côté server :
      • si la case “Enregistrer uniquement les changements d’états” est cochée, soit :

        • on ajoute dans la table t_device_feature une colonne “only_keep_value_changes” celle-ci est à 0 d’origine,
        • ou lorsque cochée on ajoute un paramètre dans device_param :
        {
          "name": "[FEATURE_SELECTOR]:only_keep_value_changes",
          "value": true
        }
        
      • si la valeur de ce paramètre est true et que la nouvelle valeur est la même que la précédente, on passe par un nouveau fichier server/lib/device/device.saveLastStateChanged.js pour enregistrer dans la table t_device_feature la saveLastStateChanged comme tu me l’avais demandé à l’époque du dev Netatmo :

Proposition de Code
const db = require('../../models');
const logger = require('../../utils/logger');
const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../utils/constants');

/**
 * @description Save new device feature state in DB.
 * @param {Object} deviceFeature - A DeviceFeature object.
 * @example
 * saveLastValueChanged({
 *   id: 'fc235c88-b10d-4706-8b59-fef92a7119b2',
 *   selector: 'my-light'
 * });
 */
async function saveLastStateChanged(deviceFeature) {
  // logger.debug(`device.saveLastStateChanged of deviceFeature ${deviceFeature.selector}`);
  const now = new Date();
  // save local state in RAM
  this.stateManager.setState('deviceFeature', deviceFeature.selector, {
    last_value_changed: now,
  });
  await db.sequelize.transaction(async (t) => {
    // update deviceFeature lastValue in DB
    await db.DeviceFeature.update(
      {
        last_value_changed: now,
      },
      {
        where: {
          id: deviceFeature.id,
        },
      },
      {
        transaction: t,
      },
    );
  });

  // send websocket event
  this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, {
    type: WEBSOCKET_MESSAGE_TYPES.DEVICE.NEW_STATE_NO_CHANGED,
    payload: {
      device_feature_selector: deviceFeature.selector,
      last_value_changed: now,
    },
  });
}

module.exports = {
  saveLastStateChanged,
};
  • Lorsque la valeur change de nouveau, on repasse par la fonction du fichier server/lib/device/device.saveState.js qu’on doit modifier pour réenregistrer, avant la nouvelle valeur, l’ancienne valeur déjà en base, pour avoir une courbe ajustée :
Proposition de Code
const db = require('../../models');
const logger = require('../../utils/logger');
const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../utils/constants');
const { BadParameters } = require('../../utils/coreErrors');

const DEFAULT_OPTIONS = {
  skip: 0,
  order_dir: 'DESC',
  order_by: 'created_at',
};

/**
 * @description Save new device feature state in DB.
 * @param {Object} deviceFeature - A DeviceFeature object.
 * @param {number} newValue - The new value of the deviceFeature to save.
 * @example
 * saveState({
 *   id: 'fc235c88-b10d-4706-8b59-fef92a7119b2',
 *   selector: 'my-light'
 * }, 12);
 */
async function saveState(deviceFeature, newValue) {
  if (Number.isNaN(newValue)) {
    throw new BadParameters(`device.saveState of NaN value on ${deviceFeature.selector}`);
  }
  const optionsWithDefault = Object.assign({}, DEFAULT_OPTIONS);

  // logger.debug(`device.saveState of deviceFeature ${deviceFeature.selector}`);
  const now = new Date();
  const previousDeviceFeature = this.stateManager.get('deviceFeature', deviceFeature.selector);
  const previousDeviceFeatureValue = previousDeviceFeature ? previousDeviceFeature.last_value : null;

  const previousDeviceFeatureLastValueChanged = previousDeviceFeature ? previousDeviceFeature.last_value_changed : null;

  const deviceFeaturesState = await db.DeviceFeatureState.findOne({
    attributes: ['device_feature_id', 'value', 'created_at'],
    order: [[optionsWithDefault.order_by, optionsWithDefault.order_dir]],
    where: {
      device_feature_id: deviceFeature.id,
    },
  });
  const previousDeviceFeatureStateLastValueChanged = deviceFeaturesState ? deviceFeaturesState.created_at : 0;

  // save local state in RAM
  this.stateManager.setState('deviceFeature', deviceFeature.selector, {
    last_value: newValue,
    last_value_changed: now,
  });
  // update deviceFeature lastValue in DB
  await db.DeviceFeature.update(
    {
      last_value: newValue,
      last_value_changed: now,
    },
    {
      where: {
        id: deviceFeature.id,
      },
    },
  );
  // if the deviceFeature should keep history, we save a new deviceFeatureState
  if (deviceFeature.keep_history) {
    // if the previous created deviceFeatureState is different of deviceFeature
    // last value changed, we save a new deviceFeatureState of old state
    if (previousDeviceFeatureLastValueChanged - previousDeviceFeatureStateLastValueChanged > 0) {
      await db.DeviceFeatureState.create({
        device_feature_id: deviceFeature.id,
        value: previousDeviceFeatureValue,
        created_at: previousDeviceFeatureLastValueChanged,
      });
    }
    await db.DeviceFeatureState.create({
      device_feature_id: deviceFeature.id,
      value: newValue,
    });
  }
  // });

  // send websocket event
  this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, {
    type: WEBSOCKET_MESSAGE_TYPES.DEVICE.NEW_STATE,
    payload: {
      device_feature_selector: deviceFeature.selector,
      last_value: newValue,
      last_value_changed: now,
    },
  });

  // check if there is a trigger matching
  this.eventManager.emit(EVENTS.TRIGGERS.CHECK, {
    type: EVENTS.DEVICE.NEW_STATE,
    device_feature: deviceFeature.selector,
    previous_value: previousDeviceFeatureValue,
    last_value: newValue,
    last_value_changed: now,
  });
}

module.exports = {
  saveState,
};

Exemple :

si on reçoit pour une puissance consommée
{
  value: 1014.7,
  created_at: 2022-04-19 04:20:13.277 +00:00
},
{
  value: 1014.9,
  created_at: 2022-04-19 04:30:13.277 +00:00
},
{
  value: 1014.9,
  created_at: 2022-04-19 04:40:13.277 +00:00
},
{
  value: 1014.9,
  created_at: 2022-04-19 04:50:13.277 +00:00
},
{
  value: 1014.9,
  created_at: 2022-04-19 05:00:13.277 +00:00
},
{
  value: 1014.9,
  created_at: 2022-04-19 05:10:13.277 +00:00
},
{
  value: 78.0,
  created_at: 2022-04-19 05:20:13.277 +00:00
}

On retrouvera en DB seulement

{
  value: 1014.7,
  created_at: 2022-04-19 04:20:13.277 +00:00
},
{
  value: 1014.9,
  created_at: 2022-04-19 04:30:13.277 +00:00
},
// ici on a pas sauvegardé les valeurs identiques
{
  value: 1014.9,
  created_at: 2022-04-19 05:10:13.277 +00:00
},
// Après avoir écrit la valeur ci-dessous, on a renvoyé
{
  value: 78.0,
  created_at: 2022-04-19 05:20:13.277 +00:00
}

Question : Est-ce utile ou indispensable de voir quelque chose pour les données agrégées ? Je m’explique : suite à lecture du post de lmilcent (cité précédemment concernant la rétention), lors de la 1ère PR il faut tenir compte de cela pour ne pas casser l’agrégation des données si le keep_history de la feature est réglé sur 1 jour. L’agrégation des 1 an devra tout de même conserver toutes ses données sauf si le keep_history est réglé à « OFF », n’est ce pas ??

En complément, est-il possible de proposer une alternative à « keep_history » qui correspond à l’historique complet des capteurs depuis le lancement de l’instance Gladys avec par exemple « keep aggregated history », qui correspond aux données aggrégées tous les jours, donc max 100 valeurs / jour.

Dans mon installation, avec une prise qui m’envoie les infos toutes les 3 secondes, ça permet de passer de 480 28800 messages par jour à 100, soit une sacré diminution des données !

A voir si la multitude d’option est vraiment une solution. Mais @lmilcent, on est d’accord que pouvoir choisir sur ces devices de n’enregistrer que les changements d’état résoudrait ce problème bien au-delà de cette diminution non ?

Pourquoi 480 ? ^^

Hum, je crois que travailler et répondre à un message sur le forum c’est pas la meilleur idée :joy:
Donc un message toute les 3 secondes, ça fait 20 message / minutes, soit 1200 messages / heures, soit 28 800 / jour.

Donc clairement, si je veux garder l’historique des valeurs, sans exploser le stockage, l’agrégation me semble plus qu’une bonne alternative.

Oui tout à fait, pour certains device, je me fout des valeurs identiques (pression atmosphérique, température, etc.) et c’est un vrai levier aussi ici pour économiser du stockage.
Attention cependant, c’est important de savoir que ma prise détecte toujours 200W d’utilisation instantanné, et je souhaite garder en mémoire les valeurs identiques dans certains cas. Il faut donc prévoir de pouvoir activer l’option « ne pas enregistrer les valeurs identiques » pour chaque device (voire feature).

1 « J'aime »

J’ai du mal à suivre

Quel cas ?

Si la valeur change c’est que juste avant tu étais à 200W donc on a la valeur

1 « J'aime »

Je pense que je n’avais pas bien compris l’idée, maintenant c’est plus clair merci :slight_smile:
Donc je confirme, ça fera déjà économiser un peu de stockage.

Dans le cas de ma prise connectée, elle peut renvoyer 201W puis 200W, puis 202W, donc l’aggrégation est aussi une solution viable et intéressante pour gagner en stockage, et donc en rapidité pour tout (gladys, I/O carte SD, etc).

1 « J'aime »

Hello !!

Bon la 1ère PR pour le Keep History est presque prête selon ce que nous avons dit hier. Simple… ou presque ^^
1 point à voir tout de même, j’ai fait la modif pour les intégrations dans lesquelles l’édition des devices est modifiable ainsi que pour mqtt qui est indépendante. Toutefois certaines intégrations (comme Philips Hue) ne sont pas modifiables. Est-ce que j’en profite pour ajouter cette page d’édition pour les intégration manquantes ?

Sinon ça donne ça pour mqtt :

1 « J'aime »

Nickel !

Juste le label est pas super explicite je trouve, je mettrais plutôt un « Garder l’historique des états », et rajouter en dessous un petite description comme dans la catégorie « Topic MQTT pour publier », pour expliquer ce que cette case fait.

Description:

Si vous activez cette option, Gladys gardera l’historique de tous les états que prend cette fonctionnalité, ce qui permet ensuite d’afficher des graphiques sur le tableau de bord.

Aussi, j’y pense maintenant mais c’est un truc qu’il va falloir faire, dans la tâche de clean qui passer pour nettoyer la DB, il faudra rajouter une tâche pour clean les device_feature_state + aggregate des feature dont le keep_history est passé à false (car tu peux activer le keep_history = false à posteriori, et donc il faut passer pour nettoyer)

2 « J'aime »

Merci pour cette réponse … instantanée ^^

Très bien. C’est fait, juste à voir si la description te convient ? J’ai mis la même phrase que dans les réglages système (Conserver plutôt que Garder et fonctionnalité à la place d’appareil du coup.)
image

Justement j’étais en train en attendant une réponse à la question … dont tu n’as pas répondu :slight_smile: :

Ah pardon ! Je pense pas que ce soit nécessaire pour ces intégrations pour l’instant, je pense que ces intégrations on pourrait juste mettre des valeurs par défaut qui font sens. Le Philips Hue n’est pas très verbose de toute façon…

1 « J'aime »

En effet je n’avais pas bien regardé, c’est déjà le cas sur Philips Hue notamment, les températures, couleur, brightness et autres ne sont pas en keep_history ^^

Bon j’ai fait un truc pour ça, tu me rediras !! Ca fonctionne très bien, mais pas sûr de la propreté du code …