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

@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 ??