@pierre-gilles, @AlexTrovato, @VonOx,
Ich erlaube mir, das Thema wieder aufzugreifen, da es für einige etwas schwergewichtige Instanzen möglicherweise kritisch sein könnte.
Zur Erinnerung die Themen der Debatte:
Falls wir die gesamte Roadmap definieren und eine Spezifikation validieren können, bin ich bereit, mich so weit wie möglich darum zu kümmern und einen ersten Entwurf schnell zu erstellen.
Zur Erinnerung auch mein Vorschlag (modifiziert nach dem Vorschlag von Pierre-Gilles in der Reihenfolge) zur Einleitung der Diskussion:
-
1. PR:
- Frontend:
- Ein Auswahlfeld für jedes Feature „Historische Zustände der Feature beibehalten“ mit denselben Optionen wie im Tab Einstellungen/Systeme,
- Das Feld ist standardmäßig auf der allgemeinen Gladys-Konfiguration ausgewählt (außer für Kameras)
- Server:
- Die Spalte « keep_history » ist bereits vorhanden, also nichts zu tun auf dieser Seite
- Entweder:
- Wir fügen in der Tabelle t_device_feature eine Spalte „delay_keep_history“ hinzu, die standardmäßig auf der allgemeinen Gladys-Konfiguration basiert?
- Oder wir ändern die Spalte „keep_history“, um direkt den Wert einzutragen?
- Oder wir gehen über einen Geräteparameter mit zum Beispiel:
Falls vorhanden, berücksichtigen wir diesen Parameter für die Werte des Features, andernfalls verwenden wir den allgemeinen Gladys-Parameter wie heute.{ "name": "[FEATURE_SELECTOR]_HISTORY_IN_DAYS", "value": [Verzögerung in Tagen] } - destroy_states entsprechend diesem Parameter überarbeiten.
- Frontend:
-
2. PR:
- Frontend:
- Ein Kontrollkästchen für jedes Feature „Nur Zustandsänderungen speichern“,
- Dieses Kästchen ist standardmäßig deaktiviert, um den aktuellen Zustand beizubehalten,
- Server:
-
Wenn das Kästchen „Nur Zustandsänderungen speichern“ aktiviert ist, entweder:
- Wir fügen in der Tabelle t_device_feature eine Spalte „only_keep_value_changes“ hinzu, die standardmäßig auf 0 gesetzt ist,
- Oder wir fügen bei Aktivierung einen Parameter in device_param hinzu:
{ "name": "[FEATURE_SELECTOR]:only_keep_value_changes", "value": true } -
Wenn der Wert dieses Parameters true ist und der neue Wert derselbe wie der vorherige ist, gehen wir über eine neue Datei server/lib/device/device.saveLastStateChanged.js, um in der Tabelle t_device_feature die
saveLastStateChangedzu speichern, wie du es mir damals während der Netatmo-Entwicklung vorgeschlagen hast:
-
- Frontend:
Codevorschlag
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,
};
- Wenn der Wert sich erneut ändert, gehen wir wieder über die Funktion der Datei server/lib/device/device.saveState.js, die wir ändern müssen, um vor dem neuen Wert den alten Wert, der bereits in der Datenbank gespeichert ist, erneut zu speichern, um eine angepasste Kurve zu erhalten:
Codevorschlag
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,
};
Beispiel:
wenn wir für einen verbrauchten Leistung erhalten
{
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
}
In der Datenbank wird man nur finden
{
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
},
// Hier haben wir die identischen Werte nicht gespeichert
{
value: 1014.9,
created_at: 2022-04-19 05:10:13.277 +00:00
},
// Nach dem Schreiben des folgenden Wertes wurde zurückgeschickt
{
value: 78.0,
created_at: 2022-04-19 05:20:13.277 +00:00
}
Frage: Ist es nützlich oder unerlässlich, etwas für die aggregierten Daten zu sehen? Ich erkläre: nach dem Lesen des Beitrags von lmilcent (zuvor zitiert bezüglich der Retention), muss man bei der ersten PR dies berücksichtigen, um die Aggregation der Daten nicht zu brechen, wenn der keep_history der Funktion auf 1 Tag eingestellt ist. Die Aggregation von 1 Jahr sollte trotzdem alle Daten behalten, außer wenn der keep_history auf „OFF“ eingestellt ist, oder nicht??